Compare commits

..

339 Commits

Author SHA1 Message Date
John Preston
a0c2318919 Beta version 2.7.7.
- Optimized video playback in media viewer and Picture-in-Picture mode.
- Added integration with System Media Transport Controls on Windows 10.
- Added "Now Playing" integration for music playback on macOS.
- Added "Archive Sticker" into the "..." menu of the Sticker Set Box.
- Fixed memory not being freed on Linux.
- Several crash fixes.
2021-06-11 17:37:49 +04:00
John Preston
8d8fffd306 Fix OpenGL renderers on macOS. 2021-06-11 17:35:16 +04:00
John Preston
ecb53e3e0b Display Puny-encoded domains in proxy confirmation. 2021-06-11 14:20:00 +04:00
John Preston
468d43c4c7 Don't send by Enter elements, chosen by mouse. 2021-06-11 14:20:00 +04:00
John Preston
425a632965 Accept DD / EE as proxy type tag.
Fixes #16218.
2021-06-11 14:20:00 +04:00
John Preston
bf581a1ba4 Fix build with DESKTOP_APP_DISABLE_DBUS_INTEGRATION. 2021-06-11 14:20:00 +04:00
John Preston
7a0ba58ffd Don't construct Webrtc::VideoTrack only for state tracking. 2021-06-11 14:20:00 +04:00
John Preston
4543656aa3 Implement screencast pause in TDesktop. 2021-06-11 14:20:00 +04:00
23rd
8d72026cbd Fixed build for Linux. 2021-06-10 23:00:56 +03:00
23rd
32e47c24b4 Moved Linux MPRIS support to lib_base. 2021-06-10 23:00:56 +03:00
23rd
69eaecc218 Moved SystemMediaControlsManagerto Media namespace. 2021-06-10 23:00:56 +03:00
23rd
3ffbb94fdb Removed SPMediaKeyTap solution for macOS media keys.
Related commit: fd6a312abc.
2021-06-10 23:00:55 +03:00
23rd
4f8bab6a5f Fixed auto-seeking by system for non-fully loaded audio files in SMC. 2021-06-10 23:00:55 +03:00
23rd
2f0fd398d5 Fixed update of availability of next and previous tracks in SMC. 2021-06-10 23:00:55 +03:00
23rd
c6fde48936 Optimized updating of position in SMC. 2021-06-10 23:00:55 +03:00
23rd
8356bac6d7 Added new event of seeking to media player. 2021-06-10 23:00:55 +03:00
23rd
b553520a48 Fixed track change notification in media player. 2021-06-10 23:00:55 +03:00
23rd
3c86da78af Added volume support to SMC class. 2021-06-10 23:00:55 +03:00
23rd
fdbf63229d Added ability to specify application name for Linux MPRIS. 2021-06-10 23:00:55 +03:00
23rd
d22601461a Added new commands to Quit and Raise for Linux MPRIS. 2021-06-10 23:00:55 +03:00
23rd
13c70a9ce9 Added initial support of seeking to SMC class. 2021-06-10 23:00:55 +03:00
23rd
1cfbf24635 Moved Windows SMTC as part of cross-platform media controls solution. 2021-06-10 23:00:55 +03:00
Ilya Fedin
f90f1c02c3 Add extra-cmake-modules as telegram dependency in snapcraft.yaml 2021-06-11 00:00:16 +04:00
Ilya Fedin
8d0f5bb828 Use custom xdg-shell fork for shadows on Wayland 2021-06-10 23:49:10 +04:00
John Preston
2f986660ff Optimize background painting in OpenGL renderers. 2021-06-10 23:37:09 +04:00
John Preston
f9f98975a1 Implement OpenGL renderer for one-on-one calls. 2021-06-10 17:16:17 +04:00
John Preston
e0e2b973f1 Remove shadow debugging image saving. 2021-06-10 15:38:19 +04:00
John Preston
aeb994dd40 Fix radial loading and seek frame in PiP. 2021-06-10 14:05:15 +04:00
John Preston
112c597556 Fix video rotation apply in PiP player. 2021-06-10 11:44:31 +04:00
John Preston
fc94045f41 Fix PiP cursor after drag/resize finished. 2021-06-10 10:39:09 +04:00
John Preston
2a8055c513 Fix action invocation after dragging PiP window.
Regression was introduced in d752aa3481.
2021-06-10 10:31:47 +04:00
John Preston
fba116f0d5 Implement custom OpenGL renderer for PiP. 2021-06-10 10:12:37 +04:00
John Preston
b38f89d69e Fix 2x and 3x volume off icon in media viewer. 2021-06-10 10:12:23 +04:00
John Preston
84f6a5f957 Handle "video_joined" flag in self participant. 2021-06-09 09:19:29 +04:00
John Preston
054223efe0 Request screen recording permissions on macOS. 2021-06-09 09:19:29 +04:00
John Preston
de3ea30d69 Don't show messages to support as always-read. 2021-06-09 09:19:29 +04:00
John Preston
c5a46d9d1b For surface with parent backend should be fixed. 2021-06-09 09:19:29 +04:00
John Preston
e41fb0d8fd Fix ARGB32 frame rendering. 2021-06-09 09:19:28 +04:00
John Preston
5970f3de9e Show video paused icon. 2021-06-09 09:19:28 +04:00
John Preston
7878552e7d Fix hiding rows with video in narrow column. 2021-06-09 09:19:28 +04:00
John Preston
b3648d0147 Use QOpenGLShaderProgram::bind. 2021-06-09 09:19:28 +04:00
John Preston
dd79b3c0d5 Add noise to blur to remove color banding. 2021-06-09 09:19:28 +04:00
John Preston
3d76e6de55 Improve level meter design in group call settings. 2021-06-09 09:19:28 +04:00
John Preston
ef61443342 Fix tg://settings/devices link. 2021-06-09 09:19:28 +04:00
John Preston
4f8989fad7 Fix large video being removed animation. 2021-06-09 09:19:28 +04:00
John Preston
945411274f Close large video tile by click anywhere. 2021-06-09 09:19:28 +04:00
John Preston
f1f7330bf6 Hide "Remove" context action for participants-groups. 2021-06-09 09:19:28 +04:00
John Preston
a1957fe5c0 Stop connecting sound when group call leave starts. 2021-06-09 09:19:28 +04:00
John Preston
9a21d55de7 Show last blurred frame or userpic for paused videos. 2021-06-09 09:19:28 +04:00
John Preston
303ad02c61 Always call raise() before activateWindow(). 2021-06-09 09:19:28 +04:00
John Preston
1886a5c4ed Fix build on macOS. 2021-06-09 09:19:28 +04:00
John Preston
71ddfacfaa Fix showing static content in OpenGL media viewer. 2021-06-09 09:19:28 +04:00
John Preston
23c2bce1bb Animated zoom+rotate in OpenGL media viewer. 2021-06-09 09:19:28 +04:00
John Preston
5324a626be Animated zoom in OpenGL media viewer. 2021-06-09 09:19:28 +04:00
John Preston
4c5421916a Closed alpha version 2.7.6.2. 2021-06-09 09:19:28 +04:00
John Preston
2801bd99b8 Render media viewer icons in RendererGL. 2021-06-09 09:19:28 +04:00
John Preston
38a0eb3b52 Fix color conversions on GPU. 2021-06-09 09:19:28 +04:00
John Preston
607263b8be Fix seek / cancel of video playback in media viewer. 2021-06-09 09:19:28 +04:00
John Preston
42b62e90ca Fix video in non-group calls. 2021-06-09 09:19:28 +04:00
John Preston
f6f0b02333 Render YUV420 video in media viewer. 2021-06-09 09:19:28 +04:00
John Preston
1858e7e8ac Fix many video tiles in voice chats. 2021-06-09 09:19:28 +04:00
John Preston
482ad74c57 Stop camera/screen video when muted by admin. 2021-06-09 09:19:27 +04:00
John Preston
68ae40ee56 Draw all except controls in OpenGL media viewer. 2021-06-09 09:19:27 +04:00
John Preston
24f8a88625 Start OpenGL media viewer renderer. 2021-06-09 09:19:27 +04:00
23rd
fc78769e9c Added floating date header to Shared Files and Shared Links. 2021-06-09 09:19:27 +04:00
23rd
5c54d3690c Added floating date badge to Shared Photos and Shared Videos. 2021-06-09 09:19:27 +04:00
23rd
2cd8b00610 Added ability to override foreground color for date service messages. 2021-06-09 09:19:27 +04:00
23rd
7ee35bc80c Added ability to override background color for service messages. 2021-06-09 09:19:27 +04:00
23rd
cfbbce26c4 Added suitable name for floating badge colors. 2021-06-09 09:19:27 +04:00
John Preston
dfb26cabfc Closed alpha version 2.7.6.1. 2021-06-09 09:19:27 +04:00
John Preston
70f0cce340 Update API scheme. 2021-06-09 09:19:27 +04:00
Alexander Bushnev
3af0c37c6b Fix video player volume level changing
Bug:
Sometimes changing of the volume level or mute/unmute has no effect.
It happens because Fader::onTimer get a current volume level from the Mixer,
but it gets an event about the volume modification from the settings.
Bug appear when the method onTimer calling between updating
of the settings and the Mixer volume.

