Compare commits

..

112 Commits

Author SHA1 Message Date
Ilya Fedin
8ed56bb4e4 Don't mess GTK scale factor with other scaling settings
Have this order for scaling settings:
1. devicePixelRatio
2. GTK
3. DPI
2021-01-23 21:55:33 +04:00
John Preston
3793f7c3c9 Update tg_owt commit in snap build. 2021-01-23 20:31:24 +04:00
John Preston
0d1b778612 Beta version 2.5.6: Fix build on macOS. 2021-01-23 16:14:37 +04:00
Ilya Fedin
b919a0627a Ensure GtkIntegration::load() is called only once 2021-01-23 16:14:22 +04:00
Ilya Fedin
6374d4eeda Some cosmetic changes in settigs setters 2021-01-23 16:14:22 +04:00
Ilya Fedin
3967052375 Get scale factor from GTK on Linux 2021-01-23 16:14:22 +04:00
Ilya Fedin
89ccc95023 Fix early return from Platform::ThirdParty::start on Linux 2021-01-23 16:14:22 +04:00
John Preston
24f2ca7443 Beta version 2.5.6.
- Press Up arrow to edit your last sent comment.
- Add more information to date tooltips
in Recent Actions and channel comments.
- Bug and crash fixes.
2021-01-22 19:00:11 +04:00
John Preston
f90e13f8b1 Fix crash after account reset after QR login. 2021-01-22 18:19:27 +04:00
John Preston
606f5377d5 Cherry-pick fix for Pulseaudio OpenAL backend. 2021-01-22 18:08:49 +04:00
John Preston
c698327b24 Update tg_owt commit in Snap. 2021-01-22 17:42:06 +04:00
Ilya Fedin
655731741c Add config for lock bot 2021-01-22 17:23:20 +04:00
Ilya Fedin
d5cdb5582b Add config for no-response bot 2021-01-22 17:23:20 +04:00
Ilya Fedin
5cb081ca9a Fix build without dbus 2021-01-22 17:22:37 +04:00
Ilya Fedin
f1e0b36f61 Use operator-> for tray icon biggest size 2021-01-22 17:22:37 +04:00
Ilya Fedin
ea9813825d Move EscapeShell to specific_linux 2021-01-22 17:22:37 +04:00
Ilya Fedin
36b6f70613 Get rid of unneeded includes in specific_linux 2021-01-22 17:22:37 +04:00
Ilya Fedin
5e60b87cf9 Remove platform_specific.h include from mainwindow.h
In order to avoid mass rebuilds on specific_*.h changing
2021-01-22 17:22:37 +04:00
Ilya Fedin
ada22ee6cc Split GTK integration into a singleton 2021-01-22 17:22:37 +04:00
Ilya Fedin
bb016e1489 Restore frameless hint on showing to workaround a bug in Qt 2021-01-22 17:12:53 +04:00
Ilya Fedin
b115ea74d0 Set config dir for OpenSSL and disable OpenSSL DSO
System-provided engines may crash bundled OpenSSL
2021-01-22 17:12:18 +04:00
Ilya Fedin
1008774aef Update vdpau to latest version 2021-01-22 17:12:18 +04:00
Ilya Fedin
73018ff958 Update libva to latest version 2021-01-22 17:12:18 +04:00
Ilya Fedin
e799fdaa3d Update wayland-protocols to latest version 2021-01-22 17:12:18 +04:00
Ilya Fedin
7656a546b0 Update libxkbcommon to latest version 2021-01-22 17:12:18 +04:00
Ilya Fedin
57f9ae4b2a Fix speed control support check 2021-01-22 17:10:22 +04:00
Ilya Fedin
cbdd86d398 Fix deadlock on OpenAL errors 2021-01-22 17:10:22 +04:00
Ilya Fedin
2fe2105a5f Don't add counter when icon theme has 'panel' icon
These icons should have a dot indicating unread messages, counter is redudant for them
2021-01-22 17:09:50 +04:00
Ilya Fedin
a986d7a3d6 Fix checking cover stream on seeking 2021-01-22 17:05:43 +04:00
Ilya Fedin
690c5df87c Format dbus errors logging 2021-01-22 17:02:50 +04:00
Ilya Fedin
1e2759840d Check _sniDBusProxy for nullptr before connecting to signals 2021-01-22 17:02:50 +04:00
Ilya Fedin
bad888496c Decrease some indentation in linux platform code 2021-01-22 17:02:50 +04:00
Ilya Fedin
4348ddf938 Adjust some constexprs in linux platform code 2021-01-22 17:02:50 +04:00
Ilya Fedin
894d6028bd Don't skip native notification toasts 2021-01-22 17:02:50 +04:00
Ilya Fedin
e8edbb16ae Make notification manager creation async 2021-01-22 17:02:50 +04:00
Ilya Fedin
a0a71687e7 Use QDBusPendingReply in GetServerInformation 2021-01-22 17:02:50 +04:00
Ilya Fedin
d042963a47 Make notification show method async 2021-01-22 17:02:50 +04:00
Ilya Fedin
64b12bde55 Allow qualified notification daemons by default on Linux 2021-01-22 17:02:50 +04:00
Ilya Fedin
49736cd879 Recreate notification manager on notification service owner change 2021-01-22 17:02:50 +04:00
the-vyld
e55581e0c9 fix typos in changelog.txt 2021-01-22 16:55:04 +04:00
John Preston
574d915c23 Fix build and tray icon menu on Windows. 2021-01-22 16:53:59 +04:00
23rd
2616659116 Added missed date detailed info in tooltips to admin log and sections. 2021-01-22 16:53:59 +04:00
23rd
3d1f21bd05 Added sent date info to tooltip of messages in admin log.
Fixed #5706.
2021-01-22 16:53:59 +04:00
23rd
dc631ef631 Added ability to see url of inline button in tooltip.
Fixed #5457.
2021-01-22 16:53:59 +04:00
23rd
5277080115 Fixed adding caption to grouped files.
Fixed #10192.
2021-01-22 16:53:59 +04:00
23rd
1ccfcc824c Updated code to be consistent with lib_ui. 2021-01-22 16:53:59 +04:00
23rd
97e8c0956f Moved all files related to menu to separate namespace. 2021-01-22 16:53:59 +04:00
23rd
03a7131a1a Replaced slots with lambdas to fill context menu in OverlayWidget. 2021-01-22 16:53:59 +04:00
23rd
2d906bddb2 Replaced raw PopupMenu pointer with unique_qptr in OverlayWidget. 2021-01-22 16:53:59 +04:00
23rd
dd7598a701 Replaced singleShot with InvokeQueued in OverlayWidget. 2021-01-22 16:53:59 +04:00
23rd
b6f17e1cea Replaced QTimer with base::Timer in OverlayWidget. 2021-01-22 16:53:59 +04:00
23rd
eb42a77eb7 Changed Up arrow shortcut for albums to edit item with caption.
Fixed #10134.
2021-01-22 16:53:59 +04:00
23rd
ad761011d6 Added ability to fetch song cover from external resource. 2021-01-22 16:53:59 +04:00
23rd
3fadf2ee54 Added Up arrow shortcut to edit comments. 2021-01-11 22:46:56 +03:00
23rd
15254599e2 Unified checking for editable message. 2021-01-11 22:46:56 +03:00
23rd
1607752cf9 Added ability to show song cover in inline results. 2021-01-11 22:46:56 +03:00
23rd
cf0cde6e83 Added ability to show song cover in EditCaptionBox and SendFilesBox. 2021-01-11 22:46:56 +03:00
23rd
8fffe7d128 Added ability to show song cover in HistoryView and Overview::Layout. 2021-01-11 22:46:45 +03:00
John Preston
a483eb98a1 Remove not-needed requests for file parts above real size. 2021-01-11 12:38:47 +04:00
John Preston
838a3b23c7 Beta version 2.5.5.
- Fix recording of audio in voice chats.
- Fix media viewer zoom and crashing.
2021-01-10 12:30:58 +04:00
23rd
a030911ad5 Fixed filling context menu in TabbedPanel between sections.
Fixed #10082.
2021-01-09 14:24:41 +03:00
John Preston
8ae1b10b91 Fix media viewer regression. 2021-01-09 13:55:55 +04:00
John Preston
adc8d6a6d1 Fix audio recording in voice chats. 2021-01-08 19:09:45 +04:00
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
170 changed files with 4556 additions and 2820 deletions

21
.github/lock.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
# Number of days of inactivity before a closed issue or pull request is locked
daysUntilLock: 45
# Skip issues and pull requests created before a given timestamp. Timestamp must
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
skipCreatedBefore: false
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
exemptLabels: []
# Label to add before locking, such as `outdated`. Set to `false` to disable
lockLabel: false
# Comment to post before locking. Set to `false` to disable
lockComment: >
This thread has been automatically locked since there has not been
any recent activity after it was closed. Please open a new issue for
related bugs.
# Assign `resolved` as the reason for locking. Set to `false` to disable
setLockReason: true

11
.github/no-response.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
# Number of days of inactivity before an Issue is closed for lack of response
daysUntilClose: 30
# Label requiring a response
responseRequiredLabel: waiting for answer
# Comment to post when closing an Issue for lack of response. Set to `false` to disable
closeComment: >
This issue has been automatically closed because there has been no response
to our request for more information from the original author. With only the
information that is currently in the issue, we don't have enough information
to take action. Please reach out if you have or find the answers we need so
that we can investigate further.

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

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

@@ -84,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)
@@ -812,8 +819,17 @@ PRIVATE
platform/linux/linux_desktop_environment.h
platform/linux/linux_gdk_helper.cpp
platform/linux/linux_gdk_helper.h
platform/linux/linux_libs.cpp
platform/linux/linux_libs.h
platform/linux/linux_gsd_media_keys.cpp
platform/linux/linux_gsd_media_keys.h
platform/linux/linux_gtk_file_dialog.cpp
platform/linux/linux_gtk_file_dialog.h
platform/linux/linux_gtk_integration_p.h
platform/linux/linux_gtk_integration.cpp
platform/linux/linux_gtk_integration.h
platform/linux/linux_notification_service_watcher.cpp
platform/linux/linux_notification_service_watcher.h
platform/linux/linux_open_with_dialog.cpp
platform/linux/linux_open_with_dialog.h
platform/linux/linux_wayland_integration.cpp
platform/linux/linux_wayland_integration.h
platform/linux/linux_xlib_helper.cpp
@@ -952,6 +968,8 @@ PRIVATE
storage/storage_account.h
storage/storage_cloud_blob.cpp
storage/storage_cloud_blob.h
storage/storage_cloud_song_cover.cpp
storage/storage_cloud_song_cover.h
storage/storage_domain.cpp
storage/storage_domain.h
storage/storage_facade.cpp
@@ -1101,11 +1119,46 @@ 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/linux_notification_service_watcher.cpp
platform/linux/linux_notification_service_watcher.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)
endif()
if (LINUX AND TDESKTOP_DISABLE_GTK_INTEGRATION)
remove_target_sources(Telegram ${src_loc}
platform/linux/linux_gdk_helper.cpp
platform/linux/linux_gdk_helper.h
platform/linux/linux_gtk_file_dialog.cpp
platform/linux/linux_gtk_file_dialog.h
platform/linux/linux_gtk_integration_p.h
platform/linux/linux_gtk_integration.cpp
platform/linux/linux_open_with_dialog.cpp
platform/linux/linux_open_with_dialog.h
platform/linux/linux_xlib_helper.cpp
platform/linux/linux_xlib_helper.h
)
nice_target_sources(Telegram ${src_loc}
PRIVATE
platform/linux/linux_gtk_integration_dummy.cpp
)
endif()
if (NOT DESKTOP_APP_USE_PACKAGED)
nice_target_sources(Telegram ${src_loc} PRIVATE platform/mac/mac_iconv_helper.c)
endif()

View File

@@ -1187,6 +1187,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_in_reply_to" = "In reply to";
"lng_edited" = "edited";
"lng_edited_date" = "Edited: {date}";
"lng_sent_date" = "Sent: {date}";
"lng_admin_badge" = "admin";
"lng_owner_badge" = "owner";
"lng_channel_badge" = "channel";

View File

@@ -9,7 +9,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="2.5.2.0" />
Version="2.5.6.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,2,0
PRODUCTVERSION 2,5,2,0
FILEVERSION 2,5,6,0
PRODUCTVERSION 2,5,6,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.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2020"
VALUE "FileVersion", "2.5.6.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.5.2.0"
VALUE "ProductVersion", "2.5.6.0"
END
END
BLOCK "VarFileInfo"

View File

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

View File

