Compare commits
501 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60cbd96d91 | ||
|
|
ee0400f1ac | ||
|
|
b8a3746558 | ||
|
|
14f25fc997 | ||
|
|
27da6ee9eb | ||
|
|
48d482006a | ||
|
|
9afee2620a | ||
|
|
baca3047d4 | ||
|
|
43a5265e0c | ||
|
|
5519bb3523 | ||
|
|
ff213d1386 | ||
|
|
55b3f99653 | ||
|
|
8a6ff3f414 | ||
|
|
feb8624d05 | ||
|
|
a2c33545d4 | ||
|
|
7decf68122 | ||
|
|
6b62ec97c6 | ||
|
|
ea3dab4a06 | ||
|
|
5c8f08fc92 | ||
|
|
00a0b2c8b6 | ||
|
|
007218cc13 | ||
|
|
8afe495a4f | ||
|
|
257f2086d1 | ||
|
|
f011c84ce8 | ||
|
|
8b839f46b2 | ||
|
|
c1067d8fe1 | ||
|
|
bb76818cc8 | ||
|
|
5dcc219f1c | ||
|
|
4ff9e90153 | ||
|
|
28fe98af80 | ||
|
|
5eba65aaa0 | ||
|
|
d1e3e7d240 | ||
|
|
90ff8ecd0f | ||
|
|
468e75a572 | ||
|
|
ae3e5487d7 | ||
|
|
03147a5426 | ||
|
|
14a2b10989 | ||
|
|
b29f8aa1e6 | ||
|
|
f9bb932cd8 | ||
|
|
a38cbbf7e8 | ||
|
|
d5bb1717e0 | ||
|
|
520ff8f2ce | ||
|
|
b3848f6a84 | ||
|
|
518f387e0c | ||
|
|
635f76a312 | ||
|
|
ff14ac68ee | ||
|
|
948c5d50cb | ||
|
|
6b520ecc05 | ||
|
|
bb474686eb | ||
|
|
659ddae9a8 | ||
|
|
b70276912e | ||
|
|
858c575782 | ||
|
|
62fe14d592 | ||
|
|
6ad037e556 | ||
|
|
a55b41faa1 | ||
|
|
a26d769304 | ||
|
|
e1120d1cb5 | ||
|
|
8897f9e46a | ||
|
|
7a588be54f | ||
|
|
1cb1f1cbc1 | ||
|
|
5827d6ffdb | ||
|
|
766bc90921 | ||
|
|
eb228eb744 | ||
|
|
7c02d67665 | ||
|
|
23c54896e5 | ||
|
|
460baa54d8 | ||
|
|
6c56fad180 | ||
|
|
3fd772ce17 | ||
|
|
8834ec8bf2 | ||
|
|
003fb52fb9 | ||
|
|
ec234cdc43 | ||
|
|
55e494f55a | ||
|
|
0b4605a656 | ||
|
|
18a86e500b | ||
|
|
e19af1257c | ||
|
|
e0159e15b2 | ||
|
|
b6e77537e2 | ||
|
|
beaa4190eb | ||
|
|
7924979dfb | ||
|
|
570ed5691d | ||
|
|
c25779b844 | ||
|
|
7304f2b695 | ||
|
|
b4bff939b1 | ||
|
|
1f816c249b | ||
|
|
8591d58798 | ||
|
|
9290cd3a16 | ||
|
|
dc0aaec4a4 | ||
|
|
eefa7263b5 | ||
|
|
7885be4a94 | ||
|
|
583c3d3429 | ||
|
|
6d0d399250 | ||
|
|
0e89c93993 | ||
|
|
b422ec025e | ||
|
|
c8535acad8 | ||
|
|
e2a97e2ae9 | ||
|
|
77a019325d | ||
|
|
115dc460ac | ||
|
|
3df1a73cf5 | ||
|
|
5cf69366d1 | ||
|
|
8c2b1168af | ||
|
|
a425024f21 | ||
|
|
e85026ec46 | ||
|
|
1c6e2eae04 | ||
|
|
4db5624beb | ||
|
|
6d08542afa | ||
|
|
707b36dc12 | ||
|
|
da3e140069 | ||
|
|
5334372671 | ||
|
|
16db8468fa | ||
|
|
2ed3543b53 | ||
|
|
5b4d442799 | ||
|
|
9669a8a44a | ||
|
|
80fe2f57e9 | ||
|
|
824fbc21e8 | ||
|
|
bf7f117323 | ||
|
|
9b488f03a1 | ||
|
|
295a863d69 | ||
|
|
0d814066d6 | ||
|
|
1af8e89eb9 | ||
|
|
7cf79e1f8a | ||
|
|
019fd83c8a | ||
|
|
65779ec37e | ||
|
|
2d90a06078 | ||
|
|
d2c8780c0f | ||
|
|
54dd63d61a | ||
|
|
7852c82eab | ||
|
|
77c8bf8176 | ||
|
|
daa14466e5 | ||
|
|
aad38c2809 | ||
|
|
2c50d3d87b | ||
|
|
0fe7c07007 | ||
|
|
c22d200c17 | ||
|
|
9e6afa0d4e | ||
|
|
3340b2dc03 | ||
|
|
386fae952b | ||
|
|
a164cb9480 | ||
|
|
bc9b288617 | ||
|
|
5c7229f875 | ||
|
|
658d5a1322 | ||
|
|
52e841ec29 | ||
|
|
df28da4d97 | ||
|
|
8dac6896d6 | ||
|
|
f18e157e46 | ||
|
|
b2bf8244dd | ||
|
|
4e0355d09f | ||
|
|
7059336ff0 | ||
|
|
94f10ce72e | ||
|
|
68be54288c | ||
|
|
10636d931f | ||
|
|
3b1aa55d21 | ||
|
|
37f59095f4 | ||
|
|
f85d1b8a29 | ||
|
|
ece491eee7 | ||
|
|
750c13e5fe | ||
|
|
0fde35f59e | ||
|
|
5ab8a7d9c5 | ||
|
|
b1fad4f7e0 | ||
|
|
4f0f815201 | ||
|
|
f2286fdffd | ||
|
|
46dfde881a | ||
|
|
95f7704d14 | ||
|
|
64a6838764 | ||
|
|
a0c2318919 | ||
|
|
8d8fffd306 | ||
|
|
ecb53e3e0b | ||
|
|
468d43c4c7 | ||
|
|
425a632965 | ||
|
|
bf581a1ba4 | ||
|
|
7a0ba58ffd | ||
|
|
4543656aa3 | ||
|
|
8d72026cbd | ||
|
|
32e47c24b4 | ||
|
|
69eaecc218 | ||
|
|
3ffbb94fdb | ||
|
|
4f8bab6a5f | ||
|
|
2f0fd398d5 | ||
|
|
c6fde48936 | ||
|
|
8356bac6d7 | ||
|
|
b553520a48 | ||
|
|
3c86da78af | ||
|
|
fdbf63229d | ||
|
|
d22601461a | ||
|
|
13c70a9ce9 | ||
|
|
1cfbf24635 | ||
|
|
f90f1c02c3 | ||
|
|
8d0f5bb828 | ||
|
|
2f986660ff | ||
|
|
f9f98975a1 | ||
|
|
e0e2b973f1 | ||
|
|
aeb994dd40 | ||
|
|
112c597556 | ||
|
|
fc94045f41 | ||
|
|
2a8055c513 | ||
|
|
fba116f0d5 | ||
|
|
b38f89d69e | ||
|
|
84f6a5f957 | ||
|
|
054223efe0 | ||
|
|
de3ea30d69 | ||
|
|
c5a46d9d1b | ||
|
|
e41fb0d8fd | ||
|
|
5970f3de9e | ||
|
|
7878552e7d | ||
|
|
b3648d0147 | ||
|
|
dd79b3c0d5 | ||
|
|
3d76e6de55 | ||
|
|
ef61443342 | ||
|
|
4f8989fad7 | ||
|
|
945411274f | ||
|
|
f1f7330bf6 | ||
|
|
a1957fe5c0 | ||
|
|
9a21d55de7 | ||
|
|
303ad02c61 | ||
|
|
1886a5c4ed | ||
|
|
71ddfacfaa | ||
|
|
23c2bce1bb | ||
|
|
5324a626be | ||
|
|
4c5421916a | ||
|
|
2801bd99b8 | ||
|
|
38a0eb3b52 | ||
|
|
607263b8be | ||
|
|
42b62e90ca | ||
|
|
f6f0b02333 | ||
|
|
1858e7e8ac | ||
|
|
482ad74c57 | ||
|
|
68ae40ee56 | ||
|
|
24f8a88625 | ||
|
|
fc78769e9c | ||
|
|
5c54d3690c | ||
|
|
2cd8b00610 | ||
|
|
7ee35bc80c | ||
|
|
cfbbce26c4 | ||
|
|
dfb26cabfc | ||
|
|
70f0cce340 | ||
|
|
3af0c37c6b | ||
|
|
b1906a778e | ||
|
|
d752aa3481 | ||
|
|
afc5191644 | ||
|
|
254b02ad6b | ||
|
|
885365a1c2 | ||
|
|
245be4cd63 | ||
|
|
ce413f2946 | ||
|
|
0d84ba406f | ||
|
|
9047b3c121 | ||
|
|
3ff9543106 | ||
|
|
46b4a5fc5a | ||
|
|
bd456568ed | ||
|
|
93fa0e1df5 | ||
|
|
fe4c5155eb | ||
|
|
24c435bb5f | ||
|
|
e6977b2c33 | ||
|
|
28f83f2af4 | ||
|
|
5a6e8a0a8c | ||
|
|
003da28699 | ||
|
|
e8dd969e78 | ||
|
|
acce2a217d | ||
|
|
b964c681f8 | ||
|
|
c6dcc57c5e | ||
|
|
cf8e1cfd0f | ||
|
|
78b40a1f66 | ||
|
|
2fe75f8296 | ||
|
|
b22363224f | ||
|
|
b3c92ed3f4 | ||
|
|
464b0a0f30 | ||
|
|
dfcc13c7e6 | ||
|
|
ba6cee6f81 | ||
|
|
bcdfd2150d | ||
|
|
d19d6bbcd9 | ||
|
|
4080fa9bdc | ||
|
|
ce091b0b63 | ||
|
|
136e930362 | ||
|
|
78dfe940ef | ||
|
|
690fbe83fd | ||
|
|
f98e8f3e04 | ||
|
|
63febef3ed | ||
|
|
2599ae45d6 | ||
|
|
bfb03621c2 | ||
|
|
be53bd5293 | ||
|
|
a429500b57 | ||
|
|
ec9fa00f46 | ||
|
|
6a001f2e6c | ||
|
|
8bde53cd0f | ||
|
|
090d7d7112 | ||
|
|
97c7c0742c | ||
|
|
90efbf1210 | ||
|
|
38506d27a1 | ||
|
|
8a693bc932 | ||
|
|
0e49bf5dee | ||
|
|
6a967948de | ||
|
|
0771fc14db | ||
|
|
e1614a280f | ||
|
|
ddf81c949b | ||
|
|
b906b2f625 | ||
|
|
3f2b473287 | ||
|
|
9a9430b5e1 | ||
|
|
d659200a42 | ||
|
|
cb630c69f0 | ||
|
|
9a812090a2 | ||
|
|
5b0278847d | ||
|
|
9d07bb2946 | ||
|
|
b27d314fa7 | ||
|
|
df666ff724 | ||
|
|
3709714339 | ||
|
|
deecf80f20 | ||
|
|
6ea66bc527 | ||
|
|
513c8d1a65 | ||
|
|
49f71f4e1e | ||
|
|
0c5258b43a | ||
|
|
a0506f009a | ||
|
|
9f93dae6f9 | ||
|
|
45cca35724 | ||
|
|
1c42513e44 | ||
|
|
f3e6f5e772 | ||
|
|
c6f44e7928 | ||
|
|
7b6b32db74 | ||
|
|
e39f9bef1f | ||
|
|
479b604c0e | ||
|
|
e7ef3c4b6d | ||
|
|
87cae1c3a7 | ||
|
|
562fc74481 | ||
|
|
51d8e9c43d | ||
|
|
e50a7a2e42 | ||
|
|
aaad250a77 | ||
|
|
699730b7f4 | ||
|
|
302cffba1c | ||
|
|
e299aa032d | ||
|
|
ca6f70746c | ||
|
|
2af1d95650 | ||
|
|
df6f5d83d6 | ||
|
|
42baa3e1bc | ||
|
|
5f393babd6 | ||
|
|
b864563f47 | ||
|
|
3edb2d08ba | ||
|
|
d44f923277 | ||
|
|
ec468431b4 | ||
|
|
4774f438a9 | ||
|
|
f40659a7b4 | ||
|
|
047989abcf | ||
|
|
3e79b67032 | ||
|
|
ca4b1e6ae0 | ||
|
|
b56749426b | ||
|
|
cbe6e1caad | ||
|
|
748eb9ff12 | ||
|
|
385b98ff3d | ||
|
|
c12a50544e | ||
|
|
c64e953174 | ||
|
|
ccc599c83e | ||
|
|
a45064257a | ||
|
|
9510ba07f7 | ||
|
|
8e3dc76dd7 | ||
|
|
451332b2e7 | ||
|
|
445c798bbc | ||
|
|
c48c4d4283 | ||
|
|
b421d0c5cc | ||
|
|
f7454a4284 | ||
|
|
9144f4ea7b | ||
|
|
aaea367fba | ||
|
|
e0e878cbb1 | ||
|
|
b905a18161 | ||
|
|
f4ae7ecbe7 | ||
|
|
9a8812d00b | ||
|
|
13b3de683a | ||
|
|
64243d1437 | ||
|
|
a730c88491 | ||
|
|
bd90cc4134 | ||
|
|
316f0537c4 | ||
|
|
7f739065e8 | ||
|
|
bd83ed8130 | ||
|
|
e39ffbc83c | ||
|
|
1471e9b8e2 | ||
|
|
4c23d51be5 | ||
|
|
412cfb24d2 | ||
|
|
2a5977e97f | ||
|
|
64c34b7029 | ||
|
|
0db0abe608 | ||
|
|
5f4903a279 | ||
|
|
20ff79abf4 | ||
|
|
3a321d64f6 | ||
|
|
7e8d1f7974 | ||
|
|
9f41461209 | ||
|
|
6b10045b7b | ||
|
|
9ca6d0d893 | ||
|
|
2830049a53 | ||
|
|
50558de591 | ||
|
|
80e3e8a01e | ||
|
|
d38780c94d | ||
|
|
801435e57c | ||
|
|
8001efe6ab | ||
|
|
909a3cef9b | ||
|
|
9ac510a1ad | ||
|
|
00ce302b38 | ||
|
|
0dcc7a05f7 | ||
|
|
54c2769d8a | ||
|
|
2e400d88d3 | ||
|
|
d9aa660253 | ||
|
|
ba1dade4b0 | ||
|
|
a48649987e | ||
|
|
022c0a1327 | ||
|
|
69ceed5bbc | ||
|
|
b3fcb4ef36 | ||
|
|
8342b2d275 | ||
|
|
36888f844f | ||
|
|
75f220c3d9 | ||
|
|
1a784fc678 | ||
|
|
dac9017df1 | ||
|
|
7b3b5a1463 | ||
|
|
b7fc3f67d7 | ||
|
|
e0bfaad3a2 | ||
|
|
24c77a8956 | ||
|
|
380a0d1f86 | ||
|
|
b7f6fc9a2d | ||
|
|
e12fe974b2 | ||
|
|
b15623d435 | ||
|
|
eb8f709943 | ||
|
|
c93ddf6aac | ||
|
|
6e34360f7e | ||
|
|
c9d07cd0f8 | ||
|
|
9ff6b57b94 | ||
|
|
fb49b0ca27 | ||
|
|
fef1f80570 | ||
|
|
38cb1b195d | ||
|
|
ebdbe4a8d6 | ||
|
|
ba02a5c46a | ||
|
|
a6f379a17a | ||
|
|
a41b7b62ac | ||
|
|
5010c9033b | ||
|
|
93e4161d5e | ||
|
|
e0d6faf45b | ||
|
|
fbe4e3f0ec | ||
|
|
8e02c50f7d | ||
|
|
837485974a | ||
|
|
3cf739eca9 | ||
|
|
cfee688feb | ||
|
|
30d8894c30 | ||
|
|
0b86feeeb5 | ||
|
|
434ef34378 | ||
|
|
166c28c215 | ||
|
|
17c514e851 | ||
|
|
f7489592d6 | ||
|
|
3cb9312805 | ||
|
|
3722486b19 | ||
|
|
57b3982346 | ||
|
|
a8807bc915 | ||
|
|
71deaa48af | ||
|
|
e7ca35a276 | ||
|
|
2d8f43bd8c | ||
|
|
383acf0ffc | ||
|
|
ee156fc6a8 | ||
|
|
680a9a7ca7 | ||
|
|
7de8d6f9ac | ||
|
|
d79fab8b3c | ||
|
|
0cb32181c5 | ||
|
|
dba3c39726 | ||
|
|
95b4435396 | ||
|
|
f1a9884011 | ||
|
|
691dcb8ae1 | ||
|
|
db6b571f60 | ||
|
|
5ce1b00291 | ||
|
|
8332ba8450 | ||
|
|
b1c4524612 | ||
|
|
9a857659ce | ||
|
|
68dc00be27 | ||
|
|
ee00f12131 | ||
|
|
7444f17c4e | ||
|
|
99e70f7783 | ||
|
|
578833446d | ||
|
|
4fae827f1e | ||
|
|
98180d3a9e | ||
|
|
434a4af9ef | ||
|
|
298215542e | ||
|
|
197b3c1cb5 | ||
|
|
3cad89f299 | ||
|
|
99b9a46428 | ||
|
|
56a5363eb9 | ||
|
|
b1c95d719a | ||
|
|
d87ea056c6 | ||
|
|
34534a9653 | ||
|
|
5d0222b1c1 | ||
|
|
b72260f420 | ||
|
|
896eee9841 | ||
|
|
0d96657c33 | ||
|
|
41078869a9 | ||
|
|
89b11ef084 | ||
|
|
26d3995424 | ||
|
|
b6fad35146 | ||
|
|
70bf328e7d | ||
|
|
404538c989 | ||
|
|
9c9fc9e881 | ||
|
|
1d089366ff | ||
|
|
fe40464e33 | ||
|
|
728b1efb9a | ||
|
|
175f3d7a38 | ||
|
|
ae1fb8841a | ||
|
|
16ba20f898 | ||
|
|
d8ffc114d3 | ||
|
|
23bd76a8dd | ||
|
|
d85981cca0 | ||
|
|
7b466e0643 | ||
|
|
d984c5924d | ||
|
|
cfa3352caf | ||
|
|
05d2fc819c | ||
|
|
cb930a89ce |
2
.github/stale.yml
vendored
@@ -3,7 +3,7 @@ daysUntilStale: 180
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 30
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels: []
|
||||
exemptLabels: [ "enhancement" ]
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
|
||||
14
.github/workflows/mac.yml
vendored
@@ -205,6 +205,16 @@ jobs:
|
||||
cd $LibrariesPath
|
||||
sudo cp -R opus-cache/. /
|
||||
|
||||
- name: Rnnoise.
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
|
||||
git clone $GIT/desktop-app/rnnoise.git
|
||||
mkdir -p rnnoise/out/Debug
|
||||
cd rnnoise/out/Debug
|
||||
cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ../..
|
||||
ninja
|
||||
|
||||
- name: Libiconv cache.
|
||||
id: cache-libiconv
|
||||
uses: actions/cache@v2
|
||||
@@ -240,7 +250,7 @@ jobs:
|
||||
|
||||
git clone $GIT/FFmpeg/FFmpeg.git ffmpeg
|
||||
cd ffmpeg
|
||||
git checkout release/4.2
|
||||
git checkout release/4.4
|
||||
CFLAGS=`freetype-config --cflags`
|
||||
LDFLAGS=`freetype-config --libs`
|
||||
PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig:/usr/X11/lib/pkgconfig
|
||||
@@ -317,7 +327,6 @@ jobs:
|
||||
--enable-decoder=pcm_u32be \
|
||||
--enable-decoder=pcm_u32le \
|
||||
--enable-decoder=pcm_u8 \
|
||||
--enable-decoder=pcm_zork \
|
||||
--enable-decoder=vorbis \
|
||||
--enable-decoder=wavpack \
|
||||
--enable-decoder=wmalossless \
|
||||
@@ -490,6 +499,7 @@ jobs:
|
||||
cmake -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DTG_OWT_SPECIAL_TARGET=mac \
|
||||
-DTG_OWT_BUILD_AUDIO_BACKENDS=OFF \
|
||||
-DTG_OWT_LIBJPEG_INCLUDE_PATH=$PREFIX/include \
|
||||
-DTG_OWT_OPENSSL_INCLUDE_PATH=`pwd`/../../../openssl_$OPENSSL_VER/include \
|
||||
-DTG_OWT_OPUS_INCLUDE_PATH=$PREFIX/include/opus \
|
||||
|
||||
24
.github/workflows/win.yml
vendored
@@ -114,6 +114,11 @@ jobs:
|
||||
- name: Choco installs.
|
||||
run: choco install --no-progress -y nasm yasm jom ninja
|
||||
|
||||
- name: NuGet sources.
|
||||
run: |
|
||||
nuget sources Disable -Name "Microsoft Visual Studio Offline Packages"
|
||||
nuget sources Add -Source https://api.nuget.org/v3/index.json & exit 0
|
||||
|
||||
- name: Patches.
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -279,6 +284,17 @@ jobs:
|
||||
msbuild -m opus.sln /property:Configuration=Debug /property:Platform="Win32"
|
||||
msbuild -m opus.sln /property:Configuration=Release /property:Platform="Win32"
|
||||
|
||||
- name: Rnnoise.
|
||||
shell: cmd
|
||||
run: |
|
||||
%VC%
|
||||
|
||||
git clone %GIT%/desktop-app/rnnoise.git
|
||||
mkdir rnnoise\out
|
||||
cd rnnoise\out
|
||||
cmake -A Win32 ..
|
||||
cmake --build . --config Debug
|
||||
|
||||
- name: FFmpeg cache.
|
||||
id: cache-ffmpeg
|
||||
uses: actions/cache@v2
|
||||
@@ -293,7 +309,7 @@ jobs:
|
||||
|
||||
git clone %GIT%/FFmpeg/FFmpeg.git ffmpeg
|
||||
cd ffmpeg
|
||||
git checkout release/4.2
|
||||
git checkout release/4.4
|
||||
set CHERE_INVOKING=enabled_from_arguments
|
||||
set MSYS2_PATH_TYPE=inherit
|
||||
call c:\tools\msys64\usr\bin\bash --login ../patches/build_ffmpeg_win.sh
|
||||
@@ -332,7 +348,7 @@ jobs:
|
||||
-confirm-license ^
|
||||
-static ^
|
||||
-static-runtime -I "%SSL%\include" ^
|
||||
-no-opengl ^
|
||||
-opengl dynamic ^
|
||||
-openssl-linked ^
|
||||
OPENSSL_LIBS_DEBUG="%SSL%\out32.dbg\libssl.lib %SSL%\out32.dbg\%LIBS%" ^
|
||||
OPENSSL_LIBS_RELEASE="%SSL%\out32\libssl.lib %SSL%\out32\%LIBS%" ^
|
||||
@@ -372,6 +388,7 @@ jobs:
|
||||
cmake -G Ninja ^
|
||||
-DCMAKE_BUILD_TYPE=Debug ^
|
||||
-DTG_OWT_SPECIAL_TARGET=win ^
|
||||
-DTG_OWT_BUILD_AUDIO_BACKENDS=OFF ^
|
||||
-DTG_OWT_LIBJPEG_INCLUDE_PATH=%cd%/../../../mozjpeg ^
|
||||
-DTG_OWT_OPENSSL_INCLUDE_PATH=%cd%/../../../openssl_%OPENSSL_VER%/include ^
|
||||
-DTG_OWT_OPUS_INCLUDE_PATH=%cd%/../../../opus/include ^
|
||||
@@ -400,6 +417,9 @@ jobs:
|
||||
fi
|
||||
echo "TDESKTOP_BUILD_DEFINE=$DEFINE" >> $GITHUB_ENV
|
||||
|
||||
- name: Free up some disk space.
|
||||
run: del /S *.pdb
|
||||
|
||||
- name: Telegram Desktop build.
|
||||
if: env.ONLY_CACHE == 'false'
|
||||
run: |
|
||||
|
||||
6
.gitmodules
vendored
@@ -91,3 +91,9 @@
|
||||
[submodule "Telegram/lib_webview"]
|
||||
path = Telegram/lib_webview
|
||||
url = https://github.com/desktop-app/lib_webview.git
|
||||
[submodule "Telegram/ThirdParty/mallocng"]
|
||||
path = Telegram/ThirdParty/mallocng
|
||||
url = https://github.com/desktop-app/mallocng.git
|
||||
[submodule "Telegram/lib_waylandshells"]
|
||||
path = Telegram/lib_waylandshells
|
||||
url = https://github.com/desktop-app/lib_waylandshells.git
|
||||
|
||||
@@ -31,6 +31,7 @@ include(cmake/target_link_static_libraries.cmake)
|
||||
include(cmake/target_link_frameworks.cmake)
|
||||
include(cmake/init_target.cmake)
|
||||
include(cmake/generate_target.cmake)
|
||||
include(cmake/nuget.cmake)
|
||||
|
||||
include(cmake/options.cmake)
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ The source code is published under GPLv3 with OpenSSL exception, the license is
|
||||
|
||||
The latest version is available for
|
||||
|
||||
* [Windows 7 and above](https://telegram.org/dl/desktop/win) ([portable](https://telegram.org/dl/desktop/win_portable))
|
||||
* [Windows 7 and above (64 bit)](https://telegram.org/dl/desktop/win64) ([portable](https://telegram.org/dl/desktop/win64_portable))
|
||||
* [Windows 7 and above (32 bit)](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)
|
||||
* [Linux static build for 64 bit](https://telegram.org/dl/desktop/linux)
|
||||
* [Snap](https://snapcraft.io/telegram-desktop)
|
||||
@@ -52,13 +53,13 @@ Version **1.8.15** was the last that supports older systems
|
||||
* Guideline Support Library ([MIT License](https://github.com/Microsoft/GSL/blob/master/LICENSE))
|
||||
* Range-v3 ([Boost License](https://github.com/ericniebler/range-v3/blob/master/LICENSE.txt))
|
||||
* Open Sans font ([Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0.html))
|
||||
* Vazir font ([License](https://github.com/rastikerdar/vazir-font/blob/master/LICENSE))
|
||||
* Vazir font ([SIL Open Font License 1.1](https://github.com/rastikerdar/vazir-font/blob/master/OFL.txt))
|
||||
* Emoji alpha codes ([MIT License](https://github.com/emojione/emojione/blob/master/extras/alpha-codes/LICENSE.md))
|
||||
* Catch test framework ([Boost License](https://github.com/philsquared/Catch/blob/master/LICENSE.txt))
|
||||
* xxHash ([BSD License](https://github.com/Cyan4973/xxHash/blob/dev/LICENSE))
|
||||
* QR Code generator ([MIT License](https://github.com/nayuki/QR-Code-generator#license))
|
||||
* CMake ([New BSD License](https://github.com/Kitware/CMake/blob/master/Copyright.txt))
|
||||
* Hunspell ([GPL](https://github.com/hunspell/hunspell/blob/master/COPYING))
|
||||
* Hunspell ([LGPL](https://github.com/hunspell/hunspell/blob/master/COPYING.LESSER))
|
||||
|
||||
## Build instructions
|
||||
|
||||
|
||||
@@ -18,6 +18,9 @@ endif()
|
||||
add_subdirectory(lib_storage)
|
||||
add_subdirectory(lib_lottie)
|
||||
add_subdirectory(lib_qr)
|
||||
if (LINUX AND NOT DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION)
|
||||
add_subdirectory(lib_waylandshells)
|
||||
endif()
|
||||
add_subdirectory(lib_webrtc)
|
||||
add_subdirectory(lib_webview)
|
||||
add_subdirectory(codegen)
|
||||
@@ -79,16 +82,12 @@ PRIVATE
|
||||
desktop-app::external_xxhash
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
desktop-app::lib_webview_winrt
|
||||
)
|
||||
elseif (LINUX)
|
||||
if (LINUX)
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
desktop-app::external_glibmm
|
||||
desktop-app::external_glib
|
||||
desktop-app::external_mallocng
|
||||
)
|
||||
|
||||
if (NOT DESKTOP_APP_DISABLE_DBUS_INTEGRATION)
|
||||
@@ -109,6 +108,7 @@ elseif (LINUX)
|
||||
if (NOT DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION)
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
desktop-app::lib_waylandshells
|
||||
desktop-app::external_kwayland
|
||||
)
|
||||
endif()
|
||||
@@ -125,7 +125,7 @@ elseif (LINUX)
|
||||
target_link_libraries(Telegram PRIVATE PkgConfig::X11)
|
||||
endif()
|
||||
else()
|
||||
pkg_search_module(GTK REQUIRED gtk+-3.0 gtk+-2.0)
|
||||
pkg_check_modules(GTK REQUIRED gtk+-3.0)
|
||||
target_include_directories(Telegram PRIVATE ${GTK_INCLUDE_DIRS})
|
||||
|
||||
if (NOT DESKTOP_APP_DISABLE_X11_INTEGRATION)
|
||||
@@ -271,23 +271,39 @@ PRIVATE
|
||||
boxes/url_auth_box.h
|
||||
boxes/username_box.cpp
|
||||
boxes/username_box.h
|
||||
calls/group/calls_choose_join_as.cpp
|
||||
calls/group/calls_choose_join_as.h
|
||||
calls/group/calls_group_call.cpp
|
||||
calls/group/calls_group_call.h
|
||||
calls/group/calls_group_common.h
|
||||
calls/group/calls_group_invite_controller.cpp
|
||||
calls/group/calls_group_invite_controller.h
|
||||
calls/group/calls_group_members.cpp
|
||||
calls/group/calls_group_members.h
|
||||
calls/group/calls_group_members_row.cpp
|
||||
calls/group/calls_group_members_row.h
|
||||
calls/group/calls_group_menu.cpp
|
||||
calls/group/calls_group_menu.h
|
||||
calls/group/calls_group_panel.cpp
|
||||
calls/group/calls_group_panel.h
|
||||
calls/group/calls_group_settings.cpp
|
||||
calls/group/calls_group_settings.h
|
||||
calls/group/calls_group_toasts.cpp
|
||||
calls/group/calls_group_toasts.h
|
||||
calls/group/calls_group_viewport.cpp
|
||||
calls/group/calls_group_viewport.h
|
||||
calls/group/calls_group_viewport_opengl.cpp
|
||||
calls/group/calls_group_viewport_opengl.h
|
||||
calls/group/calls_group_viewport_raster.cpp
|
||||
calls/group/calls_group_viewport_raster.h
|
||||
calls/group/calls_group_viewport_tile.cpp
|
||||
calls/group/calls_group_viewport_tile.h
|
||||
calls/group/calls_volume_item.cpp
|
||||
calls/group/calls_volume_item.h
|
||||
calls/calls_box_controller.cpp
|
||||
calls/calls_box_controller.h
|
||||
calls/calls_call.cpp
|
||||
calls/calls_call.h
|
||||
calls/calls_choose_join_as.cpp
|
||||
calls/calls_choose_join_as.h
|
||||
calls/calls_group_call.cpp
|
||||
calls/calls_group_call.h
|
||||
calls/calls_group_common.h
|
||||
calls/calls_group_members.cpp
|
||||
calls/calls_group_members.h
|
||||
calls/calls_group_menu.cpp
|
||||
calls/calls_group_menu.h
|
||||
calls/calls_group_panel.cpp
|
||||
calls/calls_group_panel.h
|
||||
calls/calls_group_settings.cpp
|
||||
calls/calls_group_settings.h
|
||||
calls/calls_emoji_fingerprint.cpp
|
||||
calls/calls_emoji_fingerprint.h
|
||||
calls/calls_instance.cpp
|
||||
@@ -302,8 +318,8 @@ PRIVATE
|
||||
calls/calls_userpic.h
|
||||
calls/calls_video_bubble.cpp
|
||||
calls/calls_video_bubble.h
|
||||
calls/calls_volume_item.cpp
|
||||
calls/calls_volume_item.h
|
||||
calls/calls_video_incoming.cpp
|
||||
calls/calls_video_incoming.h
|
||||
chat_helpers/bot_keyboard.cpp
|
||||
chat_helpers/bot_keyboard.h
|
||||
chat_helpers/emoji_keywords.cpp
|
||||
@@ -352,6 +368,8 @@ PRIVATE
|
||||
core/core_cloud_password.h
|
||||
core/core_settings.cpp
|
||||
core/core_settings.h
|
||||
core/core_settings_proxy.cpp
|
||||
core/core_settings_proxy.h
|
||||
core/crash_report_window.cpp
|
||||
core/crash_report_window.h
|
||||
core/crash_reports.cpp
|
||||
@@ -399,10 +417,14 @@ PRIVATE
|
||||
data/data_document.h
|
||||
data/data_document_media.cpp
|
||||
data/data_document_media.h
|
||||
data/data_document_resolver.cpp
|
||||
data/data_document_resolver.h
|
||||
data/data_drafts.cpp
|
||||
data/data_drafts.h
|
||||
data/data_folder.cpp
|
||||
data/data_folder.h
|
||||
data/data_file_click_handler.cpp
|
||||
data/data_file_click_handler.h
|
||||
data/data_file_origin.cpp
|
||||
data/data_file_origin.h
|
||||
data/data_flags.h
|
||||
@@ -708,6 +730,8 @@ PRIVATE
|
||||
main/main_session.h
|
||||
main/main_session_settings.cpp
|
||||
main/main_session_settings.h
|
||||
media/system_media_controls_manager.h
|
||||
media/system_media_controls_manager.cpp
|
||||
media/audio/media_audio.cpp
|
||||
media/audio/media_audio.h
|
||||
media/audio/media_audio_capture.cpp
|
||||
@@ -762,14 +786,25 @@ PRIVATE
|
||||
media/streaming/media_streaming_video_track.h
|
||||
media/view/media_view_group_thumbs.cpp
|
||||
media/view/media_view_group_thumbs.h
|
||||
media/view/media_view_overlay_opengl.cpp
|
||||
media/view/media_view_overlay_opengl.h
|
||||
media/view/media_view_overlay_raster.cpp
|
||||
media/view/media_view_overlay_raster.h
|
||||
media/view/media_view_overlay_renderer.h
|
||||
media/view/media_view_overlay_widget.cpp
|
||||
media/view/media_view_overlay_widget.h
|
||||
media/view/media_view_pip.cpp
|
||||
media/view/media_view_pip.h
|
||||
media/view/media_view_pip_opengl.cpp
|
||||
media/view/media_view_pip_opengl.h
|
||||
media/view/media_view_pip_raster.cpp
|
||||
media/view/media_view_pip_raster.h
|
||||
media/view/media_view_pip_renderer.h
|
||||
media/view/media_view_playback_controls.cpp
|
||||
media/view/media_view_playback_controls.h
|
||||
media/view/media_view_playback_progress.cpp
|
||||
media/view/media_view_playback_progress.h
|
||||
media/view/media_view_open_common.h
|
||||
mtproto/config_loader.cpp
|
||||
mtproto/config_loader.h
|
||||
mtproto/connection_abstract.cpp
|
||||
@@ -830,15 +865,15 @@ PRIVATE
|
||||
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_dummy.cpp
|
||||
platform/linux/linux_gtk_integration_p.h
|
||||
platform/linux/linux_gtk_integration.cpp
|
||||
platform/linux/linux_gtk_integration.h
|
||||
platform/linux/linux_gtk_open_with_dialog.cpp
|
||||
platform/linux/linux_gtk_open_with_dialog.h
|
||||
platform/linux/linux_mpris_support.cpp
|
||||
platform/linux/linux_mpris_support.h
|
||||
platform/linux/linux_notification_service_watcher.cpp
|
||||
platform/linux/linux_notification_service_watcher.h
|
||||
platform/linux/linux_wayland_integration_dummy.cpp
|
||||
platform/linux/linux_wayland_integration.cpp
|
||||
platform/linux/linux_wayland_integration.h
|
||||
platform/linux/linux_xdp_file_dialog.cpp
|
||||
@@ -851,6 +886,7 @@ PRIVATE
|
||||
platform/linux/launcher_linux.h
|
||||
platform/linux/main_window_linux.cpp
|
||||
platform/linux/main_window_linux.h
|
||||
platform/linux/notifications_manager_linux_dummy.cpp
|
||||
platform/linux/notifications_manager_linux.cpp
|
||||
platform/linux/notifications_manager_linux.h
|
||||
platform/linux/specific_linux.cpp
|
||||
@@ -860,6 +896,7 @@ PRIVATE
|
||||
platform/mac/file_utilities_mac.h
|
||||
platform/mac/launcher_mac.mm
|
||||
platform/mac/launcher_mac.h
|
||||
platform/mac/mac_iconv_helper.c
|
||||
platform/mac/main_window_mac.mm
|
||||
platform/mac/main_window_mac.h
|
||||
platform/mac/notifications_manager_mac.mm
|
||||
@@ -1036,6 +1073,8 @@ PRIVATE
|
||||
ui/search_field_controller.h
|
||||
ui/special_buttons.cpp
|
||||
ui/special_buttons.h
|
||||
ui/text/format_song_document_name.cpp
|
||||
ui/text/format_song_document_name.h
|
||||
ui/unread_badge.cpp
|
||||
ui/unread_badge.h
|
||||
window/main_window.cpp
|
||||
@@ -1049,6 +1088,8 @@ PRIVATE
|
||||
window/section_memento.h
|
||||
window/section_widget.cpp
|
||||
window/section_widget.h
|
||||
window/window_adaptive.cpp
|
||||
window/window_adaptive.h
|
||||
window/window_connecting_widget.cpp
|
||||
window/window_connecting_widget.h
|
||||
window/window_controller.cpp
|
||||
@@ -1135,16 +1176,20 @@ if (DESKTOP_APP_DISABLE_DBUS_INTEGRATION)
|
||||
platform/linux/linux_xdp_open_with_dialog.h
|
||||
platform/linux/notifications_manager_linux.cpp
|
||||
)
|
||||
|
||||
nice_target_sources(Telegram ${src_loc}
|
||||
PRIVATE
|
||||
else()
|
||||
remove_target_sources(Telegram ${src_loc}
|
||||
platform/linux/notifications_manager_linux_dummy.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if (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)
|
||||
remove_target_sources(Telegram ${src_loc}
|
||||
platform/linux/linux_wayland_integration.cpp
|
||||
)
|
||||
else()
|
||||
remove_target_sources(Telegram ${src_loc}
|
||||
platform/linux/linux_wayland_integration_dummy.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if (DESKTOP_APP_DISABLE_GTK_INTEGRATION)
|
||||
@@ -1158,15 +1203,16 @@ if (DESKTOP_APP_DISABLE_GTK_INTEGRATION)
|
||||
platform/linux/linux_gtk_open_with_dialog.cpp
|
||||
platform/linux/linux_gtk_open_with_dialog.h
|
||||
)
|
||||
|
||||
nice_target_sources(Telegram ${src_loc}
|
||||
PRIVATE
|
||||
else()
|
||||
remove_target_sources(Telegram ${src_loc}
|
||||
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)
|
||||
if (DESKTOP_APP_USE_PACKAGED)
|
||||
remove_target_sources(Telegram ${src_loc}
|
||||
platform/mac/mac_iconv_helper.c
|
||||
)
|
||||
endif()
|
||||
|
||||
nice_target_sources(Telegram ${res_loc}
|
||||
@@ -1202,8 +1248,6 @@ if (WIN32)
|
||||
# $<IF:${release},"Appending compatibility manifest.","Finalizing build.">
|
||||
# )
|
||||
elseif (APPLE)
|
||||
target_link_libraries(Telegram PRIVATE desktop-app::external_sp_media_key_tap)
|
||||
|
||||
if (NOT DESKTOP_APP_USE_PACKAGED)
|
||||
target_link_libraries(Telegram PRIVATE desktop-app::external_iconv)
|
||||
endif()
|
||||
|
||||
|
Before Width: | Height: | Size: 345 B |
|
Before Width: | Height: | Size: 591 B |
|
Before Width: | Height: | Size: 932 B |
BIN
Telegram/Resources/icons/calls/calls_more.png
Normal file
|
After Width: | Height: | Size: 277 B |
BIN
Telegram/Resources/icons/calls/calls_more@2x.png
Normal file
|
After Width: | Height: | Size: 444 B |
BIN
Telegram/Resources/icons/calls/calls_more@3x.png
Normal file
|
After Width: | Height: | Size: 653 B |
BIN
Telegram/Resources/icons/calls/calls_present.png
Normal file
|
After Width: | Height: | Size: 553 B |
BIN
Telegram/Resources/icons/calls/calls_present@2x.png
Normal file
|
After Width: | Height: | Size: 991 B |
BIN
Telegram/Resources/icons/calls/calls_present@3x.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
Telegram/Resources/icons/calls/calls_settings.png
Normal file
|
After Width: | Height: | Size: 756 B |
BIN
Telegram/Resources/icons/calls/calls_settings@2x.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
Telegram/Resources/icons/calls/calls_settings@3x.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
Telegram/Resources/icons/calls/video_back.png
Normal file
|
After Width: | Height: | Size: 294 B |
BIN
Telegram/Resources/icons/calls/video_back@2x.png
Normal file
|
After Width: | Height: | Size: 532 B |
BIN
Telegram/Resources/icons/calls/video_back@3x.png
Normal file
|
After Width: | Height: | Size: 790 B |
BIN
Telegram/Resources/icons/calls/video_large_paused.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/calls/video_large_paused@2x.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
Telegram/Resources/icons/calls/video_large_paused@3x.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
Telegram/Resources/icons/calls/video_mini_mute.png
Normal file
|
After Width: | Height: | Size: 436 B |
BIN
Telegram/Resources/icons/calls/video_mini_mute@2x.png
Normal file
|
After Width: | Height: | Size: 761 B |
BIN
Telegram/Resources/icons/calls/video_mini_mute@3x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/calls/video_mini_screencast.png
Normal file
|
After Width: | Height: | Size: 288 B |
BIN
Telegram/Resources/icons/calls/video_mini_screencast@2x.png
Normal file
|
After Width: | Height: | Size: 403 B |
BIN
Telegram/Resources/icons/calls/video_mini_screencast@3x.png
Normal file
|
After Width: | Height: | Size: 639 B |
BIN
Telegram/Resources/icons/calls/video_mini_speak.png
Normal file
|
After Width: | Height: | Size: 560 B |
BIN
Telegram/Resources/icons/calls/video_mini_speak@2x.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
Telegram/Resources/icons/calls/video_mini_speak@3x.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
Telegram/Resources/icons/calls/video_mini_video.png
Normal file
|
After Width: | Height: | Size: 355 B |
BIN
Telegram/Resources/icons/calls/video_mini_video@2x.png
Normal file
|
After Width: | Height: | Size: 480 B |
BIN
Telegram/Resources/icons/calls/video_mini_video@3x.png
Normal file
|
After Width: | Height: | Size: 729 B |
BIN
Telegram/Resources/icons/calls/video_over_mute.png
Normal file
|
After Width: | Height: | Size: 596 B |
BIN
Telegram/Resources/icons/calls/video_over_mute@2x.png
Normal file
|
After Width: | Height: | Size: 965 B |
BIN
Telegram/Resources/icons/calls/video_over_mute@3x.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
Telegram/Resources/icons/calls/video_over_pin.png
Normal file
|
After Width: | Height: | Size: 413 B |
BIN
Telegram/Resources/icons/calls/video_over_pin@2x.png
Normal file
|
After Width: | Height: | Size: 672 B |
BIN
Telegram/Resources/icons/calls/video_over_pin@3x.png
Normal file
|
After Width: | Height: | Size: 876 B |
BIN
Telegram/Resources/icons/calls/video_tooltip.png
Normal file
|
After Width: | Height: | Size: 360 B |
BIN
Telegram/Resources/icons/calls/video_tooltip@2x.png
Normal file
|
After Width: | Height: | Size: 622 B |
BIN
Telegram/Resources/icons/calls/video_tooltip@3x.png
Normal file
|
After Width: | Height: | Size: 967 B |
|
Before Width: | Height: | Size: 854 B After Width: | Height: | Size: 664 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.0 KiB |
@@ -451,6 +451,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_system_integration" = "System integration";
|
||||
"lng_settings_performance" = "Performance";
|
||||
"lng_settings_enable_animations" = "Enable animations";
|
||||
"lng_settings_enable_opengl" = "Enable OpenGL rendering for media";
|
||||
"lng_settings_sensitive_title" = "Sensitive content";
|
||||
"lng_settings_sensitive_disable_filtering" = "Disable filtering";
|
||||
"lng_settings_sensitive_about" = "Display sensitive media in public channels on all your Telegram devices.";
|
||||
@@ -496,6 +497,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_background_text1" = "Ah, you kids today with techno music! You should enjoy the classics, like Hasselhoff!";
|
||||
"lng_background_text2" = "I can't even take you seriously right now.";
|
||||
"lng_background_bad_link" = "This background link appears to be invalid.";
|
||||
"lng_background_gradient_unsupported" = "Telegram Desktop doesn't support gradient backgrounds yet.";
|
||||
"lng_background_apply" = "Apply";
|
||||
"lng_background_share" = "Share";
|
||||
"lng_background_link_copied" = "Link copied to clipboard";
|
||||
@@ -1005,6 +1007,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_report_group_title" = "Report group";
|
||||
"lng_report_bot_title" = "Report bot";
|
||||
"lng_report_message_title" = "Report message";
|
||||
"lng_report_please_select_messages" = "Please select messages to report.";
|
||||
"lng_report_select_messages" = "Select messages";
|
||||
"lng_report_messages_none" = "Select Messages";
|
||||
"lng_report_messages_count#one" = "Report {count} Message";
|
||||
@@ -1112,6 +1115,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_group_call_scheduled_group" = "{from} scheduled a voice chat for {date}";
|
||||
"lng_action_group_call_scheduled_channel" = "Voice chat scheduled for {date}";
|
||||
"lng_action_group_call_finished" = "Voice chat finished ({duration})";
|
||||
"lng_action_group_call_finished_group" = "{from} ended the voice chat ({duration})";
|
||||
"lng_action_add_user" = "{from} added {user}";
|
||||
"lng_action_add_users_many" = "{from} added {users}";
|
||||
"lng_action_add_users_and_one" = "{accumulated}, {user}";
|
||||
@@ -1400,6 +1404,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_stickers_search_sets" = "Search sticker sets";
|
||||
"lng_stickers_nothing_found" = "No stickers found";
|
||||
"lng_stickers_remove_pack_confirm" = "Remove";
|
||||
"lng_stickers_archive_pack" = "Archive Stickers";
|
||||
"lng_stickers_has_been_archived" = "Sticker pack has been archived.";
|
||||
|
||||
"lng_in_dlg_photo" = "Photo";
|
||||
"lng_in_dlg_album" = "Album";
|
||||
@@ -1995,8 +2001,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_call_raised_hand_status" = "wants to speak";
|
||||
"lng_group_call_settings" = "Settings";
|
||||
"lng_group_call_share_button" = "Share";
|
||||
"lng_group_call_video" = "Video";
|
||||
"lng_group_call_screen_share_start" = "Share Screen";
|
||||
"lng_group_call_screen_share_stop" = "Stop Sharing";
|
||||
"lng_group_call_screen_title" = "Screen {index}";
|
||||
"lng_group_call_unmute_small" = "Unmute";
|
||||
"lng_group_call_more" = "More";
|
||||
"lng_group_call_unmute" = "Unmute";
|
||||
"lng_group_call_unmute_sub" = "or hold spacebar to talk";
|
||||
"lng_group_call_unmute_sub" = "Hold space bar to temporarily unmute.";
|
||||
"lng_group_call_you_are_live" = "You are Live";
|
||||
"lng_group_call_force_muted" = "Muted by admin";
|
||||
"lng_group_call_force_muted_sub" = "You are in Listen Only mode";
|
||||
@@ -2014,6 +2026,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_call_create_sure" = "Do you really want to start a voice chat in this group?";
|
||||
"lng_group_call_create_sure_channel" = "Are you sure you want to start a voice chat in this channel as your personal account?";
|
||||
"lng_group_call_join_sure_personal" = "Are you sure you want to join this voice chat as your personal account?";
|
||||
"lng_group_call_muted_no_camera" = "You can't turn on video while you're muted by admin.";
|
||||
"lng_group_call_muted_no_screen" = "You can't share your screen while you're muted by admin.";
|
||||
"lng_group_call_chat_no_camera" = "You can't turn on video in this chat.";
|
||||
"lng_group_call_chat_no_screen" = "You can't share your screen in this chat.";
|
||||
"lng_group_call_failed_screen" = "An error occured. Screencast has stopped.";
|
||||
"lng_group_call_failed_camera" = "Could not enable camera. Perhaps another app is using the camera already. Try closing other apps.";
|
||||
"lng_group_call_tooltip_screen" = "Share screen";
|
||||
"lng_group_call_tooltip_camera" = "Your camera is off. Click here to enable camera.";
|
||||
"lng_group_call_tooltip_microphone" = "You are on mute. Click here to speak.";
|
||||
"lng_group_call_tooltip_camera_off" = "Disable camera";
|
||||
"lng_group_call_tooltip_force_muted" = "Muted by admin. Click if you want to speak.";
|
||||
"lng_group_call_tooltip_raised_hand" = "You asked to speak. We let the speakers know.";
|
||||
"lng_group_call_also_end" = "End voice chat";
|
||||
"lng_group_call_settings_title" = "Settings";
|
||||
"lng_group_call_invite" = "Invite Member";
|
||||
@@ -2036,6 +2060,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_call_ptt_delay_s" = "{amount}s";
|
||||
"lng_group_call_ptt_delay" = "Push to Talk release delay: {delay}";
|
||||
"lng_group_call_share" = "Share Invite Link";
|
||||
"lng_group_call_noise_suppression" = "Enable Noise Suppression";
|
||||
"lng_group_call_limit#one" = "Video is only available\nfor the first {count} member";
|
||||
"lng_group_call_limit#other" = "Video is only available\nfor the first {count} members";
|
||||
"lng_group_call_video_paused" = "Video is paused";
|
||||
"lng_group_call_share_speaker" = "Users with this link can speak";
|
||||
"lng_group_call_copy_speaker_link" = "Copy Speaker Link";
|
||||
"lng_group_call_copy_listener_link" = "Copy Listener Link";
|
||||
@@ -2056,6 +2084,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_call_context_remove_hand" = "Cancel request to speak";
|
||||
"lng_group_call_context_mute_for_me" = "Mute for me";
|
||||
"lng_group_call_context_unmute_for_me" = "Unmute for me";
|
||||
"lng_group_call_context_pin_camera" = "Pin video";
|
||||
"lng_group_call_context_unpin_camera" = "Unpin video";
|
||||
"lng_group_call_context_pin_screen" = "Pin screencast";
|
||||
"lng_group_call_context_unpin_screen" = "Unpin screencast";
|
||||
"lng_group_call_context_remove" = "Remove";
|
||||
"lng_group_call_remove_channel" = "Remove {channel} from the voice chat?";
|
||||
"lng_group_call_duration_days#one" = "{count} day";
|
||||
@@ -2069,6 +2101,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_call_mac_access" = "Telegram Desktop does not have access to system wide keyboard input required for Push to Talk.";
|
||||
"lng_group_call_mac_input" = "Please allow **Input Monitoring** for Telegram in Privacy Settings.";
|
||||
"lng_group_call_mac_accessibility" = "Please allow **Accessibility** for Telegram in Privacy Settings.\n\nApp restart may be required.";
|
||||
"lng_group_call_mac_screencast_access" = "Telegram Desktop does not have access to screen recording required for Screen Sharing.";
|
||||
"lng_group_call_mac_recording" = "Please allow **Screen Recording** for Telegram in Privacy Settings.";
|
||||
"lng_group_call_mac_settings" = "Open Settings";
|
||||
|
||||
"lng_group_call_start_as_header" = "Start Voice Chat as...";
|
||||
@@ -2105,6 +2139,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_call_recording_started" = "Voice chat recording started.";
|
||||
"lng_group_call_recording_stopped" = "Voice chat recording stopped.";
|
||||
"lng_group_call_recording_saved" = "Audio saved to Saved Messages.";
|
||||
"lng_group_call_pinned_camera_me" = "Your video is pinned.";
|
||||
"lng_group_call_pinned_screen_me" = "Your screencast is pinned.";
|
||||
"lng_group_call_pinned_camera" = "{user}'s video is pinned.";
|
||||
"lng_group_call_pinned_screen" = "{user}'s screencast is pinned.";
|
||||
"lng_group_call_unpinned_camera_me" = "Your video is unpinned.";
|
||||
"lng_group_call_unpinned_screen_me" = "Your screencast is unpinned.";
|
||||
"lng_group_call_unpinned_camera" = "{user}'s video is unpinned.";
|
||||
"lng_group_call_unpinned_screen" = "{user}'s screencast is unpinned.";
|
||||
"lng_group_call_sure_screencast" = "{user} is screensharing. This action will make your screencast pinned for all participants.";
|
||||
"lng_group_call_recording_start_sure" = "Do you want to start recording this chat and save the result into an audio file?\n\nOther members will see the chat is being recorded.";
|
||||
"lng_group_call_recording_stop_sure" = "Do you want to stop recording this chat?";
|
||||
"lng_group_call_recording_start_field" = "Recording Title";
|
||||
@@ -2771,6 +2814,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_mac_menu_show" = "Show Telegram";
|
||||
"lng_mac_menu_emoji_and_symbols" = "Emoji & Symbols";
|
||||
|
||||
"lng_mac_menu_player_pause" = "Pause";
|
||||
"lng_mac_menu_player_resume" = "Resume";
|
||||
"lng_mac_menu_player_next" = "Next";
|
||||
"lng_mac_menu_player_previous" = "Previous";
|
||||
"lng_mac_menu_player_stop" = "Stop";
|
||||
|
||||
"lng_mac_touchbar_favorite_stickers" = "Favorite stickers";
|
||||
|
||||
// Keys finished
|
||||
|
||||
@@ -68,7 +68,7 @@ inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string pro
|
||||
inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = InputMedia;
|
||||
inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia;
|
||||
inputMediaGame#d33f43f3 id:InputGame = InputMedia;
|
||||
inputMediaInvoice#f4e096c3 flags:# multiple_allowed:flags.1?true can_forward:flags.2?true title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia;
|
||||
inputMediaInvoice#d9799874 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:flags.1?string = InputMedia;
|
||||
inputMediaGeoLive#971fa843 flags:# stopped:flags.0?true geo_point:InputGeoPoint heading:flags.2?int period:flags.1?int proximity_notification_radius:flags.3?int = InputMedia;
|
||||
inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector<bytes> solution:flags.1?string solution_entities:flags.1?Vector<MessageEntity> = InputMedia;
|
||||
inputMediaDice#e66fbf7b emoticon:string = InputMedia;
|
||||
@@ -223,7 +223,7 @@ peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bo
|
||||
peerSettings#733f2961 flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true geo_distance:flags.6?int = PeerSettings;
|
||||
|
||||
wallPaper#a437c3ed id:long flags:# creator:flags.0?true default:flags.1?true pattern:flags.3?true dark:flags.4?true access_hash:long slug:string document:Document settings:flags.2?WallPaperSettings = WallPaper;
|
||||
wallPaperNoFile#8af40b25 flags:# default:flags.1?true dark:flags.4?true settings:flags.2?WallPaperSettings = WallPaper;
|
||||
wallPaperNoFile#e0804116 id:long flags:# default:flags.1?true dark:flags.4?true settings:flags.2?WallPaperSettings = WallPaper;
|
||||
|
||||
inputReportReasonSpam#58dbcab8 = ReportReason;
|
||||
inputReportReasonViolence#1e22c78d = ReportReason;
|
||||
@@ -356,7 +356,7 @@ updateDeleteScheduledMessages#90866cee peer:Peer messages:Vector<int> = Update;
|
||||
updateTheme#8216fba3 theme:Theme = Update;
|
||||
updateGeoLiveViewed#871fb939 peer:Peer msg_id:int = Update;
|
||||
updateLoginToken#564fe691 = Update;
|
||||
updateMessagePollVote#42f88f2c poll_id:long user_id:int options:Vector<bytes> = Update;
|
||||
updateMessagePollVote#37f69f0b poll_id:long user_id:int options:Vector<bytes> qts:int = Update;
|
||||
updateDialogFilter#26ffde7d flags:# id:int filter:flags.0?DialogFilter = Update;
|
||||
updateDialogFilterOrder#a5d72105 order:Vector<int> = Update;
|
||||
updateDialogFilters#3504914f = Update;
|
||||
@@ -375,6 +375,7 @@ updatePeerHistoryTTL#bb9bb9a5 flags:# peer:Peer ttl_period:flags.0?int = Update;
|
||||
updateChatParticipant#f3b3781f flags:# chat_id:int date:int actor_id:int user_id:int prev_participant:flags.0?ChatParticipant new_participant:flags.1?ChatParticipant invite:flags.2?ExportedChatInvite qts:int = Update;
|
||||
updateChannelParticipant#7fecb1ec flags:# channel_id:int date:int actor_id:int user_id:int prev_participant:flags.0?ChannelParticipant new_participant:flags.1?ChannelParticipant invite:flags.2?ExportedChatInvite qts:int = Update;
|
||||
updateBotStopped#7f9488a user_id:int date:int stopped:Bool qts:int = Update;
|
||||
updateGroupCallConnection#b783982 flags:# presentation:flags.0?true params:DataJSON = Update;
|
||||
|
||||
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
|
||||
|
||||
@@ -405,7 +406,7 @@ config#330b4067 flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:fla
|
||||
|
||||
nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc;
|
||||
|
||||
help.appUpdate#1da7158f flags:# can_not_skip:flags.0?true id:int version:string text:string entities:Vector<MessageEntity> document:flags.1?Document url:flags.2?string = help.AppUpdate;
|
||||
help.appUpdate#ccbbce30 flags:# can_not_skip:flags.0?true id:int version:string text:string entities:Vector<MessageEntity> document:flags.1?Document url:flags.2?string sticker:flags.3?Document = help.AppUpdate;
|
||||
help.noAppUpdate#c45a6536 = help.AppUpdate;
|
||||
|
||||
help.inviteText#18cb9f78 message:string = help.InviteText;
|
||||
@@ -649,7 +650,7 @@ inputBotInlineMessageMediaGeo#96929a85 flags:# geo_point:InputGeoPoint heading:f
|
||||
inputBotInlineMessageMediaVenue#417bbf11 flags:# geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
|
||||
inputBotInlineMessageMediaContact#a6edbffd flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
|
||||
inputBotInlineMessageGame#4b425864 flags:# reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
|
||||
inputBotInlineMessageMediaInvoice#d5348d85 flags:# multiple_allowed:flags.1?true can_forward:flags.3?true title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
|
||||
inputBotInlineMessageMediaInvoice#d7e78225 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
|
||||
|
||||
inputBotInlineResult#88bf9319 flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?InputWebDocument content:flags.5?InputWebDocument send_message:InputBotInlineMessage = InputBotInlineResult;
|
||||
inputBotInlineResultPhoto#a8d864a7 id:string type:string photo:InputPhoto send_message:InputBotInlineMessage = InputBotInlineResult;
|
||||
@@ -1069,14 +1070,14 @@ chatBannedRights#9f120418 flags:# view_messages:flags.0?true send_messages:flags
|
||||
|
||||
inputWallPaper#e630b979 id:long access_hash:long = InputWallPaper;
|
||||
inputWallPaperSlug#72091c80 slug:string = InputWallPaper;
|
||||
inputWallPaperNoFile#8427bbac = InputWallPaper;
|
||||
inputWallPaperNoFile#967a462e id:long = InputWallPaper;
|
||||
|
||||
account.wallPapersNotModified#1c199183 = account.WallPapers;
|
||||
account.wallPapers#702b65a9 hash:int wallpapers:Vector<WallPaper> = account.WallPapers;
|
||||
|
||||
codeSettings#debebe83 flags:# allow_flashcall:flags.0?true current_number:flags.1?true allow_app_hash:flags.4?true = CodeSettings;
|
||||
|
||||
wallPaperSettings#5086cf8 flags:# blur:flags.1?true motion:flags.2?true background_color:flags.0?int second_background_color:flags.4?int intensity:flags.3?int rotation:flags.4?int = WallPaperSettings;
|
||||
wallPaperSettings#1dc1bca4 flags:# blur:flags.1?true motion:flags.2?true background_color:flags.0?int second_background_color:flags.4?int third_background_color:flags.5?int fourth_background_color:flags.6?int intensity:flags.3?int rotation:flags.4?int = WallPaperSettings;
|
||||
|
||||
autoDownloadSettings#e04232f3 flags:# disabled:flags.0?true video_preload_large:flags.1?true audio_preload_next:flags.2?true phonecalls_less_data:flags.3?true photo_size_max:int video_size_max:int file_size_max:int video_upload_maxbitrate:int = AutoDownloadSettings;
|
||||
|
||||
@@ -1204,11 +1205,11 @@ peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked;
|
||||
stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats;
|
||||
|
||||
groupCallDiscarded#7780bcb4 id:long access_hash:long duration:int = GroupCall;
|
||||
groupCall#c95c6654 flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true id:long access_hash:long participants_count:int params:flags.0?DataJSON title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int version:int = GroupCall;
|
||||
groupCall#653dbaad flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true can_start_video:flags.9?true id:long access_hash:long participants_count:int title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int version:int = GroupCall;
|
||||
|
||||
inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall;
|
||||
|
||||
groupCallParticipant#b96b25ee flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long params:flags.6?DataJSON = GroupCallParticipant;
|
||||
groupCallParticipant#eba636fe flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true video_joined:flags.15?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long video:flags.6?GroupCallParticipantVideo presentation:flags.14?GroupCallParticipantVideo = GroupCallParticipant;
|
||||
|
||||
phone.groupCall#9e727aad call:GroupCall participants:Vector<GroupCallParticipant> participants_next_offset:string chats:Vector<Chat> users:Vector<User> = phone.GroupCall;
|
||||
|
||||
@@ -1245,6 +1246,10 @@ phone.joinAsPeers#afe5623f peers:Vector<Peer> chats:Vector<Chat> users:Vector<Us
|
||||
|
||||
phone.exportedGroupCallInvite#204bd158 link:string = phone.ExportedGroupCallInvite;
|
||||
|
||||
groupCallParticipantVideoSourceGroup#dcb118b7 semantics:string sources:Vector<int> = GroupCallParticipantVideoSourceGroup;
|
||||
|
||||
groupCallParticipantVideo#78e41663 flags:# paused:flags.0?true endpoint:string source_groups:Vector<GroupCallParticipantVideoSourceGroup> = GroupCallParticipantVideo;
|
||||
|
||||
---functions---
|
||||
|
||||
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
|
||||
@@ -1617,22 +1622,24 @@ phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhon
|
||||
phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool;
|
||||
phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool;
|
||||
phone.createGroupCall#48cdc6d8 flags:# peer:InputPeer random_id:int title:flags.0?string schedule_date:flags.1?int = Updates;
|
||||
phone.joinGroupCall#b132ff7b flags:# muted:flags.0?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string params:DataJSON = Updates;
|
||||
phone.joinGroupCall#b132ff7b flags:# muted:flags.0?true video_stopped:flags.2?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string params:DataJSON = Updates;
|
||||
phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates;
|
||||
phone.inviteToGroupCall#7b393160 call:InputGroupCall users:Vector<InputUser> = Updates;
|
||||
phone.discardGroupCall#7a777135 call:InputGroupCall = Updates;
|
||||
phone.toggleGroupCallSettings#74bbb43d flags:# reset_invite_hash:flags.1?true call:InputGroupCall join_muted:flags.0?Bool = Updates;
|
||||
phone.getGroupCall#c7cb017 call:InputGroupCall = phone.GroupCall;
|
||||
phone.getGroupParticipants#c558d8ab call:InputGroupCall ids:Vector<InputPeer> sources:Vector<int> offset:string limit:int = phone.GroupParticipants;
|
||||
phone.checkGroupCall#b74a7bea call:InputGroupCall source:int = Bool;
|
||||
phone.checkGroupCall#b59cf977 call:InputGroupCall sources:Vector<int> = Vector<int>;
|
||||
phone.toggleGroupCallRecord#c02a66d7 flags:# start:flags.0?true call:InputGroupCall title:flags.1?string = Updates;
|
||||
phone.editGroupCallParticipant#d975eb80 flags:# muted:flags.0?true call:InputGroupCall participant:InputPeer volume:flags.1?int raise_hand:flags.2?Bool = Updates;
|
||||
phone.editGroupCallParticipant#a5273abf flags:# call:InputGroupCall participant:InputPeer muted:flags.0?Bool volume:flags.1?int raise_hand:flags.2?Bool video_stopped:flags.3?Bool video_paused:flags.4?Bool presentation_paused:flags.5?Bool = Updates;
|
||||
phone.editGroupCallTitle#1ca6ac0a call:InputGroupCall title:string = Updates;
|
||||
phone.getGroupCallJoinAs#ef7c213a peer:InputPeer = phone.JoinAsPeers;
|
||||
phone.exportGroupCallInvite#e6aa647f flags:# can_self_unmute:flags.0?true call:InputGroupCall = phone.ExportedGroupCallInvite;
|
||||
phone.toggleGroupCallStartSubscription#219c34e6 call:InputGroupCall subscribed:Bool = Updates;
|
||||
phone.startScheduledGroupCall#5680e342 call:InputGroupCall = Updates;
|
||||
phone.saveDefaultGroupCallJoinAs#575e1f8c peer:InputPeer join_as:InputPeer = Bool;
|
||||
phone.joinGroupCallPresentation#cbea6bc4 call:InputGroupCall params:DataJSON = Updates;
|
||||
phone.leaveGroupCallPresentation#1c50d144 call:InputGroupCall = Updates;
|
||||
|
||||
langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference;
|
||||
langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector<string> = Vector<LangPackString>;
|
||||
@@ -1649,4 +1656,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
|
||||
stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
|
||||
stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
|
||||
|
||||
// LAYER 128
|
||||
// LAYER 129
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="2.7.3.0" />
|
||||
Version="2.8.3.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,7,3,0
|
||||
PRODUCTVERSION 2,7,3,0
|
||||
FILEVERSION 2,8,3,0
|
||||
PRODUCTVERSION 2,8,3,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "2.7.3.0"
|
||||
VALUE "FileVersion", "2.8.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.7.3.0"
|
||||
VALUE "ProductVersion", "2.8.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,7,3,0
|
||||
PRODUCTVERSION 2,7,3,0
|
||||
FILEVERSION 2,8,3,0
|
||||
PRODUCTVERSION 2,8,3,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "2.7.3.0"
|
||||
VALUE "FileVersion", "2.8.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.7.3.0"
|
||||
VALUE "ProductVersion", "2.8.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -47,6 +47,18 @@ typedef signed int int32;
|
||||
|
||||
namespace{
|
||||
|
||||
struct BIODeleter {
|
||||
void operator()(BIO *value) {
|
||||
BIO_free(value);
|
||||
}
|
||||
};
|
||||
|
||||
inline auto makeBIO(const void *buf, int len) {
|
||||
return std::unique_ptr<BIO, BIODeleter>{
|
||||
BIO_new_mem_buf(buf, len),
|
||||
};
|
||||
}
|
||||
|
||||
inline uint32 sha1Shift(uint32 v, uint32 shift) {
|
||||
return ((v << shift) | (v >> (32 - shift)));
|
||||
}
|
||||
@@ -430,7 +442,15 @@ int main(int argc, char *argv[])
|
||||
uint32 siglen = 0;
|
||||
|
||||
cout << "Signing..\n";
|
||||
RSA *prKey = PEM_read_bio_RSAPrivateKey(BIO_new_mem_buf(const_cast<char*>((BetaChannel || AlphaVersion) ? PrivateBetaKey : PrivateKey), -1), 0, 0, 0);
|
||||
RSA *prKey = [] {
|
||||
const auto bio = makeBIO(
|
||||
const_cast<char*>(
|
||||
(BetaChannel || AlphaVersion)
|
||||
? PrivateBetaKey
|
||||
: PrivateKey),
|
||||
-1);
|
||||
return PEM_read_bio_RSAPrivateKey(bio.get(), 0, 0, 0);
|
||||
}();
|
||||
if (!prKey) {
|
||||
cout << "Could not read RSA private key!\n";
|
||||
return -1;
|
||||
@@ -453,7 +473,15 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
cout << "Checking signature..\n";
|
||||
RSA *pbKey = PEM_read_bio_RSAPublicKey(BIO_new_mem_buf(const_cast<char*>((BetaChannel || AlphaVersion) ? PublicBetaKey : PublicKey), -1), 0, 0, 0);
|
||||
RSA *pbKey = [] {
|
||||
const auto bio = makeBIO(
|
||||
const_cast<char*>(
|
||||
(BetaChannel || AlphaVersion)
|
||||
? PublicBetaKey
|
||||
: PublicKey),
|
||||
-1);
|
||||
return PEM_read_bio_RSAPublicKey(bio.get(), 0, 0, 0);
|
||||
}();
|
||||
if (!pbKey) {
|
||||
cout << "Could not read RSA public key!\n";
|
||||
return -1;
|
||||
@@ -510,7 +538,12 @@ QString countAlphaVersionSignature(quint64 version) { // duplicated in autoupdat
|
||||
|
||||
uint32 siglen = 0;
|
||||
|
||||
RSA *prKey = PEM_read_bio_RSAPrivateKey(BIO_new_mem_buf(const_cast<char*>(cAlphaPrivateKey.constData()), -1), 0, 0, 0);
|
||||
RSA *prKey = [&] {
|
||||
const auto bio = makeBIO(
|
||||
const_cast<char*>(cAlphaPrivateKey.constData()),
|
||||
-1);
|
||||
return PEM_read_bio_RSAPrivateKey(bio.get(), 0, 0, 0);
|
||||
}();
|
||||
if (!prKey) {
|
||||
cout << "Error: Could not read alpha private key!\n";
|
||||
return QString();
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <cstdio>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/sendfile.h>
|
||||
#include <cstdlib>
|
||||
#include <unistd.h>
|
||||
#include <dirent.h>
|
||||
@@ -87,7 +88,7 @@ void writeLog(const char *format, ...) {
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
bool copyFile(const char *from, const char *to) {
|
||||
bool copyFile(const char *from, const char *to, bool writeprotected) {
|
||||
FILE *ffrom = fopen(from, "rb"), *fto = fopen(to, "wb");
|
||||
if (!ffrom) {
|
||||
if (fto) fclose(fto);
|
||||
@@ -97,11 +98,6 @@ bool copyFile(const char *from, const char *to) {
|
||||
fclose(ffrom);
|
||||
return false;
|
||||
}
|
||||
static const int BufSize = 65536;
|
||||
char buf[BufSize];
|
||||
while (size_t size = fread(buf, 1, BufSize, ffrom)) {
|
||||
fwrite(buf, 1, size, fto);
|
||||
}
|
||||
|
||||
struct stat fst; // from http://stackoverflow.com/questions/5486774/keeping-fileowner-and-permissions-after-copying-file-in-c
|
||||
//let's say this wont fail since you already worked OK on that fp
|
||||
@@ -110,8 +106,35 @@ bool copyFile(const char *from, const char *to) {
|
||||
fclose(fto);
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t copied = sendfile(
|
||||
fileno(fto),
|
||||
fileno(ffrom),
|
||||
nullptr,
|
||||
fst.st_size);
|
||||
|
||||
if (copied == -1) {
|
||||
writeLog(
|
||||
"Copy by sendfile '%s' to '%s' failed, error: %d, fallback now.",
|
||||
from,
|
||||
to,
|
||||
int(errno));
|
||||
static const int BufSize = 65536;
|
||||
char buf[BufSize];
|
||||
while (size_t size = fread(buf, 1, BufSize, ffrom)) {
|
||||
fwrite(buf, 1, size, fto);
|
||||
}
|
||||
} else {
|
||||
writeLog(
|
||||
"Copy by sendfile '%s' to '%s' done, size: %d, result: %d.",
|
||||
from,
|
||||
to,
|
||||
int(fst.st_size),
|
||||
int(copied));
|
||||
}
|
||||
|
||||
//update to the same uid/gid
|
||||
if (fchown(fileno(fto), fst.st_uid, fst.st_gid) != 0) {
|
||||
if (!writeprotected && fchown(fileno(fto), fst.st_uid, fst.st_gid) != 0) {
|
||||
fclose(ffrom);
|
||||
fclose(fto);
|
||||
return false;
|
||||
@@ -210,7 +233,7 @@ void delFolder() {
|
||||
rmdir(delFolder.c_str());
|
||||
}
|
||||
|
||||
bool update() {
|
||||
bool update(bool writeprotected) {
|
||||
writeLog("Update started..");
|
||||
|
||||
string updDir = workDir + "tupdates/temp", readyFilePath = workDir + "tupdates/temp/ready", tdataDir = workDir + "tupdates/temp/tdata";
|
||||
@@ -323,7 +346,7 @@ bool update() {
|
||||
writeLog("Copying file '%s' to '%s'..", fname.c_str(), tofname.c_str());
|
||||
int copyTries = 0, triesLimit = 30;
|
||||
do {
|
||||
if (!copyFile(fname.c_str(), tofname.c_str())) {
|
||||
if (!copyFile(fname.c_str(), tofname.c_str(), writeprotected)) {
|
||||
++copyTries;
|
||||
usleep(100000);
|
||||
} else {
|
||||
@@ -358,6 +381,7 @@ int main(int argc, char *argv[]) {
|
||||
bool needupdate = true;
|
||||
bool autostart = false;
|
||||
bool debug = false;
|
||||
bool writeprotected = false;
|
||||
bool tosettings = false;
|
||||
bool startintray = false;
|
||||
bool testmode = false;
|
||||
@@ -383,6 +407,8 @@ int main(int argc, char *argv[]) {
|
||||
tosettings = true;
|
||||
} else if (equal(argv[i], "-workdir_custom")) {
|
||||
customWorkingDir = true;
|
||||
} else if (equal(argv[i], "-writeprotected")) {
|
||||
writeprotected = true;
|
||||
} else if (equal(argv[i], "-key") && ++i < argc) {
|
||||
key = argv[i];
|
||||
} else if (equal(argv[i], "-workpath") && ++i < argc) {
|
||||
@@ -404,6 +430,7 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
if (needupdate) writeLog("Need to update!");
|
||||
if (autostart) writeLog("From autostart!");
|
||||
if (writeprotected) writeLog("Write Protected folder!");
|
||||
|
||||
updaterName = CurrentExecutablePath(argc, argv);
|
||||
writeLog("Updater binary full path is: %s", updaterName.c_str());
|
||||
@@ -454,7 +481,7 @@ int main(int argc, char *argv[]) {
|
||||
} else {
|
||||
writeLog("Passed workpath is '%s'", workDir.c_str());
|
||||
}
|
||||
update();
|
||||
update(writeprotected);
|
||||
}
|
||||
} else {
|
||||
writeLog("Error: bad exe name!");
|
||||
@@ -494,14 +521,17 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
args.push_back(nullptr);
|
||||
|
||||
pid_t pid = fork();
|
||||
switch (pid) {
|
||||
case -1:
|
||||
writeLog("fork() failed!");
|
||||
return 1;
|
||||
case 0:
|
||||
execv(path, args.data());
|
||||
return 1;
|
||||
// let the parent launch instead
|
||||
if (!writeprotected) {
|
||||
pid_t pid = fork();
|
||||
switch (pid) {
|
||||
case -1:
|
||||
writeLog("fork() failed!");
|
||||
return 1;
|
||||
case 0:
|
||||
execv(args[0], args.data());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
writeLog("Executed Telegram, closing log and quitting..");
|
||||
|
||||
@@ -36,10 +36,12 @@ void AttachedStickers::request(
|
||||
return;
|
||||
}
|
||||
if (result.v.isEmpty()) {
|
||||
Ui::show(Box<InformBox>(tr::lng_stickers_not_found(tr::now)));
|
||||
strongController->show(
|
||||
Box<InformBox>(tr::lng_stickers_not_found(tr::now)));
|
||||
return;
|
||||
} else if (result.v.size() > 1) {
|
||||
Ui::show(Box<StickersBox>(strongController, result));
|
||||
strongController->show(
|
||||
Box<StickersBox>(strongController, result));
|
||||
return;
|
||||
}
|
||||
// Single attached sticker pack.
|
||||
@@ -52,12 +54,15 @@ void AttachedStickers::request(
|
||||
const auto setId = (setData->vid().v && setData->vaccess_hash().v)
|
||||
? MTP_inputStickerSetID(setData->vid(), setData->vaccess_hash())
|
||||
: MTP_inputStickerSetShortName(setData->vshort_name());
|
||||
Ui::show(
|
||||
strongController->show(
|
||||
Box<StickerSetBox>(strongController, setId),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_requestId = 0;
|
||||
Ui::show(Box<InformBox>(tr::lng_stickers_not_found(tr::now)));
|
||||
if (const auto strongController = weak.get()) {
|
||||
strongController->show(
|
||||
Box<InformBox>(tr::lng_stickers_not_found(tr::now)));
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,11 @@ void CheckChatInvite(
|
||||
session->api().checkChatInvite(hash, [=](const MTPChatInvite &result) {
|
||||
Core::App().hideMediaView();
|
||||
result.match([=](const MTPDchatInvite &data) {
|
||||
const auto box = Ui::show(Box<ConfirmInviteBox>(
|
||||
const auto strongController = weak.get();
|
||||
if (!strongController) {
|
||||
return;
|
||||
}
|
||||
const auto box = strongController->show(Box<ConfirmInviteBox>(
|
||||
session,
|
||||
data,
|
||||
invitePeekChannel,
|
||||
@@ -80,7 +84,10 @@ void CheckChatInvite(
|
||||
return;
|
||||
}
|
||||
Core::App().hideMediaView();
|
||||
Ui::show(Box<InformBox>(tr::lng_group_invite_bad_link(tr::now)));
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->show(
|
||||
Box<InformBox>(tr::lng_group_invite_bad_link(tr::now)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -862,26 +862,35 @@ int32 Updates::pts() const {
|
||||
return _ptsWaiter.current();
|
||||
}
|
||||
|
||||
void Updates::updateOnline() {
|
||||
updateOnline(false);
|
||||
void Updates::updateOnline(crl::time lastNonIdleTime) {
|
||||
updateOnline(lastNonIdleTime, false);
|
||||
}
|
||||
|
||||
bool Updates::isIdle() const {
|
||||
return _isIdle;
|
||||
return _isIdle.current();
|
||||
}
|
||||
|
||||
void Updates::updateOnline(bool gotOtherOffline) {
|
||||
crl::on_main(&session(), [] { Core::App().checkAutoLock(); });
|
||||
rpl::producer<bool> Updates::isIdleValue() const {
|
||||
return _isIdle.value();
|
||||
}
|
||||
|
||||
void Updates::updateOnline(crl::time lastNonIdleTime, bool gotOtherOffline) {
|
||||
if (!lastNonIdleTime) {
|
||||
lastNonIdleTime = Core::App().lastNonIdleTime();
|
||||
}
|
||||
crl::on_main(&session(), [=] {
|
||||
Core::App().checkAutoLock(lastNonIdleTime);
|
||||
});
|
||||
|
||||
const auto &config = _session->serverConfig();
|
||||
bool isOnline = Core::App().hasActiveWindow(&session());
|
||||
int updateIn = config.onlineUpdatePeriod;
|
||||
Assert(updateIn >= 0);
|
||||
if (isOnline) {
|
||||
const auto idle = crl::now() - Core::App().lastNonIdleTime();
|
||||
const auto idle = crl::now() - lastNonIdleTime;
|
||||
if (idle >= config.offlineIdleTimeout) {
|
||||
isOnline = false;
|
||||
if (!_isIdle) {
|
||||
if (!isIdle()) {
|
||||
_isIdle = true;
|
||||
_idleFinishTimer.callOnce(900);
|
||||
}
|
||||
@@ -929,13 +938,15 @@ void Updates::updateOnline(bool gotOtherOffline) {
|
||||
_onlineTimer.callOnce(updateIn);
|
||||
}
|
||||
|
||||
void Updates::checkIdleFinish() {
|
||||
if (crl::now() - Core::App().lastNonIdleTime()
|
||||
void Updates::checkIdleFinish(crl::time lastNonIdleTime) {
|
||||
if (!lastNonIdleTime) {
|
||||
lastNonIdleTime = Core::App().lastNonIdleTime();
|
||||
}
|
||||
if (crl::now() - lastNonIdleTime
|
||||
< _session->serverConfig().offlineIdleTimeout) {
|
||||
updateOnline(lastNonIdleTime);
|
||||
_idleFinishTimer.cancel();
|
||||
_isIdle = false;
|
||||
updateOnline();
|
||||
App::wnd()->checkHistoryActivation();
|
||||
} else {
|
||||
_idleFinishTimer.callOnce(900);
|
||||
}
|
||||
@@ -954,9 +965,10 @@ bool Updates::isQuitPrevent() {
|
||||
return false;
|
||||
}
|
||||
LOG(("Api::Updates prevents quit, sending offline status..."));
|
||||
updateOnline();
|
||||
updateOnline(crl::now());
|
||||
return true;
|
||||
}
|
||||
|
||||
void Updates::handleSendActionUpdate(
|
||||
PeerId peerId,
|
||||
MsgId rootId,
|
||||
@@ -1747,7 +1759,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
if (UserId(d.vuser_id()) == session().userId()) {
|
||||
if (d.vstatus().type() == mtpc_userStatusOffline
|
||||
|| d.vstatus().type() == mtpc_userStatusEmpty) {
|
||||
updateOnline(true);
|
||||
updateOnline(Core::App().lastNonIdleTime(), true);
|
||||
if (d.vstatus().type() == mtpc_userStatusOffline) {
|
||||
cSetOtherOnline(
|
||||
d.vstatus().c_userStatusOffline().vwas_online().v);
|
||||
@@ -1878,6 +1890,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
case mtpc_updatePhoneCall:
|
||||
case mtpc_updatePhoneCallSignalingData:
|
||||
case mtpc_updateGroupCallParticipants:
|
||||
case mtpc_updateGroupCallConnection:
|
||||
case mtpc_updateGroupCall: {
|
||||
Core::App().calls().handleUpdate(&session(), update);
|
||||
} break;
|
||||
|
||||
@@ -38,9 +38,10 @@ public:
|
||||
|
||||
[[nodiscard]] int32 pts() const;
|
||||
|
||||
void updateOnline();
|
||||
void updateOnline(crl::time lastNonIdleTime = 0);
|
||||
[[nodiscard]] bool isIdle() const;
|
||||
void checkIdleFinish();
|
||||
[[nodiscard]] rpl::producer<bool> isIdleValue() const;
|
||||
void checkIdleFinish(crl::time lastNonIdleTime = 0);
|
||||
bool lastWasOnline() const;
|
||||
crl::time lastSetOnline() const;
|
||||
bool isQuitPrevent();
|
||||
@@ -86,7 +87,7 @@ private:
|
||||
MsgRange range,
|
||||
const MTPupdates_ChannelDifference &result);
|
||||
|
||||
void updateOnline(bool gotOtherOffline);
|
||||
void updateOnline(crl::time lastNonIdleTime, bool gotOtherOffline);
|
||||
void sendPing();
|
||||
void getDifferenceByPts();
|
||||
void getDifferenceAfterFail();
|
||||
@@ -185,7 +186,7 @@ private:
|
||||
base::Timer _idleFinishTimer;
|
||||
crl::time _lastSetOnline = 0;
|
||||
bool _lastWasOnline = false;
|
||||
bool _isIdle = false;
|
||||
rpl::variable<bool> _isIdle = false;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
|
||||
@@ -212,6 +212,11 @@ ApiWrap::ApiWrap(not_null<Main::Session*> session)
|
||||
}, _session->lifetime());
|
||||
|
||||
setupSupportMode();
|
||||
|
||||
Core::App().settings().proxy().connectionTypeValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshTopPromotion();
|
||||
}, _session->lifetime());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -261,10 +266,10 @@ void ApiWrap::refreshTopPromotion() {
|
||||
return;
|
||||
}
|
||||
const auto key = [&]() -> std::pair<QString, uint32> {
|
||||
if (Global::ProxySettings() != MTP::ProxyData::Settings::Enabled) {
|
||||
if (!Core::App().settings().proxy().isEnabled()) {
|
||||
return {};
|
||||
}
|
||||
const auto &proxy = Global::SelectedProxy();
|
||||
const auto &proxy = Core::App().settings().proxy().selected();
|
||||
if (proxy.type != MTP::ProxyData::Type::Mtproto) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/application.h"
|
||||
#include "mainwindow.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "facades.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
@@ -45,8 +44,7 @@ void AutoLockBox::prepare() {
|
||||
void AutoLockBox::durationChanged(int seconds) {
|
||||
Core::App().settings().setAutoLock(seconds);
|
||||
Core::App().saveSettingsDelayed();
|
||||
Global::RefLocalPasscodeChanged().notify();
|
||||
|
||||
Core::App().checkAutoLock();
|
||||
Core::App().checkAutoLock(crl::now());
|
||||
closeBox();
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ void BackgroundBox::prepare() {
|
||||
|
||||
_inner->chooseEvents(
|
||||
) | rpl::start_with_next([=](const Data::WallPaper &paper) {
|
||||
Ui::show(
|
||||
_controller->show(
|
||||
Box<BackgroundPreviewBox>(_controller, paper),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}, _inner->lifetime());
|
||||
@@ -176,7 +176,7 @@ void BackgroundBox::removePaper(const Data::WallPaper &paper) {
|
||||
paper.mtpSettings()
|
||||
)).send();
|
||||
};
|
||||
Ui::show(
|
||||
_controller->show(
|
||||
Box<ConfirmBox>(
|
||||
tr::lng_background_sure_delete(tr::now),
|
||||
tr::lng_selected_delete(tr::now),
|
||||
|
||||
@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_document_resolver.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
@@ -769,23 +770,25 @@ bool BackgroundPreviewBox::Start(
|
||||
const QString &slug,
|
||||
const QMap<QString, QString> ¶ms) {
|
||||
if (const auto paper = Data::WallPaper::FromColorSlug(slug)) {
|
||||
Ui::show(Box<BackgroundPreviewBox>(
|
||||
controller->show(Box<BackgroundPreviewBox>(
|
||||
controller,
|
||||
paper->withUrlParams(params)));
|
||||
return true;
|
||||
}
|
||||
if (!IsValidWallPaperSlug(slug)) {
|
||||
Ui::show(Box<InformBox>(tr::lng_background_bad_link(tr::now)));
|
||||
controller->show(
|
||||
Box<InformBox>(tr::lng_background_bad_link(tr::now)));
|
||||
return false;
|
||||
}
|
||||
controller->session().api().requestWallPaper(slug, crl::guard(controller, [=](
|
||||
const Data::WallPaper &result) {
|
||||
Ui::show(Box<BackgroundPreviewBox>(
|
||||
controller->show(Box<BackgroundPreviewBox>(
|
||||
controller,
|
||||
result.withUrlParams(params)));
|
||||
}), [](const MTP::Error &error) {
|
||||
Ui::show(Box<InformBox>(tr::lng_background_bad_link(tr::now)));
|
||||
});
|
||||
}), crl::guard(controller, [=](const MTP::Error &error) {
|
||||
controller->show(
|
||||
Box<InformBox>(tr::lng_background_bad_link(tr::now)));
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -433,7 +433,7 @@ editMediaButtonFileSkipRight: 1px;
|
||||
editMediaButtonFileSkipTop: 7px;
|
||||
|
||||
editMediaButtonIconFile: icon {{ "settings_edit", menuIconFg }};
|
||||
editMediaButtonIconPhoto: icon {{ "settings_edit", msgServiceFg }};
|
||||
editMediaButtonIconPhoto: icon {{ "settings_edit", roundedFg }};
|
||||
editMediaButton: IconButton {
|
||||
width: editMediaButtonSize;
|
||||
height: editMediaButtonSize;
|
||||
@@ -464,8 +464,8 @@ sendBoxAlbumGroupButtonFile: IconButton(editMediaButton) {
|
||||
sendBoxAlbumGroupEditButtonIconFile: editMediaButtonIconFile;
|
||||
sendBoxAlbumGroupDeleteButtonIconFile: icon {{ "history_file_cancel", menuIconFg, point(6px, 6px) }};
|
||||
|
||||
sendBoxAlbumGroupButtonMediaEdit: icon {{ "settings_edit", msgServiceFg, point(3px, -2px) }};
|
||||
sendBoxAlbumGroupButtonMediaDelete: icon {{ "history_file_cancel", msgServiceFg, point(2px, 5px) }};
|
||||
sendBoxAlbumGroupButtonMediaEdit: icon {{ "settings_edit", roundedFg, point(3px, -2px) }};
|
||||
sendBoxAlbumGroupButtonMediaDelete: icon {{ "history_file_cancel", roundedFg, point(2px, 5px) }};
|
||||
sendBoxAlbumGroupButtonMedia: IconButton {
|
||||
width: sendBoxAlbumGroupHeight;
|
||||
height: sendBoxAlbumGroupHeight;
|
||||
@@ -729,10 +729,6 @@ termsAgePadding: margins(22px, 16px, 16px, 0px);
|
||||
|
||||
themesSmallSkip: 10px;
|
||||
themesBackgroundSize: 120px;
|
||||
themesScroll: ScrollArea(defaultScrollArea) {
|
||||
bottomsh: 0px;
|
||||
topsh: 0px;
|
||||
}
|
||||
themesMenuToggle: IconButton(defaultIconButton) {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
|
||||
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/qthelp_url.h"
|
||||
#include "base/call_delayed.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "main/main_account.h"
|
||||
#include "mtproto/facade.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
@@ -26,7 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/effects/radial_animation.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "facades.h"
|
||||
#include "ui/basic_click_handlers.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
@@ -185,7 +186,10 @@ class ProxiesBox : public Ui::BoxContent {
|
||||
public:
|
||||
using View = ProxiesBoxController::ItemView;
|
||||
|
||||
ProxiesBox(QWidget*, not_null<ProxiesBoxController*> controller);
|
||||
ProxiesBox(
|
||||
QWidget*,
|
||||
not_null<ProxiesBoxController*> controller,
|
||||
Core::SettingsProxy &settings);
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
@@ -200,6 +204,7 @@ private:
|
||||
void refreshProxyForCalls();
|
||||
|
||||
not_null<ProxiesBoxController*> _controller;
|
||||
Core::SettingsProxy &_settings;
|
||||
QPointer<Ui::Checkbox> _tryIPv6;
|
||||
std::shared_ptr<Ui::RadioenumGroup<ProxyData::Settings>> _proxySettings;
|
||||
QPointer<Ui::SlideWrap<Ui::Checkbox>> _proxyForCalls;
|
||||
@@ -565,8 +570,10 @@ void ProxyRow::showMenu() {
|
||||
|
||||
ProxiesBox::ProxiesBox(
|
||||
QWidget*,
|
||||
not_null<ProxiesBoxController*> controller)
|
||||
not_null<ProxiesBoxController*> controller,
|
||||
Core::SettingsProxy &settings)
|
||||
: _controller(controller)
|
||||
, _settings(settings)
|
||||
, _initialWrap(this) {
|
||||
_controller->views(
|
||||
) | rpl::start_with_next([=](View &&view) {
|
||||
@@ -590,11 +597,11 @@ void ProxiesBox::setupContent() {
|
||||
object_ptr<Ui::Checkbox>(
|
||||
inner,
|
||||
tr::lng_connection_try_ipv6(tr::now),
|
||||
Global::TryIPv6()),
|
||||
_settings.tryIPv6()),
|
||||
st::proxyTryIPv6Padding);
|
||||
_proxySettings
|
||||
= std::make_shared<Ui::RadioenumGroup<ProxyData::Settings>>(
|
||||
Global::ProxySettings());
|
||||
_settings.settings());
|
||||
inner->add(
|
||||
object_ptr<Ui::Radioenum<ProxyData::Settings>>(
|
||||
inner,
|
||||
@@ -622,7 +629,7 @@ void ProxiesBox::setupContent() {
|
||||
object_ptr<Ui::Checkbox>(
|
||||
inner,
|
||||
tr::lng_proxy_use_for_calls(tr::now),
|
||||
Global::UseProxyForCalls()),
|
||||
_settings.useProxyForCalls()),
|
||||
style::margins(
|
||||
0,
|
||||
st::proxyUsePadding.top(),
|
||||
@@ -651,7 +658,7 @@ void ProxiesBox::setupContent() {
|
||||
|
||||
_proxySettings->setChangedCallback([=](ProxyData::Settings value) {
|
||||
if (!_controller->setProxySettings(value)) {
|
||||
_proxySettings->setValue(Global::ProxySettings());
|
||||
_proxySettings->setValue(_settings.settings());
|
||||
addNewProxy();
|
||||
}
|
||||
refreshProxyForCalls();
|
||||
@@ -1050,20 +1057,22 @@ void ProxyBox::addLabel(
|
||||
|
||||
ProxiesBoxController::ProxiesBoxController(not_null<Main::Account*> account)
|
||||
: _account(account)
|
||||
, _settings(Core::App().settings().proxy())
|
||||
, _saveTimer([] { Local::writeSettings(); }) {
|
||||
_list = ranges::views::all(
|
||||
Global::ProxiesList()
|
||||
_settings.list()
|
||||
) | ranges::views::transform([&](const ProxyData &proxy) {
|
||||
return Item{ ++_idCounter, proxy };
|
||||
}) | ranges::to_vector;
|
||||
|
||||
subscribe(Global::RefConnectionTypeChanged(), [=] {
|
||||
_proxySettingsChanges.fire_copy(Global::ProxySettings());
|
||||
const auto i = findByProxy(Global::SelectedProxy());
|
||||
_settings.connectionTypeChanges(
|
||||
) | rpl::start_with_next([=] {
|
||||
_proxySettingsChanges.fire_copy(_settings.settings());
|
||||
const auto i = findByProxy(_settings.selected());
|
||||
if (i != end(_list)) {
|
||||
updateView(*i);
|
||||
}
|
||||
});
|
||||
}, _lifetime);
|
||||
|
||||
for (auto &item : _list) {
|
||||
refreshChecker(item);
|
||||
@@ -1086,17 +1095,32 @@ void ProxiesBoxController::ShowApplyConfirmation(
|
||||
proxy.password = fields.value(qsl("secret"));
|
||||
}
|
||||
if (proxy) {
|
||||
const auto displayed = "https://" + server + "/";
|
||||
const auto parsed = QUrl::fromUserInput(displayed);
|
||||
const auto displayUrl = !UrlClickHandler::IsSuspicious(displayed)
|
||||
? displayed
|
||||
: parsed.isValid()
|
||||
? QString::fromUtf8(parsed.toEncoded())
|
||||
: UrlClickHandler::ShowEncoded(displayed);
|
||||
const auto displayServer = QString(
|
||||
displayUrl
|
||||
).replace(
|
||||
QRegularExpression(
|
||||
"^https://",
|
||||
QRegularExpression::CaseInsensitiveOption),
|
||||
QString()
|
||||
).replace(QRegularExpression("/$"), QString());
|
||||
const auto text = tr::lng_sure_enable_socks(
|
||||
tr::now,
|
||||
lt_server,
|
||||
server,
|
||||
displayServer,
|
||||
lt_port,
|
||||
QString::number(port))
|
||||
+ (proxy.type == Type::Mtproto
|
||||
? "\n\n" + tr::lng_proxy_sponsor_warning(tr::now)
|
||||
: QString());
|
||||
auto callback = [=](Fn<void()> &&close) {
|
||||
auto &proxies = Global::RefProxiesList();
|
||||
auto &proxies = Core::App().settings().proxy().list();
|
||||
if (!ranges::contains(proxies, proxy)) {
|
||||
proxies.push_back(proxy);
|
||||
}
|
||||
@@ -1123,7 +1147,7 @@ void ProxiesBoxController::ShowApplyConfirmation(
|
||||
auto ProxiesBoxController::proxySettingsValue() const
|
||||
-> rpl::producer<ProxyData::Settings> {
|
||||
return _proxySettingsChanges.events_starting_with_copy(
|
||||
Global::ProxySettings()
|
||||
_settings.settings()
|
||||
) | rpl::distinct_until_changed();
|
||||
}
|
||||
|
||||
@@ -1134,6 +1158,7 @@ void ProxiesBoxController::refreshChecker(Item &item) {
|
||||
: Variants::Tcp;
|
||||
const auto mtproto = &_account->mtp();
|
||||
const auto dcId = mtproto->mainDcId();
|
||||
const auto forFiles = false;
|
||||
|
||||
item.state = ItemState::Checking;
|
||||
const auto setup = [&](Checker &checker, const bytes::vector &secret) {
|
||||
@@ -1152,7 +1177,8 @@ void ProxiesBoxController::refreshChecker(Item &item) {
|
||||
item.data.host,
|
||||
item.data.port,
|
||||
secret,
|
||||
dcId);
|
||||
dcId,
|
||||
forFiles);
|
||||
item.checkerv6 = nullptr;
|
||||
} else {
|
||||
const auto options = mtproto->dcOptions().lookup(
|
||||
@@ -1164,7 +1190,8 @@ void ProxiesBoxController::refreshChecker(Item &item) {
|
||||
Variants::Address address) {
|
||||
const auto &list = options.data[address][type];
|
||||
if (list.empty()
|
||||
|| (address == Variants::IPv6 && !Global::TryIPv6())) {
|
||||
|| ((address == Variants::IPv6)
|
||||
&& !Core::App().settings().proxy().tryIPv6())) {
|
||||
checker = nullptr;
|
||||
return;
|
||||
}
|
||||
@@ -1174,7 +1201,8 @@ void ProxiesBoxController::refreshChecker(Item &item) {
|
||||
QString::fromStdString(endpoint.ip),
|
||||
endpoint.port,
|
||||
endpoint.secret,
|
||||
dcId);
|
||||
dcId,
|
||||
forFiles);
|
||||
};
|
||||
connect(item.checker, Variants::IPv4);
|
||||
connect(item.checkerv6, Variants::IPv6);
|
||||
@@ -1225,7 +1253,7 @@ object_ptr<Ui::BoxContent> ProxiesBoxController::CreateOwningBox(
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> ProxiesBoxController::create() {
|
||||
auto result = Box<ProxiesBox>(this);
|
||||
auto result = Box<ProxiesBox>(this, _settings);
|
||||
for (const auto &item : _list) {
|
||||
updateView(item);
|
||||
}
|
||||
@@ -1263,14 +1291,13 @@ void ProxiesBoxController::shareItem(int id) {
|
||||
|
||||
void ProxiesBoxController::applyItem(int id) {
|
||||
auto item = findById(id);
|
||||
if ((Global::ProxySettings() == ProxyData::Settings::Enabled)
|
||||
&& Global::SelectedProxy() == item->data) {
|
||||
if (_settings.isEnabled() && (_settings.selected() == item->data)) {
|
||||
return;
|
||||
} else if (item->deleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto j = findByProxy(Global::SelectedProxy());
|
||||
auto j = findByProxy(_settings.selected());
|
||||
|
||||
Core::App().setCurrentProxy(
|
||||
item->data,
|
||||
@@ -1288,12 +1315,13 @@ void ProxiesBoxController::setDeleted(int id, bool deleted) {
|
||||
item->deleted = deleted;
|
||||
|
||||
if (deleted) {
|
||||
auto &proxies = Global::RefProxiesList();
|
||||
auto &proxies = _settings.list();
|
||||
proxies.erase(ranges::remove(proxies, item->data), end(proxies));
|
||||
|
||||
if (item->data == Global::SelectedProxy()) {
|
||||
_lastSelectedProxy = base::take(Global::RefSelectedProxy());
|
||||
if (Global::ProxySettings() == ProxyData::Settings::Enabled) {
|
||||
if (item->data == _settings.selected()) {
|
||||
_lastSelectedProxy = _settings.selected();
|
||||
_settings.setSelected(MTP::ProxyData());
|
||||
if (_settings.isEnabled()) {
|
||||
_lastSelectedProxyUsed = true;
|
||||
Core::App().setCurrentProxy(
|
||||
ProxyData(),
|
||||
@@ -1304,7 +1332,7 @@ void ProxiesBoxController::setDeleted(int id, bool deleted) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto &proxies = Global::RefProxiesList();
|
||||
auto &proxies = _settings.list();
|
||||
if (ranges::find(proxies, item->data) == end(proxies)) {
|
||||
auto insertBefore = item + 1;
|
||||
while (insertBefore != end(_list) && insertBefore->deleted) {
|
||||
@@ -1316,15 +1344,15 @@ void ProxiesBoxController::setDeleted(int id, bool deleted) {
|
||||
proxies.insert(insertBeforeIt, item->data);
|
||||
}
|
||||
|
||||
if (!Global::SelectedProxy() && _lastSelectedProxy == item->data) {
|
||||
Assert(Global::ProxySettings() != ProxyData::Settings::Enabled);
|
||||
if (!_settings.selected() && _lastSelectedProxy == item->data) {
|
||||
Assert(!_settings.isEnabled());
|
||||
|
||||
if (base::take(_lastSelectedProxyUsed)) {
|
||||
Core::App().setCurrentProxy(
|
||||
base::take(_lastSelectedProxy),
|
||||
ProxyData::Settings::Enabled);
|
||||
} else {
|
||||
Global::SetSelectedProxy(base::take(_lastSelectedProxy));
|
||||
_settings.setSelected(base::take(_lastSelectedProxy));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1352,7 +1380,7 @@ object_ptr<Ui::BoxContent> ProxiesBoxController::editItemBox(int id) {
|
||||
void ProxiesBoxController::replaceItemWith(
|
||||
std::vector<Item>::iterator which,
|
||||
std::vector<Item>::iterator with) {
|
||||
auto &proxies = Global::RefProxiesList();
|
||||
auto &proxies = _settings.list();
|
||||
proxies.erase(ranges::remove(proxies, which->data), end(proxies));
|
||||
|
||||
_views.fire({ which->id });
|
||||
@@ -1372,7 +1400,7 @@ void ProxiesBoxController::replaceItemValue(
|
||||
restoreItem(which->id);
|
||||
}
|
||||
|
||||
auto &proxies = Global::RefProxiesList();
|
||||
auto &proxies = _settings.list();
|
||||
const auto i = ranges::find(proxies, which->data);
|
||||
Assert(i != end(proxies));
|
||||
*i = proxy;
|
||||
@@ -1403,7 +1431,7 @@ object_ptr<Ui::BoxContent> ProxiesBoxController::addNewItemBox() {
|
||||
}
|
||||
|
||||
void ProxiesBoxController::addNewItem(const ProxyData &proxy) {
|
||||
auto &proxies = Global::RefProxiesList();
|
||||
auto &proxies = _settings.list();
|
||||
proxies.push_back(proxy);
|
||||
|
||||
_list.push_back({ ++_idCounter, proxy });
|
||||
@@ -1412,43 +1440,42 @@ void ProxiesBoxController::addNewItem(const ProxyData &proxy) {
|
||||
}
|
||||
|
||||
bool ProxiesBoxController::setProxySettings(ProxyData::Settings value) {
|
||||
if (Global::ProxySettings() == value) {
|
||||
if (_settings.settings() == value) {
|
||||
return true;
|
||||
} else if (value == ProxyData::Settings::Enabled) {
|
||||
if (Global::ProxiesList().empty()) {
|
||||
if (_settings.list().empty()) {
|
||||
return false;
|
||||
} else if (!Global::SelectedProxy()) {
|
||||
Global::SetSelectedProxy(Global::ProxiesList().back());
|
||||
auto j = findByProxy(Global::SelectedProxy());
|
||||
} else if (!_settings.selected()) {
|
||||
_settings.setSelected(_settings.list().back());
|
||||
auto j = findByProxy(_settings.selected());
|
||||
if (j != end(_list)) {
|
||||
updateView(*j);
|
||||
}
|
||||
}
|
||||
}
|
||||
Core::App().setCurrentProxy(Global::SelectedProxy(), value);
|
||||
Core::App().setCurrentProxy(_settings.selected(), value);
|
||||
saveDelayed();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProxiesBoxController::setProxyForCalls(bool enabled) {
|
||||
if (Global::UseProxyForCalls() == enabled) {
|
||||
if (_settings.useProxyForCalls() == enabled) {
|
||||
return;
|
||||
}
|
||||
Global::SetUseProxyForCalls(enabled);
|
||||
if ((Global::ProxySettings() == ProxyData::Settings::Enabled)
|
||||
&& Global::SelectedProxy().supportsCalls()) {
|
||||
Global::RefConnectionTypeChanged().notify();
|
||||
_settings.setUseProxyForCalls(enabled);
|
||||
if (_settings.isEnabled() && _settings.selected().supportsCalls()) {
|
||||
_settings.connectionTypeChangesNotify();
|
||||
}
|
||||
saveDelayed();
|
||||
}
|
||||
|
||||
void ProxiesBoxController::setTryIPv6(bool enabled) {
|
||||
if (Global::TryIPv6() == enabled) {
|
||||
if (Core::App().settings().proxy().tryIPv6() == enabled) {
|
||||
return;
|
||||
}
|
||||
Global::SetTryIPv6(enabled);
|
||||
Core::App().settings().proxy().setTryIPv6(enabled);
|
||||
_account->mtp().restart();
|
||||
Global::RefConnectionTypeChanged().notify();
|
||||
_settings.connectionTypeChangesNotify();
|
||||
saveDelayed();
|
||||
}
|
||||
|
||||
@@ -1461,7 +1488,7 @@ auto ProxiesBoxController::views() const -> rpl::producer<ItemView> {
|
||||
}
|
||||
|
||||
void ProxiesBoxController::updateView(const Item &item) {
|
||||
const auto selected = (Global::SelectedProxy() == item.data);
|
||||
const auto selected = (_settings.selected() == item.data);
|
||||
const auto deleted = item.deleted;
|
||||
const auto type = [&] {
|
||||
switch (item.data.type) {
|
||||
@@ -1475,8 +1502,7 @@ void ProxiesBoxController::updateView(const Item &item) {
|
||||
Unexpected("Proxy type in ProxiesBoxController::updateView.");
|
||||
}();
|
||||
const auto state = [&] {
|
||||
if (!selected
|
||||
|| (Global::ProxySettings() != ProxyData::Settings::Enabled)) {
|
||||
if (!selected || !_settings.isEnabled()) {
|
||||
return item.state;
|
||||
} else if (_account->mtp().dcstate() == MTP::ConnectedState) {
|
||||
return ItemState::Online;
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "base/timer.h"
|
||||
#include "base/object_ptr.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "mtproto/connection_abstract.h"
|
||||
#include "mtproto/mtproto_proxy_data.h"
|
||||
|
||||
@@ -28,7 +29,7 @@ namespace Main {
|
||||
class Account;
|
||||
} // namespace Main
|
||||
|
||||
class ProxiesBoxController : public base::Subscriber {
|
||||
class ProxiesBoxController {
|
||||
public:
|
||||
using ProxyData = MTP::ProxyData;
|
||||
using Type = ProxyData::Type;
|
||||
@@ -110,6 +111,7 @@ private:
|
||||
void addNewItem(const ProxyData &proxy);
|
||||
|
||||
const not_null<Main::Account*> _account;
|
||||
Core::SettingsProxy &_settings;
|
||||
int _idCounter = 0;
|
||||
std::vector<Item> _list;
|
||||
rpl::event_stream<ItemView> _views;
|
||||
@@ -119,4 +121,6 @@ private:
|
||||
ProxyData _lastSelectedProxy;
|
||||
bool _lastSelectedProxyUsed = false;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
@@ -1085,7 +1085,7 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
||||
send({ .silent = true });
|
||||
};
|
||||
const auto sendScheduled = [=] {
|
||||
Ui::show(
|
||||
_controller->show(
|
||||
HistoryView::PrepareScheduleBox(
|
||||
this,
|
||||
SendMenu::Type::Scheduled,
|
||||
|
||||
@@ -47,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/text/format_song_document_name.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/chat/attach/attach_prepare.h"
|
||||
@@ -220,7 +221,7 @@ EditCaptionBox::EditCaptionBox(
|
||||
const auto document = _documentMedia->owner();
|
||||
const auto nameString = document->isVoiceMessage()
|
||||
? tr::lng_media_audio(tr::now)
|
||||
: document->composeNameString();
|
||||
: Ui::Text::FormatSongNameFor(document).string();
|
||||
setName(nameString, document->size);
|
||||
_isImage = document->isImage();
|
||||
_isAudio = document->isVoiceMessage()
|
||||
@@ -486,13 +487,13 @@ void EditCaptionBox::handleStreamingError(Error &&error) {
|
||||
}
|
||||
|
||||
void EditCaptionBox::streamingReady(Information &&info) {
|
||||
const auto calculateGifDimensions = [&]() {
|
||||
const auto calculateGifDimensions = [&] {
|
||||
const auto scaled = QSize(
|
||||
info.video.size.width(),
|
||||
info.video.size.height()
|
||||
).scaled(
|
||||
st::sendMediaPreviewSize * cIntRetinaFactor(),
|
||||
st::confirmMaxHeight * cIntRetinaFactor(),
|
||||
st::sendMediaPreviewSize,
|
||||
st::confirmMaxHeight,
|
||||
Qt::KeepAspectRatio);
|
||||
_thumbw = _gifw = scaled.width();
|
||||
_thumbh = _gifh = scaled.height();
|
||||
@@ -549,10 +550,10 @@ void EditCaptionBox::updateEditPreview() {
|
||||
if (shouldAsDoc) {
|
||||
auto nameString = filename;
|
||||
if (const auto song = std::get_if<Info::Song>(fileMedia)) {
|
||||
nameString = Ui::ComposeNameString(
|
||||
nameString = Ui::Text::FormatSongName(
|
||||
filename,
|
||||
song->title,
|
||||
song->performer);
|
||||
song->performer).string();
|
||||
_isAudio = true;
|
||||
|
||||
if (auto cover = song->cover; !cover.isNull()) {
|
||||
@@ -620,7 +621,7 @@ void EditCaptionBox::updateEditMediaButton() {
|
||||
const auto icon = _doc
|
||||
? &st::editMediaButtonIconFile
|
||||
: &st::editMediaButtonIconPhoto;
|
||||
const auto color = _doc ? &st::windowBgRipple : &st::callFingerprintBg;
|
||||
const auto color = _doc ? &st::windowBgRipple : &st::roundedBg;
|
||||
_editMedia->setIconOverride(icon);
|
||||
_editMedia->setRippleColorOverride(color);
|
||||
_editMedia->setForceRippled(!_doc, anim::type::instant);
|
||||
|
||||
@@ -27,8 +27,8 @@ public:
|
||||
return _y;
|
||||
}
|
||||
|
||||
base::Observable<void> &changed() {
|
||||
return _changed;
|
||||
rpl::producer<> changed() const {
|
||||
return _changed.events();
|
||||
}
|
||||
void setHSB(HSB hsb);
|
||||
void setRGB(int red, int green, int blue);
|
||||
@@ -61,7 +61,7 @@ private:
|
||||
float64 _y = 0.;
|
||||
|
||||
bool _choosing = false;
|
||||
base::Observable<void> _changed;
|
||||
rpl::event_stream<> _changed;
|
||||
|
||||
};
|
||||
|
||||
@@ -234,7 +234,7 @@ void EditColorBox::Picker::updateCurrentPoint(QPoint localPosition) {
|
||||
_x = x;
|
||||
_y = y;
|
||||
update();
|
||||
_changed.notify();
|
||||
_changed.fire({});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,8 +284,8 @@ public:
|
||||
};
|
||||
Slider(QWidget *parent, Direction direction, Type type, QColor color);
|
||||
|
||||
base::Observable<void> &changed() {
|
||||
return _changed;
|
||||
rpl::producer<> changed() const {
|
||||
return _changed.events();
|
||||
}
|
||||
float64 value() const {
|
||||
return _value;
|
||||
@@ -335,7 +335,7 @@ private:
|
||||
QBrush _transparent;
|
||||
|
||||
bool _choosing = false;
|
||||
base::Observable<void> _changed;
|
||||
rpl::event_stream<> _changed;
|
||||
|
||||
};
|
||||
|
||||
@@ -349,7 +349,9 @@ EditColorBox::Slider::Slider(
|
||||
, _type(type)
|
||||
, _color(color.red(), color.green(), color.blue())
|
||||
, _value(valueFromColor(color))
|
||||
, _transparent((_type == Type::Opacity) ? style::transparentPlaceholderBrush() : QBrush()) {
|
||||
, _transparent((_type == Type::Opacity)
|
||||
? style::TransparentPlaceholder()
|
||||
: QBrush()) {
|
||||
prepareMinSize();
|
||||
}
|
||||
|
||||
@@ -538,7 +540,7 @@ void EditColorBox::Slider::updateCurrentPoint(QPoint localPosition) {
|
||||
if (_value != value) {
|
||||
_value = value;
|
||||
update();
|
||||
_changed.notify();
|
||||
_changed.fire({});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -758,7 +760,7 @@ EditColorBox::EditColorBox(
|
||||
, _greenField(this, st::colorValueInput, "G", 255)
|
||||
, _blueField(this, st::colorValueInput, "B", 255)
|
||||
, _result(this, st::colorResultInput)
|
||||
, _transparent(style::transparentPlaceholderBrush())
|
||||
, _transparent(style::TransparentPlaceholder())
|
||||
, _current(current)
|
||||
, _new(current) {
|
||||
if (_mode == Mode::RGBA) {
|
||||
@@ -824,16 +826,14 @@ void EditColorBox::prepare() {
|
||||
auto height = st::colorEditSkip + st::colorPickerSize + st::colorEditSkip + st::colorSliderWidth + st::colorEditSkip;
|
||||
setDimensions(st::colorEditWidth, height);
|
||||
|
||||
subscribe(_picker->changed(), [=] { updateFromControls(); });
|
||||
if (_hueSlider) {
|
||||
subscribe(_hueSlider->changed(), [=] { updateFromControls(); });
|
||||
}
|
||||
if (_opacitySlider) {
|
||||
subscribe(_opacitySlider->changed(), [=] { updateFromControls(); });
|
||||
}
|
||||
if (_lightnessSlider) {
|
||||
subscribe(_lightnessSlider->changed(), [=] { updateFromControls(); });
|
||||
}
|
||||
rpl::merge(
|
||||
_picker->changed(),
|
||||
(_hueSlider ? _hueSlider->changed() : rpl::never<>()),
|
||||
(_opacitySlider ? _opacitySlider->changed() : rpl::never<>()),
|
||||
(_lightnessSlider ? _lightnessSlider->changed() : rpl::never<>())
|
||||
) | rpl::start_with_next([=] {
|
||||
updateFromControls();
|
||||
}, lifetime());
|
||||
|
||||
boxClosing() | rpl::start_with_next([=] {
|
||||
if (_cancelCallback) {
|
||||
|
||||
@@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "boxes/abstract_box.h"
|
||||
|
||||
class EditColorBox : public Ui::BoxContent, private base::Subscriber {
|
||||
class EditColorBox : public Ui::BoxContent {
|
||||
public:
|
||||
enum class Mode {
|
||||
RGBA,
|
||||
|
||||
@@ -165,7 +165,7 @@ void EditPrivacyBox::editExceptions(
|
||||
}));
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
};
|
||||
Ui::show(
|
||||
_window->show(
|
||||
Box<PeerListBox>(std::move(controller), std::move(initBox)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
@@ -1166,15 +1166,19 @@ base::binary_guard LanguageBox::Show() {
|
||||
if (manager.languageList().empty()) {
|
||||
auto guard = std::make_shared<base::binary_guard>(
|
||||
result.make_guard());
|
||||
auto alive = std::make_shared<std::unique_ptr<base::Subscription>>(
|
||||
std::make_unique<base::Subscription>());
|
||||
**alive = manager.languageListChanged().add_subscription([=] {
|
||||
auto lifetime = std::make_shared<rpl::lifetime>();
|
||||
manager.languageListChanged(
|
||||
) | rpl::take(
|
||||
1
|
||||
) | rpl::start_with_next([=]() mutable {
|
||||
const auto show = guard->alive();
|
||||
*alive = nullptr;
|
||||
if (lifetime) {
|
||||
base::take(lifetime)->destroy();
|
||||
}
|
||||
if (show) {
|
||||
Ui::show(Box<LanguageBox>());
|
||||
}
|
||||
});
|
||||
}, *lifetime);
|
||||
} else {
|
||||
Ui::show(Box<LanguageBox>());
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "passport/passport_encryption.h"
|
||||
#include "passport/passport_panel_edit_contact.h"
|
||||
#include "settings/settings_privacy_security.h"
|
||||
#include "facades.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_passport.h"
|
||||
#include "styles/style_boxes.h"
|
||||
@@ -119,7 +118,12 @@ PasscodeBox::PasscodeBox(
|
||||
, _turningOff(turningOff)
|
||||
, _about(st::boxWidth - st::boxPadding.left() * 1.5)
|
||||
, _oldPasscode(this, st::defaultInputField, tr::lng_passcode_enter_old())
|
||||
, _newPasscode(this, st::defaultInputField, Global::LocalPasscode() ? tr::lng_passcode_enter_new() : tr::lng_passcode_enter_first())
|
||||
, _newPasscode(
|
||||
this,
|
||||
st::defaultInputField,
|
||||
session->domain().local().hasLocalPasscode()
|
||||
? tr::lng_passcode_enter_new()
|
||||
: tr::lng_passcode_enter_first())
|
||||
, _reenterPasscode(this, st::defaultInputField, tr::lng_passcode_confirm_new())
|
||||
, _passwordHint(this, st::defaultInputField, tr::lng_cloud_password_hint())
|
||||
, _recoverEmail(this, st::defaultInputField, tr::lng_cloud_password_email())
|
||||
@@ -164,7 +168,9 @@ rpl::producer<> PasscodeBox::clearUnconfirmedPassword() const {
|
||||
}
|
||||
|
||||
bool PasscodeBox::currentlyHave() const {
|
||||
return _cloudPwd ? (!!_cloudFields.curRequest) : Global::LocalPasscode();
|
||||
return _cloudPwd
|
||||
? (!!_cloudFields.curRequest)
|
||||
: _session->domain().local().hasLocalPasscode();
|
||||
}
|
||||
|
||||
bool PasscodeBox::onlyCheckCurrent() const {
|
||||
@@ -520,7 +526,7 @@ void PasscodeBox::save(bool force) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Core::App().domain().local().checkPasscode(old.toUtf8())) {
|
||||
if (_session->domain().local().checkPasscode(old.toUtf8())) {
|
||||
cSetPasscodeBadTries(0);
|
||||
if (_turningOff) pwd = conf = QString();
|
||||
} else {
|
||||
@@ -588,7 +594,7 @@ void PasscodeBox::save(bool force) {
|
||||
closeReplacedBy();
|
||||
const auto weak = Ui::MakeWeak(this);
|
||||
cSetPasscodeBadTries(0);
|
||||
Core::App().domain().local().setPasscode(pwd.toUtf8());
|
||||
_session->domain().local().setPasscode(pwd.toUtf8());
|
||||
Core::App().localPasscodeChanged();
|
||||
if (weak) {
|
||||
closeBox();
|
||||
|
||||
@@ -434,6 +434,7 @@ PeerListRow::PeerListRow(not_null<PeerData*> peer)
|
||||
PeerListRow::PeerListRow(not_null<PeerData*> peer, PeerListRowId id)
|
||||
: _id(id)
|
||||
, _peer(peer)
|
||||
, _hidden(false)
|
||||
, _initialized(false)
|
||||
, _isSearchResult(false)
|
||||
, _isSavedMessagesChat(false)
|
||||
@@ -442,6 +443,7 @@ PeerListRow::PeerListRow(not_null<PeerData*> peer, PeerListRowId id)
|
||||
|
||||
PeerListRow::PeerListRow(PeerListRowId id)
|
||||
: _id(id)
|
||||
, _hidden(false)
|
||||
, _initialized(false)
|
||||
, _isSearchResult(false)
|
||||
, _isSavedMessagesChat(false)
|
||||
@@ -529,7 +531,7 @@ QString PeerListRow::generateShortName() {
|
||||
: peer()->shortName();
|
||||
}
|
||||
|
||||
std::shared_ptr<Data::CloudImageView> PeerListRow::ensureUserpicView() {
|
||||
std::shared_ptr<Data::CloudImageView> &PeerListRow::ensureUserpicView() {
|
||||
if (!_userpic) {
|
||||
_userpic = peer()->createUserpicView();
|
||||
}
|
||||
@@ -588,11 +590,14 @@ void PeerListRow::paintStatusText(
|
||||
_status.drawLeftElided(p, x, y, availableWidth, outerWidth);
|
||||
}
|
||||
|
||||
template <typename UpdateCallback>
|
||||
void PeerListRow::addRipple(const style::PeerListItem &st, QSize size, QPoint point, UpdateCallback updateCallback) {
|
||||
template <typename MaskGenerator, typename UpdateCallback>
|
||||
void PeerListRow::addRipple(const style::PeerListItem &st, MaskGenerator &&maskGenerator, QPoint point, UpdateCallback &&updateCallback) {
|
||||
if (!_ripple) {
|
||||
auto mask = Ui::RippleAnimation::rectMask(size);
|
||||
_ripple = std::make_unique<Ui::RippleAnimation>(st.button.ripple, std::move(mask), std::move(updateCallback));
|
||||
auto mask = maskGenerator();
|
||||
if (mask.isNull()) {
|
||||
return;
|
||||
}
|
||||
_ripple = std::make_unique<Ui::RippleAnimation>(st.button.ripple, std::move(mask), std::forward<UpdateCallback>(updateCallback));
|
||||
}
|
||||
_ripple->add(point);
|
||||
}
|
||||
@@ -741,12 +746,42 @@ PeerListContent::PeerListContent(
|
||||
_repaintByStatus.setCallback([this] { update(); });
|
||||
}
|
||||
|
||||
void PeerListContent::setMode(Mode mode) {
|
||||
if (mode == Mode::Default && _mode == Mode::Default) {
|
||||
return;
|
||||
}
|
||||
_mode = mode;
|
||||
switch (_mode) {
|
||||
case Mode::Default:
|
||||
_rowHeight = _st.item.height;
|
||||
break;
|
||||
case Mode::Custom:
|
||||
_rowHeight = _controller->customRowHeight();
|
||||
break;
|
||||
}
|
||||
const auto wasMouseSelection = _mouseSelection;
|
||||
const auto wasLastMousePosition = _lastMousePosition;
|
||||
_contextMenu = nullptr;
|
||||
if (wasMouseSelection) {
|
||||
setSelected(Selected());
|
||||
}
|
||||
setPressed(Selected());
|
||||
refreshRows();
|
||||
if (wasMouseSelection && wasLastMousePosition) {
|
||||
selectByMouse(*wasLastMousePosition);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListContent::appendRow(std::unique_ptr<PeerListRow> row) {
|
||||
Expects(row != nullptr);
|
||||
|
||||
if (_rowsById.find(row->id()) == _rowsById.cend()) {
|
||||
row->setAbsoluteIndex(_rows.size());
|
||||
addRowEntry(row.get());
|
||||
if (!_hiddenRows.empty()) {
|
||||
Assert(!row->hidden());
|
||||
_filterResults.push_back(row.get());
|
||||
}
|
||||
_rows.push_back(std::move(row));
|
||||
}
|
||||
}
|
||||
@@ -784,6 +819,17 @@ void PeerListContent::changeCheckState(
|
||||
[=] { updateRow(row); });
|
||||
}
|
||||
|
||||
void PeerListContent::setRowHidden(not_null<PeerListRow*> row, bool hidden) {
|
||||
Expects(!row->isSearchResult());
|
||||
|
||||
row->setHidden(hidden);
|
||||
if (hidden) {
|
||||
_hiddenRows.emplace(row);
|
||||
} else {
|
||||
_hiddenRows.remove(row);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListContent::addRowEntry(not_null<PeerListRow*> row) {
|
||||
if (_controller->respectSavedMessagesChat() && !row->special()) {
|
||||
if (row->peer()->isSelf()) {
|
||||
@@ -850,6 +896,10 @@ void PeerListContent::prependRow(std::unique_ptr<PeerListRow> row) {
|
||||
|
||||
if (_rowsById.find(row->id()) == _rowsById.cend()) {
|
||||
addRowEntry(row.get());
|
||||
if (!_hiddenRows.empty()) {
|
||||
Assert(!row->hidden());
|
||||
_filterResults.insert(_filterResults.begin(), row.get());
|
||||
}
|
||||
_rows.insert(_rows.begin(), std::move(row));
|
||||
refreshIndices();
|
||||
}
|
||||
@@ -865,6 +915,10 @@ void PeerListContent::prependRowFromSearchResult(not_null<PeerListRow*> row) {
|
||||
Assert(_searchRows[index].get() == row);
|
||||
|
||||
row->setIsSearchResult(false);
|
||||
if (!_hiddenRows.empty()) {
|
||||
Assert(!row->hidden());
|
||||
_filterResults.insert(_filterResults.begin(), row);
|
||||
}
|
||||
_rows.insert(_rows.begin(), std::move(_searchRows[index]));
|
||||
refreshIndices();
|
||||
removeRowAtIndex(_searchRows, index);
|
||||
@@ -918,6 +972,7 @@ void PeerListContent::removeRow(not_null<PeerListRow*> row) {
|
||||
_filterResults.erase(
|
||||
ranges::remove(_filterResults, row),
|
||||
end(_filterResults));
|
||||
_hiddenRows.remove(row);
|
||||
removeRowAtIndex(eraseFrom, index);
|
||||
|
||||
restoreSelection();
|
||||
@@ -955,7 +1010,9 @@ void PeerListContent::convertRowToSearchResult(not_null<PeerListRow*> row) {
|
||||
|
||||
removeFromSearchIndex(row);
|
||||
row->setIsSearchResult(true);
|
||||
row->setHidden(false);
|
||||
row->setAbsoluteIndex(_searchRows.size());
|
||||
_hiddenRows.remove(row);
|
||||
_searchRows.push_back(std::move(_rows[index]));
|
||||
removeRowAtIndex(_rows, index);
|
||||
}
|
||||
@@ -1040,6 +1097,14 @@ int PeerListContent::labelHeight() const {
|
||||
}
|
||||
|
||||
void PeerListContent::refreshRows() {
|
||||
if (!_hiddenRows.empty()) {
|
||||
_filterResults.clear();
|
||||
for (const auto &row : _rows) {
|
||||
if (!row->hidden()) {
|
||||
_filterResults.push_back(row.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
resizeToWidth(width());
|
||||
if (_visibleBottom > 0) {
|
||||
checkScrollForPreload();
|
||||
@@ -1053,7 +1118,7 @@ void PeerListContent::refreshRows() {
|
||||
void PeerListContent::setSearchMode(PeerListSearchMode mode) {
|
||||
if (_searchMode != mode) {
|
||||
if (!addingToSearchIndex()) {
|
||||
for_const (auto &row, _rows) {
|
||||
for (const auto &row : _rows) {
|
||||
addToSearchIndex(row.get());
|
||||
}
|
||||
}
|
||||
@@ -1080,25 +1145,27 @@ void PeerListContent::clearSearchRows() {
|
||||
void PeerListContent::paintEvent(QPaintEvent *e) {
|
||||
Painter p(this);
|
||||
|
||||
auto clip = e->rect();
|
||||
p.fillRect(clip, _st.item.button.textBg);
|
||||
const auto clip = e->rect();
|
||||
if (_mode != Mode::Custom) {
|
||||
p.fillRect(clip, _st.item.button.textBg);
|
||||
}
|
||||
|
||||
auto repaintByStatusAfter = _repaintByStatus.remainingTime();
|
||||
const auto repaintByStatusAfter = _repaintByStatus.remainingTime();
|
||||
auto repaintAfterMin = repaintByStatusAfter;
|
||||
|
||||
auto rowsTopCached = rowsTop();
|
||||
auto ms = crl::now();
|
||||
auto yFrom = clip.y() - rowsTopCached;
|
||||
auto yTo = clip.y() + clip.height() - rowsTopCached;
|
||||
const auto rowsTopCached = rowsTop();
|
||||
const auto now = crl::now();
|
||||
const auto yFrom = clip.y() - rowsTopCached;
|
||||
const auto yTo = clip.y() + clip.height() - rowsTopCached;
|
||||
p.translate(0, rowsTopCached);
|
||||
auto count = shownRowsCount();
|
||||
const auto count = shownRowsCount();
|
||||
if (count > 0) {
|
||||
auto from = floorclamp(yFrom, _rowHeight, 0, count);
|
||||
auto to = ceilclamp(yTo, _rowHeight, 0, count);
|
||||
const auto from = floorclamp(yFrom, _rowHeight, 0, count);
|
||||
const auto to = ceilclamp(yTo, _rowHeight, 0, count);
|
||||
p.translate(0, from * _rowHeight);
|
||||
for (auto index = from; index != to; ++index) {
|
||||
auto repaintAfter = paintRow(p, ms, RowIndex(index));
|
||||
if (repaintAfter >= 0
|
||||
const auto repaintAfter = paintRow(p, now, RowIndex(index));
|
||||
if (repaintAfter > 0
|
||||
&& (repaintAfterMin < 0
|
||||
|| repaintAfterMin > repaintAfter)) {
|
||||
repaintAfterMin = repaintAfter;
|
||||
@@ -1213,9 +1280,16 @@ void PeerListContent::mousePressEvent(QMouseEvent *e) {
|
||||
row->addActionRipple(point, std::move(updateCallback));
|
||||
}
|
||||
} else {
|
||||
auto size = QSize(width(), _rowHeight);
|
||||
auto point = mapFromGlobal(QCursor::pos()) - QPoint(0, getRowTop(_selected.index));
|
||||
row->addRipple(_st.item, size, point, std::move(updateCallback));
|
||||
if (_mode == Mode::Custom) {
|
||||
row->addRipple(_st.item, _controller->customRowRippleMaskGenerator(), point, std::move(updateCallback));
|
||||
} else {
|
||||
const auto maskGenerator = [&] {
|
||||
return Ui::RippleAnimation::rectMask(
|
||||
QSize(width(), _rowHeight));
|
||||
};
|
||||
row->addRipple(_st.item, maskGenerator, point, std::move(updateCallback));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (anim::Disabled()) {
|
||||
@@ -1246,13 +1320,22 @@ void PeerListContent::mousePressReleased(Qt::MouseButton button) {
|
||||
|
||||
void PeerListContent::showRowMenu(
|
||||
not_null<PeerListRow*> row,
|
||||
bool highlightRow,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed) {
|
||||
showRowMenu(findRowIndex(row), QCursor::pos(), std::move(destroyed));
|
||||
const auto index = findRowIndex(row);
|
||||
showRowMenu(
|
||||
index,
|
||||
row,
|
||||
QCursor::pos(),
|
||||
highlightRow,
|
||||
std::move(destroyed));
|
||||
}
|
||||
|
||||
bool PeerListContent::showRowMenu(
|
||||
RowIndex index,
|
||||
PeerListRow *row,
|
||||
QPoint globalPos,
|
||||
bool highlightRow,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed) {
|
||||
if (_contextMenu) {
|
||||
_contextMenu->setDestroyedCallback(nullptr);
|
||||
@@ -1263,7 +1346,9 @@ bool PeerListContent::showRowMenu(
|
||||
mousePressReleased(_pressButton);
|
||||
}
|
||||
|
||||
const auto row = getRow(index);
|
||||
if (highlightRow) {
|
||||
row = getRow(index);
|
||||
}
|
||||
if (!row) {
|
||||
return false;
|
||||
}
|
||||
@@ -1274,11 +1359,15 @@ bool PeerListContent::showRowMenu(
|
||||
return false;
|
||||
}
|
||||
|
||||
setContexted({ index, false });
|
||||
if (highlightRow) {
|
||||
setContexted({ index, false });
|
||||
}
|
||||
raw->setDestroyedCallback(crl::guard(
|
||||
this,
|
||||
[=] {
|
||||
setContexted(Selected());
|
||||
if (highlightRow) {
|
||||
setContexted(Selected());
|
||||
}
|
||||
handleMouseMove(QCursor::pos());
|
||||
if (destroyed) {
|
||||
destroyed(raw);
|
||||
@@ -1292,7 +1381,7 @@ void PeerListContent::contextMenuEvent(QContextMenuEvent *e) {
|
||||
if (e->reason() == QContextMenuEvent::Mouse) {
|
||||
handleMouseMove(e->globalPos());
|
||||
}
|
||||
if (showRowMenu(_selected.index, e->globalPos())) {
|
||||
if (showRowMenu(_selected.index, nullptr, e->globalPos(), true)) {
|
||||
e->accept();
|
||||
}
|
||||
}
|
||||
@@ -1307,7 +1396,7 @@ void PeerListContent::setPressed(Selected pressed) {
|
||||
|
||||
crl::time PeerListContent::paintRow(
|
||||
Painter &p,
|
||||
crl::time ms,
|
||||
crl::time now,
|
||||
RowIndex index) {
|
||||
const auto row = getRow(index);
|
||||
Assert(row != nullptr);
|
||||
@@ -1315,13 +1404,15 @@ crl::time PeerListContent::paintRow(
|
||||
row->lazyInitialize(_st.item);
|
||||
|
||||
auto refreshStatusAt = row->refreshStatusTime();
|
||||
if (refreshStatusAt >= 0 && ms >= refreshStatusAt) {
|
||||
if (refreshStatusAt > 0 && now >= refreshStatusAt) {
|
||||
row->refreshStatus();
|
||||
refreshStatusAt = row->refreshStatusTime();
|
||||
}
|
||||
const auto refreshStatusIn = (refreshStatusAt > 0)
|
||||
? std::max(refreshStatusAt - now, crl::time(1))
|
||||
: 0;
|
||||
|
||||
const auto peer = row->special() ? nullptr : row->peer().get();
|
||||
const auto user = peer ? peer->asUser() : nullptr;
|
||||
const auto active = (_contexted.index.value >= 0)
|
||||
? _contexted
|
||||
: (_pressed.index.value >= 0)
|
||||
@@ -1330,6 +1421,11 @@ crl::time PeerListContent::paintRow(
|
||||
const auto selected = (active.index == index);
|
||||
const auto actionSelected = (selected && active.action);
|
||||
|
||||
if (_mode == Mode::Custom) {
|
||||
_controller->customRowPaint(p, now, row, selected);
|
||||
return refreshStatusIn;
|
||||
}
|
||||
|
||||
const auto &bg = selected
|
||||
? _st.item.button.textBgOver
|
||||
: _st.item.button.textBg;
|
||||
@@ -1412,7 +1508,7 @@ crl::time PeerListContent::paintRow(
|
||||
} else {
|
||||
row->paintStatusText(p, _st.item, _st.item.statusPosition.x(), _st.item.statusPosition.y(), statusw, width(), selected);
|
||||
}
|
||||
return (refreshStatusAt - ms);
|
||||
return refreshStatusIn;
|
||||
}
|
||||
|
||||
PeerListContent::SkipResult PeerListContent::selectSkip(int direction) {
|
||||
@@ -1562,6 +1658,8 @@ void PeerListContent::searchQueryChanged(QString query) {
|
||||
if (_normalizedSearchQuery != normalizedQuery) {
|
||||
setSearchQuery(query, normalizedQuery);
|
||||
if (_controller->searchInLocal() && !searchWordsList.isEmpty()) {
|
||||
Assert(_hiddenRows.empty());
|
||||
|
||||
auto minimalList = (const std::vector<not_null<PeerListRow*>>*)nullptr;
|
||||
for (const auto &searchWord : searchWordsList) {
|
||||
auto searchWordStart = searchWord[0].toLower();
|
||||
@@ -1611,15 +1709,17 @@ void PeerListContent::searchQueryChanged(QString query) {
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListState> PeerListContent::saveState() const {
|
||||
Expects(_hiddenRows.empty());
|
||||
|
||||
auto result = std::make_unique<PeerListState>();
|
||||
result->controllerState
|
||||
= std::make_unique<PeerListController::SavedStateBase>();
|
||||
result->list.reserve(_rows.size());
|
||||
for (auto &row : _rows) {
|
||||
for (const auto &row : _rows) {
|
||||
result->list.push_back(row->peer());
|
||||
}
|
||||
result->filterResults.reserve(_filterResults.size());
|
||||
for (auto &row : _filterResults) {
|
||||
for (const auto &row : _filterResults) {
|
||||
result->filterResults.push_back(row->peer());
|
||||
}
|
||||
result->searchQuery = _searchQuery;
|
||||
@@ -1740,15 +1840,25 @@ void PeerListContent::selectByMouse(QPoint globalPosition) {
|
||||
_mouseSelection = true;
|
||||
_lastMousePosition = globalPosition;
|
||||
const auto point = mapFromGlobal(globalPosition);
|
||||
const auto customMode = (_mode == Mode::Custom);
|
||||
auto in = parentWidget()->rect().contains(parentWidget()->mapFromGlobal(globalPosition));
|
||||
auto selected = Selected();
|
||||
auto rowsPointY = point.y() - rowsTop();
|
||||
selected.index.value = (in && rowsPointY >= 0 && rowsPointY < shownRowsCount() * _rowHeight) ? (rowsPointY / _rowHeight) : -1;
|
||||
selected.index.value = (in
|
||||
&& rowsPointY >= 0
|
||||
&& rowsPointY < shownRowsCount() * _rowHeight)
|
||||
? (rowsPointY / _rowHeight)
|
||||
: -1;
|
||||
if (selected.index.value >= 0) {
|
||||
auto row = getRow(selected.index);
|
||||
if (row->disabled()) {
|
||||
const auto row = getRow(selected.index);
|
||||
if (row->disabled()
|
||||
|| (customMode
|
||||
&& !_controller->customRowSelectionPoint(
|
||||
row,
|
||||
point.x(),
|
||||
rowsPointY - (selected.index.value * _rowHeight)))) {
|
||||
selected = Selected();
|
||||
} else {
|
||||
} else if (!customMode) {
|
||||
if (getActiveActionRect(row, selected.index).contains(point)) {
|
||||
selected.action = true;
|
||||
}
|
||||
@@ -1789,8 +1899,7 @@ void PeerListContent::updateRow(RowIndex index) {
|
||||
if (index.value < 0) {
|
||||
return;
|
||||
}
|
||||
auto row = getRow(index);
|
||||
if (row->disabled()) {
|
||||
if (const auto row = getRow(index); row && row->disabled()) {
|
||||
if (index == _selected.index) {
|
||||
setSelected(Selected());
|
||||
}
|
||||
@@ -1885,3 +1994,10 @@ PeerListContent::~PeerListContent() {
|
||||
_contextMenu->setDestroyedCallback(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListContentDelegate::peerListShowRowMenu(
|
||||
not_null<PeerListRow*> row,
|
||||
bool highlightRow,
|
||||
Fn<void(not_null<Ui::PopupMenu *>)> destroyed) {
|
||||
_content->showRowMenu(row, highlightRow, std::move(destroyed));
|
||||
}
|
||||
@@ -82,7 +82,7 @@ public:
|
||||
return _id;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Data::CloudImageView> ensureUserpicView();
|
||||
[[nodiscard]] std::shared_ptr<Data::CloudImageView> &ensureUserpicView();
|
||||
|
||||
[[nodiscard]] virtual QString generateName();
|
||||
[[nodiscard]] virtual QString generateShortName();
|
||||
@@ -122,7 +122,7 @@ public:
|
||||
bool actionSelected) {
|
||||
}
|
||||
|
||||
void refreshName(const style::PeerListItem &st);
|
||||
virtual void refreshName(const style::PeerListItem &st);
|
||||
const Ui::Text::String &name() const {
|
||||
return _name;
|
||||
}
|
||||
@@ -161,7 +161,7 @@ public:
|
||||
template <typename UpdateCallback>
|
||||
void setChecked(
|
||||
bool checked,
|
||||
const style::RoundImageCheckbox &st,
|
||||
const style::RoundImageCheckbox &st,
|
||||
anim::type animated,
|
||||
UpdateCallback callback) {
|
||||
if (checked && !_checkbox) {
|
||||
@@ -169,15 +169,21 @@ public:
|
||||
}
|
||||
setCheckedInternal(checked, animated);
|
||||
}
|
||||
void setHidden(bool hidden) {
|
||||
_hidden = hidden;
|
||||
}
|
||||
[[nodiscard]] bool hidden() const {
|
||||
return _hidden;
|
||||
}
|
||||
void finishCheckedAnimation();
|
||||
void invalidatePixmapsCache();
|
||||
|
||||
template <typename UpdateCallback>
|
||||
template <typename MaskGenerator, typename UpdateCallback>
|
||||
void addRipple(
|
||||
const style::PeerListItem &st,
|
||||
QSize size,
|
||||
MaskGenerator &&maskGenerator,
|
||||
QPoint point,
|
||||
UpdateCallback updateCallback);
|
||||
UpdateCallback &&updateCallback);
|
||||
void stopLastRipple();
|
||||
void paintRipple(Painter &p, int x, int y, int outerWidth);
|
||||
void paintUserpic(
|
||||
@@ -237,6 +243,7 @@ private:
|
||||
base::flat_set<QChar> _nameFirstLetters;
|
||||
int _absoluteIndex = -1;
|
||||
State _disabledState = State::Active;
|
||||
bool _hidden : 1;
|
||||
bool _initialized : 1;
|
||||
bool _isSearchResult : 1;
|
||||
bool _isSavedMessagesChat : 1;
|
||||
@@ -274,6 +281,7 @@ public:
|
||||
virtual void peerListConvertRowToSearchResult(not_null<PeerListRow*> row) = 0;
|
||||
virtual bool peerListIsRowChecked(not_null<PeerListRow*> row) = 0;
|
||||
virtual void peerListSetRowChecked(not_null<PeerListRow*> row, bool checked) = 0;
|
||||
virtual void peerListSetRowHidden(not_null<PeerListRow*> row, bool hidden) = 0;
|
||||
virtual void peerListSetForeignRowChecked(
|
||||
not_null<PeerListRow*> row,
|
||||
bool checked,
|
||||
@@ -304,6 +312,7 @@ public:
|
||||
|
||||
virtual void peerListShowRowMenu(
|
||||
not_null<PeerListRow*> row,
|
||||
bool highlightRow,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr) = 0;
|
||||
virtual int peerListSelectedRowsCount() = 0;
|
||||
virtual std::unique_ptr<PeerListState> peerListSaveState() const = 0;
|
||||
@@ -450,6 +459,25 @@ public:
|
||||
[[nodiscard]] virtual bool respectSavedMessagesChat() const {
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] virtual int customRowHeight() {
|
||||
Unexpected("PeerListController::customRowHeight.");
|
||||
}
|
||||
virtual void customRowPaint(
|
||||
Painter &p,
|
||||
crl::time now,
|
||||
not_null<PeerListRow*> row,
|
||||
bool selected) {
|
||||
Unexpected("PeerListController::customRowPaint.");
|
||||
}
|
||||
[[nodiscard]] virtual bool customRowSelectionPoint(
|
||||
not_null<PeerListRow*> row,
|
||||
int x,
|
||||
int y) {
|
||||
Unexpected("PeerListController::customRowSelectionPoint.");
|
||||
}
|
||||
[[nodiscard]] virtual Fn<QImage()> customRowRippleMaskGenerator() {
|
||||
Unexpected("PeerListController::customRowRippleMaskGenerator.");
|
||||
}
|
||||
|
||||
[[nodiscard]] virtual rpl::producer<int> onlineCountValue() const;
|
||||
|
||||
@@ -514,6 +542,12 @@ public:
|
||||
SkipResult selectSkip(int direction);
|
||||
void selectSkipPage(int height, int direction);
|
||||
|
||||
enum class Mode {
|
||||
Default,
|
||||
Custom,
|
||||
};
|
||||
void setMode(Mode mode);
|
||||
|
||||
[[nodiscard]] rpl::producer<int> selectedIndexValue() const;
|
||||
[[nodiscard]] bool hasSelection() const;
|
||||
[[nodiscard]] bool hasPressed() const;
|
||||
@@ -552,6 +586,9 @@ public:
|
||||
not_null<PeerListRow*> row,
|
||||
bool checked,
|
||||
anim::type animated);
|
||||
void setRowHidden(
|
||||
not_null<PeerListRow*> row,
|
||||
bool hidden);
|
||||
|
||||
template <typename ReorderCallback>
|
||||
void reorderRows(ReorderCallback &&callback) {
|
||||
@@ -560,6 +597,9 @@ public:
|
||||
callback(searchEntity.second.begin(), searchEntity.second.end());
|
||||
}
|
||||
refreshIndices();
|
||||
if (!_hiddenRows.empty()) {
|
||||
callback(_filterResults.begin(), _filterResults.end());
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -568,6 +608,7 @@ public:
|
||||
|
||||
void showRowMenu(
|
||||
not_null<PeerListRow*> row,
|
||||
bool highlightRow,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed);
|
||||
|
||||
auto scrollToRequests() const {
|
||||
@@ -655,10 +696,12 @@ private:
|
||||
|
||||
bool showRowMenu(
|
||||
RowIndex index,
|
||||
PeerListRow *row,
|
||||
QPoint globalPos,
|
||||
bool highlightRow,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr);
|
||||
|
||||
crl::time paintRow(Painter &p, crl::time ms, RowIndex index);
|
||||
crl::time paintRow(Painter &p, crl::time now, RowIndex index);
|
||||
|
||||
void addRowEntry(not_null<PeerListRow*> row);
|
||||
void addToSearchIndex(not_null<PeerListRow*> row);
|
||||
@@ -666,7 +709,7 @@ private:
|
||||
void removeFromSearchIndex(not_null<PeerListRow*> row);
|
||||
void setSearchQuery(const QString &query, const QString &normalizedQuery);
|
||||
bool showingSearch() const {
|
||||
return !_searchQuery.isEmpty();
|
||||
return !_hiddenRows.empty() || !_searchQuery.isEmpty();
|
||||
}
|
||||
int shownRowsCount() const {
|
||||
return showingSearch() ? _filterResults.size() : _rows.size();
|
||||
@@ -688,6 +731,7 @@ private:
|
||||
not_null<PeerListController*> _controller;
|
||||
PeerListSearchMode _searchMode = PeerListSearchMode::Disabled;
|
||||
|
||||
Mode _mode = Mode::Default;
|
||||
int _rowHeight = 0;
|
||||
int _visibleTop = 0;
|
||||
int _visibleBottom = 0;
|
||||
@@ -711,6 +755,7 @@ private:
|
||||
QString _normalizedSearchQuery;
|
||||
QString _mentionHighlight;
|
||||
std::vector<not_null<PeerListRow*>> _filterResults;
|
||||
base::flat_set<not_null<PeerListRow*>> _hiddenRows;
|
||||
|
||||
int _aboveHeight = 0;
|
||||
int _belowHeight = 0;
|
||||
@@ -775,6 +820,11 @@ public:
|
||||
bool checked) override {
|
||||
_content->changeCheckState(row, checked, anim::type::normal);
|
||||
}
|
||||
void peerListSetRowHidden(
|
||||
not_null<PeerListRow*> row,
|
||||
bool hidden) override {
|
||||
_content->setRowHidden(row, hidden);
|
||||
}
|
||||
void peerListSetForeignRowChecked(
|
||||
not_null<PeerListRow*> row,
|
||||
bool checked,
|
||||
@@ -845,10 +895,9 @@ public:
|
||||
_content->restoreState(std::move(state));
|
||||
}
|
||||
void peerListShowRowMenu(
|
||||
not_null<PeerListRow*> row,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr) override {
|
||||
_content->showRowMenu(row, std::move(destroyed));
|
||||
}
|
||||
not_null<PeerListRow*> row,
|
||||
bool highlightRow,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr) override;
|
||||
|
||||
protected:
|
||||
not_null<PeerListContent*> content() const {
|
||||
|
||||
@@ -201,8 +201,10 @@ void SaveSlowmodeSeconds(
|
||||
void ShowEditPermissions(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer) {
|
||||
const auto box = Ui::show(
|
||||
Box<EditPeerPermissionsBox>(navigation, peer),
|
||||
auto content = Box<EditPeerPermissionsBox>(navigation, peer);
|
||||
const auto box = QPointer<EditPeerPermissionsBox>(content.data());
|
||||
navigation->parentController()->show(
|
||||
std::move(content),
|
||||
Ui::LayerOption::KeepOther);
|
||||
const auto saving = box->lifetime().make_state<int>(0);
|
||||
const auto save = [=](
|
||||
@@ -244,8 +246,10 @@ void ShowEditPermissions(
|
||||
void ShowEditInviteLinks(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer) {
|
||||
const auto box = Ui::show(
|
||||
Box<EditPeerPermissionsBox>(navigation, peer),
|
||||
auto content = Box<EditPeerPermissionsBox>(navigation, peer);
|
||||
const auto box = QPointer<EditPeerPermissionsBox>(content.data());
|
||||
navigation->parentController()->show(
|
||||
std::move(content),
|
||||
Ui::LayerOption::KeepOther);
|
||||
const auto saving = box->lifetime().make_state<int>(0);
|
||||
const auto save = [=](
|
||||
@@ -612,7 +616,7 @@ object_ptr<Ui::RpWidget> Controller::createStickersEdit() {
|
||||
tr::lng_group_stickers_add(tr::now),
|
||||
st::editPeerInviteLinkButton)
|
||||
)->addClickHandler([=] {
|
||||
Ui::show(
|
||||
_navigation->parentController()->show(
|
||||
Box<StickersBox>(_navigation->parentController(), channel),
|
||||
Ui::LayerOption::KeepOther);
|
||||
});
|
||||
@@ -649,7 +653,7 @@ void Controller::showEditPeerTypeBox(
|
||||
_usernameSavedValue = publicLink;
|
||||
refreshHistoryVisibility();
|
||||
});
|
||||
Ui::show(
|
||||
_navigation->parentController()->show(
|
||||
Box<EditPeerTypeBox>(
|
||||
_peer,
|
||||
_channelHasLocationOriginalValue,
|
||||
@@ -681,7 +685,7 @@ void Controller::showEditLinkedChatBox() {
|
||||
|| channel->canEditPreHistoryHidden()));
|
||||
|
||||
if (const auto chat = *_linkedChatSavedValue) {
|
||||
*box = Ui::show(
|
||||
*box = _navigation->parentController()->show(
|
||||
EditLinkedChatBox(
|
||||
_navigation,
|
||||
channel,
|
||||
@@ -709,7 +713,7 @@ void Controller::showEditLinkedChatBox() {
|
||||
for (const auto &item : list) {
|
||||
chats.emplace_back(_peer->owner().processChat(item));
|
||||
}
|
||||
*box = Ui::show(
|
||||
*box = _navigation->parentController()->show(
|
||||
EditLinkedChatBox(
|
||||
_navigation,
|
||||
channel,
|
||||
@@ -858,7 +862,7 @@ void Controller::fillHistoryVisibilityButton() {
|
||||
_historyVisibilitySavedValue = checked;
|
||||
});
|
||||
const auto buttonCallback = [=] {
|
||||
Ui::show(
|
||||
_navigation->parentController()->show(
|
||||
Box<EditPeerHistoryVisibilityBox>(
|
||||
_peer,
|
||||
boxCallback,
|
||||
@@ -1023,9 +1027,15 @@ void Controller::fillManageSection() {
|
||||
wrap->entity(),
|
||||
tr::lng_manage_peer_invite_links(),
|
||||
rpl::duplicate(count) | ToPositiveNumberString(),
|
||||
[=] { Ui::show(
|
||||
Box(ManageInviteLinksBox, _peer, _peer->session().user(), 0, 0),
|
||||
Ui::LayerOption::KeepOther);
|
||||
[=] {
|
||||
_navigation->parentController()->show(
|
||||
Box(
|
||||
ManageInviteLinksBox,
|
||||
_peer,
|
||||
_peer->session().user(),
|
||||
0,
|
||||
0),
|
||||
Ui::LayerOption::KeepOther);
|
||||
},
|
||||
st::infoIconInviteLinks);
|
||||
|
||||
@@ -1520,7 +1530,7 @@ void Controller::deleteWithConfirmation() {
|
||||
const auto deleteCallback = crl::guard(this, [=] {
|
||||
deleteChannel();
|
||||
});
|
||||
Ui::show(
|
||||
_navigation->parentController()->show(
|
||||
Box<ConfirmBox>(
|
||||
text,
|
||||
tr::lng_box_delete(tr::now),
|
||||
|
||||
@@ -133,8 +133,8 @@ QImage QrExact(const Qr::Data &data, int pixel, QColor color) {
|
||||
skip,
|
||||
skip,
|
||||
Intro::details::TelegramLogoImage().scaled(
|
||||
logoSize,
|
||||
logoSize,
|
||||
logoSize * cIntRetinaFactor(),
|
||||
logoSize * cIntRetinaFactor(),
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation));
|
||||
}
|
||||
|
||||
@@ -533,7 +533,7 @@ void LinksController::rowClicked(not_null<PeerListRow*> row) {
|
||||
}
|
||||
|
||||
void LinksController::rowActionClicked(not_null<PeerListRow*> row) {
|
||||
delegate()->peerListShowRowMenu(row, nullptr);
|
||||
delegate()->peerListShowRowMenu(row, true);
|
||||
}
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> LinksController::rowContextMenu(
|
||||
|
||||
@@ -1002,7 +1002,7 @@ void SendFilesBox::sendScheduled() {
|
||||
? SendMenu::Type::ScheduledToUser
|
||||
: _sendMenuType;
|
||||
const auto callback = [=](Api::SendOptions options) { send(options); };
|
||||
Ui::show(
|
||||
_controller->show(
|
||||
HistoryView::PrepareScheduleBox(this, type, callback),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
@@ -63,12 +63,14 @@ public:
|
||||
bool loaded() const;
|
||||
bool notInstalled() const;
|
||||
bool official() const;
|
||||
rpl::producer<TextWithEntities> title() const;
|
||||
QString shortName() const;
|
||||
[[nodiscard]] rpl::producer<TextWithEntities> title() const;
|
||||
[[nodiscard]] QString shortName() const;
|
||||
|
||||
void install();
|
||||
rpl::producer<uint64> setInstalled() const;
|
||||
rpl::producer<> updateControls() const;
|
||||
[[nodiscard]] rpl::producer<uint64> setInstalled() const;
|
||||
[[nodiscard]] rpl::producer<> updateControls() const;
|
||||
|
||||
[[nodiscard]] rpl::producer<Error> errors() const;
|
||||
|
||||
~Inner();
|
||||
|
||||
@@ -137,6 +139,7 @@ private:
|
||||
|
||||
rpl::event_stream<uint64> _setInstalled;
|
||||
rpl::event_stream<> _updateControls;
|
||||
rpl::event_stream<Error> _errors;
|
||||
|
||||
};
|
||||
|
||||
@@ -153,7 +156,7 @@ QPointer<Ui::BoxContent> StickerSetBox::Show(
|
||||
not_null<DocumentData*> document) {
|
||||
if (const auto sticker = document->sticker()) {
|
||||
if (sticker->set.type() != mtpc_inputStickerSetEmpty) {
|
||||
return Ui::show(
|
||||
return controller->show(
|
||||
Box<StickerSetBox>(controller, sticker->set),
|
||||
Ui::LayerOption::KeepOther).data();
|
||||
}
|
||||
@@ -186,6 +189,11 @@ void StickerSetBox::prepare() {
|
||||
_controller->session().api().stickerSetInstalled(setId);
|
||||
closeBox();
|
||||
}, lifetime());
|
||||
|
||||
_inner->errors(
|
||||
) | rpl::start_with_next([=](Error error) {
|
||||
handleError(error);
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void StickerSetBox::addStickers() {
|
||||
@@ -198,6 +206,52 @@ void StickerSetBox::copyStickersLink() {
|
||||
QGuiApplication::clipboard()->setText(url);
|
||||
}
|
||||
|
||||
void StickerSetBox::handleError(Error error) {
|
||||
const auto guard = gsl::finally(crl::guard(this, [=] {
|
||||
closeBox();
|
||||
}));
|
||||
|
||||
switch (error) {
|
||||
case Error::NotFound:
|
||||
_controller->show(
|
||||
Box<InformBox>(tr::lng_stickers_not_found(tr::now)));
|
||||
break;
|
||||
default: Unexpected("Error in StickerSetBox::handleError.");
|
||||
}
|
||||
}
|
||||
|
||||
void StickerSetBox::archiveStickers() {
|
||||
const auto weak = base::make_weak(_controller.get());
|
||||
const auto setId = _set.c_inputStickerSetID().vid().v;
|
||||
_controller->session().api().request(MTPmessages_InstallStickerSet(
|
||||
_set,
|
||||
MTP_boolTrue()
|
||||
)).done([=](const MTPmessages_StickerSetInstallResult &result) {
|
||||
const auto controller = weak.get();
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
if (result.type() == mtpc_messages_stickerSetInstallResultSuccess) {
|
||||
Ui::Toast::Show(tr::lng_stickers_has_been_archived(tr::now));
|
||||
|
||||
const auto &session = controller->session();
|
||||
auto &order = session.data().stickers().setsOrderRef();
|
||||
const auto index = order.indexOf(setId);
|
||||
if (index == -1) {
|
||||
return;
|
||||
}
|
||||
order.removeAt(index);
|
||||
|
||||
session.local().writeInstalledStickers();
|
||||
session.local().writeArchivedStickers();
|
||||
|
||||
session.data().stickers().notifyUpdated();
|
||||
}
|
||||
}).fail([](const MTP::Error &error) {
|
||||
Ui::Toast::Show(Lang::Hard::ServerError());
|
||||
}).send();
|
||||
}
|
||||
|
||||
void StickerSetBox::updateTitleAndButtons() {
|
||||
setTitle(_inner->title());
|
||||
updateButtons();
|
||||
@@ -237,6 +291,24 @@ void StickerSetBox::updateButtons() {
|
||||
};
|
||||
addButton(tr::lng_stickers_share_pack(), std::move(share));
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
|
||||
if (!_inner->shortName().isEmpty()) {
|
||||
const auto top = addTopButton(st::infoTopBarMenu);
|
||||
const auto archive = [=] {
|
||||
archiveStickers();
|
||||
closeBox();
|
||||
};
|
||||
const auto menu =
|
||||
std::make_shared<base::unique_qptr<Ui::PopupMenu>>();
|
||||
top->setClickedCallback([=] {
|
||||
*menu = base::make_unique_q<Ui::PopupMenu>(top);
|
||||
(*menu)->addAction(
|
||||
tr::lng_stickers_archive_pack(tr::now),
|
||||
archive);
|
||||
(*menu)->popup(QCursor::pos());
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
@@ -274,7 +346,7 @@ StickerSetBox::Inner::Inner(
|
||||
gotSet(result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_loaded = true;
|
||||
Ui::show(Box<InformBox>(tr::lng_stickers_not_found(tr::now)));
|
||||
_errors.fire(Error::NotFound);
|
||||
}).send();
|
||||
|
||||
_controller->session().api().updateStickers();
|
||||
@@ -369,7 +441,7 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) {
|
||||
});
|
||||
|
||||
if (_pack.isEmpty()) {
|
||||
Ui::show(Box<InformBox>(tr::lng_stickers_not_found(tr::now)));
|
||||
_errors.fire(Error::NotFound);
|
||||
return;
|
||||
} else {
|
||||
int32 rows = _pack.size() / kStickersPanelPerRow + ((_pack.size() % kStickersPanelPerRow) ? 1 : 0);
|
||||
@@ -389,6 +461,10 @@ rpl::producer<> StickerSetBox::Inner::updateControls() const {
|
||||
return _updateControls.events();
|
||||
}
|
||||
|
||||
rpl::producer<StickerSetBox::Error> StickerSetBox::Inner::errors() const {
|
||||
return _errors.events();
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::installDone(
|
||||
const MTPmessages_StickerSetInstallResult &result) {
|
||||
auto &sets = _controller->session().data().stickers().setsRef();
|
||||
@@ -745,7 +821,7 @@ QString StickerSetBox::Inner::shortName() const {
|
||||
|
||||
void StickerSetBox::Inner::install() {
|
||||
if (isMasksSet()) {
|
||||
Ui::show(
|
||||
_controller->show(
|
||||
Box<InformBox>(tr::lng_stickers_masks_pack(tr::now)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
return;
|
||||
@@ -758,7 +834,7 @@ void StickerSetBox::Inner::install() {
|
||||
)).done([=](const MTPmessages_StickerSetInstallResult &result) {
|
||||
installDone(result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
Ui::show(Box<InformBox>(tr::lng_stickers_not_found(tr::now)));
|
||||
_errors.fire(Error::NotFound);
|
||||
}).send();
|
||||
}
|
||||
|
||||
|
||||
@@ -38,10 +38,16 @@ protected:
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
private:
|
||||
enum class Error {
|
||||
NotFound,
|
||||
};
|
||||
|
||||
void updateTitleAndButtons();
|
||||
void updateButtons();
|
||||
void addStickers();
|
||||
void copyStickersLink();
|
||||
void archiveStickers();
|
||||
void handleError(Error error);
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
MTPInputStickerSet _set;
|
||||
|
||||
@@ -68,9 +68,7 @@ private:
|
||||
};
|
||||
|
||||
// This class is hold in header because it requires Qt preprocessing.
|
||||
class StickersBox::Inner
|
||||
: public Ui::RpWidget
|
||||
, private base::Subscriber {
|
||||
class StickersBox::Inner : public Ui::RpWidget {
|
||||
public:
|
||||
using Section = StickersBox::Section;
|
||||
|
||||
@@ -85,7 +83,9 @@ public:
|
||||
|
||||
[[nodiscard]] Main::Session &session() const;
|
||||
|
||||
base::Observable<int> scrollToY;
|
||||
rpl::producer<int> scrollsToY() const {
|
||||
return _scrollsToY.events();
|
||||
}
|
||||
void setInnerFocus();
|
||||
|
||||
void saveGroupSet();
|
||||
@@ -276,6 +276,8 @@ private:
|
||||
int _above = -1;
|
||||
rpl::event_stream<int> _draggingScrollDelta;
|
||||
|
||||
rpl::event_stream<int> _scrollsToY;
|
||||
|
||||
int _minHeight = 0;
|
||||
|
||||
int _scrollbar = 0;
|
||||
@@ -387,9 +389,10 @@ StickersBox::StickersBox(
|
||||
, _section(Section::Installed)
|
||||
, _installed(0, this, controller, megagroup)
|
||||
, _megagroupSet(megagroup) {
|
||||
subscribe(_installed.widget()->scrollToY, [=](int y) {
|
||||
_installed.widget()->scrollsToY(
|
||||
) | rpl::start_with_next([=](int y) {
|
||||
onScrollToY(y);
|
||||
});
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
StickersBox::StickersBox(
|
||||
@@ -1589,7 +1592,7 @@ void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
|
||||
}();
|
||||
const auto showSetByRow = [&](const Row &row) {
|
||||
setSelected(SelectedRow());
|
||||
Ui::show(
|
||||
_controller->show(
|
||||
Box<StickerSetBox>(_controller, row.set->mtpInput()),
|
||||
Ui::LayerOption::KeepOther);
|
||||
};
|
||||
@@ -1898,7 +1901,7 @@ void StickersBox::Inner::rebuild() {
|
||||
void StickersBox::Inner::setMegagroupSelectedSet(const MTPInputStickerSet &set) {
|
||||
_megagroupSetInput = set;
|
||||
rebuild();
|
||||
scrollToY.notify(0, true);
|
||||
_scrollsToY.fire(0);
|
||||
updateSelected();
|
||||
}
|
||||
|
||||
|
||||
@@ -175,6 +175,87 @@ callCameraUnmute: CallButton(callMicrophoneUnmute) {
|
||||
}
|
||||
callBottomShadowSize: 124px;
|
||||
|
||||
CallMuteButton {
|
||||
active: CallButton;
|
||||
muted: CallButton;
|
||||
labelAdditional: pixels;
|
||||
sublabel: FlatLabel;
|
||||
labelsSkip: pixels;
|
||||
sublabelSkip: pixels;
|
||||
lottieSize: size;
|
||||
lottieTop: pixels;
|
||||
}
|
||||
|
||||
callMuteButtonLabel: FlatLabel(defaultFlatLabel) {
|
||||
textFg: groupCallMembersFg;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(14px);
|
||||
linkFont: font(14px);
|
||||
linkFontOver: font(14px underline);
|
||||
}
|
||||
}
|
||||
callMuteButtonActiveInner: IconButton {
|
||||
width: 112px;
|
||||
height: 138px;
|
||||
}
|
||||
callMuteButtonSmallActiveInner: IconButton {
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
callMuteButtonActive: CallButton {
|
||||
button: callMuteButtonActiveInner;
|
||||
bg: groupCallLive1;
|
||||
bgSize: 77px;
|
||||
bgPosition: point(18px, 18px);
|
||||
outerRadius: 18px;
|
||||
outerBg: callAnswerBgOuter;
|
||||
label: callMuteButtonLabel;
|
||||
}
|
||||
callMuteButton: CallMuteButton {
|
||||
active: callMuteButtonActive;
|
||||
muted: CallButton(callMuteButtonActive) {
|
||||
bg: groupCallMuted1;
|
||||
label: callMuteButtonLabel;
|
||||
}
|
||||
labelAdditional: 5px;
|
||||
sublabel: FlatLabel(defaultFlatLabel) {
|
||||
textFg: groupCallMemberNotJoinedStatus;
|
||||
}
|
||||
labelsSkip: 8px;
|
||||
sublabelSkip: 14px;
|
||||
lottieSize: size(54px, 54px);
|
||||
lottieTop: 31px;
|
||||
}
|
||||
callMuteButtonSmallActive: CallButton(callMuteButtonActive) {
|
||||
button: callMuteButtonSmallActiveInner;
|
||||
bgSize: 42px;
|
||||
bgPosition: point(13px, 13px);
|
||||
outerRadius: 13px;
|
||||
label: callButtonLabel;
|
||||
}
|
||||
callMuteButtonSmall: CallMuteButton(callMuteButton) {
|
||||
active: callMuteButtonSmallActive;
|
||||
muted: CallButton(callMuteButtonSmallActive) {
|
||||
bg: groupCallMuted1;
|
||||
label: callButtonLabel;
|
||||
}
|
||||
labelsSkip: 0px;
|
||||
sublabelSkip: 0px;
|
||||
lottieSize: size(36px, 36px);
|
||||
lottieTop: 17px;
|
||||
}
|
||||
|
||||
callMuteMinorBlobMinRadius: 64px;
|
||||
callMuteMinorBlobMaxRadius: 74px;
|
||||
callMuteMajorBlobMinRadius: 67px;
|
||||
callMuteMajorBlobMaxRadius: 77px;
|
||||
callMuteBlobRadiusForDiameter: 100px;
|
||||
|
||||
callConnectingRadial: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {
|
||||
color: lightButtonFg;
|
||||
thickness: 4px;
|
||||
}
|
||||
|
||||
callName: FlatLabel(defaultFlatLabel) {
|
||||
minWidth: 260px;
|
||||
maxHeight: 30px;
|
||||
@@ -208,55 +289,6 @@ callRemoteAudioMute: FlatLabel(callStatus) {
|
||||
}
|
||||
callRemoteAudioMuteSkip: 12px;
|
||||
|
||||
callMuteMainBlobMinRadius: 57px;
|
||||
callMuteMainBlobMaxRadius: 63px;
|
||||
callMuteMinorBlobMinRadius: 64px;
|
||||
callMuteMinorBlobMaxRadius: 74px;
|
||||
callMuteMajorBlobMinRadius: 67px;
|
||||
callMuteMajorBlobMaxRadius: 77px;
|
||||
|
||||
callMuteButtonActiveInner: IconButton {
|
||||
width: 136px;
|
||||
height: 165px;
|
||||
}
|
||||
callMuteButtonLabel: FlatLabel(defaultFlatLabel) {
|
||||
textFg: groupCallMembersFg;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(14px);
|
||||
linkFont: font(14px);
|
||||
linkFontOver: font(14px underline);
|
||||
}
|
||||
}
|
||||
callMuteButtonSublabel: FlatLabel(defaultFlatLabel) {
|
||||
textFg: groupCallMemberNotJoinedStatus;
|
||||
}
|
||||
callMuteButtonLabelsSkip: 5px;
|
||||
callMuteButtonSublabelSkip: 19px;
|
||||
callMuteButtonActive: CallButton {
|
||||
button: callMuteButtonActiveInner;
|
||||
bg: groupCallLive1;
|
||||
bgSize: 100px;
|
||||
bgPosition: point(18px, 18px);
|
||||
outerRadius: 18px;
|
||||
outerBg: callAnswerBgOuter;
|
||||
label: callMuteButtonLabel;
|
||||
}
|
||||
callMuteButtonMuted: CallButton(callMuteButtonActive) {
|
||||
bg: groupCallMuted1;
|
||||
label: callMuteButtonLabel;
|
||||
}
|
||||
callMuteButtonConnecting: CallButton(callMuteButtonMuted) {
|
||||
bg: callIconBg;
|
||||
label: callMuteButtonLabel;
|
||||
}
|
||||
callMuteButtonLabelAdditional: 5px;
|
||||
|
||||
callConnectingRadial: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {
|
||||
color: lightButtonFg;
|
||||
thickness: 4px;
|
||||
size: size(100px, 100px);
|
||||
}
|
||||
|
||||
callBarHeight: 38px;
|
||||
callBarMuteToggle: IconButton {
|
||||
width: 41px;
|
||||
@@ -433,7 +465,8 @@ callTitle: WindowTitle(defaultWindowTitle) {
|
||||
closeIconActive: callTitleCloseIcon;
|
||||
closeIconActiveOver: callTitleCloseIconOver;
|
||||
}
|
||||
callTitleShadow: icon {{ "calls/calls_shadow_controls", windowShadowFg }};
|
||||
callTitleShadowRight: icon {{ "calls/calls_shadow_controls", windowShadowFg }};
|
||||
callTitleShadowLeft: icon {{ "calls/calls_shadow_controls-flip_horizontal", windowShadowFg }};
|
||||
|
||||
callErrorToast: Toast(defaultToast) {
|
||||
minWidth: 240px;
|
||||
@@ -442,8 +475,6 @@ callErrorToast: Toast(defaultToast) {
|
||||
groupCallWidth: 380px;
|
||||
groupCallHeight: 580px;
|
||||
|
||||
groupCallMuteButtonIconSize: size(67px, 67px);
|
||||
groupCallMuteButtonIconTop: 35px;
|
||||
groupCallRipple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: groupCallMembersBgRipple;
|
||||
}
|
||||
@@ -459,6 +490,7 @@ groupCallMenu: Menu(defaultMenu) {
|
||||
itemFgShortcutDisabled: groupCallMemberNotJoinedStatus;
|
||||
|
||||
separatorFg: groupCallMenuBgOver;
|
||||
separatorPadding: margins(0px, 4px, 0px, 4px);
|
||||
|
||||
arrow: icon {{ "dropdown_submenu_arrow", groupCallMemberNotJoinedStatus }};
|
||||
|
||||
@@ -482,6 +514,16 @@ groupCallPopupMenu: PopupMenu(defaultPopupMenu) {
|
||||
menu: groupCallMenu;
|
||||
animation: groupCallPanelAnimation;
|
||||
}
|
||||
groupCallPopupMenuWithVolume: PopupMenu(groupCallPopupMenu) {
|
||||
scrollPadding: margins(0px, 3px, 0px, 8px);
|
||||
menu: Menu(groupCallMenu) {
|
||||
widthMin: 210px;
|
||||
}
|
||||
}
|
||||
groupCallPopupVolumeMenu: Menu(groupCallMenu) {
|
||||
widthMin: 210px;
|
||||
itemBgOver: groupCallMenuBg;
|
||||
}
|
||||
|
||||
groupCallRecordingTimerPadding: margins(0px, 4px, 0px, 4px);
|
||||
groupCallRecordingTimerFont: font(12px);
|
||||
@@ -532,6 +574,11 @@ groupCallMembersListItem: PeerListItem(defaultPeerListItem) {
|
||||
statusFgOver: groupCallMemberInactiveStatus;
|
||||
statusFgActive: groupCallMemberActiveStatus;
|
||||
}
|
||||
groupCallNarrowMembersListItem: PeerListItem(groupCallMembersListItem) {
|
||||
statusFg: groupCallMemberNotJoinedStatus;
|
||||
statusFgOver: groupCallMemberNotJoinedStatus;
|
||||
statusFgActive: groupCallMemberActiveStatus;
|
||||
}
|
||||
groupCallMembersList: PeerList(defaultPeerList) {
|
||||
bg: groupCallMembersBg;
|
||||
about: FlatLabel(defaultPeerListAbout) {
|
||||
@@ -648,8 +695,8 @@ groupCallShareBoxList: PeerList(groupCallMembersList) {
|
||||
groupCallMembersTop: 51px;
|
||||
groupCallTitleTop: 8px;
|
||||
groupCallSubtitleTop: 26px;
|
||||
groupCallWideVideoTop: 24px;
|
||||
|
||||
groupCallMembersMargin: margins(16px, 16px, 16px, 28px);
|
||||
groupCallAddMember: SettingsButton(defaultSettingsButton) {
|
||||
textFg: groupCallMemberNotJoinedStatus;
|
||||
textFgOver: groupCallMemberNotJoinedStatus;
|
||||
@@ -678,7 +725,7 @@ groupCallTitleLabel: FlatLabel(groupCallSubtitleLabel) {
|
||||
}
|
||||
}
|
||||
groupCallAddButtonPosition: point(10px, 7px);
|
||||
groupCallMembersWidthMax: 360px;
|
||||
groupCallMembersWidthMax: 480px;
|
||||
groupCallRecordingMark: 6px;
|
||||
groupCallRecordingMarkSkip: 4px;
|
||||
groupCallRecordingMarkTop: 8px;
|
||||
@@ -704,6 +751,7 @@ groupCallJoinAsToggle: UserpicButton(defaultUserpicButton) {
|
||||
photoPosition: point(3px, 3px);
|
||||
}
|
||||
groupCallMenuPosition: point(-1px, 29px);
|
||||
groupCallWideMenuPosition: point(-2px, 28px);
|
||||
|
||||
groupCallActiveButton: IconButton {
|
||||
width: 36px;
|
||||
@@ -736,33 +784,106 @@ groupCallMemberRaisedHand: icon {{ "calls/group_calls_raised_hand", groupCallMem
|
||||
|
||||
groupCallSettingsInner: IconButton(callButton) {
|
||||
iconPosition: point(-1px, 22px);
|
||||
icon: icon {{ "calls/call_settings", groupCallIconFg }};
|
||||
icon: icon {{ "calls/calls_settings", groupCallIconFg }};
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: callMuteRipple;
|
||||
}
|
||||
}
|
||||
groupCallShareInner: IconButton(groupCallSettingsInner) {
|
||||
icon: icon {{ "calls/group_calls_share", groupCallIconFg }};
|
||||
}
|
||||
groupCallVideoInner: IconButton(groupCallSettingsInner) {
|
||||
icon: icon {{ "calls/call_camera_muted", groupCallIconFg }};
|
||||
iconPosition: point(-1px, 16px);
|
||||
}
|
||||
groupCallHangupInner: IconButton(callButton) {
|
||||
icon: icon {{ "calls/call_discard", groupCallIconFg }};
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: groupCallLeaveBgRipple;
|
||||
}
|
||||
}
|
||||
groupCallSettings: CallButton(callMicrophoneMute) {
|
||||
button: groupCallSettingsInner;
|
||||
}
|
||||
groupCallShare: CallButton(groupCallSettings) {
|
||||
button: IconButton(groupCallSettingsInner) {
|
||||
icon: icon {{ "calls/group_calls_share", groupCallIconFg }};
|
||||
}
|
||||
button: groupCallShareInner;
|
||||
}
|
||||
groupCallVideo: CallButton(groupCallSettings) {
|
||||
button: groupCallVideoInner;
|
||||
}
|
||||
groupCallVideoInnerActive: IconButton(groupCallVideoInner) {
|
||||
icon: icon {{ "calls/call_camera_active", groupCallIconFg }};
|
||||
}
|
||||
groupCallVideoActive: CallButton(groupCallVideo) {
|
||||
button: groupCallVideoInnerActive;
|
||||
}
|
||||
groupCallHangup: CallButton(callHangup) {
|
||||
button: IconButton(callButton) {
|
||||
icon: icon {{ "calls/call_discard", groupCallIconFg }};
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: groupCallLeaveBgRipple;
|
||||
}
|
||||
}
|
||||
button: groupCallHangupInner;
|
||||
bg: groupCallLeaveBg;
|
||||
outerBg: groupCallLeaveBg;
|
||||
label: callButtonLabel;
|
||||
}
|
||||
groupCallButtonSkip: 43px;
|
||||
groupCallButtonBottomSkip: 145px;
|
||||
groupCallMuteBottomSkip: 160px;
|
||||
groupCallSettingsSmall: CallButton(groupCallSettings) {
|
||||
button: IconButton(groupCallSettingsInner) {
|
||||
width: 60px;
|
||||
height: 68px;
|
||||
rippleAreaPosition: point(8px, 12px);
|
||||
}
|
||||
bgPosition: point(8px, 12px);
|
||||
}
|
||||
groupCallHangupSmall: CallButton(groupCallHangup) {
|
||||
button: IconButton(groupCallHangupInner) {
|
||||
width: 60px;
|
||||
height: 68px;
|
||||
rippleAreaPosition: point(8px, 12px);
|
||||
}
|
||||
bgPosition: point(8px, 12px);
|
||||
}
|
||||
groupCallVideoSmall: CallButton(groupCallSettingsSmall) {
|
||||
button: IconButton(groupCallVideoInner) {
|
||||
width: 60px;
|
||||
height: 68px;
|
||||
rippleAreaPosition: point(8px, 12px);
|
||||
}
|
||||
}
|
||||
groupCallVideoActiveSmall: CallButton(groupCallVideoSmall) {
|
||||
button: IconButton(groupCallVideoInnerActive) {
|
||||
width: 60px;
|
||||
height: 68px;
|
||||
rippleAreaPosition: point(8px, 12px);
|
||||
}
|
||||
}
|
||||
groupCallScreenShareSmall: CallButton(groupCallSettingsSmall) {
|
||||
button: IconButton(groupCallSettingsInner) {
|
||||
icon: icon {{ "calls/calls_present", groupCallIconFg }};
|
||||
width: 60px;
|
||||
height: 68px;
|
||||
rippleAreaPosition: point(8px, 12px);
|
||||
}
|
||||
}
|
||||
groupCallMenuToggleSmall: CallButton(groupCallSettingsSmall) {
|
||||
button: IconButton(groupCallSettingsInner) {
|
||||
icon: icon {{ "calls/calls_more", groupCallIconFg }};
|
||||
width: 60px;
|
||||
height: 68px;
|
||||
rippleAreaPosition: point(8px, 12px);
|
||||
}
|
||||
}
|
||||
groupCallButtonSkip: 40px;
|
||||
groupCallButtonSkipSmall: 5px;
|
||||
groupCallButtonBottomSkip: 113px;
|
||||
groupCallButtonBottomSkipSmall: 95px;
|
||||
groupCallButtonBottomSkipWide: 108px;
|
||||
groupCallControlsBackMargin: margins(10px, 0px, 10px, 0px);
|
||||
groupCallControlsBackRadius: 12px;
|
||||
groupCallMuteBottomSkip: 116px;
|
||||
|
||||
groupCallMembersMargin: margins(16px, 16px, 16px, 60px);
|
||||
groupCallMembersTopSkip: 6px;
|
||||
groupCallMembersBottomSkip: 80px;
|
||||
groupCallMembersShadowHeight: 160px;
|
||||
groupCallMembersFadeSkip: 10px;
|
||||
groupCallMembersFadeHeight: 100px;
|
||||
|
||||
groupCallTopBarUserpics: GroupCallUserpics {
|
||||
size: 28px;
|
||||
@@ -780,17 +901,18 @@ groupCallTopBarOpen: RoundButton(groupCallTopBarJoin) {
|
||||
color: shadowFg;
|
||||
}
|
||||
}
|
||||
groupCallBox: Box(defaultBox) {
|
||||
button: RoundButton(defaultBoxButton) {
|
||||
textFg: groupCallActiveFg;
|
||||
textFgOver: groupCallActiveFg;
|
||||
numbersTextFg: groupCallActiveFg;
|
||||
numbersTextFgOver: groupCallActiveFg;
|
||||
textBg: groupCallMembersBg;
|
||||
textBgOver: groupCallMembersBgOver;
|
||||
groupCallBoxButton: RoundButton(defaultBoxButton) {
|
||||
textFg: groupCallActiveFg;
|
||||
textFgOver: groupCallActiveFg;
|
||||
numbersTextFg: groupCallActiveFg;
|
||||
numbersTextFgOver: groupCallActiveFg;
|
||||
textBg: groupCallMembersBg;
|
||||
textBgOver: groupCallMembersBgOver;
|
||||
|
||||
ripple: groupCallRipple;
|
||||
}
|
||||
ripple: groupCallRipple;
|
||||
}
|
||||
groupCallBox: Box(defaultBox) {
|
||||
button: groupCallBoxButton;
|
||||
margin: margins(0px, 56px, 0px, 10px);
|
||||
bg: groupCallMembersBg;
|
||||
title: FlatLabel(boxTitle) {
|
||||
@@ -807,7 +929,7 @@ groupCallLevelMeter: LevelMeter(defaultLevelMeter) {
|
||||
lineSpacing: 5px;
|
||||
lineCount: 44;
|
||||
activeFg: groupCallActiveFg;
|
||||
inactiveFg: groupCallMemberNotJoinedStatus;
|
||||
inactiveFg: groupCallMembersBgRipple;
|
||||
}
|
||||
groupCallCheckboxIcon: icon {{ "default_checkbox_check", groupCallMembersFg, point(4px, 7px) }};
|
||||
groupCallCheck: Check(defaultCheck) {
|
||||
@@ -960,13 +1082,17 @@ groupCallMuteCrossLine: CrossLineAnimation {
|
||||
|
||||
groupCallMenuSpeakerArcsSkip: 1px;
|
||||
groupCallMenuVolumeSkip: 5px;
|
||||
groupCallMenuVolumePadding: margins(17px, 6px, 17px, 5px);
|
||||
groupCallMenuVolumeMargin: margins(55px, 0px, 15px, 0px);
|
||||
groupCallMenuVolumeSlider: MediaSlider(defaultContinuousSlider) {
|
||||
activeFg: groupCallMembersFg;
|
||||
inactiveFg: groupCallMemberInactiveIcon;
|
||||
inactiveFg: groupCallMembersBgOver;
|
||||
activeFgOver: groupCallMembersFg;
|
||||
inactiveFgOver: groupCallMemberInactiveIcon;
|
||||
activeFgDisabled: groupCallMemberInactiveIcon;
|
||||
receivedTillFg: groupCallMemberInactiveIcon;
|
||||
inactiveFgOver: groupCallMembersBgOver;
|
||||
activeFgDisabled: groupCallMembersBgOver;
|
||||
receivedTillFg: groupCallMembersBgOver;
|
||||
width: 7px;
|
||||
seekSize: size(7px, 7px);
|
||||
}
|
||||
|
||||
groupCallSpeakerArcsAnimation: ArcsAnimation {
|
||||
@@ -1015,3 +1141,141 @@ groupCallStartsInTop: 10px;
|
||||
groupCallStartsWhenTop: 160px;
|
||||
groupCallCountdownFont: font(64px semibold);
|
||||
groupCallCountdownTop: 52px;
|
||||
|
||||
desktopCaptureMargins: margins(12px, 8px, 12px, 6px);
|
||||
desktopCaptureSourceSize: size(235px, 165px);
|
||||
desktopCaptureSourceSkips: size(2px, 10px);
|
||||
desktopCaptureSourceTitle: WindowTitle(groupCallTitle) {
|
||||
bg: groupCallMembersBgOver;
|
||||
bgActive: groupCallMembersBgOver;
|
||||
height: 21px;
|
||||
}
|
||||
desktopCapturePadding: margins(7px, 7px, 7px, 33px);
|
||||
desktopCaptureLabelBottom: 7px;
|
||||
desktopCaptureLabel: FlatLabel(defaultFlatLabel) {
|
||||
minWidth: 200px;
|
||||
maxHeight: 20px;
|
||||
textFg: groupCallMembersFg;
|
||||
style: semiboldTextStyle;
|
||||
}
|
||||
desktopCaptureCancel: RoundButton(defaultBoxButton) {
|
||||
textFg: groupCallActiveFg;
|
||||
textFgOver: groupCallActiveFg;
|
||||
numbersTextFg: groupCallActiveFg;
|
||||
numbersTextFgOver: groupCallActiveFg;
|
||||
textBg: groupCallMembersBg;
|
||||
textBgOver: groupCallMembersBgOver;
|
||||
|
||||
ripple: groupCallRipple;
|
||||
}
|
||||
desktopCaptureFinish: RoundButton(desktopCaptureCancel) {
|
||||
textFg: groupCallMemberMutedIcon;
|
||||
textFgOver: groupCallMemberMutedIcon;
|
||||
}
|
||||
desktopCaptureSubmit: RoundButton(desktopCaptureCancel) {
|
||||
textFg: groupCallIconFg;
|
||||
textFgOver: groupCallIconFg;
|
||||
numbersTextFg: groupCallIconFg;
|
||||
numbersTextFgOver: groupCallIconFg;
|
||||
textBg: groupCallMuted1;
|
||||
textBgOver: groupCallMuted1;
|
||||
|
||||
ripple: RippleAnimation(groupCallRipple) {
|
||||
color: shadowFg;
|
||||
}
|
||||
}
|
||||
|
||||
groupCallNarrowSkip: 9px;
|
||||
groupCallNarrowMembersWidth: 204px;
|
||||
groupCallNarrowVideoHeight: 120px;
|
||||
groupCallWideModeWidthMin: 600px;
|
||||
groupCallWideModeSize: size(960px, 580px);
|
||||
groupCallNarrowInactiveCrossLine: CrossLineAnimation {
|
||||
fg: groupCallMemberNotJoinedStatus;
|
||||
icon: icon {{ "calls/video_mini_mute", groupCallMemberNotJoinedStatus }};
|
||||
startPosition: point(3px, 0px);
|
||||
endPosition: point(13px, 12px);
|
||||
stroke: 3px;
|
||||
strokeDenominator: 2;
|
||||
}
|
||||
groupCallNarrowColoredCrossLine: CrossLineAnimation(groupCallNarrowInactiveCrossLine) {
|
||||
fg: groupCallMemberNotJoinedStatus;
|
||||
icon: icon {{ "calls/video_mini_mute", groupCallMemberActiveStatus }};
|
||||
}
|
||||
groupCallNarrowRaisedHand: icon {{ "calls/video_mini_speak", groupCallMemberInactiveStatus }};
|
||||
groupCallNarrowCameraIcon: icon {{ "calls/video_mini_video", groupCallMemberNotJoinedStatus }};
|
||||
groupCallNarrowScreenIcon: icon {{ "calls/video_mini_screencast", groupCallMemberNotJoinedStatus }};
|
||||
groupCallNarrowIconPosition: point(-4px, 2px);
|
||||
groupCallNarrowIconSkip: 15px;
|
||||
groupCallOutline: 2px;
|
||||
groupCallVideoCrossLine: CrossLineAnimation(groupCallMemberColoredCrossLine) {
|
||||
fg: groupCallVideoTextFg;
|
||||
icon: icon {{ "calls/video_over_mute", groupCallVideoTextFg }};
|
||||
}
|
||||
|
||||
GroupCallVideoTile {
|
||||
shadowHeight: pixels;
|
||||
namePosition: point;
|
||||
pin: CrossLineAnimation;
|
||||
pinPosition: point;
|
||||
pinPadding: margins;
|
||||
pinTextPosition: point;
|
||||
back: icon;
|
||||
iconPosition: point;
|
||||
}
|
||||
|
||||
groupCallVideoTile: GroupCallVideoTile {
|
||||
shadowHeight: 40px;
|
||||
namePosition: point(15px, 8px);
|
||||
pin: CrossLineAnimation {
|
||||
fg: groupCallVideoTextFg;
|
||||
icon: icon {{ "calls/video_over_pin", groupCallVideoTextFg }};
|
||||
startPosition: point(7px, 4px);
|
||||
endPosition: point(17px, 14px);
|
||||
stroke: 3px;
|
||||
strokeDenominator: 2;
|
||||
}
|
||||
pinPosition: point(18px, 18px);
|
||||
pinPadding: margins(6px, 2px, 12px, 1px);
|
||||
pinTextPosition: point(1px, 3px);
|
||||
back: icon {{ "calls/video_back", groupCallVideoTextFg }};
|
||||
iconPosition: point(10px, 5px);
|
||||
}
|
||||
|
||||
groupCallVideoSmallSkip: 4px;
|
||||
groupCallVideoLargeSkip: 6px;
|
||||
groupCallVideoPlaceholderHeight: 212px;
|
||||
groupCallVideoPlaceholderIconTop: 50px;
|
||||
groupCallVideoPlaceholderTextTop: 120px;
|
||||
|
||||
groupCallTooltip: Tooltip(defaultTooltip) {
|
||||
textBg: groupCallMembersBg;
|
||||
textFg: groupCallMembersFg;
|
||||
textBorder: groupCallMembersBgOver;
|
||||
}
|
||||
groupCallNiceTooltip: ImportantTooltip(defaultImportantTooltip) {
|
||||
bg: importantTooltipBg;
|
||||
padding: margins(10px, 3px, 10px, 5px);
|
||||
radius: 4px;
|
||||
arrow: 4px;
|
||||
}
|
||||
groupCallNiceTooltipLabel: FlatLabel(defaultImportantTooltipLabel) {
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(11px);
|
||||
linkFont: font(11px);
|
||||
linkFontOver: font(11px underline);
|
||||
}
|
||||
}
|
||||
groupCallStickedTooltip: ImportantTooltip(groupCallNiceTooltip) {
|
||||
padding: margins(10px, 1px, 6px, 3px);
|
||||
}
|
||||
groupCallStickedTooltipClose: IconButton(defaultIconButton) {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
iconPosition: point(4px, 3px);
|
||||
icon: icon {{ "calls/video_tooltip", importantTooltipFg }};
|
||||
iconOver: icon {{ "calls/video_tooltip", importantTooltipFg }};
|
||||
ripple: emptyRippleAnimation;
|
||||
}
|
||||
groupCallNiceTooltipTop: 4px;
|
||||
groupCallPaused: icon {{ "calls/video_large_paused", groupCallVideoTextFg }};
|
||||
|
||||
@@ -357,7 +357,7 @@ base::unique_qptr<Ui::PopupMenu> BoxController::rowContextMenu(
|
||||
|
||||
auto result = base::make_unique_q<Ui::PopupMenu>(parent);
|
||||
result->addAction(tr::lng_context_delete_selected(tr::now), [=] {
|
||||
Ui::show(
|
||||
_window->show(
|
||||
Box<DeleteMessagesBox>(session, base::duplicate(ids)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
});
|
||||
|
||||
@@ -28,7 +28,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "webrtc/webrtc_create_adm.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_session.h"
|
||||
#include "facades.h"
|
||||
|
||||
#include <tgcalls/Instance.h>
|
||||
#include <tgcalls/VideoCaptureInterface.h>
|
||||
@@ -161,8 +160,12 @@ Call::Call(
|
||||
, _user(user)
|
||||
, _api(&_user->session().mtp())
|
||||
, _type(type)
|
||||
, _videoIncoming(std::make_unique<Webrtc::VideoTrack>(StartVideoState(video)))
|
||||
, _videoOutgoing(std::make_unique<Webrtc::VideoTrack>(StartVideoState(video))) {
|
||||
, _videoIncoming(
|
||||
std::make_unique<Webrtc::VideoTrack>(
|
||||
StartVideoState(video)))
|
||||
, _videoOutgoing(
|
||||
std::make_unique<Webrtc::VideoTrack>(
|
||||
StartVideoState(video))) {
|
||||
_discardByTimeoutTimer.setCallback([=] { hangup(); });
|
||||
|
||||
if (_type == Type::Outgoing) {
|
||||
@@ -380,7 +383,7 @@ void Call::setupOutgoingVideo() {
|
||||
// Paused not supported right now.
|
||||
Assert(state == Webrtc::VideoState::Active);
|
||||
if (!_videoCapture) {
|
||||
_videoCapture = _delegate->getVideoCapture();
|
||||
_videoCapture = _delegate->callGetVideoCapture();
|
||||
_videoCapture->setOutput(_videoOutgoing->sink());
|
||||
}
|
||||
if (_instance) {
|
||||
@@ -801,16 +804,19 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
|
||||
AppendServer(descriptor.rtcServers, connection);
|
||||
}
|
||||
|
||||
if (Global::UseProxyForCalls()
|
||||
&& (Global::ProxySettings() == MTP::ProxyData::Settings::Enabled)) {
|
||||
const auto &selected = Global::SelectedProxy();
|
||||
if (selected.supportsCalls() && !selected.host.isEmpty()) {
|
||||
Assert(selected.type == MTP::ProxyData::Type::Socks5);
|
||||
descriptor.proxy = std::make_unique<tgcalls::Proxy>();
|
||||
descriptor.proxy->host = selected.host.toStdString();
|
||||
descriptor.proxy->port = selected.port;
|
||||
descriptor.proxy->login = selected.user.toStdString();
|
||||
descriptor.proxy->password = selected.password.toStdString();
|
||||
{
|
||||
auto &settingsProxy = Core::App().settings().proxy();
|
||||
using ProxyData = MTP::ProxyData;
|
||||
if (settingsProxy.useProxyForCalls() && settingsProxy.isEnabled()) {
|
||||
const auto &selected = settingsProxy.selected();
|
||||
if (selected.supportsCalls() && !selected.host.isEmpty()) {
|
||||
Assert(selected.type == ProxyData::Type::Socks5);
|
||||
descriptor.proxy = std::make_unique<tgcalls::Proxy>();
|
||||
descriptor.proxy->host = selected.host.toStdString();
|
||||
descriptor.proxy->port = selected.port;
|
||||
descriptor.proxy->login = selected.user.toStdString();
|
||||
descriptor.proxy->password = selected.password.toStdString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,11 @@ struct Error {
|
||||
QString details;
|
||||
};
|
||||
|
||||
enum class CallType {
|
||||
Incoming,
|
||||
Outgoing,
|
||||
};
|
||||
|
||||
class Call : public base::has_weak_ptr {
|
||||
public:
|
||||
class Delegate {
|
||||
@@ -72,7 +77,7 @@ public:
|
||||
Fn<void()> onSuccess,
|
||||
bool video) = 0;
|
||||
|
||||
virtual auto getVideoCapture()
|
||||
virtual auto callGetVideoCapture()
|
||||
-> std::shared_ptr<tgcalls::VideoCaptureInterface> = 0;
|
||||
|
||||
virtual ~Delegate() = default;
|
||||
@@ -81,11 +86,12 @@ public:
|
||||
|
||||
static constexpr auto kSoundSampleMs = 100;
|
||||
|
||||
enum class Type {
|
||||
Incoming,
|
||||
Outgoing,
|
||||
};
|
||||
Call(not_null<Delegate*> delegate, not_null<UserData*> user, Type type, bool video);
|
||||
using Type = CallType;
|
||||
Call(
|
||||
not_null<Delegate*> delegate,
|
||||
not_null<UserData*> user,
|
||||
Type type,
|
||||
bool video);
|
||||
|
||||
[[nodiscard]] Type type() const {
|
||||
return _type;
|
||||
|
||||
@@ -1,363 +0,0 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/weak_ptr.h"
|
||||
#include "base/timer.h"
|
||||
#include "base/bytes.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "mtproto/mtproto_auth_key.h"
|
||||
|
||||
class History;
|
||||
|
||||
namespace tgcalls {
|
||||
class GroupInstanceCustomImpl;
|
||||
struct GroupLevelsUpdate;
|
||||
struct GroupNetworkState;
|
||||
struct GroupParticipantDescription;
|
||||
} // namespace tgcalls
|
||||
|
||||
namespace base {
|
||||
class GlobalShortcutManager;
|
||||
class GlobalShortcutValue;
|
||||
} // namespace base
|
||||
|
||||
namespace Webrtc {
|
||||
class MediaDevices;
|
||||
} // namespace Webrtc
|
||||
|
||||
namespace Data {
|
||||
struct LastSpokeTimes;
|
||||
struct GroupCallParticipant;
|
||||
class GroupCall;
|
||||
} // namespace Data
|
||||
|
||||
namespace Calls {
|
||||
|
||||
namespace Group {
|
||||
struct MuteRequest;
|
||||
struct VolumeRequest;
|
||||
struct ParticipantState;
|
||||
struct JoinInfo;
|
||||
struct RejoinEvent;
|
||||
} // namespace Group
|
||||
|
||||
enum class MuteState {
|
||||
Active,
|
||||
PushToTalk,
|
||||
Muted,
|
||||
ForceMuted,
|
||||
RaisedHand,
|
||||
};
|
||||
|
||||
[[nodiscard]] inline auto MapPushToTalkToActive() {
|
||||
return rpl::map([=](MuteState state) {
|
||||
return (state == MuteState::PushToTalk) ? MuteState::Active : state;
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsGroupCallAdmin(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<PeerData*> participantPeer);
|
||||
|
||||
struct LevelUpdate {
|
||||
uint32 ssrc = 0;
|
||||
float value = 0.;
|
||||
bool voice = false;
|
||||
bool me = false;
|
||||
};
|
||||
|
||||
class GroupCall final : public base::has_weak_ptr {
|
||||
public:
|
||||
class Delegate {
|
||||
public:
|
||||
virtual ~Delegate() = default;
|
||||
|
||||
virtual void groupCallFinished(not_null<GroupCall*> call) = 0;
|
||||
virtual void groupCallFailed(not_null<GroupCall*> call) = 0;
|
||||
virtual void groupCallRequestPermissionsOrFail(
|
||||
Fn<void()> onSuccess) = 0;
|
||||
|
||||
enum class GroupCallSound {
|
||||
Started,
|
||||
Connecting,
|
||||
AllowedToSpeak,
|
||||
Ended,
|
||||
};
|
||||
virtual void groupCallPlaySound(GroupCallSound sound) = 0;
|
||||
};
|
||||
|
||||
using GlobalShortcutManager = base::GlobalShortcutManager;
|
||||
|
||||
GroupCall(
|
||||
not_null<Delegate*> delegate,
|
||||
Group::JoinInfo info,
|
||||
const MTPInputGroupCall &inputCall);
|
||||
~GroupCall();
|
||||
|
||||
[[nodiscard]] uint64 id() const {
|
||||
return _id;
|
||||
}
|
||||
[[nodiscard]] not_null<PeerData*> peer() const {
|
||||
return _peer;
|
||||
}
|
||||
[[nodiscard]] not_null<PeerData*> joinAs() const {
|
||||
return _joinAs;
|
||||
}
|
||||
[[nodiscard]] bool showChooseJoinAs() const;
|
||||
[[nodiscard]] TimeId scheduleDate() const {
|
||||
return _scheduleDate;
|
||||
}
|
||||
[[nodiscard]] bool scheduleStartSubscribed() const;
|
||||
|
||||
[[nodiscard]] Data::GroupCall *lookupReal() const;
|
||||
[[nodiscard]] rpl::producer<not_null<Data::GroupCall*>> real() const;
|
||||
|
||||
void start(TimeId scheduleDate);
|
||||
void hangup();
|
||||
void discard();
|
||||
void rejoinAs(Group::JoinInfo info);
|
||||
void rejoinWithHash(const QString &hash);
|
||||
void join(const MTPInputGroupCall &inputCall);
|
||||
void handleUpdate(const MTPUpdate &update);
|
||||
void handlePossibleCreateOrJoinResponse(const MTPDupdateGroupCall &data);
|
||||
void changeTitle(const QString &title);
|
||||
void toggleRecording(bool enabled, const QString &title);
|
||||
[[nodiscard]] bool recordingStoppedByMe() const {
|
||||
return _recordingStoppedByMe;
|
||||
}
|
||||
void startScheduledNow();
|
||||
void toggleScheduleStartSubscribed(bool subscribed);
|
||||
|
||||
void setMuted(MuteState mute);
|
||||
void setMutedAndUpdate(MuteState mute);
|
||||
[[nodiscard]] MuteState muted() const {
|
||||
return _muted.current();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<MuteState> mutedValue() const {
|
||||
return _muted.value();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto otherParticipantStateValue() const
|
||||
-> rpl::producer<Group::ParticipantState>;
|
||||
|
||||
enum State {
|
||||
Creating,
|
||||
Waiting,
|
||||
Joining,
|
||||
Connecting,
|
||||
Joined,
|
||||
FailedHangingUp,
|
||||
Failed,
|
||||
HangingUp,
|
||||
Ended,
|
||||
};
|
||||
[[nodiscard]] State state() const {
|
||||
return _state.current();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<State> stateValue() const {
|
||||
return _state.value();
|
||||
}
|
||||
|
||||
enum class InstanceState {
|
||||
Disconnected,
|
||||
TransitionToRtc,
|
||||
Connected,
|
||||
};
|
||||
[[nodiscard]] InstanceState instanceState() const {
|
||||
return _instanceState.current();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<InstanceState> instanceStateValue() const {
|
||||
return _instanceState.value();
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<LevelUpdate> levelUpdates() const {
|
||||
return _levelUpdates.events();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<Group::RejoinEvent> rejoinEvents() const {
|
||||
return _rejoinEvents.events();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<> allowedToSpeakNotifications() const {
|
||||
return _allowedToSpeakNotifications.events();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<> titleChanged() const {
|
||||
return _titleChanged.events();
|
||||
}
|
||||
static constexpr auto kSpeakLevelThreshold = 0.2;
|
||||
|
||||
void setCurrentAudioDevice(bool input, const QString &deviceId);
|
||||
//void setAudioVolume(bool input, float level);
|
||||
void setAudioDuckingEnabled(bool enabled);
|
||||
|
||||
void toggleMute(const Group::MuteRequest &data);
|
||||
void changeVolume(const Group::VolumeRequest &data);
|
||||
std::variant<int, not_null<UserData*>> inviteUsers(
|
||||
const std::vector<not_null<UserData*>> &users);
|
||||
|
||||
std::shared_ptr<GlobalShortcutManager> ensureGlobalShortcutManager();
|
||||
void applyGlobalShortcutChanges();
|
||||
|
||||
void pushToTalk(bool pressed, crl::time delay);
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
|
||||
private:
|
||||
class LoadPartTask;
|
||||
|
||||
public:
|
||||
void broadcastPartStart(std::shared_ptr<LoadPartTask> task);
|
||||
void broadcastPartCancel(not_null<LoadPartTask*> task);
|
||||
|
||||
private:
|
||||
using GlobalShortcutValue = base::GlobalShortcutValue;
|
||||
|
||||
struct LoadingPart {
|
||||
std::shared_ptr<LoadPartTask> task;
|
||||
mtpRequestId requestId = 0;
|
||||
};
|
||||
|
||||
enum class FinishType {
|
||||
None,
|
||||
Ended,
|
||||
Failed,
|
||||
};
|
||||
enum class InstanceMode {
|
||||
None,
|
||||
Rtc,
|
||||
Stream,
|
||||
};
|
||||
enum class SendUpdateType {
|
||||
Mute,
|
||||
RaiseHand,
|
||||
};
|
||||
|
||||
void handlePossibleCreateOrJoinResponse(const MTPDgroupCall &data);
|
||||
void handlePossibleDiscarded(const MTPDgroupCallDiscarded &data);
|
||||
void handleUpdate(const MTPDupdateGroupCall &data);
|
||||
void handleUpdate(const MTPDupdateGroupCallParticipants &data);
|
||||
void handleRequestError(const MTP::Error &error);
|
||||
void handleControllerError(const QString &error);
|
||||
void ensureControllerCreated();
|
||||
void destroyController();
|
||||
|
||||
void setState(State state);
|
||||
void finish(FinishType type);
|
||||
void maybeSendMutedUpdate(MuteState previous);
|
||||
void sendSelfUpdate(SendUpdateType type);
|
||||
void updateInstanceMuteState();
|
||||
void updateInstanceVolumes();
|
||||
void applyMeInCallLocally();
|
||||
void rejoin();
|
||||
void rejoin(not_null<PeerData*> as);
|
||||
void setJoinAs(not_null<PeerData*> as);
|
||||
void saveDefaultJoinAs(not_null<PeerData*> as);
|
||||
void subscribeToReal(not_null<Data::GroupCall*> real);
|
||||
void setScheduledDate(TimeId date);
|
||||
|
||||
void audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data);
|
||||
void setInstanceConnected(tgcalls::GroupNetworkState networkState);
|
||||
void setInstanceMode(InstanceMode mode);
|
||||
void checkLastSpoke();
|
||||
void pushToTalkCancel();
|
||||
|
||||
void checkGlobalShortcutAvailability();
|
||||
void checkJoined();
|
||||
void checkFirstTimeJoined();
|
||||
void notifyAboutAllowedToSpeak();
|
||||
|
||||
void playConnectingSound();
|
||||
void stopConnectingSound();
|
||||
void playConnectingSoundOnce();
|
||||
|
||||
void requestParticipantsInformation(const std::vector<uint32_t> &ssrcs);
|
||||
void addParticipantsToInstance();
|
||||
void prepareParticipantForAdding(
|
||||
const Data::GroupCallParticipant &participant);
|
||||
void addPreparedParticipants();
|
||||
void addPreparedParticipantsDelayed();
|
||||
|
||||
void editParticipant(
|
||||
not_null<PeerData*> participantPeer,
|
||||
bool mute,
|
||||
std::optional<int> volume);
|
||||
void applyParticipantLocally(
|
||||
not_null<PeerData*> participantPeer,
|
||||
bool mute,
|
||||
std::optional<int> volume);
|
||||
void applyQueuedSelfUpdates();
|
||||
void applySelfUpdate(const MTPDgroupCallParticipant &data);
|
||||
void applyOtherParticipantUpdate(const MTPDgroupCallParticipant &data);
|
||||
|
||||
[[nodiscard]] MTPInputGroupCall inputCall() const;
|
||||
|
||||
const not_null<Delegate*> _delegate;
|
||||
not_null<PeerData*> _peer; // Can change in legacy group migration.
|
||||
rpl::event_stream<PeerData*> _peerStream;
|
||||
not_null<History*> _history; // Can change in legacy group migration.
|
||||
MTP::Sender _api;
|
||||
rpl::event_stream<not_null<Data::GroupCall*>> _realChanges;
|
||||
rpl::variable<State> _state = State::Creating;
|
||||
rpl::variable<InstanceState> _instanceState
|
||||
= InstanceState::Disconnected;
|
||||
bool _instanceTransitioning = false;
|
||||
InstanceMode _instanceMode = InstanceMode::None;
|
||||
base::flat_set<uint32> _unresolvedSsrcs;
|
||||
std::vector<tgcalls::GroupParticipantDescription> _preparedParticipants;
|
||||
bool _addPreparedParticipantsScheduled = false;
|
||||
bool _recordingStoppedByMe = false;
|
||||
|
||||
MTP::DcId _broadcastDcId = 0;
|
||||
base::flat_map<not_null<LoadPartTask*>, LoadingPart> _broadcastParts;
|
||||
|
||||
not_null<PeerData*> _joinAs;
|
||||
std::vector<not_null<PeerData*>> _possibleJoinAs;
|
||||
QString _joinHash;
|
||||
|
||||
rpl::variable<MuteState> _muted = MuteState::Muted;
|
||||
bool _initialMuteStateSent = false;
|
||||
bool _acceptFields = false;
|
||||
|
||||
rpl::event_stream<Group::ParticipantState> _otherParticipantStateValue;
|
||||
std::vector<MTPGroupCallParticipant> _queuedSelfUpdates;
|
||||
|
||||
uint64 _id = 0;
|
||||
uint64 _accessHash = 0;
|
||||
uint32 _mySsrc = 0;
|
||||
TimeId _scheduleDate = 0;
|
||||
base::flat_set<uint32> _mySsrcs;
|
||||
mtpRequestId _createRequestId = 0;
|
||||
mtpRequestId _updateMuteRequestId = 0;
|
||||
|
||||
std::unique_ptr<tgcalls::GroupInstanceCustomImpl> _instance;
|
||||
rpl::event_stream<LevelUpdate> _levelUpdates;
|
||||
base::flat_map<uint32, Data::LastSpokeTimes> _lastSpoke;
|
||||
rpl::event_stream<Group::RejoinEvent> _rejoinEvents;
|
||||
rpl::event_stream<> _allowedToSpeakNotifications;
|
||||
rpl::event_stream<> _titleChanged;
|
||||
base::Timer _lastSpokeCheckTimer;
|
||||
base::Timer _checkJoinedTimer;
|
||||
|
||||
crl::time _lastSendProgressUpdate = 0;
|
||||
|
||||
std::shared_ptr<GlobalShortcutManager> _shortcutManager;
|
||||
std::shared_ptr<GlobalShortcutValue> _pushToTalk;
|
||||
base::Timer _pushToTalkCancelTimer;
|
||||
base::Timer _connectingSoundTimer;
|
||||
bool _hadJoinedState = false;
|
||||
|
||||
std::unique_ptr<Webrtc::MediaDevices> _mediaDevices;
|
||||
QString _audioInputId;
|
||||
QString _audioOutputId;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Calls
|
||||
@@ -1,146 +0,0 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/weak_ptr.h"
|
||||
#include "base/timer.h"
|
||||
#include "base/object_ptr.h"
|
||||
#include "calls/calls_group_call.h"
|
||||
#include "calls/calls_choose_join_as.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/rp_widget.h"
|
||||
|
||||
class Image;
|
||||
|
||||
namespace Data {
|
||||
class PhotoMedia;
|
||||
class CloudImageView;
|
||||
class GroupCall;
|
||||
} // namespace Data
|
||||
|
||||
namespace Ui {
|
||||
class AbstractButton;
|
||||
class DropdownMenu;
|
||||
class CallButton;
|
||||
class CallMuteButton;
|
||||
class IconButton;
|
||||
class FlatLabel;
|
||||
template <typename Widget>
|
||||
class FadeWrap;
|
||||
template <typename Widget>
|
||||
class PaddingWrap;
|
||||
class Window;
|
||||
class ScrollArea;
|
||||
class GenericBox;
|
||||
class LayerManager;
|
||||
class GroupCallScheduledLeft;
|
||||
namespace Platform {
|
||||
class TitleControls;
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
|
||||
namespace style {
|
||||
struct CallSignalBars;
|
||||
struct CallBodyLayout;
|
||||
} // namespace style
|
||||
|
||||
namespace Calls::Group {
|
||||
|
||||
class Members;
|
||||
|
||||
class Panel final {
|
||||
public:
|
||||
Panel(not_null<GroupCall*> call);
|
||||
~Panel();
|
||||
|
||||
[[nodiscard]] bool isActive() const;
|
||||
void minimize();
|
||||
void close();
|
||||
void showAndActivate();
|
||||
void closeBeforeDestroy();
|
||||
|
||||
private:
|
||||
using State = GroupCall::State;
|
||||
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> widget() const;
|
||||
|
||||
void paint(QRect clip);
|
||||
|
||||
void initWindow();
|
||||
void initWidget();
|
||||
void initControls();
|
||||
void initShareAction();
|
||||
void initLayout();
|
||||
void initGeometry();
|
||||
void setupScheduledLabels(rpl::producer<TimeId> date);
|
||||
void setupMembers();
|
||||
void setupJoinAsChangedToasts();
|
||||
void setupTitleChangedToasts();
|
||||
void setupAllowedToSpeakToasts();
|
||||
void setupRealMuteButtonState(not_null<Data::GroupCall*> real);
|
||||
|
||||
bool handleClose();
|
||||
void startScheduledNow();
|
||||
|
||||
void updateControlsGeometry();
|
||||
void updateMembersGeometry();
|
||||
void showControls();
|
||||
void refreshLeftButton();
|
||||
|
||||
void endCall();
|
||||
|
||||
void showMainMenu();
|
||||
void chooseJoinAs();
|
||||
void addMembers();
|
||||
void kickParticipant(not_null<PeerData*> participantPeer);
|
||||
void kickParticipantSure(not_null<PeerData*> participantPeer);
|
||||
[[nodiscard]] QRect computeTitleRect() const;
|
||||
void refreshTitle();
|
||||
void refreshTitleGeometry();
|
||||
void setupRealCallViewers();
|
||||
void subscribeToChanges(not_null<Data::GroupCall*> real);
|
||||
|
||||
void migrate(not_null<ChannelData*> channel);
|
||||
void subscribeToPeerChanges();
|
||||
|
||||
const not_null<GroupCall*> _call;
|
||||
not_null<PeerData*> _peer;
|
||||
|
||||
const std::unique_ptr<Ui::Window> _window;
|
||||
const std::unique_ptr<Ui::LayerManager> _layerBg;
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
std::unique_ptr<Ui::Platform::TitleControls> _controls;
|
||||
#endif // !Q_OS_MAC
|
||||
|
||||
rpl::lifetime _callLifetime;
|
||||
|
||||
object_ptr<Ui::FlatLabel> _title = { nullptr };
|
||||
object_ptr<Ui::FlatLabel> _subtitle = { nullptr };
|
||||
object_ptr<Ui::AbstractButton> _recordingMark = { nullptr };
|
||||
object_ptr<Ui::IconButton> _menuToggle = { nullptr };
|
||||
object_ptr<Ui::DropdownMenu> _menu = { nullptr };
|
||||
object_ptr<Ui::AbstractButton> _joinAsToggle = { nullptr };
|
||||
object_ptr<Members> _members = { nullptr };
|
||||
object_ptr<Ui::FlatLabel> _startsIn = { nullptr };
|
||||
object_ptr<Ui::RpWidget> _countdown = { nullptr };
|
||||
std::shared_ptr<Ui::GroupCallScheduledLeft> _countdownData;
|
||||
object_ptr<Ui::FlatLabel> _startsWhen = { nullptr };
|
||||
ChooseJoinAsProcess _joinAsProcess;
|
||||
|
||||
object_ptr<Ui::CallButton> _settings = { nullptr };
|
||||
object_ptr<Ui::CallButton> _share = { nullptr };
|
||||
std::unique_ptr<Ui::CallMuteButton> _mute;
|
||||
object_ptr<Ui::CallButton> _hangup;
|
||||
Fn<void()> _shareLinkCallback;
|
||||
|
||||
rpl::lifetime _peerLifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Calls::Group
|
||||
@@ -7,7 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "calls/calls_instance.h"
|
||||
|
||||
#include "calls/calls_group_common.h"
|
||||
#include "calls/calls_call.h"
|
||||
#include "calls/group/calls_group_common.h"
|
||||
#include "calls/group/calls_choose_join_as.h"
|
||||
#include "calls/group/calls_group_call.h"
|
||||
#include "mtproto/mtproto_dh_utils.h"
|
||||
#include "core/application.h"
|
||||
#include "main/main_session.h"
|
||||
@@ -15,10 +18,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "apiwrap.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "calls/group/calls_group_call.h"
|
||||
#include "calls/group/calls_group_panel.h"
|
||||
#include "calls/calls_call.h"
|
||||
#include "calls/calls_group_call.h"
|
||||
#include "calls/calls_panel.h"
|
||||
#include "calls/calls_group_panel.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_group_call.h"
|
||||
#include "data/data_channel.h"
|
||||
@@ -41,11 +44,142 @@ namespace {
|
||||
|
||||
constexpr auto kServerConfigUpdateTimeoutMs = 24 * 3600 * crl::time(1000);
|
||||
|
||||
using CallSound = Call::Delegate::CallSound;
|
||||
using GroupCallSound = GroupCall::Delegate::GroupCallSound;
|
||||
|
||||
} // namespace
|
||||
|
||||
Instance::Instance() = default;
|
||||
class Instance::Delegate final
|
||||
: public Call::Delegate
|
||||
, public GroupCall::Delegate {
|
||||
public:
|
||||
explicit Delegate(not_null<Instance*> instance);
|
||||
|
||||
Instance::~Instance() = default;
|
||||
DhConfig getDhConfig() const override;
|
||||
|
||||
void callFinished(not_null<Call*> call) override;
|
||||
void callFailed(not_null<Call*> call) override;
|
||||
void callRedial(not_null<Call*> call) override;
|
||||
void callRequestPermissionsOrFail(
|
||||
Fn<void()> onSuccess,
|
||||
bool video) override;
|
||||
void callPlaySound(CallSound sound) override;
|
||||
auto callGetVideoCapture()
|
||||
-> std::shared_ptr<tgcalls::VideoCaptureInterface> override;
|
||||
|
||||
void groupCallFinished(not_null<GroupCall*> call) override;
|
||||
void groupCallFailed(not_null<GroupCall*> call) override;
|
||||
void groupCallRequestPermissionsOrFail(Fn<void()> onSuccess) override;
|
||||
void groupCallPlaySound(GroupCallSound sound) override;
|
||||
auto groupCallGetVideoCapture(const QString &deviceId)
|
||||
-> std::shared_ptr<tgcalls::VideoCaptureInterface> override;
|
||||
FnMut<void()> groupCallAddAsyncWaiter() override;
|
||||
|
||||
private:
|
||||
const not_null<Instance*> _instance;
|
||||
|
||||
};
|
||||
|
||||
Instance::Delegate::Delegate(not_null<Instance*> instance)
|
||||
: _instance(instance) {
|
||||
}
|
||||
|
||||
DhConfig Instance::Delegate::getDhConfig() const {
|
||||
return *_instance->_cachedDhConfig;
|
||||
}
|
||||
|
||||
void Instance::Delegate::callFinished(not_null<Call*> call) {
|
||||
crl::on_main(call, [=] {
|
||||
_instance->destroyCall(call);
|
||||
});
|
||||
}
|
||||
|
||||
void Instance::Delegate::callFailed(not_null<Call*> call) {
|
||||
crl::on_main(call, [=] {
|
||||
_instance->destroyCall(call);
|
||||
});
|
||||
}
|
||||
|
||||
void Instance::Delegate::callRedial(not_null<Call*> call) {
|
||||
if (_instance->_currentCall.get() == call) {
|
||||
_instance->refreshDhConfig();
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::Delegate::callRequestPermissionsOrFail(
|
||||
Fn<void()> onSuccess,
|
||||
bool video) {
|
||||
_instance->requestPermissionsOrFail(std::move(onSuccess), video);
|
||||
}
|
||||
|
||||
void Instance::Delegate::callPlaySound(CallSound sound) {
|
||||
_instance->playSoundOnce([&] {
|
||||
switch (sound) {
|
||||
case CallSound::Busy: return "call_busy";
|
||||
case CallSound::Ended: return "call_end";
|
||||
case CallSound::Connecting: return "call_connect";
|
||||
}
|
||||
Unexpected("CallSound in Instance::callPlaySound.");
|
||||
}());
|
||||
}
|
||||
|
||||
auto Instance::Delegate::callGetVideoCapture()
|
||||
-> std::shared_ptr<tgcalls::VideoCaptureInterface> {
|
||||
return _instance->getVideoCapture();
|
||||
}
|
||||
|
||||
void Instance::Delegate::groupCallFinished(not_null<GroupCall*> call) {
|
||||
crl::on_main(call, [=] {
|
||||
_instance->destroyGroupCall(call);
|
||||
});
|
||||
}
|
||||
|
||||
void Instance::Delegate::groupCallFailed(not_null<GroupCall*> call) {
|
||||
crl::on_main(call, [=] {
|
||||
_instance->destroyGroupCall(call);
|
||||
});
|
||||
}
|
||||
|
||||
void Instance::Delegate::groupCallRequestPermissionsOrFail(
|
||||
Fn<void()> onSuccess) {
|
||||
_instance->requestPermissionsOrFail(std::move(onSuccess), false);
|
||||
}
|
||||
|
||||
void Instance::Delegate::groupCallPlaySound(GroupCallSound sound) {
|
||||
_instance->playSoundOnce([&] {
|
||||
switch (sound) {
|
||||
case GroupCallSound::Started: return "group_call_start";
|
||||
case GroupCallSound::Ended: return "group_call_end";
|
||||
case GroupCallSound::AllowedToSpeak: return "group_call_allowed";
|
||||
case GroupCallSound::Connecting: return "group_call_connect";
|
||||
}
|
||||
Unexpected("GroupCallSound in Instance::groupCallPlaySound.");
|
||||
}());
|
||||
}
|
||||
|
||||
auto Instance::Delegate::groupCallGetVideoCapture(const QString &deviceId)
|
||||
-> std::shared_ptr<tgcalls::VideoCaptureInterface> {
|
||||
return _instance->getVideoCapture(deviceId);
|
||||
}
|
||||
|
||||
FnMut<void()> Instance::Delegate::groupCallAddAsyncWaiter() {
|
||||
return _instance->addAsyncWaiter();
|
||||
}
|
||||
|
||||
Instance::Instance()
|
||||
: _delegate(std::make_unique<Delegate>(this))
|
||||
, _cachedDhConfig(std::make_unique<DhConfig>())
|
||||
, _chooseJoinAs(std::make_unique<Group::ChooseJoinAsProcess>()) {
|
||||
}
|
||||
|
||||
Instance::~Instance() {
|
||||
destroyCurrentCall();
|
||||
|
||||
while (!_asyncWaiters.empty()) {
|
||||
_asyncWaiters.front()->acquire();
|
||||
_asyncWaiters.erase(_asyncWaiters.begin());
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::startOutgoingCall(not_null<UserData*> user, bool video) {
|
||||
if (activateCurrentCall()) {
|
||||
@@ -72,7 +206,7 @@ void Instance::startOrJoinGroupCall(
|
||||
: peer->groupCall()
|
||||
? Group::ChooseJoinAsProcess::Context::Join
|
||||
: Group::ChooseJoinAsProcess::Context::Create;
|
||||
_chooseJoinAs.start(peer, context, [=](object_ptr<Ui::BoxContent> box) {
|
||||
_chooseJoinAs->start(peer, context, [=](object_ptr<Ui::BoxContent> box) {
|
||||
Ui::show(std::move(box), Ui::LayerOption::KeepOther);
|
||||
}, [=](QString text) {
|
||||
Ui::Toast::Show(text);
|
||||
@@ -85,36 +219,6 @@ void Instance::startOrJoinGroupCall(
|
||||
});
|
||||
}
|
||||
|
||||
void Instance::callFinished(not_null<Call*> call) {
|
||||
crl::on_main(call, [=] {
|
||||
destroyCall(call);
|
||||
});
|
||||
}
|
||||
|
||||
void Instance::callFailed(not_null<Call*> call) {
|
||||
crl::on_main(call, [=] {
|
||||
destroyCall(call);
|
||||
});
|
||||
}
|
||||
|
||||
void Instance::callRedial(not_null<Call*> call) {
|
||||
if (_currentCall.get() == call) {
|
||||
refreshDhConfig();
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::groupCallFinished(not_null<GroupCall*> call) {
|
||||
crl::on_main(call, [=] {
|
||||
destroyGroupCall(call);
|
||||
});
|
||||
}
|
||||
|
||||
void Instance::groupCallFailed(not_null<GroupCall*> call) {
|
||||
crl::on_main(call, [=] {
|
||||
destroyGroupCall(call);
|
||||
});
|
||||
}
|
||||
|
||||
not_null<Media::Audio::Track*> Instance::ensureSoundLoaded(
|
||||
const QString &key) {
|
||||
const auto i = _tracks.find(key);
|
||||
@@ -132,31 +236,6 @@ void Instance::playSoundOnce(const QString &key) {
|
||||
ensureSoundLoaded(key)->playOnce();
|
||||
}
|
||||
|
||||
void Instance::callPlaySound(CallSound sound) {
|
||||
playSoundOnce([&] {
|
||||
switch (sound) {
|
||||
case CallSound::Busy: return "call_busy";
|
||||
case CallSound::Ended: return "call_end";
|
||||
case CallSound::Connecting: return "call_connect";
|
||||
}
|
||||
Unexpected("CallSound in Instance::callPlaySound.");
|
||||
return "";
|
||||
}());
|
||||
}
|
||||
|
||||
void Instance::groupCallPlaySound(GroupCallSound sound) {
|
||||
playSoundOnce([&] {
|
||||
switch (sound) {
|
||||
case GroupCallSound::Started: return "group_call_start";
|
||||
case GroupCallSound::Ended: return "group_call_end";
|
||||
case GroupCallSound::AllowedToSpeak: return "group_call_allowed";
|
||||
case GroupCallSound::Connecting: return "group_call_connect";
|
||||
}
|
||||
Unexpected("GroupCallSound in Instance::groupCallPlaySound.");
|
||||
return "";
|
||||
}());
|
||||
}
|
||||
|
||||
void Instance::destroyCall(not_null<Call*> call) {
|
||||
if (_currentCall.get() == call) {
|
||||
_currentCallPanel->closeBeforeDestroy();
|
||||
@@ -174,7 +253,7 @@ void Instance::destroyCall(not_null<Call*> call) {
|
||||
}
|
||||
|
||||
void Instance::createCall(not_null<UserData*> user, Call::Type type, bool video) {
|
||||
auto call = std::make_unique<Call>(getCallDelegate(), user, type, video);
|
||||
auto call = std::make_unique<Call>(_delegate.get(), user, type, video);
|
||||
const auto raw = call.get();
|
||||
|
||||
user->session().account().sessionChanges(
|
||||
@@ -217,7 +296,7 @@ void Instance::createGroupCall(
|
||||
destroyCurrentCall();
|
||||
|
||||
auto call = std::make_unique<GroupCall>(
|
||||
getGroupCallDelegate(),
|
||||
_delegate.get(),
|
||||
std::move(info),
|
||||
inputCall);
|
||||
const auto raw = call.get();
|
||||
@@ -237,7 +316,7 @@ void Instance::refreshDhConfig() {
|
||||
|
||||
const auto weak = base::make_weak(_currentCall);
|
||||
_currentCall->user()->session().api().request(MTPmessages_GetDhConfig(
|
||||
MTP_int(_dhConfig.version),
|
||||
MTP_int(_cachedDhConfig->version),
|
||||
MTP_int(MTP::ModExpFirst::kRandomPowerSize)
|
||||
)).done([=](const MTPmessages_DhConfig &result) {
|
||||
const auto call = weak.get();
|
||||
@@ -249,14 +328,14 @@ void Instance::refreshDhConfig() {
|
||||
Assert(random.size() == MTP::ModExpFirst::kRandomPowerSize);
|
||||
call->start(random);
|
||||
} else {
|
||||
callFailed(call);
|
||||
_delegate->callFailed(call);
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
const auto call = weak.get();
|
||||
if (!call) {
|
||||
return;
|
||||
}
|
||||
callFailed(call);
|
||||
_delegate->callFailed(call);
|
||||
}).send();
|
||||
}
|
||||
|
||||
@@ -277,13 +356,13 @@ bytes::const_span Instance::updateDhConfig(
|
||||
} else if (!validRandom(data.vrandom().v)) {
|
||||
return {};
|
||||
}
|
||||
_dhConfig.g = data.vg().v;
|
||||
_dhConfig.p = std::move(primeBytes);
|
||||
_dhConfig.version = data.vversion().v;
|
||||
_cachedDhConfig->g = data.vg().v;
|
||||
_cachedDhConfig->p = std::move(primeBytes);
|
||||
_cachedDhConfig->version = data.vversion().v;
|
||||
return bytes::make_span(data.vrandom().v);
|
||||
}, [&](const MTPDmessages_dhConfigNotModified &data)
|
||||
-> bytes::const_span {
|
||||
if (!_dhConfig.g || _dhConfig.p.empty()) {
|
||||
if (!_cachedDhConfig->g || _cachedDhConfig->p.empty()) {
|
||||
LOG(("API Error: dhConfigNotModified on zero version."));
|
||||
return {};
|
||||
} else if (!validRandom(data.vrandom().v)) {
|
||||
@@ -324,6 +403,8 @@ void Instance::handleUpdate(
|
||||
handleSignalingData(session, data);
|
||||
}, [&](const MTPDupdateGroupCall &data) {
|
||||
handleGroupCallUpdate(session, update);
|
||||
}, [&](const MTPDupdateGroupCallConnection &data) {
|
||||
handleGroupCallUpdate(session, update);
|
||||
}, [&](const MTPDupdateGroupCallParticipants &data) {
|
||||
handleGroupCallUpdate(session, update);
|
||||
}, [](const auto &) {
|
||||
@@ -357,6 +438,26 @@ void Instance::setCurrentAudioDevice(bool input, const QString &deviceId) {
|
||||
}
|
||||
}
|
||||
|
||||
FnMut<void()> Instance::addAsyncWaiter() {
|
||||
auto semaphore = std::make_unique<crl::semaphore>();
|
||||
const auto raw = semaphore.get();
|
||||
const auto weak = base::make_weak(this);
|
||||
_asyncWaiters.emplace(std::move(semaphore));
|
||||
return [raw, weak] {
|
||||
raw->release();
|
||||
crl::on_main(weak, [raw, weak] {
|
||||
auto &waiters = weak->_asyncWaiters;
|
||||
auto wrapped = std::unique_ptr<crl::semaphore>(raw);
|
||||
const auto i = waiters.find(wrapped);
|
||||
wrapped.release();
|
||||
|
||||
if (i != end(waiters)) {
|
||||
waiters.erase(i);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
bool Instance::isQuitPrevent() {
|
||||
if (!_currentCall || _currentCall->isIncomingWaiting()) {
|
||||
return false;
|
||||
@@ -415,10 +516,15 @@ void Instance::handleGroupCallUpdate(
|
||||
&& (&_currentGroupCall->peer()->session() == session)) {
|
||||
update.match([&](const MTPDupdateGroupCall &data) {
|
||||
_currentGroupCall->handlePossibleCreateOrJoinResponse(data);
|
||||
}, [&](const MTPDupdateGroupCallConnection &data) {
|
||||
_currentGroupCall->handlePossibleCreateOrJoinResponse(data);
|
||||
}, [](const auto &) {
|
||||
});
|
||||
}
|
||||
|
||||
if (update.type() == mtpc_updateGroupCallConnection) {
|
||||
return;
|
||||
}
|
||||
const auto callId = update.match([](const MTPDupdateGroupCall &data) {
|
||||
return data.vcall().match([](const auto &data) {
|
||||
return data.vid().v;
|
||||
@@ -592,14 +698,19 @@ void Instance::requestPermissionOrFail(Platform::PermissionType type, Fn<void()>
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<tgcalls::VideoCaptureInterface> Instance::getVideoCapture() {
|
||||
std::shared_ptr<tgcalls::VideoCaptureInterface> Instance::getVideoCapture(
|
||||
QString deviceId) {
|
||||
if (deviceId.isEmpty()) {
|
||||
deviceId = Core::App().settings().callVideoInputDeviceId();
|
||||
}
|
||||
if (auto result = _videoCapture.lock()) {
|
||||
result->switchToDevice(deviceId.toStdString());
|
||||
return result;
|
||||
}
|
||||
auto result = std::shared_ptr<tgcalls::VideoCaptureInterface>(
|
||||
tgcalls::VideoCaptureInterface::Create(
|
||||
tgcalls::StaticThreads::getThreads(),
|
||||
Core::App().settings().callVideoInputDeviceId().toStdString()));
|
||||
deviceId.toStdString()));
|
||||
_videoCapture = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -8,9 +8,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/sender.h"
|
||||
#include "calls/calls_call.h"
|
||||
#include "calls/calls_group_call.h"
|
||||
#include "calls/calls_choose_join_as.h"
|
||||
|
||||
namespace crl {
|
||||
class semaphore;
|
||||
} // namespace crl
|
||||
|
||||
namespace Platform {
|
||||
enum class PermissionType;
|
||||
@@ -27,17 +28,22 @@ class Session;
|
||||
namespace Calls::Group {
|
||||
struct JoinInfo;
|
||||
class Panel;
|
||||
class ChooseJoinAsProcess;
|
||||
} // namespace Calls::Group
|
||||
|
||||
namespace tgcalls {
|
||||
class VideoCaptureInterface;
|
||||
} // namespace tgcalls
|
||||
|
||||
namespace Calls {
|
||||
|
||||
class Call;
|
||||
enum class CallType;
|
||||
class GroupCall;
|
||||
class Panel;
|
||||
struct DhConfig;
|
||||
|
||||
class Instance
|
||||
: private Call::Delegate
|
||||
, private GroupCall::Delegate
|
||||
, private base::Subscriber
|
||||
, public base::has_weak_ptr {
|
||||
class Instance : private base::Subscriber, public base::has_weak_ptr {
|
||||
public:
|
||||
Instance();
|
||||
~Instance();
|
||||
@@ -69,49 +75,24 @@ public:
|
||||
bool activateCurrentCall(const QString &joinHash = QString());
|
||||
bool minimizeCurrentActiveCall();
|
||||
bool closeCurrentActiveCall();
|
||||
auto getVideoCapture()
|
||||
-> std::shared_ptr<tgcalls::VideoCaptureInterface> override;
|
||||
[[nodiscard]] auto getVideoCapture(QString deviceId = QString())
|
||||
-> std::shared_ptr<tgcalls::VideoCaptureInterface>;
|
||||
void requestPermissionsOrFail(Fn<void()> onSuccess, bool video = true);
|
||||
|
||||
void setCurrentAudioDevice(bool input, const QString &deviceId);
|
||||
|
||||
[[nodiscard]] FnMut<void()> addAsyncWaiter();
|
||||
|
||||
[[nodiscard]] bool isQuitPrevent();
|
||||
|
||||
private:
|
||||
using CallSound = Call::Delegate::CallSound;
|
||||
using GroupCallSound = GroupCall::Delegate::GroupCallSound;
|
||||
|
||||
[[nodiscard]] not_null<Call::Delegate*> getCallDelegate() {
|
||||
return static_cast<Call::Delegate*>(this);
|
||||
}
|
||||
[[nodiscard]] not_null<GroupCall::Delegate*> getGroupCallDelegate() {
|
||||
return static_cast<GroupCall::Delegate*>(this);
|
||||
}
|
||||
[[nodiscard]] DhConfig getDhConfig() const override {
|
||||
return _dhConfig;
|
||||
}
|
||||
class Delegate;
|
||||
friend class Delegate;
|
||||
|
||||
not_null<Media::Audio::Track*> ensureSoundLoaded(const QString &key);
|
||||
void playSoundOnce(const QString &key);
|
||||
|
||||
void callFinished(not_null<Call*> call) override;
|
||||
void callFailed(not_null<Call*> call) override;
|
||||
void callRedial(not_null<Call*> call) override;
|
||||
void callRequestPermissionsOrFail(
|
||||
Fn<void()> onSuccess,
|
||||
bool video) override {
|
||||
requestPermissionsOrFail(std::move(onSuccess), video);
|
||||
}
|
||||
void callPlaySound(CallSound sound) override;
|
||||
|
||||
void groupCallFinished(not_null<GroupCall*> call) override;
|
||||
void groupCallFailed(not_null<GroupCall*> call) override;
|
||||
void groupCallRequestPermissionsOrFail(Fn<void()> onSuccess) override {
|
||||
requestPermissionsOrFail(std::move(onSuccess), false);
|
||||
}
|
||||
void groupCallPlaySound(GroupCallSound sound) override;
|
||||
|
||||
void createCall(not_null<UserData*> user, Call::Type type, bool video);
|
||||
void createCall(not_null<UserData*> user, CallType type, bool video);
|
||||
void destroyCall(not_null<Call*> call);
|
||||
|
||||
void createGroupCall(
|
||||
@@ -138,7 +119,8 @@ private:
|
||||
not_null<Main::Session*> session,
|
||||
const MTPUpdate &update);
|
||||
|
||||
DhConfig _dhConfig;
|
||||
const std::unique_ptr<Delegate> _delegate;
|
||||
const std::unique_ptr<DhConfig> _cachedDhConfig;
|
||||
|
||||
crl::time _lastServerConfigUpdateTime = 0;
|
||||
base::weak_ptr<Main::Session> _serverConfigRequestSession;
|
||||
@@ -154,7 +136,9 @@ private:
|
||||
|
||||
base::flat_map<QString, std::unique_ptr<Media::Audio::Track>> _tracks;
|
||||
|
||||
Group::ChooseJoinAsProcess _chooseJoinAs;
|
||||
const std::unique_ptr<Group::ChooseJoinAsProcess> _chooseJoinAs;
|
||||
|
||||
base::flat_set<std::unique_ptr<crl::semaphore>> _asyncWaiters;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "calls/calls_signal_bars.h"
|
||||
#include "calls/calls_userpic.h"
|
||||
#include "calls/calls_video_bubble.h"
|
||||
#include "calls/calls_video_incoming.h"
|
||||
#include "ui/platform/ui_platform_window_title.h"
|
||||
#include "ui/widgets/call_button.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
@@ -29,6 +30,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/wrap/fade_wrap.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/platform/ui_platform_utility.h"
|
||||
#include "ui/gl/gl_surface.h"
|
||||
#include "ui/gl/gl_shader.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/emoji_config.h"
|
||||
@@ -39,7 +42,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "platform/platform_specific.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "window/main_window.h"
|
||||
#include "media/view/media_view_pip.h" // Utilities for frame rotation.
|
||||
#include "app.h"
|
||||
#include "webrtc/webrtc_video_track.h"
|
||||
#include "styles/style_calls.h"
|
||||
@@ -48,146 +50,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <QtWidgets/QDesktopWidget>
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtCore/QTimer>
|
||||
|
||||
namespace Calls {
|
||||
namespace {
|
||||
|
||||
#if defined Q_OS_MAC && !defined OS_MAC_OLD
|
||||
#define USE_OPENGL_OVERLAY_WIDGET
|
||||
#endif // Q_OS_MAC && !OS_MAC_OLD
|
||||
|
||||
#ifdef USE_OPENGL_OVERLAY_WIDGET
|
||||
using IncomingParent = Ui::RpWidgetWrap<QOpenGLWidget>;
|
||||
#else // USE_OPENGL_OVERLAY_WIDGET
|
||||
using IncomingParent = Ui::RpWidget;
|
||||
#endif // USE_OPENGL_OVERLAY_WIDGET
|
||||
|
||||
} // namespace
|
||||
|
||||
class Panel::Incoming final : public IncomingParent {
|
||||
public:
|
||||
Incoming(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Webrtc::VideoTrack*> track);
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
void initBottomShadow();
|
||||
void fillTopShadow(QPainter &p);
|
||||
void fillBottomShadow(QPainter &p);
|
||||
|
||||
const not_null<Webrtc::VideoTrack*> _track;
|
||||
QPixmap _bottomShadow;
|
||||
|
||||
};
|
||||
|
||||
Panel::Incoming::Incoming(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Webrtc::VideoTrack*> track)
|
||||
: IncomingParent(parent)
|
||||
, _track(track) {
|
||||
initBottomShadow();
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
}
|
||||
|
||||
void Panel::Incoming::paintEvent(QPaintEvent *e) {
|
||||
QPainter p(this);
|
||||
|
||||
const auto [image, rotation] = _track->frameOriginalWithRotation();
|
||||
if (image.isNull()) {
|
||||
p.fillRect(e->rect(), Qt::black);
|
||||
} else {
|
||||
using namespace Media::View;
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
if (UsePainterRotation(rotation)) {
|
||||
if (rotation) {
|
||||
p.save();
|
||||
p.rotate(rotation);
|
||||
}
|
||||
p.drawImage(RotatedRect(rect(), rotation), image);
|
||||
if (rotation) {
|
||||
p.restore();
|
||||
}
|
||||
} else if (rotation) {
|
||||
p.drawImage(rect(), RotateFrameImage(image, rotation));
|
||||
} else {
|
||||
p.drawImage(rect(), image);
|
||||
}
|
||||
fillBottomShadow(p);
|
||||
fillTopShadow(p);
|
||||
}
|
||||
_track->markFrameShown();
|
||||
}
|
||||
|
||||
void Panel::Incoming::initBottomShadow() {
|
||||
auto image = QImage(
|
||||
QSize(1, st::callBottomShadowSize) * cIntRetinaFactor(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
const auto colorFrom = uint32(0);
|
||||
const auto colorTill = uint32(74);
|
||||
const auto rows = image.height();
|
||||
const auto step = (uint64(colorTill - colorFrom) << 32) / rows;
|
||||
auto accumulated = uint64();
|
||||
auto bytes = image.bits();
|
||||
for (auto y = 0; y != rows; ++y) {
|
||||
accumulated += step;
|
||||
const auto color = (colorFrom + uint32(accumulated >> 32)) << 24;
|
||||
for (auto x = 0; x != image.width(); ++x) {
|
||||
*(reinterpret_cast<uint32*>(bytes) + x) = color;
|
||||
}
|
||||
bytes += image.bytesPerLine();
|
||||
}
|
||||
_bottomShadow = Images::PixmapFast(std::move(image));
|
||||
}
|
||||
|
||||
void Panel::Incoming::fillTopShadow(QPainter &p) {
|
||||
#ifdef Q_OS_WIN
|
||||
const auto width = parentWidget()->width();
|
||||
const auto position = QPoint(width - st::callTitleShadow.width(), 0);
|
||||
const auto shadowArea = QRect(
|
||||
position,
|
||||
st::callTitleShadow.size());
|
||||
const auto fill = shadowArea.intersected(geometry()).translated(-pos());
|
||||
if (fill.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
p.save();
|
||||
p.setClipRect(fill);
|
||||
st::callTitleShadow.paint(p, position - pos(), width);
|
||||
p.restore();
|
||||
#endif // Q_OS_WIN
|
||||
}
|
||||
|
||||
void Panel::Incoming::fillBottomShadow(QPainter &p) {
|
||||
const auto shadowArea = QRect(
|
||||
0,
|
||||
parentWidget()->height() - st::callBottomShadowSize,
|
||||
parentWidget()->width(),
|
||||
st::callBottomShadowSize);
|
||||
const auto fill = shadowArea.intersected(geometry()).translated(-pos());
|
||||
if (fill.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
const auto factor = cIntRetinaFactor();
|
||||
p.drawPixmap(
|
||||
fill,
|
||||
_bottomShadow,
|
||||
QRect(
|
||||
0,
|
||||
factor * (fill.y() - shadowArea.translated(-pos()).y()),
|
||||
factor,
|
||||
factor * fill.height()));
|
||||
}
|
||||
|
||||
Panel::Panel(not_null<Call*> call)
|
||||
: _call(call)
|
||||
, _user(call->user())
|
||||
, _window(std::make_unique<Ui::Window>())
|
||||
#ifndef Q_OS_MAC
|
||||
, _controls(std::make_unique<Ui::Platform::TitleControls>(
|
||||
_window->body(),
|
||||
widget(),
|
||||
st::callTitle,
|
||||
[=](bool maximized) { toggleFullScreen(maximized); }))
|
||||
#endif // !Q_OS_MAC
|
||||
@@ -214,26 +86,26 @@ Panel::Panel(not_null<Call*> call)
|
||||
Panel::~Panel() = default;
|
||||
|
||||
bool Panel::isActive() const {
|
||||
return _window->isActiveWindow()
|
||||
&& _window->isVisible()
|
||||
&& !(_window->windowState() & Qt::WindowMinimized);
|
||||
return window()->isActiveWindow()
|
||||
&& window()->isVisible()
|
||||
&& !(window()->windowState() & Qt::WindowMinimized);
|
||||
}
|
||||
|
||||
void Panel::showAndActivate() {
|
||||
if (_window->isHidden()) {
|
||||
_window->show();
|
||||
if (window()->isHidden()) {
|
||||
window()->show();
|
||||
}
|
||||
const auto state = _window->windowState();
|
||||
const auto state = window()->windowState();
|
||||
if (state & Qt::WindowMinimized) {
|
||||
_window->setWindowState(state & ~Qt::WindowMinimized);
|
||||
window()->setWindowState(state & ~Qt::WindowMinimized);
|
||||
}
|
||||
_window->raise();
|
||||
_window->activateWindow();
|
||||
_window->setFocus();
|
||||
window()->raise();
|
||||
window()->activateWindow();
|
||||
window()->setFocus();
|
||||
}
|
||||
|
||||
void Panel::minimize() {
|
||||
_window->setWindowState(_window->windowState() | Qt::WindowMinimized);
|
||||
window()->setWindowState(window()->windowState() | Qt::WindowMinimized);
|
||||
}
|
||||
|
||||
void Panel::replaceCall(not_null<Call*> call) {
|
||||
@@ -242,26 +114,26 @@ void Panel::replaceCall(not_null<Call*> call) {
|
||||
}
|
||||
|
||||
void Panel::initWindow() {
|
||||
_window->setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
_window->setAttribute(Qt::WA_NoSystemBackground);
|
||||
_window->setWindowIcon(
|
||||
window()->setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
window()->setAttribute(Qt::WA_NoSystemBackground);
|
||||
window()->setWindowIcon(
|
||||
QIcon(QPixmap::fromImage(Image::Empty()->original(), Qt::ColorOnly)));
|
||||
_window->setTitle(u" "_q);
|
||||
_window->setTitleStyle(st::callTitle);
|
||||
window()->setTitle(u" "_q);
|
||||
window()->setTitleStyle(st::callTitle);
|
||||
|
||||
_window->events(
|
||||
window()->events(
|
||||
) | rpl::start_with_next([=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::Close) {
|
||||
handleClose();
|
||||
} else if (e->type() == QEvent::KeyPress) {
|
||||
if ((static_cast<QKeyEvent*>(e.get())->key() == Qt::Key_Escape)
|
||||
&& _window->isFullScreen()) {
|
||||
_window->showNormal();
|
||||
&& window()->isFullScreen()) {
|
||||
window()->showNormal();
|
||||
}
|
||||
}
|
||||
}, _window->lifetime());
|
||||
}, window()->lifetime());
|
||||
|
||||
_window->setBodyTitleArea([=](QPoint widgetPoint) {
|
||||
window()->setBodyTitleArea([=](QPoint widgetPoint) {
|
||||
using Flag = Ui::WindowTitleHitTestFlag;
|
||||
if (!widget()->rect().contains(widgetPoint)) {
|
||||
return Flag::None | Flag(0);
|
||||
@@ -287,28 +159,31 @@ void Panel::initWindow() {
|
||||
: (Flag::Move | Flag::FullScreen);
|
||||
});
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// On Windows we replace snap-to-top maximizing with fullscreen.
|
||||
//
|
||||
// We have to switch first to showNormal, so that showFullScreen
|
||||
// will remember correct normal window geometry and next showNormal
|
||||
// will show it instead of a moving maximized window.
|
||||
//
|
||||
// We have to do it in InvokeQueued, otherwise it still captures
|
||||
// the maximized window geometry and saves it.
|
||||
//
|
||||
// I couldn't find a less glitchy way to do that *sigh*.
|
||||
const auto object = _window->windowHandle();
|
||||
const auto signal = &QWindow::windowStateChanged;
|
||||
QObject::connect(object, signal, [=](Qt::WindowState state) {
|
||||
if (state == Qt::WindowMaximized) {
|
||||
InvokeQueued(object, [=] {
|
||||
_window->showNormal();
|
||||
_window->showFullScreen();
|
||||
});
|
||||
}
|
||||
});
|
||||
#endif // Q_OS_WIN
|
||||
// Don't do that, it looks awful :(
|
||||
//#ifdef Q_OS_WIN
|
||||
// // On Windows we replace snap-to-top maximizing with fullscreen.
|
||||
// //
|
||||
// // We have to switch first to showNormal, so that showFullScreen
|
||||
// // will remember correct normal window geometry and next showNormal
|
||||
// // will show it instead of a moving maximized window.
|
||||
// //
|
||||
// // We have to do it in InvokeQueued, otherwise it still captures
|
||||
// // the maximized window geometry and saves it.
|
||||
// //
|
||||
// // I couldn't find a less glitchy way to do that *sigh*.
|
||||
// const auto object = window()->windowHandle();
|
||||
// const auto signal = &QWindow::windowStateChanged;
|
||||
// QObject::connect(object, signal, [=](Qt::WindowState state) {
|
||||
// if (state == Qt::WindowMaximized) {
|
||||
// InvokeQueued(object, [=] {
|
||||
// window()->showNormal();
|
||||
// InvokeQueued(object, [=] {
|
||||
// window()->showFullScreen();
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
//#endif // Q_OS_WIN
|
||||
}
|
||||
|
||||
void Panel::initWidget() {
|
||||
@@ -392,7 +267,7 @@ void Panel::refreshIncomingGeometry() {
|
||||
Expects(_incoming != nullptr);
|
||||
|
||||
if (_incomingFrameSize.isEmpty()) {
|
||||
_incoming->hide();
|
||||
_incoming->widget()->hide();
|
||||
return;
|
||||
}
|
||||
const auto to = widget()->size();
|
||||
@@ -401,7 +276,7 @@ void Panel::refreshIncomingGeometry() {
|
||||
to,
|
||||
Qt::KeepAspectRatioByExpanding);
|
||||
|
||||
// If we cut out no more than 0.33 of the original, let's use expanding.
|
||||
// If we cut out no more than 0.25 of the original, let's use expanding.
|
||||
const auto use = ((big.width() * 3 <= to.width() * 4)
|
||||
&& (big.height() * 3 <= to.height() * 4))
|
||||
? big
|
||||
@@ -409,8 +284,8 @@ void Panel::refreshIncomingGeometry() {
|
||||
const auto pos = QPoint(
|
||||
(to.width() - use.width()) / 2,
|
||||
(to.height() - use.height()) / 2);
|
||||
_incoming->setGeometry(QRect(pos, use));
|
||||
_incoming->show();
|
||||
_incoming->widget()->setGeometry(QRect(pos, use));
|
||||
_incoming->widget()->show();
|
||||
}
|
||||
|
||||
void Panel::reinitWithCall(Call *call) {
|
||||
@@ -446,8 +321,9 @@ void Panel::reinitWithCall(Call *call) {
|
||||
_call->videoOutgoing());
|
||||
_incoming = std::make_unique<Incoming>(
|
||||
widget(),
|
||||
_call->videoIncoming());
|
||||
_incoming->hide();
|
||||
_call->videoIncoming(),
|
||||
_window.backend());
|
||||
_incoming->widget()->hide();
|
||||
|
||||
_call->mutedValue(
|
||||
) | rpl::start_with_next([=](bool mute) {
|
||||
@@ -474,28 +350,34 @@ void Panel::reinitWithCall(Call *call) {
|
||||
_call->videoIncoming()->renderNextFrame(
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto track = _call->videoIncoming();
|
||||
const auto [frame, rotation] = track->frameOriginalWithRotation();
|
||||
setIncomingSize((rotation == 90 || rotation == 270)
|
||||
? QSize(frame.height(), frame.width())
|
||||
: frame.size());
|
||||
if (_incoming->isHidden()) {
|
||||
setIncomingSize(track->state() == Webrtc::VideoState::Active
|
||||
? track->frameSize()
|
||||
: QSize());
|
||||
if (_incoming->widget()->isHidden()) {
|
||||
return;
|
||||
}
|
||||
const auto incoming = incomingFrameGeometry();
|
||||
const auto outgoing = outgoingFrameGeometry();
|
||||
_incoming->update();
|
||||
_incoming->widget()->update();
|
||||
if (incoming.intersects(outgoing)) {
|
||||
widget()->update(outgoing);
|
||||
}
|
||||
}, _callLifetime);
|
||||
|
||||
_call->videoIncoming()->stateValue(
|
||||
) | rpl::start_with_next([=](Webrtc::VideoState state) {
|
||||
setIncomingSize((state == Webrtc::VideoState::Active)
|
||||
? _call->videoIncoming()->frameSize()
|
||||
: QSize());
|
||||
}, _callLifetime);
|
||||
|
||||
_call->videoOutgoing()->renderNextFrame(
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto incoming = incomingFrameGeometry();
|
||||
const auto outgoing = outgoingFrameGeometry();
|
||||
widget()->update(outgoing);
|
||||
if (incoming.intersects(outgoing)) {
|
||||
_incoming->update();
|
||||
_incoming->widget()->update();
|
||||
}
|
||||
}, _callLifetime);
|
||||
|
||||
@@ -539,7 +421,13 @@ void Panel::reinitWithCall(Call *call) {
|
||||
_name->setText(_user->name);
|
||||
updateStatusText(_call->state());
|
||||
|
||||
_incoming->lower();
|
||||
_answerHangupRedial->raise();
|
||||
_decline->raise();
|
||||
_cancel->raise();
|
||||
_camera->raise();
|
||||
_mute->raise();
|
||||
|
||||
_incoming->widget()->lower();
|
||||
}
|
||||
|
||||
void Panel::createRemoteAudioMute() {
|
||||
@@ -604,7 +492,7 @@ void Panel::showControls() {
|
||||
_cancel->setVisible(_cancel->toggled());
|
||||
|
||||
const auto shown = !_incomingFrameSize.isEmpty();
|
||||
_incoming->setVisible(shown);
|
||||
_incoming->widget()->setVisible(shown);
|
||||
_name->setVisible(!shown);
|
||||
_status->setVisible(!shown);
|
||||
_userpic->setVisible(!shown);
|
||||
@@ -614,16 +502,20 @@ void Panel::showControls() {
|
||||
}
|
||||
|
||||
void Panel::closeBeforeDestroy() {
|
||||
_window->close();
|
||||
window()->close();
|
||||
reinitWithCall(nullptr);
|
||||
}
|
||||
|
||||
rpl::lifetime &Panel::lifetime() {
|
||||
return window()->lifetime();
|
||||
}
|
||||
|
||||
void Panel::initGeometry() {
|
||||
const auto center = Core::App().getPointForCallPanelCenter();
|
||||
const auto initRect = QRect(0, 0, st::callWidth, st::callHeight);
|
||||
_window->setGeometry(initRect.translated(center - initRect.center()));
|
||||
_window->setMinimumSize({ st::callWidthMin, st::callHeightMin });
|
||||
_window->show();
|
||||
window()->setGeometry(initRect.translated(center - initRect.center()));
|
||||
window()->setMinimumSize({ st::callWidthMin, st::callHeightMin });
|
||||
window()->show();
|
||||
updateControlsGeometry();
|
||||
}
|
||||
|
||||
@@ -641,16 +533,16 @@ void Panel::refreshOutgoingPreviewInBody(State state) {
|
||||
|
||||
void Panel::toggleFullScreen(bool fullscreen) {
|
||||
if (fullscreen) {
|
||||
_window->showFullScreen();
|
||||
window()->showFullScreen();
|
||||
} else {
|
||||
_window->showNormal();
|
||||
window()->showNormal();
|
||||
}
|
||||
}
|
||||
|
||||
QRect Panel::incomingFrameGeometry() const {
|
||||
return (!_incoming || _incoming->isHidden())
|
||||
return (!_incoming || _incoming->widget()->isHidden())
|
||||
? QRect()
|
||||
: _incoming->geometry();
|
||||
: _incoming->widget()->geometry();
|
||||
}
|
||||
|
||||
QRect Panel::outgoingFrameGeometry() const {
|
||||
@@ -666,15 +558,31 @@ void Panel::updateControlsGeometry() {
|
||||
}
|
||||
if (_fingerprint) {
|
||||
#ifndef Q_OS_MAC
|
||||
const auto minRight = _controls->geometry().width()
|
||||
+ st::callFingerprintTop;
|
||||
const auto controlsGeometry = _controls->geometry();
|
||||
const auto halfWidth = widget()->width() / 2;
|
||||
const auto minLeft = (controlsGeometry.center().x() < halfWidth)
|
||||
? (controlsGeometry.width() + st::callFingerprintTop)
|
||||
: 0;
|
||||
const auto minRight = (controlsGeometry.center().x() >= halfWidth)
|
||||
? (controlsGeometry.width() + st::callFingerprintTop)
|
||||
: 0;
|
||||
_incoming->setControlsAlignment(minLeft
|
||||
? style::al_left
|
||||
: style::al_right);
|
||||
#else // !Q_OS_MAC
|
||||
const auto minLeft = 0;
|
||||
const auto minRight = 0;
|
||||
#endif // _controls
|
||||
const auto desired = (widget()->width() - _fingerprint->width()) / 2;
|
||||
_fingerprint->moveToRight(
|
||||
std::max(desired, minRight),
|
||||
st::callFingerprintTop);
|
||||
if (minLeft) {
|
||||
_fingerprint->moveToLeft(
|
||||
std::max(desired, minLeft),
|
||||
st::callFingerprintTop);
|
||||
} else {
|
||||
_fingerprint->moveToRight(
|
||||
std::max(desired, minRight),
|
||||
st::callFingerprintTop);
|
||||
}
|
||||
}
|
||||
const auto innerHeight = std::max(widget()->height(), st::callHeightMin);
|
||||
const auto innerWidth = widget()->width() - 2 * st::callInnerPadding;
|
||||
@@ -786,13 +694,13 @@ void Panel::paint(QRect clip) {
|
||||
Painter p(widget());
|
||||
|
||||
auto region = QRegion(clip);
|
||||
if (!_incoming->isHidden()) {
|
||||
region = region.subtracted(QRegion(_incoming->geometry()));
|
||||
if (!_incoming->widget()->isHidden()) {
|
||||
region = region.subtracted(QRegion(_incoming->widget()->geometry()));
|
||||
}
|
||||
for (const auto rect : region) {
|
||||
p.fillRect(rect, st::callBgOpaque);
|
||||
}
|
||||
if (_incoming && _incoming->isHidden()) {
|
||||
if (_incoming && _incoming->widget()->isHidden()) {
|
||||
_call->videoIncoming()->markFrameShown();
|
||||
}
|
||||
}
|
||||
@@ -803,8 +711,12 @@ void Panel::handleClose() {
|
||||
}
|
||||
}
|
||||
|
||||
not_null<Ui::Window*> Panel::window() const {
|
||||
return _window.window();
|
||||
}
|
||||
|
||||
not_null<Ui::RpWidget*> Panel::widget() const {
|
||||
return _window->body();
|
||||
return _window.widget();
|
||||
}
|
||||
|
||||
void Panel::stateChanged(State state) {
|
||||
@@ -820,7 +732,7 @@ void Panel::stateChanged(State state) {
|
||||
auto toggleButton = [&](auto &&button, bool visible) {
|
||||
button->toggle(
|
||||
visible,
|
||||
_window->isHidden()
|
||||
window()->isHidden()
|
||||
? anim::type::instant
|
||||
: anim::type::normal);
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/object_ptr.h"
|
||||
#include "calls/calls_call.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/gl/gl_window.h"
|
||||
#include "ui/rp_widget.h"
|
||||
|
||||
class Image;
|
||||
@@ -30,6 +31,9 @@ class FadeWrap;
|
||||
template <typename Widget>
|
||||
class PaddingWrap;
|
||||
class Window;
|
||||
namespace GL {
|
||||
enum class Backend;
|
||||
} // namespace GL
|
||||
namespace Platform {
|
||||
class TitleControls;
|
||||
} // namespace Platform
|
||||
@@ -57,6 +61,8 @@ public:
|
||||
void replaceCall(not_null<Call*> call);
|
||||
void closeBeforeDestroy();
|
||||
|
||||
rpl::lifetime &lifetime();
|
||||
|
||||
private:
|
||||
class Incoming;
|
||||
using State = Call::State;
|
||||
@@ -67,6 +73,7 @@ private:
|
||||
Redial,
|
||||
};
|
||||
|
||||
[[nodiscard]] not_null<Ui::Window*> window() const;
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> widget() const;
|
||||
|
||||
void paint(QRect clip);
|
||||
@@ -80,9 +87,6 @@ private:
|
||||
|
||||
void handleClose();
|
||||
|
||||
QRect signalBarsRect() const;
|
||||
void paintSignalBarsBg(Painter &p);
|
||||
|
||||
void updateControlsGeometry();
|
||||
void updateHangupGeometry();
|
||||
void updateStatusGeometry();
|
||||
@@ -105,7 +109,7 @@ private:
|
||||
Call *_call = nullptr;
|
||||
not_null<UserData*> _user;
|
||||
|
||||
const std::unique_ptr<Ui::Window> _window;
|
||||
Ui::GL::Window _window;
|
||||
std::unique_ptr<Incoming> _incoming;
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
|
||||
@@ -22,7 +22,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "calls/calls_call.h"
|
||||
#include "calls/calls_instance.h"
|
||||
#include "calls/calls_signal_bars.h"
|
||||
#include "calls/calls_group_menu.h" // Group::LeaveBox.
|
||||
#include "calls/group/calls_group_call.h"
|
||||
#include "calls/group/calls_group_menu.h" // Group::LeaveBox.
|
||||
#include "history/view/history_view_group_call_tracker.h" // ContentByCall.
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_group_call.h"
|
||||
@@ -279,8 +280,7 @@ void TopBar::initControls() {
|
||||
if (const auto call = _call.get()) {
|
||||
call->setMuted(!call->muted());
|
||||
} else if (const auto group = _groupCall.get()) {
|
||||
if (group->muted() == MuteState::ForceMuted
|
||||
|| group->muted() == MuteState::RaisedHand) {
|
||||
if (group->mutedByAdmin()) {
|
||||
Ui::Toast::Show(tr::lng_group_call_force_muted_sub(tr::now));
|
||||
} else {
|
||||
group->setMuted((group->muted() == MuteState::Muted)
|
||||
|
||||
576
Telegram/SourceFiles/calls/calls_video_incoming.cpp
Normal file
@@ -0,0 +1,576 @@
|
||||
/*
|
||||
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 "calls/calls_video_incoming.h"
|
||||
|
||||
#include "ui/gl/gl_surface.h"
|
||||
#include "ui/gl/gl_shader.h"
|
||||
#include "ui/gl/gl_image.h"
|
||||
#include "ui/gl/gl_primitives.h"
|
||||
#include "media/view/media_view_pip.h"
|
||||
#include "webrtc/webrtc_video_track.h"
|
||||
#include "styles/style_calls.h"
|
||||
|
||||
#include <QtGui/QOpenGLShader>
|
||||
#include <QtGui/QOpenGLBuffer>
|
||||
|
||||
namespace Calls {
|
||||
namespace {
|
||||
|
||||
constexpr auto kBottomShadowAlphaMax = 74;
|
||||
|
||||
using namespace Ui::GL;
|
||||
|
||||
[[nodiscard]] ShaderPart FragmentBottomShadow() {
|
||||
return {
|
||||
.header = R"(
|
||||
uniform vec3 shadow; // fullHeight, shadowTop, maxOpacity
|
||||
)",
|
||||
.body = R"(
|
||||
float shadowCoord = shadow.y - gl_FragCoord.y;
|
||||
float shadowValue = clamp(shadowCoord / shadow.x, 0., 1.);
|
||||
float shadowShown = shadowValue * shadow.z;
|
||||
result = vec4(min(result.rgb, vec3(1.)) * (1. - shadowShown), result.a);
|
||||
)",
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class Panel::Incoming::RendererGL final : public Ui::GL::Renderer {
|
||||
public:
|
||||
explicit RendererGL(not_null<Incoming*> owner);
|
||||
|
||||
void init(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
QOpenGLFunctions &f) override;
|
||||
|
||||
void deinit(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
QOpenGLFunctions &f) override;
|
||||
|
||||
void paint(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
QOpenGLFunctions &f) override;
|
||||
|
||||
private:
|
||||
void uploadTexture(
|
||||
QOpenGLFunctions &f,
|
||||
GLint internalformat,
|
||||
GLint format,
|
||||
QSize size,
|
||||
QSize hasSize,
|
||||
int stride,
|
||||
const void *data) const;
|
||||
void validateShadowImage();
|
||||
|
||||
const not_null<Incoming*> _owner;
|
||||
|
||||
QSize _viewport;
|
||||
float _factor = 1.;
|
||||
QVector2D _uniformViewport;
|
||||
|
||||
std::optional<QOpenGLBuffer> _contentBuffer;
|
||||
std::optional<QOpenGLShaderProgram> _argb32Program;
|
||||
QOpenGLShader *_texturedVertexShader = nullptr;
|
||||
std::optional<QOpenGLShaderProgram> _yuv420Program;
|
||||
std::optional<QOpenGLShaderProgram> _imageProgram;
|
||||
Ui::GL::Textures<4> _textures;
|
||||
QSize _rgbaSize;
|
||||
QSize _lumaSize;
|
||||
QSize _chromaSize;
|
||||
int _trackFrameIndex = 0;
|
||||
|
||||
Ui::GL::Image _controlsShadowImage;
|
||||
QRect _controlsShadowLeft;
|
||||
QRect _controlsShadowRight;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
class Panel::Incoming::RendererSW final : public Ui::GL::Renderer {
|
||||
public:
|
||||
explicit RendererSW(not_null<Incoming*> owner);
|
||||
|
||||
void paintFallback(
|
||||
Painter &&p,
|
||||
const QRegion &clip,
|
||||
Ui::GL::Backend backend) override;
|
||||
|
||||
private:
|
||||
void initBottomShadow();
|
||||
void fillTopShadow(QPainter &p);
|
||||
void fillBottomShadow(QPainter &p);
|
||||
|
||||
const not_null<Incoming*> _owner;
|
||||
|
||||
QImage _bottomShadow;
|
||||
|
||||
};
|
||||
|
||||
Panel::Incoming::RendererGL::RendererGL(not_null<Incoming*> owner)
|
||||
: _owner(owner) {
|
||||
style::PaletteChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
_controlsShadowImage.invalidate();
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void Panel::Incoming::RendererGL::init(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
QOpenGLFunctions &f) {
|
||||
constexpr auto kQuads = 2;
|
||||
constexpr auto kQuadVertices = kQuads * 4;
|
||||
constexpr auto kQuadValues = kQuadVertices * 4;
|
||||
|
||||
_contentBuffer.emplace();
|
||||
_contentBuffer->setUsagePattern(QOpenGLBuffer::DynamicDraw);
|
||||
_contentBuffer->create();
|
||||
_contentBuffer->bind();
|
||||
_contentBuffer->allocate(kQuadValues * sizeof(GLfloat));
|
||||
|
||||
_textures.ensureCreated(f);
|
||||
|
||||
_imageProgram.emplace();
|
||||
_texturedVertexShader = LinkProgram(
|
||||
&*_imageProgram,
|
||||
VertexShader({
|
||||
VertexViewportTransform(),
|
||||
VertexPassTextureCoord(),
|
||||
}),
|
||||
FragmentShader({
|
||||
FragmentSampleARGB32Texture(),
|
||||
})).vertex;
|
||||
|
||||
_argb32Program.emplace();
|
||||
LinkProgram(
|
||||
&*_argb32Program,
|
||||
_texturedVertexShader,
|
||||
FragmentShader({
|
||||
FragmentSampleARGB32Texture(),
|
||||
FragmentBottomShadow(),
|
||||
}));
|
||||
|
||||
_yuv420Program.emplace();
|
||||
LinkProgram(
|
||||
&*_yuv420Program,
|
||||
_texturedVertexShader,
|
||||
FragmentShader({
|
||||
FragmentSampleYUV420Texture(),
|
||||
FragmentBottomShadow(),
|
||||
}));
|
||||
}
|
||||
|
||||
void Panel::Incoming::RendererGL::deinit(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
QOpenGLFunctions &f) {
|
||||
_textures.destroy(f);
|
||||
_imageProgram = std::nullopt;
|
||||
_texturedVertexShader = nullptr;
|
||||
_argb32Program = std::nullopt;
|
||||
_yuv420Program = std::nullopt;
|
||||
_contentBuffer = std::nullopt;
|
||||
}
|
||||
|
||||
void Panel::Incoming::RendererGL::paint(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
QOpenGLFunctions &f) {
|
||||
const auto markGuard = gsl::finally([&] {
|
||||
_owner->_track->markFrameShown();
|
||||
});
|
||||
const auto data = _owner->_track->frameWithInfo(false);
|
||||
if (data.format == Webrtc::FrameFormat::None) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto factor = widget->devicePixelRatio();
|
||||
if (_factor != factor) {
|
||||
_factor = factor;
|
||||
_controlsShadowImage.invalidate();
|
||||
}
|
||||
_viewport = widget->size();
|
||||
_uniformViewport = QVector2D(
|
||||
_viewport.width() * _factor,
|
||||
_viewport.height() * _factor);
|
||||
|
||||
const auto rgbaFrame = (data.format == Webrtc::FrameFormat::ARGB32);
|
||||
const auto upload = (_trackFrameIndex != data.index);
|
||||
_trackFrameIndex = data.index;
|
||||
auto &program = rgbaFrame ? _argb32Program : _yuv420Program;
|
||||
program->bind();
|
||||
if (rgbaFrame) {
|
||||
Assert(!data.original.isNull());
|
||||
f.glActiveTexture(GL_TEXTURE0);
|
||||
_textures.bind(f, 0);
|
||||
if (upload) {
|
||||
uploadTexture(
|
||||
f,
|
||||
GL_RGBA,
|
||||
GL_RGBA,
|
||||
data.original.size(),
|
||||
_rgbaSize,
|
||||
data.original.bytesPerLine() / 4,
|
||||
data.original.constBits());
|
||||
_rgbaSize = data.original.size();
|
||||
}
|
||||
program->setUniformValue("s_texture", GLint(0));
|
||||
} else {
|
||||
Assert(data.format == Webrtc::FrameFormat::YUV420);
|
||||
Assert(!data.yuv420->size.isEmpty());
|
||||
const auto yuv = data.yuv420;
|
||||
|
||||
f.glActiveTexture(GL_TEXTURE0);
|
||||
_textures.bind(f, 1);
|
||||
if (upload) {
|
||||
f.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
uploadTexture(
|
||||
f,
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
yuv->size,
|
||||
_lumaSize,
|
||||
yuv->y.stride,
|
||||
yuv->y.data);
|
||||
_lumaSize = yuv->size;
|
||||
}
|
||||
f.glActiveTexture(GL_TEXTURE1);
|
||||
_textures.bind(f, 2);
|
||||
if (upload) {
|
||||
uploadTexture(
|
||||
f,
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
yuv->chromaSize,
|
||||
_chromaSize,
|
||||
yuv->u.stride,
|
||||
yuv->u.data);
|
||||
}
|
||||
f.glActiveTexture(GL_TEXTURE2);
|
||||
_textures.bind(f, 3);
|
||||
if (upload) {
|
||||
uploadTexture(
|
||||
f,
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
yuv->chromaSize,
|
||||
_chromaSize,
|
||||
yuv->v.stride,
|
||||
yuv->v.data);
|
||||
_chromaSize = yuv->chromaSize;
|
||||
f.glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
}
|
||||
program->setUniformValue("y_texture", GLint(0));
|
||||
program->setUniformValue("u_texture", GLint(1));
|
||||
program->setUniformValue("v_texture", GLint(2));
|
||||
}
|
||||
const auto rect = TransformRect(
|
||||
widget->rect(),
|
||||
_viewport,
|
||||
_factor);
|
||||
std::array<std::array<GLfloat, 2>, 4> texcoords = { {
|
||||
{ { 0.f, 1.f } },
|
||||
{ { 1.f, 1.f } },
|
||||
{ { 1.f, 0.f } },
|
||||
{ { 0.f, 0.f } },
|
||||
} };
|
||||
if (const auto shift = (data.rotation / 90); shift != 0) {
|
||||
std::rotate(
|
||||
begin(texcoords),
|
||||
begin(texcoords) + shift,
|
||||
end(texcoords));
|
||||
}
|
||||
|
||||
const auto width = widget->parentWidget()->width();
|
||||
const auto left = (_owner->_topControlsAlignment == style::al_left);
|
||||
validateShadowImage();
|
||||
const auto position = left
|
||||
? QPoint()
|
||||
: QPoint(width - st::callTitleShadowRight.width(), 0);
|
||||
const auto translated = position - widget->pos();
|
||||
const auto shadowArea = QRect(translated, st::callTitleShadowLeft.size());
|
||||
const auto shadow = _controlsShadowImage.texturedRect(
|
||||
shadowArea,
|
||||
(left ? _controlsShadowLeft : _controlsShadowRight),
|
||||
widget->rect());
|
||||
const auto shadowRect = TransformRect(
|
||||
shadow.geometry,
|
||||
_viewport,
|
||||
_factor);
|
||||
|
||||
const GLfloat coords[] = {
|
||||
rect.left(), rect.top(),
|
||||
texcoords[0][0], texcoords[0][1],
|
||||
|
||||
rect.right(), rect.top(),
|
||||
texcoords[1][0], texcoords[1][1],
|
||||
|
||||
rect.right(), rect.bottom(),
|
||||
texcoords[2][0], texcoords[2][1],
|
||||
|
||||
rect.left(), rect.bottom(),
|
||||
texcoords[3][0], texcoords[3][1],
|
||||
|
||||
shadowRect.left(), shadowRect.top(),
|
||||
shadow.texture.left(), shadow.texture.bottom(),
|
||||
|
||||
shadowRect.right(), shadowRect.top(),
|
||||
shadow.texture.right(), shadow.texture.bottom(),
|
||||
|
||||
shadowRect.right(), shadowRect.bottom(),
|
||||
shadow.texture.right(), shadow.texture.top(),
|
||||
|
||||
shadowRect.left(), shadowRect.bottom(),
|
||||
shadow.texture.left(), shadow.texture.top(),
|
||||
};
|
||||
|
||||
_contentBuffer->write(0, coords, sizeof(coords));
|
||||
|
||||
const auto bottomShadowArea = QRect(
|
||||
0,
|
||||
widget->parentWidget()->height() - st::callBottomShadowSize,
|
||||
widget->parentWidget()->width(),
|
||||
st::callBottomShadowSize);
|
||||
const auto bottomShadowFill = bottomShadowArea.intersected(
|
||||
widget->geometry()).translated(-widget->pos());
|
||||
const auto shadowHeight = bottomShadowFill.height();
|
||||
const auto shadowAlpha = (shadowHeight * kBottomShadowAlphaMax)
|
||||
/ (st::callBottomShadowSize * 255.);
|
||||
|
||||
program->setUniformValue("viewport", _uniformViewport);
|
||||
program->setUniformValue("shadow", QVector3D(
|
||||
shadowHeight * _factor,
|
||||
TransformRect(bottomShadowFill, _viewport, _factor).bottom(),
|
||||
shadowAlpha));
|
||||
|
||||
FillTexturedRectangle(f, &*program);
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
if (!shadowRect.empty()) {
|
||||
f.glEnable(GL_BLEND);
|
||||
f.glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
const auto guard = gsl::finally([&] {
|
||||
f.glDisable(GL_BLEND);
|
||||
});
|
||||
|
||||
_imageProgram->bind();
|
||||
_imageProgram->setUniformValue("viewport", _uniformViewport);
|
||||
_imageProgram->setUniformValue("s_texture", GLint(0));
|
||||
|
||||
f.glActiveTexture(GL_TEXTURE0);
|
||||
_controlsShadowImage.bind(f);
|
||||
|
||||
FillTexturedRectangle(f, &*_imageProgram, 4);
|
||||
}
|
||||
#endif // Q_OS_MAC
|
||||
}
|
||||
|
||||
void Panel::Incoming::RendererGL::validateShadowImage() {
|
||||
if (_controlsShadowImage) {
|
||||
return;
|
||||
}
|
||||
const auto size = st::callTitleShadowLeft.size();
|
||||
const auto full = QSize(size.width(), 2 * size.height()) * int(_factor);
|
||||
auto image = QImage(full, QImage::Format_ARGB32_Premultiplied);
|
||||
image.setDevicePixelRatio(_factor);
|
||||
image.fill(Qt::transparent);
|
||||
{
|
||||
auto p = QPainter(&image);
|
||||
st::callTitleShadowLeft.paint(p, 0, 0, size.width());
|
||||
_controlsShadowLeft = QRect(0, 0, full.width(), full.height() / 2);
|
||||
st::callTitleShadowRight.paint(p, 0, size.height(), size.width());
|
||||
_controlsShadowRight = QRect(
|
||||
0,
|
||||
full.height() / 2,
|
||||
full.width(),
|
||||
full.height() / 2);
|
||||
}
|
||||
_controlsShadowImage.setImage(std::move(image));
|
||||
}
|
||||
|
||||
void Panel::Incoming::RendererGL::uploadTexture(
|
||||
QOpenGLFunctions &f,
|
||||
GLint internalformat,
|
||||
GLint format,
|
||||
QSize size,
|
||||
QSize hasSize,
|
||||
int stride,
|
||||
const void *data) const {
|
||||
f.glPixelStorei(GL_UNPACK_ROW_LENGTH, stride);
|
||||
if (hasSize != size) {
|
||||
f.glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
internalformat,
|
||||
size.width(),
|
||||
size.height(),
|
||||
0,
|
||||
format,
|
||||
GL_UNSIGNED_BYTE,
|
||||
data);
|
||||
} else {
|
||||
f.glTexSubImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
size.width(),
|
||||
size.height(),
|
||||
format,
|
||||
GL_UNSIGNED_BYTE,
|
||||
data);
|
||||
}
|
||||
f.glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
}
|
||||
|
||||
Panel::Incoming::RendererSW::RendererSW(not_null<Incoming*> owner)
|
||||
: _owner(owner) {
|
||||
initBottomShadow();
|
||||
}
|
||||
|
||||
void Panel::Incoming::RendererSW::paintFallback(
|
||||
Painter &&p,
|
||||
const QRegion &clip,
|
||||
Ui::GL::Backend backend) {
|
||||
const auto markGuard = gsl::finally([&] {
|
||||
_owner->_track->markFrameShown();
|
||||
});
|
||||
const auto data = _owner->_track->frameWithInfo(true);
|
||||
const auto &image = data.original;
|
||||
const auto rotation = data.rotation;
|
||||
if (image.isNull()) {
|
||||
p.fillRect(clip.boundingRect(), Qt::black);
|
||||
} else {
|
||||
const auto rect = _owner->widget()->rect();
|
||||
using namespace Media::View;
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
if (UsePainterRotation(rotation)) {
|
||||
if (rotation) {
|
||||
p.save();
|
||||
p.rotate(rotation);
|
||||
}
|
||||
p.drawImage(RotatedRect(rect, rotation), image);
|
||||
if (rotation) {
|
||||
p.restore();
|
||||
}
|
||||
} else if (rotation) {
|
||||
p.drawImage(rect, RotateFrameImage(image, rotation));
|
||||
} else {
|
||||
p.drawImage(rect, image);
|
||||
}
|
||||
fillBottomShadow(p);
|
||||
fillTopShadow(p);
|
||||
}
|
||||
}
|
||||
|
||||
void Panel::Incoming::RendererSW::initBottomShadow() {
|
||||
auto image = QImage(
|
||||
QSize(1, st::callBottomShadowSize) * cIntRetinaFactor(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
const auto colorFrom = uint32(0);
|
||||
const auto colorTill = uint32(kBottomShadowAlphaMax);
|
||||
const auto rows = image.height();
|
||||
const auto step = (uint64(colorTill - colorFrom) << 32) / rows;
|
||||
auto accumulated = uint64();
|
||||
auto bytes = image.bits();
|
||||
for (auto y = 0; y != rows; ++y) {
|
||||
accumulated += step;
|
||||
const auto color = (colorFrom + uint32(accumulated >> 32)) << 24;
|
||||
for (auto x = 0; x != image.width(); ++x) {
|
||||
*(reinterpret_cast<uint32*>(bytes) + x) = color;
|
||||
}
|
||||
bytes += image.bytesPerLine();
|
||||
}
|
||||
_bottomShadow = std::move(image);
|
||||
}
|
||||
|
||||
void Panel::Incoming::RendererSW::fillTopShadow(QPainter &p) {
|
||||
#ifndef Q_OS_MAC
|
||||
const auto widget = _owner->widget();
|
||||
const auto width = widget->parentWidget()->width();
|
||||
const auto left = (_owner->_topControlsAlignment == style::al_left);
|
||||
const auto &icon = left
|
||||
? st::callTitleShadowLeft
|
||||
: st::callTitleShadowRight;
|
||||
const auto position = left
|
||||
? QPoint()
|
||||
: QPoint(width - icon.width(), 0);
|
||||
const auto shadowArea = QRect(position, icon.size());
|
||||
const auto fill = shadowArea.intersected(
|
||||
widget->geometry()).translated(-widget->pos());
|
||||
if (fill.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
p.save();
|
||||
p.setClipRect(fill);
|
||||
icon.paint(p, position - widget->pos(), width);
|
||||
p.restore();
|
||||
#endif // Q_OS_MAC
|
||||
}
|
||||
|
||||
void Panel::Incoming::RendererSW::fillBottomShadow(QPainter &p) {
|
||||
const auto widget = _owner->widget();
|
||||
const auto shadowArea = QRect(
|
||||
0,
|
||||
widget->parentWidget()->height() - st::callBottomShadowSize,
|
||||
widget->parentWidget()->width(),
|
||||
st::callBottomShadowSize);
|
||||
const auto fill = shadowArea.intersected(
|
||||
widget->geometry()).translated(-widget->pos());
|
||||
if (fill.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
const auto factor = cIntRetinaFactor();
|
||||
p.drawImage(
|
||||
fill,
|
||||
_bottomShadow,
|
||||
QRect(
|
||||
0,
|
||||
(factor
|
||||
* (fill.y() - shadowArea.translated(-widget->pos()).y())),
|
||||
factor,
|
||||
factor * fill.height()));
|
||||
}
|
||||
|
||||
Panel::Incoming::Incoming(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Webrtc::VideoTrack*> track,
|
||||
Ui::GL::Backend backend)
|
||||
: _surface(Ui::GL::CreateSurface(parent, chooseRenderer(backend)))
|
||||
, _track(track) {
|
||||
widget()->setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
widget()->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
}
|
||||
|
||||
not_null<QWidget*> Panel::Incoming::widget() const {
|
||||
return _surface->rpWidget();
|
||||
}
|
||||
|
||||
not_null<Ui::RpWidgetWrap*> Panel::Incoming::rp() const {
|
||||
return _surface.get();
|
||||
}
|
||||
|
||||
void Panel::Incoming::setControlsAlignment(style::align align) {
|
||||
if (_topControlsAlignment != align) {
|
||||
_topControlsAlignment = align;
|
||||
widget()->update();
|
||||
}
|
||||
}
|
||||
|
||||
Ui::GL::ChosenRenderer Panel::Incoming::chooseRenderer(
|
||||
Ui::GL::Backend backend) {
|
||||
_opengl = (backend == Ui::GL::Backend::OpenGL);
|
||||
return {
|
||||
.renderer = (_opengl
|
||||
? std::unique_ptr<Ui::GL::Renderer>(
|
||||
std::make_unique<RendererGL>(this))
|
||||
: std::make_unique<RendererSW>(this)),
|
||||
.backend = backend,
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Calls
|
||||
45
Telegram/SourceFiles/calls/calls_video_incoming.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "calls/calls_panel.h"
|
||||
|
||||
namespace Ui::GL {
|
||||
enum class Backend;
|
||||
struct ChosenRenderer;
|
||||
} // namespace Ui::GL
|
||||
|
||||
namespace Calls {
|
||||
|
||||
class Panel::Incoming final {
|
||||
public:
|
||||
Incoming(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Webrtc::VideoTrack*> track,
|
||||
Ui::GL::Backend backend);
|
||||
|
||||
[[nodiscard]] not_null<QWidget*> widget() const;
|
||||
[[nodiscard]] not_null<Ui::RpWidgetWrap*> rp() const;
|
||||
|
||||
void setControlsAlignment(style::align align);
|
||||
|
||||
private:
|
||||
class RendererGL;
|
||||
class RendererSW;
|
||||
|
||||
[[nodiscard]] Ui::GL::ChosenRenderer chooseRenderer(
|
||||
Ui::GL::Backend backend);
|
||||
|
||||
const std::unique_ptr<Ui::RpWidgetWrap> _surface;
|
||||
const not_null<Webrtc::VideoTrack*> _track;
|
||||
style::align _topControlsAlignment = style::al_left;
|
||||
bool _opengl = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Calls
|
||||
@@ -5,10 +5,10 @@ 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 "calls/calls_choose_join_as.h"
|
||||
#include "calls/group/calls_choose_join_as.h"
|
||||
|
||||
#include "calls/calls_group_common.h"
|
||||
#include "calls/calls_group_menu.h"
|
||||
#include "calls/group/calls_group_common.h"
|
||||
#include "calls/group/calls_group_menu.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_channel.h"
|
||||