Solution:
Updating the Mixer volume before the settings.
(maybe will be better to get the volume level
from the settings in place of the Mixer,
but I am not sure about other side effects of this)
2021-06-09 08:18:49 +03:00
Anthony Axenov
b1906a778e Actual link to Vazir Fonts license in README 2021-06-09 08:17:53 +03:00
Alexander Bushnev
d752aa3481 Add volume controls to the PiP window
Add volume toggle and volume level controls to the PiP window.
2021-06-09 08:17:32 +03:00
John Preston
afc5191644 Update mallocng. 2021-06-07 16:21:55 +04:00
John Preston
254b02ad6b Fix Release build on Windows. 2021-06-07 16:21:55 +04:00
23rd
885365a1c2 Fixed Github CI Windows build. 2021-06-04 11:47:32 +03:00
23rd
245be4cd63 Fixed Github CI macOS build. 2021-06-04 11:47:32 +03:00
Ilya Fedin
ce413f2946 Update tg_owt in snap 2021-06-04 11:47:01 +03:00
Ilya Fedin
0d84ba406f Exclude some development files from snap 2021-06-04 11:47:01 +03:00
Ilya Fedin
9047b3c121 Prime tg_owt dependencies 2021-06-04 11:47:01 +03:00
Ilya Fedin
3ff9543106 Fix rnnoise installation path 2021-06-04 11:47:01 +03:00
Ilya Fedin
46b4a5fc5a Fix snap build 2021-06-02 14:00:43 +03:00
Ilya Fedin
bd456568ed Set -DTG_OWT_BUILD_AUDIO_BACKENDS=OFF in snapcraft.yaml 2021-06-01 19:33:43 +03:00
Ilya Fedin
93fa0e1df5 Add rnnoise to snapcraft.yaml 2021-06-01 19:33:43 +03:00
Ilya Fedin
fe4c5155eb Fix some warnings from GCC 11 2021-06-01 19:33:19 +03:00
John Preston
24c435bb5f Improve hide media viewer workaround on Windows. 2021-06-01 16:56:38 +04:00
John Preston
e6977b2c33 Start sharing without source choosing in case of PipeWire. 2021-06-01 14:58:30 +04:00
John Preston
28f83f2af4 Show participants Bio when not speaking. 2021-06-01 14:16:55 +04:00
John Preston
5a6e8a0a8c Improve style of desktop capture source choosing. 2021-06-01 13:58:15 +04:00
John Preston
003da28699 Beta version 2.7.6: Update submodules. 2021-05-31 23:54:09 +04:00
John Preston
e8dd969e78 Fix paint-image vertex shader on macOS. 2021-05-31 23:53:41 +04:00
John Preston
acce2a217d Beta version 2.7.6: Fix build on macOS. 2021-05-31 21:51:47 +04:00
Ilya Fedin
b964c681f8 Get system dark mode state asynchronously on Linux 2021-05-31 20:31:24 +03:00
Ilya Fedin
c6dcc57c5e Set _KDE_NET_WM_DESKTOP_FILE on X11 2021-05-31 20:30:51 +03:00
John Preston
cf8e1cfd0f Beta version 2.7.6.
- Optimized video playback in media viewer and Picture-in-Picture
on Windows and Linux.
- Added integration with System Media Transport Controls
on Windows 10.
- Added "Archive Sticker" into the "..." menu of the Sticker Set Box.
- Fixed memory not being freed on Linux.
- Several crash fixes.
2021-05-31 21:30:21 +04:00
John Preston
78b40a1f66 Revert test code in 'tgcalls'. 2021-05-31 21:27:14 +04:00
John Preston
2fe75f8296 Add large / small video animation. 2021-05-31 21:25:15 +04:00
John Preston
b22363224f Track tiles visibility separately from geometry. 2021-05-31 18:40:53 +04:00
John Preston
b3c92ed3f4 Rejoin if the call started accepting video. 2021-05-31 18:00:41 +04:00
John Preston
464b0a0f30 Use old media viewer hide workaround on macOS. 2021-05-31 17:39:15 +04:00
John Preston
dfcc13c7e6 Warn user about pinning of his screencast. 2021-05-31 17:35:41 +04:00
John Preston
ba6cee6f81 Add video/screencast pinned/unpinned toasts. 2021-05-31 17:10:30 +04:00
John Preston
bcdfd2150d Auto-pin new screencasts. 2021-05-31 15:53:15 +04:00
John Preston
d19d6bbcd9 Group call owner chat/channel is always admin. 2021-05-31 15:52:51 +04:00
John Preston
4080fa9bdc Allow mute for me even force-muted participants. 2021-05-31 15:46:15 +04:00
John Preston
ce091b0b63 Fix second click in media viewer double-click. 2021-05-31 14:54:56 +04:00
John Preston
136e930362 Paint outline in raster mode. 2021-05-31 08:54:23 +04:00
23rd
78dfe940ef Added Windows SMTC support.
System Media Transport Controls.
2021-05-31 08:54:23 +04:00
23rd
690fbe83fd Unified and moved to single place generating of song name from SongData. 2021-05-31 08:54:22 +04:00
23rd
f98e8f3e04 Fixed stuck of forward bar when using -sendpath argument.
Regression was introduced in b3d1602354.
2021-05-31 08:54:22 +04:00
23rd
63febef3ed Added Dock menu for macOS. 2021-05-30 23:39:55 +03:00
23rd
2599ae45d6 Fixed handle of last message on Up arrow in sections with key modifiers. 2021-05-30 23:39:55 +03:00
23rd
bfb03621c2 Fixed GIFs in EditCaptionBox on Retina screens. 2021-05-30 23:39:55 +03:00
23rd
be53bd5293 Added ability to start recording voice chat without title. 2021-05-30 23:39:55 +03:00
23rd
a429500b57 Fixed first animation of album thumbs in SendFilesBox on Retina screens. 2021-05-30 23:39:55 +03:00
John Preston
ec9fa00f46 Bring second large peer video to the top. 2021-05-31 00:07:11 +04:00
John Preston
6a001f2e6c Auto-switch large video by speaking participant. 2021-05-30 23:52:01 +04:00
John Preston
8bde53cd0f Add Back button to large video tiles. 2021-05-30 21:08:54 +04:00
John Preston
090d7d7112 Show video large on click. 2021-05-30 19:14:08 +04:00
John Preston
97c7c0742c Rename LargeVideo to Video in voice chats. 2021-05-30 18:46:51 +04:00
John Preston
90efbf1210 Remove some experimental code. 2021-05-30 18:21:11 +04:00
John Preston
38506d27a1 Hide member rows with active small videos. 2021-05-30 18:09:59 +04:00
John Preston
8a693bc932 Move 'Invite members' button to the bottom. 2021-05-30 14:56:54 +04:00
John Preston
0e49bf5dee Add video tiles in narrow members column. 2021-05-29 23:57:21 +04:00
John Preston
6a967948de Optimize uploading of textures. 2021-05-29 23:27:12 +04:00
John Preston
0771fc14db Use unique_id for tiles instead of PeerData*. 2021-05-29 22:58:11 +04:00
John Preston
e1614a280f Add blur effect for video tile background. 2021-05-29 17:00:09 +04:00
John Preston
ddf81c949b Add green outline to speaking video tiles. 2021-05-28 21:05:28 +04:00
John Preston
b906b2f625 Closed alpha version 2.7.5.7. 2021-05-28 19:11:07 +04:00
John Preston
3f2b473287 Add tooltips for wide mode controls. 2021-05-28 19:08:11 +04:00
John Preston
9a9430b5e1 Move some logging to DEBUG_LOG instead of LOG. 2021-05-28 15:21:15 +04:00
John Preston
d659200a42 Show unmute button tooltip about space bar. 2021-05-28 14:47:41 +04:00
John Preston
cb630c69f0 Fix tooltip colors in default dark green theme. 2021-05-28 13:39:38 +04:00
John Preston
9a812090a2 Add some error tooltips in group calls. 2021-05-28 13:23:24 +04:00
John Preston
5b0278847d Add context menu to video tile right click. 2021-05-27 23:20:16 +04:00
John Preston
9d07bb2946 Remove 'Share Screen' menu item in scheduled chats. 2021-05-27 22:47:18 +04:00
John Preston
b27d314fa7 Skip deleted users in voice chat invites. 2021-05-27 22:46:20 +04:00
John Preston
df666ff724 Implement more robust reconnect management. 2021-05-27 17:09:36 +04:00
John Preston
3709714339 Make '[un]registerLeaveSubscription' work in all windows. 2021-05-27 09:34:33 +04:00
John Preston
deecf80f20 Add OpenGL media viewer glitches workaround. 2021-05-27 08:39:01 +04:00
John Preston
6ea66bc527 Fix joining broadcasted streams in voice chats. 2021-05-27 00:37:05 +04:00
John Preston
513c8d1a65 Fix PiP drag pausing the video. 2021-05-26 23:49:33 +04:00
John Preston
49f71f4e1e Use av_packet_alloc instead of av_init_packet. 2021-05-26 22:53:54 +04:00
John Preston
0c5258b43a Closed alpha version 2.7.5.6. 2021-05-26 18:35:35 +04:00
John Preston
a0506f009a Update tg_owt revision. 2021-05-26 18:35:25 +04:00
John Preston
9f93dae6f9 Update FFmpeg to 4.4. 2021-05-26 18:22:30 +04:00
John Preston
45cca35724 Implement menu in wide video mode. 2021-05-26 18:21:50 +04:00
John Preston
1c42513e44 Fix pinning video from narrow mode. 2021-05-26 15:29:26 +04:00
John Preston
f3e6f5e772 Cancel outgoing video when muted by admin. 2021-05-26 15:12:00 +04:00
John Preston
c6f44e7928 Hide video button if the call doesn't support video. 2021-05-26 14:27:48 +04:00
John Preston
7b6b32db74 Fix name display in group call video tiles. 2021-05-26 13:35:01 +04:00
John Preston
e39f9bef1f Fix voice message recording with FFmpeg 4.2. 2021-05-26 09:59:25 +04:00
John Preston
479b604c0e Closed alpha version 2.7.5.5. 2021-05-25 23:34:55 +04:00
John Preston
e7ef3c4b6d Fix crash in GL_RED textures uploading. 2021-05-25 23:34:55 +04:00
John Preston
87cae1c3a7 Fix build on macOS. 2021-05-25 23:34:55 +04:00
John Preston
562fc74481 Closed alpha version 2.7.5.4. 2021-05-25 22:11:11 +04:00
John Preston
51d8e9c43d Skip empty rects in painting. 2021-05-25 17:23:50 +04:00
John Preston
e50a7a2e42 Toggle wide mode only when videos with frames. 2021-05-25 16:55:01 +04:00
John Preston
aaad250a77 Show names in OpenGL renderer. 2021-05-25 16:26:41 +04:00
John Preston
699730b7f4 Show mute icon in OpenGL renderer. 2021-05-25 14:56:26 +04:00
John Preston
302cffba1c Display pin button in OpenGL renderer. 2021-05-25 12:08:06 +04:00
John Preston
e299aa032d Update tg_owt to webrtc m91. 2021-05-25 12:07:49 +04:00
John Preston
ca6f70746c Closed alpha version 2.7.5.3. 2021-05-24 22:45:16 +04:00
John Preston
2af1d95650 Fix scheduled voice chat creation. 2021-05-24 22:45:16 +04:00
John Preston
df6f5d83d6 Make shaders work on OpenGL 2.1. 2021-05-24 22:45:16 +04:00
John Preston
42baa3e1bc Convert YUV420 -> ARGB32 in fragment shader. 2021-05-24 22:45:16 +04:00
John Preston
5f393babd6 Fix video wrap geometry in narrow column. 2021-05-24 22:45:16 +04:00
John Preston
b864563f47 Support mode switching in single widget Viewport. 2021-05-24 22:45:16 +04:00
John Preston
3edb2d08ba Optimize frame shader, apply rotation. 2021-05-24 22:45:16 +04:00
John Preston
d44f923277 OpenGL render of frames in single widget. 2021-05-24 22:45:16 +04:00
John Preston
ec468431b4 Raster render of wide mode in single widget. 2021-05-24 22:45:16 +04:00
John Preston
4774f438a9 Update API scheme. 2021-05-24 22:45:16 +04:00
John Preston
f40659a7b4 Round corners of LargeVideo. 2021-05-24 22:45:16 +04:00
John Preston
047989abcf Fill solid background in OpenGL renderer. 2021-05-24 22:45:16 +04:00
John Preston
3e79b67032 Start OpenGL renderer for group calls. 2021-05-24 22:45:16 +04:00
John Preston
ca4b1e6ae0 Fix build for macOS. 2021-05-24 22:45:16 +04:00
John Preston
b56749426b Fix build on Clang/GCC. 2021-05-24 22:45:16 +04:00
John Preston
cbe6e1caad Closed alpha version 2.7.5.2. 2021-05-24 22:45:16 +04:00
John Preston
748eb9ff12 Remove labels from wide mode buttons. 2021-05-24 22:45:16 +04:00
John Preston
385b98ff3d Nice three-button narrow mode with gradient fading. 2021-05-24 22:45:16 +04:00
John Preston
c12a50544e Allow force-disabling OpenGL. 2021-05-24 22:45:15 +04:00
John Preston
c64e953174 Choose OpenGL / Raster in media viewer. 2021-05-24 22:45:15 +04:00
John Preston
ccc599c83e Fix PiP window first show. 2021-05-24 22:45:15 +04:00
John Preston
a45064257a Choose OpenGL / Raster surface at runtime. 2021-05-24 22:45:15 +04:00
John Preston
9510ba07f7 Drop old sticker set cover locations. 2021-05-24 22:45:15 +04:00
John Preston
8e3dc76dd7 Disable tg_owt audio backends explicitly. 2021-05-24 22:45:15 +04:00
John Preston
451332b2e7 Closed alpha version 2.7.5.1. 2021-05-24 22:45:15 +04:00
John Preston
445c798bbc Improve pin video button design and controls hiding. 2021-05-24 22:45:15 +04:00
John Preston
c48c4d4283 Use QOpenGLWidget on all systems. 2021-05-24 22:45:15 +04:00
John Preston
b421d0c5cc Build Qt with dynamic OpenGL on Windows. 2021-05-24 22:45:15 +04:00
John Preston
f7454a4284 Fix crash on quit in idle state. 2021-05-24 22:45:15 +04:00
John Preston
9144f4ea7b Fix streaming frames prepare. 2021-05-24 22:45:15 +04:00
John Preston
aaea367fba Hide controls when mouse is out of video area. 2021-05-24 22:45:15 +04:00
John Preston
e0e878cbb1 Update tg_owt to webrtc m90. 2021-05-24 22:45:15 +04:00
John Preston
b905a18161 Destroy all tgcalls instances before quit. 2021-05-24 22:45:15 +04:00
John Preston
f4ae7ecbe7 Remove tracking of video ssrc-s. 2021-05-24 22:45:15 +04:00
John Preston
9a8812d00b Move participants list to the right of the window. 2021-05-24 22:45:15 +04:00
John Preston
13b3de683a Improve narrow participants column design. 2021-05-24 22:45:15 +04:00
John Preston
64243d1437 Fix crash in joining active video chat. 2021-05-24 22:45:15 +04:00
John Preston
a730c88491 Add icons and improve narrow participants column. 2021-05-24 22:45:15 +04:00
John Preston
bd90cc4134 Fix closing of pre-launch windows. 2021-05-24 22:45:15 +04:00
John Preston
316f0537c4 Implement video pin / controls hiding in video tiles. 2021-05-24 22:45:15 +04:00
John Preston
7f739065e8 First version of tiled layout. 2021-05-24 22:45:15 +04:00
John Preston
bd83ed8130 Add 'pin screencast' context menu item. 2021-05-24 22:45:15 +04:00
John Preston
e39ffbc83c Update tgcalls, request required video channels. 2021-05-24 22:45:15 +04:00
John Preston
1471e9b8e2 Destroy group call instances async. 2021-05-24 22:45:15 +04:00
John Preston
4c23d51be5 Fix volume icon over video. 2021-05-24 22:45:15 +04:00
John Preston
412cfb24d2 Stop video when rejoining-as, handle errors. 2021-05-24 22:45:15 +04:00
John Preston
2a5977e97f Support enlarge / minimize of video. 2021-05-24 22:45:15 +04:00
John Preston
64c34b7029 Hide controls in wide video mode. 2021-05-24 22:45:15 +04:00
John Preston
0db0abe608 Use LargeVideo in the default mode. 2021-05-24 22:45:15 +04:00
John Preston
5f4903a279 Show controls in the middle of wide video. 2021-05-24 22:45:14 +04:00
John Preston
20ff79abf4 Fix camera / screen self track activation. 2021-05-24 22:45:14 +04:00
John Preston
3a321d64f6 Switch between videos by left click. 2021-05-24 22:45:14 +04:00
John Preston
7e8d1f7974 Toggle video pin from LargeVideo. 2021-05-24 22:45:14 +04:00
John Preston
9f41461209 Load .dll symbols in a similar way. 2021-05-24 22:45:14 +04:00
John Preston
6b10045b7b Enable screen sharing on Linux. 2021-05-24 22:45:14 +04:00
John Preston
9ca6d0d893 Build tg_owt with desktop_capture support. 2021-05-24 22:45:14 +04:00
John Preston
2830049a53 Add support for DirectX desktop capturer backend. 2021-05-24 22:45:14 +04:00
John Preston
50558de591 Show name and information on wide large video. 2021-05-24 22:45:14 +04:00
John Preston
80e3e8a01e Extract MembersRow from calls_group_members module. 2021-05-24 22:45:14 +04:00
John Preston
d38780c94d Add shadow under name over video. 2021-05-24 22:45:14 +04:00
John Preston
801435e57c Show participant names in narrow column. 2021-05-24 22:45:14 +04:00
John Preston
8001efe6ab Track peer together with video endpoint. 2021-05-24 22:45:14 +04:00
John Preston
909a3cef9b Create a new Instance for each screencast. 2021-05-24 22:45:14 +04:00
John Preston
9ac510a1ad Fix title of wide mode video chat. 2021-05-24 22:45:14 +04:00
John Preston
00ce302b38 Improve narrow participants column design. 2021-05-24 22:45:14 +04:00
John Preston
0dcc7a05f7 Fix crash in pinned video switching. 2021-05-24 22:45:14 +04:00
John Preston
54c2769d8a Improve voice /video chat members management. 2021-05-24 22:45:14 +04:00
John Preston
2e400d88d3 Fix creating group calls. 2021-05-24 22:45:14 +04:00
John Preston
d9aa660253 Handle members slice loaded. 2021-05-24 22:45:14 +04:00
John Preston
ba1dade4b0 New API/tgcalls with two outgoing videos. 2021-05-24 22:45:14 +04:00
John Preston
a48649987e Improve screencast source choosing design. 2021-05-24 22:45:14 +04:00
John Preston
022c0a1327 Update tgcalls library. 2021-05-24 22:45:14 +04:00
John Preston
69ceed5bbc Update API scheme to layer 129. 2021-05-24 22:45:14 +04:00
John Preston
b3fcb4ef36 Allow 'mute for me' of muted by admin participants. 2021-05-24 22:45:14 +04:00
John Preston
8342b2d275 Fix bug with video_muted flag. 2021-05-24 22:45:14 +04:00
John Preston
36888f844f Disable (crashing) capturer on Linux. 2021-05-24 22:45:14 +04:00
John Preston
75f220c3d9 Fix build for Linux. 2021-05-24 22:45:14 +04:00
John Preston
1a784fc678 Track video_muted from API. 2021-05-24 22:45:14 +04:00
John Preston
dac9017df1 Improve video chat controls layout. 2021-05-24 22:45:14 +04:00
John Preston
7b3b5a1463 Use 'Screencast' video content type. 2021-05-24 22:45:14 +04:00
John Preston
b7fc3f67d7 Add toggle video and screen sharing buttons. 2021-05-24 22:45:13 +04:00
John Preston
e0bfaad3a2 Make style of mute button customizable. 2021-05-24 22:45:13 +04:00
John Preston
24c77a8956 Fix build on macOS. 2021-05-24 22:45:13 +04:00
John Preston
380a0d1f86 Improve wide video layout. 2021-05-24 22:45:13 +04:00
John Preston
b7f6fc9a2d Update API scheme. 2021-05-24 22:45:13 +04:00
John Preston
e12fe974b2 Add pinned video in wide mode. 2021-05-24 22:45:13 +04:00
John Preston
b15623d435 Allow pinning video to top of members list. 2021-05-24 22:45:13 +04:00
John Preston
eb8f709943 Show members list in PanelMode::Wide. 2021-05-24 22:45:13 +04:00
John Preston
c93ddf6aac Separate Call/GroupCall delegates and Instance. 2021-05-24 22:45:13 +04:00
John Preston
6e34360f7e Move some group call modules. 2021-05-24 22:45:13 +04:00
John Preston
c9d07cd0f8 Proof-of-concept desktop capture source picker. 2021-05-24 22:45:13 +04:00
John Preston
9ff6b57b94 Use common desktop_capturer code on Windows. 2021-05-24 22:45:13 +04:00
John Preston
fb49b0ca27 Add proof-of-concept screen sharing on macOS. 2021-05-24 22:45:13 +04:00
John Preston
fef1f80570 Disable video preview in Settings when in group call. 2021-05-24 22:45:13 +04:00
John Preston
38cb1b195d Add proof-of-concept screen sharing on Windows. 2021-05-24 22:45:13 +04:00
John Preston
ebdbe4a8d6 Show video instead of userpics in members list. 2021-05-24 22:45:13 +04:00
John Preston
ba02a5c46a Resolve video chat participants by unknown ssrcs. 2021-05-24 22:45:13 +04:00
John Preston
a6f379a17a Parse and serialize video parameters. 2021-05-24 22:45:13 +04:00
John Preston
a41b7b62ac Build and link rnnoise on Windows. 2021-05-24 22:45:13 +04:00
Ilya Fedin
5010c9033b Specify buildtype for meson 2021-05-22 17:31:26 +03:00
23rd
93e4161d5e Fixed NuGet link in instructions for Windows build. 2021-05-19 14:39:22 +03:00
Ilya Fedin
e0d6faf45b Fix build with LTO 2021-05-19 12:26:07 +03:00
John Preston
fbe4e3f0ec Fix build with mallocng. 2021-05-17 13:38:28 +04:00
John Preston
8e02c50f7d Fix build on Linux. 2021-05-17 13:07:39 +04:00
Ilya Fedin
837485974a Use mallocng on Linux 2021-05-17 10:35:18 +03:00
Ilya Fedin
3cf739eca9 Use event filter to get surface expose event 2021-05-17 10:00:01 +03:00
Ilya Fedin
cfee688feb Replace call_delayed with InvokeQueued in MainWindow::handleVisibleChanged 2021-05-17 10:00:01 +03:00
Ilya Fedin
30d8894c30 Use nodiscard in WaylandIntegration 2021-05-17 10:00:01 +03:00
Ilya Fedin
0b86feeeb5 Implement appmenu on Wayland with org_kde_kwin_appmenu protocol 2021-05-17 10:00:01 +03:00
Ilya Fedin
434ef34378 Implement taskbar icon hidding on Wayland with org_kde_plasma_shell protocol 2021-05-17 10:00:01 +03:00
Ilya Fedin
166c28c215 Get rid of default_delete 2021-05-17 10:00:01 +03:00
Ilya Fedin
17c514e851 Use surface as XdgExporter parent 2021-05-17 10:00:01 +03:00
Ilya Fedin
f7489592d6 Fix BIO_new_mem_buf leaks 2021-05-17 09:53:02 +03:00
Ilya Fedin
3cb9312805 NEON support for ARMv7 is fixed in tg_owt 2021-05-16 11:28:11 +03:00
Ilya Fedin
3722486b19 Add new tg_owt dependencies to snap 2021-05-16 11:28:11 +03:00
John Preston
57b3982346 Update tg_owt in Snap build. 2021-05-14 15:22:39 +04:00
John Preston
a8807bc915 Fix voice recoding with FFmpeg 4.4.
Fixes #16217.
2021-05-13 15:36:27 +04:00
John Preston
71deaa48af Don't crash on voice recording error.
Fixes #16217.
2021-05-13 15:36:24 +04:00
John Preston
e7ca35a276 Fix possible deadlock in debug logs. 2021-05-13 15:36:08 +04:00
John Preston
2d8f43bd8c Fix text color for crash report window. 2021-05-13 15:35:36 +04:00
John Preston
383acf0ffc Fix crash in native notifications on Linux. 2021-05-12 12:10:55 +04:00
c0re100
ee156fc6a8 Add "Archive Sticker" into Sticker Box 3dots menu 2021-05-11 10:54:25 +03:00
Ilya Fedin
680a9a7ca7 Implement parent setting for portal and gtk dialogs on Wayland via xdg-foreign-v2 2021-05-11 10:53:32 +03:00
Ilya Fedin
7de8d6f9ac Fix resetting of available geometry fix in some cases 2021-05-11 10:43:04 +03:00
John Preston
d79fab8b3c Build ffmpeg with 'CONFIG_SAFE_BITSTREAM_READER' on macOS. 2021-05-10 16:45:21 +04:00
John Preston
0cb32181c5 Handle small SLOWMODE_WAIT as FLOOD_WAIT. 2021-05-10 16:44:28 +04:00
John Preston
dba3c39726 Skip 'enhancement' issues by stale bot. 2021-05-10 13:53:54 +04:00
Ilya Fedin
95b4435396 Resubscribe to signal when running with old xdg-desktop-portal 2021-05-07 18:32:02 +03:00
John Preston
f1a9884011 Beta version 2.7.5.
- Add "Voice chats" filter in "Recent actions" for channels.
- Write local drafts to disk on a background thread.
- Support autoupdate for Telegram in write-protected folders on Linux.
- Fix crash in native notifications on Linux.
- Fix crash in file dialog on Linux.
2021-05-04 00:27:59 +04:00
John Preston
691dcb8ae1 Fix build on macOS and Linux. 2021-05-04 00:02:24 +04:00
John Preston
db6b571f60 Add voice chat admin event log filter in channels. 2021-05-03 23:05:58 +04:00
John Preston
5ce1b00291 Fix build on Windows #2. 2021-05-03 23:05:22 +04:00
John Preston
8332ba8450 Fix build on Windows. 2021-05-03 21:39:50 +04:00
John Preston
b1c4524612 Fix crash dump generation on Linux. 2021-05-03 17:34:33 +04:00
Ilya Fedin
9a857659ce Check action type before launching new version 2021-05-03 14:53:21 +03:00
Ilya Fedin
68dc00be27 Move weak_ptr include to .cpp in linux notifications 2021-05-03 14:39:24 +03:00
Ilya Fedin
ee00f12131 Launch new version directly in write-protected mode 2021-05-03 14:39:24 +03:00
John Preston
7444f17c4e Use sendfile only on Linux. 2021-05-03 14:59:24 +04:00
Ilya Fedin
99e70f7783 Build Qt with libproxy 2021-05-03 13:03:54 +03:00
Ilya Fedin
578833446d Add support for write-protected update on Linux 2021-05-03 12:54:08 +03:00
John Preston
4fae827f1e Use a separate string for voice chat ending in groups. 2021-05-03 13:23:28 +04:00
John Preston
98180d3a9e Always guard and send on_main in native notifications. 2021-05-03 13:08:50 +04:00
John Preston
434a4af9ef Fix sendfile() arguments. 2021-05-03 12:43:36 +04:00
John Preston
298215542e Fix legacy-style connect in Intro. 2021-05-03 12:29:21 +04:00
John Preston
197b3c1cb5 Push all QSaveFile-s to background thread. 2021-05-03 12:27:45 +04:00
John Preston
3cad89f299 Fix build, add logs for sendfile(). 2021-05-03 11:21:38 +04:00
Ilya Fedin
99b9a46428 Update lib_base 2021-05-03 10:20:26 +03:00
Ilya Fedin
56a5363eb9 Move keyPressed out of GSDMediaKeys::Private 2021-05-03 10:20:26 +03:00
Ilya Fedin
b1c95d719a Use crl::guard in XDPFileDialog 2021-05-03 10:20:26 +03:00
Ilya Fedin
d87ea056c6 Fix a crash in NotificationData::show 2021-05-03 10:20:26 +03:00
Ilya Fedin
34534a9653 Use kernel accelerated sendfile to copy files on Linux 2021-05-03 09:59:58 +03:00
GitHub Action
5d0222b1c1 Update User-Agent for DNS to Chrome 90.0.4430.85. 2021-05-02 10:45:06 +03:00
Ilya Fedin
b72260f420 Avoid 30s freeze when opening file dialog in broken envirionments 2021-04-30 08:43:25 +03:00
Ilya Fedin
896eee9841 Check whether portal dialog is failed to open 2021-04-30 08:43:25 +03:00
Ilya Fedin
0d96657c33 Fix check for disconnected error 2021-04-30 08:43:25 +03:00
Ilya Fedin
41078869a9 Update submodules 2021-04-30 08:41:05 +03:00
Ilya Fedin
89b11ef084 Move gtk initialization back to ThirdParty::start 2021-04-30 08:41:05 +03:00
Ilya Fedin
26d3995424 Move wayland helper to cmake_helpers 2021-04-30 08:41:05 +03:00
John Preston
b6fad35146 Improve library loading on Linux. 2021-04-29 12:05:32 +04:00
John Preston
70bf328e7d Load wayland-client dynamically and provide functions. 2021-04-29 11:21:57 +04:00
John Preston
404538c989 Fix build with dummy notifications manager on Linux. 2021-04-29 11:21:57 +04:00
John Preston
9c9fc9e881 Version 2.7.4: Fix build for macOS. 2021-04-28 14:08:02 +04:00
John Preston
1d089366ff Version 2.7.4.
- Fix crash in viewing an invoice after a payment is made.
- Respect Focus Assist only for native notifications.
- Mark messages as read only in active window.
2021-04-28 13:16:01 +04:00
John Preston
fe40464e33 Force separate panel into the screen geometry. 2021-04-28 13:16:00 +04:00
John Preston
728b1efb9a Respect Focus Assist only for native notifications.
Fixes #16215.
2021-04-28 11:20:39 +04:00
John Preston
175f3d7a38 Use our fork of 'snapcraft-desktop-helpers'. 2021-04-28 10:46:03 +04:00
Stepan Skryabin
ae1fb8841a Update supported operating systems 2021-04-28 09:39:58 +03:00
Ilya Fedin
16ba20f898 Prefer portal file dialog in all environments 2021-04-28 09:39:20 +03:00
Daniil
d8ffc114d3 Revert number keys check 2021-04-28 09:38:23 +03:00
Daniil
23bd76a8dd Expand moderating mode some more
Revert key check, since number keys stopped working if bot have 
Add missing commands since last PR, also add keys to respect both keypad and regular keyboard users.
2021-04-28 09:38:23 +03:00
John Preston
d85981cca0 Revert "Check if the window is not overlapped when is not active"
This reverts commit 6b1bc1e845.

Fixes #16188. Maybe add an option later.
2021-04-28 10:36:15 +04:00
Ilya Fedin
7b466e0643 Take shadow into account when saving/restoring window geometry 2021-04-28 10:13:54 +04:00
John Preston
d984c5924d Fix crash in invoice view.
Fixes #16210.
2021-04-27 22:18:35 +04:00
23rd
cfa3352caf Added NuGet to Github CI for Windows. 2021-04-27 21:16:26 +03:00
Ilya Fedin
05d2fc819c Bind path to WebkitWebProcess 2021-04-27 17:44:41 +03:00
Ilya Fedin
cb930a89ce Silence ServiceUnknown and Disconnected errors for native notifications 2021-04-27 17:44:16 +03:00
300 changed files with 22617 additions and 9763 deletions

2
.github/stale.yml vendored
View File

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

View File

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

View File

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

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

View File

@@ -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,7 +53,7 @@ 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))

View File