@@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "history/history_drag_area.h"
#include "history/history_item.h"
#include "history/view/media/history_view_document.h" // DrawThumbnailAsSongCover
#include "platform/platform_specific.h"
#include "lang/lang_keys.h"
#include "media/streaming/media_streaming_instance.h"
@@ -171,7 +172,9 @@ EditCaptionBox::EditCaptionBox(
_thumbw = 0;
_thumbnailImageLoaded = true;
} else {
const auto thumbSize = st::msgFileThumbLayout.thumbSize;
const auto thumbSize = (!media->document()->isSongWithCover()
? st::msgFileThumbLayout
: st::msgFileLayout).thumbSize;
const auto tw = dimensions.width(), th = dimensions.height();
if (tw > th) {
_thumbw = (tw * thumbSize) / th;
@@ -183,19 +186,31 @@ EditCaptionBox::EditCaptionBox(
if (!image) {
return;
}
const auto options = Images::Option::Smooth
| Images::Option::RoundedSmall
| Images::Option::RoundedTopLeft
| Images::Option::RoundedTopRight
| Images::Option::RoundedBottomLeft
| Images::Option::RoundedBottomRight;
_thumb = App::pixmapFromImageInPlace(Images::prepare(
image->original(),
_thumbw * cIntRetinaFactor(),
0,
options,
thumbSize,
thumbSize));
if (media->document()->isSongWithCover()) {
const auto size = QSize(thumbSize, thumbSize);
_thumb = QPixmap(size);
_thumb.fill(Qt::transparent);
Painter p(&_thumb);
HistoryView::DrawThumbnailAsSongCover(
p,
_documentMedia,
QRect(QPoint(), size));
} else {
const auto options = Images::Option::Smooth
| Images::Option::RoundedSmall
| Images::Option::RoundedTopLeft
| Images::Option::RoundedTopRight
| Images::Option::RoundedBottomLeft
| Images::Option::RoundedBottomRight;
_thumb = App::pixmapFromImageInPlace(Images::prepare(
image->original(),
_thumbw * cIntRetinaFactor(),
0,
options,
thumbSize,
thumbSize));
}
_thumbnailImageLoaded = true;
};
_refreshThumbnail();
@@ -539,6 +554,14 @@ void EditCaptionBox::updateEditPreview() {
song->title,
song->performer);
_isAudio = true;
if (auto cover = song->cover; !cover.isNull()) {
_thumb = Ui::PrepareSongCoverForThumbnail(
cover,
st::msgFileLayout.thumbSize);
_thumbw = _thumb.width() / cIntRetinaFactor();
_thumbh = _thumb.height() / cIntRetinaFactor();
}
}
const auto getExt = [&] {
@@ -810,15 +833,21 @@ void EditCaptionBox::setupDragArea() {
areas.photo->setDroppedCallback(droppedCallback(true));
}
bool EditCaptionBox::isThumbedLayout() const {
return (_thumbw && !_isAudio);
}
void EditCaptionBox::updateBoxSize() {
auto newHeight = st::boxPhotoPadding.top() + st::boxPhotoCaptionSkip + _field->height() + errorTopSkip() + st::normalFont->height;
if (_photo) {
newHeight += _wayWrap->height() / 2;
}
const auto &st = _thumbw ? st::msgFileThumbLayout : st::msgFileLayout;
const auto &st = isThumbedLayout()
? st::msgFileThumbLayout
: st::msgFileLayout;
if (_photo || _animated) {
newHeight += std::max(_thumbh, _gifh);
} else if (_thumbw || _doc) {
} else if (isThumbedLayout() || _doc) {
newHeight += 0 + st.thumbSize + 0;
} else {
newHeight += st::boxTitleFont->height;
@@ -902,7 +931,9 @@ void EditCaptionBox::paintEvent(QPaintEvent *e) {
icon->paintInCenter(p, inner);
}
} else if (_doc) {
const auto &st = _thumbw ? st::msgFileThumbLayout : st::msgFileLayout;
const auto &st = isThumbedLayout()
? st::msgFileThumbLayout
: st::msgFileLayout;
const auto w = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right();
const auto h = 0 + st.thumbSize + 0;
const auto nameleft = 0 + st.thumbSize + st.padding.right();
@@ -918,18 +949,24 @@ void EditCaptionBox::paintEvent(QPaintEvent *e) {
// Ui::FillRoundCorner(p, x, y, w, h, st::msgInBg, Ui::MessageInCorners, &st::msgInShadow);
const auto rthumb = style::rtlrect(x + 0, y + 0, st.thumbSize, st.thumbSize, width());
if (_thumbw) {
if (isThumbedLayout()) {
p.drawPixmap(rthumb.topLeft(), _thumb);
} else {
p.setPen(Qt::NoPen);
p.setBrush(st::msgFileInBg);
{
if (_isAudio && _thumbw) {
p.drawPixmap(rthumb.topLeft(), _thumb);
} else {
p.setBrush(st::msgFileInBg);
PainterHighQualityEnabler hq(p);
p.drawEllipse(rthumb);
}
const auto icon = &(_isAudio ? st::historyFileInPlay : _isImage ? st::historyFileInImage : st::historyFileInDocument);
const auto icon = &(_isAudio
? st::historyFileInPlay
: _isImage
? st::historyFileInImage
: st::historyFileInDocument);
icon->paintInCenter(p, rthumb);
}
p.setFont(st::semiboldFont);

View File

@@ -96,6 +96,8 @@ private:
void createEditMediaButton();
bool setPreparedList(Ui::PreparedList &&list);
bool isThumbedLayout() const;
inline QString getNewMediaPath() {
return _preparedList.files.empty()
? QString()

View File

@@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "history/history.h"
#include "dialogs/dialogs_main_list.h"
#include "window/window_session_controller.h" // onShowAddContact()
#include "window/window_session_controller.h" // showAddContact()
#include "facades.h"
#include "styles/style_boxes.h"
#include "styles/style_profile.h"
@@ -112,7 +112,7 @@ object_ptr<Ui::BoxContent> PrepareContactsBox(
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
box->addLeftButton(
tr::lng_profile_add_contact(),
[=] { controller->widget()->onShowAddContact(); });
[=] { controller->widget()->showAddContact(); });
};
return Box<PeerListBox>(
std::make_unique<ContactsBoxController>(

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

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

@@ -273,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;
@@ -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,10 +746,14 @@ 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()) {
@@ -767,6 +776,14 @@ void MembersController::updateRow(
}
}
delegate()->peerListRefreshRows();
countChange = 1;
}
if (countChange) {
const auto fullCountMin = _fullCountMin.current() + countChange;
if (_fullCountMax.current() < fullCountMin) {
_fullCountMax = fullCountMin;
}
_fullCountMin = fullCountMin;
}
}
@@ -879,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();
}
@@ -898,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);
@@ -912,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;
@@ -927,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;
}
}
}

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

@@ -1138,7 +1138,7 @@ void FieldAutocomplete::Inner::contextMenuEvent(QContextMenuEvent *e) {
SendMenu::DefaultSilentCallback(send),
SendMenu::DefaultScheduleCallback(this, type, send));
if (!_menu->actions().empty()) {
if (!_menu->empty()) {
_menu->popup(QCursor::pos());
}
}

View File

@@ -594,7 +594,7 @@ bool TabbedSelector::preventAutoHide() const {
}
bool TabbedSelector::hasMenu() const {
return (_menu && !_menu->actions().empty());
return (_menu && !_menu->empty());
}
QImage TabbedSelector::grabForAnimation() {
@@ -877,18 +877,22 @@ void TabbedSelector::scrollToY(int y) {
}
}
void TabbedSelector::contextMenuEvent(QContextMenuEvent *e) {
void TabbedSelector::showMenuWithType(SendMenu::Type type) {
_menu = base::make_unique_q<Ui::PopupMenu>(this);
const auto type = _sendMenuType
? _sendMenuType()
: SendMenu::Type::Disabled;
currentTab()->widget()->fillContextMenu(_menu, type);
if (!_menu->actions().empty()) {
if (!_menu->empty()) {
_menu->popup(QCursor::pos());
}
}
rpl::producer<> TabbedSelector::contextMenuRequested() const {
return events(
) | rpl::filter([=](not_null<QEvent*> e) {
return e->type() == QEvent::ContextMenu;
}) | rpl::to_empty;
}
TabbedSelector::Inner::Inner(
QWidget *parent,
not_null<Window::SessionController*> controller)

View File

@@ -82,6 +82,7 @@ public:
rpl::producer<> cancelled() const;
rpl::producer<> checkForHide() const;
rpl::producer<> slideFinished() const;
rpl::producer<> contextMenuRequested() const;
void setRoundRadius(int radius);
void refreshStickers();
@@ -109,9 +110,7 @@ public:
_beforeHidingCallback = std::move(callback);
}
void setSendMenuType(Fn<SendMenu::Type()> &&callback) {
_sendMenuType = std::move(callback);
}
void showMenuWithType(SendMenu::Type type);
// Float player interface.
bool floatPlayerHandleWheelEvent(QEvent *e);
@@ -127,7 +126,6 @@ public:
protected:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void contextMenuEvent(QContextMenuEvent *e) override;
private:
class Tab {
@@ -228,8 +226,6 @@ private:
Fn<void(SelectorTab)> _afterShownCallback;
Fn<void(SelectorTab)> _beforeHidingCallback;
Fn<SendMenu::Type()> _sendMenuType;
rpl::event_stream<> _showRequests;
rpl::event_stream<> _slideFinished;

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"
},
{
@@ -82,6 +82,37 @@ std::map<int, const char*> BetaLogs() {
"- 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"
},
{
2005005,
"- Fix recording of audio in voice chats.\n"
"- Fix media viewer zoom and crashing.\n"
},
{
2005006,
"- Press Up arrow to edit your last sent comment.\n"
"- Add more information to date tooltips "
"in Recent Actions and channel comments.\n"
"- Bug and crash fixes.\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

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/sandbox.h"
#include "base/platform/base_platform_info.h"
#include "platform/platform_specific.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "storage/localstorage.h"

View File

@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/application.h"
#include "media/player/media_player_instance.h"
#include "base/platform/base_platform_info.h"
#include "platform/platform_specific.h"
#include "base/parse_helper.h"
#include "facades.h"

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 = 2005002;
constexpr auto AppVersionStr = "2.5.2";
constexpr auto AppVersion = 2005006;
constexpr auto AppVersionStr = "2.5.6";
constexpr auto AppBetaVersion = true;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_gif.h"
#include "window/window_session_controller.h"
#include "storage/cache/storage_cache_database.h"
#include "storage/storage_cloud_song_cover.h"
#include "boxes/confirm_box.h"
#include "ui/image/image.h"
#include "ui/text/text_utilities.h"
@@ -540,6 +541,13 @@ void DocumentData::setattributes(
songData->duration = data.vduration().v;
songData->title = qs(data.vtitle().value_or_empty());
songData->performer = qs(data.vperformer().value_or_empty());
if (!hasThumbnail()
&& !songData->title.isEmpty()
&& !songData->performer.isEmpty()) {
Storage::CloudSongCover::LoadThumbnailFromExternal(this);
}
}
}, [&](const MTPDdocumentAttributeFilename &data) {
_filename = qs(data.vfile_name());
@@ -1488,6 +1496,10 @@ bool DocumentData::isSong() const {
return (type == SongDocument);
}
bool DocumentData::isSongWithCover() const {
return isSong() && hasThumbnail();
}
bool DocumentData::isAudioFile() const {
if (isVoiceMessage()) {
return false;

View File

@@ -139,6 +139,7 @@ public:
[[nodiscard]] bool isVoiceMessage() const;
[[nodiscard]] bool isVideoMessage() const;
[[nodiscard]] bool isSong() const;
[[nodiscard]] bool isSongWithCover() const;
[[nodiscard]] bool isAudioFile() const;
[[nodiscard]] bool isVideoFile() const;
[[nodiscard]] bool isAnimation() const;

View File

@@ -145,4 +145,20 @@ void Groups::refreshViews(const HistoryItemsList &items) {
}
}
not_null<HistoryItem*> Groups::findItemToEdit(
not_null<HistoryItem*> item) const {
const auto group = find(item);
if (!group) {
return item;
}
const auto &list = group->items;
const auto it = ranges::find_if(
list,
ranges::not_fn(&HistoryItem::emptyText));
if (it == end(list)) {
return list.front();
}
return (*it);
}
} // namespace Data

View File

@@ -31,6 +31,8 @@ public:
const Group *find(not_null<HistoryItem*> item) const;
not_null<HistoryItem*> findItemToEdit(not_null<HistoryItem*> item) const;
private:
HistoryItemsList::const_iterator findPositionForItem(
const HistoryItemsList &group,

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_replies_list.h"
#include "base/unixtime.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/history_service.h"
@@ -626,4 +627,22 @@ bool RepliesList::processMessagesIsEmpty(const MTPmessages_Messages &result) {
return (list.size() == skipped);
}
HistoryItem *RepliesList::lastEditableMessage() {
const auto message = [&](MsgId msgId) {
return _history->owner().message(_history->channelId(), msgId);
};
const auto now = base::unixtime::now();
auto proj = [&](MsgId msgId) {
if (const auto item = message(msgId)) {
return item->allowsEdit(now);
}
return false;
};
const auto it = ranges::find_if(_list, std::move(proj));
return (it == end(_list))
? nullptr
: _history->owner().groups().findItemToEdit(message(*it)).get();
}
} // namespace Data

View File

@@ -31,6 +31,8 @@ public:
[[nodiscard]] rpl::producer<int> fullCount() const;
[[nodiscard]] HistoryItem *lastEditableMessage();
private:
struct Viewer;

View File

@@ -543,7 +543,8 @@ int32 ScheduledMessages::countListHash(const List &list) const {
return HashFinalize(hash);
}
HistoryItem *ScheduledMessages::lastSentMessage(not_null<History*> history) {
HistoryItem *ScheduledMessages::lastEditableMessage(
not_null<History*> history) {
const auto i = _data.find(history);
if (i == end(_data)) {
return nullptr;
@@ -552,10 +553,16 @@ HistoryItem *ScheduledMessages::lastSentMessage(not_null<History*> history) {
sort(list);
const auto items = ranges::view::reverse(list.items);
const auto it = ranges::find_if(
items,
&HistoryItem::canBeEditedFromHistory);
return (it == end(items)) ? nullptr : (*it).get();
const auto now = base::unixtime::now();
auto proj = [&](const OwnedItem &item) {
return item->allowsEdit(now);
};
const auto it = ranges::find_if(items, std::move(proj));
return (it == end(items))
? nullptr
: history->owner().groups().findItemToEdit((*it).get()).get();
}
} // namespace Data

View File

@@ -32,7 +32,8 @@ public:
[[nodiscard]] HistoryItem *lookupItem(PeerId peer, MsgId msg) const;
[[nodiscard]] HistoryItem *lookupItem(FullMsgId itemId) const;
[[nodiscard]] int count(not_null<History*> history) const;
[[nodiscard]] HistoryItem *lastSentMessage(not_null<History*> history);
[[nodiscard]] HistoryItem *lastEditableMessage(
not_null<History*> history);
void checkEntitiesAndUpdate(const MTPDmessage &data);
void apply(const MTPDupdateNewScheduledMessage &update);

View File

@@ -1841,7 +1841,7 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) {
selectByMouse(globalPosition);
}
});
if (_menu->actions().empty()) {
if (_menu->empty()) {
_menu = nullptr;
} else {
_menu->popup(e->globalPos());
@@ -2281,7 +2281,7 @@ void InnerWidget::refreshEmptyLabel() {
resizeEmptyLabel();
_empty->setClickHandlerFilter([=](const auto &...) {
if (_emptyState == EmptyState::NoContacts) {
App::wnd()->onShowAddContact();
App::wnd()->showAddContact();
} else if (_emptyState == EmptyState::EmptyFolder) {
editOpenedFilter();
}

View File

@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/message_field.h"
#include "boxes/sticker_set_box.h"
#include "base/platform/base_platform_info.h"
#include "base/unixtime.h"
#include "mainwindow.h"
#include "mainwidget.h"
#include "core/application.h"
@@ -509,8 +510,17 @@ QString InnerWidget::tooltipText() const {
if (_mouseCursorState == CursorState::Date
&& _mouseAction == MouseAction::None) {
if (const auto view = App::hoveredItem()) {
auto dateText = view->dateTime().toString(
QLocale::system().dateTimeFormat(QLocale::LongFormat));
const auto format = QLocale::system().dateTimeFormat(
QLocale::LongFormat);
auto dateText = HistoryView::DateTooltipText(view);
const auto sentIt = _itemDates.find(view->data());
if (sentIt != end(_itemDates)) {
dateText += '\n' + tr::lng_sent_date(
tr::now,
lt_date,
base::unixtime::parse(sentIt->second).toString(format));
}
return dateText;
}
} else if (_mouseCursorState == CursorState::Forwarded
@@ -556,7 +566,7 @@ bool InnerWidget::elementUnderCursor(
}
crl::time InnerWidget::elementHighlightTime(
not_null<const HistoryView::Element*> element) {
not_null<const HistoryItem*> item) {
return crl::time(0);
}
@@ -722,7 +732,10 @@ void InnerWidget::addEvents(Direction direction, const QVector<MTPChannelAdminLo
}
auto count = 0;
const auto addOne = [&](OwnedItem item) {
const auto addOne = [&](OwnedItem item, TimeId sentDate) {
if (sentDate) {
_itemDates.emplace(item->data(), sentDate);
}
_eventIds.emplace(id);
_itemsByData.emplace(item->data(), item.get());
addToItems.push_back(std::move(item));
@@ -1177,7 +1190,7 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}
}
if (_menu->actions().empty()) {
if (_menu->empty()) {
_menu = nullptr;
} else {
_menu->popup(e->globalPos());

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,
@@ -242,6 +242,7 @@ private:
std::vector<OwnedItem> _items;
std::set<uint64> _eventIds;
std::map<not_null<const HistoryItem*>, not_null<Element*>> _itemsByData;
base::flat_map<not_null<const HistoryItem*>, TimeId> _itemDates;
base::flat_set<FullMsgId> _animatedStickersPlayed;
base::flat_map<
not_null<PeerData*>,

View File

@@ -45,6 +45,16 @@ TextWithEntities PrepareText(const QString &value, const QString &emptyValue) {
return result;
}
TimeId ExtractSentDate(const MTPMessage &message) {
return message.match([&](const MTPDmessageEmpty &) {
return 0;
}, [&](const MTPDmessageService &data) {
return data.vdate().v;
}, [&](const MTPDmessage &data) {
return data.vdate().v;
});
}
MTPMessage PrepareLogMessage(
const MTPMessage &message,
MsgId newId,
@@ -380,7 +390,7 @@ void GenerateItems(
not_null<HistoryView::ElementDelegate*> delegate,
not_null<History*> history,
const MTPDchannelAdminLogEvent &event,
Fn<void(OwnedItem item)> callback) {
Fn<void(OwnedItem item, TimeId sentDate)> callback) {
Expects(history->peer->isChannel());
const auto session = &history->session();
@@ -389,8 +399,10 @@ void GenerateItems(
const auto channel = history->peer->asChannel();
const auto &action = event.vaction();
const auto date = event.vdate().v;
const auto addPart = [&](not_null<HistoryItem*> item) {
return callback(OwnedItem(delegate, item));
const auto addPart = [&](
not_null<HistoryItem*> item,
TimeId sentDate = 0) {
return callback(OwnedItem(delegate, item), sentDate);
};
using Flag = MTPDmessage::Flag;
@@ -545,13 +557,15 @@ void GenerateItems(
addSimpleServiceMessage(text);
auto detachExistingItem = false;
addPart(history->createItem(
PrepareLogMessage(
action.vmessage(),
history->nextNonHistoryEntryId(),
date),
MTPDmessage_ClientFlag::f_admin_log_entry,
detachExistingItem));
addPart(
history->createItem(
PrepareLogMessage(
action.vmessage(),
history->nextNonHistoryEntryId(),
date),
MTPDmessage_ClientFlag::f_admin_log_entry,
detachExistingItem),
ExtractSentDate(action.vmessage()));
}, [&](const auto &) {
auto text = tr::lng_admin_log_unpinned_message(tr::now, lt_from, fromLinkText);
addSimpleServiceMessage(text);
@@ -598,10 +612,15 @@ void GenerateItems(
addSimpleServiceMessage(text);
auto detachExistingItem = false;
addPart(history->createItem(
PrepareLogMessage(action.vmessage(), history->nextNonHistoryEntryId(), date),
MTPDmessage_ClientFlag::f_admin_log_entry,
detachExistingItem));
addPart(
history->createItem(
PrepareLogMessage(
action.vmessage(),
history->nextNonHistoryEntryId(),
date),
MTPDmessage_ClientFlag::f_admin_log_entry,
detachExistingItem),
ExtractSentDate(action.vmessage()));
};
auto createParticipantJoin = [&]() {
@@ -740,10 +759,15 @@ void GenerateItems(
addSimpleServiceMessage(text);
auto detachExistingItem = false;
addPart(history->createItem(
PrepareLogMessage(action.vmessage(), history->nextNonHistoryEntryId(), date),
MTPDmessage_ClientFlag::f_admin_log_entry,
detachExistingItem));
addPart(
history->createItem(
PrepareLogMessage(
action.vmessage(),
history->nextNonHistoryEntryId(),
date),
MTPDmessage_ClientFlag::f_admin_log_entry,
detachExistingItem),
ExtractSentDate(action.vmessage()));
};
auto createChangeLinkedChat = [&](const MTPDchannelAdminLogEventActionChangeLinkedChat &action) {
@@ -838,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_muted_participant(
tr::now,
lt_from,
@@ -862,7 +886,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

@@ -22,7 +22,7 @@ void GenerateItems(
not_null<HistoryView::ElementDelegate*> delegate,
not_null<History*> history,
const MTPDchannelAdminLogEvent &event,
Fn<void(OwnedItem item)> callback);
Fn<void(OwnedItem item, TimeId sentDate)> callback);
// Smart pointer wrapper for HistoryItem* that destroys the owned item.
class OwnedItem {

View File

@@ -2704,15 +2704,16 @@ MsgId History::msgIdForRead() const {
: result;
}
HistoryItem *History::lastSentMessage() const {
HistoryItem *History::lastEditableMessage() const {
if (!loadedAtBottom()) {
return nullptr;
}
const auto now = base::unixtime::now();
for (const auto &block : ranges::view::reverse(blocks)) {
for (const auto &message : ranges::view::reverse(block->messages)) {
const auto item = message->data();
if (item->canBeEditedFromHistory()) {
return item;
if (item->allowsEdit(now)) {
return owner().groups().findItemToEdit(item);
}
}
}

View File

@@ -259,7 +259,7 @@ public:
MsgId minMsgId() const;
MsgId maxMsgId() const;
MsgId msgIdForRead() const;
HistoryItem *lastSentMessage() const;
HistoryItem *lastEditableMessage() const;
void resizeToWidth(int newWidth);
void forceFullResize();

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), [=] {
@@ -1858,7 +1851,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}
}
if (_menu->actions().empty()) {
if (_menu->empty()) {
_menu = nullptr;
} else {
_menu->popup(e->globalPos());
@@ -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) {
@@ -3320,40 +3313,7 @@ QString HistoryInner::tooltipText() const {
if (_mouseCursorState == CursorState::Date
&& _mouseAction == MouseAction::None) {
if (const auto view = App::hoveredItem()) {
auto dateText = view->dateTime().toString(
QLocale::system().dateTimeFormat(QLocale::LongFormat));
if (const auto editedDate = view->displayedEditDate()) {
dateText += '\n' + tr::lng_edited_date(
tr::now,
lt_date,
base::unixtime::parse(editedDate).toString(
QLocale::system().dateTimeFormat(
QLocale::LongFormat)));
}
if (const auto forwarded = view->data()->Get<HistoryMessageForwarded>()) {
dateText += '\n' + tr::lng_forwarded_date(
tr::now,
lt_date,
base::unixtime::parse(forwarded->originalDate).toString(
QLocale::system().dateTimeFormat(
QLocale::LongFormat)));
if (const auto media = view->media()) {
if (media->hidesForwardedInfo()) {
dateText += "\n" + tr::lng_forwarded(
tr::now,
lt_user,
(forwarded->originalSender
? forwarded->originalSender->shortName()
: forwarded->hiddenSenderInfo->firstName));
}
}
}
if (const auto msgsigned = view->data()->Get<HistoryMessageSigned>()) {
if (msgsigned->isElided && !msgsigned->isAnonymousRank) {
dateText += '\n' + tr::lng_signed_author(tr::now, lt_user, msgsigned->author);
}
}
return dateText;
return HistoryView::DateTooltipText(view);
}
} else if (_mouseCursorState == CursorState::Forwarded
&& _mouseAction == MouseAction::None) {
@@ -3421,8 +3381,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

@@ -762,27 +762,6 @@ bool HistoryItem::canUpdateDate() const {
return isScheduled();
}
bool HistoryItem::canBeEditedFromHistory() const {
// Skip if message is editing media.
if (isEditingMedia()) {
return false;
}
// Skip if message is video message or sticker.
if (const auto m = media()) {
// Skip only if media is not webpage.
if (!m->webpage() && !m->allowsEditCaption()) {
return false;
}
}
if ((IsServerMsgId(id) || isScheduled())
&& !serviceMsg()
&& (out() || history()->peer->isSelf())
&& !Has<HistoryMessageForwarded>()) {
return true;
}
return false;
}
void HistoryItem::sendFailed() {
Expects(_clientFlags & MTPDmessage_ClientFlag::f_sending);
Expects(!(_clientFlags & MTPDmessage_ClientFlag::f_failed));

View File

@@ -390,8 +390,6 @@ public:
void updateDate(TimeId newDate);
[[nodiscard]] bool canUpdateDate() const;
[[nodiscard]] bool canBeEditedFromHistory() const;
virtual ~HistoryItem();
MsgId id;

View File

@@ -413,23 +413,13 @@ ReplyMarkupClickHandler::ReplyMarkupClickHandler(
// Copy to clipboard support.
QString ReplyMarkupClickHandler::copyToClipboardText() const {
if (const auto button = getButton()) {
using Type = HistoryMessageMarkupButton::Type;
if (button->type == Type::Url || button->type == Type::Auth) {
return QString::fromUtf8(button->data);
}
}
return QString();
const auto button = getUrlButton();
return button ? QString::fromUtf8(button->data) : QString();
}
QString ReplyMarkupClickHandler::copyToClipboardContextItemText() const {
if (const auto button = getButton()) {
using Type = HistoryMessageMarkupButton::Type;
if (button->type == Type::Url || button->type == Type::Auth) {
return tr::lng_context_copy_link(tr::now);
}
}
return QString();
const auto button = getUrlButton();
return button ? tr::lng_context_copy_link(tr::now) : QString();
}
// Finds the corresponding button in the items markup struct.
@@ -440,6 +430,17 @@ const HistoryMessageMarkupButton *ReplyMarkupClickHandler::getButton() const {
return HistoryMessageMarkupButton::Get(_owner, _itemId, _row, _column);
}
auto ReplyMarkupClickHandler::getUrlButton() const
-> const HistoryMessageMarkupButton* {
if (const auto button = getButton()) {
using Type = HistoryMessageMarkupButton::Type;
if (button->type == Type::Url || button->type == Type::Auth) {
return button;
}
}
return nullptr;
}
void ReplyMarkupClickHandler::onClickImpl() const {
if (const auto item = _owner->message(_itemId)) {
App::activateBotCommand(item, _row, _column);
@@ -454,6 +455,19 @@ QString ReplyMarkupClickHandler::buttonText() const {
return QString();
}
QString ReplyMarkupClickHandler::tooltip() const {
const auto button = getUrlButton();
const auto url = button ? QString::fromUtf8(button->data) : QString();
const auto text = _fullDisplayed ? QString() : buttonText();
if (!url.isEmpty() && !text.isEmpty()) {
return QString("%1\n\n%2").arg(text).arg(url);
} else if (url.isEmpty() != text.isEmpty()) {
return text + url;
} else {
return QString();
}
}
ReplyKeyboard::Button::Button() = default;
ReplyKeyboard::Button::Button(Button &&other) = default;
ReplyKeyboard::Button &ReplyKeyboard::Button::operator=(

View File

@@ -251,9 +251,7 @@ public:
int column,
FullMsgId context);
QString tooltip() const override {
return _fullDisplayed ? QString() : buttonText();
}
QString tooltip() const override;
void setFullDisplayed(bool full) {
_fullDisplayed = full;
@@ -269,6 +267,8 @@ public:
// than the one was used when constructing the handler, but not a big deal.
const HistoryMessageMarkupButton *getButton() const;
const HistoryMessageMarkupButton *getUrlButton() const;
// We hold only FullMsgId, not HistoryItem*, because all click handlers
// are activated async and the item may be already destroyed.
void setMessageId(const FullMsgId &msgId) {

View File

@@ -846,6 +846,11 @@ void HistoryWidget::initTabbedSelector() {
return base::EventFilterResult::Continue;
});
auto filter = rpl::filter([=] {
return !isHidden();
});
using Selector = TabbedSelector;
selector->emojiChosen(
) | rpl::filter([=] {
return !isHidden() && !_field->isHidden();
@@ -854,27 +859,24 @@ void HistoryWidget::initTabbedSelector() {
}, lifetime());
selector->fileChosen(
) | rpl::filter([=] {
return !isHidden();
}) | rpl::start_with_next([=](TabbedSelector::FileChosen data) {
) | filter | rpl::start_with_next([=](Selector::FileChosen data) {
sendExistingDocument(data.document, data.options);
}, lifetime());
selector->photoChosen(
) | rpl::filter([=] {
return !isHidden();
}) | rpl::start_with_next([=](TabbedSelector::PhotoChosen data) {
) | filter | rpl::start_with_next([=](Selector::PhotoChosen data) {
sendExistingPhoto(data.photo, data.options);
}, lifetime());
selector->inlineResultChosen(
) | rpl::filter([=] {
return !isHidden();
}) | rpl::start_with_next([=](TabbedSelector::InlineChosen data) {
) | filter | rpl::start_with_next([=](Selector::InlineChosen data) {
sendInlineResult(data);
}, lifetime());
selector->setSendMenuType([=] { return sendMenuType(); });
selector->contextMenuRequested(
) | filter | rpl::start_with_next([=] {
selector->showMenuWithType(sendMenuType());
}, lifetime());
}
void HistoryWidget::supportInitAutocomplete() {
@@ -1045,11 +1047,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 +1093,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 +1102,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 +1663,7 @@ void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
_editMsgId = 0;
_replyToId = readyToForward() ? 0 : _history->localDraft()->msgId;
}
updateCmdStartShown();
updateControlsVisibility();
updateControlsGeometry();
refreshTopBarActiveChat();
@@ -2228,11 +2231,7 @@ void HistoryWidget::updateControlsVisibility() {
_botCommandStart->hide();
} else {
_botKeyboardShow->hide();
if (_cmdStartShown) {
_botCommandStart->show();
} else {
_botCommandStart->hide();
}
_botCommandStart->setVisible(_cmdStartShown);
}
}
_attachToggle->show();
@@ -3738,7 +3737,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 +4080,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 +4844,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) {
_tabbedSelectorToggle->show();
_botKeyboardHide->hide();
_botKeyboardShow->hide();
_botCommandStart->show();
_botCommandStart->setVisible(!_editMsgId);
}
_field->setMaxHeight(computeMaxFieldHeight());
_kbShown = false;
@@ -5023,10 +5022,9 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) {
_scroll->keyPressEvent(e);
} else if (e->key() == Qt::Key_Up && !commonModifiers) {
const auto item = _history
? _history->lastSentMessage()
? _history->lastEditableMessage()
: nullptr;
if (item
&& item->allowsEdit(base::unixtime::now())
&& _field->empty()
&& !_editMsgId
&& !_replyToId) {
@@ -5652,7 +5650,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

@@ -1410,7 +1410,10 @@ void ComposeControls::initTabbedSelector() {
selector->inlineResultChosen(
) | rpl::start_to_stream(_inlineResultChosen, wrap->lifetime());
selector->setSendMenuType([=] { return sendMenuType(); });
selector->contextMenuRequested(
) | rpl::start_with_next([=] {
selector->showMenuWithType(sendMenuType());
}, wrap->lifetime());
}
void ComposeControls::initSendButton() {
@@ -1814,7 +1817,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

@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_sticker.h"
#include "history/view/media/history_view_large_emoji.h"
#include "history/history.h"
#include "base/unixtime.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "main/main_session.h"
@@ -79,7 +80,7 @@ bool SimpleElementDelegate::elementUnderCursor(
}
crl::time SimpleElementDelegate::elementHighlightTime(
not_null<const Element*> element) {
not_null<const HistoryItem*> item) {
return crl::time(0);
}
@@ -157,6 +158,40 @@ TextSelection ShiftItemSelection(
return ShiftItemSelection(selection, byText.length());
}
QString DateTooltipText(not_null<Element*> view) {
const auto format = QLocale::system().dateTimeFormat(QLocale::LongFormat);
auto dateText = view->dateTime().toString(format);
if (const auto editedDate = view->displayedEditDate()) {
dateText += '\n' + tr::lng_edited_date(
tr::now,
lt_date,
base::unixtime::parse(editedDate).toString(format));
}
if (const auto forwarded = view->data()->Get<HistoryMessageForwarded>()) {
dateText += '\n' + tr::lng_forwarded_date(
tr::now,
lt_date,
base::unixtime::parse(forwarded->originalDate).toString(format));
if (const auto media = view->media()) {
if (media->hidesForwardedInfo()) {
dateText += '\n' + tr::lng_forwarded(
tr::now,
lt_user,
(forwarded->originalSender
? forwarded->originalSender->shortName()
: forwarded->hiddenSenderInfo->firstName));
}
}
}
if (const auto msgsigned = view->data()->Get<HistoryMessageSigned>()) {
if (msgsigned->isElided && !msgsigned->isAnonymousRank) {
dateText += '\n'
+ tr::lng_signed_author(tr::now, lt_user, msgsigned->author);
}
}
return dateText;
}
void UnreadBar::init(const QString &string) {
text = string;
width = st::semiboldFont->width(text);
@@ -280,29 +315,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,
@@ -126,6 +126,8 @@ TextSelection ShiftItemSelection(
TextSelection selection,
const Ui::Text::String &byText);
QString DateTooltipText(not_null<Element*> view);
// Any HistoryView::Element can have this Component for
// displaying the unread messages bar above the message.
struct UnreadBar : public RuntimeComponent<UnreadBar, Element> {
@@ -301,6 +303,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

@@ -321,6 +321,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
call->fullCountValue(
) | rpl::start_with_next([=](int count) {
state->current.count = count;
state->current.shown = (count > 0);
consumer.put_next_copy(state->current);
}, lifetime);

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;
@@ -1202,8 +1216,7 @@ QString ListWidget::tooltipText() const {
? _overElement->data().get()
: nullptr;
if (_mouseCursorState == CursorState::Date && item) {
return _overElement->dateTime().toString(
QLocale::system().dateTimeFormat(QLocale::LongFormat));
return HistoryView::DateTooltipText(_overElement);
} else if (_mouseCursorState == CursorState::Forwarded && item) {
if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
return forwarded->text.toString();
@@ -1244,8 +1257,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;
}
@@ -1818,7 +1831,7 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
_overState));
_menu = FillContextMenu(this, request);
if (_menu && !_menu->actions().empty()) {
if (_menu && !_menu->empty()) {
_menu->popup(e->globalPos());
e->accept();
} else if (_menu) {

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)

View File

@@ -492,13 +492,11 @@ void RepliesWidget::setupComposeControls() {
) | rpl::start_with_next([=](not_null<QKeyEvent*> e) {
if (e->key() == Qt::Key_Up) {
if (!_composeControls->isEditingMessage()) {
// #TODO replies edit last sent message
//auto &messages = session().data().scheduledMessages();
//if (const auto item = messages.lastSentMessage(_history)) {
// _inner->editMessageRequestNotify(item->fullId());
//} else {
if (const auto item = _replies->lastEditableMessage()) {
_inner->editMessageRequestNotify(item->fullId());
} else {
_scroll->keyPressEvent(e);
//}
}
} else {
_scroll->keyPressEvent(e);
}

View File

@@ -235,8 +235,9 @@ void ScheduledWidget::setupComposeControls() {
) | rpl::start_with_next([=](not_null<QKeyEvent*> e) {
if (e->key() == Qt::Key_Up) {
if (!_composeControls->isEditingMessage()) {
auto &messages = session().data().scheduledMessages();
if (const auto item = messages.lastSentMessage(_history)) {
const auto item = session().data().scheduledMessages()
.lastEditableMessage(_history);
if (item) {
_inner->editMessageRequestNotify(item->fullId());
} else {
_scroll->keyPressEvent(e);

View File

@@ -256,7 +256,7 @@ void TopBarWidget::showMenu() {
_controller,
_activeChat,
addAction);
if (_menu->actions().empty()) {
if (_menu->empty()) {
_menu.destroy();
} else {
_menu->moveToRight((parentWidget()->width() - width()) + st::topBarMenuPosition.x(), st::topBarMenuPosition.y());
@@ -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

@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/image/image.h"
#include "ui/text/format_values.h"
#include "ui/cached_round_corners.h"
#include "ui/ui_utility.h"
#include "layout.h" // FullSelection
#include "data/data_session.h"
#include "data/data_document.h"
@@ -442,14 +443,14 @@ void Document::draw(
}
} else {
p.setPen(Qt::NoPen);
if (selected) {
p.setBrush(outbg ? st::msgFileOutBgSelected : st::msgFileInBgSelected);
} else {
p.setBrush(outbg ? st::msgFileOutBg : st::msgFileInBg);
}
{
const auto coverDrawn = _data->isSongWithCover()
&& DrawThumbnailAsSongCover(p, _dataMedia, inner, selected);
if (!coverDrawn) {
PainterHighQualityEnabler hq(p);
p.setBrush(selected
? (outbg ? st::msgFileOutBgSelected : st::msgFileInBgSelected)
: (outbg ? st::msgFileOutBg : st::msgFileInBg));
p.drawEllipse(inner);
}
@@ -581,7 +582,7 @@ void Document::ensureDataMediaCreated() const {
return;
}
_dataMedia = _data->createMediaView();
if (Get<HistoryDocumentThumbed>()) {
if (Get<HistoryDocumentThumbed>() || _data->isSongWithCover()) {
_dataMedia->thumbnailWanted(_realParent->fullId());
}
history()->owner().registerHeavyViewPart(_parent);
@@ -951,6 +952,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());
@@ -1071,4 +1073,49 @@ Ui::Text::String Document::createCaption() {
timestampLinkBase);
}
bool DrawThumbnailAsSongCover(
Painter &p,
const std::shared_ptr<Data::DocumentMedia> &dataMedia,
const QRect &rect,
const bool selected) {
if (!dataMedia) {
return false;
}
QPixmap cover;
const auto ow = rect.width();
const auto oh = rect.height();
const auto r = ImageRoundRadius::Ellipse;
const auto c = RectPart::AllCorners;
const auto color = &st::songCoverOverlayFg;
const auto aspectRatio = Qt::KeepAspectRatioByExpanding;
const auto scaled = [&](not_null<Image*> image) -> std::pair<int, int> {
const auto size = image->size().scaled(ow, oh, aspectRatio);
return { size.width(), size.height() };
};
if (const auto normal = dataMedia->thumbnail()) {
const auto &[w, h] = scaled(normal);
cover = normal->pixSingle(w, h, ow, oh, r, c, color);
} else if (const auto blurred = dataMedia->thumbnailInline()) {
const auto &[w, h] = scaled(blurred);
cover = blurred->pixBlurredSingle(w, h, ow, oh, r, c, color);
} else {
return false;
}
if (selected) {
auto selectedCover = Images::prepareColored(
p.textPalette().selectOverlay,
cover.toImage());
cover = QPixmap::fromImage(
std::move(selectedCover),
Qt::ColorOnly);
}
p.drawPixmap(rect.topLeft(), cover);
return true;
}
} // namespace HistoryView

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(
@@ -142,4 +143,10 @@ private:
};
bool DrawThumbnailAsSongCover(
Painter &p,
const std::shared_ptr<Data::DocumentMedia> &dataMedia,
const QRect &rect,
const bool selected = false);
} // namespace HistoryView

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

@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/player/media_player_instance.h"
#include "history/history_location_manager.h"
#include "history/view/history_view_cursor_state.h"
#include "history/view/media/history_view_document.h" // DrawThumbnailAsSongCover
#include "ui/image/image.h"
#include "ui/text/format_values.h"
#include "ui/cached_round_corners.h"
@@ -865,16 +866,21 @@ void File::paint(Painter &p, const QRect &clip, const PaintContext *context) con
auto inner = style::rtlrect(0, st::inlineRowMargin, st::inlineFileSize, st::inlineFileSize, _width);
p.setPen(Qt::NoPen);
if (isThumbAnimation()) {
auto over = _animation->a_thumbOver.value(1.);
p.setBrush(anim::brush(st::msgFileInBg, st::msgFileInBgOver, over));
} else {
bool over = ClickHandler::showAsActive(_document->loading() ? _cancel : _open);
p.setBrush(over ? st::msgFileInBgOver : st::msgFileInBg);
}
{
const auto coverDrawn = _document->isSongWithCover()
&& HistoryView::DrawThumbnailAsSongCover(p, _documentMedia, inner);
if (!coverDrawn) {
PainterHighQualityEnabler hq(p);
if (isThumbAnimation()) {
const auto over = _animation->a_thumbOver.value(1.);
p.setBrush(
anim::brush(st::msgFileInBg, st::msgFileInBgOver, over));
} else {
const auto over = ClickHandler::showAsActive(_document->loading()
? _cancel
: _open);
p.setBrush(over ? st::msgFileInBgOver : st::msgFileInBg);
}
p.drawEllipse(inner);
}

View File

@@ -305,7 +305,7 @@ void Inner::contextMenuEvent(QContextMenuEvent *e) {
SendMenu::DefaultSilentCallback(send),
SendMenu::DefaultScheduleCallback(this, type, send));
if (!_menu->actions().empty()) {
if (!_menu->empty()) {
_menu->popup(QCursor::pos());
}
}

View File

@@ -478,10 +478,17 @@ void Widget::resetAccount() {
_resetRequest = 0;
Ui::hideLayer();
moveToStep(
new SignupWidget(this, _account, getData()),
StackAction::Replace,
Animate::Forward);
if (getData()->phone.isEmpty()) {
moveToStep(
new QrWidget(this, _account, getData()),
StackAction::Replace,
Animate::Back);
} else {
moveToStep(
new SignupWidget(this, _account, getData()),
StackAction::Replace,
Animate::Forward);
}
}).fail([=](const RPCError &error) {
_resetRequest = 0;

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

@@ -146,11 +146,24 @@ void MainWindow::createTrayIconMenu() {
: tr::lng_enable_notifications_from_tray(tr::now);
if (Platform::IsLinux() && !Platform::IsWayland()) {
trayIconMenu->addAction(tr::lng_open_from_tray(tr::now), this, SLOT(showFromTray()));
trayIconMenu->addAction(tr::lng_open_from_tray(tr::now), [=] {
showFromTray();
});
}
trayIconMenu->addAction(tr::lng_minimize_to_tray(tr::now), this, SLOT(minimizeToTray()));
trayIconMenu->addAction(notificationActionText, this, SLOT(toggleDisplayNotifyFromTray()));
trayIconMenu->addAction(tr::lng_quit_from_tray(tr::now), this, SLOT(quitFromTray()));
const auto showLifetime = std::make_shared<rpl::lifetime>();
trayIconMenu->addAction(tr::lng_minimize_to_tray(tr::now), [=] {
if (_activeForTrayIconAction) {
minimizeToTray();
} else {
showFromTrayMenu();
}
});
trayIconMenu->addAction(notificationActionText, [=] {
toggleDisplayNotifyFromTray();
});
trayIconMenu->addAction(tr::lng_quit_from_tray(tr::now), [=] {
quitFromTray();
});
initTrayMenuHook();
}
@@ -635,18 +648,17 @@ void MainWindow::updateTrayMenu(bool force) {
auto actions = trayIconMenu->actions();
if (Platform::IsLinux() && !Platform::IsWayland()) {
auto minimizeAction = actions.at(1);
const auto minimizeAction = actions.at(1);
minimizeAction->setEnabled(isVisible());
} else {
updateIsActive();
auto active = Platform::IsWayland() ? isVisible() : isActive();
auto toggleAction = actions.at(0);
disconnect(toggleAction, SIGNAL(triggered(bool)), this, SLOT(minimizeToTray()));
disconnect(toggleAction, SIGNAL(triggered(bool)), this, SLOT(showFromTray()));
connect(toggleAction, SIGNAL(triggered(bool)), this, active ? SLOT(minimizeToTray()) : SLOT(showFromTray()));
toggleAction->setText(active
? tr::lng_minimize_to_tray(tr::now)
: tr::lng_open_from_tray(tr::now));
const auto active = isActiveForTrayMenu();
if (_activeForTrayIconAction != active) {
_activeForTrayIconAction = active;
const auto toggleAction = actions.at(0);
toggleAction->setText(_activeForTrayIconAction
? tr::lng_minimize_to_tray(tr::now)
: tr::lng_open_from_tray(tr::now));
}
}
auto notificationAction = actions.at(Platform::IsLinux() && !Platform::IsWayland() ? 2 : 1);
auto notificationActionText = Core::App().settings().desktopNotify()
@@ -657,8 +669,10 @@ void MainWindow::updateTrayMenu(bool force) {
psTrayMenuUpdated();
}
void MainWindow::onShowAddContact() {
if (isHidden()) showFromTray();
void MainWindow::showAddContact() {
if (isHidden()) {
showFromTray();
}
if (const auto controller = sessionController()) {
Ui::show(
@@ -667,8 +681,10 @@ void MainWindow::onShowAddContact() {
}
}
void MainWindow::onShowNewGroup() {
if (isHidden()) showFromTray();
void MainWindow::showNewGroup() {
if (isHidden()) {
showFromTray();
}
if (const auto controller = sessionController()) {
Ui::show(
@@ -677,8 +693,10 @@ void MainWindow::onShowNewGroup() {
}
}
void MainWindow::onShowNewChannel() {
if (isHidden()) showFromTray();
void MainWindow::showNewChannel() {
if (isHidden()) {
showFromTray();
}
if (const auto controller = sessionController()) {
Ui::show(
@@ -720,24 +738,6 @@ void MainWindow::showLogoutConfirmation() {
callback));
}
void MainWindow::quitFromTray() {
App::quit();
}
void MainWindow::activate() {
bool wasHidden = !isVisible();
setWindowState(windowState() & ~Qt::WindowMinimized);
setVisible(true);
psActivateProcess();
activateWindow();
controller().updateIsActiveFocus();
if (wasHidden) {
if (_main) {
_main->windowShown();
}
}
}
bool MainWindow::takeThirdSectionFromLayer() {
return _layer ? _layer->takeToThirdSection() : false;
}
@@ -749,23 +749,12 @@ void MainWindow::fixOrder() {
if (_testingThemeWarning) _testingThemeWarning->raise();
}
void MainWindow::showFromTray(QSystemTrayIcon::ActivationReason reason) {
if (reason != QSystemTrayIcon::Context) {
base::call_delayed(1, this, [this] {
updateTrayMenu();
updateGlobalMenu();
});
activate();
updateUnreadCounter();
}
}
void MainWindow::handleTrayIconActication(
QSystemTrayIcon::ActivationReason reason) {
updateIsActive();
if (Platform::IsMac() && isActive()) {
if (trayIcon && !trayIcon->contextMenu()) {
showFromTray(reason);
showFromTray();
}
return;
}
@@ -775,10 +764,10 @@ void MainWindow::handleTrayIconActication(
psShowTrayMenu();
});
} else if (!skipTrayClick()) {
if (Platform::IsWayland() ? isVisible() : isActive()) {
if (isActiveForTrayMenu()) {
minimizeToTray();
} else {
showFromTray(reason);
showFromTray();
}
_lastTrayClickTime = crl::now();
}

View File

@@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "platform/platform_specific.h"
#include "platform/platform_main_window.h"
#include "base/unique_qptr.h"
#include "ui/layers/layer_widget.h"
@@ -40,8 +39,6 @@ class LayerStackWidget;
class MediaPreviewWidget;
class MainWindow : public Platform::MainWindow {
Q_OBJECT
public:
explicit MainWindow(not_null<Window::Controller*> controller);
~MainWindow();
@@ -55,11 +52,17 @@ public:
void setupIntro(Intro::EnterPoint point);
void setupMain();
void showSettings();
void showAddContact();
void showNewGroup();
void showNewChannel();
void setInnerFocus();
MainWidget *sessionContent() const;
[[nodiscard]] bool doWeMarkAsRead();
void activate();
bool takeThirdSectionFromLayer();
@@ -115,18 +118,6 @@ protected:
void updateIsActiveHook() override;
void clearWidgetsHook() override;
public slots:
void showSettings();
void setInnerFocus();
void quitFromTray();
void showFromTray(QSystemTrayIcon::ActivationReason reason = QSystemTrayIcon::Unknown);
void toggleDisplayNotifyFromTray();
void onShowAddContact();
void onShowNewGroup();
void onShowNewChannel();
private:
[[nodiscard]] bool skipTrayClick() const;
@@ -140,12 +131,15 @@ private:
void themeUpdated(const Window::Theme::BackgroundUpdate &data);
void toggleDisplayNotifyFromTray();
QPixmap grabInner();
QImage icon16, icon32, icon64, iconbig16, iconbig32, iconbig64;
crl::time _lastTrayClickTime = 0;
QPoint _lastMousePosition;
bool _activeForTrayIconAction = true;
object_ptr<Window::PasscodeLockWidget> _passcodeLock = { nullptr };
object_ptr<Intro::Widget> _intro = { nullptr };

View File

@@ -276,7 +276,16 @@ void StopDetachIfNotUsedSafe() {
}
bool SupportsSpeedControl() {
return OpenAL::HasEFXExtension();
return OpenAL::HasEFXExtension()
&& (alGetEnumValue("AL_AUXILIARY_SEND_FILTER") != 0)
&& (alGetEnumValue("AL_DIRECT_FILTER") != 0)
&& (alGetEnumValue("AL_EFFECT_TYPE") != 0)
&& (alGetEnumValue("AL_EFFECT_PITCH_SHIFTER") != 0)
&& (alGetEnumValue("AL_FILTER_TYPE") != 0)
&& (alGetEnumValue("AL_FILTER_LOWPASS") != 0)
&& (alGetEnumValue("AL_LOWPASS_GAIN") != 0)
&& (alGetEnumValue("AL_PITCH_SHIFTER_COARSE_TUNE") != 0)
&& (alGetEnumValue("AL_EFFECTSLOT_EFFECT") != 0);
}
} // namespace Audio
@@ -587,7 +596,7 @@ Mixer::Mixer(not_null<Audio::Instance*> instance)
}, _lifetime);
connect(this, SIGNAL(loaderOnStart(const AudioMsgId&, qint64)), _loader, SLOT(onStart(const AudioMsgId&, qint64)));
connect(this, SIGNAL(loaderOnCancel(const AudioMsgId&)), _loader, SLOT(onCancel(const AudioMsgId&)));
connect(this, SIGNAL(loaderOnCancel(const AudioMsgId&)), _loader, SLOT(onCancel(const AudioMsgId&)), Qt::QueuedConnection);
connect(_loader, SIGNAL(needToCheck()), _fader, SLOT(onTimer()));
connect(_loader, SIGNAL(error(const AudioMsgId&)), this, SLOT(onError(const AudioMsgId&)));
connect(_fader, SIGNAL(needToPreload(const AudioMsgId&)), _loader, SLOT(onLoad(const AudioMsgId&)));

View File

@@ -148,6 +148,10 @@ Stream File::Context::initStream(
const auto info = format->streams[index];
if (type == AVMEDIA_TYPE_VIDEO) {
if (info->disposition & AV_DISPOSITION_ATTACHED_PIC) {
// ignore cover streams
return Stream();
}
result.rotation = FFmpeg::ReadRotationFromMetadata(info);
result.aspect = FFmpeg::ValidateAspectRatio(info->sample_aspect_ratio);
} else if (type == AVMEDIA_TYPE_AUDIO) {
@@ -159,10 +163,6 @@ Stream File::Context::initStream(
result.codec = FFmpeg::MakeCodecPointer(info);
if (!result.codec) {
if (info->codecpar->codec_id == AV_CODEC_ID_MJPEG) {
// mp3 files contain such "video stream", just ignore it.
return Stream();
}
return result;
}

View File

@@ -322,8 +322,7 @@ OverlayWidget::OverlayWidget()
, _radial([=](crl::time now) { return radialAnimationCallback(now); })
, _lastAction(-st::mediaviewDeltaFromLastAction, -st::mediaviewDeltaFromLastAction)
, _stateAnimation([=](crl::time now) { return stateAnimationCallback(now); })
, _dropdown(this, st::mediaviewDropdownMenu)
, _dropdownShowTimer(this) {
, _dropdown(this, st::mediaviewDropdownMenu) {
Lang::Updated(
) | rpl::start_with_next([=] {
refreshLang();
@@ -360,6 +359,7 @@ OverlayWidget::OverlayWidget()
setWindowFlags(Qt::FramelessWindowHint);
}
updateGeometry();
updateControlsGeometry();
setAttribute(Qt::WA_NoSystemBackground, true);
setAttribute(Qt::WA_TranslucentBackground, true);
setMouseTracking(true);
@@ -409,23 +409,19 @@ OverlayWidget::OverlayWidget()
}
}, lifetime());
_saveMsgUpdater.setSingleShot(true);
connect(&_saveMsgUpdater, SIGNAL(timeout()), this, SLOT(updateImage()));
_saveMsgUpdater.setCallback([=] { updateImage(); });
setAttribute(Qt::WA_AcceptTouchEvents);
_touchTimer.setSingleShot(true);
connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer()));
_touchTimer.setCallback([=] { onTouchTimer(); });
_controlsHideTimer.setSingleShot(true);
connect(&_controlsHideTimer, SIGNAL(timeout()), this, SLOT(onHideControls()));
_controlsHideTimer.setCallback([=] { onHideControls(); });
_docDownload->addClickHandler([=] { onDownload(); });
_docSaveAs->addClickHandler([=] { onSaveAs(); });
_docCancel->addClickHandler([=] { onSaveCancel(); });
_dropdown->setHiddenCallback([this] { dropdownHidden(); });
_dropdownShowTimer->setSingleShot(true);
connect(_dropdownShowTimer, SIGNAL(timeout()), this, SLOT(onDropdown()));
_dropdownShowTimer.setCallback([=] { onDropdown(); });
}
void OverlayWidget::refreshLang() {
@@ -446,17 +442,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::IsLinux()) {
return;
}
const auto screen = windowHandle() && windowHandle()->screen()
? windowHandle()->screen()
: QApplication::primaryScreen();
@@ -465,7 +460,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);
@@ -477,6 +478,7 @@ void OverlayWidget::updateGeometry() {
_saveMsg.moveTo((width() - _saveMsg.width()) / 2, (height() - _saveMsg.height()) / 2);
_photoRadialRect = QRect(QPoint((width() - st::radialSize.width()) / 2, (height() - st::radialSize.height()) / 2), st::radialSize);
updateControls();
resizeContentByScreenSize();
update();
}
@@ -791,33 +793,37 @@ void OverlayWidget::refreshCaptionGeometry() {
captionHeight);
}
void OverlayWidget::updateActions() {
_actions.clear();
void OverlayWidget::fillContextMenuActions(const MenuCallback &addAction) {
if (_document && _document->loading()) {
_actions.push_back({ tr::lng_cancel(tr::now), SLOT(onSaveCancel()) });
addAction(tr::lng_cancel(tr::now), [=] { onSaveCancel(); });
}
if (IsServerMsgId(_msgid.msg)) {
_actions.push_back({ tr::lng_context_to_msg(tr::now), SLOT(onToMessage()) });
addAction(tr::lng_context_to_msg(tr::now), [=] { onToMessage(); });
}
if (_document && !_document->filepath(true).isEmpty()) {
_actions.push_back({ Platform::IsMac() ? tr::lng_context_show_in_finder(tr::now) : tr::lng_context_show_in_folder(tr::now), SLOT(onShowInFolder()) });
const auto text = Platform::IsMac()
? tr::lng_context_show_in_finder(tr::now)
: tr::lng_context_show_in_folder(tr::now);
addAction(text, [=] { onShowInFolder(); });
}
if ((_document && documentContentShown()) || (_photo && _photoMedia->loaded())) {
_actions.push_back({ tr::lng_mediaview_copy(tr::now), SLOT(onCopy()) });
addAction(tr::lng_mediaview_copy(tr::now), [=] { onCopy(); });
}
if ((_photo && _photo->hasAttachedStickers())
|| (_document && _document->hasAttachedStickers())) {
auto member = _photo
? SLOT(onPhotoAttachedStickers())
: SLOT(onDocumentAttachedStickers());
_actions.push_back({
auto callback = [=] {
if (_photo) {
onPhotoAttachedStickers();
} else if (_document) {
onDocumentAttachedStickers();
}
};
addAction(
tr::lng_context_attached_stickers(tr::now),
std::move(member)
});
std::move(callback));
}
if (_canForwardItem) {
_actions.push_back({ tr::lng_mediaview_forward(tr::now), SLOT(onForward()) });
addAction(tr::lng_mediaview_forward(tr::now), [=] { onForward(); });
}
const auto canDelete = [&] {
if (_canDeleteItem) {
@@ -837,12 +843,15 @@ void OverlayWidget::updateActions() {
return false;
}();
if (canDelete) {
_actions.push_back({ tr::lng_mediaview_delete(tr::now), SLOT(onDelete()) });
addAction(tr::lng_mediaview_delete(tr::now), [=] { onDelete(); });
}
_actions.push_back({ tr::lng_mediaview_save_as(tr::now), SLOT(onSaveAs()) });
addAction(tr::lng_mediaview_save_as(tr::now), [=] { onSaveAs(); });
if (const auto overviewType = computeOverviewType()) {
_actions.push_back({ _document ? tr::lng_mediaview_files_all(tr::now) : tr::lng_mediaview_photos_all(tr::now), SLOT(onOverview()) });
const auto text = _document
? tr::lng_mediaview_files_all(tr::now)
: tr::lng_mediaview_photos_all(tr::now);
addAction(text, [=] { onOverview(); });
}
}
@@ -1160,8 +1169,6 @@ void OverlayWidget::clearSession() {
_animationOpacities.clear();
}
clearStreaming();
delete _menu;
_menu = nullptr;
setContext(v::null);
_from = nullptr;
_fromName = QString();
@@ -1233,7 +1240,7 @@ void OverlayWidget::close() {
void OverlayWidget::activateControls() {
if (!_menu && !_mousePressed) {
_controlsHideTimer.start(int(st::mediaviewWaitHide));
_controlsHideTimer.callOnce(st::mediaviewWaitHide);
}
if (_fullScreenVideo) {
if (_streamed) {
@@ -1618,7 +1625,9 @@ void OverlayWidget::onDelete() {
}
void OverlayWidget::onOverview() {
if (_menu) _menu->hideMenu(true);
if (_menu) {
_menu->hideMenu(true);
}
update();
if (const auto overviewType = computeOverviewType()) {
close();
@@ -2443,7 +2452,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;
}
@@ -3104,7 +3115,7 @@ void OverlayWidget::paintEvent(QPaintEvent *e) {
}
if (!_blurred) {
auto nextFrame = (dt < st::mediaviewSaveMsgShowing || hidingDt >= 0) ? int(AnimationTimerDelta) : (st::mediaviewSaveMsgShowing + st::mediaviewSaveMsgShown + 1 - dt);
_saveMsgUpdater.start(nextFrame);
_saveMsgUpdater.callOnce(nextFrame);
}
} else {
_saveMsgStarted = 0;
@@ -3854,7 +3865,9 @@ void OverlayWidget::preloadData(int delta) {
void OverlayWidget::mousePressEvent(QMouseEvent *e) {
updateOver(e->pos());
if (_menu || !_receiveMouse) return;
if (_menu || !_receiveMouse) {
return;
}
ClickHandler::pressed();
@@ -3959,9 +3972,9 @@ bool OverlayWidget::updateOverState(OverState newState) {
bool result = true;
if (_over != newState) {
if (newState == OverMore && !_ignoringDropdown) {
_dropdownShowTimer->start(0);
_dropdownShowTimer.callOnce(0);
} else {
_dropdownShowTimer->stop();
_dropdownShowTimer.cancel();
}
updateOverRect(_over);
updateOverRect(newState);
@@ -4095,7 +4108,7 @@ void OverlayWidget::mouseReleaseEvent(QMouseEvent *e) {
} else if (_over == OverIcon && _down == OverIcon) {
onDocClick();
} else if (_over == OverMore && _down == OverMore) {
QTimer::singleShot(0, this, SLOT(onDropdown()));
InvokeQueued(this, [=] { onDropdown(); });
} else if (_over == OverClose && _down == OverClose) {
close();
} else if (_over == OverVideo && _down == OverVideo) {
@@ -4134,16 +4147,17 @@ void OverlayWidget::mouseReleaseEvent(QMouseEvent *e) {
void OverlayWidget::contextMenuEvent(QContextMenuEvent *e) {
if (e->reason() != QContextMenuEvent::Mouse || QRect(_x, _y, _w, _h).contains(e->pos())) {
if (_menu) {
_menu->deleteLater();
_menu = nullptr;
}
_menu = new Ui::PopupMenu(this, st::mediaviewPopupMenu);
updateActions();
for_const (auto &action, _actions) {
_menu->addAction(action.text, this, action.member);
}
connect(_menu, SIGNAL(destroyed(QObject*)), this, SLOT(onMenuDestroy(QObject*)));
_menu = base::make_unique_q<Ui::PopupMenu>(
this,
st::mediaviewPopupMenu);
fillContextMenuActions([&] (const QString &text, Fn<void()> handler) {
_menu->addAction(text, std::move(handler));
});
_menu->setDestroyedCallback(crl::guard(this, [=] {
activateControls();
_receiveMouse = false;
InvokeQueued(this, [=] { receiveMouse(); });
}));
_menu->popup(e->globalPos());
e->accept();
activateControls();
@@ -4154,7 +4168,7 @@ void OverlayWidget::touchEvent(QTouchEvent *e) {
switch (e->type()) {
case QEvent::TouchBegin: {
if (_touchPress || e->touchPoints().isEmpty()) return;
_touchTimer.start(QApplication::startDragTime());
_touchTimer.callOnce(QApplication::startDragTime());
_touchPress = true;
_touchMove = _touchRightButton = false;
_touchStart = e->touchPoints().cbegin()->screenPos().toPoint();
@@ -4194,14 +4208,14 @@ void OverlayWidget::touchEvent(QTouchEvent *e) {
}
}
if (weak) {
_touchTimer.stop();
_touchTimer.cancel();
_touchPress = _touchMove = _touchRightButton = false;
}
} break;
case QEvent::TouchCancel: {
_touchPress = false;
_touchTimer.stop();
_touchTimer.cancel();
} break;
}
}
@@ -4305,8 +4319,10 @@ void OverlayWidget::setVisibleHook(bool visible) {
assignMediaPointer(nullptr);
_preloadPhotos.clear();
_preloadDocuments.clear();
if (_menu) _menu->hideMenu(true);
_controlsHideTimer.stop();
if (_menu) {
_menu->hideMenu(true);
}
_controlsHideTimer.cancel();
_controlsState = ControlsShown;
_controlsOpacity = anim::value(1, 1);
_groupThumbs = nullptr;
@@ -4329,25 +4345,15 @@ void OverlayWidget::setVisibleHook(bool visible) {
}
}
void OverlayWidget::onMenuDestroy(QObject *obj) {
if (_menu == obj) {
_menu = nullptr;
activateControls();
}
_receiveMouse = false;
QTimer::singleShot(0, this, SLOT(receiveMouse()));
}
void OverlayWidget::receiveMouse() {
_receiveMouse = true;
}
void OverlayWidget::onDropdown() {
updateActions();
_dropdown->clearActions();
for_const (auto &action, _actions) {
_dropdown->addAction(action.text, this, action.member);
}
fillContextMenuActions([&] (const QString &text, Fn<void()> handler) {
_dropdown->addAction(text, std::move(handler));
});
_dropdown->moveToRight(0, height() - _dropdown->height());
_dropdown->showAnimated(Ui::PanelAnimation::Origin::BottomRight);
_dropdown->setFocus();

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/timer.h"
#include "ui/rp_widget.h"
#include "ui/widgets/dropdown_menu.h"
#include "ui/effects/animations.h"
@@ -55,10 +56,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
@@ -123,7 +128,6 @@ private slots:
void onDelete();
void onOverview();
void onCopy();
void onMenuDestroy(QObject *obj);
void receiveMouse();
void onPhotoAttachedStickers();
void onDocumentAttachedStickers();
@@ -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;
@@ -269,7 +274,11 @@ private:
void dropdownHidden();
void updateDocSize();
void updateControls();
void updateActions();
void updateControlsGeometry();
using MenuCallback = Fn<void(const QString &, Fn<void()>)>;
void fillContextMenuActions(const MenuCallback &addAction);
void resizeCenteredControls();
void resizeContentByScreenSize();
@@ -487,26 +496,20 @@ private:
};
ControlsState _controlsState = ControlsShown;
crl::time _controlsAnimStarted = 0;
QTimer _controlsHideTimer;
base::Timer _controlsHideTimer;
anim::value _controlsOpacity;
bool _mousePressed = false;
Ui::PopupMenu *_menu = nullptr;
base::unique_qptr<Ui::PopupMenu> _menu;
object_ptr<Ui::DropdownMenu> _dropdown;
object_ptr<QTimer> _dropdownShowTimer;
struct ActionData {
QString text;
const char *member;
};
QList<ActionData> _actions;
base::Timer _dropdownShowTimer;
bool _receiveMouse = true;
bool _touchPress = false;
bool _touchMove = false;
bool _touchRightButton = false;
QTimer _touchTimer;
base::Timer _touchTimer;
QPoint _touchStart;
QPoint _accumScroll;
@@ -514,7 +517,7 @@ private:
crl::time _saveMsgStarted = 0;
anim::value _saveMsgOpacity;
QRect _saveMsg;
QTimer _saveMsgUpdater;
base::Timer _saveMsgUpdater;
Ui::Text::String _saveMsgText;
SavePhotoVideo _savePhotoVideoWhenLoaded = SavePhotoVideo::None;

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

@@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item.h"
#include "history/history_item_components.h"
#include "history/view/history_view_cursor_state.h"
#include "history/view/media/history_view_document.h" // DrawThumbnailAsSongCover
#include "base/unixtime.h"
#include "base/qt_adapters.h"
#include "ui/effects/round_checkbox.h"
@@ -1008,21 +1009,33 @@ void Document::paint(Painter &p, const QRect &clip, TextSelection selection, con
auto inner = style::rtlrect(_st.songPadding.left(), _st.songPadding.top(), _st.songThumbSize, _st.songThumbSize, _width);
if (clip.intersects(inner)) {
const auto isLoading = (!cornerDownload
&& (_data->loading() || _data->uploading()));
p.setPen(Qt::NoPen);
if (selected) {
p.setBrush(st::msgFileInBgSelected);
} else {
auto over = ClickHandler::showAsActive((!cornerDownload && (_data->loading() || _data->uploading())) ? _cancell : (loaded || _dataMedia->canBePlayed()) ? _openl : _savel);
p.setBrush(anim::brush(_st.songIconBg, _st.songOverBg, _a_iconOver.value(over ? 1. : 0.)));
}
{
using namespace HistoryView;
const auto coverDrawn = _data->isSongWithCover()
&& DrawThumbnailAsSongCover(p, _dataMedia, inner, selected);
if (!coverDrawn) {
if (selected) {
p.setBrush(st::msgFileInBgSelected);
} else {
const auto over = ClickHandler::showAsActive(isLoading
? _cancell
: (loaded || _dataMedia->canBePlayed())
? _openl
: _savel);
p.setBrush(anim::brush(
_st.songIconBg,
_st.songOverBg,
_a_iconOver.value(over ? 1. : 0.)));
}
PainterHighQualityEnabler hq(p);
p.drawEllipse(inner);
}
const auto icon = [&] {
if (!cornerDownload && (_data->loading() || _data->uploading())) {
if (isLoading) {
return &(selected ? _st.songCancelSelected : _st.songCancel);
} else if (showPause) {
return &(selected ? _st.songPauseSelected : _st.songPause);
@@ -1479,9 +1492,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 +1635,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 +1643,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 +1660,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 +1705,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

@@ -7,14 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "platform/linux/file_utilities_linux.h"
#include "platform/linux/linux_libs.h"
#include "platform/linux/linux_gdk_helper.h"
#include "platform/linux/linux_desktop_environment.h"
#include "platform/linux/linux_gtk_integration.h"
#include "platform/linux/specific_linux.h"
#include "storage/localstorage.h"
#include "base/qt_adapters.h"
#include "window/window_controller.h"
#include "core/application.h"
#include <QtGui/QDesktopServices>
@@ -24,100 +18,10 @@ extern "C" {
#define signals public
} // extern "C"
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
#include <private/qguiapplication_p.h>
extern "C" {
#undef signals
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#define signals public
} // extern "C"
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
using Platform::internal::GtkIntegration;
namespace Platform {
namespace File {
namespace {
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
bool ShowOpenWithSupported() {
return Platform::internal::GdkHelperLoaded()
&& (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) {
GAppInfo *chosenAppInfo = nullptr;
switch (responseId) {
case GTK_RESPONSE_OK:
chosenAppInfo = Libs::gtk_app_chooser_get_app_info(
Libs::gtk_app_chooser_cast(dialog));
if (chosenAppInfo) {
GList *uris = nullptr;
uris = g_list_prepend(uris, g_file_get_uri(file));
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:
break;
}
}
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
} // namespace
namespace internal {
QByteArray EscapeShell(const QByteArray &content) {
auto result = QByteArray();
auto b = content.constData(), e = content.constEnd();
for (auto ch = b; ch != e; ++ch) {
if (*ch == ' ' || *ch == '"' || *ch == '\'' || *ch == '\\') {
if (result.isEmpty()) {
result.reserve(content.size() * 2);
}
if (ch > b) {
result.append(b, ch - b);
}
result.append('\\');
b = ch;
}
}
if (result.isEmpty()) {
return content;
}
if (e > b) {
result.append(b, e - b);
}
return result;
}
} // namespace internal
void UnsafeOpenUrl(const QString &url) {
if (!g_app_info_launch_default_for_uri(
@@ -133,41 +37,16 @@ void UnsafeOpenEmailLink(const QString &email) {
}
bool UnsafeShowOpenWith(const QString &filepath) {
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
if (InFlatpak()
|| InSnap()
|| !ShowOpenWithSupported()) {
if (InFlatpak() || InSnap()) {
return false;
}
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());
if (const auto integration = GtkIntegration::Instance()) {
const auto absolutePath = QFileInfo(filepath).absoluteFilePath();
return integration->showOpenWithDialog(absolutePath);
}
Libs::gtk_widget_show(appChooserDialog);
return true;
#else // !TDESKTOP_DISABLE_GTK_INTEGRATION
return false;
#endif // TDESKTOP_DISABLE_GTK_INTEGRATION
}
void UnsafeLaunch(const QString &filepath) {
@@ -186,136 +65,6 @@ void UnsafeLaunch(const QString &filepath) {
} // namespace File
namespace FileDialog {
namespace {
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
// GTK file chooser image preview: thanks to Chromium
// The size of the preview we display for selected image files. We set height
// larger than width because generally there is more free space vertically
// than horiztonally (setting the preview image will alway expand the width of
// the dialog, but usually not the height). The image's aspect ratio will always
// be preserved.
constexpr auto kPreviewWidth = 256;
constexpr auto kPreviewHeight = 512;
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
using Type = ::FileDialog::internal::Type;
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
bool UseNative(Type type = Type::ReadFile) {
// use gtk file dialog on gtk-based desktop environments
// or if QT_QPA_PLATFORMTHEME=(gtk2|gtk3)
// or if portals are used and operation is to open folder
// and portal doesn't support folder choosing
const auto sandboxedOrCustomPortal = InFlatpak()
|| InSnap()
|| UseXDGDesktopPortal();
const auto neededForPortal = (type == Type::ReadFolder)
&& !CanOpenDirectoryWithPortal();
const auto neededNonForced = DesktopEnvironment::IsGtkBased()
|| (sandboxedOrCustomPortal && neededForPortal);
const auto excludeNonForced = sandboxedOrCustomPortal && !neededForPortal;
return IsGtkIntegrationForced()
|| (neededNonForced && !excludeNonForced);
}
bool NativeSupported() {
return Platform::internal::GdkHelperLoaded()
&& (Libs::gtk_widget_hide_on_delete != nullptr)
&& (Libs::gtk_clipboard_store != nullptr)
&& (Libs::gtk_clipboard_get != nullptr)
&& (Libs::gtk_widget_destroy != nullptr)
&& (Libs::gtk_dialog_get_type != nullptr)
&& (Libs::gtk_dialog_run != nullptr)
&& (Libs::gtk_widget_realize != nullptr)
&& (Libs::gdk_window_set_modal_hint != nullptr)
&& (Libs::gtk_widget_show != nullptr)
&& (Libs::gdk_window_focus != nullptr)
&& (Libs::gtk_widget_hide != nullptr)
&& (Libs::gtk_widget_hide_on_delete != nullptr)
&& (Libs::gtk_file_chooser_dialog_new != nullptr)
&& (Libs::gtk_file_chooser_get_type != nullptr)
&& (Libs::gtk_file_chooser_set_current_folder != nullptr)
&& (Libs::gtk_file_chooser_get_current_folder != nullptr)
&& (Libs::gtk_file_chooser_set_current_name != nullptr)
&& (Libs::gtk_file_chooser_select_filename != nullptr)
&& (Libs::gtk_file_chooser_get_filenames != nullptr)
&& (Libs::gtk_file_chooser_set_filter != nullptr)
&& (Libs::gtk_file_chooser_get_filter != nullptr)
&& (Libs::gtk_window_get_type != nullptr)
&& (Libs::gtk_window_set_title != nullptr)
&& (Libs::gtk_file_chooser_set_local_only != nullptr)
&& (Libs::gtk_file_chooser_set_action != nullptr)
&& (Libs::gtk_file_chooser_set_select_multiple != nullptr)
&& (Libs::gtk_file_chooser_set_do_overwrite_confirmation != nullptr)
&& (Libs::gtk_file_chooser_remove_filter != nullptr)
&& (Libs::gtk_file_filter_set_name != nullptr)
&& (Libs::gtk_file_filter_add_pattern != nullptr)
&& (Libs::gtk_file_chooser_add_filter != nullptr)
&& (Libs::gtk_file_filter_new != nullptr);
}
bool PreviewSupported() {
return NativeSupported()
&& (Libs::gdk_pixbuf_new_from_file_at_size != nullptr);
}
bool GetNative(
QPointer<QWidget> parent,
QStringList &files,
QByteArray &remoteContent,
const QString &caption,
const QString &filter,
Type type,
QString startFile) {
internal::GtkFileDialog dialog(parent, caption, QString(), filter);
dialog.setModal(true);
if (type == Type::ReadFile || type == Type::ReadFiles) {
dialog.setFileMode((type == Type::ReadFiles) ? QFileDialog::ExistingFiles : QFileDialog::ExistingFile);
dialog.setAcceptMode(QFileDialog::AcceptOpen);
} else if (type == Type::ReadFolder) {
dialog.setAcceptMode(QFileDialog::AcceptOpen);
dialog.setFileMode(QFileDialog::Directory);
dialog.setOption(QFileDialog::ShowDirsOnly);
} else {
dialog.setFileMode(QFileDialog::AnyFile);
dialog.setAcceptMode(QFileDialog::AcceptSave);
}
if (startFile.isEmpty() || startFile.at(0) != '/') {
startFile = cDialogLastPath() + '/' + startFile;
}
dialog.selectFile(startFile);
int res = dialog.exec();
QString path = dialog.directory().absolutePath();
if (path != cDialogLastPath()) {
cSetDialogLastPath(path);
Local::writeSettings();
}
if (res == QDialog::Accepted) {
if (type == Type::ReadFiles) {
files = dialog.selectedFiles();
} else {
files = dialog.selectedFiles().mid(0, 1);
}
return true;
}
files = QStringList();
remoteContent = QByteArray();
return false;
}
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
} // namespace
bool Get(
QPointer<QWidget> parent,
@@ -323,23 +72,24 @@ bool Get(
QByteArray &remoteContent,
const QString &caption,
const QString &filter,
Type type,
::FileDialog::internal::Type type,
QString startFile) {
if (parent) {
parent = parent->window();
}
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
if (UseNative(type) && NativeSupported()) {
return GetNative(
parent,
files,
remoteContent,
caption,
filter,
type,
startFile);
if (const auto integration = GtkIntegration::Instance()) {
if (integration->fileDialogSupported()
&& integration->useFileDialog(type)) {
return integration->getFileDialog(
parent,
files,
remoteContent,
caption,
filter,
type,
startFile);
}
}
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
return ::FileDialog::internal::GetDefault(
parent,
files,
@@ -350,448 +100,5 @@ bool Get(
startFile);
}
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
namespace internal {
QGtkDialog::QGtkDialog(GtkWidget *gtkWidget) : gtkWidget(gtkWidget) {
g_signal_connect_swapped(G_OBJECT(gtkWidget), "response", G_CALLBACK(onResponse), this);
g_signal_connect(G_OBJECT(gtkWidget), "delete-event", G_CALLBACK(Libs::gtk_widget_hide_on_delete), nullptr);
if (PreviewSupported()) {
_preview = Libs::gtk_image_new();
g_signal_connect_swapped(G_OBJECT(gtkWidget), "update-preview", G_CALLBACK(onUpdatePreview), this);
Libs::gtk_file_chooser_set_preview_widget(Libs::gtk_file_chooser_cast(gtkWidget), _preview);
}
}
QGtkDialog::~QGtkDialog() {
Libs::gtk_clipboard_store(Libs::gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
Libs::gtk_widget_destroy(gtkWidget);
}
GtkDialog *QGtkDialog::gtkDialog() const {
return Libs::gtk_dialog_cast(gtkWidget);
}
void QGtkDialog::exec() {
if (modality() == Qt::ApplicationModal) {
// block input to the whole app, including other GTK dialogs
Libs::gtk_dialog_run(gtkDialog());
} else {
// block input to the window, allow input to other GTK dialogs
QEventLoop loop;
connect(this, SIGNAL(accept()), &loop, SLOT(quit()));
connect(this, SIGNAL(reject()), &loop, SLOT(quit()));
loop.exec();
}
}
void QGtkDialog::show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) {
connect(parent, &QWindow::destroyed, this, &QGtkDialog::onParentWindowDestroyed,
Qt::UniqueConnection);
setParent(parent);
setFlags(flags);
setModality(modality);
Libs::gtk_widget_realize(gtkWidget); // creates X window
if (parent) {
Platform::internal::XSetTransientForHint(Libs::gtk_widget_get_window(gtkWidget), parent->winId());
}
if (modality != Qt::NonModal) {
Libs::gdk_window_set_modal_hint(Libs::gtk_widget_get_window(gtkWidget), true);
QGuiApplicationPrivate::showModalWindow(this);
}
Libs::gtk_widget_show(gtkWidget);
Libs::gdk_window_focus(Libs::gtk_widget_get_window(gtkWidget), 0);
}
void QGtkDialog::hide() {
QGuiApplicationPrivate::hideModalWindow(this);
Libs::gtk_widget_hide(gtkWidget);
}
void QGtkDialog::onResponse(QGtkDialog *dialog, int response) {
if (response == GTK_RESPONSE_OK)
emit dialog->accept();
else
emit dialog->reject();
}
void QGtkDialog::onUpdatePreview(QGtkDialog* dialog) {
auto filename = Libs::gtk_file_chooser_get_preview_filename(Libs::gtk_file_chooser_cast(dialog->gtkWidget));
if (!filename) {
Libs::gtk_file_chooser_set_preview_widget_active(Libs::gtk_file_chooser_cast(dialog->gtkWidget), false);
return;
}
// Don't attempt to open anything which isn't a regular file. If a named pipe,
// this may hang. See https://crbug.com/534754.
struct stat stat_buf;
if (stat(filename, &stat_buf) != 0 || !S_ISREG(stat_buf.st_mode)) {
g_free(filename);
Libs::gtk_file_chooser_set_preview_widget_active(Libs::gtk_file_chooser_cast(dialog->gtkWidget), false);
return;
}
// This will preserve the image's aspect ratio.
auto pixbuf = Libs::gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth, kPreviewHeight, nullptr);
g_free(filename);
if (pixbuf) {
Libs::gtk_image_set_from_pixbuf(Libs::gtk_image_cast(dialog->_preview), pixbuf);
g_object_unref(pixbuf);
}
Libs::gtk_file_chooser_set_preview_widget_active(Libs::gtk_file_chooser_cast(dialog->gtkWidget), pixbuf ? true : false);
}
void QGtkDialog::onParentWindowDestroyed() {
// The Gtk*DialogHelper classes own this object. Make sure the parent doesn't delete it.
setParent(nullptr);
}
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
namespace {
const char *filterRegExp =
"^(.*)\\(([a-zA-Z0-9_.,*? +;#\\-\\[\\]@\\{\\}/!<>\\$%&=^~:\\|]*)\\)$";
QStringList makeFilterList(const QString &filter) {
QString f(filter);
if (f.isEmpty())
return QStringList();
QString sep(QLatin1String(";;"));
int i = f.indexOf(sep, 0);
if (i == -1) {
if (f.indexOf(QLatin1Char('\n'), 0) != -1) {
sep = QLatin1Char('\n');
i = f.indexOf(sep, 0);
}
}
return f.split(sep);
}
// Makes a list of filters from a normal filter string "Image Files (*.png *.jpg)"
QStringList cleanFilterList(const QString &filter) {
QRegExp regexp(QString::fromLatin1(filterRegExp));
Q_ASSERT(regexp.isValid());
QString f = filter;
int i = regexp.indexIn(f);
if (i >= 0)
f = regexp.cap(2);
return f.split(QLatin1Char(' '), base::QStringSkipEmptyParts);
}
} // namespace
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
GtkFileDialog::GtkFileDialog(QWidget *parent, const QString &caption, const QString &directory, const QString &filter) : QDialog(parent)
, _windowTitle(caption)
, _initialDirectory(directory) {
auto filters = makeFilterList(filter);
const int numFilters = filters.count();
_nameFilters.reserve(numFilters);
for (int i = 0; i < numFilters; ++i) {
_nameFilters << filters[i].simplified();
}
d.reset(new QGtkDialog(Libs::gtk_file_chooser_dialog_new("", nullptr,
GTK_FILE_CHOOSER_ACTION_OPEN,
// https://developer.gnome.org/gtk3/stable/GtkFileChooserDialog.html#gtk-file-chooser-dialog-new
// first_button_text doesn't need explicit conversion to char*, while all others are vardict
tr::lng_cancel(tr::now).toUtf8(), GTK_RESPONSE_CANCEL,
tr::lng_box_ok(tr::now).toUtf8().constData(), GTK_RESPONSE_OK, nullptr)));
connect(d.data(), SIGNAL(accept()), this, SLOT(onAccepted()));
connect(d.data(), SIGNAL(reject()), this, SLOT(onRejected()));
g_signal_connect(Libs::gtk_file_chooser_cast(d->gtkDialog()), "selection-changed", G_CALLBACK(onSelectionChanged), this);
g_signal_connect_swapped(Libs::gtk_file_chooser_cast(d->gtkDialog()), "current-folder-changed", G_CALLBACK(onCurrentFolderChanged), this);
}
GtkFileDialog::~GtkFileDialog() {
}
void GtkFileDialog::showHelper(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) {
_dir.clear();
_selection.clear();
applyOptions();
return d->show(flags, modality, parent);
}
void GtkFileDialog::setVisible(bool visible) {
if (visible) {
if (testAttribute(Qt::WA_WState_ExplicitShowHide) && !testAttribute(Qt::WA_WState_Hidden)) {
return;
}
} else if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden)) {
return;
}
if (visible) {
showHelper(windowFlags(), windowModality(), parentWidget() ? parentWidget()->windowHandle() : nullptr);
} else {
hideHelper();
}
// Set WA_DontShowOnScreen so that QDialog::setVisible(visible) below
// updates the state correctly, but skips showing the non-native version:
setAttribute(Qt::WA_DontShowOnScreen);
QDialog::setVisible(visible);
}
int GtkFileDialog::exec() {
d->setModality(windowModality());
bool deleteOnClose = testAttribute(Qt::WA_DeleteOnClose);
setAttribute(Qt::WA_DeleteOnClose, false);
bool wasShowModal = testAttribute(Qt::WA_ShowModal);
setAttribute(Qt::WA_ShowModal, true);
setResult(0);
show();
QPointer<QDialog> guard = this;
d->exec();
if (guard.isNull())
return QDialog::Rejected;
setAttribute(Qt::WA_ShowModal, wasShowModal);
return result();
}
void GtkFileDialog::hideHelper() {
// After GtkFileChooserDialog has been hidden, gtk_file_chooser_get_current_folder()
// & gtk_file_chooser_get_filenames() will return bogus values -> cache the actual
// values before hiding the dialog
_dir = directory().absolutePath();
_selection = selectedFiles();
d->hide();
}
bool GtkFileDialog::defaultNameFilterDisables() const {
return false;
}
void GtkFileDialog::setDirectory(const QString &directory) {
GtkDialog *gtkDialog = d->gtkDialog();
Libs::gtk_file_chooser_set_current_folder(Libs::gtk_file_chooser_cast(gtkDialog), directory.toUtf8());
}
QDir GtkFileDialog::directory() const {
// While GtkFileChooserDialog is hidden, gtk_file_chooser_get_current_folder()
// returns a bogus value -> return the cached value before hiding
if (!_dir.isEmpty())
return _dir;
QString ret;
GtkDialog *gtkDialog = d->gtkDialog();
gchar *folder = Libs::gtk_file_chooser_get_current_folder(Libs::gtk_file_chooser_cast(gtkDialog));
if (folder) {
ret = QString::fromUtf8(folder);
g_free(folder);
}
return QDir(ret);
}
void GtkFileDialog::selectFile(const QString &filename) {
_initialFiles.clear();
_initialFiles.append(filename);
}
QStringList GtkFileDialog::selectedFiles() const {
// While GtkFileChooserDialog is hidden, gtk_file_chooser_get_filenames()
// returns a bogus value -> return the cached value before hiding
if (!_selection.isEmpty())
return _selection;
QStringList selection;
GtkDialog *gtkDialog = d->gtkDialog();
GSList *filenames = Libs::gtk_file_chooser_get_filenames(Libs::gtk_file_chooser_cast(gtkDialog));
for (GSList *it = filenames; it; it = it->next)
selection += QString::fromUtf8((const char*)it->data);
g_slist_free(filenames);
return selection;
}
void GtkFileDialog::setFilter() {
applyOptions();
}
void GtkFileDialog::selectNameFilter(const QString &filter) {
GtkFileFilter *gtkFilter = _filters.value(filter);
if (gtkFilter) {
GtkDialog *gtkDialog = d->gtkDialog();
Libs::gtk_file_chooser_set_filter(Libs::gtk_file_chooser_cast(gtkDialog), gtkFilter);
}
}
QString GtkFileDialog::selectedNameFilter() const {
GtkDialog *gtkDialog = d->gtkDialog();
GtkFileFilter *gtkFilter = Libs::gtk_file_chooser_get_filter(Libs::gtk_file_chooser_cast(gtkDialog));
return _filterNames.value(gtkFilter);
}
void GtkFileDialog::onAccepted() {
emit accept();
// QString filter = selectedNameFilter();
// if (filter.isEmpty())
// emit filterSelected(filter);
// QList<QUrl> files = selectedFiles();
// emit filesSelected(files);
// if (files.count() == 1)
// emit fileSelected(files.first());
}
void GtkFileDialog::onRejected() {
emit reject();
//
}
void GtkFileDialog::onSelectionChanged(GtkDialog *gtkDialog, GtkFileDialog *helper) {
// QString selection;
// gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(gtkDialog));
// if (filename) {
// selection = QString::fromUtf8(filename);
// g_free(filename);
// }
// emit helper->currentChanged(QUrl::fromLocalFile(selection));
}
void GtkFileDialog::onCurrentFolderChanged(GtkFileDialog *dialog) {
// emit dialog->directoryEntered(dialog->directory());
}
GtkFileChooserAction gtkFileChooserAction(QFileDialog::FileMode fileMode, QFileDialog::AcceptMode acceptMode) {
switch (fileMode) {
case QFileDialog::AnyFile:
case QFileDialog::ExistingFile:
case QFileDialog::ExistingFiles:
if (acceptMode == QFileDialog::AcceptOpen)
return GTK_FILE_CHOOSER_ACTION_OPEN;
else
return GTK_FILE_CHOOSER_ACTION_SAVE;
case QFileDialog::Directory:
default:
if (acceptMode == QFileDialog::AcceptOpen)
return GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
else
return GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER;
}
}
bool CustomButtonsSupported() {
return (Libs::gtk_dialog_get_widget_for_response != nullptr)
&& (Libs::gtk_button_set_label != nullptr)
&& (Libs::gtk_button_get_type != nullptr);
}
void GtkFileDialog::applyOptions() {
GtkDialog *gtkDialog = d->gtkDialog();
Libs::gtk_window_set_title(Libs::gtk_window_cast(gtkDialog), _windowTitle.toUtf8());
Libs::gtk_file_chooser_set_local_only(Libs::gtk_file_chooser_cast(gtkDialog), true);
const GtkFileChooserAction action = gtkFileChooserAction(_fileMode, _acceptMode);
Libs::gtk_file_chooser_set_action(Libs::gtk_file_chooser_cast(gtkDialog), action);
const bool selectMultiple = (_fileMode == QFileDialog::ExistingFiles);
Libs::gtk_file_chooser_set_select_multiple(Libs::gtk_file_chooser_cast(gtkDialog), selectMultiple);
const bool confirmOverwrite = !_options.testFlag(QFileDialog::DontConfirmOverwrite);
Libs::gtk_file_chooser_set_do_overwrite_confirmation(Libs::gtk_file_chooser_cast(gtkDialog), confirmOverwrite);
if (!_nameFilters.isEmpty())
setNameFilters(_nameFilters);
if (!_initialDirectory.isEmpty())
setDirectory(_initialDirectory);
for_const (const auto &filename, _initialFiles) {
if (_acceptMode == QFileDialog::AcceptSave) {
QFileInfo fi(filename);
Libs::gtk_file_chooser_set_current_folder(Libs::gtk_file_chooser_cast(gtkDialog), fi.path().toUtf8());
Libs::gtk_file_chooser_set_current_name(Libs::gtk_file_chooser_cast(gtkDialog), fi.fileName().toUtf8());
} else if (filename.endsWith('/')) {
Libs::gtk_file_chooser_set_current_folder(Libs::gtk_file_chooser_cast(gtkDialog), filename.toUtf8());
} else {
Libs::gtk_file_chooser_select_filename(Libs::gtk_file_chooser_cast(gtkDialog), filename.toUtf8());
}
}
const QString initialNameFilter = _nameFilters.isEmpty() ? QString() : _nameFilters.front();
if (!initialNameFilter.isEmpty())
selectNameFilter(initialNameFilter);
if (CustomButtonsSupported()) {
GtkWidget *acceptButton = Libs::gtk_dialog_get_widget_for_response(gtkDialog, GTK_RESPONSE_OK);
if (acceptButton) {
/*if (opts->isLabelExplicitlySet(QFileDialogOptions::Accept))
Libs::gtk_button_set_label(Libs::gtk_button_cast(acceptButton), opts->labelText(QFileDialogOptions::Accept).toUtf8());
else*/ if (_acceptMode == QFileDialog::AcceptOpen)
Libs::gtk_button_set_label(Libs::gtk_button_cast(acceptButton), tr::lng_open_link(tr::now).toUtf8());
else
Libs::gtk_button_set_label(Libs::gtk_button_cast(acceptButton), tr::lng_settings_save(tr::now).toUtf8());
}
GtkWidget *rejectButton = Libs::gtk_dialog_get_widget_for_response(gtkDialog, GTK_RESPONSE_CANCEL);
if (rejectButton) {
/*if (opts->isLabelExplicitlySet(QFileDialogOptions::Reject))
Libs::gtk_button_set_label(Libs::gtk_button_cast(rejectButton), opts->labelText(QFileDialogOptions::Reject).toUtf8());
else*/
Libs::gtk_button_set_label(Libs::gtk_button_cast(rejectButton), tr::lng_cancel(tr::now).toUtf8());
}
}
}
void GtkFileDialog::setNameFilters(const QStringList &filters) {
GtkDialog *gtkDialog = d->gtkDialog();
foreach (GtkFileFilter *filter, _filters)
Libs::gtk_file_chooser_remove_filter(Libs::gtk_file_chooser_cast(gtkDialog), filter);
_filters.clear();
_filterNames.clear();
for_const (auto &filter, filters) {
GtkFileFilter *gtkFilter = Libs::gtk_file_filter_new();
auto name = filter;//.left(filter.indexOf(QLatin1Char('(')));
auto extensions = cleanFilterList(filter);
Libs::gtk_file_filter_set_name(gtkFilter, name.isEmpty() ? extensions.join(QStringLiteral(", ")).toUtf8() : name.toUtf8());
for_const (auto &ext, extensions) {
auto caseInsensitiveExt = QString();
caseInsensitiveExt.reserve(4 * ext.size());
for_const (auto ch, ext) {
auto chLower = ch.toLower();
auto chUpper = ch.toUpper();
if (chLower != chUpper) {
caseInsensitiveExt.append('[').append(chLower).append(chUpper).append(']');
} else {
caseInsensitiveExt.append(ch);
}
}
Libs::gtk_file_filter_add_pattern(gtkFilter, caseInsensitiveExt.toUtf8());
}
Libs::gtk_file_chooser_add_filter(Libs::gtk_file_chooser_cast(gtkDialog), gtkFilter);
_filters.insert(filter, gtkFilter);
_filterNames.insert(gtkFilter, filter);
}
}
} // namespace internal
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
} // namespace FileDialog
} // namespace Platform

View File

@@ -9,22 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "platform/platform_file_utilities.h"
#include <QtGui/QWindow>
#include <QtWidgets/QFileDialog>
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
typedef struct _GtkWidget GtkWidget;
typedef struct _GtkDialog GtkDialog;
typedef struct _GtkFileFilter GtkFileFilter;
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
namespace Platform {
namespace File {
namespace internal {
QByteArray EscapeShell(const QByteArray &content);
} // namespace internal
inline QString UrlToLocal(const QUrl &url) {
return ::File::internal::UrlToLocalDefault(url);
@@ -45,114 +31,5 @@ inline void InitLastPath() {
::FileDialog::internal::InitLastPathDefault();
}
namespace internal {
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
// This is a patched copy of qgtk2 theme plugin.
// We need to use our own gtk file dialog instead of
// styling Qt file dialog, because Qt only works with gtk2.
// We need to be able to work with gtk2 and gtk3, because
// we use gtk3 to work with appindicator3.
class QGtkDialog : public QWindow {
Q_OBJECT
public:
QGtkDialog(GtkWidget *gtkWidget);
~QGtkDialog();
GtkDialog *gtkDialog() const;
void exec();
void show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent);
void hide();
signals:
void accept();
void reject();
protected:
static void onResponse(QGtkDialog *dialog, int response);
static void onUpdatePreview(QGtkDialog *dialog);
private slots:
void onParentWindowDestroyed();
private:
GtkWidget *gtkWidget;
GtkWidget *_preview = nullptr;
};
class GtkFileDialog : public QDialog {
Q_OBJECT
public:
GtkFileDialog(QWidget *parent = Q_NULLPTR,
const QString &caption = QString(),
const QString &directory = QString(),
const QString &filter = QString());
~GtkFileDialog();
void setVisible(bool visible) override;
void setWindowTitle(const QString &windowTitle) {
_windowTitle = windowTitle;
}
void setAcceptMode(QFileDialog::AcceptMode acceptMode) {
_acceptMode = acceptMode;
}
void setFileMode(QFileDialog::FileMode fileMode) {
_fileMode = fileMode;
}
void setOption(QFileDialog::Option option, bool on = true) {
if (on) {
_options |= option;
} else {
_options &= ~option;
}
}
int exec() override;
bool defaultNameFilterDisables() const;
void setDirectory(const QString &directory);
QDir directory() const;
void selectFile(const QString &filename);
QStringList selectedFiles() const;
void setFilter();
void selectNameFilter(const QString &filter);
QString selectedNameFilter() const;
private slots:
void onAccepted();
void onRejected();
private:
static void onSelectionChanged(GtkDialog *dialog, GtkFileDialog *helper);
static void onCurrentFolderChanged(GtkFileDialog *helper);
void applyOptions();
void setNameFilters(const QStringList &filters);
void showHelper(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent);
void hideHelper();
// Options
QFileDialog::Options _options;
QString _windowTitle = "Choose file";
QString _initialDirectory;
QStringList _initialFiles;
QStringList _nameFilters;
QFileDialog::AcceptMode _acceptMode = QFileDialog::AcceptOpen;
QFileDialog::FileMode _fileMode = QFileDialog::ExistingFile;
QString _dir;
QStringList _selection;
QHash<QString, GtkFileFilter*> _filters;
QHash<GtkFileFilter*, QString> _filterNames;
QScopedPointer<QGtkDialog> d;
};
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
} // namespace internal
} // namespace FileDialog
} // namespace Platform

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

@@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "platform/linux/linux_desktop_environment.h"
#include "platform/linux/specific_linux.h"
#include "base/qt_adapters.h"
namespace Platform {

View File

@@ -5,12 +5,9 @@ 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
*/
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
#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"
#include "platform/linux/linux_gtk_integration_p.h"
extern "C" {
#undef signals
@@ -21,6 +18,8 @@ extern "C" {
namespace Platform {
namespace internal {
using namespace Platform::Gtk;
enum class GtkLoaded {
GtkNone,
Gtk2,
@@ -33,6 +32,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;
@@ -43,24 +45,33 @@ f_gdk_x11_window_get_type gdk_x11_window_get_type = nullptr;
// To be able to compile with gtk-2.0 headers as well
template <typename Object>
inline bool gdk_is_x11_window_check(Object *obj) {
return Libs::g_type_cit_helper(obj, gdk_x11_window_get_type());
return 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;
bool GdkHelperLoadGtk2(QLibrary &lib) {
#if defined DESKTOP_APP_USE_PACKAGED && !defined DESKTOP_APP_USE_PACKAGED_LAZY
#ifdef LINK_TO_GTK
return false;
#else // DESKTOP_APP_USE_PACKAGED && !DESKTOP_APP_USE_PACKAGED_LAZY
if (!LOAD_SYMBOL(lib, "gdk_x11_drawable_get_xid", gdk_x11_drawable_get_xid)) return false;
#else // LINK_TO_GTK
if (!LOAD_GTK_SYMBOL(lib, "gdk_x11_drawable_get_xdisplay", gdk_x11_drawable_get_xdisplay)) return false;
if (!LOAD_GTK_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
#endif // !LINK_TO_GTK
}
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_x11_window_get_xid", gdk_x11_window_get_xid)) return false;
if (!LOAD_GTK_SYMBOL(lib, "gdk_x11_window_get_type", gdk_x11_window_get_type)) return false;
if (!LOAD_GTK_SYMBOL(lib, "gdk_window_get_display", gdk_window_get_display)) return false;
if (!LOAD_GTK_SYMBOL(lib, "gdk_x11_display_get_xdisplay", gdk_x11_display_get_xdisplay)) return false;
if (!LOAD_GTK_SYMBOL(lib, "gdk_x11_window_get_xid", gdk_x11_window_get_xid)) return false;
return true;
}
@@ -79,32 +90,17 @@ 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);
}
}
}
} // namespace internal
} // namespace Platform
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION

View File

@@ -7,11 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include <QtCore/QObject>
class QLibrary;
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
extern "C" {
#undef signals
#include <gtk/gtk.h>
@@ -28,4 +25,3 @@ void XSetTransientForHint(GdkWindow *window, quintptr winId);
} // namespace internal
} // namespace Platform
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION

View File

@@ -0,0 +1,181 @@
/*
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 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,
-1,
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,
-1,
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

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