@@ -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)
@@ -89,6 +92,7 @@ elseif (LINUX)
PRIVATE
desktop-app::external_glibmm
desktop-app::external_glib
desktop-app::external_mallocng
)
if (NOT DESKTOP_APP_DISABLE_DBUS_INTEGRATION)
@@ -109,6 +113,7 @@ elseif (LINUX)
if (NOT DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION)
target_link_libraries(Telegram
PRIVATE
desktop-app::lib_waylandshells
desktop-app::external_kwayland
)
endif()
@@ -271,23 +276,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 +323,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
@@ -708,6 +729,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,10 +785,20 @@ 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
@@ -830,15 +863,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 +884,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 +894,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 +1071,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
@@ -1135,16 +1172,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 +1199,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 +1244,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()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 591 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 991 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 756 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 639 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 729 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 965 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 672 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 876 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 854 B

After

Width:  |  Height:  |  Size: 664 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -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.";
@@ -1112,6 +1113,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 +1402,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 +1999,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 +2024,17 @@ 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_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";
@@ -2056,6 +2077,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 +2094,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 +2132,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 +2807,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

View File

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

View File

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

View File

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

View File

@@ -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,7,7,0
PRODUCTVERSION 2,7,7,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.7.7.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.7.3.0"
VALUE "ProductVersion", "2.7.7.0"
END
END
BLOCK "VarFileInfo"

View File

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

View File

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

View File

@@ -867,7 +867,11 @@ void Updates::updateOnline() {
}
bool Updates::isIdle() const {
return _isIdle;
return _isIdle.current();
}
rpl::producer<bool> Updates::isIdleValue() const {
return _isIdle.value();
}
void Updates::updateOnline(bool gotOtherOffline) {
@@ -881,7 +885,7 @@ void Updates::updateOnline(bool gotOtherOffline) {
const auto idle = crl::now() - Core::App().lastNonIdleTime();
if (idle >= config.offlineIdleTimeout) {
isOnline = false;
if (!_isIdle) {
if (!isIdle()) {
_isIdle = true;
_idleFinishTimer.callOnce(900);
}
@@ -932,10 +936,9 @@ void Updates::updateOnline(bool gotOtherOffline) {
void Updates::checkIdleFinish() {
if (crl::now() - Core::App().lastNonIdleTime()
< _session->serverConfig().offlineIdleTimeout) {
updateOnline();
_idleFinishTimer.cancel();
_isIdle = false;
updateOnline();
App::wnd()->checkHistoryActivation();
} else {
_idleFinishTimer.callOnce(900);
}
@@ -1878,6 +1881,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;

View File

@@ -40,6 +40,7 @@ public:
void updateOnline();
[[nodiscard]] bool isIdle() const;
[[nodiscard]] rpl::producer<bool> isIdleValue() const;
void checkIdleFinish();
bool lastWasOnline() const;
crl::time lastSetOnline() const;
@@ -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;

View File

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

View File

@@ -26,6 +26,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 "ui/basic_click_handlers.h"
#include "facades.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
@@ -1086,10 +1087,25 @@ 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -198,6 +198,38 @@ void StickerSetBox::copyStickersLink() {
QGuiApplication::clipboard()->setText(url);
}
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 +269,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(); });

View File

@@ -42,6 +42,7 @@ private:
void updateButtons();
void addStickers();
void copyStickersLink();
void archiveStickers();
const not_null<Window::SessionController*> _controller;
MTPInputStickerSet _set;

View File

@@ -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;
}
@@ -532,6 +563,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 +684,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 +714,7 @@ groupCallTitleLabel: FlatLabel(groupCallSubtitleLabel) {
}
}
groupCallAddButtonPosition: point(10px, 7px);
groupCallMembersWidthMax: 360px;
groupCallMembersWidthMax: 480px;
groupCallRecordingMark: 6px;
groupCallRecordingMarkSkip: 4px;
groupCallRecordingMarkTop: 8px;
@@ -704,6 +740,7 @@ groupCallJoinAsToggle: UserpicButton(defaultUserpicButton) {
photoPosition: point(3px, 3px);
}
groupCallMenuPosition: point(-1px, 29px);
groupCallWideMenuPosition: point(-2px, 28px);
groupCallActiveButton: IconButton {
width: 36px;
@@ -736,33 +773,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: 122px;
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 +890,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 +918,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) {
@@ -1015,3 +1126,127 @@ 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;
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);
}
}
groupCallNiceTooltipTop: 4px;
groupCallPaused: icon {{ "calls/video_large_paused", groupCallVideoTextFg }};

View File

@@ -161,8 +161,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 +384,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) {

View File

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

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -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"
@@ -50,141 +53,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtGui/QWindow>
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>())
, _window(createWindow())
#ifndef Q_OS_MAC
, _controls(std::make_unique<Ui::Platform::TitleControls>(
_window->body(),
@@ -213,6 +86,25 @@ Panel::Panel(not_null<Call*> call)
Panel::~Panel() = default;
std::unique_ptr<Ui::Window> Panel::createWindow() {
auto result = std::make_unique<Ui::Window>();
const auto capabilities = Ui::GL::CheckCapabilities(result.get());
const auto use = Platform::IsMac()
? true
: Platform::IsWindows()
? capabilities.supported
: capabilities.transparency;
LOG(("OpenGL: %1 (Incoming)").arg(Logs::b(use)));
_backend = use ? Ui::GL::Backend::OpenGL : Ui::GL::Backend::Raster;
if (use) {
return result;
}
// We have to create a new window, if OpenGL initialization failed.
return std::make_unique<Ui::Window>();
}
bool Panel::isActive() const {
return _window->isActiveWindow()
&& _window->isVisible()
@@ -392,7 +284,7 @@ void Panel::refreshIncomingGeometry() {
Expects(_incoming != nullptr);
if (_incomingFrameSize.isEmpty()) {
_incoming->hide();
_incoming->widget()->hide();
return;
}
const auto to = widget()->size();
@@ -401,7 +293,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 +301,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 +338,9 @@ void Panel::reinitWithCall(Call *call) {
_call->videoOutgoing());
_incoming = std::make_unique<Incoming>(
widget(),
_call->videoIncoming());
_incoming->hide();
_call->videoIncoming(),
_backend);
_incoming->widget()->hide();
_call->mutedValue(
) | rpl::start_with_next([=](bool mute) {
@@ -474,28 +367,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 +438,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 +509,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);
@@ -648,9 +553,9 @@ void Panel::toggleFullScreen(bool fullscreen) {
}
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 +571,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 +707,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();
}
}

View File

@@ -30,6 +30,9 @@ class FadeWrap;
template <typename Widget>
class PaddingWrap;
class Window;
namespace GL {
enum class Backend;
} // namespace GL
namespace Platform {
class TitleControls;
} // namespace Platform
@@ -67,6 +70,7 @@ private:
Redial,
};
std::unique_ptr<Ui::Window> createWindow();
[[nodiscard]] not_null<Ui::RpWidget*> widget() const;
void paint(QRect clip);
@@ -80,9 +84,6 @@ private:
void handleClose();
QRect signalBarsRect() const;
void paintSignalBarsBg(Painter &p);
void updateControlsGeometry();
void updateHangupGeometry();
void updateStatusGeometry();
@@ -105,6 +106,7 @@ private:
Call *_call = nullptr;
not_null<UserData*> _user;
Ui::GL::Backend _backend = Ui::GL::Backend();
const std::unique_ptr<Ui::Window> _window;
std::unique_ptr<Incoming> _incoming;

View File

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

View 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

View 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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,646 @@
/*
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;
class VideoCaptureInterface;
} // namespace tgcalls
namespace base {
class GlobalShortcutManager;
class GlobalShortcutValue;
} // namespace base
namespace Webrtc {
class MediaDevices;
class VideoTrack;
enum class VideoState;
} // 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;
enum class VideoQuality;
enum class Error;
} // 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;
};
enum class VideoEndpointType {
Camera,
Screen,
};
struct VideoEndpoint {
VideoEndpoint() = default;
VideoEndpoint(
VideoEndpointType type,
not_null<PeerData*> peer,
std::string id)
: type(type)
, peer(peer)
, id(std::move(id)) {
}
VideoEndpointType type = VideoEndpointType::Camera;
PeerData *peer = nullptr;
std::string id;
[[nodiscard]] bool empty() const noexcept {
return id.empty();
}
[[nodiscard]] explicit operator bool() const noexcept {
return !empty();
}
};
inline bool operator==(
const VideoEndpoint &a,
const VideoEndpoint &b) noexcept {
return (a.id == b.id);
}
inline bool operator!=(
const VideoEndpoint &a,
const VideoEndpoint &b) noexcept {
return !(a == b);
}
inline bool operator<(
const VideoEndpoint &a,
const VideoEndpoint &b) noexcept {
return (a.peer < b.peer)
|| (a.peer == b.peer && a.id < b.id);
}
inline bool operator>(
const VideoEndpoint &a,
const VideoEndpoint &b) noexcept {
return (b < a);
}
inline bool operator<=(
const VideoEndpoint &a,
const VideoEndpoint &b) noexcept {
return !(b < a);
}
inline bool operator>=(
const VideoEndpoint &a,
const VideoEndpoint &b) noexcept {
return !(a < b);
}
struct VideoStateToggle {
VideoEndpoint endpoint;
bool value = false;
};
struct VideoQualityRequest {
VideoEndpoint endpoint;
Group::VideoQuality quality = Group::VideoQuality();
};
struct ParticipantVideoParams;
[[nodiscard]] std::shared_ptr<ParticipantVideoParams> ParseVideoParams(
const tl::conditional<MTPGroupCallParticipantVideo> &camera,
const tl::conditional<MTPGroupCallParticipantVideo> &screen,
const std::shared_ptr<ParticipantVideoParams> &existing);
[[nodiscard]] const std::string &GetCameraEndpoint(
const std::shared_ptr<ParticipantVideoParams> &params);
[[nodiscard]] const std::string &GetScreenEndpoint(
const std::shared_ptr<ParticipantVideoParams> &params);
[[nodiscard]] bool IsCameraPaused(
const std::shared_ptr<ParticipantVideoParams> &params);
[[nodiscard]] bool IsScreenPaused(
const std::shared_ptr<ParticipantVideoParams> &params);
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;
virtual auto groupCallGetVideoCapture(const QString &deviceId)
-> std::shared_ptr<tgcalls::VideoCaptureInterface> = 0;
[[nodiscard]] virtual FnMut<void()> groupCallAddAsyncWaiter() = 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 handlePossibleCreateOrJoinResponse(
const MTPDupdateGroupCallConnection &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);
bool emitShareScreenError();
bool emitShareCameraError();
[[nodiscard]] rpl::producer<Group::Error> errors() const {
return _errors.events();
}
void addVideoOutput(
const std::string &endpoint,
not_null<Webrtc::VideoTrack*> track);
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]] auto videoStreamActiveUpdates() const
-> rpl::producer<VideoStateToggle> {
return _videoStreamActiveUpdates.events();
}
[[nodiscard]] auto videoStreamShownUpdates() const
-> rpl::producer<VideoStateToggle> {
return _videoStreamShownUpdates.events();
}
void requestVideoQuality(
const VideoEndpoint &endpoint,
Group::VideoQuality quality);
[[nodiscard]] bool videoEndpointPinned() const {
return _videoEndpointPinned.current();
}
[[nodiscard]] rpl::producer<bool> videoEndpointPinnedValue() const {
return _videoEndpointPinned.value();
}
void pinVideoEndpoint(VideoEndpoint endpoint);
void showVideoEndpointLarge(VideoEndpoint endpoint);
[[nodiscard]] const VideoEndpoint &videoEndpointLarge() const {
return _videoEndpointLarge.current();
}
[[nodiscard]] auto videoEndpointLargeValue() const
-> rpl::producer<VideoEndpoint> {
return _videoEndpointLarge.value();
}
struct VideoTrack {
std::unique_ptr<Webrtc::VideoTrack> track;
PeerData *peer = nullptr;
rpl::lifetime shownTrackingLifetime;
Group::VideoQuality quality = Group::VideoQuality();
[[nodiscard]] explicit operator bool() const {
return (track != nullptr);
}
[[nodiscard]] bool operator==(const VideoTrack &other) const {
return (track == other.track) && (peer == other.peer);
}
[[nodiscard]] bool operator!=(const VideoTrack &other) const {
return !(*this == other);
}
};
[[nodiscard]] auto activeVideoTracks() const
-> const base::flat_map<VideoEndpoint, VideoTrack> & {
return _activeVideoTracks;
}
[[nodiscard]] auto shownVideoTracks() const
-> const base::flat_set<VideoEndpoint> & {
return _shownVideoTracks;
}
[[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;
[[nodiscard]] bool mutedByAdmin() const;
[[nodiscard]] bool canManage() const;
[[nodiscard]] rpl::producer<bool> canManageValue() const;
[[nodiscard]] bool videoIsWorking() const {
return _videoIsWorking.current();
}
[[nodiscard]] rpl::producer<bool> videoIsWorkingValue() const {
return _videoIsWorking.value();
}
void setCurrentAudioDevice(bool input, const QString &deviceId);
void setCurrentVideoDevice(const QString &deviceId);
[[nodiscard]] bool isSharingScreen() const;
[[nodiscard]] rpl::producer<bool> isSharingScreenValue() const;
[[nodiscard]] bool isScreenPaused() const;
[[nodiscard]] const std::string &screenSharingEndpoint() const;
[[nodiscard]] bool isSharingCamera() const;
[[nodiscard]] rpl::producer<bool> isSharingCameraValue() const;
[[nodiscard]] bool isCameraPaused() const;
[[nodiscard]] const std::string &cameraSharingEndpoint() const;
[[nodiscard]] QString screenSharingDeviceId() const;
void toggleVideo(bool active);
void toggleScreenSharing(std::optional<QString> uniqueId);
[[nodiscard]] bool hasVideoWithFrames() const;
[[nodiscard]] rpl::producer<bool> hasVideoWithFramesValue() const;
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);
void setNotRequireARGB32();
[[nodiscard]] rpl::lifetime &lifetime() {
return _lifetime;
}
private:
class LoadPartTask;
class MediaChannelDescriptionsTask;
public:
void broadcastPartStart(std::shared_ptr<LoadPartTask> task);
void broadcastPartCancel(not_null<LoadPartTask*> task);
void mediaChannelDescriptionsStart(
std::shared_ptr<MediaChannelDescriptionsTask> task);
void mediaChannelDescriptionsCancel(
not_null<MediaChannelDescriptionsTask*> task);
private:
using GlobalShortcutValue = base::GlobalShortcutValue;
using Error = Group::Error;
struct SinkPointer;
static constexpr uint32 kDisabledSsrc = uint32(-1);
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 = 0x01,
RaiseHand = 0x02,
CameraStopped = 0x04,
CameraPaused = 0x08,
ScreenPaused = 0x10,
};
enum class JoinAction {
None,
Joining,
Leaving,
};
struct JoinState {
uint32 ssrc = 0;
JoinAction action = JoinAction::None;
bool nextActionPending = false;
void finish(uint32 updatedSsrc = 0) {
action = JoinAction::None;
ssrc = updatedSsrc;
}
};
friend inline constexpr bool is_flag_type(SendUpdateType) {
return true;
}
[[nodiscard]] bool mediaChannelDescriptionsFill(
not_null<MediaChannelDescriptionsTask*> task,
Fn<bool(uint32)> resolved = nullptr);
void checkMediaChannelDescriptions(Fn<bool(uint32)> resolved = nullptr);
void handlePossibleCreateOrJoinResponse(const MTPDgroupCall &data);
void handlePossibleDiscarded(const MTPDgroupCallDiscarded &data);
void handleUpdate(const MTPDupdateGroupCall &data);
void handleUpdate(const MTPDupdateGroupCallParticipants &data);
bool tryCreateController();
void destroyController();
bool tryCreateScreencast();
void destroyScreencast();
void emitShareCameraError(Error error);
void emitShareScreenError(Error error);
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 leave();
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 rejoinPresentation();
void leavePresentation();
void checkNextJoinAction();
void audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data);
void setInstanceConnected(tgcalls::GroupNetworkState networkState);
void setInstanceMode(InstanceMode mode);
void setScreenInstanceConnected(tgcalls::GroupNetworkState networkState);
void setScreenInstanceMode(InstanceMode mode);
void checkLastSpoke();
void pushToTalkCancel();
void checkGlobalShortcutAvailability();
void checkJoined();
void checkFirstTimeJoined();
void notifyAboutAllowedToSpeak();
void playConnectingSound();
void stopConnectingSound();
void playConnectingSoundOnce();
void updateRequestedVideoChannels();
void updateRequestedVideoChannelsDelayed();
void fillActiveVideoEndpoints();
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 sendPendingSelfUpdates();
void applySelfUpdate(const MTPDgroupCallParticipant &data);
void applyOtherParticipantUpdate(const MTPDgroupCallParticipant &data);
void setupMediaDevices();
void setupOutgoingVideo();
void setScreenEndpoint(std::string endpoint);
void setCameraEndpoint(std::string endpoint);
void addVideoOutput(const std::string &endpoint, SinkPointer sink);
void setVideoEndpointLarge(VideoEndpoint endpoint);
void markEndpointActive(
VideoEndpoint endpoint,
bool active,
bool paused);
void markTrackPaused(const VideoEndpoint &endpoint, bool paused);
void markTrackShown(const VideoEndpoint &endpoint, bool shown);
[[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;
base::flat_set<uint32> _unresolvedSsrcs;
rpl::event_stream<Error> _errors;
bool _recordingStoppedByMe = false;
bool _requestedVideoChannelsUpdateScheduled = false;
MTP::DcId _broadcastDcId = 0;
base::flat_map<not_null<LoadPartTask*>, LoadingPart> _broadcastParts;
base::flat_set<
std::shared_ptr<
MediaChannelDescriptionsTask>,
base::pointer_comparator<MediaChannelDescriptionsTask>> _mediaChannelDescriptionses;
not_null<PeerData*> _joinAs;
std::vector<not_null<PeerData*>> _possibleJoinAs;
QString _joinHash;
rpl::variable<MuteState> _muted = MuteState::Muted;
rpl::variable<bool> _canManage = false;
rpl::variable<bool> _videoIsWorking = false;
bool _initialMuteStateSent = false;
bool _acceptFields = false;
rpl::event_stream<Group::ParticipantState> _otherParticipantStateValue;
std::vector<MTPGroupCallParticipant> _queuedSelfUpdates;
uint64 _id = 0;
uint64 _accessHash = 0;
JoinState _joinState;
JoinState _screenJoinState;
std::string _cameraEndpoint;
std::string _screenEndpoint;
TimeId _scheduleDate = 0;
base::flat_set<uint32> _mySsrcs;
mtpRequestId _createRequestId = 0;
mtpRequestId _selfUpdateRequestId = 0;
rpl::variable<InstanceState> _instanceState
= InstanceState::Disconnected;
bool _instanceTransitioning = false;
InstanceMode _instanceMode = InstanceMode::None;
std::unique_ptr<tgcalls::GroupInstanceCustomImpl> _instance;
base::has_weak_ptr _instanceGuard;
std::shared_ptr<tgcalls::VideoCaptureInterface> _cameraCapture;
rpl::variable<Webrtc::VideoState> _cameraState;
rpl::variable<bool> _isSharingCamera = false;
base::flat_map<std::string, SinkPointer> _pendingVideoOutputs;
rpl::variable<InstanceState> _screenInstanceState
= InstanceState::Disconnected;
InstanceMode _screenInstanceMode = InstanceMode::None;
std::unique_ptr<tgcalls::GroupInstanceCustomImpl> _screenInstance;
base::has_weak_ptr _screenInstanceGuard;
std::shared_ptr<tgcalls::VideoCaptureInterface> _screenCapture;
rpl::variable<Webrtc::VideoState> _screenState;
rpl::variable<bool> _isSharingScreen = false;
QString _screenDeviceId;
base::flags<SendUpdateType> _pendingSelfUpdates;
bool _requireARGB32 = true;
rpl::event_stream<LevelUpdate> _levelUpdates;
rpl::event_stream<VideoStateToggle> _videoStreamActiveUpdates;
rpl::event_stream<VideoStateToggle> _videoStreamPausedUpdates;
rpl::event_stream<VideoStateToggle> _videoStreamShownUpdates;
base::flat_map<VideoEndpoint, VideoTrack> _activeVideoTracks;
base::flat_set<VideoEndpoint> _shownVideoTracks;
rpl::variable<VideoEndpoint> _videoEndpointLarge;
rpl::variable<bool> _videoEndpointPinned = false;
crl::time _videoLargeTillTime = 0;
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;
QString _cameraInputId;
rpl::lifetime _lifetime;
};
} // namespace Calls

View File

@@ -13,6 +13,7 @@ namespace Calls::Group {
constexpr auto kDefaultVolume = 10000;
constexpr auto kMaxVolume = 20000;
constexpr auto kBlobsEnterDuration = crl::time(250);
struct MuteRequest {
not_null<PeerData*> peer;
@@ -47,4 +48,24 @@ struct JoinInfo {
TimeId scheduleDate = 0;
};
enum class PanelMode {
Default,
Wide,
};
enum class VideoQuality {
Thumbnail,
Medium,
Full,
};
enum class Error {
NoCamera,
ScreenFailed,
MutedNoCamera,
MutedNoScreen,
DisabledNoCamera,
DisabledNoScreen,
};
} // namespace Calls::Group

View File

@@ -0,0 +1,305 @@
/*
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/group/calls_group_invite_controller.h"
#include "calls/group/calls_group_call.h"
#include "calls/group/calls_group_menu.h"
#include "boxes/peer_lists_box.h"
#include "data/data_user.h"
#include "data/data_channel.h"
#include "data/data_session.h"
#include "data/data_group_call.h"
#include "main/main_session.h"
#include "ui/text/text_utilities.h"
#include "ui/layers/generic_box.h"
#include "ui/widgets/labels.h"
#include "apiwrap.h"
#include "lang/lang_keys.h"
#include "styles/style_calls.h"
namespace Calls::Group {
namespace {
[[nodiscard]] object_ptr<Ui::RpWidget> CreateSectionSubtitle(
QWidget *parent,
rpl::producer<QString> text) {
auto result = object_ptr<Ui::FixedHeightWidget>(
parent,
st::searchedBarHeight);
const auto raw = result.data();
raw->paintRequest(
) | rpl::start_with_next([=](QRect clip) {
auto p = QPainter(raw);
p.fillRect(clip, st::groupCallMembersBgOver);
}, raw->lifetime());
const auto label = Ui::CreateChild<Ui::FlatLabel>(
raw,
std::move(text),
st::groupCallBoxLabel);
raw->widthValue(
) | rpl::start_with_next([=](int width) {
const auto padding = st::groupCallInviteDividerPadding;
const auto available = width - padding.left() - padding.right();
label->resizeToNaturalWidth(available);
label->moveToLeft(padding.left(), padding.top(), width);
}, label->lifetime());
return result;
}
} // namespace
InviteController::InviteController(
not_null<PeerData*> peer,
base::flat_set<not_null<UserData*>> alreadyIn)
: ParticipantsBoxController(CreateTag{}, nullptr, peer, Role::Members)
, _peer(peer)
, _alreadyIn(std::move(alreadyIn)) {
SubscribeToMigration(
_peer,
lifetime(),
[=](not_null<ChannelData*> channel) { _peer = channel; });
}
void InviteController::prepare() {
delegate()->peerListSetHideEmpty(true);
ParticipantsBoxController::prepare();
delegate()->peerListSetAboveWidget(CreateSectionSubtitle(
nullptr,
tr::lng_group_call_invite_members()));
delegate()->peerListSetAboveSearchWidget(CreateSectionSubtitle(
nullptr,
tr::lng_group_call_invite_members()));
}
void InviteController::rowClicked(not_null<PeerListRow*> row) {
delegate()->peerListSetRowChecked(row, !row->checked());
}
base::unique_qptr<Ui::PopupMenu> InviteController::rowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) {
return nullptr;
}
void InviteController::itemDeselectedHook(not_null<PeerData*> peer) {
}
bool InviteController::hasRowFor(not_null<PeerData*> peer) const {
return (delegate()->peerListFindRow(peer->id.value) != nullptr);
}
bool InviteController::isAlreadyIn(not_null<UserData*> user) const {
return _alreadyIn.contains(user);
}
std::unique_ptr<PeerListRow> InviteController::createRow(
not_null<PeerData*> participant) const {
const auto user = participant->asUser();
if (!user
|| user->isSelf()
|| user->isBot()
|| (user->flags() & MTPDuser::Flag::f_deleted)) {
return nullptr;
}
auto result = std::make_unique<PeerListRow>(user);
_rowAdded.fire_copy(user);
_inGroup.emplace(user);
if (isAlreadyIn(user)) {
result->setDisabledState(PeerListRow::State::DisabledChecked);
}
return result;
}
auto InviteController::peersWithRows() const
-> not_null<const base::flat_set<not_null<UserData*>>*> {
return &_inGroup;
}
rpl::producer<not_null<UserData*>> InviteController::rowAdded() const {
return _rowAdded.events();
}
InviteContactsController::InviteContactsController(
not_null<PeerData*> peer,
base::flat_set<not_null<UserData*>> alreadyIn,
not_null<const base::flat_set<not_null<UserData*>>*> inGroup,
rpl::producer<not_null<UserData*>> discoveredInGroup)
: AddParticipantsBoxController(peer, std::move(alreadyIn))
, _inGroup(inGroup)
, _discoveredInGroup(std::move(discoveredInGroup)) {
}
void InviteContactsController::prepareViewHook() {
AddParticipantsBoxController::prepareViewHook();
delegate()->peerListSetAboveWidget(CreateSectionSubtitle(
nullptr,
tr::lng_contacts_header()));
delegate()->peerListSetAboveSearchWidget(CreateSectionSubtitle(
nullptr,
tr::lng_group_call_invite_search_results()));
std::move(
_discoveredInGroup
) | rpl::start_with_next([=](not_null<UserData*> user) {
if (auto row = delegate()->peerListFindRow(user->id.value)) {
delegate()->peerListRemoveRow(row);
}
}, _lifetime);
}
std::unique_ptr<PeerListRow> InviteContactsController::createRow(
not_null<UserData*> user) {
return _inGroup->contains(user)
? nullptr
: AddParticipantsBoxController::createRow(user);
}
object_ptr<Ui::BoxContent> PrepareInviteBox(
not_null<GroupCall*> call,
Fn<void(TextWithEntities&&)> showToast) {
const auto real = call->lookupReal();
if (!real) {
return nullptr;
}
const auto peer = call->peer();
auto alreadyIn = peer->owner().invitedToCallUsers(real->id());
for (const auto &participant : real->participants()) {
if (const auto user = participant.peer->asUser()) {
alreadyIn.emplace(user);
}
}
alreadyIn.emplace(peer->session().user());
auto controller = std::make_unique<InviteController>(peer, alreadyIn);
controller->setStyleOverrides(
&st::groupCallInviteMembersList,
&st::groupCallMultiSelect);
auto contactsController = std::make_unique<InviteContactsController>(
peer,
std::move(alreadyIn),
controller->peersWithRows(),
controller->rowAdded());
contactsController->setStyleOverrides(
&st::groupCallInviteMembersList,
&st::groupCallMultiSelect);
const auto weak = base::make_weak(call.get());
const auto invite = [=](const std::vector<not_null<UserData*>> &users) {
const auto call = weak.get();
if (!call) {
return;
}
const auto result = call->inviteUsers(users);
if (const auto user = std::get_if<not_null<UserData*>>(&result)) {
showToast(tr::lng_group_call_invite_done_user(
tr::now,
lt_user,
Ui::Text::Bold((*user)->firstName),
Ui::Text::WithEntities));
} else if (const auto count = std::get_if<int>(&result)) {
if (*count > 0) {
showToast(tr::lng_group_call_invite_done_many(
tr::now,
lt_count,
*count,
Ui::Text::RichLangValue));
}
} else {
Unexpected("Result in GroupCall::inviteUsers.");
}
};
const auto inviteWithAdd = [=](
const std::vector<not_null<UserData*>> &users,
const std::vector<not_null<UserData*>> &nonMembers,
Fn<void()> finish) {
peer->session().api().addChatParticipants(
peer,
nonMembers,
[=](bool) { invite(users); finish(); });
};
const auto inviteWithConfirmation = [=](
not_null<PeerListsBox*> parentBox,
const std::vector<not_null<UserData*>> &users,
const std::vector<not_null<UserData*>> &nonMembers,
Fn<void()> finish) {
if (nonMembers.empty()) {
invite(users);
finish();
return;
}
const auto name = peer->name;
const auto text = (nonMembers.size() == 1)
? tr::lng_group_call_add_to_group_one(
tr::now,
lt_user,
nonMembers.front()->shortName(),
lt_group,
name)
: (nonMembers.size() < users.size())
? tr::lng_group_call_add_to_group_some(tr::now, lt_group, name)
: tr::lng_group_call_add_to_group_all(tr::now, lt_group, name);
const auto shared = std::make_shared<QPointer<Ui::GenericBox>>();
const auto finishWithConfirm = [=] {
if (*shared) {
(*shared)->closeBox();
}
finish();
};
const auto done = [=] {
inviteWithAdd(users, nonMembers, finishWithConfirm);
};
auto box = ConfirmBox({
.text = { text },
.button = tr::lng_participant_invite(),
.callback = done,
});
*shared = box.data();
parentBox->getDelegate()->showBox(
std::move(box),
Ui::LayerOption::KeepOther,
anim::type::normal);
};
auto initBox = [=, controller = controller.get()](
not_null<PeerListsBox*> box) {
box->setTitle(tr::lng_group_call_invite_title());
box->addButton(tr::lng_group_call_invite_button(), [=] {
const auto rows = box->collectSelectedRows();
const auto users = ranges::views::all(
rows
) | ranges::views::transform([](not_null<PeerData*> peer) {
return not_null<UserData*>(peer->asUser());
}) | ranges::to_vector;
const auto nonMembers = ranges::views::all(
users
) | ranges::views::filter([&](not_null<UserData*> user) {
return !controller->hasRowFor(user);
}) | ranges::to_vector;
const auto finish = [box = Ui::MakeWeak(box)]() {
if (box) {
box->closeBox();
}
};
inviteWithConfirmation(box, users, nonMembers, finish);
});
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
};
auto controllers = std::vector<std::unique_ptr<PeerListController>>();
controllers.push_back(std::move(controller));
controllers.push_back(std::move(contactsController));
return Box<PeerListsBox>(std::move(controllers), initBox);
}
} // namespace Calls::Group

View File

@@ -0,0 +1,82 @@
/*
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 "boxes/peers/edit_participants_box.h"
#include "boxes/peers/add_participants_box.h"
namespace Calls {
class GroupCall;
} // namespace Calls
namespace Calls::Group {
class InviteController final : public ParticipantsBoxController {
public:
InviteController(
not_null<PeerData*> peer,
base::flat_set<not_null<UserData*>> alreadyIn);
void prepare() override;
void rowClicked(not_null<PeerListRow*> row) override;
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) override;
void itemDeselectedHook(not_null<PeerData*> peer) override;
[[nodiscard]] auto peersWithRows() const
-> not_null<const base::flat_set<not_null<UserData*>>*>;
[[nodiscard]] rpl::producer<not_null<UserData*>> rowAdded() const;
[[nodiscard]] bool hasRowFor(not_null<PeerData*> peer) const;
private:
[[nodiscard]] bool isAlreadyIn(not_null<UserData*> user) const;
std::unique_ptr<PeerListRow> createRow(
not_null<PeerData*> participant) const override;
not_null<PeerData*> _peer;
const base::flat_set<not_null<UserData*>> _alreadyIn;
mutable base::flat_set<not_null<UserData*>> _inGroup;
rpl::event_stream<not_null<UserData*>> _rowAdded;
};
class InviteContactsController final : public AddParticipantsBoxController {
public:
InviteContactsController(
not_null<PeerData*> peer,
base::flat_set<not_null<UserData*>> alreadyIn,
not_null<const base::flat_set<not_null<UserData*>>*> inGroup,
rpl::producer<not_null<UserData*>> discoveredInGroup);
private:
void prepareViewHook() override;
std::unique_ptr<PeerListRow> createRow(
not_null<UserData*> user) override;
bool needsInviteLinkButton() override {
return false;
}
const not_null<const base::flat_set<not_null<UserData*>>*> _inGroup;
rpl::producer<not_null<UserData*>> _discoveredInGroup;
rpl::lifetime _lifetime;
};
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareInviteBox(
not_null<GroupCall*> call,
Fn<void(TextWithEntities&&)> showToast);
} // namespace Calls::Group

View File

@@ -10,8 +10,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peer_list_box.h"
namespace Ui {
class RpWidget;
class ScrollArea;
class VerticalLayout;
class SettingsButton;
namespace GL {
enum class Backend;
} // namespace GL
} // namespace Ui
namespace Data {
@@ -24,8 +29,11 @@ class GroupCall;
namespace Calls::Group {
class Viewport;
class MembersRow;
struct VolumeRequest;
struct MuteRequest;
enum class PanelMode;
class Members final
: public Ui::RpWidget
@@ -33,8 +41,12 @@ class Members final
public:
Members(
not_null<QWidget*> parent,
not_null<GroupCall*> call);
not_null<GroupCall*> call,
PanelMode mode,
Ui::GL::Backend backend);
~Members();
[[nodiscard]] not_null<Viewport*> viewport() const;
[[nodiscard]] int desiredHeight() const;
[[nodiscard]] rpl::producer<int> desiredHeightValue() const override;
[[nodiscard]] rpl::producer<int> fullCountValue() const;
@@ -48,7 +60,14 @@ public:
return _addMemberRequests.events();
}
[[nodiscard]] MembersRow *lookupRow(not_null<PeerData*> peer) const;
void setMode(PanelMode mode);
[[nodiscard]] QRect getInnerGeometry() const;
private:
class Controller;
struct VideoTile;
using ListWidget = PeerListContent;
void resizeEvent(QResizeEvent *e) override;
@@ -73,14 +92,20 @@ private:
void setupList();
void setupFakeRoundCorners();
void trackViewportGeometry();
void updateControlsGeometry();
const not_null<GroupCall*> _call;
rpl::variable<PanelMode> _mode = PanelMode();
object_ptr<Ui::ScrollArea> _scroll;
std::unique_ptr<PeerListController> _listController;
object_ptr<Ui::SettingsButton> _addMember = { nullptr };
rpl::variable<Ui::SettingsButton*> _addMemberButton = nullptr;
ListWidget *_list = { nullptr };
std::unique_ptr<Controller> _listController;
not_null<Ui::VerticalLayout*> _layout;
const not_null<Ui::RpWidget*> _videoWrap;
std::unique_ptr<Viewport> _viewport;
rpl::variable<Ui::RpWidget*> _addMemberButton = nullptr;
RpWidget *_topSkip = nullptr;
RpWidget *_bottomSkip = nullptr;
ListWidget *_list = nullptr;
rpl::event_stream<> _addMemberRequests;
rpl::variable<bool> _canAddMembers;

View File

@@ -0,0 +1,773 @@
/*
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/group/calls_group_members_row.h"
#include "calls/group/calls_group_call.h"
#include "calls/group/calls_group_common.h"
#include "data/data_peer.h"
#include "data/data_group_call.h"
#include "ui/paint/arcs.h"
#include "ui/paint/blobs.h"
#include "ui/text/text_options.h"
#include "ui/effects/ripple_animation.h"
#include "lang/lang_keys.h"
#include "webrtc/webrtc_video_track.h"
#include "styles/style_calls.h"
namespace Calls::Group {
namespace {
constexpr auto kLevelDuration = 100. + 500. * 0.23;
constexpr auto kBlobScale = 0.605;
constexpr auto kMinorBlobFactor = 0.9f;
constexpr auto kUserpicMinScale = 0.8;
constexpr auto kMaxLevel = 1.;
constexpr auto kWideScale = 5;
constexpr auto kArcsStrokeRatio = 0.8;
const auto kSpeakerThreshold = std::vector<float>{
Group::kDefaultVolume * 0.1f / Group::kMaxVolume,
Group::kDefaultVolume * 0.9f / Group::kMaxVolume };
auto RowBlobs() -> std::array<Ui::Paint::Blobs::BlobData, 2> {
return { {
{
.segmentsCount = 6,
.minScale = kBlobScale * kMinorBlobFactor,
.minRadius = st::groupCallRowBlobMinRadius * kMinorBlobFactor,
.maxRadius = st::groupCallRowBlobMaxRadius * kMinorBlobFactor,
.speedScale = 1.,
.alpha = .5,
},
{
.segmentsCount = 8,
.minScale = kBlobScale,
.minRadius = (float)st::groupCallRowBlobMinRadius,
.maxRadius = (float)st::groupCallRowBlobMaxRadius,
.speedScale = 1.,
.alpha = .2,
},
} };
}
[[nodiscard]] QString StatusPercentString(float volume) {
return QString::number(int(std::round(volume * 200))) + '%';
}
[[nodiscard]] int StatusPercentWidth(const QString &percent) {
return st::normalFont->width(percent);
}
} // namespace
struct MembersRow::BlobsAnimation {
BlobsAnimation(
std::vector<Ui::Paint::Blobs::BlobData> blobDatas,
float levelDuration,
float maxLevel);
Ui::Paint::Blobs blobs;
crl::time lastTime = 0;
crl::time lastSoundingUpdateTime = 0;
float64 enter = 0.;
QImage userpicCache;
InMemoryKey userpicKey;
rpl::lifetime lifetime;
};
struct MembersRow::StatusIcon {
StatusIcon(bool shown, float volume);
const style::icon &speaker;
Ui::Paint::ArcsAnimation arcs;
Ui::Animations::Simple arcsAnimation;
Ui::Animations::Simple shownAnimation;
QString percent;
int percentWidth = 0;
int arcsWidth = 0;
int wasArcsWidth = 0;
bool shown = true;
rpl::lifetime lifetime;
};
MembersRow::BlobsAnimation::BlobsAnimation(
std::vector<Ui::Paint::Blobs::BlobData> blobDatas,
float levelDuration,
float maxLevel)
: blobs(std::move(blobDatas), levelDuration, maxLevel) {
style::PaletteChanged(
) | rpl::start_with_next([=] {
userpicCache = QImage();
}, lifetime);
}
MembersRow::StatusIcon::StatusIcon(bool shown, float volume)
: speaker(st::groupCallStatusSpeakerIcon)
, arcs(
st::groupCallStatusSpeakerArcsAnimation,
kSpeakerThreshold,
volume,
Ui::Paint::ArcsAnimation::Direction::Right)
, percent(StatusPercentString(volume))
, percentWidth(StatusPercentWidth(percent))
, shown(shown) {
}
MembersRow::MembersRow(
not_null<MembersRowDelegate*> delegate,
not_null<PeerData*> participantPeer)
: PeerListRow(participantPeer)
, _delegate(delegate)
, _sounding(false)
, _speaking(false)
, _raisedHandStatus(false)
, _skipLevelUpdate(false)
, _mutedByMe(false) {
refreshStatus();
_aboutText = participantPeer->about();
}
MembersRow::~MembersRow() = default;
void MembersRow::setSkipLevelUpdate(bool value) {
_skipLevelUpdate = value;
}
void MembersRow::updateState(
const Data::GroupCallParticipant *participant) {
setSsrc(participant ? participant->ssrc : 0);
setVolume(participant
? participant->volume
: Group::kDefaultVolume);
if (!participant) {
setState(State::Invited);
setSounding(false);
setSpeaking(false);
_mutedByMe = false;
_raisedHandRating = 0;
} else if (!participant->muted
|| (participant->sounding && participant->ssrc != 0)) {
setState(State::Active);
setSounding(participant->sounding && participant->ssrc != 0);
setSpeaking(participant->speaking && participant->ssrc != 0);
_mutedByMe = participant->mutedByMe;
_raisedHandRating = 0;
} else if (participant->canSelfUnmute) {
setState(State::Inactive);
setSounding(false);
setSpeaking(false);
_mutedByMe = participant->mutedByMe;
_raisedHandRating = 0;
} else {
setSounding(false);
setSpeaking(false);
_mutedByMe = participant->mutedByMe;
_raisedHandRating = participant->raisedHandRating;
setState(_raisedHandRating ? State::RaisedHand : State::Muted);
}
refreshStatus();
}
void MembersRow::setSpeaking(bool speaking) {
if (_speaking == speaking) {
return;
}
_speaking = speaking;
_speakingAnimation.start(
[=] { _delegate->rowUpdateRow(this); },
_speaking ? 0. : 1.,
_speaking ? 1. : 0.,
st::widgetFadeDuration);
if (!_speaking
|| _mutedByMe
|| (_state == State::Muted)
|| (_state == State::RaisedHand)) {
if (_statusIcon) {
_statusIcon = nullptr;
_delegate->rowUpdateRow(this);
}
} else if (!_statusIcon) {
_statusIcon = std::make_unique<StatusIcon>(
(_volume != Group::kDefaultVolume),
(float)_volume / Group::kMaxVolume);
_statusIcon->arcs.setStrokeRatio(kArcsStrokeRatio);
_statusIcon->arcsWidth = _statusIcon->arcs.finishedWidth();
_statusIcon->arcs.startUpdateRequests(
) | rpl::start_with_next([=] {
if (!_statusIcon->arcsAnimation.animating()) {
_statusIcon->wasArcsWidth = _statusIcon->arcsWidth;
}
auto callback = [=](float64 value) {
_statusIcon->arcs.update(crl::now());
_statusIcon->arcsWidth = anim::interpolate(
_statusIcon->wasArcsWidth,
_statusIcon->arcs.finishedWidth(),
value);
_delegate->rowUpdateRow(this);
};
_statusIcon->arcsAnimation.start(
std::move(callback),
0.,
1.,
st::groupCallSpeakerArcsAnimation.duration);
}, _statusIcon->lifetime);
}
}
void MembersRow::setSounding(bool sounding) {
if (_sounding == sounding) {
return;
}
_sounding = sounding;
if (!_sounding) {
_blobsAnimation = nullptr;
} else if (!_blobsAnimation) {
_blobsAnimation = std::make_unique<BlobsAnimation>(
RowBlobs() | ranges::to_vector,
kLevelDuration,
kMaxLevel);
_blobsAnimation->lastTime = crl::now();
updateLevel(GroupCall::kSpeakLevelThreshold);
}
}
void MembersRow::clearRaisedHandStatus() {
if (!_raisedHandStatus) {
return;
}
_raisedHandStatus = false;
refreshStatus();
_delegate->rowUpdateRow(this);
}
void MembersRow::setState(State state) {
if (_state == state) {
return;
}
const auto wasActive = (_state == State::Active);
const auto wasMuted = (_state == State::Muted)
|| (_state == State::RaisedHand);
const auto wasRaisedHand = (_state == State::RaisedHand);
_state = state;
const auto nowActive = (_state == State::Active);
const auto nowMuted = (_state == State::Muted)
|| (_state == State::RaisedHand);
const auto nowRaisedHand = (_state == State::RaisedHand);
if (!wasRaisedHand && nowRaisedHand) {
_raisedHandStatus = true;
_delegate->rowScheduleRaisedHandStatusRemove(this);
}
if (nowActive != wasActive) {
_activeAnimation.start(
[=] { _delegate->rowUpdateRow(this); },
nowActive ? 0. : 1.,
nowActive ? 1. : 0.,
st::widgetFadeDuration);
}
if (nowMuted != wasMuted) {
_mutedAnimation.start(
[=] { _delegate->rowUpdateRow(this); },
nowMuted ? 0. : 1.,
nowMuted ? 1. : 0.,
st::widgetFadeDuration);
}
}
void MembersRow::setSsrc(uint32 ssrc) {
_ssrc = ssrc;
}
void MembersRow::setVolume(int volume) {
_volume = volume;
if (_statusIcon) {
const auto floatVolume = (float)volume / Group::kMaxVolume;
_statusIcon->arcs.setValue(floatVolume);
_statusIcon->percent = StatusPercentString(floatVolume);
_statusIcon->percentWidth = StatusPercentWidth(_statusIcon->percent);
const auto shown = (volume != Group::kDefaultVolume);
if (_statusIcon->shown != shown) {
_statusIcon->shown = shown;
_statusIcon->shownAnimation.start(
[=] { _delegate->rowUpdateRow(this); },
shown ? 0. : 1.,
shown ? 1. : 0.,
st::groupCallSpeakerArcsAnimation.duration);
}
}
}
void MembersRow::updateLevel(float level) {
Expects(_blobsAnimation != nullptr);
const auto spoke = (level >= GroupCall::kSpeakLevelThreshold)
? crl::now()
: crl::time();
if (spoke && _speaking) {
_speakingLastTime = spoke;
}
if (_skipLevelUpdate) {
return;
}
if (spoke) {
_blobsAnimation->lastSoundingUpdateTime = spoke;
}
_blobsAnimation->blobs.setLevel(level);
}
void MembersRow::updateBlobAnimation(crl::time now) {
Expects(_blobsAnimation != nullptr);
const auto soundingFinishesAt = _blobsAnimation->lastSoundingUpdateTime
+ Data::GroupCall::kSoundStatusKeptFor;
const auto soundingStartsFinishing = soundingFinishesAt
- kBlobsEnterDuration;
const auto soundingFinishes = (soundingStartsFinishing < now);
if (soundingFinishes) {
_blobsAnimation->enter = std::clamp(
(soundingFinishesAt - now) / float64(kBlobsEnterDuration),
0.,
1.);
} else if (_blobsAnimation->enter < 1.) {
_blobsAnimation->enter = std::clamp(
(_blobsAnimation->enter
+ ((now - _blobsAnimation->lastTime)
/ float64(kBlobsEnterDuration))),
0.,
1.);
}
_blobsAnimation->blobs.updateLevel(now - _blobsAnimation->lastTime);
_blobsAnimation->lastTime = now;
}
void MembersRow::ensureUserpicCache(
std::shared_ptr<Data::CloudImageView> &view,
int size) {
Expects(_blobsAnimation != nullptr);
const auto user = peer();
const auto key = user->userpicUniqueKey(view);
const auto full = QSize(size, size) * kWideScale * cIntRetinaFactor();
auto &cache = _blobsAnimation->userpicCache;
if (cache.isNull()) {
cache = QImage(full, QImage::Format_ARGB32_Premultiplied);
cache.setDevicePixelRatio(cRetinaFactor());
} else if (_blobsAnimation->userpicKey == key
&& cache.size() == full) {
return;
}
_blobsAnimation->userpicKey = key;
cache.fill(Qt::transparent);
{
Painter p(&cache);
const auto skip = (kWideScale - 1) / 2 * size;
user->paintUserpicLeft(p, view, skip, skip, kWideScale * size, size);
}
}
void MembersRow::paintBlobs(
Painter &p,
int x,
int y,
int sizew,
int sizeh,
PanelMode mode) {
if (!_blobsAnimation) {
return;
}
auto size = sizew;
const auto shift = QPointF(x + size / 2., y + size / 2.);
auto hq = PainterHighQualityEnabler(p);
p.translate(shift);
const auto brush = _mutedByMe
? st::groupCallMemberMutedIcon->b
: anim::brush(
st::groupCallMemberInactiveStatus,
st::groupCallMemberActiveStatus,
_speakingAnimation.value(_speaking ? 1. : 0.));
_blobsAnimation->blobs.paint(p, brush);
p.translate(-shift);
p.setOpacity(1.);
}
void MembersRow::paintScaledUserpic(
Painter &p,
std::shared_ptr<Data::CloudImageView> &userpic,
int x,
int y,
int outerWidth,
int sizew,
int sizeh,
PanelMode mode) {
auto size = sizew;
if (!_blobsAnimation) {
peer()->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
return;
}
const auto enter = _blobsAnimation->enter;
const auto &minScale = kUserpicMinScale;
const auto scaleUserpic = minScale
+ (1. - minScale) * _blobsAnimation->blobs.currentLevel();
const auto scale = scaleUserpic * enter + 1. * (1. - enter);
if (scale == 1.) {
peer()->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
return;
}
ensureUserpicCache(userpic, size);
PainterHighQualityEnabler hq(p);
auto target = QRect(
x + (1 - kWideScale) / 2 * size,
y + (1 - kWideScale) / 2 * size,
kWideScale * size,
kWideScale * size);
auto shrink = anim::interpolate(
(1 - kWideScale) / 2 * size,
0,
scale);
auto margins = QMargins(shrink, shrink, shrink, shrink);
p.drawImage(
target.marginsAdded(margins),
_blobsAnimation->userpicCache);
}
void MembersRow::paintMuteIcon(
Painter &p,
QRect iconRect,
MembersRowStyle style) {
_delegate->rowPaintIcon(p, iconRect, computeIconState(style));
}
auto MembersRow::generatePaintUserpicCallback() -> PaintRoundImageCallback {
return [=](Painter &p, int x, int y, int outerWidth, int size) {
const auto outer = outerWidth;
paintComplexUserpic(p, x, y, outer, size, size, PanelMode::Default);
};
}
void MembersRow::paintComplexUserpic(
Painter &p,
int x,
int y,
int outerWidth,
int sizew,
int sizeh,
PanelMode mode,
bool selected) {
paintBlobs(p, x, y, sizew, sizeh, mode);
paintScaledUserpic(
p,
ensureUserpicView(),
x,
y,
outerWidth,
sizew,
sizeh,
mode);
}
int MembersRow::statusIconWidth(bool skipIcon) const {
if (!_statusIcon || !_speaking) {
return 0;
}
const auto shown = _statusIcon->shownAnimation.value(
_statusIcon->shown ? 1. : 0.);
const auto iconWidth = skipIcon
? 0
: (_statusIcon->speaker.width() + _statusIcon->arcsWidth);
const auto full = iconWidth
+ _statusIcon->percentWidth
+ st::normalFont->spacew;
return int(std::round(shown * full));
}
int MembersRow::statusIconHeight() const {
return (_statusIcon && _speaking) ? _statusIcon->speaker.height() : 0;
}
void MembersRow::paintStatusIcon(
Painter &p,
int x,
int y,
const style::PeerListItem &st,
const style::font &font,
bool selected,
bool skipIcon) {
if (!_statusIcon) {
return;
}
const auto shown = _statusIcon->shownAnimation.value(
_statusIcon->shown ? 1. : 0.);
if (shown == 0.) {
return;
}
p.setFont(font);
const auto color = (_speaking
? st.statusFgActive
: (selected ? st.statusFgOver : st.statusFg))->c;
p.setPen(color);
const auto speakerRect = QRect(
QPoint(x, y + (font->height - statusIconHeight()) / 2),
_statusIcon->speaker.size());
const auto arcPosition = speakerRect.topLeft()
+ QPoint(
speakerRect.width() - st::groupCallStatusSpeakerArcsSkip,
speakerRect.height() / 2);
const auto iconWidth = skipIcon
? 0
: (speakerRect.width() + _statusIcon->arcsWidth);
const auto fullWidth = iconWidth
+ _statusIcon->percentWidth
+ st::normalFont->spacew;
p.save();
if (shown < 1.) {
const auto centerx = speakerRect.x() + fullWidth / 2;
const auto centery = speakerRect.y() + speakerRect.height() / 2;
p.translate(centerx, centery);
p.scale(shown, shown);
p.translate(-centerx, -centery);
}
if (!skipIcon) {
_statusIcon->speaker.paint(
p,
speakerRect.topLeft(),
speakerRect.width(),
color);
p.translate(arcPosition);
_statusIcon->arcs.paint(p, color);
p.translate(-arcPosition);
}
p.setFont(st::normalFont);
p.setPen(st.statusFgActive);
p.drawTextLeft(
x + iconWidth,
y,
fullWidth,
_statusIcon->percent);
p.restore();
}
void MembersRow::setAbout(const QString &about) {
if (_aboutText == about) {
return;
}
_aboutText = about;
_delegate->rowUpdateRow(this);
}
void MembersRow::paintStatusText(
Painter &p,
const style::PeerListItem &st,
int x,
int y,
int availableWidth,
int outerWidth,
bool selected) {
paintComplexStatusText(
p,
st,
x,
y,
availableWidth,
outerWidth,
selected,
MembersRowStyle::Default);
}
void MembersRow::paintComplexStatusText(
Painter &p,
const style::PeerListItem &st,
int x,
int y,
int availableWidth,
int outerWidth,
bool selected,
MembersRowStyle style) {
const auto skip = (style == MembersRowStyle::Default)
? _delegate->rowPaintStatusIcon(
p,
x,
y,
outerWidth,
this,
computeIconState(MembersRowStyle::Narrow))
: 0;
const auto narrowMode = (skip > 0);
x += skip;
availableWidth -= skip;
const auto &font = st::normalFont;
const auto about = (style == MembersRowStyle::Video)
? QString()
: ((_state == State::RaisedHand && !_raisedHandStatus)
|| (_state != State::RaisedHand && !_speaking))
? _aboutText
: QString();
if (about.isEmpty()
&& _state != State::Invited
&& !_mutedByMe) {
paintStatusIcon(p, x, y, st, font, selected, narrowMode);
const auto translatedWidth = statusIconWidth(narrowMode);
p.translate(translatedWidth, 0);
const auto guard = gsl::finally([&] {
p.translate(-translatedWidth, 0);
});
const auto &style = (!narrowMode
|| (_state == State::RaisedHand && _raisedHandStatus))
? st
: st::groupCallNarrowMembersListItem;
PeerListRow::paintStatusText(
p,
style,
x,
y,
availableWidth - translatedWidth,
outerWidth,
selected);
return;
}
p.setFont(font);
if (style == MembersRowStyle::Video) {
p.setPen(st::groupCallVideoSubTextFg);
} else if (_mutedByMe) {
p.setPen(st::groupCallMemberMutedIcon);
} else {
p.setPen(st::groupCallMemberNotJoinedStatus);
}
p.drawTextLeft(
x,
y,
outerWidth,
(_mutedByMe
? tr::lng_group_call_muted_by_me_status(tr::now)
: !about.isEmpty()
? font->m.elidedText(about, Qt::ElideRight, availableWidth)
: _delegate->rowIsMe(peer())
? tr::lng_status_connecting(tr::now)
: tr::lng_group_call_invited_status(tr::now)));
}
QSize MembersRow::actionSize() const {
return _delegate->rowIsNarrow() ? QSize() : QSize(
st::groupCallActiveButton.width,
st::groupCallActiveButton.height);
}
bool MembersRow::actionDisabled() const {
return _delegate->rowIsMe(peer())
|| (_state == State::Invited)
|| !_delegate->rowCanMuteMembers();
}
QMargins MembersRow::actionMargins() const {
return QMargins(
0,
0,
st::groupCallMemberButtonSkip,
0);
}
void MembersRow::paintAction(
Painter &p,
int x,
int y,
int outerWidth,
bool selected,
bool actionSelected) {
auto size = actionSize();
const auto iconRect = style::rtlrect(
x,
y,
size.width(),
size.height(),
outerWidth);
if (_state == State::Invited) {
_actionRipple = nullptr;
}
if (_actionRipple) {
_actionRipple->paint(
p,
x + st::groupCallActiveButton.rippleAreaPosition.x(),
y + st::groupCallActiveButton.rippleAreaPosition.y(),
outerWidth);
if (_actionRipple->empty()) {
_actionRipple.reset();
}
}
paintMuteIcon(p, iconRect);
}
MembersRowDelegate::IconState MembersRow::computeIconState(
MembersRowStyle style) const {
const auto speaking = _speakingAnimation.value(_speaking ? 1. : 0.);
const auto active = _activeAnimation.value(
(_state == State::Active) ? 1. : 0.);
const auto muted = _mutedAnimation.value(
(_state == State::Muted || _state == State::RaisedHand) ? 1. : 0.);
return {
.speaking = speaking,
.active = active,
.muted = muted,
.mutedByMe = _mutedByMe,
.raisedHand = (_state == State::RaisedHand),
.invited = (_state == State::Invited),
.style = style,
};
}
void MembersRow::showContextMenu() {
return _delegate->rowShowContextMenu(this);
}
void MembersRow::refreshStatus() {
setCustomStatus(
(_speaking
? tr::lng_group_call_active(tr::now)
: _raisedHandStatus
? tr::lng_group_call_raised_hand_status(tr::now)
: tr::lng_group_call_inactive(tr::now)),
_speaking);
}
void MembersRow::addActionRipple(QPoint point, Fn<void()> updateCallback) {
if (!_actionRipple) {
auto mask = Ui::RippleAnimation::ellipseMask(QSize(
st::groupCallActiveButton.rippleAreaSize,
st::groupCallActiveButton.rippleAreaSize));
_actionRipple = std::make_unique<Ui::RippleAnimation>(
st::groupCallActiveButton.ripple,
std::move(mask),
std::move(updateCallback));
}
_actionRipple->add(point - st::groupCallActiveButton.rippleAreaPosition);
}
void MembersRow::refreshName(const style::PeerListItem &st) {
PeerListRow::refreshName(st);
//_narrowName = Ui::Text::String();
}
void MembersRow::stopLastActionRipple() {
if (_actionRipple) {
_actionRipple->lastStop();
}
}
} // namespace Calls::Group

View File

@@ -0,0 +1,225 @@
/*
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 "boxes/peer_list_box.h"
#include "calls/group/calls_group_common.h"
class PeerData;
class Painter;
namespace Data {
struct GroupCallParticipant;
} // namespace Data
namespace Ui {
class RippleAnimation;
} // namespace Ui
namespace Calls::Group {
enum class MembersRowStyle {
Default,
Narrow,
Video,
};
class MembersRow;
class MembersRowDelegate {
public:
struct IconState {
float64 speaking = 0.;
float64 active = 0.;
float64 muted = 0.;
bool mutedByMe = false;
bool raisedHand = false;
bool invited = false;
MembersRowStyle style = MembersRowStyle::Default;
};
virtual bool rowIsMe(not_null<PeerData*> participantPeer) = 0;
virtual bool rowCanMuteMembers() = 0;
virtual void rowUpdateRow(not_null<MembersRow*> row) = 0;
virtual void rowScheduleRaisedHandStatusRemove(
not_null<MembersRow*> row) = 0;
virtual void rowPaintIcon(
Painter &p,
QRect rect,
const IconState &state) = 0;
virtual int rowPaintStatusIcon(
Painter &p,
int x,
int y,
int outerWidth,
not_null<MembersRow*> row,
const IconState &state) = 0;
virtual bool rowIsNarrow() = 0;
virtual void rowShowContextMenu(not_null<PeerListRow*> row) = 0;
};
class MembersRow final : public PeerListRow {
public:
MembersRow(
not_null<MembersRowDelegate*> delegate,
not_null<PeerData*> participantPeer);
~MembersRow();
enum class State {
Active,
Inactive,
Muted,
RaisedHand,
Invited,
};
void setAbout(const QString &about);
void setSkipLevelUpdate(bool value);
void updateState(const Data::GroupCallParticipant *participant);
void updateLevel(float level);
void updateBlobAnimation(crl::time now);
void clearRaisedHandStatus();
[[nodiscard]] State state() const {
return _state;
}
[[nodiscard]] uint32 ssrc() const {
return _ssrc;
}
[[nodiscard]] bool sounding() const {
return _sounding;
}
[[nodiscard]] bool speaking() const {
return _speaking;
}
[[nodiscard]] bool mutedByMe() const {
return _mutedByMe;
}
[[nodiscard]] crl::time speakingLastTime() const {
return _speakingLastTime;
}
[[nodiscard]] int volume() const {
return _volume;
}
[[nodiscard]] uint64 raisedHandRating() const {
return _raisedHandRating;
}
void addActionRipple(QPoint point, Fn<void()> updateCallback) override;
void stopLastActionRipple() override;
void refreshName(const style::PeerListItem &st) override;
QSize actionSize() const override;
bool actionDisabled() const override;
QMargins actionMargins() const override;
void paintAction(
Painter &p,
int x,
int y,
int outerWidth,
bool selected,
bool actionSelected) override;
PaintRoundImageCallback generatePaintUserpicCallback() override;
void paintComplexUserpic(
Painter &p,
int x,
int y,
int outerWidth,
int sizew,
int sizeh,
PanelMode mode,
bool selected = false);
void paintStatusText(
Painter &p,
const style::PeerListItem &st,
int x,
int y,
int availableWidth,
int outerWidth,
bool selected) override;
void paintComplexStatusText(
Painter &p,
const style::PeerListItem &st,
int x,
int y,
int availableWidth,
int outerWidth,
bool selected,
MembersRowStyle style);
void paintMuteIcon(
Painter &p,
QRect iconRect,
MembersRowStyle style = MembersRowStyle::Default);
[[nodiscard]] MembersRowDelegate::IconState computeIconState(
MembersRowStyle style = MembersRowStyle::Default) const;
void showContextMenu();
private:
struct BlobsAnimation;
struct StatusIcon;
int statusIconWidth(bool skipIcon) const;
int statusIconHeight() const;
void paintStatusIcon(
Painter &p,
int x,
int y,
const style::PeerListItem &st,
const style::font &font,
bool selected,
bool skipIcon);
void refreshStatus() override;
void setSounding(bool sounding);
void setSpeaking(bool speaking);
void setState(State state);
void setSsrc(uint32 ssrc);
void setVolume(int volume);
void ensureUserpicCache(
std::shared_ptr<Data::CloudImageView> &view,
int size);
void paintBlobs(
Painter &p,
int x,
int y,
int sizew,
int sizeh, PanelMode mode);
void paintScaledUserpic(
Painter &p,
std::shared_ptr<Data::CloudImageView> &userpic,
int x,
int y,
int outerWidth,
int sizew,
int sizeh,
PanelMode mode);
const not_null<MembersRowDelegate*> _delegate;
State _state = State::Inactive;
std::unique_ptr<Ui::RippleAnimation> _actionRipple;
std::unique_ptr<BlobsAnimation> _blobsAnimation;
std::unique_ptr<StatusIcon> _statusIcon;
Ui::Animations::Simple _speakingAnimation; // For gray-red/green icon.
Ui::Animations::Simple _mutedAnimation; // For gray/red icon.
Ui::Animations::Simple _activeAnimation; // For icon cross animation.
QString _aboutText;
crl::time _speakingLastTime = 0;
uint64 _raisedHandRating = 0;
uint32 _ssrc = 0;
int _volume = Group::kDefaultVolume;
bool _sounding : 1;
bool _speaking : 1;
bool _raisedHandStatus : 1;
bool _skipLevelUpdate : 1;
bool _mutedByMe : 1;
};
} // namespace Calls::Group

View File

@@ -5,11 +5,11 @@ 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_group_menu.h"
#include "calls/group/calls_group_menu.h"
#include "calls/calls_group_call.h"
#include "calls/calls_group_settings.h"
#include "calls/calls_group_panel.h"
#include "calls/group/calls_group_call.h"
#include "calls/group/calls_group_settings.h"
#include "calls/group/calls_group_panel.h"
#include "data/data_peer.h"
#include "data/data_group_call.h"
#include "info/profile/info_profile_values.h" // Info::Profile::NameValue.
@@ -78,10 +78,6 @@ void StartGroupCallRecordingBox(
});
box->addButton(tr::lng_group_call_recording_start_button(), [=] {
const auto result = input->getLastText().trimmed();
if (result.isEmpty()) {
input->showError();
return;
}
box->closeBox();
done(result);
});
@@ -590,7 +586,9 @@ void FillMenu(
not_null<Ui::DropdownMenu*> menu,
not_null<PeerData*> peer,
not_null<GroupCall*> call,
bool wide,
Fn<void()> chooseJoinAs,
Fn<void()> chooseShareScreenSource,
Fn<void(object_ptr<Ui::BoxContent>)> showBox) {
const auto weak = base::make_weak(call.get());
const auto resolveReal = [=] {
@@ -606,8 +604,10 @@ void FillMenu(
}
const auto addEditJoinAs = call->showChooseJoinAs();
const auto addEditTitle = peer->canManageGroupCall();
const auto addEditRecording = peer->canManageGroupCall()
const auto addEditTitle = call->canManage();
const auto addEditRecording = call->canManage() && !real->scheduleDate();
const auto addScreenCast = !wide
&& call->videoIsWorking()
&& !real->scheduleDate();
if (addEditJoinAs) {
menu->addAction(MakeJoinAsAction(
@@ -660,6 +660,23 @@ void FillMenu(
real->recordStartDateValue(),
handler));
}
if (addScreenCast) {
const auto sharing = call->isSharingScreen();
const auto toggle = [=] {
if (const auto strong = weak.get()) {
if (sharing) {
strong->toggleScreenSharing(std::nullopt);
} else {
chooseShareScreenSource();
}
}
};
menu->addAction(
(call->isSharingScreen()
? tr::lng_group_call_screen_share_stop(tr::now)
: tr::lng_group_call_screen_share_start(tr::now)),
toggle);
}
menu->addAction(tr::lng_group_call_settings(tr::now), [=] {
if (const auto strong = weak.get()) {
showBox(Box(SettingsBox, strong));
@@ -677,8 +694,12 @@ void FillMenu(
menu->addAction(MakeAttentionAction(
menu->menu(),
(real->scheduleDate()
? tr::lng_group_call_cancel(tr::now)
: tr::lng_group_call_end(tr::now)),
? (call->canManage()
? tr::lng_group_call_cancel(tr::now)
: tr::lng_group_call_leave(tr::now))
: (call->canManage()
? tr::lng_group_call_end(tr::now)
: tr::lng_group_call_leave(tr::now))),
finish));
}

View File

@@ -61,7 +61,9 @@ void FillMenu(
not_null<Ui::DropdownMenu*> menu,
not_null<PeerData*> peer,
not_null<GroupCall*> call,
bool wide,
Fn<void()> chooseJoinAs,
Fn<void()> chooseShareScreenSource,
Fn<void(object_ptr<Ui::BoxContent>)> showBox);
[[nodiscard]] base::unique_qptr<Ui::Menu::ItemBase> MakeAttentionAction(

File diff suppressed because it is too large Load Diff

View File

@@ -10,8 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#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 "calls/group/calls_group_call.h"
#include "calls/group/calls_choose_join_as.h"
#include "calls/group/ui/desktop_capture_choose_source.h"
#include "ui/effects/animations.h"
#include "ui/rp_widget.h"
@@ -25,11 +26,13 @@ class GroupCall;
namespace Ui {
class AbstractButton;
class ImportantTooltip;
class DropdownMenu;
class CallButton;
class CallMuteButton;
class IconButton;
class FlatLabel;
class RpWidget;
template <typename Widget>
class FadeWrap;
template <typename Widget>
@@ -39,6 +42,12 @@ class ScrollArea;
class GenericBox;
class LayerManager;
class GroupCallScheduledLeft;
namespace GL {
enum class Backend;
} // namespace GL
namespace Toast {
class Instance;
} // namespace Toast
namespace Platform {
class TitleControls;
} // namespace Platform
@@ -51,14 +60,21 @@ struct CallBodyLayout;
namespace Calls::Group {
class Toasts;
class Members;
class Viewport;
enum class PanelMode;
class Panel final {
class Panel final : private Ui::DesktopCapture::ChooseSourceDelegate {
public:
Panel(not_null<GroupCall*> call);
~Panel();
[[nodiscard]] not_null<GroupCall*> call() const;
[[nodiscard]] bool isActive() const;
void showToast(TextWithEntities &&text, crl::time duration = 0);
void minimize();
void close();
void showAndActivate();
@@ -66,9 +82,13 @@ public:
private:
using State = GroupCall::State;
struct ControlsBackgroundNarrow;
std::unique_ptr<Ui::Window> createWindow();
[[nodiscard]] not_null<Ui::RpWidget*> widget() const;
[[nodiscard]] PanelMode mode() const;
void paint(QRect clip);
void initWindow();
@@ -79,23 +99,42 @@ private:
void initGeometry();
void setupScheduledLabels(rpl::producer<TimeId> date);
void setupMembers();
void setupJoinAsChangedToasts();
void setupTitleChangedToasts();
void setupAllowedToSpeakToasts();
void setupVideo(not_null<Viewport*> viewport);
void setupRealMuteButtonState(not_null<Data::GroupCall*> real);
bool handleClose();
void startScheduledNow();
void trackControls(bool track);
void raiseControls();
void enlargeVideo();
void minimizeVideo();
void trackControl(Ui::RpWidget *widget, rpl::lifetime &lifetime);
void trackControlOver(not_null<Ui::RpWidget*> control, bool over);
void showNiceTooltip(not_null<Ui::RpWidget*> control);
bool updateMode();
void updateControlsGeometry();
void updateButtonsGeometry();
void updateButtonsStyles();
void updateMembersGeometry();
void refreshControlsBackground();
void setupControlsBackgroundWide();
void setupControlsBackgroundNarrow();
void showControls();
void refreshLeftButton();
void refreshVideoButtons(
std::optional<bool> overrideWideMode = std::nullopt);
void refreshTopButton();
void toggleWideControls(bool shown);
[[nodiscard]] bool videoButtonInNarrowMode() const;
void endCall();
void showMainMenu();
void chooseJoinAs();
void chooseShareScreenSource();
void screenSharingPrivacyRequest();
void addMembers();
void kickParticipant(not_null<PeerData*> participantPeer);
void kickParticipantSure(not_null<PeerData*> participantPeer);
@@ -108,11 +147,19 @@ private:
void migrate(not_null<ChannelData*> channel);
void subscribeToPeerChanges();
QWidget *chooseSourceParent() override;
QString chooseSourceActiveDeviceId() override;
rpl::lifetime &chooseSourceInstanceLifetime() override;
void chooseSourceAccepted(const QString &deviceId) override;
void chooseSourceStop() override;
const not_null<GroupCall*> _call;
not_null<PeerData*> _peer;
Ui::GL::Backend _backend = Ui::GL::Backend();
const std::unique_ptr<Ui::Window> _window;
const std::unique_ptr<Ui::LayerManager> _layerBg;
rpl::variable<PanelMode> _mode;
#ifndef Q_OS_MAC
std::unique_ptr<Ui::Platform::TitleControls> _controls;
@@ -125,19 +172,40 @@ private:
object_ptr<Ui::AbstractButton> _recordingMark = { nullptr };
object_ptr<Ui::IconButton> _menuToggle = { nullptr };
object_ptr<Ui::DropdownMenu> _menu = { nullptr };
rpl::variable<bool> _wideMenuShown = false;
object_ptr<Ui::AbstractButton> _joinAsToggle = { nullptr };
object_ptr<Members> _members = { nullptr };
std::unique_ptr<Viewport> _viewport;
rpl::lifetime _trackControlsLifetime;
rpl::lifetime _trackControlsOverStateLifetime;
rpl::lifetime _trackControlsMenuLifetime;
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;
std::optional<QRect> _lastSmallGeometry;
std::optional<QRect> _lastLargeGeometry;
bool _lastLargeMaximized = false;
bool _showWideControls = false;
bool _trackControls = false;
bool _wideControlsShown = false;
Ui::Animations::Simple _wideControlsAnimation;
object_ptr<Ui::RpWidget> _controlsBackgroundWide = { nullptr };
std::unique_ptr<ControlsBackgroundNarrow> _controlsBackgroundNarrow;
object_ptr<Ui::CallButton> _settings = { nullptr };
object_ptr<Ui::CallButton> _share = { nullptr };
object_ptr<Ui::CallButton> _wideMenu = { nullptr };
object_ptr<Ui::CallButton> _callShare = { nullptr };
object_ptr<Ui::CallButton> _video = { nullptr };
object_ptr<Ui::CallButton> _screenShare = { nullptr };
std::unique_ptr<Ui::CallMuteButton> _mute;
object_ptr<Ui::CallButton> _hangup;
Fn<void()> _shareLinkCallback;
object_ptr<Ui::ImportantTooltip> _niceTooltip = { nullptr };
Fn<void()> _callShareLinkCallback;
const std::unique_ptr<Toasts> _toasts;
base::weak_ptr<Ui::Toast::Instance> _lastToast;
rpl::lifetime _peerLifetime;

View File

@@ -5,13 +5,13 @@ 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_group_settings.h"
#include "calls/group/calls_group_settings.h"
#include "calls/calls_group_call.h"
#include "calls/calls_group_menu.h" // LeaveBox.
#include "calls/calls_group_common.h"
#include "calls/group/calls_group_call.h"
#include "calls/group/calls_group_menu.h" // LeaveBox.
#include "calls/group/calls_group_common.h"
#include "calls/group/calls_choose_join_as.h"
#include "calls/calls_instance.h"
#include "calls/calls_choose_join_as.h"
#include "ui/widgets/level_meter.h"
#include "ui/widgets/continuous_sliders.h"
#include "ui/widgets/buttons.h"

View File

@@ -0,0 +1,175 @@
/*
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/group/calls_group_toasts.h"
#include "calls/group/calls_group_call.h"
#include "calls/group/calls_group_common.h"
#include "calls/group/calls_group_panel.h"
#include "data/data_peer.h"
#include "data/data_group_call.h"
#include "ui/text/text_utilities.h"
#include "ui/toasts/common_toasts.h"
#include "lang/lang_keys.h"
namespace Calls::Group {
namespace {
constexpr auto kErrorDuration = 2 * crl::time(1000);
using State = GroupCall::State;
} // namespace
Toasts::Toasts(not_null<Panel*> panel)
: _panel(panel)
, _call(panel->call()) {
setup();
}
void Toasts::setup() {
setupJoinAsChanged();
setupTitleChanged();
setupRequestedToSpeak();
setupAllowedToSpeak();
setupPinnedVideo();
setupError();
}
void Toasts::setupJoinAsChanged() {
_call->rejoinEvents(
) | rpl::filter([](RejoinEvent event) {
return (event.wasJoinAs != event.nowJoinAs);
}) | rpl::map([=] {
return _call->stateValue() | rpl::filter([](State state) {
return (state == State::Joined);
}) | rpl::take(1);
}) | rpl::flatten_latest() | rpl::start_with_next([=] {
_panel->showToast(tr::lng_group_call_join_as_changed(
tr::now,
lt_name,
Ui::Text::Bold(_call->joinAs()->name),
Ui::Text::WithEntities));
}, _lifetime);
}
void Toasts::setupTitleChanged() {
_call->titleChanged(
) | rpl::filter([=] {
return (_call->lookupReal() != nullptr);
}) | rpl::map([=] {
const auto peer = _call->peer();
return peer->groupCall()->title().isEmpty()
? peer->name
: peer->groupCall()->title();
}) | rpl::start_with_next([=](const QString &title) {
_panel->showToast(tr::lng_group_call_title_changed(
tr::now,
lt_title,
Ui::Text::Bold(title),
Ui::Text::WithEntities));
}, _lifetime);
}
void Toasts::setupAllowedToSpeak() {
_call->allowedToSpeakNotifications(
) | rpl::start_with_next([=] {
if (_panel->isActive()) {
_panel->showToast({
tr::lng_group_call_can_speak_here(tr::now),
});
} else {
const auto real = _call->lookupReal();
const auto name = (real && !real->title().isEmpty())
? real->title()
: _call->peer()->name;
Ui::ShowMultilineToast({
.text = tr::lng_group_call_can_speak(
tr::now,
lt_chat,
Ui::Text::Bold(name),
Ui::Text::WithEntities),
});
}
}, _lifetime);
}
void Toasts::setupPinnedVideo() {
_call->videoEndpointPinnedValue(
) | rpl::map([=](bool pinned) {
return pinned
? _call->videoEndpointLargeValue()
: rpl::single(_call->videoEndpointLarge());
}) | rpl::flatten_latest(
) | rpl::start_with_next([=](const VideoEndpoint &endpoint) {
const auto pinned = _call->videoEndpointPinned();
const auto peer = endpoint.peer;
if (!peer) {
return;
}
const auto text = [&] {
const auto me = (peer == _call->joinAs());
const auto camera = (endpoint.type == VideoEndpointType::Camera);
if (me) {
const auto key = camera
? (pinned
? tr::lng_group_call_pinned_camera_me
: tr::lng_group_call_unpinned_camera_me)
: (pinned
? tr::lng_group_call_pinned_screen_me
: tr::lng_group_call_unpinned_screen_me);
return key(tr::now);
}
const auto key = camera
? (pinned
? tr::lng_group_call_pinned_camera
: tr::lng_group_call_unpinned_camera)
: (pinned
? tr::lng_group_call_pinned_screen
: tr::lng_group_call_unpinned_screen);
return key(tr::now, lt_user, peer->shortName());
}();
_panel->showToast({ text });
}, _lifetime);
}
void Toasts::setupRequestedToSpeak() {
_call->mutedValue(
) | rpl::combine_previous(
) | rpl::start_with_next([=](MuteState was, MuteState now) {
if (was == MuteState::ForceMuted && now == MuteState::RaisedHand) {
_panel->showToast({
tr::lng_group_call_tooltip_raised_hand(tr::now),
});
}
}, _lifetime);
}
void Toasts::setupError() {
_call->errors(
) | rpl::start_with_next([=](Error error) {
const auto key = [&] {
switch (error) {
case Error::NoCamera: return tr::lng_call_error_no_camera;
case Error::ScreenFailed:
return tr::lng_group_call_failed_screen;
case Error::MutedNoCamera:
return tr::lng_group_call_muted_no_camera;
case Error::MutedNoScreen:
return tr::lng_group_call_muted_no_screen;
case Error::DisabledNoCamera:
return tr::lng_group_call_chat_no_camera;
case Error::DisabledNoScreen:
return tr::lng_group_call_chat_no_screen;
}
Unexpected("Error in Calls::Group::Toasts::setupErrorToasts.");
}();
_panel->showToast({ key(tr::now) }, kErrorDuration);
}, _lifetime);
}
} // namespace Calls::Group

View File

@@ -0,0 +1,38 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace Calls {
class GroupCall;
} // namespace Calls
namespace Calls::Group {
class Panel;
class Toasts final {
public:
explicit Toasts(not_null<Panel*> panel);
private:
void setup();
void setupJoinAsChanged();
void setupTitleChanged();
void setupRequestedToSpeak();
void setupAllowedToSpeak();
void setupPinnedVideo();
void setupError();
const not_null<Panel*> _panel;
const not_null<GroupCall*> _call;
rpl::lifetime _lifetime;
};
} // namespace Calls::Group

View File

@@ -0,0 +1,935 @@
/*
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/group/calls_group_viewport.h"
#include "calls/group/calls_group_viewport_tile.h"
#include "calls/group/calls_group_viewport_opengl.h"
#include "calls/group/calls_group_viewport_raster.h"
#include "calls/group/calls_group_common.h"
#include "calls/group/calls_group_call.h"
#include "calls/group/calls_group_members_row.h"
#include "media/view/media_view_pip.h"
#include "base/platform/base_platform_info.h"
#include "webrtc/webrtc_video_track.h"
#include "ui/painter.h"
#include "ui/abstract_button.h"
#include "ui/gl/gl_surface.h"
#include "ui/effects/animations.h"
#include "ui/effects/cross_line.h"
#include "data/data_group_call.h" // MuteButtonTooltip.
#include "lang/lang_keys.h"
#include "styles/style_calls.h"
#include <QtGui/QtEvents>
#include <QtGui/QOpenGLShader>
namespace Calls::Group {
namespace {
[[nodiscard]] QRect InterpolateRect(QRect a, QRect b, float64 ratio) {
const auto left = anim::interpolate(a.x(), b.x(), ratio);
const auto top = anim::interpolate(a.y(), b.y(), ratio);
const auto right = anim::interpolate(
a.x() + a.width(),
b.x() + b.width(),
ratio);
const auto bottom = anim::interpolate(
a.y() + a.height(),
b.y() + b.height(),
ratio);
return { left, top, right - left, bottom - top };
}
} // namespace
Viewport::Viewport(
not_null<QWidget*> parent,
PanelMode mode,
Ui::GL::Backend backend)
: _mode(mode)
, _content(Ui::GL::CreateSurface(parent, chooseRenderer(backend))) {
setup();
}
Viewport::~Viewport() = default;
not_null<QWidget*> Viewport::widget() const {
return _content->rpWidget();
}
not_null<Ui::RpWidgetWrap*> Viewport::rp() const {
return _content.get();
}
void Viewport::setup() {
const auto raw = widget();
raw->resize(0, 0);
raw->setAttribute(Qt::WA_OpaquePaintEvent);
raw->setMouseTracking(true);
_content->sizeValue(
) | rpl::filter([=] {
return wide();
}) | rpl::start_with_next([=] {
updateTilesGeometry();
}, lifetime());
_content->events(
) | rpl::start_with_next([=](not_null<QEvent*> e) {
const auto type = e->type();
if (type == QEvent::Enter) {
Ui::Integration::Instance().registerLeaveSubscription(raw);
_mouseInside = true;
} else if (type == QEvent::Leave) {
Ui::Integration::Instance().unregisterLeaveSubscription(raw);
setSelected({});
_mouseInside = false;
} else if (type == QEvent::MouseButtonPress) {
handleMousePress(
static_cast<QMouseEvent*>(e.get())->pos(),
static_cast<QMouseEvent*>(e.get())->button());
} else if (type == QEvent::MouseButtonRelease) {
handleMouseRelease(
static_cast<QMouseEvent*>(e.get())->pos(),
static_cast<QMouseEvent*>(e.get())->button());
} else if (type == QEvent::MouseMove) {
handleMouseMove(static_cast<QMouseEvent*>(e.get())->pos());
}
}, lifetime());
}
void Viewport::setGeometry(QRect geometry) {
Expects(wide());
if (widget()->geometry() != geometry) {
_geometryStaleAfterModeChange = false;
widget()->setGeometry(geometry);
} else if (_geometryStaleAfterModeChange) {
_geometryStaleAfterModeChange = false;
updateTilesGeometry();
}
}
void Viewport::resizeToWidth(int width) {
Expects(!wide());
updateTilesGeometry(width);
}
void Viewport::setScrollTop(int scrollTop) {
if (_scrollTop == scrollTop) {
return;
}
_scrollTop = scrollTop;
updateTilesGeometry();
}
bool Viewport::wide() const {
return (_mode == PanelMode::Wide);
}
void Viewport::setMode(PanelMode mode, not_null<QWidget*> parent) {
if (_mode == mode && widget()->parent() == parent) {
return;
}
_mode = mode;
_scrollTop = 0;
setControlsShown(1.);
if (widget()->parent() != parent) {
const auto hidden = widget()->isHidden();
widget()->setParent(parent);
if (!hidden) {
widget()->show();
}
}
if (!wide()) {
for (const auto &tile : _tiles) {
tile->toggleTopControlsShown(false);
}
} else if (_selected.tile) {
_selected.tile->toggleTopControlsShown(true);
}
}
void Viewport::handleMousePress(QPoint position, Qt::MouseButton button) {
handleMouseMove(position);
setPressed(_selected);
}
void Viewport::handleMouseRelease(QPoint position, Qt::MouseButton button) {
handleMouseMove(position);
const auto pressed = _pressed;
setPressed({});
if (const auto tile = pressed.tile) {
if (pressed == _selected) {
if (button == Qt::RightButton) {
tile->row()->showContextMenu();
} else if (!wide()
|| (_hasTwoOrMore && !_large)
|| pressed.element != Selection::Element::PinButton) {
_clicks.fire_copy(tile->endpoint());
} else if (pressed.element == Selection::Element::PinButton) {
_pinToggles.fire(!tile->pinned());
}
}
}
}
void Viewport::handleMouseMove(QPoint position) {
updateSelected(position);
}
void Viewport::updateSelected(QPoint position) {
if (!widget()->rect().contains(position)) {
setSelected({});
return;
}
for (const auto &tile : _tiles) {
const auto geometry = tile->shown()
? tile->geometry()
: QRect();
if (geometry.contains(position)) {
const auto pin = wide()
&& tile->pinOuter().contains(position - geometry.topLeft());
const auto back = wide()
&& tile->backOuter().contains(position - geometry.topLeft());
setSelected({
.tile = tile.get(),
.element = (pin
? Selection::Element::PinButton
: back
? Selection::Element::BackButton
: Selection::Element::Tile),
});
return;
}
}
setSelected({});
}
void Viewport::updateSelected() {
updateSelected(widget()->mapFromGlobal(QCursor::pos()));
}
void Viewport::setControlsShown(float64 shown) {
_controlsShownRatio = shown;
widget()->update();
}
void Viewport::add(
const VideoEndpoint &endpoint,
VideoTileTrack track,
rpl::producer<bool> pinned) {
_tiles.push_back(std::make_unique<VideoTile>(
endpoint,
track,
std::move(pinned),
[=] { widget()->update(); }));
_tiles.back()->trackSizeValue(
) | rpl::filter([](QSize size) {
return !size.isEmpty();
}) | rpl::start_with_next([=] {
updateTilesGeometry();
}, _tiles.back()->lifetime());
_tiles.back()->track()->stateValue(
) | rpl::start_with_next([=] {
updateTilesGeometry();
}, _tiles.back()->lifetime());
}
void Viewport::remove(const VideoEndpoint &endpoint) {
const auto i = ranges::find(_tiles, endpoint, &VideoTile::endpoint);
if (i == end(_tiles)) {
return;
}
const auto removing = i->get();
const auto largeRemoved = (_large == removing);
if (largeRemoved) {
prepareLargeChangeAnimation();
_large = nullptr;
}
if (_selected.tile == removing) {
setSelected({});
}
if (_pressed.tile == removing) {
setPressed({});
}
for (auto &geometry : _startTilesLayout.list) {
if (geometry.tile == removing) {
geometry.tile = nullptr;
}
}
for (auto &geometry : _finishTilesLayout.list) {
if (geometry.tile == removing) {
geometry.tile = nullptr;
}
}
_tiles.erase(i);
if (largeRemoved) {
startLargeChangeAnimation();
} else {
updateTilesGeometry();
}
}
void Viewport::prepareLargeChangeAnimation() {
if (!wide()) {
return;
} else if (_largeChangeAnimation.animating()) {
updateTilesAnimated();
const auto field = _finishTilesLayout.useColumns
? &Geometry::columns
: &Geometry::rows;
for (auto &finish : _finishTilesLayout.list) {
const auto tile = finish.tile;
if (!tile) {
continue;
}
finish.*field = tile->geometry();
}
_startTilesLayout = std::move(_finishTilesLayout);
_largeChangeAnimation.stop();
_startTilesLayout.list.erase(
ranges::remove(_startTilesLayout.list, nullptr, &Geometry::tile),
end(_startTilesLayout.list));
} else {
_startTilesLayout = applyLarge(std::move(_startTilesLayout));
}
}
void Viewport::startLargeChangeAnimation() {
Expects(!_largeChangeAnimation.animating());
if (!wide()
|| anim::Disabled()
|| (_startTilesLayout.list.size() < 2)
|| !_opengl
|| widget()->size().isEmpty()) {
updateTilesGeometry();
return;
}
_finishTilesLayout = applyLarge(
countWide(widget()->width(), widget()->height()));
if (_finishTilesLayout.list.empty()
|| _finishTilesLayout.outer != _startTilesLayout.outer) {
updateTilesGeometry();
return;
}
_largeChangeAnimation.start(
[=] { updateTilesAnimated(); },
0.,
1.,
st::slideDuration);
}
Viewport::Layout Viewport::applyLarge(Layout layout) const {
auto &list = layout.list;
if (!_large) {
return layout;
}
const auto i = ranges::find(list, _large, &Geometry::tile);
if (i == end(list)) {
return layout;
}
const auto field = layout.useColumns
? &Geometry::columns
: &Geometry::rows;
const auto fullWidth = layout.outer.width();
const auto fullHeight = layout.outer.height();
const auto largeRect = (*i).*field;
const auto largeLeft = largeRect.x();
const auto largeTop = largeRect.y();
const auto largeRight = largeLeft + largeRect.width();
const auto largeBottom = largeTop + largeRect.height();
const auto largeCenter = largeRect.center();
for (auto &geometry : list) {
if (geometry.tile == _large) {
geometry.*field = { QPoint(), layout.outer };
} else if (layout.useColumns) {
auto &rect = geometry.columns;
const auto center = rect.center();
if (center.x() < largeLeft) {
rect = rect.translated(-largeLeft, 0);
} else if (center.x() > largeRight) {
rect = rect.translated(fullWidth - largeRight, 0);
} else if (center.y() < largeTop) {
rect = QRect(
0,
rect.y() - largeTop,
fullWidth,
rect.height());
} else if (center.y() > largeBottom) {
rect = QRect(
0,
rect.y() + (fullHeight - largeBottom),
fullWidth,
rect.height());
}
} else {
auto &rect = geometry.rows;
const auto center = rect.center();
if (center.y() < largeTop) {
rect = rect.translated(0, -largeTop);
} else if (center.y() > largeBottom) {
rect = rect.translated(0, fullHeight - largeBottom);
} else if (center.x() < largeLeft) {
rect = QRect(
rect.x() - largeLeft,
0,
rect.width(),
fullHeight);
} else {
rect = QRect(
rect.x() + (fullWidth - largeRight),
0,
rect.width(),
fullHeight);
}
}
}
return layout;
}
void Viewport::updateTilesAnimated() {
if (!_largeChangeAnimation.animating()) {
updateTilesGeometry();
return;
}
const auto ratio = _largeChangeAnimation.value(1.);
const auto field = _finishTilesLayout.useColumns
? &Geometry::columns
: &Geometry::rows;
for (const auto &finish : _finishTilesLayout.list) {
const auto tile = finish.tile;
if (!tile) {
continue;
}
const auto i = ranges::find(
_startTilesLayout.list,
tile,
&Geometry::tile);
if (i == end(_startTilesLayout.list)) {
LOG(("Tiles Animation Error 1!"));
_largeChangeAnimation.stop();
updateTilesGeometry();
return;
}
const auto from = (*i).*field;
const auto to = finish.*field;
tile->setGeometry(
InterpolateRect(from, to, ratio),
TileAnimation{ from.size(), to.size(), ratio });
}
widget()->update();
}
Viewport::Layout Viewport::countWide(int outerWidth, int outerHeight) const {
auto result = Layout{ .outer = QSize(outerWidth, outerHeight) };
auto &sizes = result.list;
sizes.reserve(_tiles.size());
for (const auto &tile : _tiles) {
const auto video = tile.get();
const auto size = video->trackOrUserpicSize();
if (!size.isEmpty()) {
sizes.push_back(Geometry{ video, size });
}
}
if (sizes.empty()) {
return result;
} else if (sizes.size() == 1) {
sizes.front().rows = { 0, 0, outerWidth, outerHeight };
return result;
}
auto columnsBlack = uint64();
auto rowsBlack = uint64();
const auto count = int(sizes.size());
const auto skip = st::groupCallVideoLargeSkip;
const auto slices = int(std::ceil(std::sqrt(float64(count))));
{
auto index = 0;
const auto columns = slices;
const auto sizew = (outerWidth + skip) / float64(columns);
for (auto column = 0; column != columns; ++column) {
const auto left = int(std::round(column * sizew));
const auto width = int(std::round(column * sizew + sizew - skip))
- left;
const auto rows = int(std::round((count - index)
/ float64(columns - column)));
const auto sizeh = (outerHeight + skip) / float64(rows);
for (auto row = 0; row != rows; ++row) {
const auto top = int(std::round(row * sizeh));
const auto height = int(std::round(
row * sizeh + sizeh - skip)) - top;
auto &geometry = sizes[index];
geometry.columns = {
left,
top,
width,
height };
const auto scaled = geometry.size.scaled(
width,
height,
Qt::KeepAspectRatio);
columnsBlack += (scaled.width() < width)
? (width - scaled.width()) * height
: (height - scaled.height()) * width;
++index;
}
}
}
{
auto index = 0;
const auto rows = slices;
const auto sizeh = (outerHeight + skip) / float64(rows);
for (auto row = 0; row != rows; ++row) {
const auto top = int(std::round(row * sizeh));
const auto height = int(std::round(row * sizeh + sizeh - skip))
- top;
const auto columns = int(std::round((count - index)
/ float64(rows - row)));
const auto sizew = (outerWidth + skip) / float64(columns);
for (auto column = 0; column != columns; ++column) {
const auto left = int(std::round(column * sizew));
const auto width = int(std::round(
column * sizew + sizew - skip)) - left;
auto &geometry = sizes[index];
geometry.rows = {
left,
top,
width,
height };
const auto scaled = geometry.size.scaled(
width,
height,
Qt::KeepAspectRatio);
rowsBlack += (scaled.width() < width)
? (width - scaled.width()) * height
: (height - scaled.height()) * width;
++index;
}
}
}
result.useColumns = (columnsBlack < rowsBlack);
return result;
}
void Viewport::showLarge(const VideoEndpoint &endpoint) {
// If a video get's switched off, GroupCall first unpins it,
// then removes it from Large endpoint, then removes from active tracks.
//
// If we want to animate large video removal properly, we need to
// delay this update and start animation directly from removing of the
// track from the active list. Otherwise final state won't be correct.
_updateLargeScheduled = [=] {
const auto i = ranges::find(_tiles, endpoint, &VideoTile::endpoint);
const auto large = (i != end(_tiles)) ? i->get() : nullptr;
if (_large != large) {
prepareLargeChangeAnimation();
_large = large;
updateTopControlsVisibility();
startLargeChangeAnimation();
}
Ensures(!_large || !_large->trackOrUserpicSize().isEmpty());
};
crl::on_main(widget(), [=] {
if (!_updateLargeScheduled) {
return;
}
base::take(_updateLargeScheduled)();
});
}
void Viewport::updateTilesGeometry() {
updateTilesGeometry(widget()->width());
}
void Viewport::updateTilesGeometry(int outerWidth) {
const auto mouseInside = _mouseInside.current();
const auto guard = gsl::finally([&] {
if (mouseInside) {
updateSelected();
}
widget()->update();
});
const auto outerHeight = widget()->height();
if (_tiles.empty() || !outerWidth) {
_fullHeight = 0;
return;
}
if (wide()) {
updateTilesGeometryWide(outerWidth, outerHeight);
refreshHasTwoOrMore();
_fullHeight = 0;
} else {
updateTilesGeometryNarrow(outerWidth);
}
}
void Viewport::refreshHasTwoOrMore() {
auto hasTwoOrMore = false;
auto oneFound = false;
for (const auto &tile : _tiles) {
if (!tile->trackOrUserpicSize().isEmpty()) {
if (oneFound) {
hasTwoOrMore = true;
break;
}
oneFound = true;
}
}
if (_hasTwoOrMore == hasTwoOrMore) {
return;
}
_hasTwoOrMore = hasTwoOrMore;
updateCursor();
updateTopControlsVisibility();
}
void Viewport::updateTopControlsVisibility() {
if (_selected.tile) {
_selected.tile->toggleTopControlsShown(
_hasTwoOrMore && wide() && _large && _large == _selected.tile);
}
}
void Viewport::updateTilesGeometryWide(int outerWidth, int outerHeight) {
if (!outerHeight) {
return;
} else if (_largeChangeAnimation.animating()) {
if (_startTilesLayout.outer == QSize(outerWidth, outerHeight)) {
return;
}
_largeChangeAnimation.stop();
}
_startTilesLayout = countWide(outerWidth, outerHeight);
if (_large && !_large->trackOrUserpicSize().isEmpty()) {
for (const auto &geometry : _startTilesLayout.list) {
if (geometry.tile == _large) {
setTileGeometry(_large, { 0, 0, outerWidth, outerHeight });
} else {
geometry.tile->hide();
}
}
} else {
const auto field = _startTilesLayout.useColumns
? &Geometry::columns
: &Geometry::rows;
for (const auto &geometry : _startTilesLayout.list) {
if (const auto video = geometry.tile) {
setTileGeometry(video, geometry.*field);
}
}
}
}
void Viewport::updateTilesGeometryNarrow(int outerWidth) {
if (outerWidth <= st::groupCallNarrowMembersWidth) {
updateTilesGeometryColumn(outerWidth);
return;
}
const auto y = -_scrollTop;
auto sizes = base::flat_map<not_null<VideoTile*>, QSize>();
sizes.reserve(_tiles.size());
for (const auto &tile : _tiles) {
const auto video = tile.get();
const auto size = video->trackOrUserpicSize();
if (size.isEmpty()) {
video->hide();
} else {
sizes.emplace(video, size);
}
}
if (sizes.empty()) {
_fullHeight = 0;
return;
} else if (sizes.size() == 1) {
const auto size = sizes.front().second;
const auto heightMin = (outerWidth * 9) / 16;
const auto heightMax = (outerWidth * 3) / 4;
const auto scaled = size.scaled(
QSize(outerWidth, heightMax),
Qt::KeepAspectRatio);
const auto height = std::max(scaled.height(), heightMin);
const auto skip = st::groupCallVideoSmallSkip;
setTileGeometry(sizes.front().first, { 0, y, outerWidth, height });
_fullHeight = height + skip;
return;
}
const auto min = (st::groupCallWidth
- st::groupCallMembersMargin.left()
- st::groupCallMembersMargin.right()
- st::groupCallVideoSmallSkip) / 2;
const auto square = (outerWidth - st::groupCallVideoSmallSkip) / 2;
const auto skip = (outerWidth - 2 * square);
const auto put = [&](not_null<VideoTile*> tile, int column, int row) {
setTileGeometry(tile, {
(column == 2) ? 0 : column ? (outerWidth - square) : 0,
y + row * (min + skip),
(column == 2) ? outerWidth : square,
min,
});
};
const auto rows = (sizes.size() + 1) / 2;
if (sizes.size() == 3) {
put(sizes.front().first, 2, 0);
put((sizes.begin() + 1)->first, 0, 1);
put((sizes.begin() + 2)->first, 1, 1);
} else {
auto row = 0;
auto column = 0;
for (const auto &[video, endpoint] : sizes) {
put(video, column, row);
if (column) {
++row;
column = (row + 1 == rows && sizes.size() % 2) ? 2 : 0;
} else {
column = 1;
}
}
}
_fullHeight = rows * (min + skip);
}
void Viewport::updateTilesGeometryColumn(int outerWidth) {
const auto y = -_scrollTop;
auto top = 0;
const auto layoutNext = [&](not_null<VideoTile*> tile) {
const auto size = tile->trackOrUserpicSize();
const auto shown = !size.isEmpty() && _large && tile != _large;
const auto height = shown
? st::groupCallNarrowVideoHeight
: 0;
setTileGeometry(tile, { 0, y + top, outerWidth, height });
top += height ? (height + st::groupCallVideoSmallSkip) : 0;
};
const auto topPeer = _large ? _large->row()->peer().get() : nullptr;
const auto reorderNeeded = [&] {
if (!_large) {
return false;
}
for (const auto &tile : _tiles) {
if (tile.get() != _large && tile->row()->peer() == topPeer) {
return (tile.get() != _tiles.front().get())
&& !tile->trackOrUserpicSize().isEmpty();
}
}
return false;
}();
if (reorderNeeded) {
_tilesForOrder.clear();
_tilesForOrder.reserve(_tiles.size());
for (const auto &tile : _tiles) {
_tilesForOrder.push_back(tile.get());
}
ranges::stable_partition(
_tilesForOrder,
[&](not_null<VideoTile*> tile) {
return (tile->row()->peer() == topPeer);
});
for (const auto &tile : _tilesForOrder) {
layoutNext(tile);
}
} else {
for (const auto &tile : _tiles) {
layoutNext(tile.get());
}
}
_fullHeight = top;
}
void Viewport::setTileGeometry(not_null<VideoTile*> tile, QRect geometry) {
tile->setGeometry(geometry);
const auto min = std::min(geometry.width(), geometry.height());
const auto kMedium = style::ConvertScale(480);
const auto kSmall = style::ConvertScale(240);
const auto quality = (min >= kMedium)
? VideoQuality::Full
: (min >= kSmall)
? VideoQuality::Medium
: VideoQuality::Thumbnail;
if (tile->updateRequestedQuality(quality)) {
_qualityRequests.fire(VideoQualityRequest{
.endpoint = tile->endpoint(),
.quality = quality,
});
}
}
void Viewport::setSelected(Selection value) {
if (_selected == value) {
return;
}
if (_selected.tile) {
_selected.tile->toggleTopControlsShown(false);
}
_selected = value;
updateTopControlsVisibility();
updateCursor();
}
void Viewport::updateCursor() {
const auto pointer = _selected.tile && (!wide() || _hasTwoOrMore);
widget()->setCursor(pointer ? style::cur_pointer : style::cur_default);
}
void Viewport::setPressed(Selection value) {
if (_pressed == value) {
return;
}
_pressed = value;
}
Ui::GL::ChosenRenderer Viewport::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,
};
}
bool Viewport::requireARGB32() const {
return !_opengl;
}
int Viewport::fullHeight() const {
return _fullHeight.current();
}
rpl::producer<int> Viewport::fullHeightValue() const {
return _fullHeight.value();
}
rpl::producer<bool> Viewport::pinToggled() const {
return _pinToggles.events();
}
rpl::producer<VideoEndpoint> Viewport::clicks() const {
return _clicks.events();
}
rpl::producer<VideoQualityRequest> Viewport::qualityRequests() const {
return _qualityRequests.events();
}
rpl::producer<bool> Viewport::mouseInsideValue() const {
return _mouseInside.value();
}
rpl::lifetime &Viewport::lifetime() {
return _content->lifetime();
}
QImage GenerateShadow(
int height,
int topAlpha,
int bottomAlpha,
QColor color) {
Expects(topAlpha >= 0 && topAlpha < 256);
Expects(bottomAlpha >= 0 && bottomAlpha < 256);
Expects(height * style::DevicePixelRatio() < 65536);
const auto base = (uint32(color.red()) << 16)
| (uint32(color.green()) << 8)
| uint32(color.blue());
const auto premultiplied = (topAlpha == bottomAlpha) || !base;
auto result = QImage(
QSize(1, height * style::DevicePixelRatio()),
(premultiplied
? QImage::Format_ARGB32_Premultiplied
: QImage::Format_ARGB32));
if (topAlpha == bottomAlpha) {
color.setAlpha(topAlpha);
result.fill(color);
return result;
}
constexpr auto kShift = 16;
constexpr auto kMultiply = (1U << kShift);
const auto values = std::abs(topAlpha - bottomAlpha);
const auto rows = uint32(result.height());
const auto step = (values * kMultiply) / (rows - 1);
const auto till = rows * uint32(step);
Assert(result.bytesPerLine() == sizeof(uint32));
auto ints = reinterpret_cast<uint32*>(result.bits());
if (topAlpha < bottomAlpha) {
for (auto i = uint32(0); i != till; i += step) {
*ints++ = base | ((topAlpha + (i >> kShift)) << 24);
}
} else {
for (auto i = uint32(0); i != till; i += step) {
*ints++ = base | ((topAlpha - (i >> kShift)) << 24);
}
}
if (!premultiplied) {
result = std::move(result).convertToFormat(
QImage::Format_ARGB32_Premultiplied);
}
return result;
}
rpl::producer<QString> MuteButtonTooltip(not_null<GroupCall*> call) {
//return rpl::single(std::make_tuple(
// (Data::GroupCall*)nullptr,
// call->scheduleDate()
//)) | rpl::then(call->real(
//) | rpl::map([](not_null<Data::GroupCall*> real) {
// using namespace rpl::mappers;
// return real->scheduleDateValue(
// ) | rpl::map([=](TimeId scheduleDate) {
// return std::make_tuple(real.get(), scheduleDate);
// });
//}) | rpl::flatten_latest(
//)) | rpl::map([=](
// Data::GroupCall *real,
// TimeId scheduleDate) -> rpl::producer<QString> {
// if (scheduleDate) {
// return rpl::combine(
// call->canManageValue(),
// (real
// ? real->scheduleStartSubscribedValue()
// : rpl::single(false))
// ) | rpl::map([](bool canManage, bool subscribed) {
// return canManage
// ? tr::lng_group_call_start_now()
// : subscribed
// ? tr::lng_group_call_cancel_reminder()
// : tr::lng_group_call_set_reminder();
// }) | rpl::flatten_latest();
// }
return call->mutedValue(
) | rpl::map([](MuteState muted) {
switch (muted) {
case MuteState::Active:
case MuteState::PushToTalk:
return tr::lng_group_call_you_are_live();
case MuteState::ForceMuted:
return tr::lng_group_call_tooltip_force_muted();
case MuteState::RaisedHand:
return tr::lng_group_call_tooltip_raised_hand();
case MuteState::Muted:
return tr::lng_group_call_tooltip_microphone();
}
Unexpected("Value in MuteState in showNiceTooltip.");
}) | rpl::flatten_latest();
//}) | rpl::flatten_latest();
}
} // namespace Calls::Group

View File

@@ -0,0 +1,202 @@
/*
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 "ui/rp_widget.h"
#include "ui/effects/animations.h"
namespace Ui {
class AbstractButton;
class RpWidgetWrap;
namespace GL {
enum class Backend;
struct Capabilities;
struct ChosenRenderer;
} // namespace GL
} // namespace Ui
namespace Calls {
class GroupCall;
struct VideoEndpoint;
struct VideoQualityRequest;
} // namespace Calls
namespace Webrtc {
class VideoTrack;
} // namespace Webrtc
namespace Calls::Group {
class MembersRow;
enum class PanelMode;
enum class VideoQuality;
struct VideoTileTrack {
Webrtc::VideoTrack *track = nullptr;
MembersRow *row = nullptr;
[[nodiscard]] explicit operator bool() const {
return track != nullptr;
}
};
[[nodiscard]] inline bool operator==(
VideoTileTrack a,
VideoTileTrack b) noexcept {
return (a.track == b.track) && (a.row == b.row);
}
[[nodiscard]] inline bool operator!=(
VideoTileTrack a,
VideoTileTrack b) noexcept {
return !(a == b);
}
class Viewport final {
public:
Viewport(
not_null<QWidget*> parent,
PanelMode mode,
Ui::GL::Backend backend);
~Viewport();
[[nodiscard]] not_null<QWidget*> widget() const;
[[nodiscard]] not_null<Ui::RpWidgetWrap*> rp() const;
void setMode(PanelMode mode, not_null<QWidget*> parent);
void setControlsShown(float64 shown);
void setGeometry(QRect geometry);
void resizeToWidth(int newWidth);
void setScrollTop(int scrollTop);
void add(
const VideoEndpoint &endpoint,
VideoTileTrack track,
rpl::producer<bool> pinned);
void remove(const VideoEndpoint &endpoint);
void showLarge(const VideoEndpoint &endpoint);
[[nodiscard]] bool requireARGB32() const;
[[nodiscard]] int fullHeight() const;
[[nodiscard]] rpl::producer<int> fullHeightValue() const;
[[nodiscard]] rpl::producer<bool> pinToggled() const;
[[nodiscard]] rpl::producer<VideoEndpoint> clicks() const;
[[nodiscard]] rpl::producer<VideoQualityRequest> qualityRequests() const;
[[nodiscard]] rpl::producer<bool> mouseInsideValue() const;
[[nodiscard]] rpl::lifetime &lifetime();
private:
struct Textures;
class VideoTile;
class RendererSW;
class RendererGL;
using TileId = quintptr;
struct Geometry {
VideoTile *tile = nullptr;
QSize size;
QRect rows;
QRect columns;
};
struct Layout {
std::vector<Geometry> list;
QSize outer;
bool useColumns = false;
};
struct TileAnimation {
QSize from;
QSize to;
float64 ratio = -1.;
};
struct Selection {
enum class Element {
None,
Tile,
PinButton,
BackButton,
};
VideoTile *tile = nullptr;
Element element = Element::None;
inline bool operator==(Selection other) const {
return (tile == other.tile) && (element == other.element);
}
};
static constexpr auto kShadowMaxAlpha = 80;
void setup();
[[nodiscard]] bool wide() const;
void updateCursor();
void updateTilesGeometry();
void updateTilesGeometry(int outerWidth);
void updateTilesGeometryWide(int outerWidth, int outerHeight);
void updateTilesGeometryNarrow(int outerWidth);
void updateTilesGeometryColumn(int outerWidth);
void setTileGeometry(not_null<VideoTile*> tile, QRect geometry);
void refreshHasTwoOrMore();
void updateTopControlsVisibility();
void prepareLargeChangeAnimation();
void startLargeChangeAnimation();
void updateTilesAnimated();
[[nodiscard]] Layout countWide(int outerWidth, int outerHeight) const;
[[nodiscard]] Layout applyLarge(Layout layout) const;
void setSelected(Selection value);
void setPressed(Selection value);
void handleMousePress(QPoint position, Qt::MouseButton button);
void handleMouseRelease(QPoint position, Qt::MouseButton button);
void handleMouseMove(QPoint position);
void updateSelected(QPoint position);
void updateSelected();
[[nodiscard]] Ui::GL::ChosenRenderer chooseRenderer(
Ui::GL::Backend backend);
PanelMode _mode = PanelMode();
bool _opengl = false;
bool _geometryStaleAfterModeChange = false;
const std::unique_ptr<Ui::RpWidgetWrap> _content;
std::vector<std::unique_ptr<VideoTile>> _tiles;
std::vector<not_null<VideoTile*>> _tilesForOrder;
rpl::variable<int> _fullHeight = 0;
bool _hasTwoOrMore = false;
int _scrollTop = 0;
QImage _shadow;
rpl::event_stream<VideoEndpoint> _clicks;
rpl::event_stream<bool> _pinToggles;
rpl::event_stream<VideoQualityRequest> _qualityRequests;
float64 _controlsShownRatio = 1.;
VideoTile *_large = nullptr;
Fn<void()> _updateLargeScheduled;
Ui::Animations::Simple _largeChangeAnimation;
Layout _startTilesLayout;
Layout _finishTilesLayout;
Selection _selected;
Selection _pressed;
rpl::variable<bool> _mouseInside = false;
};
[[nodiscard]] QImage GenerateShadow(
int height,
int topAlpha,
int bottomAlpha,
QColor color = QColor(0, 0, 0));
[[nodiscard]] rpl::producer<QString> MuteButtonTooltip(
not_null<GroupCall*> call);
} // namespace Calls::Group

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,167 @@
/*
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/group/calls_group_viewport.h"
#include "ui/round_rect.h"
#include "ui/effects/animations.h"
#include "ui/effects/cross_line.h"
#include "ui/gl/gl_primitives.h"
#include "ui/gl/gl_surface.h"
#include "ui/gl/gl_image.h"
#include <QtGui/QOpenGLBuffer>
#include <QtGui/QOpenGLShaderProgram>
namespace Webrtc {
struct FrameWithInfo;
} // namespace Webrtc
namespace Calls::Group {
class Viewport::RendererGL final : public Ui::GL::Renderer {
public:
explicit RendererGL(not_null<Viewport*> 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;
std::optional<QColor> clearColor() override;
private:
struct TileData {
quintptr id = 0;
not_null<PeerData*> peer;
Ui::GL::Textures<5> textures;
Ui::GL::Framebuffers<2> framebuffers;
Ui::Animations::Simple outlined;
Ui::Animations::Simple paused;
QImage userpicFrame;
QRect nameRect;
int nameVersion = 0;
mutable int trackIndex = -1;
mutable QSize rgbaSize;
mutable QSize textureSize;
mutable QSize textureChromaSize;
mutable QSize textureBlurSize;
bool stale = false;
bool pause = false;
bool outline = false;
};
struct Program {
std::optional<QOpenGLShaderProgram> argb32;
std::optional<QOpenGLShaderProgram> yuv420;
};
void setDefaultViewport(QOpenGLFunctions &f);
void paintTile(
QOpenGLFunctions &f,
GLuint defaultFramebufferObject,
not_null<VideoTile*> tile,
TileData &nameData);
[[nodiscard]] Ui::GL::Rect transformRect(const QRect &raster) const;
[[nodiscard]] Ui::GL::Rect transformRect(
const Ui::GL::Rect &raster) const;
void ensureARGB32Program();
void ensureButtonsImage();
void prepareObjects(
QOpenGLFunctions &f,
TileData &tileData,
QSize blurSize);
void bindFrame(
QOpenGLFunctions &f,
const Webrtc::FrameWithInfo &data,
TileData &tileData,
Program &program);
void drawDownscalePass(
QOpenGLFunctions &f,
TileData &tileData);
void drawFirstBlurPass(
QOpenGLFunctions &f,
TileData &tileData,
QSize blurSize);
void validateDatas();
void validateNoiseTexture(
QOpenGLFunctions &f,
GLuint defaultFramebufferObject);
void validateOutlineAnimation(
not_null<VideoTile*> tile,
TileData &data);
void validatePausedAnimation(
not_null<VideoTile*> tile,
TileData &data);
void validateUserpicFrame(
not_null<VideoTile*> tile,
TileData &tileData);
void uploadTexture(
QOpenGLFunctions &f,
GLint internalformat,
GLint format,
QSize size,
QSize hasSize,
int stride,
const void *data) const;
[[nodiscard]] bool isExpanded(
not_null<VideoTile*> tile,
QSize unscaled,
QSize tileSize) const;
[[nodiscard]] float64 countExpandRatio(
not_null<VideoTile*> tile,
QSize unscaled,
const TileAnimation &animation) const;
const not_null<Viewport*> _owner;
GLfloat _factor = 1.;
QSize _viewport;
bool _rgbaFrame = false;
bool _userpicFrame;
std::optional<QOpenGLBuffer> _frameBuffer;
Program _downscaleProgram;
std::optional<QOpenGLShaderProgram> _blurProgram;
Program _frameProgram;
std::optional<QOpenGLShaderProgram> _imageProgram;
Ui::GL::Textures<1> _noiseTexture;
Ui::GL::Framebuffers<1> _noiseFramebuffer;
QOpenGLShader *_downscaleVertexShader = nullptr;
QOpenGLShader *_frameVertexShader = nullptr;
Ui::GL::Image _buttons;
QRect _pinOn;
QRect _pinOff;
QRect _back;
QRect _muteOn;
QRect _muteOff;
QRect _paused;
Ui::GL::Image _names;
std::vector<TileData> _tileData;
std::vector<int> _tileDataIndices;
Ui::CrossLineAnimation _pinIcon;
Ui::CrossLineAnimation _muteIcon;
Ui::RoundRect _pinBackground;
rpl::lifetime _lifetime;
};
} // namespace Calls::Group

View File

@@ -0,0 +1,301 @@
/*
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/group/calls_group_viewport_raster.h"
#include "calls/group/calls_group_common.h"
#include "calls/group/calls_group_viewport_tile.h"
#include "calls/group/calls_group_members_row.h"
#include "data/data_peer.h"
#include "media/view/media_view_pip.h"
#include "webrtc/webrtc_video_track.h"
#include "lang/lang_keys.h"
#include "styles/style_calls.h"
#include "styles/palette.h"
namespace Calls::Group {
namespace {
constexpr auto kBlurRadius = 15;
} // namespace
Viewport::RendererSW::RendererSW(not_null<Viewport*> owner)
: _owner(owner)
, _pinIcon(st::groupCallVideoTile.pin)
, _pinBackground(
(st::groupCallVideoTile.pinPadding.top()
+ st::groupCallVideoTile.pin.icon.height()
+ st::groupCallVideoTile.pinPadding.bottom()) / 2,
st::radialBg) {
}
void Viewport::RendererSW::paintFallback(
Painter &&p,
const QRegion &clip,
Ui::GL::Backend backend) {
auto bg = clip;
auto hq = PainterHighQualityEnabler(p);
const auto bounding = clip.boundingRect();
for (auto &[tile, tileData] : _tileData) {
tileData.stale = true;
}
for (const auto &tile : _owner->_tiles) {
if (!tile->shown()) {
continue;
}
paintTile(p, tile.get(), bounding, bg);
}
for (const auto rect : bg) {
p.fillRect(rect, st::groupCallBg);
}
for (auto i = _tileData.begin(); i != _tileData.end();) {
if (i->second.stale) {
i = _tileData.erase(i);
} else {
++i;
}
}
}
void Viewport::RendererSW::validateUserpicFrame(
not_null<VideoTile*> tile,
TileData &data) {
if (!_userpicFrame) {
data.userpicFrame = QImage();
return;
} else if (!data.userpicFrame.isNull()) {
return;
}
auto userpic = QImage(
tile->trackOrUserpicSize(),
QImage::Format_ARGB32_Premultiplied);
userpic.fill(Qt::black);
{
auto p = Painter(&userpic);
tile->row()->peer()->paintUserpicSquare(
p,
tile->row()->ensureUserpicView(),
0,
0,
userpic.width());
}
data.userpicFrame = Images::BlurLargeImage(
std::move(userpic),
kBlurRadius);
}
void Viewport::RendererSW::paintTile(
Painter &p,
not_null<VideoTile*> tile,
const QRect &clip,
QRegion &bg) {
const auto track = tile->track();
const auto markGuard = gsl::finally([&] {
tile->track()->markFrameShown();
});
const auto data = track->frameWithInfo(true);
auto &tileData = _tileData[tile];
tileData.stale = false;
_userpicFrame = (data.format == Webrtc::FrameFormat::None);
_pausedFrame = (track->state() == Webrtc::VideoState::Paused);
validateUserpicFrame(tile, tileData);
if (_userpicFrame || !_pausedFrame) {
tileData.blurredFrame = QImage();
} else if (tileData.blurredFrame.isNull()) {
tileData.blurredFrame = Images::BlurLargeImage(
data.original.scaled(
VideoTile::PausedVideoSize(),
Qt::KeepAspectRatio),
kBlurRadius);
}
const auto &image = _userpicFrame
? tileData.userpicFrame
: _pausedFrame
? tileData.blurredFrame
: data.original;
const auto frameRotation = _userpicFrame ? 0 : data.rotation;
Assert(!image.isNull());
const auto fill = [&](QRect rect) {
const auto intersected = rect.intersected(clip);
if (!intersected.isEmpty()) {
p.fillRect(intersected, st::groupCallMembersBg);
bg -= intersected;
}
};
using namespace Media::View;
const auto geometry = tile->geometry();
const auto x = geometry.x();
const auto y = geometry.y();
const auto width = geometry.width();
const auto height = geometry.height();
const auto scaled = FlipSizeByRotation(
image.size(),
frameRotation
).scaled(QSize(width, height), Qt::KeepAspectRatio);
const auto left = (width - scaled.width()) / 2;
const auto top = (height - scaled.height()) / 2;
const auto target = QRect(QPoint(x + left, y + top), scaled);
if (UsePainterRotation(frameRotation)) {
if (frameRotation) {
p.save();
p.rotate(frameRotation);
}
p.drawImage(RotatedRect(target, frameRotation), image);
if (frameRotation) {
p.restore();
}
} else if (frameRotation) {
p.drawImage(target, RotateFrameImage(image, frameRotation));
} else {
p.drawImage(target, image);
}
bg -= target;
if (left > 0) {
fill({ x, y, left, height });
}
if (const auto right = left + scaled.width(); right < width) {
fill({ x + right, y, width - right, height });
}
if (top > 0) {
fill({ x, y, width, top });
}
if (const auto bottom = top + scaled.height(); bottom < height) {
fill({ x, y + bottom, width, height - bottom });
}
paintTileControls(p, x, y, width, height, tile);
paintTileOutline(p, x, y, width, height, tile);
}
void Viewport::RendererSW::paintTileOutline(
Painter &p,
int x,
int y,
int width,
int height,
not_null<VideoTile*> tile) {
if (!tile->row()->speaking()) {
return;
}
const auto outline = st::groupCallOutline;
const auto &color = st::groupCallMemberActiveIcon;
p.setPen(Qt::NoPen);
p.fillRect(x, y, outline, height - outline, color);
p.fillRect(x + outline, y, width - outline, outline, color);
p.fillRect(
x + width - outline,
y + outline,
outline,
height - outline,
color);
p.fillRect(x, y + height - outline, width - outline, outline, color);
}
void Viewport::RendererSW::paintTileControls(
Painter &p,
int x,
int y,
int width,
int height,
not_null<VideoTile*> tile) {
p.setClipRect(x, y, width, height);
const auto guard = gsl::finally([&] { p.setClipping(false); });
const auto wide = _owner->wide();
if (wide) {
// Pin.
const auto pinInner = tile->pinInner();
VideoTile::PaintPinButton(
p,
tile->pinned(),
x + pinInner.x(),
y + pinInner.y(),
_owner->widget()->width(),
&_pinBackground,
&_pinIcon);
// Back.
const auto backInner = tile->backInner();
VideoTile::PaintBackButton(
p,
x + backInner.x(),
y + backInner.y(),
_owner->widget()->width(),
&_pinBackground);
}
if (_pausedFrame) {
p.fillRect(x, y, width, height, QColor(0, 0, 0, kShadowMaxAlpha));
st::groupCallPaused.paintInCenter(p, { x, y, width, height });
}
const auto shown = _owner->_controlsShownRatio;
if (shown == 0.) {
return;
}
const auto &st = st::groupCallVideoTile;
const auto fullShift = st.namePosition.y() + st::normalFont->height;
const auto shift = anim::interpolate(fullShift, 0, shown);
// Shadow.
if (_shadow.isNull()) {
_shadow = GenerateShadow(st.shadowHeight, 0, kShadowMaxAlpha);
}
const auto shadowRect = QRect(
x,
y + (height - anim::interpolate(0, st.shadowHeight, shown)),
width,
st.shadowHeight);
const auto shadowFill = shadowRect.intersected({ x, y, width, height });
if (shadowFill.isEmpty()) {
return;
}
const auto factor = style::DevicePixelRatio();
if (!_pausedFrame) {
p.drawImage(
shadowFill,
_shadow,
QRect(
0,
(shadowFill.y() - shadowRect.y()) * factor,
_shadow.width(),
shadowFill.height() * factor));
}
const auto row = tile->row();
row->lazyInitialize(st::groupCallMembersListItem);
// Mute.
const auto &icon = st::groupCallVideoCrossLine.icon;
const auto iconLeft = x + width - st.iconPosition.x() - icon.width();
const auto iconTop = y + (height
- st.iconPosition.y()
- icon.height()
+ shift);
row->paintMuteIcon(
p,
{ iconLeft, iconTop, icon.width(), icon.height() },
MembersRowStyle::Video);
// Name.
p.setPen(st::groupCallVideoTextFg);
const auto hasWidth = width
- st.iconPosition.x() - icon.width()
- st.namePosition.x();
const auto nameLeft = x + st.namePosition.x();
const auto nameTop = y + (height
- st.namePosition.y()
- st::semiboldFont->height
+ shift);
row->name().drawLeftElided(p, nameLeft, nameTop, hasWidth, width);
}
} // namespace Calls::Group

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