Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
391b370b0f | ||
|
|
9b863fcf5b | ||
|
|
609868858b | ||
|
|
c291bf0861 | ||
|
|
bfd5482c15 | ||
|
|
52ea153c31 | ||
|
|
287c99ed2f | ||
|
|
8e60f54dac | ||
|
|
1f3fae53ca | ||
|
|
05ac97526e | ||
|
|
5fadfed47a | ||
|
|
ec1d547f27 | ||
|
|
9fee0ace4c | ||
|
|
84c2a33c18 | ||
|
|
83744e77d1 | ||
|
|
85635dbefd | ||
|
|
7afda6dfc3 | ||
|
|
3a35b6c5fd | ||
|
|
c449e45def | ||
|
|
6c704e3595 | ||
|
|
318794692f | ||
|
|
62a0878198 | ||
|
|
cb1c8c4aea | ||
|
|
1ee5c14b85 | ||
|
|
01410e5d92 | ||
|
|
0e5d26a469 | ||
|
|
95e5b7be0b | ||
|
|
6d8f277904 | ||
|
|
b6325ec9d4 | ||
|
|
3373a2f382 | ||
|
|
9fcd878f1d | ||
|
|
1c50c35abe | ||
|
|
6a0ee57054 | ||
|
|
00adfa6f3d | ||
|
|
6058c8862e | ||
|
|
e5f2f68188 | ||
|
|
c6ee2772e2 | ||
|
|
16caff1ca4 | ||
|
|
ac2ae16f47 | ||
|
|
c40758f30d | ||
|
|
16aafe28d5 | ||
|
|
db96605332 | ||
|
|
31954f5266 | ||
|
|
66718de562 | ||
|
|
e2d02f4e4b | ||
|
|
e3d4bf192f | ||
|
|
e7b94f3d3a | ||
|
|
30be7af3e3 | ||
|
|
9069b4b269 | ||
|
|
040c80ae0b | ||
|
|
a87c9b15d2 | ||
|
|
635cae4f94 | ||
|
|
67e6d12384 | ||
|
|
9ccdc0e94b | ||
|
|
762c0aa579 | ||
|
|
5dc78932d7 | ||
|
|
e9c5f18142 | ||
|
|
175023b2dc | ||
|
|
0a7c42c59a | ||
|
|
42122fdea0 | ||
|
|
4bfe65d8ab | ||
|
|
1b06fe1220 | ||
|
|
1b11a7feae | ||
|
|
6b60b51775 | ||
|
|
981162e6b9 | ||
|
|
c5dd99b1f1 | ||
|
|
53c536d76d | ||
|
|
e3ab8821b9 | ||
|
|
6befea6a13 | ||
|
|
f24e3c6192 | ||
|
|
63e593b3a8 | ||
|
|
7ca4ec1bed | ||
|
|
8ed1961886 | ||
|
|
962ec1e454 | ||
|
|
bcc718b5a9 |
72
MSVC.md
72
MSVC.md
@@ -39,7 +39,7 @@ Extract to **D:\TBuild\Libraries**
|
||||
|
||||
http://www.zlib.net/ > Download [**zlib source code, version 1.2.8, zipfile format**](http://zlib.net/zlib128.zip)
|
||||
|
||||
Extract to **D:\TBuild\Libraries\**
|
||||
Extract to **D:\\TBuild\\Libraries\\**
|
||||
|
||||
#####Building library
|
||||
|
||||
@@ -66,13 +66,23 @@ or download in ZIP and extract to **D:\TBuild\Libraries\**, rename **libexif-0.6
|
||||
* Build Debug configuration
|
||||
* Build Release configuration
|
||||
|
||||
####OpenAL Soft
|
||||
####OpenAL Soft, slightly patched
|
||||
|
||||
Get sources by git – in [Git Bash](http://git-scm.com/downloads) go to **/d/tbuild/libraries** and run
|
||||
|
||||
git clone git://repo.or.cz/openal-soft.git
|
||||
|
||||
to have **D:\TBuild\Libraries\openal-soft\CMakeLists.txt**
|
||||
to have **D:\TBuild\Libraries\openal-soft\CMakeLists.txt**, then in [Git Bash](http://git-scm.com/downloads) go to **/d/tbuild/libraries/openal-soft** and run
|
||||
|
||||
git checkout 9479ea656b
|
||||
|
||||
Apply patch
|
||||
|
||||
* OR copy (with overwrite!) everything from **D:\\TBuild\\tdesktop\\\_openal\_patch\\** to **D:\\TBuild\\Libraries\\openal-soft\\**
|
||||
* OR in Git Bash go to **/d/tbuild/libraries/openal-soft/** and run
|
||||
|
||||
git apply ./../../tdesktop/Telegram/_openal_patch.diff
|
||||
|
||||
|
||||
#####Building library
|
||||
|
||||
@@ -87,35 +97,47 @@ to have **D:\TBuild\Libraries\openal-soft\CMakeLists.txt**
|
||||
* OpenAL32 Properties > C/C++ > Code Generation > Runtime Library = **Multi-threaded (/MT)** – **OK**
|
||||
* common Properties > C/C++ > Code Generation > Runtime Library = **Multi-threaded (/MT)** – **OK**
|
||||
|
||||
####libogg 1.3.2
|
||||
|
||||
Get sources from http://xiph.org/downloads/ – in [ZIP](http://downloads.xiph.org/releases/ogg/libogg-1.3.2.zip) and extract to **D:\TBuild\Libraries\**
|
||||
|
||||
#####Building library
|
||||
|
||||
* Open in VS2013 **D:\TBuild\Libraries\libogg-1.3.2\win32\VS2010\libogg_static.sln** > One-way upgrade – **OK**
|
||||
* Build Debug configuration
|
||||
* Build Release configuration
|
||||
|
||||
####Opus codec, opusfile
|
||||
####Opus codec
|
||||
|
||||
Get sources by git – in [Git Bash](http://git-scm.com/downloads) go to **/d/tbuild/libraries** and run
|
||||
|
||||
git clone git://git.opus-codec.org/opus.git
|
||||
git clone git://git.xiph.org/opusfile.git
|
||||
git clone https://github.com/telegramdesktop/opus.git
|
||||
|
||||
to have **D:\TBuild\Libraries\opus\win32**
|
||||
|
||||
#####Building libraries
|
||||
|
||||
* Open in VS2013 **D:\TBuild\Libraries\opus\win32\VS2010\opus.sln** > One-way upgrade – **OK**
|
||||
* Open in VS2013 **D:\TBuild\Libraries\opus\win32\VS2010\opus.sln**
|
||||
* Build Debug configuration
|
||||
* Build Release configuration
|
||||
* Open in VS2013 **D:\TBuild\Libraries\opusfile\win32\VS2010\opusfile.sln** > One-way upgrade – **OK**
|
||||
* For **Debug** and **Release** configurations
|
||||
* opusfile > C/C++ > General > Additional include directories > Add **../../../libogg-1.3.2/include;**
|
||||
* Build Debug configuration
|
||||
* Build Release configuration
|
||||
* Build Release configuration (it will be required in **FFmpeg** build!)
|
||||
|
||||
####FFmpeg
|
||||
|
||||
https://www.ffmpeg.org/download.html > Download [ffmpeg-2.6.3.tar.bz2](http://ffmpeg.org/releases/ffmpeg-2.6.3.tar.bz2)
|
||||
|
||||
Extract to **D:\\TBuild\\Libraries**
|
||||
|
||||
http://msys2.github.io/ > Download [msys2-x86_64-20150512.exe](http://sourceforge.net/projects/msys2/files/Base/x86_64/msys2-x86_64-20150512.exe/download) and install to **D:\\msys64**
|
||||
|
||||
#####Building libraries
|
||||
|
||||
Download [yasm for Win64](http://www.tortall.net/projects/yasm/releases/yasm-1.3.0-win64.exe) from http://yasm.tortall.net/Download.html, rename **yasm-1.3.0-win64.exe** to **yasm.exe** and place it to your Visual C++ **bin** directory, like **\\Program Files (x86)\\Microsoft Visual Studio 12\\VC\\bin\\**
|
||||
|
||||
Open **VS2013 x86 Native Tools Command Prompt.bat** (should be in **\\Program Files (x86)\\Microsoft Visual Studio 12.0\\Common7\\Tools\\Shortcuts\\** folder), go to **D:\\msys64\\** and launch **msys2_shell.bat**, there run
|
||||
|
||||
PATH="/c/Program Files (x86)/Microsoft Visual Studio 12.0/VC/BIN:$PATH"
|
||||
|
||||
cd /d/TBuild/Libraries/ffmpeg-2.6.3
|
||||
pacman -S msys/make
|
||||
pacman -S mingw64/mingw-w64-x86_64-opus
|
||||
pacman -S pkg-config
|
||||
|
||||
PKG_CONFIG_PATH="/mingw64/lib/pkgconfig:$PKG_CONFIG_PATH"
|
||||
|
||||
./configure --toolchain=msvc --disable-programs --disable-everything --enable-libopus --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=aasc --enable-decoder=mp1 --enable-decoder=mp1float --enable-decoder=mp2 --enable-decoder=mp2float --enable-decoder=mp3 --enable-decoder=mp3adu --enable-decoder=mp3adufloat --enable-decoder=mp3float --enable-decoder=mp3on4 --enable-decoder=mp3on4float --enable-decoder=wavpack --enable-decoder=opus --enable-decoder=vorbis --enable-decoder=wmalossless --enable-decoder=wmapro --enable-decoder=wmav1 --enable-decoder=wmav2 --enable-decoder=wmavoice --enable-encoder=libopus --enable-parser=aac --enable-parser=aac_latm --enable-parser=mpegaudio --enable-parser=opus --enable-parser=vorbis --enable-demuxer=aac --enable-demuxer=wav --enable-demuxer=mp3 --enable-demuxer=ogg --enable-demuxer=mov --enable-muxer=ogg --enable-muxer=opus --extra-ldflags="-libpath:/d/TBuild/Libraries/opus/win32/VS2010/Win32/Release celt.lib silk_common.lib silk_float.lib"
|
||||
|
||||
make
|
||||
make install
|
||||
|
||||
####Qt 5.4.0, slightly patched
|
||||
|
||||
@@ -126,9 +148,9 @@ Extract to **D:\TBuild\Libraries\**, rename **qt-everywhere-opensource-src-5.4.0
|
||||
Apply patch
|
||||
|
||||
* OR copy (with overwrite!) everything from **D:\TBuild\tdesktop\\\_qt\_5\_4\_0\_patch\** to **D:\TBuild\Libraries\QtStatic\**
|
||||
* OR copy **D:\TBuild\tdesktop\\\_qt\_5\_4\_0\_patch.diff** to **D:\TBuild\Libraries\QtStatic\**, go there in Git Bash and run
|
||||
* OR in Git Bash go to **/d/TBuild/Libraries/QtStatic/** and run
|
||||
|
||||
git apply _qt_5_4_0_patch.diff
|
||||
git apply ./../../tdesktop/Telegram/_qt_5_4_0_patch.diff
|
||||
|
||||
#####Building library
|
||||
|
||||
|
||||
23
QTCREATOR.md
23
QTCREATOR.md
@@ -34,27 +34,24 @@ Install dev libraries
|
||||
|
||||
Install audio libraries
|
||||
|
||||
####libogg-1.3.2
|
||||
|
||||
[Download libogg-1.3.2 sources](http://downloads.xiph.org/releases/ogg/libogg-1.3.2.tar.xz) from http://xiph.org/downloads, extract to **/home/user/TBuild/Libraries**, go to **/home/user/TBuild/Libraries/libogg-1.3.2** and run
|
||||
|
||||
./configure
|
||||
make
|
||||
sudo make install
|
||||
|
||||
####Opus codec 1.1
|
||||
|
||||
[Download opus-1.1 sources](http://downloads.xiph.org/releases/opus/opus-1.1.tar.gz) from http://www.opus-codec.org/downloads, extract to **/home/user/TBuild/Libraries**, go to **/home/user/TBuild/Libraries/opus-1.1** and run
|
||||
Download [opus-1.1 sources](http://downloads.xiph.org/releases/opus/opus-1.1.tar.gz) from http://www.opus-codec.org/downloads, extract to **/home/user/TBuild/Libraries**, go to **/home/user/TBuild/Libraries/opus-1.1** and run
|
||||
|
||||
./configure
|
||||
make
|
||||
sudo make install
|
||||
|
||||
####opusfile-0.6
|
||||
####FFmpeg
|
||||
|
||||
[Download opusfile-0.6 sources](http://downloads.xiph.org/releases/opus/opusfile-0.6.tar.gz) from http://www.opus-codec.org/downloads, extract to **/home/user/TBuild/Libraries**, go to **/home/user/TBuild/Libraries/opusfile-0.6** and run
|
||||
Download sources [ffmpeg-2.6.3.tar.bz2](http://ffmpeg.org/releases/ffmpeg-2.6.3.tar.bz2) from https://www.ffmpeg.org/download.html, extract to **/home/user/TBuild/Libraries** to have **/home/user/TBuild/Libraries/ffmpeg-2.6.3**, go to **/home/user/TBuild/Libraries/ffmpeg-2.6.3** and run
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get -y --force-yes install autoconf automake build-essential libass-dev libfreetype6-dev libgpac-dev libsdl1.2-dev libtheora-dev libtool libva-dev libvdpau-dev libvorbis-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev pkg-config texi2html zlib1g-dev
|
||||
sudo apt-get install yasm
|
||||
|
||||
./configure --prefix=/usr/local --disable-programs --disable-everything --enable-libopus --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=aasc --enable-decoder=mp1 --enable-decoder=mp1float --enable-decoder=mp2 --enable-decoder=mp2float --enable-decoder=mp3 --enable-decoder=mp3adu --enable-decoder=mp3adufloat --enable-decoder=mp3float --enable-decoder=mp3on4 --enable-decoder=mp3on4float --enable-decoder=wavpack --enable-decoder=opus --enable-decoder=vorbis --enable-decoder=wmalossless --enable-decoder=wmapro --enable-decoder=wmav1 --enable-decoder=wmav2 --enable-decoder=wmavoice --enable-encoder=libopus --enable-parser=aac --enable-parser=aac_latm --enable-parser=mpegaudio --enable-parser=opus --enable-parser=vorbis --enable-demuxer=aac --enable-demuxer=wav --enable-demuxer=mp3 --enable-demuxer=ogg --enable-demuxer=mov --enable-muxer=ogg --enable-muxer=opus
|
||||
|
||||
./configure
|
||||
make
|
||||
sudo make install
|
||||
|
||||
@@ -72,7 +69,7 @@ In Terminal go to **/home/user/TBuild/Libraries** and run
|
||||
|
||||
git clone git://repo.or.cz/openal-soft.git
|
||||
|
||||
then go to **/home/user/TBuild/Libraries/openal-soft/build** and run
|
||||
then go to **/home/user/TBuild/Libraries/openal-soft/build** and run
|
||||
|
||||
sudo apt-get install cmake
|
||||
cmake -D LIBTYPE:STRING=STATIC ..
|
||||
|
||||
22
README.md
22
README.md
@@ -6,17 +6,9 @@ Source code is published under GPL v3, license is available [here](https://githu
|
||||
|
||||
###Supported systems
|
||||
|
||||
* Windows XP
|
||||
* Windows Vista
|
||||
* Windows 7
|
||||
* Windows 8 (**not** RT)
|
||||
* Windows 8.1 (**not** RT)
|
||||
* OS X 10.7
|
||||
* OS X 10.8
|
||||
* OS X 10.9
|
||||
* Ubuntu 12.04
|
||||
* Ubuntu 13.04
|
||||
* Ubuntu 14.04
|
||||
* Windows XP - Windows 8.1 (**not** RT)
|
||||
* Mac OS X 10.7 - Mac OS X 10.10
|
||||
* Ubuntu 12.04 - Ubuntu 14.04
|
||||
|
||||
###Third-party
|
||||
|
||||
@@ -27,15 +19,15 @@ Source code is published under GPL v3, license is available [here](https://githu
|
||||
* LZMA SDK 9.20 ([public domain](http://www.7-zip.org/sdk.html))
|
||||
* liblzma ([public domain](http://tukaani.org/xz/))
|
||||
* OpenAL Soft ([LGPL](http://kcat.strangesoft.net/openal.html))
|
||||
* Opus codec, opusfile ([BSD license](http://www.opus-codec.org/license/))
|
||||
* libogg ([BSD license](http://www.xiph.org/downloads/))
|
||||
* Opus codec ([BSD license](http://www.opus-codec.org/license/))
|
||||
* FFmpeg ([LGPL](https://www.ffmpeg.org/legal.html))
|
||||
* Open Sans font ([Apache License](http://www.apache.org/licenses/LICENSE-2.0.html))
|
||||
|
||||
###[Build instructions for Visual Studio 2013](https://github.com/telegramdesktop/tdesktop/blob/master/MSVC.md)
|
||||
|
||||
###[Build instructions for XCode 5.1.1](https://github.com/telegramdesktop/tdesktop/blob/master/XCODE.md)
|
||||
###[Build instructions for XCode 6.3.1](https://github.com/telegramdesktop/tdesktop/blob/master/XCODE.md)
|
||||
|
||||
###[Build instructions for Qt Creator 3.1.2 Ubuntu](https://github.com/telegramdesktop/tdesktop/blob/master/QTCREATOR.md)
|
||||
###[Build instructions for Qt Creator 3.2.0 Ubuntu](https://github.com/telegramdesktop/tdesktop/blob/master/QTCREATOR.md)
|
||||
|
||||
##Projects in Telegram solution
|
||||
|
||||
|
||||
@@ -23,7 +23,9 @@ Replace '\-lfontconfig' '\/usr\/lib\/x86_64\-linux\-gnu\/libfontconfig\.a \/usr\
|
||||
Replace '\-lfreetype' '\/usr\/lib\/x86_64\-linux\-gnu\/libfreetype\.a'
|
||||
Replace '\-lpng' '\/usr\/lib\/x86_64\-linux\-gnu\/libpng\.a'
|
||||
Replace '\-lXext' '\/usr\/lib\/x86_64\-linux\-gnu\/libXext\.a'
|
||||
Replace '\-lopusfile' '\/usr\/local\/lib\/libopusfile\.a'
|
||||
Replace '\-lopus' '\/usr\/local\/lib\/libopus\.a'
|
||||
Replace '\-lopenal' '\/usr\/local\/lib\/libopenal\.a'
|
||||
Replace '\-logg' '\/usr\/local\/lib\/libogg\.a'
|
||||
Replace '\-lavformat' '\/usr\/local\/lib\/libavformat\.a'
|
||||
Replace '\-lavcodec' '\/usr\/local\/lib\/libavcodec\.a'
|
||||
Replace '\-lswresample' '\/usr\/local\/lib\/libswresample\.a'
|
||||
Replace '\-lavutil' '\/usr\/local\/lib\/libavutil\.a'
|
||||
|
||||
@@ -23,7 +23,9 @@ Replace '\-lfontconfig' '\/usr\/lib\/i386\-linux\-gnu\/libfontconfig\.a \/usr\/l
|
||||
Replace '\-lfreetype' '\/usr\/lib\/i386\-linux\-gnu\/libfreetype\.a'
|
||||
Replace '\-lpng' '\/usr\/lib\/i386\-linux\-gnu\/libpng\.a'
|
||||
Replace '\-lXext' '\/usr\/lib\/i386\-linux\-gnu\/libXext\.a'
|
||||
Replace '\-lopusfile' '\/usr\/local\/lib\/libopusfile\.a'
|
||||
Replace '\-lopus' '\/usr\/local\/lib\/libopus\.a'
|
||||
Replace '\-lopenal' '\/usr\/local\/lib\/libopenal\.a'
|
||||
Replace '\-logg' '\/usr\/local\/lib\/libogg\.a'
|
||||
Replace '\-lavformat' '\/usr\/local\/lib\/libavformat\.a'
|
||||
Replace '\-lavcodec' '\/usr\/local\/lib\/libavcodec\.a'
|
||||
Replace '\-lswresample' '\/usr\/local\/lib\/libswresample\.a'
|
||||
Replace '\-lavutil' '\/usr\/local\/lib\/libavutil\.a'
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
@echo OFF
|
||||
|
||||
set "AppVersion=8017"
|
||||
set "AppVersionStrSmall=0.8.17"
|
||||
set "AppVersionStr=0.8.17"
|
||||
set "AppVersionStrFull=0.8.17.0"
|
||||
set "DevChannel=0"
|
||||
set "AppVersion=8027"
|
||||
set "AppVersionStrSmall=0.8.27"
|
||||
set "AppVersionStr=0.8.27"
|
||||
set "AppVersionStrFull=0.8.27.0"
|
||||
set "DevChannel=1"
|
||||
|
||||
if %DevChannel% neq 0 goto preparedev
|
||||
|
||||
|
||||
@@ -69,6 +69,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
"lng_reconnecting_try_now" = "Try now";
|
||||
|
||||
"lng_status_service_notifications" = "service notifications";
|
||||
"lng_status_bot" = "bot";
|
||||
"lng_status_bot_reads_all" = "has access to messages";
|
||||
"lng_status_bot_not_reads_all" = "has no access to messages";
|
||||
"lng_status_offline" = "last seen a long time ago";
|
||||
"lng_status_recently" = "last seen recently";
|
||||
"lng_status_last_week" = "last seen within a week";
|
||||
@@ -333,7 +336,10 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_profile_chat_unaccessible" = "Group is unaccessible";
|
||||
"lng_topbar_info" = "Info";
|
||||
"lng_profile_about_section" = "About";
|
||||
"lng_profile_settings_section" = "Settings";
|
||||
"lng_profile_bot_settings" = "Settings";
|
||||
"lng_profile_bot_help" = "Help";
|
||||
"lng_profile_participants_section" = "Members";
|
||||
"lng_profile_info" = "Contact info";
|
||||
"lng_profile_group_info" = "Group info";
|
||||
@@ -343,6 +349,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
"lng_profile_clear_history" = "Clear history";
|
||||
"lng_profile_send_message" = "Send Message";
|
||||
"lng_profile_share_contact" = "Share Contact";
|
||||
"lng_profile_invite_to_group" = "Add to Group";
|
||||
"lng_profile_delete_contact" = "Delete";
|
||||
"lng_profile_set_group_photo" = "Set Photo";
|
||||
"lng_profile_add_participant" = "Add Member";
|
||||
@@ -450,10 +457,19 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_send_button" = "Send";
|
||||
"lng_message_ph" = "Write a message..";
|
||||
"lng_record_cancel" = "Release outside this field to cancel";
|
||||
"lng_empty_history" = "";
|
||||
"lng_willbe_history" = "Please select chat to start messaging";
|
||||
"lng_message_with_from" = "[c]{from}:[/c] {message}";
|
||||
"lng_from_you" = "You";
|
||||
"lng_bot_description" = "What can this bot do?";
|
||||
|
||||
"lng_bot_start" = "Start";
|
||||
"lng_bot_choose_group" = "Choose Group";
|
||||
"lng_bot_no_groups" = "You have no groups";
|
||||
"lng_bot_groups_not_found" = "No groups found";
|
||||
"lng_bot_sure_invite" = "Add the bot to «{group}»?";
|
||||
"lng_bot_already_in_group" = "The bot is already a member of the group.";
|
||||
|
||||
"lng_typing" = "typing";
|
||||
"lng_user_typing" = "{user} is typing";
|
||||
@@ -584,7 +600,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_new_version_wrap" = "Telegram Desktop was updated to version {version}\n\n{changes}\n\nFull version history is available here:\n{link}";
|
||||
"lng_new_version_minor" = "— Bug fixes and other minor improvements";
|
||||
"lng_new_version_text" = "— Added support for sticker packs\n— New emoji and sticker panel";
|
||||
"lng_new_version_text" = "This new version includes support for bots using the new bot API, free for everyone. If you're an engineer, create your own bots for games, services or integrations.\n\nLearn more at {blog_link}";
|
||||
|
||||
"lng_menu_insert_unicode" = "Insert Unicode control character";
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ wndBG: #FFF;
|
||||
wndShadow: sprite(209px, 46px, 19px, 19px);
|
||||
wndShadowShift: 1px;
|
||||
|
||||
layerAlpha: 0.3;
|
||||
layerAlpha: 0.5;
|
||||
layerBG: #000;
|
||||
|
||||
titleBG: #6389a8;
|
||||
@@ -390,7 +390,8 @@ btnIntroNext: flatButton(btnDefNext, btnDefBig) {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
boxShadow: sprite(230px, 46px, 9px, 9px);
|
||||
boxShadow: sprite(363px, 50px, 15px, 15px);
|
||||
boxShadowShift: 2px;
|
||||
|
||||
introCountry: countryInput {
|
||||
width: 300px;
|
||||
@@ -776,7 +777,7 @@ activeFadeOutDuration: 3000;
|
||||
|
||||
msgRadius: 3px;
|
||||
|
||||
msgMaxWidth: 550px;
|
||||
msgMaxWidth: 430px;
|
||||
msgFont: font(fsize);
|
||||
msgNameFont: font(fsize semibold);
|
||||
msgServiceFont: font(fsize semibold);
|
||||
@@ -985,12 +986,33 @@ btnAttachPhoto: iconedButton(btnAttachDocument) {
|
||||
btnAttachEmoji: iconedButton(btnAttachDocument) {
|
||||
overBgColor: white;
|
||||
icon: sprite(363px, 344px, 21px, 22px);
|
||||
iconPos: point(6px, 13px);
|
||||
iconPos: point(6px, 12px);
|
||||
downIcon: sprite(363px, 344px, 21px, 22px);
|
||||
downIconPos: point(6px, 13px);
|
||||
downIconPos: point(6px, 12px);
|
||||
|
||||
width: 33px;
|
||||
}
|
||||
btnBotKbShow: iconedButton(btnAttachEmoji) {
|
||||
icon: sprite(375px, 74px, 21px, 16px);
|
||||
iconPos: point(6px, 16px);
|
||||
downIcon: sprite(375px, 74px, 21px, 16px);
|
||||
downIconPos: point(6px, 16px);
|
||||
}
|
||||
btnBotKbHide: iconedButton(btnAttachEmoji) {
|
||||
icon: sprite(352px, 74px, 23px, 14px);
|
||||
iconPos: point(5px, 17px);
|
||||
downIcon: sprite(352px, 74px, 23px, 14px);
|
||||
downIconPos: point(5px, 17px);
|
||||
}
|
||||
btnRecordAudio: sprite(363px, 366px, 16px, 24px);
|
||||
btnRecordAudioActive: sprite(379px, 366px, 16px, 24px);
|
||||
recordSignalColor: #f17077;
|
||||
recordSignalMin: 5px;
|
||||
recordSignalMax: 10px;
|
||||
recordCancel: #aaa;
|
||||
recordCancelActive: #ec6466;
|
||||
recordFont: font(13px);
|
||||
recordTextTop: 14px;
|
||||
|
||||
replySkip: 51px;
|
||||
replyColor: #377aae;
|
||||
@@ -1031,7 +1053,7 @@ textRectMargins: margins(-2px, -1px, -2px, -1px);
|
||||
taMsgField: flatTextarea(taDefFlat) {
|
||||
font: msgFont;
|
||||
}
|
||||
maxFieldHeight: 250px;
|
||||
maxFieldHeight: 265px;
|
||||
|
||||
newMsgSound: ':/gui/art/newmsg.wav';
|
||||
|
||||
@@ -1175,6 +1197,7 @@ btnShareContact: flatButton(btnDefNext, btnDefBig) {
|
||||
font: font(17px);
|
||||
overFont: font(17px);
|
||||
}
|
||||
profileMinBtnPadding: 10px;
|
||||
|
||||
forwardWidth: 364px;
|
||||
forwardMargins: margins(30px, 10px, 30px, 10px);
|
||||
@@ -1375,6 +1398,7 @@ dropdownDef: dropdown {
|
||||
|
||||
padding: margins(10px, 10px, 10px, 10px);
|
||||
shadow: sprite(241px, 46px, 6px, 6px);
|
||||
shadowShift: 1px;
|
||||
|
||||
duration: 150;
|
||||
width: 0px;
|
||||
@@ -1639,6 +1663,38 @@ stickerPanSize: size(55px, 55px);
|
||||
stickerPanPadding: 11px;
|
||||
stickerPanDelete: sprite(123px, 132px, 12px, 12px);
|
||||
stickerPanDeleteOpacity: 0.5;
|
||||
stickerIconPadding: 3px;
|
||||
stickerIconHover: #e8ecef;
|
||||
stickerIconSel: #dfe3e6;
|
||||
stickerIconRecent: sprite(342px, 50px, 21px, 22px);
|
||||
stickerIconLeft: sprite(342px, 72px, 40px, 1px);
|
||||
stickerIconRight: sprite(342px, 73px, 40px, 1px);
|
||||
stickerIconMove: 400;
|
||||
|
||||
botKbDuration: 200;
|
||||
botKbBg: #f7f7f7;
|
||||
botKbOverBg: #e8ecef;
|
||||
botKbDownBg: #dfe3e6;
|
||||
botKbColor: #8a8a8f;
|
||||
botKbFont: font(16px);
|
||||
botKbButton: botKeyboardButton {
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
height: 46px;
|
||||
textTop: 13px;
|
||||
downTextTop: 14px;
|
||||
}
|
||||
botKbTinyButton: botKeyboardButton {
|
||||
margin: 4px;
|
||||
padding: 3px;
|
||||
height: 25px;
|
||||
textTop: 2px;
|
||||
downTextTop: 3px;
|
||||
}
|
||||
botKbScroll: flatScroll(newScroll) {
|
||||
deltax: 3px;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
mvBgColor: #222;
|
||||
mvBgOpacity: 0.92;
|
||||
@@ -1715,9 +1771,13 @@ mvDocExtColor: white;
|
||||
mvDocExtPadding: 10px;
|
||||
mvDocLinksTop: 57px;
|
||||
mvDocRed: sprite(0px, 400px, 80px, 80px);
|
||||
mvDocRedColor: #e47272;
|
||||
mvDocYellow: sprite(80px, 400px, 80px, 80px);
|
||||
mvDocYellowColor: #efc274;
|
||||
mvDocGreen: sprite(160px, 400px, 80px, 80px);
|
||||
mvDocGreenColor: #61b96e;
|
||||
mvDocBlue: sprite(240px, 400px, 80px, 80px);
|
||||
mvDocBlueColor: #72b1df;
|
||||
mvDocLink: linkButton(btnDefLink) {
|
||||
color: #4595d3;
|
||||
overColor: #4595d3;
|
||||
@@ -1796,7 +1856,7 @@ radialBgOpacity: 0.4;
|
||||
radialDownload: sprite(346px, 0px, 50px, 50px);
|
||||
radialDownloadOpacity: 0.8;
|
||||
radialCancel: sprite(378px, 50px, 18px, 18px);
|
||||
radialCancelOpacity: 0.7;
|
||||
radialCancelOpacity: 1.0;
|
||||
|
||||
overviewLoader: size(34px, 14px);
|
||||
overviewLoaderPoint: size(4px, 4px);
|
||||
@@ -1865,6 +1925,8 @@ mentionPadding: margins(8px, 5px, 8px, 5px);
|
||||
mentionTop: 11px;
|
||||
mentionFont: linkFont;
|
||||
mentionPhotoSize: msgPhotoSize;
|
||||
botCommandFont: font(fsize semibold);
|
||||
botDescFont: font(fsize italic);
|
||||
|
||||
sessionsHeight: 440px;
|
||||
sessionHeight: 70px;
|
||||
@@ -1893,3 +1955,5 @@ webPageDescriptionFont: font(fsize);
|
||||
webPagePhotoSkip: 5px;
|
||||
webPagePhotoSize: 100px;
|
||||
webPagePhotoDelta: 8px;
|
||||
|
||||
botDescSkip: 8px;
|
||||
|
||||
@@ -254,7 +254,16 @@ dropdown {
|
||||
|
||||
padding: margins;
|
||||
shadow: sprite;
|
||||
shadowShift: number;
|
||||
|
||||
duration: number;
|
||||
width: number;
|
||||
}
|
||||
|
||||
botKeyboardButton {
|
||||
margin: number;
|
||||
padding: number;
|
||||
height: number;
|
||||
textTop: number;
|
||||
downTextTop: number;
|
||||
}
|
||||
|
||||
@@ -1574,7 +1574,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org\n\
|
||||
tcpp << "\tFontFamilies _fontFamilies;\n";
|
||||
tcpp << "\tFontDatas _fontsMap;\n";
|
||||
tcpp << "\tColorDatas _colorsMap;\n";
|
||||
tcpp << "int _spriteWidth = " << spriteWidths[0] << ";\n\n";
|
||||
tcpp << "\tint _spriteWidth = " << spriteWidths[0] << ";\n\n";
|
||||
tcpp << "\tvoid startManager() {\n";
|
||||
|
||||
tcpp << "\n\t\tif (cRetina()) {\n";
|
||||
|
||||
@@ -96,12 +96,11 @@ void writeLog(const wstring &msg) {
|
||||
}
|
||||
}
|
||||
|
||||
void delFolder() {
|
||||
wstring delPath = L"tupdates\\ready", delFolder = L"tupdates";
|
||||
void fullClearPath(const wstring &dir) {
|
||||
WCHAR path[4096];
|
||||
memcpy(path, delPath.c_str(), (delPath.size() + 1) * sizeof(WCHAR));
|
||||
path[delPath.size() + 1] = 0;
|
||||
writeLog(L"Fully clearing path '" + delPath + L"'..");
|
||||
memcpy(path, dir.c_str(), (dir.size() + 1) * sizeof(WCHAR));
|
||||
path[dir.size() + 1] = 0;
|
||||
writeLog(L"Fully clearing path '" + dir + L"'..");
|
||||
SHFILEOPSTRUCT file_op = {
|
||||
NULL,
|
||||
FO_DELETE,
|
||||
@@ -116,13 +115,46 @@ void delFolder() {
|
||||
};
|
||||
int res = SHFileOperation(&file_op);
|
||||
if (res) writeLog(L"Error: failed to clear path! :(");
|
||||
}
|
||||
|
||||
void delFolder() {
|
||||
wstring delPathOld = L"tupdates\\ready", delPath = L"tupdates\\temp", delFolder = L"tupdates";
|
||||
fullClearPath(delPathOld);
|
||||
fullClearPath(delPath);
|
||||
RemoveDirectory(delFolder.c_str());
|
||||
}
|
||||
|
||||
DWORD versionNum = 0, versionLen = 0, readLen = 0;
|
||||
WCHAR versionStr[32] = { 0 };
|
||||
|
||||
bool update() {
|
||||
writeLog(L"Update started..");
|
||||
|
||||
wstring updDir = L"tupdates\\ready";
|
||||
wstring updDir = L"tupdates\\temp", readyFilePath = L"tupdates\\temp\\ready", tdataDir = L"tupdates\\temp\\tdata";
|
||||
{
|
||||
HANDLE readyFile = CreateFile(readyFilePath.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
|
||||
if (readyFile != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(readyFile);
|
||||
} else {
|
||||
updDir = L"tupdates\\ready"; // old
|
||||
tdataDir = L"tupdates\\ready\\tdata";
|
||||
}
|
||||
}
|
||||
|
||||
HANDLE versionFile = CreateFile((tdataDir + L"\\version").c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
|
||||
if (versionFile != INVALID_HANDLE_VALUE) {
|
||||
if (ReadFile(versionFile, &versionNum, sizeof(DWORD), &readLen, NULL) != TRUE || readLen != sizeof(DWORD)) {
|
||||
versionNum = 0;
|
||||
} else if (ReadFile(versionFile, &versionLen, sizeof(DWORD), &readLen, NULL) != TRUE || readLen != sizeof(DWORD) || versionLen > 63) {
|
||||
versionNum = 0;
|
||||
} else if (ReadFile(versionFile, versionStr, versionLen, &readLen, NULL) != TRUE || readLen != versionLen) {
|
||||
versionNum = 0;
|
||||
}
|
||||
CloseHandle(versionFile);
|
||||
writeLog(L"Version file read.");
|
||||
} else {
|
||||
writeLog(L"Could not open version file to update registry :(");
|
||||
}
|
||||
|
||||
deque<wstring> dirs;
|
||||
dirs.push_back(updDir);
|
||||
@@ -154,22 +186,27 @@ bool update() {
|
||||
}
|
||||
|
||||
do {
|
||||
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
wstring fname = dir + L"\\" + findData.cFileName;
|
||||
if (fname.substr(0, tdataDir.size()) == tdataDir && (fname.size() <= tdataDir.size() || fname.at(tdataDir.size()) == '/')) {
|
||||
writeLog(L"Skipped 'tdata' path '" + fname + L"'");
|
||||
} else if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
if (findData.cFileName != wstring(L".") && findData.cFileName != wstring(L"..")) {
|
||||
dirs.push_back(dir + L"\\" + findData.cFileName);
|
||||
writeLog(L"Added dir '" + dir + L"\\" + findData.cFileName + L"' in update tree..");
|
||||
dirs.push_back(fname);
|
||||
writeLog(L"Added dir '" + fname + L"' in update tree..");
|
||||
}
|
||||
} else {
|
||||
wstring fname = dir + L"\\" + findData.cFileName;
|
||||
wstring tofname = updateTo + fname.substr(updDir.size() + 1);
|
||||
if (equal(tofname, exeName)) { // bad update - has Updater.exe - delete all dir
|
||||
writeLog(L"Error: bad update, has Updater.exe! '" + tofname + L"' equal '" + exeName + L"'");
|
||||
delFolder();
|
||||
return false;
|
||||
} else if (equal(fname, readyFilePath)) {
|
||||
writeLog(L"Skipped ready file '" + fname + L"'");
|
||||
} else {
|
||||
from.push_back(fname);
|
||||
to.push_back(tofname);
|
||||
writeLog(L"Added file '" + fname + L"' to be copied to '" + tofname + L"'");
|
||||
}
|
||||
from.push_back(fname);
|
||||
to.push_back(tofname);
|
||||
writeLog(L"Added file '" + fname + L"' to be copied to '" + tofname + L"'");
|
||||
}
|
||||
} while (FindNextFile(findHandle, &findData));
|
||||
DWORD errorCode = GetLastError();
|
||||
@@ -230,73 +267,57 @@ bool update() {
|
||||
}
|
||||
|
||||
void updateRegistry() {
|
||||
HANDLE versionFile = CreateFile((updateTo + L"tdata\\version").c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
|
||||
if (versionFile != INVALID_HANDLE_VALUE) {
|
||||
if (versionNum) {
|
||||
writeLog(L"Updating registry..");
|
||||
DWORD versionNum = 0, versionLen = 0, readLen = 0;
|
||||
WCHAR versionStr[32];
|
||||
if (ReadFile(versionFile, &versionNum, sizeof(DWORD), &readLen, NULL) != TRUE || readLen != sizeof(DWORD)) {
|
||||
versionNum = 0;
|
||||
} else if (ReadFile(versionFile, &versionLen, sizeof(DWORD), &readLen, NULL) != TRUE || readLen != sizeof(DWORD) || versionLen > 63) {
|
||||
versionNum = 0;
|
||||
} else if (ReadFile(versionFile, versionStr, versionLen, &readLen, NULL) != TRUE || readLen != versionLen) {
|
||||
versionNum = 0;
|
||||
}
|
||||
CloseHandle(versionFile);
|
||||
writeLog(L"Version file read.");
|
||||
if (versionNum) {
|
||||
versionStr[versionLen / 2] = 0;
|
||||
HKEY rkey;
|
||||
LSTATUS status = RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{53F49750-6209-4FBF-9CA8-7A333C87D1ED}_is1", 0, KEY_QUERY_VALUE | KEY_SET_VALUE, &rkey);
|
||||
if (status == ERROR_SUCCESS) {
|
||||
writeLog(L"Checking registry install location..");
|
||||
static const int bufSize = 4096;
|
||||
DWORD locationType, locationSize = bufSize * 2;
|
||||
WCHAR locationStr[bufSize], exp[bufSize];
|
||||
if (RegQueryValueEx(rkey, L"InstallLocation", 0, &locationType, (BYTE*)locationStr, &locationSize) == ERROR_SUCCESS) {
|
||||
locationSize /= 2;
|
||||
if (locationStr[locationSize - 1]) {
|
||||
locationStr[locationSize++] = 0;
|
||||
versionStr[versionLen / 2] = 0;
|
||||
HKEY rkey;
|
||||
LSTATUS status = RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{53F49750-6209-4FBF-9CA8-7A333C87D1ED}_is1", 0, KEY_QUERY_VALUE | KEY_SET_VALUE, &rkey);
|
||||
if (status == ERROR_SUCCESS) {
|
||||
writeLog(L"Checking registry install location..");
|
||||
static const int bufSize = 4096;
|
||||
DWORD locationType, locationSize = bufSize * 2;
|
||||
WCHAR locationStr[bufSize], exp[bufSize];
|
||||
if (RegQueryValueEx(rkey, L"InstallLocation", 0, &locationType, (BYTE*)locationStr, &locationSize) == ERROR_SUCCESS) {
|
||||
locationSize /= 2;
|
||||
if (locationStr[locationSize - 1]) {
|
||||
locationStr[locationSize++] = 0;
|
||||
}
|
||||
if (locationType == REG_EXPAND_SZ) {
|
||||
DWORD copy = ExpandEnvironmentStrings(locationStr, exp, bufSize);
|
||||
if (copy <= bufSize) {
|
||||
memcpy(locationStr, exp, copy * sizeof(WCHAR));
|
||||
}
|
||||
if (locationType == REG_EXPAND_SZ) {
|
||||
DWORD copy = ExpandEnvironmentStrings(locationStr, exp, bufSize);
|
||||
if (copy <= bufSize) {
|
||||
memcpy(locationStr, exp, copy * sizeof(WCHAR));
|
||||
}
|
||||
}
|
||||
if (locationType == REG_EXPAND_SZ || locationType == REG_SZ) {
|
||||
if (PathCanonicalize(exp, locationStr) == TRUE) {
|
||||
memcpy(locationStr, exp, bufSize * sizeof(WCHAR));
|
||||
if (GetFullPathName(L".", bufSize, exp, 0) < bufSize) {
|
||||
wstring installpath = locationStr, mypath = exp;
|
||||
if (installpath == mypath + L"\\" || true) { // always update reg info, if we found it
|
||||
WCHAR nameStr[bufSize], dateStr[bufSize], publisherStr[bufSize], icongroupStr[bufSize];
|
||||
SYSTEMTIME stLocalTime;
|
||||
GetLocalTime(&stLocalTime);
|
||||
RegSetValueEx(rkey, L"DisplayVersion", 0, REG_SZ, (BYTE*)versionStr, ((versionLen / 2) + 1) * sizeof(WCHAR));
|
||||
wsprintf(nameStr, L"Telegram Desktop version %s", versionStr);
|
||||
RegSetValueEx(rkey, L"DisplayName", 0, REG_SZ, (BYTE*)nameStr, (wcslen(nameStr) + 1) * sizeof(WCHAR));
|
||||
wsprintf(publisherStr, L"Telegram Messenger LLP");
|
||||
RegSetValueEx(rkey, L"Publisher", 0, REG_SZ, (BYTE*)publisherStr, (wcslen(publisherStr) + 1) * sizeof(WCHAR));
|
||||
wsprintf(icongroupStr, L"Telegram Desktop");
|
||||
RegSetValueEx(rkey, L"Inno Setup: Icon Group", 0, REG_SZ, (BYTE*)icongroupStr, (wcslen(icongroupStr) + 1) * sizeof(WCHAR));
|
||||
wsprintf(dateStr, L"%04d%02d%02d", stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay);
|
||||
RegSetValueEx(rkey, L"InstallDate", 0, REG_SZ, (BYTE*)dateStr, (wcslen(dateStr) + 1) * sizeof(WCHAR));
|
||||
}
|
||||
if (locationType == REG_EXPAND_SZ || locationType == REG_SZ) {
|
||||
if (PathCanonicalize(exp, locationStr) == TRUE) {
|
||||
memcpy(locationStr, exp, bufSize * sizeof(WCHAR));
|
||||
if (GetFullPathName(L".", bufSize, exp, 0) < bufSize) {
|
||||
wstring installpath = locationStr, mypath = exp;
|
||||
if (installpath == mypath + L"\\" || true) { // always update reg info, if we found it
|
||||
WCHAR nameStr[bufSize], dateStr[bufSize], publisherStr[bufSize], icongroupStr[bufSize];
|
||||
SYSTEMTIME stLocalTime;
|
||||
GetLocalTime(&stLocalTime);
|
||||
RegSetValueEx(rkey, L"DisplayVersion", 0, REG_SZ, (BYTE*)versionStr, ((versionLen / 2) + 1) * sizeof(WCHAR));
|
||||
wsprintf(nameStr, L"Telegram Desktop version %s", versionStr);
|
||||
RegSetValueEx(rkey, L"DisplayName", 0, REG_SZ, (BYTE*)nameStr, (wcslen(nameStr) + 1) * sizeof(WCHAR));
|
||||
wsprintf(publisherStr, L"Telegram Messenger LLP");
|
||||
RegSetValueEx(rkey, L"Publisher", 0, REG_SZ, (BYTE*)publisherStr, (wcslen(publisherStr) + 1) * sizeof(WCHAR));
|
||||
wsprintf(icongroupStr, L"Telegram Desktop");
|
||||
RegSetValueEx(rkey, L"Inno Setup: Icon Group", 0, REG_SZ, (BYTE*)icongroupStr, (wcslen(icongroupStr) + 1) * sizeof(WCHAR));
|
||||
wsprintf(dateStr, L"%04d%02d%02d", stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay);
|
||||
RegSetValueEx(rkey, L"InstallDate", 0, REG_SZ, (BYTE*)dateStr, (wcslen(dateStr) + 1) * sizeof(WCHAR));
|
||||
|
||||
WCHAR *appURL = L"https://desktop.telegram.org";
|
||||
RegSetValueEx(rkey, L"HelpLink", 0, REG_SZ, (BYTE*)appURL, (wcslen(appURL) + 1) * sizeof(WCHAR));
|
||||
RegSetValueEx(rkey, L"URLInfoAbout", 0, REG_SZ, (BYTE*)appURL, (wcslen(appURL) + 1) * sizeof(WCHAR));
|
||||
RegSetValueEx(rkey, L"URLUpdateInfo", 0, REG_SZ, (BYTE*)appURL, (wcslen(appURL) + 1) * sizeof(WCHAR));
|
||||
}
|
||||
WCHAR *appURL = L"https://desktop.telegram.org";
|
||||
RegSetValueEx(rkey, L"HelpLink", 0, REG_SZ, (BYTE*)appURL, (wcslen(appURL) + 1) * sizeof(WCHAR));
|
||||
RegSetValueEx(rkey, L"URLInfoAbout", 0, REG_SZ, (BYTE*)appURL, (wcslen(appURL) + 1) * sizeof(WCHAR));
|
||||
RegSetValueEx(rkey, L"URLUpdateInfo", 0, REG_SZ, (BYTE*)appURL, (wcslen(appURL) + 1) * sizeof(WCHAR));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RegCloseKey(rkey);
|
||||
}
|
||||
RegCloseKey(rkey);
|
||||
}
|
||||
} else {
|
||||
writeLog(L"Could not open version file to update registry :(");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,7 +378,7 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, LPSTR cmdParama
|
||||
updateRegistry();
|
||||
}
|
||||
if (writeprotected) { // if we can't clear all tupdates\ready (Updater.exe is there) - clear only version
|
||||
if (DeleteFile(L"tupdates\\ready\\tdata\\version")) {
|
||||
if (DeleteFile(L"tupdates\\temp\\tdata\\version") || DeleteFile(L"tupdates\\ready\\tdata\\version")) {
|
||||
writeLog(L"Version file deleted!");
|
||||
} else {
|
||||
writeLog(L"Error: could not delete version file");
|
||||
@@ -386,7 +407,6 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, LPSTR cmdParama
|
||||
|
||||
HRESULT hres = CoInitialize(0);
|
||||
if (SUCCEEDED(hres)) {
|
||||
wstring lnk = L"tupdates\\ready\\temp.lnk";
|
||||
IShellLink* psl;
|
||||
HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
|
||||
if (SUCCEEDED(hres)) {
|
||||
@@ -401,7 +421,12 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, LPSTR cmdParama
|
||||
hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);
|
||||
|
||||
if (SUCCEEDED(hres)) {
|
||||
wstring lnk = L"tupdates\\temp\\temp.lnk";
|
||||
hres = ppf->Save(lnk.c_str(), TRUE);
|
||||
if (!SUCCEEDED(hres)) {
|
||||
lnk = L"tupdates\\ready\\temp.lnk"; // old
|
||||
hres = ppf->Save(lnk.c_str(), TRUE);
|
||||
}
|
||||
ppf->Release();
|
||||
|
||||
if (SUCCEEDED(hres)) {
|
||||
|
||||
@@ -49,12 +49,13 @@ bool do_mkdir(const char *path) { // from http://stackoverflow.com/questions/675
|
||||
}
|
||||
|
||||
bool _debug = false;
|
||||
string exeName, exeDir, workDir;
|
||||
|
||||
FILE *_logFile = 0;
|
||||
void openLog() {
|
||||
if (!_debug || _logFile) return;
|
||||
|
||||
if (!do_mkdir("DebugLogs")) {
|
||||
if (!do_mkdir((workDir + "DebugLogs").c_str())) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -65,7 +66,7 @@ void openLog() {
|
||||
|
||||
static const int maxFileLen = 65536;
|
||||
char logName[maxFileLen];
|
||||
sprintf(logName, "DebugLogs/%04d%02d%02d_%02d%02d%02d_upd.txt",
|
||||
sprintf(logName, "%sDebugLogs/%04d%02d%02d_%02d%02d%02d_upd.txt", workDir.c_str(),
|
||||
t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
|
||||
_logFile = fopen(logName, "w");
|
||||
}
|
||||
@@ -136,7 +137,7 @@ bool remove_directory(const string &path) { // from http://stackoverflow.com/que
|
||||
|
||||
if (!d) {
|
||||
writeLog("Could not open dir '%s'", path.c_str());
|
||||
return false;
|
||||
return (errno == ENOENT);
|
||||
}
|
||||
|
||||
while (struct dirent *p = readdir(d)) {
|
||||
@@ -192,8 +193,6 @@ bool mkpath(const char *path) {
|
||||
return do_mkdir(path);
|
||||
}
|
||||
|
||||
string exeName, exeDir, workDir;
|
||||
|
||||
bool equal(string a, string b) {
|
||||
std::transform(a.begin(), a.end(), a.begin(), ::tolower);
|
||||
std::transform(b.begin(), b.end(), b.begin(), ::tolower);
|
||||
@@ -201,18 +200,33 @@ bool equal(string a, string b) {
|
||||
}
|
||||
|
||||
void delFolder() {
|
||||
string delPath = workDir + "tupdates/ready", delFolder = workDir + "tupdates";
|
||||
writeLog("Fully clearing path '%s'..", delPath.c_str());
|
||||
if (!remove_directory(delPath)) {
|
||||
writeLog("Error: failed to clear path! :(");
|
||||
string delPathOld = workDir + "tupdates/ready", delPath = workDir + "tupdates/temp", delFolder = workDir + "tupdates";
|
||||
writeLog("Fully clearing old path '%s'..", delPathOld.c_str());
|
||||
if (!remove_directory(delPathOld)) {
|
||||
writeLog("Failed to clear old path! :( New path was used?..");
|
||||
}
|
||||
rmdir(delFolder.c_str());
|
||||
writeLog("Fully clearing path '%s'..", delPath.c_str());
|
||||
if (!remove_directory(delPath)) {
|
||||
writeLog("Error: failed to clear path! :(");
|
||||
}
|
||||
rmdir(delFolder.c_str());
|
||||
}
|
||||
|
||||
bool update() {
|
||||
writeLog("Update started..");
|
||||
|
||||
string updDir = workDir + "tupdates/ready";
|
||||
string updDir = workDir + "tupdates/temp", readyFilePath = workDir + "tupdates/temp/ready", tdataDir = workDir + "tupdates/temp/tdata";
|
||||
{
|
||||
FILE *readyFile = fopen(readyFilePath.c_str(), "rb");
|
||||
if (readyFile) {
|
||||
fclose(readyFile);
|
||||
writeLog("Ready file found! Using new path '%s'..", updDir.c_str());
|
||||
} else {
|
||||
updDir = workDir + "tupdates/ready"; // old
|
||||
tdataDir = workDir + "tupdates/ready/tdata";
|
||||
writeLog("Ready file not found! Using old path '%s'..", updDir.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
deque<string> dirs;
|
||||
dirs.push_back(updDir);
|
||||
@@ -242,7 +256,9 @@ bool update() {
|
||||
|
||||
string fname = dir + '/' + p->d_name;
|
||||
struct stat statbuf;
|
||||
if (!stat(fname.c_str(), &statbuf)) {
|
||||
if (fname.substr(0, tdataDir.size()) == tdataDir && (fname.size() <= tdataDir.size() || fname.at(tdataDir.size()) == '/')) {
|
||||
writeLog("Skipping 'tdata' path '%s'", fname.c_str());
|
||||
} else if (!stat(fname.c_str(), &statbuf)) {
|
||||
if (S_ISDIR(statbuf.st_mode)) {
|
||||
dirs.push_back(fname);
|
||||
writeLog("Added dir '%s' in update tree..", fname.c_str());
|
||||
@@ -253,9 +269,13 @@ bool update() {
|
||||
delFolder();
|
||||
return false;
|
||||
}
|
||||
from.push_back(fname);
|
||||
to.push_back(tofname);
|
||||
writeLog("Added file '%s' to be copied to '%s'", fname.c_str(), tofname.c_str());
|
||||
if (fname == readyFilePath) {
|
||||
writeLog("Skipped ready file '%s'", fname.c_str());
|
||||
} else {
|
||||
from.push_back(fname);
|
||||
to.push_back(tofname);
|
||||
writeLog("Added file '%s' to be copied to '%s'", fname.c_str(), tofname.c_str());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
writeLog("Could not get stat() for file %s", fname.c_str());
|
||||
@@ -299,10 +319,6 @@ bool update() {
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
openLog();
|
||||
|
||||
writeLog("Updater started..");
|
||||
|
||||
bool needupdate = true, autostart = false, debug = false, tosettings = false, startintray = false, testmode = false;
|
||||
|
||||
char *key = 0;
|
||||
@@ -313,7 +329,6 @@ int main(int argc, char *argv[]) {
|
||||
autostart = true;
|
||||
} else if (equal(argv[i], "-debug")) {
|
||||
debug = _debug = true;
|
||||
openLog();
|
||||
} else if (equal(argv[i], "-startintray")) {
|
||||
startintray = true;
|
||||
} else if (equal(argv[i], "-testmode")) {
|
||||
@@ -326,6 +341,12 @@ int main(int argc, char *argv[]) {
|
||||
workDir = argv[i];
|
||||
}
|
||||
}
|
||||
openLog();
|
||||
|
||||
writeLog("Updater started..");
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
writeLog("Argument: '%s'", argv[i]);
|
||||
}
|
||||
if (needupdate) writeLog("Need to update!");
|
||||
if (autostart) writeLog("From autostart!");
|
||||
|
||||
@@ -336,7 +357,7 @@ int main(int argc, char *argv[]) {
|
||||
exeDir = exeName.substr(0, exeName.size() - 7);
|
||||
writeLog("Exe dir is: %s", exeDir.c_str());
|
||||
if (needupdate) {
|
||||
if (workDir.empty()) { // old app launched
|
||||
if (workDir.empty()) { // old app launched, update prepared in tupdates/ready (not in tupdates/temp)
|
||||
writeLog("No workdir, trying to figure it out");
|
||||
struct passwd *pw = getpwuid(getuid());
|
||||
if (pw && pw->pw_dir && strlen(pw->pw_dir)) {
|
||||
|
||||
@@ -35,7 +35,7 @@ void openLog() {
|
||||
return;
|
||||
}
|
||||
|
||||
NSDateFormatter *fmt = [[NSDateFormatter alloc] initWithDateFormat:@"DebugLogs/%Y%m%d %H%M%S_upd.txt" allowNaturalLanguage:NO];
|
||||
NSDateFormatter *fmt = [[NSDateFormatter alloc] initWithDateFormat:@"DebugLogs/%Y%m%d_%H%M%S_upd.txt" allowNaturalLanguage:NO];
|
||||
NSString *logPath = [workDir stringByAppendingString:[fmt stringFromDate:[NSDate date]]];
|
||||
[[NSFileManager defaultManager] createFileAtPath:logPath contents:nil attributes:nil];
|
||||
_logFile = [NSFileHandle fileHandleForWritingAtPath:logPath];
|
||||
@@ -55,7 +55,14 @@ void writeLog(NSString *msg) {
|
||||
}
|
||||
|
||||
void delFolder() {
|
||||
[[NSFileManager defaultManager] removeItemAtPath:[workDir stringByAppendingString:@"tupdates/ready"] error:nil];
|
||||
writeLog([@"Fully clearing old path: " stringByAppendingString:[workDir stringByAppendingString:@"tupdates/ready"]]);
|
||||
if (![[NSFileManager defaultManager] removeItemAtPath:[workDir stringByAppendingString:@"tupdates/ready"] error:nil]) {
|
||||
writeLog(@"Failed to clear old path! :( New path was used?..");
|
||||
}
|
||||
writeLog([@"Fully clearing new path: " stringByAppendingString:[workDir stringByAppendingString:@"tupdates/temp"]]);
|
||||
if (![[NSFileManager defaultManager] removeItemAtPath:[workDir stringByAppendingString:@"tupdates/temp"] error:nil]) {
|
||||
writeLog(@"Error: failed to clear new path! :(");
|
||||
}
|
||||
rmdir([[workDir stringByAppendingString:@"tupdates"] fileSystemRepresentation]);
|
||||
}
|
||||
|
||||
@@ -132,13 +139,22 @@ int main(int argc, const char * argv[]) {
|
||||
}
|
||||
|
||||
if (update) {
|
||||
writeLog([@"Starting update files iteration, path: " stringByAppendingString: [workDir stringByAppendingString:@"tupdates/ready"]]);
|
||||
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSString *srcDir = [workDir stringByAppendingString:@"tupdates/ready/"];
|
||||
NSString *readyFilePath = [workDir stringByAppendingString:@"tupdates/temp/ready"];
|
||||
NSString *srcDir = [workDir stringByAppendingString:@"tupdates/temp/"], *srcEnum = [workDir stringByAppendingString:@"tupdates/temp"];
|
||||
if ([fileManager fileExistsAtPath:readyFilePath]) {
|
||||
writeLog([@"Ready file found! Using new path: " stringByAppendingString: srcEnum]);
|
||||
} else {
|
||||
srcDir = [workDir stringByAppendingString:@"tupdates/ready/"]; // old
|
||||
srcEnum = [workDir stringByAppendingString:@"tupdates/ready"];
|
||||
writeLog([@"Ready file not found! Using old path: " stringByAppendingString: srcEnum]);
|
||||
}
|
||||
|
||||
writeLog([@"Starting update files iteration, path: " stringByAppendingString: srcEnum]);
|
||||
|
||||
NSArray *keys = [NSArray arrayWithObject:NSURLIsDirectoryKey];
|
||||
NSDirectoryEnumerator *enumerator = [fileManager
|
||||
enumeratorAtURL:[NSURL fileURLWithPath:[workDir stringByAppendingString:@"tupdates/ready"]]
|
||||
enumeratorAtURL:[NSURL fileURLWithPath:srcEnum]
|
||||
includingPropertiesForKeys:keys
|
||||
options:0
|
||||
errorHandler:^(NSURL *url, NSError *error) {
|
||||
@@ -163,18 +179,20 @@ int main(int argc, const char * argv[]) {
|
||||
NSString *dstPath = [appDirFull stringByAppendingString:[pathPart substringFromIndex:r.length]];
|
||||
NSError *error;
|
||||
NSNumber *isDirectory = nil;
|
||||
writeLog([[NSArray arrayWithObjects: @"Copying file ", srcPath, @" to ", dstPath, nil] componentsJoinedByString:@""]);
|
||||
if (![url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:&error]) {
|
||||
writeLog([@"Failed to get IsDirectory for file " stringByAppendingString:[url path]]);
|
||||
delFolder();
|
||||
break;
|
||||
}
|
||||
if ([isDirectory boolValue]) {
|
||||
writeLog([[NSArray arrayWithObjects: @"Copying dir ", srcPath, @" to ", dstPath, nil] componentsJoinedByString:@""]);
|
||||
if (![fileManager createDirectoryAtPath:dstPath withIntermediateDirectories:YES attributes:nil error:nil]) {
|
||||
writeLog([@"Failed to force path for directory " stringByAppendingString:dstPath]);
|
||||
delFolder();
|
||||
break;
|
||||
}
|
||||
} else if ([srcPath isEqualToString:readyFilePath]) {
|
||||
writeLog([[NSArray arrayWithObjects: @"Skipping ready file ", srcPath, nil] componentsJoinedByString:@""]);
|
||||
} else if ([fileManager fileExistsAtPath:dstPath]) {
|
||||
if (![[NSData dataWithContentsOfFile:srcPath] writeToFile:dstPath atomically:YES]) {
|
||||
writeLog([@"Failed to edit file " stringByAppendingString:dstPath]);
|
||||
|
||||
@@ -119,6 +119,19 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result) {
|
||||
App::feedUsers(d.vusers);
|
||||
App::feedChats(d.vchats);
|
||||
App::feedParticipants(f.vparticipants);
|
||||
const QVector<MTPBotInfo> &v(f.vbot_info.c_vector().v);
|
||||
for (QVector<MTPBotInfo>::const_iterator i = v.cbegin(), e = v.cend(); i < e; ++i) {
|
||||
switch (i->type()) {
|
||||
case mtpc_botInfo: {
|
||||
const MTPDbotInfo &b(i->c_botInfo());
|
||||
UserData *user = App::userLoaded(b.vuser_id.v);
|
||||
if (user) {
|
||||
user->setBotInfo(*i);
|
||||
emit fullPeerUpdated(user);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
PhotoData *photo = App::feedPhoto(f.vchat_photo);
|
||||
ChatData *chat = peer->asChat();
|
||||
if (chat) {
|
||||
@@ -132,7 +145,7 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result) {
|
||||
App::main()->gotNotifySetting(MTP_inputNotifyPeer(peer->input), f.vnotify_settings);
|
||||
|
||||
_fullRequests.remove(peer);
|
||||
emit fullPeerLoaded(peer);
|
||||
emit fullPeerUpdated(peer);
|
||||
}
|
||||
|
||||
void ApiWrap::gotUserFull(PeerData *peer, const MTPUserFull &result) {
|
||||
@@ -142,8 +155,10 @@ void ApiWrap::gotUserFull(PeerData *peer, const MTPUserFull &result) {
|
||||
App::feedUserLink(MTP_int(App::userFromPeer(peer->id)), d.vlink.c_contacts_link().vmy_link, d.vlink.c_contacts_link().vforeign_link);
|
||||
App::main()->gotNotifySetting(MTP_inputNotifyPeer(peer->input), d.vnotify_settings);
|
||||
|
||||
peer->asUser()->setBotInfo(d.vbot_info);
|
||||
|
||||
_fullRequests.remove(peer);
|
||||
emit fullPeerLoaded(peer);
|
||||
emit fullPeerUpdated(peer);
|
||||
}
|
||||
|
||||
bool ApiWrap::gotPeerFailed(PeerData *peer, const RPCError &error) {
|
||||
|
||||
@@ -40,7 +40,7 @@ public:
|
||||
|
||||
signals:
|
||||
|
||||
void fullPeerLoaded(PeerData *peer);
|
||||
void fullPeerUpdated(PeerData *peer);
|
||||
|
||||
public slots:
|
||||
|
||||
|
||||
@@ -55,6 +55,9 @@ namespace {
|
||||
typedef QHash<WebPageId, WebPageData*> WebPagesData;
|
||||
WebPagesData webPagesData;
|
||||
|
||||
typedef QMap<MsgId, ReplyMarkup> ReplyMarkups;
|
||||
ReplyMarkups replyMarkups;
|
||||
|
||||
VideoItems videoItems;
|
||||
AudioItems audioItems;
|
||||
DocumentItems documentItems;
|
||||
@@ -206,7 +209,11 @@ namespace App {
|
||||
return (peer_id & 0x100000000L) ? int32(peer_id & 0xFFFFFFFFL) : 0;
|
||||
}
|
||||
|
||||
int32 onlineForSort(int32 online, int32 now) {
|
||||
int32 onlineForSort(UserData *user, int32 now) {
|
||||
if (isServiceUser(user->id) || user->botInfo) {
|
||||
return -1;
|
||||
}
|
||||
int32 online = user->onlineTill;
|
||||
if (online <= 0) {
|
||||
switch (online) {
|
||||
case 0:
|
||||
@@ -232,8 +239,12 @@ namespace App {
|
||||
return online;
|
||||
}
|
||||
|
||||
int32 onlineWillChangeIn(int32 online, int32 now) {
|
||||
if (online <= 0) {
|
||||
int32 onlineWillChangeIn(UserData *user, int32 now) {
|
||||
if (isServiceUser(user->id) || user->botInfo) {
|
||||
return 86400;
|
||||
}
|
||||
int32 online = user->onlineTill;
|
||||
if (online <= 0) {
|
||||
if (-online > now) return -online - now;
|
||||
return 86400;
|
||||
}
|
||||
@@ -255,6 +266,8 @@ namespace App {
|
||||
QString onlineText(UserData *user, int32 now, bool precise) {
|
||||
if (isServiceUser(user->id)) {
|
||||
return lang(lng_status_service_notifications);
|
||||
} else if (user->botInfo) {
|
||||
return lang(lng_status_bot);
|
||||
}
|
||||
int32 online = user->onlineTill;
|
||||
if (online <= 0) {
|
||||
@@ -333,88 +346,63 @@ namespace App {
|
||||
data->setName(lang(lng_deleted), QString(), QString(), QString());
|
||||
data->setPhoto(MTP_userProfilePhotoEmpty());
|
||||
data->access = UserNoAccess;
|
||||
data->setBotInfoVersion(-1);
|
||||
wasContact = (data->contact > 0);
|
||||
status = &emptyStatus;
|
||||
data->contact = -1;
|
||||
} break;
|
||||
case mtpc_userDeleted: {
|
||||
const MTPDuserDeleted &d(user.c_userDeleted());
|
||||
case mtpc_user: {
|
||||
const MTPDuser &d(user.c_user());
|
||||
|
||||
PeerId peer(peerFromUser(d.vid.v));
|
||||
data = App::user(peer);
|
||||
data->input = MTP_inputPeerContact(d.vid);
|
||||
data->inputUser = MTP_inputUserContact(d.vid);
|
||||
data->setName(lang(lng_deleted), QString(), QString(), QString());
|
||||
// data->setName(textOneLine(qs(d.vfirst_name)), textOneLine(qs(d.vlast_name)), QString(), textOneLine(qs(d.vusername)));
|
||||
data->setPhoto(MTP_userProfilePhotoEmpty());
|
||||
data->access = UserNoAccess;
|
||||
int32 flags = d.vflags.v;
|
||||
if (flags & MTPDuser_flag_self) {
|
||||
data->input = MTP_inputPeerSelf();
|
||||
data->inputUser = MTP_inputUserSelf();
|
||||
} else if ((flags & (MTPDuser_flag_contact | MTPDuser_flag_mutual_contact)) || !d.has_access_hash()) {
|
||||
data->input = MTP_inputPeerContact(d.vid);
|
||||
data->inputUser = MTP_inputUserContact(d.vid);
|
||||
} else {
|
||||
data->input = MTP_inputPeerForeign(d.vid, d.vaccess_hash);
|
||||
data->inputUser = MTP_inputUserForeign(d.vid, d.vaccess_hash);
|
||||
}
|
||||
if (flags & MTPDuser_flag_deleted) {
|
||||
data->setPhone(QString());
|
||||
data->setName(lang(lng_deleted), QString(), QString(), QString());
|
||||
data->setPhoto(MTP_userProfilePhotoEmpty());
|
||||
data->access = UserNoAccess;
|
||||
status = &emptyStatus;
|
||||
} else {
|
||||
data->setPhone(d.has_phone() ? qs(d.vphone) : QString());
|
||||
QString fname = d.has_first_name() ? textOneLine(qs(d.vfirst_name)) : QString();
|
||||
QString lname = d.has_last_name() ? textOneLine(qs(d.vlast_name)) : QString();
|
||||
QString uname = d.has_username() ? textOneLine(qs(d.vusername)) : QString();
|
||||
bool showPhone = !isServiceUser(data->id) && !(flags & (MTPDuser_flag_self | MTPDuser_flag_contact | MTPDuser_flag_mutual_contact));
|
||||
QString pname = (showPhone && !data->phone.isEmpty()) ? formatPhone(data->phone) : QString();
|
||||
data->setName(fname, lname, QString(), uname);
|
||||
if (d.has_photo()) {
|
||||
data->setPhoto(d.vphoto);
|
||||
} else {
|
||||
data->setPhoto(MTP_userProfilePhotoEmpty());
|
||||
}
|
||||
if (d.has_access_hash()) data->access = d.vaccess_hash.v;
|
||||
status = d.has_status() ? &d.vstatus : &emptyStatus;
|
||||
}
|
||||
wasContact = (data->contact > 0);
|
||||
status = &emptyStatus;
|
||||
data->contact = -1;
|
||||
} break;
|
||||
case mtpc_userSelf: {
|
||||
const MTPDuserSelf &d(user.c_userSelf());
|
||||
|
||||
PeerId peer(peerFromUser(d.vid.v));
|
||||
data = App::user(peer);
|
||||
data->input = MTP_inputPeerSelf();
|
||||
data->inputUser = MTP_inputUserSelf();
|
||||
data->setName(textOneLine(qs(d.vfirst_name)), textOneLine(qs(d.vlast_name)), QString(), textOneLine(qs(d.vusername)));
|
||||
data->setPhoto(d.vphoto);
|
||||
data->setPhone(qs(d.vphone));
|
||||
data->access = 0;
|
||||
wasContact = (data->contact > 0);
|
||||
status = &d.vstatus;
|
||||
|
||||
if (::self != data) {
|
||||
if (d.has_bot_info_version()) {
|
||||
data->setBotInfoVersion(d.vbot_info_version.v);
|
||||
data->botInfo->readsAllHistory = (d.vflags.v & MTPDuser_flag_bot_reads_all);
|
||||
data->botInfo->cantJoinGroups = (d.vflags.v & MTPDuser_flag_bot_cant_join);
|
||||
} else {
|
||||
data->setBotInfoVersion(-1);
|
||||
}
|
||||
data->contact = (flags & (MTPDuser_flag_contact | MTPDuser_flag_mutual_contact)) ? 1 : (data->phone.isEmpty() ? -1 : 0);
|
||||
if ((flags & MTPDuser_flag_self) && ::self != data) {
|
||||
::self = data;
|
||||
if (App::wnd()) App::wnd()->updateGlobalMenu();
|
||||
}
|
||||
} break;
|
||||
case mtpc_userContact: {
|
||||
const MTPDuserContact &d(user.c_userContact());
|
||||
|
||||
PeerId peer(peerFromUser(d.vid.v));
|
||||
data = App::user(peer);
|
||||
data->input = MTP_inputPeerContact(d.vid);
|
||||
data->inputUser = MTP_inputUserContact(d.vid);
|
||||
data->setName(textOneLine(qs(d.vfirst_name)), textOneLine(qs(d.vlast_name)), QString(), textOneLine(qs(d.vusername)));
|
||||
data->setPhoto(d.vphoto);
|
||||
data->setPhone(qs(d.vphone));
|
||||
data->access = d.vaccess_hash.v;
|
||||
wasContact = (data->contact > 0);
|
||||
data->contact = 1;
|
||||
status = &d.vstatus;
|
||||
} break;
|
||||
case mtpc_userRequest: {
|
||||
const MTPDuserRequest &d(user.c_userRequest());
|
||||
|
||||
PeerId peer(peerFromUser(d.vid.v));
|
||||
data = App::user(peer);
|
||||
data->input = MTP_inputPeerForeign(d.vid, d.vaccess_hash);
|
||||
data->inputUser = MTP_inputUserForeign(d.vid, d.vaccess_hash);
|
||||
data->setPhone(qs(d.vphone));
|
||||
data->setName(textOneLine(qs(d.vfirst_name)), textOneLine(qs(d.vlast_name)), (!isServiceUser(data->id) && !data->phone.isEmpty()) ? formatPhone(data->phone) : QString(), textOneLine(qs(d.vusername)));
|
||||
data->setPhoto(d.vphoto);
|
||||
data->access = d.vaccess_hash.v;
|
||||
wasContact = (data->contact > 0);
|
||||
data->contact = 0;
|
||||
status = &d.vstatus;
|
||||
} break;
|
||||
case mtpc_userForeign: {
|
||||
const MTPDuserForeign &d(user.c_userForeign());
|
||||
|
||||
PeerId peer(peerFromUser(d.vid.v));
|
||||
data = App::user(peer);
|
||||
data->input = MTP_inputPeerForeign(d.vid, d.vaccess_hash);
|
||||
data->inputUser = MTP_inputUserForeign(d.vid, d.vaccess_hash);
|
||||
data->setName(textOneLine(qs(d.vfirst_name)), textOneLine(qs(d.vlast_name)), QString(), textOneLine(qs(d.vusername)));
|
||||
data->setPhoto(d.vphoto);
|
||||
data->access = d.vaccess_hash.v;
|
||||
wasContact = (data->contact > 0);
|
||||
data->contact = -1;
|
||||
status = &d.vstatus;
|
||||
} break;
|
||||
}
|
||||
|
||||
if (!data) continue;
|
||||
@@ -472,6 +460,7 @@ namespace App {
|
||||
if (data->version < d.vversion.v) {
|
||||
data->version = d.vversion.v;
|
||||
data->participants = ChatData::Participants();
|
||||
data->botStatus = 0;
|
||||
}
|
||||
} break;
|
||||
case mtpc_chatForbidden: {
|
||||
@@ -505,6 +494,7 @@ namespace App {
|
||||
if (data->version < d.vversion.v) {
|
||||
data->version = d.vversion.v;
|
||||
data->participants = ChatData::Participants();
|
||||
data->botStatus = 0;
|
||||
}/**/
|
||||
} break;
|
||||
}
|
||||
@@ -518,7 +508,7 @@ namespace App {
|
||||
return data;
|
||||
}
|
||||
|
||||
void feedParticipants(const MTPChatParticipants &p) {
|
||||
void feedParticipants(const MTPChatParticipants &p, bool requestBotInfos) {
|
||||
switch (p.type()) {
|
||||
case mtpc_chatParticipantsForbidden: {
|
||||
const MTPDchatParticipantsForbidden &d(p.c_chatParticipantsForbidden());
|
||||
@@ -545,17 +535,24 @@ namespace App {
|
||||
}
|
||||
} else {
|
||||
chat->participants = ChatData::Participants();
|
||||
chat->botStatus = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!chat->participants.isEmpty()) {
|
||||
int32 botStatus = -1;
|
||||
for (ChatData::Participants::iterator i = chat->participants.begin(), e = chat->participants.end(); i != e;) {
|
||||
if (i.value() < pversion) {
|
||||
i = chat->participants.erase(i);
|
||||
} else {
|
||||
if (i.key()->botInfo) {
|
||||
botStatus = (botStatus > 0/* || i.key()->botInfo->readsAllHistory*/) ? 2 : 1;
|
||||
if (requestBotInfos && !i.key()->botInfo->inited) App::api()->requestFullPeer(i.key());
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
chat->botStatus = botStatus;
|
||||
}
|
||||
if (App::main()) App::main()->peerUpdated(chat);
|
||||
}
|
||||
@@ -571,6 +568,7 @@ namespace App {
|
||||
if (user) {
|
||||
if (chat->participants.isEmpty() && chat->count) {
|
||||
chat->count++;
|
||||
chat->botStatus = 0;
|
||||
} else if (chat->participants.find(user) == chat->participants.end()) {
|
||||
chat->participants[user] = (chat->participants.isEmpty() ? 1 : chat->participants.begin().value());
|
||||
if (d.vinviter_id.v == MTP::authedId()) {
|
||||
@@ -579,9 +577,14 @@ namespace App {
|
||||
chat->cankick.remove(user);
|
||||
}
|
||||
chat->count++;
|
||||
if (user->botInfo) {
|
||||
chat->botStatus = (chat->botStatus > 0/* || !user->botInfo->readsAllHistory*/) ? 2 : 1;
|
||||
if (!user->botInfo->inited) App::api()->requestFullPeer(user);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
chat->participants = ChatData::Participants();
|
||||
chat->botStatus = 0;
|
||||
chat->count++;
|
||||
}
|
||||
if (App::main()) App::main()->peerUpdated(chat);
|
||||
@@ -602,9 +605,23 @@ namespace App {
|
||||
chat->participants.erase(i);
|
||||
chat->count--;
|
||||
}
|
||||
if (chat->botStatus > 0 && user->botInfo) {
|
||||
int32 botStatus = -1;
|
||||
for (ChatData::Participants::const_iterator j = chat->participants.cbegin(), e = chat->participants.cend(); j != e; ++j) {
|
||||
if (j.key()->botInfo) {
|
||||
if (botStatus > 0/* || !j.key()->botInfo->readsAllHistory*/) {
|
||||
botStatus = 2;
|
||||
break;
|
||||
}
|
||||
botStatus = 1;
|
||||
}
|
||||
}
|
||||
chat->botStatus = botStatus;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
chat->participants = ChatData::Participants();
|
||||
chat->botStatus = 0;
|
||||
chat->count--;
|
||||
}
|
||||
if (App::main()) App::main()->peerUpdated(chat);
|
||||
@@ -731,11 +748,7 @@ namespace App {
|
||||
MTPint userId(MTP_int(0));
|
||||
switch (dv.vuser.type()) {
|
||||
case mtpc_userEmpty: userId = dv.vuser.c_userEmpty().vid; break;
|
||||
case mtpc_userDeleted: userId = dv.vuser.c_userDeleted().vid; break;
|
||||
case mtpc_userContact: userId = dv.vuser.c_userContact().vid; break;
|
||||
case mtpc_userSelf: userId = dv.vuser.c_userSelf().vid; break;
|
||||
case mtpc_userRequest: userId = dv.vuser.c_userRequest().vid; break;
|
||||
case mtpc_userForeign: userId = dv.vuser.c_userForeign().vid; break;
|
||||
case mtpc_user: userId = dv.vuser.c_user().vid; break;
|
||||
}
|
||||
if (userId.v) {
|
||||
feedUserLink(userId, dv.vmy_link, dv.vforeign_link);
|
||||
@@ -896,6 +909,18 @@ namespace App {
|
||||
return App::video(video.vid.v, convert, video.vaccess_hash.v, video.vuser_id.v, video.vdate.v, video.vduration.v, video.vw.v, video.vh.v, App::image(video.vthumb), video.vdc_id.v, video.vsize.v);
|
||||
}
|
||||
|
||||
AudioData *feedAudio(const MTPaudio &audio, AudioData *convert) {
|
||||
switch (audio.type()) {
|
||||
case mtpc_audio: {
|
||||
return feedAudio(audio.c_audio(), convert);
|
||||
} break;
|
||||
case mtpc_audioEmpty: {
|
||||
return App::audio(audio.c_audioEmpty().vid.v, convert);
|
||||
} break;
|
||||
}
|
||||
return App::audio(0);
|
||||
}
|
||||
|
||||
AudioData *feedAudio(const MTPDaudio &audio, AudioData *convert) {
|
||||
return App::audio(audio.vid.v, convert, audio.vaccess_hash.v, audio.vuser_id.v, audio.vdate.v, qs(audio.vmime_type), audio.vduration.v, audio.vdc_id.v, audio.vsize.v);
|
||||
}
|
||||
@@ -1171,6 +1196,7 @@ namespace App {
|
||||
}
|
||||
|
||||
DocumentData *documentSet(const DocumentId &document, DocumentData *convert, const uint64 &access, int32 date, const QVector<MTPDocumentAttribute> &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size, const StorageImageLocation &thumbLocation) {
|
||||
bool sentSticker = false;
|
||||
if (convert) {
|
||||
if (convert->id != document) {
|
||||
DocumentsData::iterator i = documentsData.find(convert->id);
|
||||
@@ -1179,6 +1205,7 @@ namespace App {
|
||||
}
|
||||
convert->id = document;
|
||||
convert->status = FileReady;
|
||||
sentSticker = !!convert->sticker;
|
||||
}
|
||||
convert->access = access;
|
||||
if (!convert->date && date) {
|
||||
@@ -1254,6 +1281,7 @@ namespace App {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sentSticker && App::main()) App::main()->incrementSticker(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1494,6 +1522,7 @@ namespace App {
|
||||
}
|
||||
::maxMsgId = 0;
|
||||
::hoveredItem = ::pressedItem = ::hoveredLinkItem = ::pressedLinkItem = ::contextItem = 0;
|
||||
replyMarkups.clear();
|
||||
}
|
||||
|
||||
void historyClearItems() {
|
||||
@@ -1649,6 +1678,9 @@ namespace App {
|
||||
prepareCorners(MediaviewSaveCorners, st::msgRadius, st::emojiPanHover);
|
||||
prepareCorners(EmojiHoverCorners, st::msgRadius, st::emojiPanHover);
|
||||
prepareCorners(StickerHoverCorners, st::msgRadius, st::emojiPanHover);
|
||||
prepareCorners(BotKeyboardCorners, st::msgRadius, st::botKbBg);
|
||||
prepareCorners(BotKeyboardOverCorners, st::msgRadius, st::botKbOverBg);
|
||||
prepareCorners(BotKeyboardDownCorners, st::msgRadius, st::botKbDownBg);
|
||||
|
||||
prepareCorners(MessageInCorners, st::msgRadius, st::msgInBg, &st::msgInShadow);
|
||||
prepareCorners(MessageInSelectedCorners, st::msgRadius, st::msgInSelectBg, &st::msgInSelectShadow);
|
||||
@@ -1943,6 +1975,56 @@ namespace App {
|
||||
if (changeInMin) App::main()->updateMutedIn(changeInMin);
|
||||
}
|
||||
|
||||
void feedReplyMarkup(MsgId msgId, const MTPReplyMarkup &markup) {
|
||||
ReplyMarkup data;
|
||||
ReplyMarkup::Commands &commands(data.commands);
|
||||
switch (markup.type()) {
|
||||
case mtpc_replyKeyboardMarkup: {
|
||||
const MTPDreplyKeyboardMarkup &d(markup.c_replyKeyboardMarkup());
|
||||
data.flags = d.vflags.v;
|
||||
|
||||
const QVector<MTPKeyboardButtonRow> &v(d.vrows.c_vector().v);
|
||||
if (!v.isEmpty()) {
|
||||
commands.reserve(v.size());
|
||||
for (int32 i = 0, l = v.size(); i < l; ++i) {
|
||||
switch (v.at(i).type()) {
|
||||
case mtpc_keyboardButtonRow: {
|
||||
const MTPDkeyboardButtonRow &r(v.at(i).c_keyboardButtonRow());
|
||||
const QVector<MTPKeyboardButton> &b(r.vbuttons.c_vector().v);
|
||||
if (!b.isEmpty()) {
|
||||
QList<QString> btns;
|
||||
btns.reserve(b.size());
|
||||
for (int32 j = 0, s = b.size(); j < s; ++j) {
|
||||
switch (b.at(j).type()) {
|
||||
case mtpc_keyboardButton: {
|
||||
btns.push_back(qs(b.at(j).c_keyboardButton().vtext));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
if (!btns.isEmpty()) commands.push_back(btns);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
if (!commands.isEmpty()) {
|
||||
replyMarkups.insert(msgId, data);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void clearReplyMarkup(MsgId msgId) {
|
||||
replyMarkups.remove(msgId);
|
||||
}
|
||||
|
||||
const ReplyMarkup &replyMarkup(MsgId msgId) {
|
||||
static ReplyMarkup zeroMarkup(MTPDreplyKeyboardMarkup_flag_ZERO);
|
||||
ReplyMarkups::const_iterator i = replyMarkups.constFind(msgId);
|
||||
if (i == replyMarkups.cend()) return zeroMarkup;
|
||||
return i.value();
|
||||
}
|
||||
|
||||
void setProxySettings(QNetworkAccessManager &manager) {
|
||||
if (cConnectionType() == dbictHttpProxy) {
|
||||
const ConnectionProxy &p(cConnectionProxy());
|
||||
@@ -1961,15 +2043,27 @@ namespace App {
|
||||
}
|
||||
}
|
||||
|
||||
void sendBotCommand(const QString &cmd, MsgId replyTo) {
|
||||
if (App::main()) {
|
||||
App::main()->sendBotCommand(cmd, replyTo);
|
||||
}
|
||||
}
|
||||
|
||||
void insertBotCommand(const QString &cmd) {
|
||||
if (App::main()) {
|
||||
App::main()->insertBotCommand(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
void searchByHashtag(const QString &tag) {
|
||||
if (App::main()) {
|
||||
App::main()->searchMessages(tag + ' ');
|
||||
}
|
||||
}
|
||||
|
||||
void openUserByName(const QString &username, bool toProfile) {
|
||||
void openUserByName(const QString &username, bool toProfile, const QString &startToken) {
|
||||
if (App::main()) {
|
||||
App::main()->openUserByName(username, toProfile);
|
||||
App::main()->openUserByName(username, toProfile, startToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2016,6 +2110,16 @@ namespace App {
|
||||
p.drawPixmap(QPoint(x + w - cw, y + h - ch), *c[3]);
|
||||
}
|
||||
|
||||
void roundShadow(QPainter &p, int32 x, int32 y, int32 w, int32 h, const style::color &sh, RoundCorners index) {
|
||||
QPixmap **c = App::corners(index);
|
||||
int32 cw = c[0]->width() / cIntRetinaFactor(), ch = c[0]->height() / cIntRetinaFactor();
|
||||
p.fillRect(x + cw, y + h, w - 2 * cw, st::msgShadow, sh->b);
|
||||
p.fillRect(x, y + h - ch, cw, st::msgShadow, sh->b);
|
||||
p.fillRect(x + w - cw, y + h - ch, cw, st::msgShadow, sh->b);
|
||||
p.drawPixmap(x, y + h - ch + st::msgShadow, *c[2]);
|
||||
p.drawPixmap(x + w - cw, y + h - ch + st::msgShadow, *c[3]);
|
||||
}
|
||||
|
||||
void initBackground(int32 id, const QImage &p, bool nowrite) {
|
||||
if (Local::readBackground()) return;
|
||||
|
||||
|
||||
@@ -35,6 +35,13 @@ typedef QHash<VideoData*, HistoryItemsMap> VideoItems;
|
||||
typedef QHash<AudioData*, HistoryItemsMap> AudioItems;
|
||||
typedef QHash<DocumentData*, HistoryItemsMap> DocumentItems;
|
||||
typedef QHash<WebPageData*, HistoryItemsMap> WebPageItems;
|
||||
struct ReplyMarkup {
|
||||
ReplyMarkup(int32 flags = 0) : flags(flags) {
|
||||
}
|
||||
typedef QList<QList<QString> > Commands;
|
||||
Commands commands;
|
||||
int32 flags;
|
||||
};
|
||||
|
||||
enum RoundCorners {
|
||||
MaskCorners = 0x00, // for images
|
||||
@@ -48,6 +55,9 @@ enum RoundCorners {
|
||||
MediaviewSaveCorners,
|
||||
EmojiHoverCorners,
|
||||
StickerHoverCorners,
|
||||
BotKeyboardCorners,
|
||||
BotKeyboardOverCorners,
|
||||
BotKeyboardDownCorners,
|
||||
|
||||
InShadowCorners, // for photos without bg
|
||||
InSelectedShadowCorners,
|
||||
@@ -92,14 +102,14 @@ namespace App {
|
||||
int32 userFromPeer(const PeerId &peer_id);
|
||||
int32 chatFromPeer(const PeerId &peer_id);
|
||||
|
||||
int32 onlineForSort(int32 online, int32 now);
|
||||
int32 onlineWillChangeIn(int32 onlineOnServer, int32 nowOnServer);
|
||||
int32 onlineForSort(UserData *user, int32 now);
|
||||
int32 onlineWillChangeIn(UserData *user, int32 nowOnServer);
|
||||
QString onlineText(UserData *user, int32 nowOnServer, bool precise = false);
|
||||
bool onlineColorUse(int32 online, int32 now);
|
||||
|
||||
UserData *feedUsers(const MTPVector<MTPUser> &users); // returns last user
|
||||
ChatData *feedChats(const MTPVector<MTPChat> &chats); // returns last chat
|
||||
void feedParticipants(const MTPChatParticipants &p);
|
||||
void feedParticipants(const MTPChatParticipants &p, bool requestBotInfos = false);
|
||||
void feedParticipantAdd(const MTPDupdateChatParticipantAdd &d);
|
||||
void feedParticipantDelete(const MTPDupdateChatParticipantDelete &d);
|
||||
void feedMsgs(const MTPVector<MTPMessage> &msgs, int msgsState = 0); // 2 - new read message, 1 - new unread message, 0 - not new message, -1 - searched message
|
||||
@@ -118,6 +128,7 @@ namespace App {
|
||||
PhotoData *feedPhoto(const MTPPhoto &photo, PhotoData *convert = 0);
|
||||
PhotoData *feedPhoto(const MTPDphoto &photo, PhotoData *convert = 0);
|
||||
VideoData *feedVideo(const MTPDvideo &video, VideoData *convert = 0);
|
||||
AudioData *feedAudio(const MTPaudio &audio, AudioData *convert = 0);
|
||||
AudioData *feedAudio(const MTPDaudio &audio, AudioData *convert = 0);
|
||||
DocumentData *feedDocument(const MTPdocument &document, const QPixmap &thumb);
|
||||
DocumentData *feedDocument(const MTPdocument &document, DocumentData *convert = 0);
|
||||
@@ -221,11 +232,17 @@ namespace App {
|
||||
void unregMuted(PeerData *peer);
|
||||
void updateMuted();
|
||||
|
||||
void feedReplyMarkup(MsgId msgId, const MTPReplyMarkup &markup);
|
||||
void clearReplyMarkup(MsgId msgId);
|
||||
const ReplyMarkup &replyMarkup(MsgId msgId);
|
||||
|
||||
void setProxySettings(QNetworkAccessManager &manager);
|
||||
void setProxySettings(QTcpSocket &socket);
|
||||
|
||||
void sendBotCommand(const QString &cmd, MsgId replyTo = 0);
|
||||
void insertBotCommand(const QString &cmd);
|
||||
void searchByHashtag(const QString &tag);
|
||||
void openUserByName(const QString &username, bool toProfile = false);
|
||||
void openUserByName(const QString &username, bool toProfile = false, const QString &startToken = QString());
|
||||
void joinGroupByHash(const QString &hash);
|
||||
void stickersBox(const QString &name);
|
||||
void openLocalUrl(const QString &url);
|
||||
@@ -236,6 +253,10 @@ namespace App {
|
||||
inline void roundRect(QPainter &p, const QRect &rect, const style::color &bg, RoundCorners index, const style::color *sh = 0) {
|
||||
return roundRect(p, rect.x(), rect.y(), rect.width(), rect.height(), bg, index, sh);
|
||||
}
|
||||
void roundShadow(QPainter &p, int32 x, int32 y, int32 w, int32 h, const style::color &sh, RoundCorners index);
|
||||
inline void roundShadow(QPainter &p, const QRect &rect, const style::color &sh, RoundCorners index) {
|
||||
return roundShadow(p, rect.x(), rect.y(), rect.width(), rect.height(), sh, index);
|
||||
}
|
||||
|
||||
void initBackground(int32 id = DefaultChatBackground, const QImage &p = QImage(), bool nowrite = false);
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
|
||||
#include "localstorage.h"
|
||||
|
||||
#include "autoupdater.h"
|
||||
|
||||
namespace {
|
||||
Application *mainApp = 0;
|
||||
FileUploader *uploader = 0;
|
||||
@@ -180,29 +182,6 @@ Application::Application(int &argc, char **argv) : PsApplication(argc, argv),
|
||||
}
|
||||
}
|
||||
|
||||
void Application::onAppUpdate(const MTPhelp_AppUpdate &response) {
|
||||
updateRequestId = 0;
|
||||
|
||||
cSetLastUpdateCheck(unixtime());
|
||||
Local::writeSettings();
|
||||
if (response.type() == mtpc_help_noAppUpdate) {
|
||||
startUpdateCheck();
|
||||
} else {
|
||||
updateThread = new QThread();
|
||||
connect(updateThread, SIGNAL(finished()), updateThread, SLOT(deleteLater()));
|
||||
updateDownloader = new PsUpdateDownloader(updateThread, response.c_help_appUpdate());
|
||||
updateThread->start();
|
||||
}
|
||||
}
|
||||
|
||||
bool Application::onAppUpdateFail() {
|
||||
updateRequestId = 0;
|
||||
cSetLastUpdateCheck(unixtime());
|
||||
Local::writeSettings();
|
||||
startUpdateCheck();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Application::updateGotCurrent() {
|
||||
if (!updateReply || updateThread) return;
|
||||
|
||||
@@ -213,7 +192,7 @@ void Application::updateGotCurrent() {
|
||||
if (currentVersion > AppVersion) {
|
||||
updateThread = new QThread();
|
||||
connect(updateThread, SIGNAL(finished()), updateThread, SLOT(deleteLater()));
|
||||
updateDownloader = new PsUpdateDownloader(updateThread, m.captured(2));
|
||||
updateDownloader = new UpdateDownloader(updateThread, m.captured(2));
|
||||
updateThread->start();
|
||||
}
|
||||
}
|
||||
@@ -487,7 +466,7 @@ void Application::uploadProfilePhoto(const QImage &tosend, const PeerId &peerId)
|
||||
int32 filesize = 0;
|
||||
QByteArray data;
|
||||
|
||||
ReadyLocalMedia ready(ToPreparePhoto, file, filename, filesize, data, id, id, qsl("jpg"), peerId, photo, photoThumbs, MTP_documentEmpty(MTP_long(0)), jpeg, false, 0);
|
||||
ReadyLocalMedia ready(ToPreparePhoto, file, filename, filesize, data, id, id, qsl("jpg"), peerId, photo, MTP_audioEmpty(MTP_long(0)), photoThumbs, MTP_documentEmpty(MTP_long(0)), jpeg, false, 0);
|
||||
|
||||
connect(App::uploader(), SIGNAL(photoReady(MsgId, const MTPInputFile &)), App::app(), SLOT(photoUpdated(MsgId, const MTPInputFile &)), Qt::UniqueConnection);
|
||||
|
||||
@@ -540,7 +519,6 @@ void Application::startUpdateCheck(bool forceWait) {
|
||||
updateReply = updateManager.get(checkVersion);
|
||||
connect(updateReply, SIGNAL(finished()), this, SLOT(updateGotCurrent()));
|
||||
connect(updateReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(updateFailedCurrent(QNetworkReply::NetworkError)));
|
||||
// updateRequestId = MTP::send(MTPhelp_GetAppUpdate(MTP_string(cApiDeviceModel()), MTP_string(cApiSystemVersion()), MTP_string(cApiAppVersion()), MTP_string(cApiLang())), rpcDone(&Application::onAppUpdate), rpcFail(&Application::onAppUpdateFail);
|
||||
emit updateChecking();
|
||||
} else {
|
||||
updateCheckTimer.start((updateInSecs + 5) * 1000);
|
||||
@@ -648,7 +626,7 @@ void Application::socketError(QLocalSocket::LocalSocketError e) {
|
||||
return App::quit();
|
||||
}
|
||||
|
||||
if (!cNoStartUpdate() && psCheckReadyUpdate()) {
|
||||
if (!cNoStartUpdate() && checkReadyUpdate()) {
|
||||
cSetRestartingUpdate(true);
|
||||
DEBUG_LOG(("Application Info: installing update instead of starting app.."));
|
||||
return App::quit();
|
||||
@@ -658,16 +636,14 @@ void Application::socketError(QLocalSocket::LocalSocketError e) {
|
||||
}
|
||||
|
||||
void Application::checkMapVersion() {
|
||||
if (Local::oldMapVersion() < AppVersion) {
|
||||
if (Local::oldMapVersion() < AppVersion) {
|
||||
psRegisterCustomScheme();
|
||||
if (Local::oldMapVersion()) {
|
||||
QString versionFeatures;
|
||||
if (DevChannel && Local::oldMapVersion() < 8015) {
|
||||
versionFeatures = QString::fromUtf8("\xe2\x80\x94 Video captions are displayed\n\xe2\x80\x94 Photo captions are displayed in photo viewer\n\xe2\x80\x94 Round corners for messages").replace('@', qsl("@") + QChar(0x200D));
|
||||
} else if (!DevChannel && Local::oldMapVersion() < 8016) {
|
||||
versionFeatures = lang(lng_new_version_text).trimmed();
|
||||
} else if (!DevChannel && Local::oldMapVersion() < 8017) {
|
||||
versionFeatures = lang(lng_new_version_minor).trimmed();
|
||||
if (DevChannel && Local::oldMapVersion() < 8027) {
|
||||
versionFeatures = lang(lng_new_version_minor);// QString::fromUtf8("\xe2\x80\x94 IPv6 connections support\n\xe2\x80\x94 Bug fixes and minor stuff");// .replace('@', qsl("@") + QChar(0x200D));
|
||||
} else if (!DevChannel && Local::oldMapVersion() < 8024) {
|
||||
versionFeatures = lng_new_version_text(lt_blog_link, qsl("https://telegram.org/blog/bot-revolution"));// lang(lng_new_version_text).trimmed();
|
||||
}
|
||||
if (!versionFeatures.isEmpty()) {
|
||||
versionFeatures = lng_new_version_wrap(lt_version, QString::fromStdWString(AppVersionStr), lt_changes, versionFeatures, lt_link, qsl("https://desktop.telegram.org/#changelog"));
|
||||
|
||||
@@ -27,6 +27,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
class MainWidget;
|
||||
class FileUploader;
|
||||
class Translator;
|
||||
class UpdateDownloader;
|
||||
|
||||
class Application : public PsApplication, public RPCSender {
|
||||
Q_OBJECT
|
||||
@@ -42,9 +43,6 @@ public:
|
||||
static int32 languageId();
|
||||
static MainWidget *main();
|
||||
|
||||
void onAppUpdate(const MTPhelp_AppUpdate &response);
|
||||
bool onAppUpdateFail();
|
||||
|
||||
enum UpdatingState {
|
||||
UpdatingNone,
|
||||
UpdatingDownload,
|
||||
@@ -80,6 +78,12 @@ public:
|
||||
|
||||
signals:
|
||||
|
||||
void updateChecking();
|
||||
void updateLatest();
|
||||
void updateDownloading(qint64 ready, qint64 total);
|
||||
void updateReady();
|
||||
void updateFailed();
|
||||
|
||||
void peerPhotoDone(PeerId peer);
|
||||
void peerPhotoFail(PeerId peer);
|
||||
|
||||
@@ -143,7 +147,7 @@ private:
|
||||
QNetworkReply *updateReply;
|
||||
SingleTimer updateCheckTimer;
|
||||
QThread *updateThread;
|
||||
PsUpdateDownloader *updateDownloader;
|
||||
UpdateDownloader *updateDownloader;
|
||||
|
||||
QTimer writeUserConfigTimer;
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 167 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 215 KiB After Width: | Height: | Size: 219 KiB |
File diff suppressed because it is too large
Load Diff
@@ -24,33 +24,35 @@ bool audioWorks();
|
||||
void audioPlayNotify();
|
||||
void audioFinish();
|
||||
|
||||
enum VoiceMessageState {
|
||||
VoiceMessageStopped,
|
||||
VoiceMessageStarting,
|
||||
VoiceMessagePlaying,
|
||||
VoiceMessageFinishing,
|
||||
VoiceMessagePausing,
|
||||
VoiceMessagePaused,
|
||||
VoiceMessageResuming,
|
||||
enum AudioPlayerState {
|
||||
AudioPlayerStopped,
|
||||
AudioPlayerStoppedAtStart,
|
||||
AudioPlayerStarting,
|
||||
AudioPlayerPlaying,
|
||||
AudioPlayerFinishing,
|
||||
AudioPlayerPausing,
|
||||
AudioPlayerPaused,
|
||||
AudioPlayerResuming,
|
||||
};
|
||||
|
||||
class VoiceMessagesFader;
|
||||
class VoiceMessagesLoader;
|
||||
class AudioPlayerFader;
|
||||
class AudioPlayerLoaders;
|
||||
|
||||
class VoiceMessages : public QObject {
|
||||
class AudioPlayer : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
VoiceMessages();
|
||||
AudioPlayer();
|
||||
|
||||
void play(AudioData *audio);
|
||||
void pauseresume();
|
||||
|
||||
void currentState(AudioData **audio, VoiceMessageState *state = 0, int64 *position = 0, int64 *duration = 0);
|
||||
void processContext();
|
||||
void currentState(AudioData **audio, AudioPlayerState *state = 0, int64 *position = 0, int64 *duration = 0, int32 *frequency = 0);
|
||||
void clearStoppedAtStart(AudioData *audio);
|
||||
void resumeDevice();
|
||||
|
||||
~VoiceMessages();
|
||||
~AudioPlayer();
|
||||
|
||||
public slots:
|
||||
|
||||
@@ -70,8 +72,8 @@ private:
|
||||
bool updateCurrentStarted(int32 pos = -1);
|
||||
|
||||
struct Msg {
|
||||
Msg() : audio(0), position(0), duration(0), skipStart(0), skipEnd(0), loading(0), started(0),
|
||||
state(VoiceMessageStopped), source(0), nextBuffer(0) {
|
||||
Msg() : audio(0), position(0), duration(0), frequency(AudioVoiceMsgFrequency), skipStart(0), skipEnd(0), loading(0), started(0),
|
||||
state(AudioPlayerStopped), source(0), nextBuffer(0) {
|
||||
memset(buffers, 0, sizeof(buffers));
|
||||
memset(samplesCount, 0, sizeof(samplesCount));
|
||||
}
|
||||
@@ -79,10 +81,11 @@ private:
|
||||
QString fname;
|
||||
QByteArray data;
|
||||
int64 position, duration;
|
||||
int32 frequency;
|
||||
int64 skipStart, skipEnd;
|
||||
bool loading;
|
||||
int64 started;
|
||||
VoiceMessageState state;
|
||||
AudioPlayerState state;
|
||||
|
||||
uint32 source;
|
||||
int32 nextBuffer;
|
||||
@@ -95,25 +98,59 @@ private:
|
||||
|
||||
QMutex _mutex;
|
||||
|
||||
friend class VoiceMessagesFader;
|
||||
friend class VoiceMessagesLoader;
|
||||
friend class AudioPlayerFader;
|
||||
friend class AudioPlayerLoaders;
|
||||
|
||||
QThread _faderThread;
|
||||
QThread _loaderThread;
|
||||
VoiceMessagesFader *_fader;
|
||||
VoiceMessagesLoader *_loader;
|
||||
QThread _faderThread, _loaderThread;
|
||||
AudioPlayerFader *_fader;
|
||||
AudioPlayerLoaders *_loader;
|
||||
|
||||
};
|
||||
|
||||
VoiceMessages *audioVoice();
|
||||
class AudioCaptureInner;
|
||||
|
||||
class VoiceMessagesFader : public QObject {
|
||||
class AudioCapture : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
VoiceMessagesFader(QThread *thread);
|
||||
void processContext();
|
||||
AudioCapture();
|
||||
|
||||
void start();
|
||||
void stop(bool needResult);
|
||||
|
||||
bool check();
|
||||
|
||||
~AudioCapture();
|
||||
|
||||
signals:
|
||||
|
||||
void captureOnStart();
|
||||
void captureOnStop(bool needResult);
|
||||
|
||||
void onDone(QByteArray data, qint32 samples);
|
||||
void onUpdate(qint16 level, qint32 samples);
|
||||
void onError();
|
||||
|
||||
private:
|
||||
|
||||
friend class AudioCaptureInner;
|
||||
|
||||
QThread _captureThread;
|
||||
AudioCaptureInner *_capture;
|
||||
|
||||
};
|
||||
|
||||
AudioPlayer *audioPlayer();
|
||||
AudioCapture *audioCapture();
|
||||
|
||||
class AudioPlayerFader : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
AudioPlayerFader(QThread *thread);
|
||||
void resumeDevice();
|
||||
|
||||
signals:
|
||||
|
||||
@@ -122,30 +159,31 @@ signals:
|
||||
void audioStopped(AudioData *audio);
|
||||
void needToPreload(AudioData *audio);
|
||||
|
||||
void stopSuspend();
|
||||
void stopPauseDevice();
|
||||
|
||||
public slots:
|
||||
|
||||
void onInit();
|
||||
void onTimer();
|
||||
void onSuspendTimer();
|
||||
void onSuspendTimerStop();
|
||||
void onPauseTimer();
|
||||
void onPauseTimerStop();
|
||||
|
||||
private:
|
||||
|
||||
QTimer _timer, _suspendTimer;
|
||||
QMutex _suspendMutex;
|
||||
bool _suspendFlag;
|
||||
QTimer _timer, _pauseTimer;
|
||||
QMutex _pauseMutex;
|
||||
bool _pauseFlag, _paused;
|
||||
|
||||
};
|
||||
|
||||
class VoiceMessagesLoader : public QObject {
|
||||
class AudioPlayerLoader;
|
||||
class AudioPlayerLoaders : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
VoiceMessagesLoader(QThread *thread);
|
||||
~VoiceMessagesLoader();
|
||||
AudioPlayerLoaders(QThread *thread);
|
||||
~AudioPlayerLoaders();
|
||||
|
||||
signals:
|
||||
|
||||
@@ -161,10 +199,43 @@ public slots:
|
||||
|
||||
private:
|
||||
|
||||
struct Loader;
|
||||
typedef QMap<AudioData*, Loader*> Loaders;
|
||||
typedef QMap<AudioData*, AudioPlayerLoader*> Loaders;
|
||||
Loaders _loaders;
|
||||
|
||||
void loadError(Loaders::iterator i);
|
||||
|
||||
};
|
||||
|
||||
struct AudioCapturePrivate;
|
||||
|
||||
class AudioCaptureInner : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
AudioCaptureInner(QThread *thread);
|
||||
~AudioCaptureInner();
|
||||
|
||||
signals:
|
||||
|
||||
void error();
|
||||
void update(qint16 level, qint32 samples);
|
||||
void done(QByteArray data, qint32 samples);
|
||||
|
||||
public slots:
|
||||
|
||||
void onInit();
|
||||
void onStart();
|
||||
void onStop(bool needResult);
|
||||
|
||||
void onTimeout();
|
||||
|
||||
private:
|
||||
|
||||
void writeFrame(int32 offset, int32 framesize);
|
||||
|
||||
AudioCapturePrivate *d;
|
||||
QTimer _timer;
|
||||
QByteArray _captured;
|
||||
|
||||
};
|
||||
|
||||
523
Telegram/SourceFiles/autoupdater.cpp
Normal file
523
Telegram/SourceFiles/autoupdater.cpp
Normal file
@@ -0,0 +1,523 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "stdafx.h"
|
||||
#include "application.h"
|
||||
#include "pspecific.h"
|
||||
#include "autoupdater.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
typedef DWORD VerInt;
|
||||
typedef WCHAR VerChar;
|
||||
#else
|
||||
typedef int VerInt;
|
||||
typedef wchar_t VerChar;
|
||||
#endif
|
||||
|
||||
UpdateDownloader::UpdateDownloader(QThread *thread, const QString &url) : reply(0), already(0), full(0) {
|
||||
updateUrl = url;
|
||||
moveToThread(thread);
|
||||
manager.moveToThread(thread);
|
||||
App::setProxySettings(manager);
|
||||
|
||||
connect(thread, SIGNAL(started()), this, SLOT(start()));
|
||||
initOutput();
|
||||
}
|
||||
|
||||
void UpdateDownloader::initOutput() {
|
||||
QString fileName;
|
||||
QRegularExpressionMatch m = QRegularExpression(qsl("/([^/\\?]+)(\\?|$)")).match(updateUrl);
|
||||
if (m.hasMatch()) {
|
||||
fileName = m.captured(1).replace(QRegularExpression(qsl("[^a-zA-Z0-9_\\-]")), QString());
|
||||
}
|
||||
if (fileName.isEmpty()) {
|
||||
fileName = qsl("tupdate-%1").arg(rand());
|
||||
}
|
||||
QString dirStr = cWorkingDir() + qsl("tupdates/");
|
||||
fileName = dirStr + fileName;
|
||||
QFileInfo file(fileName);
|
||||
|
||||
QDir dir(dirStr);
|
||||
if (dir.exists()) {
|
||||
QFileInfoList all = dir.entryInfoList(QDir::Files);
|
||||
for (QFileInfoList::iterator i = all.begin(), e = all.end(); i != e; ++i) {
|
||||
if (i->absoluteFilePath() != file.absoluteFilePath()) {
|
||||
QFile::remove(i->absoluteFilePath());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dir.mkdir(dir.absolutePath());
|
||||
}
|
||||
outputFile.setFileName(fileName);
|
||||
if (file.exists()) {
|
||||
uint64 fullSize = file.size();
|
||||
if (fullSize < INT_MAX) {
|
||||
int32 goodSize = (int32)fullSize;
|
||||
if (goodSize % UpdateChunk) {
|
||||
goodSize = goodSize - (goodSize % UpdateChunk);
|
||||
if (goodSize) {
|
||||
if (outputFile.open(QIODevice::ReadOnly)) {
|
||||
QByteArray goodData = outputFile.readAll().mid(0, goodSize);
|
||||
outputFile.close();
|
||||
if (outputFile.open(QIODevice::WriteOnly)) {
|
||||
outputFile.write(goodData);
|
||||
outputFile.close();
|
||||
|
||||
QMutexLocker lock(&mutex);
|
||||
already = goodSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QMutexLocker lock(&mutex);
|
||||
already = goodSize;
|
||||
}
|
||||
}
|
||||
if (!already) {
|
||||
QFile::remove(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateDownloader::start() {
|
||||
sendRequest();
|
||||
}
|
||||
|
||||
void UpdateDownloader::sendRequest() {
|
||||
QNetworkRequest req(updateUrl);
|
||||
QByteArray rangeHeaderValue = "bytes=" + QByteArray::number(already) + "-";
|
||||
req.setRawHeader("Range", rangeHeaderValue);
|
||||
req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
|
||||
if (reply) reply->deleteLater();
|
||||
reply = manager.get(req);
|
||||
connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(partFinished(qint64,qint64)));
|
||||
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(partFailed(QNetworkReply::NetworkError)));
|
||||
connect(reply, SIGNAL(metaDataChanged()), this, SLOT(partMetaGot()));
|
||||
}
|
||||
|
||||
void UpdateDownloader::partMetaGot() {
|
||||
typedef QList<QNetworkReply::RawHeaderPair> Pairs;
|
||||
Pairs pairs = reply->rawHeaderPairs();
|
||||
for (Pairs::iterator i = pairs.begin(), e = pairs.end(); i != e; ++i) {
|
||||
if (QString::fromUtf8(i->first).toLower() == "content-range") {
|
||||
QRegularExpressionMatch m = QRegularExpression(qsl("/(\\d+)([^\\d]|$)")).match(QString::fromUtf8(i->second));
|
||||
if (m.hasMatch()) {
|
||||
{
|
||||
QMutexLocker lock(&mutex);
|
||||
full = m.captured(1).toInt();
|
||||
}
|
||||
emit App::app()->updateDownloading(already, full);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32 UpdateDownloader::ready() {
|
||||
QMutexLocker lock(&mutex);
|
||||
return already;
|
||||
}
|
||||
|
||||
int32 UpdateDownloader::size() {
|
||||
QMutexLocker lock(&mutex);
|
||||
return full;
|
||||
}
|
||||
|
||||
void UpdateDownloader::partFinished(qint64 got, qint64 total) {
|
||||
if (!reply) return;
|
||||
|
||||
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||
if (statusCode.isValid()) {
|
||||
int status = statusCode.toInt();
|
||||
if (status != 200 && status != 206 && status != 416) {
|
||||
LOG(("Update Error: Bad HTTP status received in partFinished(): %1").arg(status));
|
||||
return fatalFail();
|
||||
}
|
||||
}
|
||||
|
||||
if (!already && !full) {
|
||||
QMutexLocker lock(&mutex);
|
||||
full = total;
|
||||
}
|
||||
DEBUG_LOG(("Update Info: part %1 of %2").arg(got).arg(total));
|
||||
|
||||
if (!outputFile.isOpen()) {
|
||||
if (!outputFile.open(QIODevice::Append)) {
|
||||
LOG(("Update Error: Could not open output file '%1' for appending").arg(outputFile.fileName()));
|
||||
return fatalFail();
|
||||
}
|
||||
}
|
||||
QByteArray r = reply->readAll();
|
||||
if (!r.isEmpty()) {
|
||||
outputFile.write(r);
|
||||
|
||||
QMutexLocker lock(&mutex);
|
||||
already += r.size();
|
||||
}
|
||||
if (got >= total) {
|
||||
reply->deleteLater();
|
||||
reply = 0;
|
||||
outputFile.close();
|
||||
unpackUpdate();
|
||||
} else {
|
||||
emit App::app()->updateDownloading(already, full);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateDownloader::partFailed(QNetworkReply::NetworkError e) {
|
||||
if (!reply) return;
|
||||
|
||||
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||
reply->deleteLater();
|
||||
reply = 0;
|
||||
if (statusCode.isValid()) {
|
||||
int status = statusCode.toInt();
|
||||
if (status == 416) { // Requested range not satisfiable
|
||||
outputFile.close();
|
||||
unpackUpdate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
LOG(("Update Error: failed to download part starting from %1, error %2").arg(already).arg(e));
|
||||
emit App::app()->updateFailed();
|
||||
}
|
||||
|
||||
void UpdateDownloader::fatalFail() {
|
||||
clearAll();
|
||||
emit App::app()->updateFailed();
|
||||
}
|
||||
|
||||
void UpdateDownloader::clearAll() {
|
||||
psDeleteDir(cWorkingDir() + qsl("tupdates"));
|
||||
}
|
||||
|
||||
//QString winapiErrorWrap() {
|
||||
// WCHAR errMsg[2048];
|
||||
// DWORD errorCode = GetLastError();
|
||||
// LPTSTR errorText = NULL, errorTextDefault = L"(Unknown error)";
|
||||
// FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&errorText, 0, 0);
|
||||
// if (!errorText) {
|
||||
// errorText = errorTextDefault;
|
||||
// }
|
||||
// StringCbPrintf(errMsg, sizeof(errMsg), L"Error code: %d, error message: %s", errorCode, errorText);
|
||||
// if (errorText != errorTextDefault) {
|
||||
// LocalFree(errorText);
|
||||
// }
|
||||
// return QString::fromWCharArray(errMsg);
|
||||
//}
|
||||
|
||||
void UpdateDownloader::unpackUpdate() {
|
||||
QByteArray packed;
|
||||
if (!outputFile.open(QIODevice::ReadOnly)) {
|
||||
LOG(("Update Error: cant read updates file!"));
|
||||
return fatalFail();
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN // use Lzma SDK for win
|
||||
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header
|
||||
#else
|
||||
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = 0, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hOriginalSizeLen; // header
|
||||
#endif
|
||||
|
||||
QByteArray compressed = outputFile.readAll();
|
||||
int32 compressedLen = compressed.size() - hSize;
|
||||
if (compressedLen <= 0) {
|
||||
LOG(("Update Error: bad compressed size: %1").arg(compressed.size()));
|
||||
return fatalFail();
|
||||
}
|
||||
outputFile.close();
|
||||
|
||||
QString tempDirPath = cWorkingDir() + qsl("tupdates/temp"), readyFilePath = cWorkingDir() + qsl("tupdates/temp/ready");
|
||||
psDeleteDir(tempDirPath);
|
||||
|
||||
QDir tempDir(tempDirPath);
|
||||
if (tempDir.exists() || QFile(readyFilePath).exists()) {
|
||||
LOG(("Update Error: cant clear tupdates/temp dir!"));
|
||||
return fatalFail();
|
||||
}
|
||||
|
||||
uchar sha1Buffer[20];
|
||||
bool goodSha1 = !memcmp(compressed.constData() + hSigLen, hashSha1(compressed.constData() + hSigLen + hShaLen, compressedLen + hPropsLen + hOriginalSizeLen, sha1Buffer), hShaLen);
|
||||
if (!goodSha1) {
|
||||
LOG(("Update Error: bad SHA1 hash of update file!"));
|
||||
return fatalFail();
|
||||
}
|
||||
|
||||
RSA *pbKey = PEM_read_bio_RSAPublicKey(BIO_new_mem_buf(const_cast<char*>(DevChannel ? UpdatesPublicDevKey : UpdatesPublicKey), -1), 0, 0, 0);
|
||||
if (!pbKey) {
|
||||
LOG(("Update Error: cant read public rsa key!"));
|
||||
return fatalFail();
|
||||
}
|
||||
if (RSA_verify(NID_sha1, (const uchar*)(compressed.constData() + hSigLen), hShaLen, (const uchar*)(compressed.constData()), hSigLen, pbKey) != 1) { // verify signature
|
||||
RSA_free(pbKey);
|
||||
LOG(("Update Error: bad RSA signature of update file!"));
|
||||
return fatalFail();
|
||||
}
|
||||
RSA_free(pbKey);
|
||||
|
||||
QByteArray uncompressed;
|
||||
|
||||
int32 uncompressedLen;
|
||||
memcpy(&uncompressedLen, compressed.constData() + hSigLen + hShaLen + hPropsLen, hOriginalSizeLen);
|
||||
uncompressed.resize(uncompressedLen);
|
||||
|
||||
size_t resultLen = uncompressed.size();
|
||||
#ifdef Q_OS_WIN // use Lzma SDK for win
|
||||
SizeT srcLen = compressedLen;
|
||||
int uncompressRes = LzmaUncompress((uchar*)uncompressed.data(), &resultLen, (const uchar*)(compressed.constData() + hSize), &srcLen, (const uchar*)(compressed.constData() + hSigLen + hShaLen), LZMA_PROPS_SIZE);
|
||||
if (uncompressRes != SZ_OK) {
|
||||
LOG(("Update Error: could not uncompress lzma, code: %1").arg(uncompressRes));
|
||||
return fatalFail();
|
||||
}
|
||||
#else
|
||||
lzma_stream stream = LZMA_STREAM_INIT;
|
||||
|
||||
lzma_ret ret = lzma_stream_decoder(&stream, UINT64_MAX, LZMA_CONCATENATED);
|
||||
if (ret != LZMA_OK) {
|
||||
const char *msg;
|
||||
switch (ret) {
|
||||
case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break;
|
||||
case LZMA_OPTIONS_ERROR: msg = "Specified preset is not supported"; break;
|
||||
case LZMA_UNSUPPORTED_CHECK: msg = "Specified integrity check is not supported"; break;
|
||||
default: msg = "Unknown error, possibly a bug"; break;
|
||||
}
|
||||
LOG(("Error initializing the decoder: %1 (error code %2)").arg(msg).arg(ret));
|
||||
return fatalFail();
|
||||
}
|
||||
|
||||
stream.avail_in = compressedLen;
|
||||
stream.next_in = (uint8_t*)(compressed.constData() + hSize);
|
||||
stream.avail_out = resultLen;
|
||||
stream.next_out = (uint8_t*)uncompressed.data();
|
||||
|
||||
lzma_ret res = lzma_code(&stream, LZMA_FINISH);
|
||||
if (stream.avail_in) {
|
||||
LOG(("Error in decompression, %1 bytes left in _in of %2 whole.").arg(stream.avail_in).arg(compressedLen));
|
||||
return fatalFail();
|
||||
} else if (stream.avail_out) {
|
||||
LOG(("Error in decompression, %1 bytes free left in _out of %2 whole.").arg(stream.avail_out).arg(resultLen));
|
||||
return fatalFail();
|
||||
}
|
||||
lzma_end(&stream);
|
||||
if (res != LZMA_OK && res != LZMA_STREAM_END) {
|
||||
const char *msg;
|
||||
switch (res) {
|
||||
case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break;
|
||||
case LZMA_FORMAT_ERROR: msg = "The input data is not in the .xz format"; break;
|
||||
case LZMA_OPTIONS_ERROR: msg = "Unsupported compression options"; break;
|
||||
case LZMA_DATA_ERROR: msg = "Compressed file is corrupt"; break;
|
||||
case LZMA_BUF_ERROR: msg = "Compressed data is truncated or otherwise corrupt"; break;
|
||||
default: msg = "Unknown error, possibly a bug"; break;
|
||||
}
|
||||
LOG(("Error in decompression: %1 (error code %2)").arg(msg).arg(res));
|
||||
return fatalFail();
|
||||
}
|
||||
#endif
|
||||
|
||||
tempDir.mkdir(tempDir.absolutePath());
|
||||
|
||||
quint32 version;
|
||||
{
|
||||
QBuffer buffer(&uncompressed);
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
QDataStream stream(&buffer);
|
||||
stream.setVersion(QDataStream::Qt_5_1);
|
||||
|
||||
stream >> version;
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("Update Error: cant read version from downloaded stream, status: %1").arg(stream.status()));
|
||||
return fatalFail();
|
||||
}
|
||||
if (int32(version) <= AppVersion) {
|
||||
LOG(("Update Error: downloaded version %1 is not greater, than mine %2").arg(version).arg(AppVersion));
|
||||
return fatalFail();
|
||||
}
|
||||
|
||||
quint32 filesCount;
|
||||
stream >> filesCount;
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("Update Error: cant read files count from downloaded stream, status: %1").arg(stream.status()));
|
||||
return fatalFail();
|
||||
}
|
||||
if (!filesCount) {
|
||||
LOG(("Update Error: update is empty!"));
|
||||
return fatalFail();
|
||||
}
|
||||
for (uint32 i = 0; i < filesCount; ++i) {
|
||||
QString relativeName;
|
||||
quint32 fileSize;
|
||||
QByteArray fileInnerData;
|
||||
bool executable = false;
|
||||
|
||||
stream >> relativeName >> fileSize >> fileInnerData;
|
||||
#if defined Q_OS_MAC || defined Q_OS_LINUX
|
||||
stream >> executable;
|
||||
#endif
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("Update Error: cant read file from downloaded stream, status: %1").arg(stream.status()));
|
||||
return fatalFail();
|
||||
}
|
||||
if (fileSize != quint32(fileInnerData.size())) {
|
||||
LOG(("Update Error: bad file size %1 not matching data size %2").arg(fileSize).arg(fileInnerData.size()));
|
||||
return fatalFail();
|
||||
}
|
||||
|
||||
QFile f(tempDirPath + '/' + relativeName);
|
||||
if (!QDir().mkpath(QFileInfo(f).absolutePath())) {
|
||||
LOG(("Update Error: cant mkpath for file '%1'").arg(tempDirPath + '/' + relativeName));
|
||||
return fatalFail();
|
||||
}
|
||||
if (!f.open(QIODevice::WriteOnly)) {
|
||||
LOG(("Update Error: cant open file '%1' for writing").arg(tempDirPath + '/' + relativeName));
|
||||
return fatalFail();
|
||||
}
|
||||
if (f.write(fileInnerData) != fileSize) {
|
||||
f.close();
|
||||
LOG(("Update Error: cant write file '%1'").arg(tempDirPath + '/' + relativeName));
|
||||
return fatalFail();
|
||||
}
|
||||
f.close();
|
||||
if (executable) {
|
||||
QFileDevice::Permissions p = f.permissions();
|
||||
p |= QFileDevice::ExeOwner | QFileDevice::ExeUser | QFileDevice::ExeGroup | QFileDevice::ExeOther;
|
||||
f.setPermissions(p);
|
||||
}
|
||||
}
|
||||
|
||||
// create tdata/version file
|
||||
tempDir.mkdir(QDir(tempDirPath + qsl("/tdata")).absolutePath());
|
||||
std::wstring versionString = ((version % 1000) ? QString("%1.%2.%3").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000)).arg(int(version % 1000)) : QString("%1.%2").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000))).toStdWString();
|
||||
|
||||
VerInt versionNum = VerInt(version), versionLen = VerInt(versionString.size() * sizeof(VerChar));
|
||||
VerChar versionStr[32];
|
||||
memcpy(versionStr, versionString.c_str(), versionLen);
|
||||
|
||||
QFile fVersion(tempDirPath + qsl("/tdata/version"));
|
||||
if (!fVersion.open(QIODevice::WriteOnly)) {
|
||||
LOG(("Update Error: cant write version file '%1'").arg(tempDirPath + qsl("/version")));
|
||||
return fatalFail();
|
||||
}
|
||||
fVersion.write((const char*)&versionNum, sizeof(VerInt));
|
||||
fVersion.write((const char*)&versionLen, sizeof(VerInt));
|
||||
fVersion.write((const char*)&versionStr[0], versionLen);
|
||||
fVersion.close();
|
||||
}
|
||||
|
||||
QFile readyFile(readyFilePath);
|
||||
if (readyFile.open(QIODevice::WriteOnly)) {
|
||||
if (readyFile.write("1", 1)) {
|
||||
readyFile.close();
|
||||
} else {
|
||||
LOG(("Update Error: cant write ready file '%1'").arg(readyFilePath));
|
||||
return fatalFail();
|
||||
}
|
||||
} else {
|
||||
LOG(("Update Error: cant create ready file '%1'").arg(readyFilePath));
|
||||
return fatalFail();
|
||||
}
|
||||
outputFile.remove();
|
||||
|
||||
emit App::app()->updateReady();
|
||||
}
|
||||
|
||||
UpdateDownloader::~UpdateDownloader() {
|
||||
delete reply;
|
||||
reply = 0;
|
||||
}
|
||||
|
||||
bool checkReadyUpdate() {
|
||||
QString readyFilePath = cWorkingDir() + qsl("tupdates/temp/ready"), readyPath = cWorkingDir() + qsl("tupdates/temp");
|
||||
if (!QFile(readyFilePath).exists()) {
|
||||
if (QDir(cWorkingDir() + qsl("tupdates/ready")).exists() || QDir(cWorkingDir() + qsl("tupdates/temp")).exists()) {
|
||||
UpdateDownloader::clearAll();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// check ready version
|
||||
QString versionPath = readyPath + qsl("/tdata/version");
|
||||
{
|
||||
QFile fVersion(versionPath);
|
||||
if (!fVersion.open(QIODevice::ReadOnly)) {
|
||||
LOG(("Update Error: cant read version file '%1'").arg(versionPath));
|
||||
UpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
VerInt versionNum;
|
||||
if (fVersion.read((char*)&versionNum, sizeof(VerInt)) != sizeof(VerInt)) {
|
||||
LOG(("Update Error: cant read version from file '%1'").arg(versionPath));
|
||||
UpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
fVersion.close();
|
||||
if (versionNum <= AppVersion) {
|
||||
LOG(("Update Error: cant install version %1 having version %2").arg(versionNum).arg(AppVersion));
|
||||
UpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
QString curUpdater = (cExeDir() + qsl("Updater.exe"));
|
||||
QFileInfo updater(cWorkingDir() + qsl("tupdates/temp/Updater.exe"));
|
||||
#elif defined Q_OS_MAC
|
||||
QString curUpdater = (cExeDir() + cExeName() + qsl("/Contents/Frameworks/Updater"));
|
||||
QFileInfo updater(cWorkingDir() + qsl("tupdates/temp/Telegram.app/Contents/Frameworks/Updater"));
|
||||
#elif defined Q_OS_LINUX
|
||||
QString curUpdater = (cExeDir() + qsl("Updater"));
|
||||
QFileInfo updater(cWorkingDir() + qsl("tupdates/temp/Updater"));
|
||||
#endif
|
||||
if (!updater.exists()) {
|
||||
QFileInfo current(curUpdater);
|
||||
if (!current.exists()) {
|
||||
UpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
if (!QFile(current.absoluteFilePath()).copy(updater.absoluteFilePath())) {
|
||||
UpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#ifdef Q_OS_WIN
|
||||
if (CopyFile(updater.absoluteFilePath().toStdWString().c_str(), curUpdater.toStdWString().c_str(), FALSE) == FALSE) {
|
||||
DWORD errorCode = GetLastError();
|
||||
if (errorCode == ERROR_ACCESS_DENIED) { // we are in write-protected dir, like Program Files
|
||||
cSetWriteProtected(true);
|
||||
return true;
|
||||
} else {
|
||||
UpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (DeleteFile(updater.absoluteFilePath().toStdWString().c_str()) == FALSE) {
|
||||
UpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
#elif defined Q_OS_MAC
|
||||
QDir().mkpath(QFileInfo(curUpdater).absolutePath());
|
||||
DEBUG_LOG(("Update Info: moving %1 to %2..").arg(updater.absoluteFilePath()).arg(curUpdater));
|
||||
if (!objc_moveFile(updater.absoluteFilePath(), curUpdater)) {
|
||||
UpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
#elif defined Q_OS_LINUX
|
||||
if (!linuxMoveFile(QFile::encodeName(updater.absoluteFilePath()).constData(), QFile::encodeName(curUpdater).constData())) {
|
||||
UpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
62
Telegram/SourceFiles/autoupdater.h
Normal file
62
Telegram/SourceFiles/autoupdater.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <QtNetwork/QLocalSocket>
|
||||
#include <QtNetwork/QLocalServer>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
|
||||
class UpdateDownloader : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
UpdateDownloader(QThread *thread, const QString &url);
|
||||
|
||||
void unpackUpdate();
|
||||
|
||||
int32 ready();
|
||||
int32 size();
|
||||
|
||||
static void clearAll();
|
||||
|
||||
~UpdateDownloader();
|
||||
|
||||
public slots:
|
||||
|
||||
void start();
|
||||
void partMetaGot();
|
||||
void partFinished(qint64 got, qint64 total);
|
||||
void partFailed(QNetworkReply::NetworkError e);
|
||||
void sendRequest();
|
||||
|
||||
private:
|
||||
void initOutput();
|
||||
|
||||
void fatalFail();
|
||||
|
||||
QString updateUrl;
|
||||
QNetworkAccessManager manager;
|
||||
QNetworkReply *reply;
|
||||
int32 already, full;
|
||||
QFile outputFile;
|
||||
|
||||
QMutex mutex;
|
||||
|
||||
};
|
||||
|
||||
bool checkReadyUpdate();
|
||||
@@ -23,7 +23,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
#include "mainwidget.h"
|
||||
#include "window.h"
|
||||
|
||||
ContactsInner::ContactsInner(bool creatingChat) : _chat(0), _creatingChat(creatingChat),
|
||||
#include "confirmbox.h"
|
||||
|
||||
ContactsInner::ContactsInner(bool creatingChat) : _chat(0), _bot(0), _creatingChat(creatingChat), _addToChat(0),
|
||||
_contacts(&App::main()->contactsList()),
|
||||
_sel(0),
|
||||
_filteredSel(-1),
|
||||
@@ -35,7 +37,7 @@ _addContactLnk(this, lang(lng_add_contact_button)) {
|
||||
init();
|
||||
}
|
||||
|
||||
ContactsInner::ContactsInner(ChatData *chat) : _chat(chat), _creatingChat(false),
|
||||
ContactsInner::ContactsInner(ChatData *chat) : _chat(chat), _bot(0), _creatingChat(false), _addToChat(0),
|
||||
_contacts(&App::main()->contactsList()),
|
||||
_sel(0),
|
||||
_filteredSel(-1),
|
||||
@@ -47,6 +49,24 @@ _addContactLnk(this, lang(lng_add_contact_button)) {
|
||||
init();
|
||||
}
|
||||
|
||||
ContactsInner::ContactsInner(UserData *bot) : _chat(0), _bot(bot), _creatingChat(false), _addToChat(0),
|
||||
_contacts(new DialogsIndexed(DialogsSortByAdd)),
|
||||
_sel(0),
|
||||
_filteredSel(-1),
|
||||
_mouseSel(false),
|
||||
_selCount(0),
|
||||
_searching(false),
|
||||
_byUsernameSel(-1),
|
||||
_addContactLnk(this, lang(lng_add_contact_button)) {
|
||||
DialogsIndexed &v(App::main()->dialogsList());
|
||||
for (DialogRow *r = v.list.begin; r != v.list.end; r = r->next) {
|
||||
if (r->history->peer->chat && !r->history->peer->asChat()->forbidden) {
|
||||
_contacts->addToEnd(r->history);
|
||||
}
|
||||
}
|
||||
init();
|
||||
}
|
||||
|
||||
void ContactsInner::init() {
|
||||
connect(&_addContactLnk, SIGNAL(clicked()), App::wnd(), SLOT(onShowAddContact()));
|
||||
|
||||
@@ -59,10 +79,29 @@ void ContactsInner::init() {
|
||||
|
||||
connect(App::main(), SIGNAL(dialogRowReplaced(DialogRow *, DialogRow *)), this, SLOT(onDialogRowReplaced(DialogRow *, DialogRow *)));
|
||||
connect(App::main(), SIGNAL(peerUpdated(PeerData*)), this, SLOT(peerUpdated(PeerData *)));
|
||||
connect(App::main(), SIGNAL(peerNameChanged(PeerData *, const PeerData::Names &, const PeerData::NameFirstChars &)), this, SLOT(peerUpdated(PeerData *)));
|
||||
connect(App::main(), SIGNAL(peerNameChanged(PeerData *, const PeerData::Names &, const PeerData::NameFirstChars &)), this, SLOT(onPeerNameChanged(PeerData *, const PeerData::Names &, const PeerData::NameFirstChars &)));
|
||||
connect(App::main(), SIGNAL(peerPhotoChanged(PeerData *)), this, SLOT(peerUpdated(PeerData *)));
|
||||
}
|
||||
|
||||
void ContactsInner::onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars) {
|
||||
if (bot()) {
|
||||
_contacts->peerNameChanged(peer, oldNames, oldChars);
|
||||
}
|
||||
peerUpdated(peer);
|
||||
}
|
||||
|
||||
void ContactsInner::onAddBot() {
|
||||
if (_bot->botInfo && !_bot->botInfo->startGroupToken.isEmpty()) {
|
||||
uint64 randomId = MTP::nonce<uint64>();
|
||||
MTP::send(MTPmessages_StartBot(_bot->inputUser, MTP_int(App::chatFromPeer(_addToChat->id)), MTP_long(randomId), MTP_string(_bot->botInfo->startGroupToken)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::addParticipantFail, _bot));
|
||||
|
||||
App::wnd()->hideLayer();
|
||||
App::main()->showPeer(_addToChat->id, 0, false);
|
||||
} else {
|
||||
App::main()->addParticipants(_addToChat, QVector<UserData*>(1, _bot));
|
||||
}
|
||||
}
|
||||
|
||||
void ContactsInner::peerUpdated(PeerData *peer) {
|
||||
if (_chat && (!peer || peer == _chat)) {
|
||||
if (_chat->forbidden) {
|
||||
@@ -81,8 +120,8 @@ void ContactsInner::peerUpdated(PeerData *peer) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!peer->chat) {
|
||||
ContactsData::iterator i = _contactsData.find(peer->asUser());
|
||||
} else {
|
||||
ContactsData::iterator i = _contactsData.find(peer);
|
||||
if (i != _contactsData.cend()) {
|
||||
for (DialogRow *row = _contacts->list.begin; row->next; row = row->next) {
|
||||
if (row->attached == i.value()) row->attached = 0;
|
||||
@@ -136,14 +175,23 @@ void ContactsInner::loadProfilePhotos(int32 yFrom) {
|
||||
ContactsInner::ContactData *ContactsInner::contactData(DialogRow *row) {
|
||||
ContactData *data = (ContactData*)row->attached;
|
||||
if (!data) {
|
||||
UserData *user = row->history->peer->asUser();
|
||||
ContactsData::const_iterator i = _contactsData.constFind(user);
|
||||
PeerData *peer = row->history->peer;
|
||||
ContactsData::const_iterator i = _contactsData.constFind(peer);
|
||||
if (i == _contactsData.cend()) {
|
||||
_contactsData.insert(user, data = new ContactData());
|
||||
data->inchat = _chat ? _chat->participants.contains(user) : false;
|
||||
_contactsData.insert(peer, data = new ContactData());
|
||||
data->inchat = (_chat && !peer->chat) ? _chat->participants.contains(peer->asUser()) : false;
|
||||
data->check = false;
|
||||
data->name.setText(st::profileListNameFont, user->name, _textNameOptions);
|
||||
data->online = App::onlineText(user, _time);
|
||||
data->name.setText(st::profileListNameFont, peer->name, _textNameOptions);
|
||||
if (peer->chat) {
|
||||
ChatData *chat = peer->asChat();
|
||||
if (chat->forbidden) {
|
||||
data->online = lang(lng_chat_status_unaccessible);
|
||||
} else {
|
||||
data->online = lng_chat_status_members(lt_count, chat->count);
|
||||
}
|
||||
} else {
|
||||
data->online = App::onlineText(peer->asUser(), _time);
|
||||
}
|
||||
} else {
|
||||
data = i.value();
|
||||
}
|
||||
@@ -152,9 +200,11 @@ ContactsInner::ContactData *ContactsInner::contactData(DialogRow *row) {
|
||||
return data;
|
||||
}
|
||||
|
||||
void ContactsInner::paintDialog(QPainter &p, UserData *user, ContactData *data, bool sel) {
|
||||
void ContactsInner::paintDialog(QPainter &p, PeerData *peer, ContactData *data, bool sel) {
|
||||
int32 left = st::profileListPadding.width();
|
||||
|
||||
UserData *user = peer->chat ? 0 : peer->asUser();
|
||||
|
||||
if (data->inchat || data->check || _selCount + (_chat ? _chat->count : 0) >= cMaxGroupCount()) {
|
||||
sel = false;
|
||||
}
|
||||
@@ -163,7 +213,7 @@ void ContactsInner::paintDialog(QPainter &p, UserData *user, ContactData *data,
|
||||
p.fillRect(0, 0, width(), 2 * st::profileListPadding.height() + st::profileListPhotoSize, ((data->inchat || data->check) ? st::profileActiveBG : st::profileHoverBG)->b);
|
||||
}
|
||||
|
||||
p.drawPixmap(left, st::profileListPadding.height(), user->photo->pix(st::profileListPhotoSize));
|
||||
p.drawPixmap(left, st::profileListPadding.height(), peer->photo->pix(st::profileListPhotoSize));
|
||||
|
||||
if (data->inchat || data->check) {
|
||||
p.setPen(st::white->p);
|
||||
@@ -181,8 +231,7 @@ void ContactsInner::paintDialog(QPainter &p, UserData *user, ContactData *data,
|
||||
p.drawPixmap(QPoint(width() - st::contactsImg.pxWidth() - st::profileCheckDeltaX, st::profileListPadding.height() + (st::profileListPhotoSize - st::contactsImg.pxHeight()) / 2 - st::profileCheckDeltaY), App::sprite(), st::contactsImg);
|
||||
}
|
||||
|
||||
|
||||
bool uname = (data->online.at(0) == '@');
|
||||
bool uname = user && (data->online.at(0) == '@');
|
||||
p.setFont(st::profileSubFont->f);
|
||||
if (uname && !data->inchat && !data->check && !_lastQuery.isEmpty() && user->username.startsWith(_lastQuery, Qt::CaseInsensitive)) {
|
||||
int32 availw = width() - (left + st::profileListPhotoSize + st::profileListPadding.width() * 2);
|
||||
@@ -200,8 +249,10 @@ void ContactsInner::paintDialog(QPainter &p, UserData *user, ContactData *data,
|
||||
} else {
|
||||
if (data->inchat || data->check) {
|
||||
p.setPen(st::white->p);
|
||||
} else if (user && (uname || App::onlineColorUse(user->onlineTill, _time))) {
|
||||
p.setPen(st::profileOnlineColor->p);
|
||||
} else {
|
||||
p.setPen(((uname || App::onlineColorUse(user->onlineTill, _time)) ? st::profileOnlineColor : st::profileOfflineColor)->p);
|
||||
p.setPen(st::profileOfflineColor->p);
|
||||
}
|
||||
p.drawText(left + st::profileListPhotoSize + st::profileListPadding.width(), st::profileListPadding.height() + st::profileListPhotoSize - st::profileListStatusBottom, data->online);
|
||||
}
|
||||
@@ -224,7 +275,7 @@ void ContactsInner::paintEvent(QPaintEvent *e) {
|
||||
DialogRow *drawFrom = _contacts->list.current;
|
||||
p.translate(0, drawFrom->pos * rh);
|
||||
while (drawFrom != _contacts->list.end && drawFrom->pos * rh < yTo) {
|
||||
paintDialog(p, drawFrom->history->peer->asUser(), contactData(drawFrom), (drawFrom == _sel));
|
||||
paintDialog(p, drawFrom->history->peer, contactData(drawFrom), (drawFrom == _sel));
|
||||
p.translate(0, rh);
|
||||
drawFrom = drawFrom->next;
|
||||
}
|
||||
@@ -253,13 +304,29 @@ void ContactsInner::paintEvent(QPaintEvent *e) {
|
||||
} else {
|
||||
p.setFont(st::noContactsFont->f);
|
||||
p.setPen(st::noContactsColor->p);
|
||||
p.drawText(QRect(0, 0, width(), st::noContactsHeight - ((cContactsReceived() && !_searching) ? st::noContactsFont->height : 0)), lang((cContactsReceived() && !_searching) ? lng_no_contacts : lng_contacts_loading), style::al_center);
|
||||
QString text;
|
||||
int32 skip = 0;
|
||||
if (bot()) {
|
||||
text = lang(cDialogsReceived() ? lng_bot_no_groups : lng_contacts_loading);
|
||||
} else if (cContactsReceived() && !_searching) {
|
||||
text = lang(lng_no_contacts);
|
||||
skip = st::noContactsFont->height;
|
||||
} else {
|
||||
text = lang(lng_contacts_loading);
|
||||
}
|
||||
p.drawText(QRect(0, 0, width(), st::noContactsHeight - skip), text, style::al_center);
|
||||
}
|
||||
} else {
|
||||
if (_filtered.isEmpty() && _byUsernameFiltered.isEmpty()) {
|
||||
p.setFont(st::noContactsFont->f);
|
||||
p.setPen(st::noContactsColor->p);
|
||||
p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang((cContactsReceived() && !_searching) ? lng_contacts_not_found : lng_contacts_loading), style::al_center);
|
||||
QString text;
|
||||
if (bot()) {
|
||||
text = lang(cDialogsReceived() ? lng_bot_groups_not_found : lng_contacts_loading);
|
||||
} else {
|
||||
text = lang((cContactsReceived() && !_searching) ? lng_contacts_not_found : lng_contacts_loading);
|
||||
}
|
||||
p.drawText(QRect(0, 0, width(), st::noContactsHeight), text, style::al_center);
|
||||
} else {
|
||||
if (!_filtered.isEmpty()) {
|
||||
int32 from = (yFrom >= 0) ? (yFrom / rh) : 0;
|
||||
@@ -269,7 +336,7 @@ void ContactsInner::paintEvent(QPaintEvent *e) {
|
||||
|
||||
p.translate(0, from * rh);
|
||||
for (; from < to; ++from) {
|
||||
paintDialog(p, _filtered[from]->history->peer->asUser(), contactData(_filtered[from]), (_filteredSel == from));
|
||||
paintDialog(p, _filtered[from]->history->peer, contactData(_filtered[from]), (_filteredSel == from));
|
||||
p.translate(0, rh);
|
||||
}
|
||||
}
|
||||
@@ -371,25 +438,32 @@ void ContactsInner::chooseParticipant() {
|
||||
}
|
||||
} else {
|
||||
int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2, from;
|
||||
PeerId peer = 0;
|
||||
PeerData *peer = 0;
|
||||
if (_filter.isEmpty()) {
|
||||
if (_byUsernameSel >= 0 && _byUsernameSel < _byUsername.size()) {
|
||||
peer = _byUsername[_byUsernameSel]->id;
|
||||
} else {
|
||||
peer = _sel ? _sel->history->peer->id : 0;
|
||||
peer = _byUsername[_byUsernameSel];
|
||||
} else if (_sel) {
|
||||
peer = _sel->history->peer;
|
||||
}
|
||||
} else {
|
||||
if (_byUsernameSel >= 0 && _byUsernameSel < _byUsernameFiltered.size()) {
|
||||
peer = _byUsernameFiltered[_byUsernameSel]->id;
|
||||
peer = _byUsernameFiltered[_byUsernameSel];
|
||||
} else {
|
||||
if (_filteredSel < 0 || _filteredSel >= _filtered.size()) return;
|
||||
peer = _filtered[_filteredSel]->history->peer->id;
|
||||
peer = _filtered[_filteredSel]->history->peer;
|
||||
}
|
||||
}
|
||||
if (peer) {
|
||||
App::wnd()->hideSettings(true);
|
||||
App::main()->showPeer(peer, 0, false, true);
|
||||
App::wnd()->hideLayer();
|
||||
if (bot() && peer->chat) {
|
||||
_addToChat = peer->asChat();
|
||||
ConfirmBox *box = new ConfirmBox(lng_bot_sure_invite(lt_group, peer->name));
|
||||
connect(box, SIGNAL(confirmed()), this, SLOT(onAddBot()));
|
||||
App::wnd()->replaceLayer(box);
|
||||
} else {
|
||||
App::wnd()->hideSettings(true);
|
||||
App::main()->showPeer(peer->id, 0, false, true);
|
||||
App::wnd()->hideLayer();
|
||||
}
|
||||
}
|
||||
}
|
||||
parentWidget()->update();
|
||||
@@ -562,8 +636,10 @@ void ContactsInner::updateFilter(QString filter) {
|
||||
|
||||
refresh();
|
||||
|
||||
_searching = true;
|
||||
emit searchByUsername();
|
||||
if (!bot()) {
|
||||
_searching = true;
|
||||
emit searchByUsername();
|
||||
}
|
||||
}
|
||||
if (parentWidget()) parentWidget()->update();
|
||||
loadProfilePhotos(0);
|
||||
@@ -612,6 +688,8 @@ void ContactsInner::peopleReceived(const QString &query, const QVector<MTPContac
|
||||
}
|
||||
if (j == already) {
|
||||
UserData *u = App::user(uid);
|
||||
if (u->botInfo && u->botInfo->cantJoinGroups && (_chat || _creatingChat)) continue; // skip bot's that can't be invited to groups
|
||||
|
||||
ContactData *d = new ContactData();
|
||||
_byUsernameDatas.push_back(d);
|
||||
d->inchat = _chat ? _chat->participants.contains(u) : false;
|
||||
@@ -634,7 +712,7 @@ void ContactsInner::refresh() {
|
||||
if (!_addContactLnk.isHidden()) _addContactLnk.hide();
|
||||
resize(width(), (_contacts->list.count * rh) + (_byUsername.isEmpty() ? 0 : (st::searchedBarHeight + _byUsername.size() * rh)));
|
||||
} else {
|
||||
if (cContactsReceived()) {
|
||||
if (cContactsReceived() && !bot()) {
|
||||
if (_addContactLnk.isHidden()) _addContactLnk.show();
|
||||
} else {
|
||||
if (!_addContactLnk.isHidden()) _addContactLnk.hide();
|
||||
@@ -656,6 +734,10 @@ ChatData *ContactsInner::chat() const {
|
||||
return _chat;
|
||||
}
|
||||
|
||||
UserData *ContactsInner::bot() const {
|
||||
return _bot;
|
||||
}
|
||||
|
||||
bool ContactsInner::creatingChat() const {
|
||||
return _creatingChat;
|
||||
}
|
||||
@@ -664,6 +746,10 @@ ContactsInner::~ContactsInner() {
|
||||
for (ContactsData::iterator i = _contactsData.begin(), e = _contactsData.end(); i != e; ++i) {
|
||||
delete *i;
|
||||
}
|
||||
if (_bot) {
|
||||
delete _contacts;
|
||||
if (_bot->botInfo) _bot->botInfo->startGroupToken = QString();
|
||||
}
|
||||
}
|
||||
|
||||
void ContactsInner::resizeEvent(QResizeEvent *e) {
|
||||
@@ -796,8 +882,8 @@ QVector<UserData*> ContactsInner::selected() {
|
||||
QVector<UserData*> result;
|
||||
result.reserve(_contactsData.size());
|
||||
for (ContactsData::const_iterator i = _contactsData.cbegin(), e = _contactsData.cend(); i != e; ++i) {
|
||||
if (i.value()->check) {
|
||||
result.push_back(i.key());
|
||||
if (i.value()->check && !i.key()->chat) {
|
||||
result.push_back(i.key()->asUser());
|
||||
}
|
||||
}
|
||||
for (int32 i = 0, l = _byUsername.size(); i < l; ++i) {
|
||||
@@ -812,7 +898,7 @@ QVector<MTPInputUser> ContactsInner::selectedInputs() {
|
||||
QVector<MTPInputUser> result;
|
||||
result.reserve(_contactsData.size());
|
||||
for (ContactsData::const_iterator i = _contactsData.cbegin(), e = _contactsData.cend(); i != e; ++i) {
|
||||
if (i.value()->check) {
|
||||
if (i.value()->check && !i.key()->chat) {
|
||||
result.push_back(i.key()->inputUser);
|
||||
}
|
||||
}
|
||||
@@ -854,6 +940,14 @@ _cancel(this, lang(lng_cancel), st::btnSelectCancel) {
|
||||
init();
|
||||
}
|
||||
|
||||
ContactsBox::ContactsBox(UserData *bot) : ItemListBox(st::boxNoTopScroll), _inner(bot),
|
||||
_addContact(this, lang(lng_add_contact_button), st::contactsAdd),
|
||||
_filter(this, st::contactsFilter, lang(lng_participant_filter)),
|
||||
_next(this, lang(lng_create_group_next), st::btnSelectDone),
|
||||
_cancel(this, lang(lng_cancel), st::contactsClose) {
|
||||
init();
|
||||
}
|
||||
|
||||
void ContactsBox::init() {
|
||||
ItemListBox::init(&_inner, _cancel.height(), st::contactsAdd.height + st::newGroupNamePadding.top() + _filter.height() + st::newGroupNamePadding.bottom());
|
||||
|
||||
@@ -961,14 +1055,17 @@ void ContactsBox::hideAll() {
|
||||
|
||||
void ContactsBox::showAll() {
|
||||
ItemListBox::showAll();
|
||||
_addContact.show();
|
||||
_filter.show();
|
||||
if (_inner.chat() || _inner.creatingChat()) {
|
||||
_next.show();
|
||||
_addContact.hide();
|
||||
} else {
|
||||
_next.hide();
|
||||
_addContact.show();
|
||||
if (_inner.bot()) {
|
||||
_addContact.hide();
|
||||
} else {
|
||||
_addContact.show();
|
||||
}
|
||||
}
|
||||
_cancel.show();
|
||||
}
|
||||
@@ -1010,6 +1107,8 @@ void ContactsBox::paintEvent(QPaintEvent *e) {
|
||||
|
||||
// paint button sep
|
||||
p.fillRect(st::btnSelectCancel.width, size().height() - st::btnSelectCancel.height, st::lineWidth, st::btnSelectCancel.height, st::btnSelectSep->b);
|
||||
} else if (_inner.bot()) {
|
||||
paintTitle(p, lang(lng_bot_choose_group), true);
|
||||
} else {
|
||||
paintTitle(p, lang(lng_contacts_header), true);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ public:
|
||||
|
||||
ContactsInner(bool creatingChat);
|
||||
ContactsInner(ChatData *chat);
|
||||
ContactsInner(UserData *bot);
|
||||
void init();
|
||||
|
||||
void paintEvent(QPaintEvent *e);
|
||||
@@ -39,7 +40,7 @@ public:
|
||||
void mousePressEvent(QMouseEvent *e);
|
||||
void resizeEvent(QResizeEvent *e);
|
||||
|
||||
void paintDialog(QPainter &p, UserData *user, ContactData *data, bool sel);
|
||||
void paintDialog(QPainter &p, PeerData *peer, ContactData *data, bool sel);
|
||||
void updateFilter(QString filter = QString());
|
||||
|
||||
void selectSkip(int32 dir);
|
||||
@@ -59,6 +60,7 @@ public:
|
||||
void refresh();
|
||||
|
||||
ChatData *chat() const;
|
||||
UserData *bot() const;
|
||||
bool creatingChat() const;
|
||||
|
||||
~ContactsInner();
|
||||
@@ -75,11 +77,17 @@ public slots:
|
||||
|
||||
void updateSel();
|
||||
void peerUpdated(PeerData *peer);
|
||||
void onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars);
|
||||
|
||||
void onAddBot();
|
||||
|
||||
private:
|
||||
|
||||
ChatData *_chat;
|
||||
UserData *_bot;
|
||||
bool _creatingChat;
|
||||
|
||||
ChatData *_addToChat;
|
||||
|
||||
int32 _time;
|
||||
|
||||
@@ -99,7 +107,7 @@ private:
|
||||
bool inchat;
|
||||
bool check;
|
||||
};
|
||||
typedef QMap<UserData*, ContactData*> ContactsData;
|
||||
typedef QMap<PeerData*, ContactData*> ContactsData;
|
||||
ContactsData _contactsData;
|
||||
|
||||
ContactData *contactData(DialogRow *row);
|
||||
@@ -125,6 +133,7 @@ public:
|
||||
|
||||
ContactsBox(bool creatingChat = false);
|
||||
ContactsBox(ChatData *chat);
|
||||
ContactsBox(UserData *bot);
|
||||
void keyPressEvent(QKeyEvent *e);
|
||||
void paintEvent(QPaintEvent *e);
|
||||
void resizeEvent(QResizeEvent *e);
|
||||
|
||||
@@ -17,9 +17,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
static const int32 AppVersion = 8017;
|
||||
static const wchar_t *AppVersionStr = L"0.8.17";
|
||||
static const bool DevChannel = false;
|
||||
static const int32 AppVersion = 8027;
|
||||
static const wchar_t *AppVersionStr = L"0.8.27";
|
||||
static const bool DevChannel = true;
|
||||
|
||||
static const wchar_t *AppNameOld = L"Telegram Win (Unofficial)";
|
||||
static const wchar_t *AppName = L"Telegram Desktop";
|
||||
@@ -40,8 +40,11 @@ enum {
|
||||
MTPContainerLives = 600, // container lives 10 minutes in haveSent map
|
||||
MTPMinReceiveDelay = 4000, // 4 seconds
|
||||
MTPMaxReceiveDelay = 64000, // 64 seconds
|
||||
MTPMinConnectDelay = 1000, // tcp connect should take less then 1 second
|
||||
MTPMaxConnectDelay = 8000, // tcp connect should take 8 seconds max
|
||||
MTPConnectionOldTimeout = 192000, // 192 seconds
|
||||
MTPTcpConnectionWaitTimeout = 3000, // 3 seconds waiting for tcp, until we accept http
|
||||
MTPTcpConnectionWaitTimeout = 2000, // 2 seconds waiting for tcp, until we accept http
|
||||
MTPIPv4ConnectionWaitTimeout = 1000, // 1 seconds waiting for ipv4, until we accept ipv6
|
||||
MTPMillerRabinIterCount = 30, // 30 Miller-Rabin iterations for dh_prime primality check
|
||||
|
||||
MTPUploadSessionsCount = 4, // max 4 upload sessions is created
|
||||
@@ -86,12 +89,16 @@ enum {
|
||||
AudioCheckPositionDelta = 4800, // update position called each 4800 samples
|
||||
AudioFadeTimeout = 10, // 10ms
|
||||
AudioFadeDuration = 500,
|
||||
AudioVoiceMsgSkip = 400, // 200ms
|
||||
AudioVoiceMsgFade = 300, // 300ms
|
||||
AudioPreloadSamples = 5 * 48000, // preload next part if less than 5 seconds remains
|
||||
AudioVoiceMsgFrequency = 48000, // 48 kHz
|
||||
AudioVoiceMsgMaxLength = 100 * 60, // 100 minutes
|
||||
AudioVoiceMsgUpdateView = 100, // 100ms
|
||||
AudioVoiceMsgChannels = 2, // stereo
|
||||
AudioVoiceMsgBufferSize = 1024 * 1024, // 1 Mb buffers
|
||||
AudioVoiceMsgInMemory = 1024 * 1024, // 1 Mb audio is hold in memory and auto loaded
|
||||
AudioSuspendTimeout = 3000, // suspend in 3 secs after playing is over
|
||||
AudioPauseDeviceTimeout = 3000, // pause in 3 secs after playing is over
|
||||
|
||||
StickerInMemory = 256 * 1024, // 128 Kb stickers hold in memory, auto loaded and displayed inline
|
||||
StickerMaxSize = 2048, // 2048x2048 is a max image size for sticker
|
||||
@@ -119,7 +126,7 @@ enum {
|
||||
SaveDraftTimeout = 1000, // save draft after 1 secs of not changing text
|
||||
SaveDraftAnywayTimeout = 5000, // or save anyway each 5 secs
|
||||
|
||||
HiddenIsOnlineAfterMessage = 30, // user with hidden last seen stays online for such amount of seconds in the interface
|
||||
SetOnlineAfterActivity = 30, // user with hidden last seen stays online for such amount of seconds in the interface
|
||||
|
||||
ServiceUserId = 777000,
|
||||
WebPageUserId = 701000,
|
||||
@@ -180,10 +187,24 @@ static const BuiltInDc _builtInDcs[] = {
|
||||
{ 5, "149.154.171.5", 443 }
|
||||
};
|
||||
|
||||
static const BuiltInDc _builtInDcsIPv6[] = {
|
||||
{ 1, "2001:b28:f23d:f001::a", 443 },
|
||||
{ 2, "2001:67c:4e8:f002::a", 443 },
|
||||
{ 3, "2001:b28:f23d:f003::a", 443 },
|
||||
{ 4, "2001:67c:4e8:f004::a", 443 },
|
||||
{ 5, "2001:b28:f23f:f005::a", 443 }
|
||||
};
|
||||
|
||||
static const BuiltInDc _builtInTestDcs[] = {
|
||||
{ 1, "149.154.175.10", 443 },
|
||||
{ 2, "149.154.167.40", 443 },
|
||||
{ 3, "174.140.142.5", 443 }
|
||||
{ 3, "149.154.175.117", 443 }
|
||||
};
|
||||
|
||||
static const BuiltInDc _builtInTestDcsIPv6[] = {
|
||||
{ 1, "2001:b28:f23d:f001::e", 443 },
|
||||
{ 2, "2001:67c:4e8:f002::e", 443 },
|
||||
{ 3, "2001:b28:f23d:f003::e", 443 }
|
||||
};
|
||||
|
||||
inline const BuiltInDc *builtInDcs() {
|
||||
@@ -194,6 +215,14 @@ inline int builtInDcsCount() {
|
||||
return (cTestMode() ? sizeof(_builtInTestDcs) : sizeof(_builtInDcs)) / sizeof(BuiltInDc);
|
||||
}
|
||||
|
||||
inline const BuiltInDc *builtInDcsIPv6() {
|
||||
return cTestMode() ? _builtInTestDcsIPv6 : _builtInDcsIPv6;
|
||||
}
|
||||
|
||||
inline int builtInDcsCountIPv6() {
|
||||
return (cTestMode() ? sizeof(_builtInTestDcsIPv6) : sizeof(_builtInDcsIPv6)) / sizeof(BuiltInDc);
|
||||
}
|
||||
|
||||
static const char *UpdatesPublicKey = "\
|
||||
-----BEGIN RSA PUBLIC KEY-----\n\
|
||||
MIGJAoGBAMA4ViQrjkPZ9xj0lrer3r23JvxOnrtE8nI69XLGSr+sRERz9YnUptnU\n\
|
||||
|
||||
@@ -28,9 +28,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
#include "localstorage.h"
|
||||
|
||||
DialogsListWidget::DialogsListWidget(QWidget *parent, MainWidget *main) : QWidget(parent),
|
||||
dialogs(false),
|
||||
contactsNoDialogs(true),
|
||||
contacts(true),
|
||||
dialogs(DialogsSortByDate),
|
||||
contactsNoDialogs(DialogsSortByName),
|
||||
contacts(DialogsSortByName),
|
||||
sel(0),
|
||||
contactSel(false),
|
||||
selByMouse(false),
|
||||
@@ -64,6 +64,8 @@ int32 DialogsListWidget::searchedOffset() const {
|
||||
}
|
||||
|
||||
void DialogsListWidget::paintEvent(QPaintEvent *e) {
|
||||
if (!App::main()) return;
|
||||
|
||||
QRect r(e->rect());
|
||||
bool trivial = (rect() == r);
|
||||
|
||||
@@ -95,7 +97,7 @@ void DialogsListWidget::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
p.translate(0, from * st::mentionHeight);
|
||||
if (from < hashtagResults.size()) {
|
||||
int32 to = (r.bottom() / int32(st::mentionHeight)) + 1, w = width();
|
||||
int32 to = (r.bottom() / int32(st::mentionHeight)) + 1, w = width(), htagwidth = w - st::dlgPaddingHor * 2;
|
||||
if (to > hashtagResults.size()) to = hashtagResults.size();
|
||||
p.setFont(st::mentionFont->f);
|
||||
p.setPen(st::black->p);
|
||||
@@ -106,8 +108,27 @@ void DialogsListWidget::paintEvent(QPaintEvent *e) {
|
||||
int skip = (st::mentionHeight - st::notifyClose.icon.pxHeight()) / 2;
|
||||
p.drawPixmap(QPoint(w - st::notifyClose.icon.pxWidth() - skip, skip), App::sprite(), st::notifyClose.icon);
|
||||
}
|
||||
QString tag = st::mentionFont->m.elidedText('#' + hashtagResults.at(from), Qt::ElideRight, w - st::dlgPaddingHor * 2);
|
||||
p.drawText(st::dlgPaddingHor, st::mentionTop + st::mentionFont->ascent, tag);
|
||||
|
||||
QString first = (_hashtagFilter.size() < 2) ? QString() : ('#' + hashtagResults.at(from).mid(0, _hashtagFilter.size() - 1)), second = (_hashtagFilter.size() < 2) ? ('#' + hashtagResults.at(from)) : hashtagResults.at(from).mid(_hashtagFilter.size() - 1);
|
||||
int32 firstwidth = st::mentionFont->m.width(first), secondwidth = st::mentionFont->m.width(second);
|
||||
if (htagwidth < firstwidth + secondwidth) {
|
||||
if (htagwidth < firstwidth + st::mentionFont->elidew) {
|
||||
first = st::mentionFont->m.elidedText(first + second, Qt::ElideRight, htagwidth);
|
||||
second = QString();
|
||||
} else {
|
||||
second = st::mentionFont->m.elidedText(second, Qt::ElideRight, htagwidth - firstwidth);
|
||||
}
|
||||
}
|
||||
|
||||
p.setFont(st::mentionFont->f);
|
||||
if (!first.isEmpty()) {
|
||||
p.setPen(st::profileOnlineColor->p);
|
||||
p.drawText(st::dlgPaddingHor, st::mentionTop + st::mentionFont->ascent, first);
|
||||
}
|
||||
if (!second.isEmpty()) {
|
||||
p.setPen(st::profileOfflineColor->p);
|
||||
p.drawText(st::dlgPaddingHor + firstwidth, st::mentionTop + st::mentionFont->ascent, second);
|
||||
}
|
||||
p.translate(0, st::mentionHeight);
|
||||
}
|
||||
}
|
||||
@@ -607,8 +628,9 @@ void DialogsListWidget::onFilterUpdate(QString newFilter, bool force) {
|
||||
}
|
||||
}
|
||||
|
||||
void DialogsListWidget::onHashtagFilterUpdate(QString newFilter) {
|
||||
void DialogsListWidget::onHashtagFilterUpdate(QStringRef newFilter) {
|
||||
if (newFilter.isEmpty() || newFilter.at(0) != '#') {
|
||||
_hashtagFilter = QString();
|
||||
if (!hashtagResults.isEmpty()) {
|
||||
hashtagResults.clear();
|
||||
refresh(true);
|
||||
@@ -616,6 +638,7 @@ void DialogsListWidget::onHashtagFilterUpdate(QString newFilter) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
_hashtagFilter = newFilter.toString();
|
||||
if (cRecentSearchHashtags().isEmpty() && cRecentWriteHashtags().isEmpty()) {
|
||||
Local::readRecentHashtags();
|
||||
}
|
||||
@@ -624,7 +647,7 @@ void DialogsListWidget::onHashtagFilterUpdate(QString newFilter) {
|
||||
if (!recent.isEmpty()) {
|
||||
hashtagResults.reserve(qMin(recent.size(), 5));
|
||||
for (RecentHashtagPack::const_iterator i = recent.cbegin(), e = recent.cend(); i != e; ++i) {
|
||||
if (i->first.startsWith(newFilter.midRef(1), Qt::CaseInsensitive) && i->first.size() + 1 != newFilter.size()) {
|
||||
if (i->first.startsWith(_hashtagFilter.midRef(1), Qt::CaseInsensitive) && i->first.size() + 1 != newFilter.size()) {
|
||||
hashtagResults.push_back(i->first);
|
||||
if (hashtagResults.size() == 5) break;
|
||||
}
|
||||
@@ -1604,7 +1627,10 @@ void DialogsWidget::onSearchMore(MsgId minMsgId) {
|
||||
|
||||
void DialogsWidget::loadDialogs() {
|
||||
if (dlgPreloading) return;
|
||||
if (dlgCount >= 0 && dlgOffset >= dlgCount) return;
|
||||
if (dlgCount >= 0 && dlgOffset >= dlgCount) {
|
||||
cSetDialogsReceived(true);
|
||||
return;
|
||||
}
|
||||
|
||||
int32 loadCount = dlgOffset ? DialogsPerPage : DialogsFirstLoad;
|
||||
dlgPreloading = MTP::send(MTPmessages_GetDialogs(MTP_int(dlgOffset), MTP_int(0), MTP_int(loadCount)), rpcDone(&DialogsWidget::dialogsReceived), rpcFail(&DialogsWidget::dialogsFailed));
|
||||
@@ -1752,12 +1778,13 @@ void DialogsWidget::onFilterUpdate(bool force) {
|
||||
|
||||
void DialogsWidget::onFilterCursorMoved(int from, int to) {
|
||||
if (to < 0) to = _filter.cursorPosition();
|
||||
QString t = _filter.text(), r;
|
||||
QString t = _filter.text();
|
||||
QStringRef r;
|
||||
for (int start = to; start > 0;) {
|
||||
--start;
|
||||
if (t.size() <= start) break;
|
||||
if (t.at(start) == '#') {
|
||||
r = t.mid(start, to - start);
|
||||
r = t.midRef(start, to - start);
|
||||
break;
|
||||
}
|
||||
if (!t.at(start).isLetterOrNumber() && t.at(start) != '_') break;
|
||||
@@ -1886,6 +1913,10 @@ DialogsIndexed &DialogsWidget::contactsList() {
|
||||
return list.contactsList();
|
||||
}
|
||||
|
||||
DialogsIndexed &DialogsWidget::dialogsList() {
|
||||
return list.dialogsList();
|
||||
}
|
||||
|
||||
void DialogsWidget::onAddContact() {
|
||||
App::wnd()->replaceLayer(new AddContactBox());
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ public:
|
||||
bool hasFilteredResults() const;
|
||||
|
||||
void onFilterUpdate(QString newFilter, bool force = false);
|
||||
void onHashtagFilterUpdate(QString newFilter);
|
||||
void onHashtagFilterUpdate(QStringRef newFilter);
|
||||
void itemRemoved(HistoryItem *item);
|
||||
void itemReplaced(HistoryItem *oldItem, HistoryItem *newItem);
|
||||
|
||||
@@ -130,7 +130,7 @@ private:
|
||||
bool contactSel;
|
||||
bool selByMouse;
|
||||
|
||||
QString filter;
|
||||
QString filter, _hashtagFilter;
|
||||
|
||||
QStringList hashtagResults;
|
||||
int32 hashtagSel;
|
||||
@@ -197,6 +197,7 @@ public:
|
||||
void removeContact(UserData *user);
|
||||
|
||||
DialogsIndexed &contactsList();
|
||||
DialogsIndexed &dialogsList();
|
||||
|
||||
void enableShadow(bool enable = true);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -269,6 +269,16 @@ private:
|
||||
int32 _stickersWidth;
|
||||
};
|
||||
|
||||
struct StickerIcon {
|
||||
StickerIcon() : setId(RecentStickerSetId), sticker(0), pixw(0), pixh(0) {
|
||||
}
|
||||
StickerIcon(uint64 setId, DocumentData *sticker, int32 pixw, int32 pixh) : setId(setId), sticker(sticker), pixw(pixw), pixh(pixh) {
|
||||
}
|
||||
uint64 setId;
|
||||
DocumentData *sticker;
|
||||
int32 pixw, pixh;
|
||||
};
|
||||
|
||||
class StickerPanInner : public TWidget, public Animated {
|
||||
Q_OBJECT
|
||||
|
||||
@@ -294,9 +304,13 @@ public:
|
||||
void refreshStickers();
|
||||
void refreshRecent(bool resize = true);
|
||||
|
||||
void fillIcons(QVector<StickerIcon> &icons);
|
||||
|
||||
void setScrollTop(int top);
|
||||
void preloadImages();
|
||||
|
||||
uint64 currentSet(int yOffset) const;
|
||||
|
||||
public slots:
|
||||
|
||||
void updateSelected();
|
||||
@@ -306,6 +320,8 @@ signals:
|
||||
void selected(DocumentData *sticker);
|
||||
void removing(uint64 setId);
|
||||
|
||||
void refreshIcons();
|
||||
|
||||
void switchToEmoji();
|
||||
|
||||
void scrollToY(int y);
|
||||
@@ -327,6 +343,7 @@ private:
|
||||
QList<uint64> _setIds;
|
||||
QList<StickerPack> _sets;
|
||||
QList<QVector<float64> > _hovers;
|
||||
QList<bool> _custom;
|
||||
|
||||
int32 _selected, _pressedSel;
|
||||
QPoint _lastMousePos;
|
||||
@@ -349,6 +366,12 @@ public:
|
||||
void otherEnter();
|
||||
void otherLeave();
|
||||
|
||||
void mousePressEvent(QMouseEvent *e);
|
||||
void mouseMoveEvent(QMouseEvent *e);
|
||||
void mouseReleaseEvent(QMouseEvent *e);
|
||||
|
||||
bool event(QEvent *e);
|
||||
|
||||
void fastHide();
|
||||
bool hiding() const {
|
||||
return _hiding || _hideTimer.isActive();
|
||||
@@ -356,6 +379,8 @@ public:
|
||||
|
||||
bool animStep(float64 ms);
|
||||
|
||||
bool iconAnim(float64 ms);
|
||||
|
||||
bool eventFilter(QObject *obj, QEvent *e);
|
||||
void stickersInstalled(uint64 setId);
|
||||
|
||||
@@ -377,6 +402,8 @@ public slots:
|
||||
void onRemoveSetSure();
|
||||
void onDelayedHide();
|
||||
|
||||
void onRefreshIcons();
|
||||
|
||||
signals:
|
||||
|
||||
void emojiSelected(EmojiPtr emoji);
|
||||
@@ -385,6 +412,13 @@ signals:
|
||||
|
||||
private:
|
||||
|
||||
bool _horizontal;
|
||||
|
||||
void leaveToChildEvent(QEvent *e);
|
||||
|
||||
void updateSelected();
|
||||
void updateIcons();
|
||||
|
||||
void prepareTab(int32 &left, int32 top, int32 _width, FlatRadiobutton &tab);
|
||||
|
||||
void showAll();
|
||||
@@ -403,6 +437,18 @@ private:
|
||||
BoxShadow _shadow;
|
||||
|
||||
FlatRadiobutton _recent, _people, _nature, _food, _celebration, _activity, _travel, _objects;
|
||||
QVector<StickerIcon> _icons;
|
||||
QVector<float64> _iconHovers;
|
||||
int32 _iconOver, _iconSel, _iconDown;
|
||||
bool _iconsDragging;
|
||||
typedef QMap<int32, uint64> Animations; // index - showing, -index - hiding
|
||||
Animations _iconAnimations;
|
||||
Animation _iconAnim;
|
||||
QPoint _iconsMousePos, _iconsMouseDown;
|
||||
int32 _iconsLeft, _iconsTop;
|
||||
int32 _iconsStartX, _iconsMax;
|
||||
anim::ivalue _iconsX;
|
||||
uint64 _iconsStartAnim;
|
||||
|
||||
bool _stickersShown;
|
||||
QPixmap _fromCache, _toCache;
|
||||
@@ -421,6 +467,7 @@ private:
|
||||
|
||||
typedef QList<UserData*> MentionRows;
|
||||
typedef QList<QString> HashtagRows;
|
||||
typedef QList<QPair<UserData*, BotCommand> > BotCommandRows;
|
||||
|
||||
class MentionsDropdown;
|
||||
class MentionsInner : public QWidget {
|
||||
@@ -428,7 +475,7 @@ class MentionsInner : public QWidget {
|
||||
|
||||
public:
|
||||
|
||||
MentionsInner(MentionsDropdown *parent, MentionRows *rows, HashtagRows *hrows);
|
||||
MentionsInner(MentionsDropdown *parent, MentionRows *rows, HashtagRows *hrows, BotCommandRows *crows);
|
||||
|
||||
void paintEvent(QPaintEvent *e);
|
||||
|
||||
@@ -459,6 +506,7 @@ private:
|
||||
MentionsDropdown *_parent;
|
||||
MentionRows *_rows;
|
||||
HashtagRows *_hrows;
|
||||
BotCommandRows *_crows;
|
||||
int32 _sel;
|
||||
bool _mouseSel;
|
||||
QPoint _mousePos;
|
||||
@@ -477,13 +525,14 @@ public:
|
||||
|
||||
void fastHide();
|
||||
|
||||
void showFiltered(ChatData *chat, QString start);
|
||||
void showFiltered(PeerData *peer, QString start);
|
||||
void updateFiltered(bool toDown = false);
|
||||
void setBoundings(QRect boundings);
|
||||
|
||||
bool animStep(float64 ms);
|
||||
|
||||
const QString &filter() const;
|
||||
ChatData *chat() const;
|
||||
|
||||
int32 innerTop();
|
||||
int32 innerBottom();
|
||||
@@ -510,11 +559,13 @@ private:
|
||||
QPixmap _cache;
|
||||
MentionRows _rows;
|
||||
HashtagRows _hrows;
|
||||
BotCommandRows _crows;
|
||||
|
||||
ScrollArea _scroll;
|
||||
MentionsInner _inner;
|
||||
|
||||
ChatData *_chat;
|
||||
UserData *_user;
|
||||
QString _filter;
|
||||
QRect _boundings;
|
||||
|
||||
|
||||
@@ -40,6 +40,10 @@ void FileUploader::uploadMedia(MsgId msgId, const ReadyLocalMedia &media) {
|
||||
if (!media.file.isEmpty()) {
|
||||
document->location = FileLocation(StorageFilePartial, media.file);
|
||||
}
|
||||
} else if (media.type == ToPrepareAudio) {
|
||||
AudioData *audio = App::feedAudio(media.audio);
|
||||
audio->status = FileUploading;
|
||||
audio->data = media.data;
|
||||
}
|
||||
queue.insert(msgId, File(media));
|
||||
sendNext();
|
||||
@@ -56,6 +60,12 @@ void FileUploader::currentFailed() {
|
||||
doc->status = FileFailed;
|
||||
}
|
||||
emit documentFailed(j.key());
|
||||
} else if (j->media.type == ToPrepareAudio) {
|
||||
AudioData *audio = App::audio(j->media.id);
|
||||
if (audio->status == FileUploading) {
|
||||
audio->status = FileFailed;
|
||||
}
|
||||
emit audioFailed(j.key());
|
||||
}
|
||||
queue.erase(j);
|
||||
}
|
||||
@@ -112,7 +122,7 @@ void FileUploader::sendNext() {
|
||||
emit photoReady(uploading, MTP_inputFile(MTP_long(i->media.id), MTP_int(i->partsCount), MTP_string(i->media.filename), MTP_string(i->media.jpeg_md5)));
|
||||
} else if (i->media.type == ToPrepareDocument) {
|
||||
QByteArray docMd5(32, Qt::Uninitialized);
|
||||
hashMd5Hex(i->docHash.result(), docMd5.data());
|
||||
hashMd5Hex(i->md5Hash.result(), docMd5.data());
|
||||
|
||||
MTPInputFile doc = (i->docSize > UseBigFilesFrom) ? MTP_inputFileBig(MTP_long(i->media.id), MTP_int(i->docPartsCount), MTP_string(i->media.filename)) : MTP_inputFile(MTP_long(i->media.id), MTP_int(i->docPartsCount), MTP_string(i->media.filename), MTP_string(docMd5));
|
||||
if (i->partsCount) {
|
||||
@@ -120,6 +130,12 @@ void FileUploader::sendNext() {
|
||||
} else {
|
||||
emit documentReady(uploading, doc);
|
||||
}
|
||||
} else if (i->media.type == ToPrepareAudio) {
|
||||
QByteArray audioMd5(32, Qt::Uninitialized);
|
||||
hashMd5Hex(i->md5Hash.result(), audioMd5.data());
|
||||
|
||||
MTPInputFile audio = (i->docSize > UseBigFilesFrom) ? MTP_inputFileBig(MTP_long(i->media.id), MTP_int(i->docPartsCount), MTP_string(i->media.filename)) : MTP_inputFile(MTP_long(i->media.id), MTP_int(i->docPartsCount), MTP_string(i->media.filename), MTP_string(audioMd5));
|
||||
emit audioReady(uploading, audio);
|
||||
}
|
||||
queue.remove(uploading);
|
||||
uploading = 0;
|
||||
@@ -139,12 +155,12 @@ void FileUploader::sendNext() {
|
||||
}
|
||||
toSend = i->docFile->read(i->docPartSize);
|
||||
if (i->docSize <= UseBigFilesFrom) {
|
||||
i->docHash.feed(toSend.constData(), toSend.size());
|
||||
i->md5Hash.feed(toSend.constData(), toSend.size());
|
||||
}
|
||||
} else {
|
||||
toSend = i->media.data.mid(i->docSentParts * i->docPartSize, i->docPartSize);
|
||||
if (i->media.type == ToPrepareDocument && i->docSentParts <= UseBigFilesFrom) {
|
||||
i->docHash.feed(toSend.constData(), toSend.size());
|
||||
if ((i->media.type == ToPrepareDocument || i->media.type == ToPrepareAudio) && i->docSentParts <= UseBigFilesFrom) {
|
||||
i->md5Hash.feed(toSend.constData(), toSend.size());
|
||||
}
|
||||
}
|
||||
if (toSend.size() > i->docPartSize || (toSend.size() < i->docPartSize && i->docSentParts + 1 != i->docPartsCount)) {
|
||||
@@ -249,6 +265,14 @@ void FileUploader::partLoaded(const MTPBool &result, mtpRequestId requestId) {
|
||||
}
|
||||
}
|
||||
emit documentProgress(k.key());
|
||||
} else if (k->media.type == ToPrepareAudio) {
|
||||
AudioData *audio = App::audio(k->media.id);
|
||||
if (audio->status == FileUploading) {
|
||||
audio->uploadOffset = (k->docSentParts - docRequestsSent.size()) * k->docPartSize;
|
||||
if (audio->uploadOffset > audio->size) {
|
||||
audio->uploadOffset = audio->size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,19 +45,22 @@ signals:
|
||||
void photoReady(MsgId msgId, const MTPInputFile &file);
|
||||
void documentReady(MsgId msgId, const MTPInputFile &file);
|
||||
void thumbDocumentReady(MsgId msgId, const MTPInputFile &file, const MTPInputFile &thumb);
|
||||
void audioReady(MsgId msgId, const MTPInputFile &file);
|
||||
|
||||
void photoProgress(MsgId msgId);
|
||||
void documentProgress(MsgId msgId);
|
||||
void audioProgress(MsgId msgId);
|
||||
|
||||
void photoFailed(MsgId msgId);
|
||||
void documentFailed(MsgId msgId);
|
||||
void audioFailed(MsgId msgId);
|
||||
|
||||
private:
|
||||
|
||||
struct File {
|
||||
File(const ReadyLocalMedia &media) : media(media), docSentParts(0) {
|
||||
partsCount = media.parts.size();
|
||||
if (media.type == ToPrepareDocument) {
|
||||
if (media.type == ToPrepareDocument || media.type == ToPrepareAudio) {
|
||||
docSize = media.file.isEmpty() ? media.data.size() : media.filesize;
|
||||
if (docSize >= 1024 * 1024 || !setPartSize(DocumentUploadPartSize0)) {
|
||||
if (docSize > 32 * 1024 * 1024 || !setPartSize(DocumentUploadPartSize1)) {
|
||||
@@ -83,12 +86,13 @@ private:
|
||||
ReadyLocalMedia media;
|
||||
int32 partsCount;
|
||||
|
||||
HashMd5 md5Hash;
|
||||
|
||||
QSharedPointer<QFile> docFile;
|
||||
int32 docSentParts;
|
||||
int32 docSize;
|
||||
int32 docPartSize;
|
||||
int32 docPartsCount;
|
||||
HashMd5 docHash;
|
||||
};
|
||||
typedef QMap<MsgId, File> Queue;
|
||||
|
||||
|
||||
@@ -223,6 +223,63 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class AnimationFunc {
|
||||
public:
|
||||
virtual bool animStep(float64 ms) = 0;
|
||||
virtual ~AnimationFunc() {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Type>
|
||||
class AnimationFuncOwned : public AnimationFunc {
|
||||
public:
|
||||
typedef bool (Type::*Method)(float64);
|
||||
|
||||
AnimationFuncOwned(Type *obj, Method method) : _obj(obj), _method(method) {
|
||||
}
|
||||
|
||||
bool animStep(float64 ms) {
|
||||
return (_obj->*_method)(ms);
|
||||
}
|
||||
|
||||
private:
|
||||
Type *_obj;
|
||||
Method _method;
|
||||
|
||||
};
|
||||
|
||||
template <typename Type>
|
||||
AnimationFunc *animFunc(Type *obj, typename AnimationFuncOwned<Type>::Method method) {
|
||||
return new AnimationFuncOwned<Type>(obj, method);
|
||||
}
|
||||
|
||||
class Animation : public Animated {
|
||||
public:
|
||||
|
||||
Animation(AnimationFunc *func) : _func(func) {
|
||||
}
|
||||
|
||||
void start() {
|
||||
anim::start(this);
|
||||
}
|
||||
void stop() {
|
||||
anim::stop(this);
|
||||
}
|
||||
|
||||
//Animation
|
||||
bool animStep(float64 ms) {
|
||||
return _func->animStep(ms);
|
||||
}
|
||||
|
||||
~Animation() {
|
||||
delete _func;
|
||||
}
|
||||
|
||||
private:
|
||||
AnimationFunc *_func;
|
||||
|
||||
};
|
||||
|
||||
class AnimationManager : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
|
||||
@@ -19,10 +19,11 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
|
||||
#include "boxshadow.h"
|
||||
|
||||
BoxShadow::BoxShadow(const style::rect &topLeft) : _size(topLeft.width() / cIntRetinaFactor()) {
|
||||
BoxShadow::BoxShadow(const style::sprite &topLeft) : _size(topLeft.pxWidth()), _pixsize(_size * cIntRetinaFactor()) {
|
||||
if (!_size) return;
|
||||
|
||||
QImage cornersImage(_size * 2, _size * 2, QImage::Format_ARGB32_Premultiplied);
|
||||
QImage cornersImage(_pixsize * 2, _pixsize * 2, QImage::Format_ARGB32_Premultiplied);
|
||||
cornersImage.setDevicePixelRatio(cRetinaFactor());
|
||||
{
|
||||
QPainter p(&cornersImage);
|
||||
p.drawPixmap(QPoint(rtl() ? _size : 0, 0), App::sprite(), topLeft);
|
||||
@@ -41,39 +42,65 @@ BoxShadow::BoxShadow(const style::rect &topLeft) : _size(topLeft.width() / cIntR
|
||||
{
|
||||
QPainter p(&cornersImage);
|
||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
p.drawImage(0, _size, cornersImage.mirrored(), 0, _size, _size, _size);
|
||||
QImage m = cornersImage.mirrored();
|
||||
m.setDevicePixelRatio(cRetinaFactor());
|
||||
p.drawImage(0, _size, m, 0, _pixsize, _pixsize, _pixsize);
|
||||
}
|
||||
{
|
||||
QPainter p(&cornersImage);
|
||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
p.drawImage(_size, 0, cornersImage.mirrored(true, false), _size, 0, _size, _size * 2);
|
||||
QImage m = cornersImage.mirrored(true, false);
|
||||
m.setDevicePixelRatio(cRetinaFactor());
|
||||
p.drawImage(_size, 0, m, _pixsize, 0, _pixsize, _pixsize * 2);
|
||||
}
|
||||
_corners = QPixmap::fromImage(cornersImage, Qt::ColorOnly);
|
||||
_colors.reserve(_size);
|
||||
_corners.setDevicePixelRatio(cRetinaFactor());
|
||||
_colors.reserve(_pixsize);
|
||||
uchar prev = 0;
|
||||
for (int32 i = 0; i < _size; ++i) {
|
||||
uchar a = (cornersImage.pixel(QPoint(i, _size - 1)) >> 24);
|
||||
for (int32 i = 0; i < _pixsize; ++i) {
|
||||
uchar a = (cornersImage.pixel(QPoint(i, _pixsize - 1)) >> 24);
|
||||
if (a < prev) break;
|
||||
|
||||
_colors.push_back(style::color(0, 0, 0, a));
|
||||
prev = a;
|
||||
}
|
||||
}
|
||||
|
||||
void BoxShadow::paint(QPainter &p, const QRect &box, const QPoint &shift, int32 flags) {
|
||||
if (!_size) return;
|
||||
|
||||
int32 count = _colors.size(), minus = _size - count + 1;
|
||||
bool left = (flags & Left), top = (flags & Top), right = (flags & Right), bottom = (flags & Bottom);
|
||||
if (left && top) p.drawPixmap(box.left() - _size + minus + shift.x(), box.top() - _size + minus + shift.y(), _corners, 0, 0, _size, _size);
|
||||
if (right && top) p.drawPixmap(box.right() - minus + 1 + shift.x(), box.top() - _size + minus + shift.y(), _corners, _size, 0, _size, _size);
|
||||
if (right && bottom) p.drawPixmap(box.right() - minus + 1 + shift.x(), box.bottom() - minus + 1 + shift.y(), _corners, _size, _size, _size, _size);
|
||||
if (left && bottom) p.drawPixmap(box.left() - _size + minus + shift.x(), box.bottom() - minus + 1 + shift.y(), _corners, 0, _size, _size, _size);
|
||||
for (int32 i = 1; i <= count; ++i) {
|
||||
p.setPen(_colors[i - 1]->p);
|
||||
if (top) p.fillRect(box.left() + (left ? minus : 0) + shift.x(), box.top() - count + i + shift.y(), box.width() - (right ? minus : 0) - (left ? minus : 0), cIntRetinaFactor(), _colors[i - 1]->b);
|
||||
if (right) p.fillRect(box.right() + count - i + shift.x(), box.top() + (top ? minus : 0) + shift.y(), cIntRetinaFactor(), box.height() - (bottom ? minus : 0) - (top ? minus : 0), _colors[i - 1]->b);
|
||||
if (bottom) p.fillRect(box.left() + (left ? minus : 0) + shift.x(), box.bottom() + count - i + shift.y(), box.width() - (right ? minus : 0) - (left ? minus : 0), cIntRetinaFactor(), _colors[i - 1]->b);
|
||||
if (left) p.fillRect(box.left() - count + i + shift.x(), box.top() + (top ? minus : 0) + shift.y(), cIntRetinaFactor(), box.height() - (bottom ? minus : 0) - (top ? minus : 0), _colors[i - 1]->b);
|
||||
if (cRetina()) {
|
||||
_left = QPixmap::fromImage(cornersImage.copy(0, _pixsize - 1, _colors.size(), 1), Qt::ColorOnly);
|
||||
_left.setDevicePixelRatio(cRetinaFactor());
|
||||
_top = QPixmap::fromImage(cornersImage.copy(_pixsize - 1, 0, 1, _colors.size()), Qt::ColorOnly);
|
||||
_top.setDevicePixelRatio(cRetinaFactor());
|
||||
_right = QPixmap::fromImage(cornersImage.copy(_pixsize * 2 - _colors.size(), _pixsize, _colors.size(), 1), Qt::ColorOnly);
|
||||
_right.setDevicePixelRatio(cRetinaFactor());
|
||||
_bottom = QPixmap::fromImage(cornersImage.copy(_pixsize, _pixsize * 2 - _colors.size(), 1, _colors.size()), Qt::ColorOnly);
|
||||
_bottom.setDevicePixelRatio(cRetinaFactor());
|
||||
}
|
||||
}
|
||||
|
||||
void BoxShadow::paint(QPainter &p, const QRect &box, int32 shifty, int32 flags) {
|
||||
if (!_size) return;
|
||||
|
||||
int32 rshifty = shifty * cIntRetinaFactor();
|
||||
int32 count = _colors.size(), countsize = count / cIntRetinaFactor(), minus = _size - countsize + shifty;
|
||||
bool left = (flags & Left), top = (flags & Top), right = (flags & Right), bottom = (flags & Bottom);
|
||||
if (left && top) p.drawPixmap(box.left() - _size + minus, box.top() - _size + minus + shifty, _corners, 0, 0, _pixsize, _pixsize);
|
||||
if (right && top) p.drawPixmap(box.left() + box.width() - minus, box.top() - _size + minus + shifty, _corners, _pixsize, 0, _pixsize, _pixsize);
|
||||
if (right && bottom) p.drawPixmap(box.left() + box.width() - minus, box.top() + box.height() - minus + shifty, _corners, _pixsize, _pixsize, _pixsize, _pixsize);
|
||||
if (left && bottom) p.drawPixmap(box.left() - _size + minus, box.top() + box.height() - minus + shifty, _corners, 0, _pixsize, _pixsize, _pixsize);
|
||||
if (cRetina()) {
|
||||
bool wasSmooth = p.renderHints().testFlag(QPainter::SmoothPixmapTransform);
|
||||
if (wasSmooth) p.setRenderHint(QPainter::SmoothPixmapTransform, false);
|
||||
if (left) p.drawPixmap(box.left() - countsize + shifty, box.top() + (top ? minus : 0) + shifty, countsize - shifty, box.height() - (bottom ? minus : 0) - (top ? minus : 0), _left, 0, 0, count - rshifty, 1);
|
||||
if (top) p.drawPixmap(box.left() + (left ? minus : 0), box.top() - countsize + 2 * shifty, box.width() - (right ? minus : 0) - (left ? minus : 0), countsize - 2 * shifty, _top, 0, 0, 1, count - 2 * rshifty);
|
||||
if (right) p.drawPixmap(box.left() + box.width(), box.top() + (top ? minus : 0) + shifty, countsize - shifty, box.height() - (bottom ? minus : 0) - (top ? minus : 0), _right, rshifty, 0, count - rshifty, 1);
|
||||
if (bottom) p.drawPixmap(box.left() + (left ? minus : 0), box.top() + box.height(), box.width() - (right ? minus : 0) - (left ? minus : 0), countsize, _bottom, 0, 0, 1, count);
|
||||
if (wasSmooth) p.setRenderHint(QPainter::SmoothPixmapTransform);
|
||||
} else {
|
||||
p.setPen(Qt::NoPen);
|
||||
for (int32 i = 0; i < count; ++i) {
|
||||
if (left && i + shifty < count) p.fillRect(box.left() - count + i + shifty, box.top() + (top ? minus : 0) + shifty, 1, box.height() - (bottom ? minus : 0) - (top ? minus : 0), _colors[i]->b);
|
||||
if (top && i + 2 * shifty < count) p.fillRect(box.left() + (left ? minus : 0), box.top() - count + i + 2 * shifty, box.width() - (right ? minus : 0) - (left ? minus : 0), 1, _colors[i]->b);
|
||||
if (right && i + shifty < count) p.fillRect(box.left() + box.width() + count - i - shifty - 1, box.top() + (top ? minus : 0) + shifty, 1, box.height() - (bottom ? minus : 0) - (top ? minus : 0), _colors[i]->b);
|
||||
if (bottom) p.fillRect(box.left() + (left ? minus : 0), box.top() + box.height() + count - i - 1, box.width() - (right ? minus : 0) - (left ? minus : 0), 1, _colors[i]->b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,14 +27,14 @@ public:
|
||||
Bottom = 8
|
||||
};
|
||||
|
||||
BoxShadow(const style::rect &topLeft);
|
||||
BoxShadow(const style::sprite &topLeft);
|
||||
|
||||
void paint(QPainter &p, const QRect &box, const QPoint &shift = QPoint(0, 1), int32 flags = Left | Top | Right | Bottom);
|
||||
void paint(QPainter &p, const QRect &box, int32 shifty, int32 flags = Left | Top | Right | Bottom);
|
||||
|
||||
private:
|
||||
|
||||
int32 _size;
|
||||
QPixmap _corners;
|
||||
int32 _size, _pixsize;
|
||||
QPixmap _corners, _left, _top, _right, _bottom;
|
||||
QVector<style::color> _colors;
|
||||
|
||||
};
|
||||
|
||||
@@ -144,7 +144,7 @@ void ContextMenu::paintEvent(QPaintEvent *e) {
|
||||
|
||||
QRect r(_st.padding.left(), _st.padding.top(), _width - _st.padding.left() - _st.padding.right(), _height - _st.padding.top() - _st.padding.bottom());
|
||||
// draw shadow
|
||||
_shadow.paint(p, r);
|
||||
_shadow.paint(p, r, _st.shadowShift);
|
||||
}
|
||||
|
||||
void ContextMenu::keyPressEvent(QKeyEvent *e) {
|
||||
|
||||
@@ -485,7 +485,7 @@ void CountrySelect::paintEvent(QPaintEvent *e) {
|
||||
p.setOpacity(1);
|
||||
|
||||
QRect inner(_innerLeft, _innerTop, _innerWidth, _innerHeight);
|
||||
_shadow.paint(p, inner);
|
||||
_shadow.paint(p, inner, st::boxShadowShift);
|
||||
if (trivial || e->rect().intersects(inner)) {
|
||||
// fill bg
|
||||
p.fillRect(inner, st::white->b);
|
||||
|
||||
@@ -81,9 +81,6 @@ inline EmojiPtr emojiFromUrl(const QString &url) {
|
||||
}
|
||||
|
||||
inline EmojiPtr emojiFromText(const QChar *ch, const QChar *e, int &len) {
|
||||
QString tmp(ch, e - ch);
|
||||
QByteArray tmp2 = tmp.toUtf8();
|
||||
const char *tmp3 = tmp2.constData();
|
||||
EmojiPtr emoji = 0;
|
||||
if (ch + 1 < e && ((ch->isHighSurrogate() && (ch + 1)->isLowSurrogate()) || (((ch->unicode() >= 48 && ch->unicode() < 58) || ch->unicode() == 35) && (ch + 1)->unicode() == 0x20E3))) {
|
||||
uint32 code = (ch->unicode() << 16) | (ch + 1)->unicode();
|
||||
|
||||
@@ -20,7 +20,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
|
||||
FlatButton::FlatButton(QWidget *parent, const QString &text, const style::flatButton &st) : Button(parent),
|
||||
_text(text),
|
||||
_st(st),
|
||||
_st(st), _autoFontPadding(0),
|
||||
a_bg(st.bgColor->c), a_text(st.color->c), _opacity(1) {
|
||||
if (_st.width < 0) {
|
||||
_st.width = textWidth() - _st.width;
|
||||
@@ -52,10 +52,35 @@ void FlatButton::setWidth(int32 w) {
|
||||
resize(_st.width, height());
|
||||
}
|
||||
|
||||
void FlatButton::setAutoFontSize(int32 padding, const QString &txt) {
|
||||
_autoFontPadding = padding;
|
||||
if (_autoFontPadding) {
|
||||
_textForAutoSize = txt;
|
||||
resizeEvent(0);
|
||||
} else {
|
||||
_textForAutoSize = QString();
|
||||
_autoFont = style::font();
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
int32 FlatButton::textWidth() const {
|
||||
return _st.font->m.width(_text);
|
||||
}
|
||||
|
||||
void FlatButton::resizeEvent(QResizeEvent *e) {
|
||||
if (_autoFontPadding) {
|
||||
_autoFont = _st.font;
|
||||
for (int32 s = _st.font->f.pixelSize(); s >= st::fsize; --s) {
|
||||
_autoFont = style::font(s, _st.font->flags(), _st.font->family());
|
||||
if (2 * _autoFontPadding + _autoFont->m.width(_textForAutoSize) <= width()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Button::resizeEvent(e);
|
||||
}
|
||||
|
||||
bool FlatButton::animStep(float64 ms) {
|
||||
float64 dt = ms / _st.duration;
|
||||
bool res = true;
|
||||
@@ -95,12 +120,15 @@ void FlatButton::paintEvent(QPaintEvent *e) {
|
||||
p.setOpacity(_opacity);
|
||||
p.fillRect(r, a_bg.current());
|
||||
|
||||
p.setFont(((_state & StateOver) ? _st.overFont : _st.font)->f);
|
||||
p.setFont((_autoFont ? _autoFont : ((_state & StateOver) ? _st.overFont : _st.font))->f);
|
||||
p.setRenderHint(QPainter::TextAntialiasing);
|
||||
p.setPen(a_text.current());
|
||||
|
||||
r.setTop((_state & StateOver) ? ((_state & StateDown) ? _st.downTextTop : _st.overTextTop) : _st.textTop);
|
||||
p.drawText(r, _text, QTextOption(Qt::AlignHCenter));
|
||||
int32 top = (_state & StateOver) ? ((_state & StateDown) ? _st.downTextTop : _st.overTextTop) : _st.textTop;
|
||||
if (_autoFont) top += (_st.font->height - _autoFont->height) / 2;
|
||||
r.setTop(top);
|
||||
|
||||
p.drawText(r, _text, style::al_top);
|
||||
}
|
||||
|
||||
BottomButton::BottomButton(QWidget *w, const QString &t, const style::flatButton &s) : FlatButton(w, t, s) {
|
||||
|
||||
@@ -29,12 +29,15 @@ public:
|
||||
|
||||
FlatButton(QWidget *parent, const QString &text, const style::flatButton &st);
|
||||
|
||||
void resizeEvent(QResizeEvent *e);
|
||||
|
||||
bool animStep(float64 ms);
|
||||
void paintEvent(QPaintEvent *e);
|
||||
void setOpacity(float64 o);
|
||||
|
||||
void setText(const QString &text);
|
||||
void setWidth(int32 w);
|
||||
void setAutoFontSize(int32 padding, const QString &txt);
|
||||
|
||||
int32 textWidth() const;
|
||||
|
||||
@@ -47,12 +50,15 @@ public slots:
|
||||
|
||||
private:
|
||||
|
||||
QString _text;
|
||||
QString _text, _textForAutoSize;
|
||||
int32 _textWidth;
|
||||
|
||||
style::flatButton _st;
|
||||
anim::cvalue a_bg, a_text;
|
||||
|
||||
int32 _autoFontPadding;
|
||||
style::font _autoFont;
|
||||
|
||||
anim::cvalue a_bg, a_text;
|
||||
float64 _opacity;
|
||||
};
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ EmojiPtr FlatTextarea::getSingleEmoji() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void FlatTextarea::getMentionHashtagStart(QString &start) const {
|
||||
void FlatTextarea::getMentionHashtagBotCommandStart(QString &start) const {
|
||||
int32 pos = textCursor().position();
|
||||
if (textCursor().anchor() != pos) return;
|
||||
|
||||
@@ -195,11 +195,16 @@ void FlatTextarea::getMentionHashtagStart(QString &start) const {
|
||||
QTextCharFormat f = fr.charFormat();
|
||||
if (f.isImageFormat()) continue;
|
||||
|
||||
bool mentionInCommand = false;
|
||||
QString t(fr.text());
|
||||
for (int i = pos - p; i > 0; --i) {
|
||||
if (t.at(i - 1) == '@') {
|
||||
if ((pos - p - i < 1 || t.at(i).isLetter()) && (i < 2 || !(t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_'))) {
|
||||
start = t.mid(i - 1, pos - p - i + 1);
|
||||
} else if ((pos - p - i < 1 || t.at(i).isLetter()) && i > 2 && (t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_') && !mentionInCommand) {
|
||||
mentionInCommand = true;
|
||||
--i;
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
} else if (t.at(i - 1) == '#') {
|
||||
@@ -207,15 +212,20 @@ void FlatTextarea::getMentionHashtagStart(QString &start) const {
|
||||
start = t.mid(i - 1, pos - p - i + 1);
|
||||
}
|
||||
return;
|
||||
} else if (t.at(i - 1) == '/') {
|
||||
if (i < 2) {
|
||||
start = t.mid(i - 1, pos - p - i + 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (pos - p - i > 63) break;
|
||||
if (pos - p - i > 127 || (!mentionInCommand && (pos - p - i > 63))) break;
|
||||
if (!t.at(i - 1).isLetterOrNumber() && t.at(i - 1) != '_') break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void FlatTextarea::onMentionOrHashtagInsert(QString mentionOrHashtag) {
|
||||
void FlatTextarea::onMentionHashtagOrBotCommandInsert(QString str) {
|
||||
QTextCursor c(textCursor());
|
||||
int32 pos = c.position();
|
||||
|
||||
@@ -231,31 +241,37 @@ void FlatTextarea::onMentionOrHashtagInsert(QString mentionOrHashtag) {
|
||||
QTextCharFormat f = fr.charFormat();
|
||||
if (f.isImageFormat()) continue;
|
||||
|
||||
bool mentionInCommand = false;
|
||||
QString t(fr.text());
|
||||
for (int i = pos - p; i > 0; --i) {
|
||||
if (t.at(i - 1) == '@' || t.at(i - 1) == '#') {
|
||||
if (t.at(i - 1) == '@' || t.at(i - 1) == '#' || t.at(i - 1) == '/') {
|
||||
if ((i == pos - p || t.at(i).isLetter() || t.at(i - 1) == '#') && (i < 2 || !(t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_'))) {
|
||||
c.setPosition(p + i - 1, QTextCursor::MoveAnchor);
|
||||
int till = p + i;
|
||||
for (; (till < e) && (till - p - i + 1 < mentionOrHashtag.size()); ++till) {
|
||||
if (t.at(till - p).toLower() != mentionOrHashtag.at(till - p - i + 1).toLower()) {
|
||||
for (; (till < e) && (till - p - i + 1 < str.size()); ++till) {
|
||||
if (t.at(till - p).toLower() != str.at(till - p - i + 1).toLower()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (till - p - i + 1 == mentionOrHashtag.size() && till < e && t.at(till - p) == ' ') {
|
||||
if (till - p - i + 1 == str.size() && till < e && t.at(till - p) == ' ') {
|
||||
++till;
|
||||
}
|
||||
c.setPosition(till, QTextCursor::KeepAnchor);
|
||||
c.insertText(mentionOrHashtag + ' ');
|
||||
c.insertText(str + ' ');
|
||||
return;
|
||||
} else if ((i == pos - p || t.at(i).isLetter()) && t.at(i - 1) == '@' && i > 2 && (t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_') && !mentionInCommand) {
|
||||
mentionInCommand = true;
|
||||
--i;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (pos - p - i > 63) break;
|
||||
if (pos - p - i > 127 || (!mentionInCommand && (pos - p - i > 63))) break;
|
||||
if (!t.at(i - 1).isLetterOrNumber() && t.at(i - 1) != '_') break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
c.insertText(mentionOrHashtag + ' ');
|
||||
c.insertText(str + ' ');
|
||||
}
|
||||
|
||||
void FlatTextarea::getSingleEmojiFragment(QString &text, QTextFragment &fragment) const {
|
||||
@@ -681,8 +697,12 @@ bool FlatTextarea::animStep(float64 ms) {
|
||||
return res;
|
||||
}
|
||||
|
||||
const QString &FlatTextarea::getLastText() const {
|
||||
return _oldtext;
|
||||
}
|
||||
|
||||
void FlatTextarea::updatePlaceholder() {
|
||||
bool vis = !hasText();
|
||||
bool vis = getLastText().isEmpty();
|
||||
if (vis == _phVisible) return;
|
||||
|
||||
a_phLeft.start(vis ? 0 : _st.phShift);
|
||||
|
||||
@@ -37,6 +37,7 @@ public:
|
||||
void resizeEvent(QResizeEvent *e);
|
||||
void mousePressEvent(QMouseEvent *e);
|
||||
|
||||
const QString &getLastText() const;
|
||||
void updatePlaceholder();
|
||||
|
||||
QRect getTextRect() const;
|
||||
@@ -48,7 +49,7 @@ public:
|
||||
QSize minimumSizeHint() const;
|
||||
|
||||
EmojiPtr getSingleEmoji() const;
|
||||
void getMentionHashtagStart(QString &start) const;
|
||||
void getMentionHashtagBotCommandStart(QString &start) const;
|
||||
void removeSingleEmoji();
|
||||
QString getText(int32 start = 0, int32 end = -1) const;
|
||||
bool hasText() const;
|
||||
@@ -71,7 +72,7 @@ public slots:
|
||||
void onUndoAvailable(bool avail);
|
||||
void onRedoAvailable(bool avail);
|
||||
|
||||
void onMentionOrHashtagInsert(QString mentionOrHashtag);
|
||||
void onMentionHashtagOrBotCommandInsert(QString str);
|
||||
|
||||
signals:
|
||||
|
||||
@@ -85,6 +86,22 @@ signals:
|
||||
protected:
|
||||
|
||||
void insertEmoji(EmojiPtr emoji, QTextCursor c);
|
||||
TWidget *tparent() {
|
||||
return qobject_cast<TWidget*>(parentWidget());
|
||||
}
|
||||
const TWidget *tparent() const {
|
||||
return qobject_cast<const TWidget*>(parentWidget());
|
||||
}
|
||||
void enterEvent(QEvent *e) {
|
||||
TWidget *p(tparent());
|
||||
if (p) p->leaveToChildEvent(e);
|
||||
return QTextEdit::enterEvent(e);
|
||||
}
|
||||
void leaveEvent(QEvent *e) {
|
||||
TWidget *p(tparent());
|
||||
if (p) p->enterFromChildEvent(e);
|
||||
return QTextEdit::leaveEvent(e);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
|
||||
@@ -596,6 +596,9 @@ void ScrollArea::enterEvent(QEvent *e) {
|
||||
hor.hideTimeout(_st.hiding);
|
||||
vert.hideTimeout(_st.hiding);
|
||||
}
|
||||
TWidget *p(tparent());
|
||||
if (p) p->leaveToChildEvent(e);
|
||||
return QScrollArea::enterEvent(e);
|
||||
}
|
||||
|
||||
void ScrollArea::leaveEvent(QEvent *e) {
|
||||
@@ -603,6 +606,9 @@ void ScrollArea::leaveEvent(QEvent *e) {
|
||||
hor.hideTimeout(0);
|
||||
vert.hideTimeout(0);
|
||||
}
|
||||
TWidget *p(tparent());
|
||||
if (p) p->enterFromChildEvent(e);
|
||||
return QScrollArea::leaveEvent(e);
|
||||
}
|
||||
|
||||
void ScrollArea::scrollToY(int toTop, int toBottom) {
|
||||
@@ -655,3 +661,7 @@ void ScrollArea::updateColors(const style::color &bar, const style::color &bg, c
|
||||
hor.update();
|
||||
vert.update();
|
||||
}
|
||||
|
||||
ScrollArea::~ScrollArea() {
|
||||
takeWidget();
|
||||
}
|
||||
|
||||
@@ -131,6 +131,8 @@ public:
|
||||
|
||||
void updateColors(const style::color &bar, const style::color &bg, const style::color &barOver, const style::color &bgOver);
|
||||
|
||||
~ScrollArea();
|
||||
|
||||
public slots:
|
||||
|
||||
void scrollToY(int toTop, int toBottom = -1);
|
||||
@@ -150,6 +152,12 @@ signals:
|
||||
protected:
|
||||
|
||||
void scrollContentsBy(int dx, int dy);
|
||||
TWidget *tparent() {
|
||||
return qobject_cast<TWidget*>(parentWidget());
|
||||
}
|
||||
const TWidget *tparent() const {
|
||||
return qobject_cast<const TWidget*>(parentWidget());
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
|
||||
@@ -61,6 +61,10 @@ namespace style {
|
||||
return _flags;
|
||||
}
|
||||
|
||||
uint32 FontData::family() const {
|
||||
return _family;
|
||||
}
|
||||
|
||||
Font FontData::otherFlagsFont(uint32 flag, bool set) const {
|
||||
int32 newFlags = set ? (_flags | flag) : (_flags & ~flag);
|
||||
if (!modified[newFlags].v()) {
|
||||
|
||||
@@ -104,6 +104,7 @@ namespace style {
|
||||
Font underline(bool set = true) const;
|
||||
|
||||
uint32 flags() const;
|
||||
uint32 family() const;
|
||||
|
||||
QFont f;
|
||||
QFontMetrics m;
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace {
|
||||
const QRegularExpression _reMailStart(qsl("^[a-zA-Z\\-_\\.0-9]{1,256}\\@"));
|
||||
const QRegularExpression _reHashtag(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])#[\\w]{2,64}([\\W]|$)"), QRegularExpression::UseUnicodePropertiesOption);
|
||||
const QRegularExpression _reMention(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])@[A-Za-z_0-9]{5,32}([\\W]|$)"), QRegularExpression::UseUnicodePropertiesOption);
|
||||
const QRegularExpression _reBotCommand(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])/[A-Za-z_0-9]{1,64}(@[A-Za-z_0-9]{5,32})?([\\W]|$)"));
|
||||
QSet<int32> _validProtocols, _validTopDomains;
|
||||
|
||||
const style::textStyle *_textStyle = 0;
|
||||
@@ -61,6 +62,10 @@ const QRegularExpression &reHashtag() {
|
||||
return _reHashtag;
|
||||
}
|
||||
|
||||
const QRegularExpression &reBotCommand() {
|
||||
return _reBotCommand;
|
||||
}
|
||||
|
||||
const style::textStyle *textstyleCurrent() {
|
||||
return _textStyle;
|
||||
}
|
||||
@@ -302,7 +307,10 @@ public:
|
||||
}
|
||||
|
||||
void getLinkData(const QString &original, QString &result, int32 &fullDisplayed) {
|
||||
if (!original.isEmpty() && original.at(0) == '@') {
|
||||
if (!original.isEmpty() && original.at(0) == '/') {
|
||||
result = original;
|
||||
fullDisplayed = -4; // bot command
|
||||
} else if (!original.isEmpty() && original.at(0) == '@') {
|
||||
result = original;
|
||||
fullDisplayed = -3; // mention
|
||||
} else if (!original.isEmpty() && original.at(0) == '#') {
|
||||
@@ -567,7 +575,9 @@ public:
|
||||
_t->_links.resize(lnkIndex);
|
||||
const TextLinkData &data(links[lnkIndex - maxLnkIndex - 1]);
|
||||
TextLinkPtr lnk;
|
||||
if (data.fullDisplayed < -2) { // mention
|
||||
if (data.fullDisplayed < -3) { // bot command
|
||||
lnk = TextLinkPtr(new BotCommandLink(data.url));
|
||||
} else if (data.fullDisplayed < -2) { // mention
|
||||
if (options.flags & TextTwitterMentions) {
|
||||
lnk = TextLinkPtr(new TextLink(qsl("https://twitter.com/") + data.url.mid(1), true));
|
||||
} else if (options.flags & TextInstagramMentions) {
|
||||
@@ -612,7 +622,7 @@ private:
|
||||
TextLinkData(const QString &url = QString(), int32 fullDisplayed = 1) : url(url), fullDisplayed(fullDisplayed) {
|
||||
}
|
||||
QString url;
|
||||
int32 fullDisplayed; // -3 - mention, -2 - hashtag, -1 - email
|
||||
int32 fullDisplayed; // -4 - bot command, -3 - mention, -2 - hashtag, -1 - email
|
||||
};
|
||||
typedef QVector<TextLinkData> TextLinks;
|
||||
TextLinks links;
|
||||
@@ -738,7 +748,15 @@ void TextLink::onClick(Qt::MouseButton button) const {
|
||||
QRegularExpressionMatch telegramMeGroup = QRegularExpression(qsl("^https?://telegram\\.me/joinchat/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"), QRegularExpression::CaseInsensitiveOption).match(url);
|
||||
QRegularExpressionMatch telegramMeStickers = QRegularExpression(qsl("^https?://telegram\\.me/addstickers/([a-zA-Z0-9\\.\\_]+)(\\?|$)"), QRegularExpression::CaseInsensitiveOption).match(url);
|
||||
if (telegramMeUser.hasMatch()) {
|
||||
App::openUserByName(telegramMeUser.captured(1));
|
||||
QString params = url.mid(telegramMeUser.captured(0).size()), start, startToken;
|
||||
if (!params.isEmpty()) {
|
||||
QRegularExpressionMatch startParams = QRegularExpression(qsl("(^|&)(start|startgroup)=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"), QRegularExpression::CaseInsensitiveOption).match(params);
|
||||
if (startParams.hasMatch()) {
|
||||
start = startParams.captured(2);
|
||||
startToken = startParams.captured(3);
|
||||
}
|
||||
}
|
||||
App::openUserByName(telegramMeUser.captured(1), start == qsl("startgroup"), startToken);
|
||||
} else if (telegramMeGroup.hasMatch()) {
|
||||
App::joinGroupByHash(telegramMeGroup.captured(1));
|
||||
} else if (telegramMeStickers.hasMatch()) {
|
||||
@@ -763,6 +781,13 @@ void HashtagLink::onClick(Qt::MouseButton button) const {
|
||||
}
|
||||
}
|
||||
|
||||
void BotCommandLink::onClick(Qt::MouseButton button) const {
|
||||
if (button == Qt::LeftButton || button == Qt::MiddleButton) {
|
||||
App::insertBotCommand(_cmd);
|
||||
// App::sendBotCommand(_cmd);
|
||||
}
|
||||
}
|
||||
|
||||
class TextPainter {
|
||||
public:
|
||||
|
||||
@@ -1088,9 +1113,33 @@ public:
|
||||
if (_yTo >= 0 && _y + _yDelta >= _yTo) return false;
|
||||
if (_y + _yDelta + _fontHeight <= _yFrom) return true;
|
||||
|
||||
uint16 trimmedLineEnd = _lineEnd;
|
||||
for (; trimmedLineEnd > _lineStart; --trimmedLineEnd) {
|
||||
QChar ch = _t->_text.at(trimmedLineEnd - 1);
|
||||
if ((ch != QChar::Space || trimmedLineEnd == _lineStart + 1) && ch != QChar::LineFeed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ITextBlock *_endBlock = (_endBlockIter == _end) ? 0 : (*_endBlockIter);
|
||||
bool elidedLine = _elideLast && _endBlock && (_y + _lineHeight >= _yTo);
|
||||
|
||||
int blockIndex = _lineStartBlock;
|
||||
ITextBlock *currentBlock = _t->_blocks[blockIndex];
|
||||
ITextBlock *nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex] : 0;
|
||||
|
||||
int32 delta = (currentBlock->from() < _lineStart ? qMin(_lineStart - currentBlock->from(), 2) : 0);
|
||||
_localFrom = _lineStart - delta;
|
||||
int32 lineEnd = (_endBlock && _endBlock->from() < trimmedLineEnd && !elidedLine) ? qMin(uint16(trimmedLineEnd + 2), _blockEnd(_t, _endBlockIter, _end)) : trimmedLineEnd;
|
||||
|
||||
QString lineText = _t->_text.mid(_localFrom, lineEnd - _localFrom);
|
||||
int32 lineStart = delta, lineLength = trimmedLineEnd - _lineStart;
|
||||
|
||||
if (elidedLine) {
|
||||
initParagraphBidi();
|
||||
prepareElidedLine(lineText, lineStart, lineLength, _endBlock);
|
||||
}
|
||||
|
||||
QFixed x = _x;
|
||||
if (_align & Qt::AlignHCenter) {
|
||||
x += (_wLeft / 2).toInt();
|
||||
@@ -1138,34 +1187,9 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/* // lpadding is counted to _wLeft
|
||||
for (; _lineStart < _lineEnd; ++_lineStart) {
|
||||
if (_t->_text.at(_lineStart) != QChar::Space) {
|
||||
break;
|
||||
}
|
||||
}/**/
|
||||
for (; _lineEnd > _lineStart; --_lineEnd) {
|
||||
QChar ch = _t->_text.at(_lineEnd - 1);
|
||||
if ((ch != QChar::Space || _lineEnd == _lineStart + 1) && ch != QChar::LineFeed) {
|
||||
break;
|
||||
}
|
||||
}/**/
|
||||
if (_lineEnd == _lineStart && !elidedLine) return true;
|
||||
if (trimmedLineEnd == _lineStart && !elidedLine) return true;
|
||||
|
||||
initParagraphBidi(); // if was not inited
|
||||
|
||||
int blockIndex = _lineStartBlock;
|
||||
ITextBlock *currentBlock = _t->_blocks[blockIndex];
|
||||
ITextBlock *nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex] : 0;
|
||||
|
||||
int32 delta = (currentBlock->from() < _lineStart ? qMin(_lineStart - currentBlock->from(), 2) : 0);
|
||||
_localFrom = _lineStart - delta;
|
||||
int32 lineEnd = (_endBlock && _endBlock->from() < _lineEnd && !elidedLine) ? qMin(uint16(_lineEnd + 2), _blockEnd(_t, _endBlockIter, _end)) : _lineEnd;
|
||||
|
||||
QString lineText = _t->_text.mid(_localFrom, lineEnd - _localFrom);
|
||||
int32 lineStart = delta, lineLength = _lineEnd - _lineStart;
|
||||
|
||||
if (elidedLine) prepareElidedLine(lineText, lineStart, lineLength, _endBlock);
|
||||
if (!elidedLine) initParagraphBidi(); // if was not inited
|
||||
|
||||
_f = _t->_font;
|
||||
QStackTextEngine engine(lineText, _f->f);
|
||||
@@ -1202,7 +1226,7 @@ public:
|
||||
}
|
||||
if (si.analysis.flags == QScriptAnalysis::Object) {
|
||||
if (_type == TextBlockEmoji || _type == TextBlockSkip) {
|
||||
si.width = currentBlock->f_width() + (nextBlock == _endBlock && (!nextBlock || nextBlock->from() >= _lineEnd) ? 0 : currentBlock->f_rpadding());
|
||||
si.width = currentBlock->f_width() + (nextBlock == _endBlock && (!nextBlock || nextBlock->from() >= trimmedLineEnd) ? 0 : currentBlock->f_rpadding());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1250,8 +1274,8 @@ public:
|
||||
*_getSymbolAfter = false;
|
||||
*_getSymbolUpon = false;
|
||||
} else {
|
||||
*_getSymbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart;
|
||||
*_getSymbolAfter = (_lineEnd > _lineStart) ? true : false;
|
||||
*_getSymbol = (trimmedLineEnd > _lineStart) ? (trimmedLineEnd - 1) : _lineStart;
|
||||
*_getSymbolAfter = (trimmedLineEnd > _lineStart) ? true : false;
|
||||
*_getSymbolUpon = false;
|
||||
}
|
||||
return false;
|
||||
@@ -4071,7 +4095,9 @@ bool textSplit(QString &sendingText, QString &leftText, int32 limit) {
|
||||
LinkRanges textParseLinks(const QString &text, int32 flags, bool rich) { // some code is duplicated in flattextarea.cpp!
|
||||
LinkRanges lnkRanges;
|
||||
|
||||
bool withHashtags = (flags & TextParseHashtags), withMentions = (flags & TextParseMentions);
|
||||
bool withHashtags = (flags & TextParseHashtags);
|
||||
bool withMentions = (flags & TextParseMentions);
|
||||
bool withBotCommands = (flags & TextParseBotCommands);
|
||||
|
||||
initLinkSets();
|
||||
int32 len = text.size(), nextCmd = rich ? 0 : len;
|
||||
@@ -4088,6 +4114,7 @@ LinkRanges textParseLinks(const QString &text, int32 flags, bool rich) { // some
|
||||
QRegularExpressionMatch mExplicitDomain = _reExplicitDomain.match(text, matchOffset);
|
||||
QRegularExpressionMatch mHashtag = withHashtags ? _reHashtag.match(text, matchOffset) : QRegularExpressionMatch();
|
||||
QRegularExpressionMatch mMention = withMentions ? _reMention.match(text, qMax(mentionSkip, matchOffset)) : QRegularExpressionMatch();
|
||||
QRegularExpressionMatch mBotCommand = withBotCommands ? _reBotCommand.match(text, matchOffset) : QRegularExpressionMatch();
|
||||
|
||||
LinkRange link;
|
||||
int32 domainOffset = mDomain.hasMatch() ? mDomain.capturedStart() : INT_MAX,
|
||||
@@ -4097,7 +4124,9 @@ LinkRanges textParseLinks(const QString &text, int32 flags, bool rich) { // some
|
||||
hashtagOffset = mHashtag.hasMatch() ? mHashtag.capturedStart() : INT_MAX,
|
||||
hashtagEnd = mHashtag.hasMatch() ? mHashtag.capturedEnd() : INT_MAX,
|
||||
mentionOffset = mMention.hasMatch() ? mMention.capturedStart() : INT_MAX,
|
||||
mentionEnd = mMention.hasMatch() ? mMention.capturedEnd() : INT_MAX;
|
||||
mentionEnd = mMention.hasMatch() ? mMention.capturedEnd() : INT_MAX,
|
||||
botCommandOffset = mBotCommand.hasMatch() ? mBotCommand.capturedStart() : INT_MAX,
|
||||
botCommandEnd = mBotCommand.hasMatch() ? mBotCommand.capturedEnd() : INT_MAX;
|
||||
if (mHashtag.hasMatch()) {
|
||||
if (!mHashtag.capturedRef(1).isEmpty()) {
|
||||
++hashtagOffset;
|
||||
@@ -4127,14 +4156,24 @@ LinkRanges textParseLinks(const QString &text, int32 flags, bool rich) { // some
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!mMention.hasMatch() && !mDomain.hasMatch() && !mExplicitDomain.hasMatch() && !mHashtag.hasMatch()) break;
|
||||
if (mBotCommand.hasMatch()) {
|
||||
if (!mBotCommand.capturedRef(1).isEmpty()) {
|
||||
++botCommandOffset;
|
||||
}
|
||||
if (!mBotCommand.capturedRef(3).isEmpty()) {
|
||||
--botCommandEnd;
|
||||
}
|
||||
}
|
||||
if (!mDomain.hasMatch() && !mExplicitDomain.hasMatch() && !mHashtag.hasMatch() && !mMention.hasMatch() && !mBotCommand.hasMatch()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (explicitDomainOffset < domainOffset) {
|
||||
domainOffset = explicitDomainOffset;
|
||||
domainEnd = explicitDomainEnd;
|
||||
mDomain = mExplicitDomain;
|
||||
}
|
||||
if (mentionOffset < hashtagOffset && mentionOffset < domainOffset) {
|
||||
if (mentionOffset < hashtagOffset && mentionOffset < domainOffset && mentionOffset < botCommandOffset) {
|
||||
if (mentionOffset > nextCmd) {
|
||||
const QChar *after = textSkipCommand(start + nextCmd, start + len);
|
||||
if (after > start + nextCmd && mentionOffset < (after - start)) {
|
||||
@@ -4145,7 +4184,7 @@ LinkRanges textParseLinks(const QString &text, int32 flags, bool rich) { // some
|
||||
|
||||
link.from = start + mentionOffset;
|
||||
link.len = start + mentionEnd - link.from;
|
||||
} else if (hashtagOffset < domainOffset) {
|
||||
} else if (hashtagOffset < domainOffset && hashtagOffset < botCommandOffset) {
|
||||
if (hashtagOffset > nextCmd) {
|
||||
const QChar *after = textSkipCommand(start + nextCmd, start + len);
|
||||
if (after > start + nextCmd && hashtagOffset < (after - start)) {
|
||||
@@ -4156,6 +4195,17 @@ LinkRanges textParseLinks(const QString &text, int32 flags, bool rich) { // some
|
||||
|
||||
link.from = start + hashtagOffset;
|
||||
link.len = start + hashtagEnd - link.from;
|
||||
} else if (botCommandOffset < domainOffset) {
|
||||
if (botCommandOffset > nextCmd) {
|
||||
const QChar *after = textSkipCommand(start + nextCmd, start + len);
|
||||
if (after > start + nextCmd && botCommandOffset < (after - start)) {
|
||||
nextCmd = offset = matchOffset = after - start;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
link.from = start + botCommandOffset;
|
||||
link.len = start + botCommandEnd - link.from;
|
||||
} else {
|
||||
if (domainOffset > nextCmd) {
|
||||
const QChar *after = textSkipCommand(start + nextCmd, start + len);
|
||||
|
||||
@@ -31,11 +31,12 @@ enum {
|
||||
TextParseRichText = 0x004,
|
||||
TextParseMentions = 0x008,
|
||||
TextParseHashtags = 0x010,
|
||||
TextParseBotCommands = 0x020,
|
||||
|
||||
TextTwitterMentions = 0x020,
|
||||
TextTwitterHashtags = 0x040,
|
||||
TextInstagramMentions = 0x080,
|
||||
TextInstagramHashtags = 0x100,
|
||||
TextTwitterMentions = 0x040,
|
||||
TextTwitterHashtags = 0x080,
|
||||
TextInstagramMentions = 0x100,
|
||||
TextInstagramHashtags = 0x200,
|
||||
};
|
||||
|
||||
struct LinkRange {
|
||||
@@ -385,6 +386,32 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class BotCommandLink : public ITextLink {
|
||||
public:
|
||||
|
||||
BotCommandLink(const QString &cmd) : _cmd(cmd) {
|
||||
}
|
||||
|
||||
const QString &text() const {
|
||||
return _cmd;
|
||||
}
|
||||
|
||||
void onClick(Qt::MouseButton button) const;
|
||||
|
||||
const QString &readable() const {
|
||||
return _cmd;
|
||||
}
|
||||
|
||||
QString encoded() const {
|
||||
return _cmd;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
QString _cmd;
|
||||
|
||||
};
|
||||
|
||||
static const QChar TextCommand(0x0010);
|
||||
enum TextCommands {
|
||||
TextCommandBold = 0x01,
|
||||
@@ -408,8 +435,7 @@ struct TextParseOptions {
|
||||
int32 maxh;
|
||||
Qt::LayoutDirection dir;
|
||||
};
|
||||
extern const TextParseOptions _defaultOptions;
|
||||
extern const TextParseOptions _textPlainOptions;
|
||||
extern const TextParseOptions _defaultOptions, _textPlainOptions;
|
||||
|
||||
enum TextSelectType {
|
||||
TextSelectLetters = 0x01,
|
||||
@@ -512,6 +538,7 @@ const QSet<int32> &validTopDomains();
|
||||
const QRegularExpression &reDomain();
|
||||
const QRegularExpression &reMailName();
|
||||
const QRegularExpression &reHashtag();
|
||||
const QRegularExpression &reBotCommand();
|
||||
|
||||
// text style
|
||||
const style::textStyle *textstyleCurrent();
|
||||
|
||||
@@ -56,6 +56,12 @@ public:
|
||||
void drawPixmapLeft(const QPoint &p, int outerw, const QPixmap &pix, const QRect &from) {
|
||||
return drawPixmapLeft(p.x(), p.y(), outerw, pix, from);
|
||||
}
|
||||
void drawPixmapLeft(int x, int y, int w, int h, int outerw, const QPixmap &pix, const QRect &from) {
|
||||
drawPixmap(QRect(rtl() ? (outerw - x - w) : x, y, w, h), pix, from);
|
||||
}
|
||||
void drawPixmapLeft(const QRect &r, int outerw, const QPixmap &pix, const QRect &from) {
|
||||
return drawPixmapLeft(r.x(), r.y(), r.width(), r.height(), outerw, pix, from);
|
||||
}
|
||||
void drawPixmapLeft(int x, int y, int outerw, const QPixmap &pix) {
|
||||
drawPixmap(QPoint(rtl() ? (outerw - x - (pix.width() / pix.devicePixelRatio())) : x, y), pix);
|
||||
}
|
||||
@@ -68,6 +74,12 @@ public:
|
||||
void drawPixmapRight(const QPoint &p, int outerw, const QPixmap &pix, const QRect &from) {
|
||||
return drawPixmapRight(p.x(), p.y(), outerw, pix, from);
|
||||
}
|
||||
void drawPixmapRight(int x, int y, int w, int h, int outerw, const QPixmap &pix, const QRect &from) {
|
||||
drawPixmap(QRect(rtl() ? x : (outerw - x - w), y, w, h), pix, from);
|
||||
}
|
||||
void drawPixmapRight(const QRect &r, int outerw, const QPixmap &pix, const QRect &from) {
|
||||
return drawPixmapRight(r.x(), r.y(), r.width(), r.height(), outerw, pix, from);
|
||||
}
|
||||
void drawPixmapRight(int x, int y, int outerw, const QPixmap &pix) {
|
||||
drawPixmap(QPoint(rtl() ? x : (outerw - x - (pix.width() / pix.devicePixelRatio())), y), pix);
|
||||
}
|
||||
@@ -86,12 +98,24 @@ public:
|
||||
void drawSpriteLeft(const QPoint &p, int outerw, const style::sprite &sprite) {
|
||||
return drawPixmapLeft(p, outerw, App::sprite(), sprite);
|
||||
}
|
||||
void drawSpriteLeft(int x, int y, int w, int h, int outerw, const style::sprite &sprite) {
|
||||
return drawPixmapLeft(x, y, w, h, outerw, App::sprite(), sprite);
|
||||
}
|
||||
void drawSpriteLeft(const QRect &r, int outerw, const style::sprite &sprite) {
|
||||
return drawPixmapLeft(r, outerw, App::sprite(), sprite);
|
||||
}
|
||||
void drawSpriteRight(int x, int y, int outerw, const style::sprite &sprite) {
|
||||
return drawPixmapRight(x, y, outerw, App::sprite(), sprite);
|
||||
}
|
||||
void drawSpriteRight(const QPoint &p, int outerw, const style::sprite &sprite) {
|
||||
return drawPixmapRight(p, outerw, App::sprite(), sprite);
|
||||
}
|
||||
void drawSpriteRight(int x, int y, int w, int h, int outerw, const style::sprite &sprite) {
|
||||
return drawPixmapRight(x, y, w, h, outerw, App::sprite(), sprite);
|
||||
}
|
||||
void drawSpriteRight(const QRect &r, int outerw, const style::sprite &sprite) {
|
||||
return drawPixmapRight(r, outerw, App::sprite(), sprite);
|
||||
}
|
||||
void drawSpriteCenter(const QRect &in, const style::sprite &sprite) {
|
||||
return drawPixmap(QPoint(in.x() + (in.width() - sprite.pxWidth()) / 2, in.y() + (in.height() - sprite.pxHeight()) / 2), App::sprite(), sprite);
|
||||
}
|
||||
|
||||
@@ -41,14 +41,20 @@ TextParseOptions _textDlgOptions = {
|
||||
1, // maxh
|
||||
Qt::LayoutDirectionAuto, // lang-dependent
|
||||
};
|
||||
TextParseOptions _historyTextOptions = {
|
||||
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
TextParseOptions _historyBotOptions = {
|
||||
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands | TextParseMultiline | TextParseRichText, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
|
||||
namespace {
|
||||
TextParseOptions _historyTextOptions = {
|
||||
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
TextParseOptions _historySrvOptions = {
|
||||
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText, // flags
|
||||
0, // maxw
|
||||
@@ -103,6 +109,14 @@ namespace {
|
||||
inline const HistoryForwarded *toHistoryForwarded(const HistoryItem *item) {
|
||||
return item ? item->toHistoryForwarded() : 0;
|
||||
}
|
||||
inline const TextParseOptions &itemTextParseOptions(HistoryItem *item) {
|
||||
History *h = item->history();
|
||||
UserData *f = item->from();
|
||||
if ((!h->peer->chat && h->peer->asUser()->botInfo) || (!f->chat && f->asUser()->botInfo) || (h->peer->chat && h->peer->asChat()->botStatus >= 0)) {
|
||||
return _historyBotOptions;
|
||||
}
|
||||
return _historyTextOptions;
|
||||
}
|
||||
}
|
||||
|
||||
void historyInit() {
|
||||
@@ -149,7 +163,7 @@ void DialogRow::paint(QPainter &p, int32 w, bool act, bool sel) const {
|
||||
rectForName.setLeft(rectForName.left() + st::dlgChatImgSkip);
|
||||
}
|
||||
|
||||
HistoryItem *last = history->last;
|
||||
HistoryItem *last = history->lastMsg;
|
||||
if (!last) {
|
||||
p.setFont(st::dlgHistFont->f);
|
||||
p.setPen((act ? st::dlgActiveColor : st::dlgSystemColor)->p);
|
||||
@@ -290,12 +304,16 @@ History::History(const PeerId &peerId) : width(0), height(0)
|
||||
, peer(App::peer(peerId))
|
||||
, oldLoaded(false)
|
||||
, newLoaded(true)
|
||||
, last(0)
|
||||
, lastMsg(0)
|
||||
, activeMsgId(0)
|
||||
, draftToId(0)
|
||||
, lastWidth(0)
|
||||
, lastScrollTop(History::ScrollMax)
|
||||
, mute(isNotifyMuted(peer->notify))
|
||||
, lastKeyboardInited(false)
|
||||
, lastKeyboardUsed(false)
|
||||
, lastKeyboardId(0)
|
||||
, lastKeyboardFrom(0)
|
||||
, sendRequestId(0)
|
||||
, textCachedFor(0)
|
||||
, lastItemTextCache(st::dlgRichMinWidth)
|
||||
@@ -371,7 +389,7 @@ bool DialogsList::del(const PeerId &peerId, DialogRow *replacedBy) {
|
||||
}
|
||||
|
||||
void DialogsIndexed::peerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars) {
|
||||
if (byName) {
|
||||
if (sortMode == DialogsSortByName) {
|
||||
DialogRow *mainRow = list.adjustByName(peer);
|
||||
if (!mainRow) return;
|
||||
|
||||
@@ -400,7 +418,7 @@ void DialogsIndexed::peerNameChanged(PeerData *peer, const PeerData::Names &oldN
|
||||
for (PeerData::NameFirstChars::const_iterator i = toAdd.cbegin(), e = toAdd.cend(); i != e; ++i) {
|
||||
DialogsIndex::iterator j = index.find(*i);
|
||||
if (j == index.cend()) {
|
||||
j = index.insert(*i, new DialogsList(byName));
|
||||
j = index.insert(*i, new DialogsList(sortMode));
|
||||
}
|
||||
j.value()->addByName(history);
|
||||
}
|
||||
@@ -422,7 +440,7 @@ void DialogsIndexed::peerNameChanged(PeerData *peer, const PeerData::Names &oldN
|
||||
}
|
||||
}
|
||||
for (PeerData::NameFirstChars::const_iterator i = toRemove.cbegin(), e = toRemove.cend(); i != e; ++i) {
|
||||
history->dialogs.remove(*i);
|
||||
if (sortMode == DialogsSortByDate) history->dialogs.remove(*i);
|
||||
DialogsIndex::iterator j = index.find(*i);
|
||||
if (j != index.cend()) {
|
||||
j.value()->del(peer->id, mainRow);
|
||||
@@ -431,9 +449,13 @@ void DialogsIndexed::peerNameChanged(PeerData *peer, const PeerData::Names &oldN
|
||||
for (PeerData::NameFirstChars::const_iterator i = toAdd.cbegin(), e = toAdd.cend(); i != e; ++i) {
|
||||
DialogsIndex::iterator j = index.find(*i);
|
||||
if (j == index.cend()) {
|
||||
j = index.insert(*i, new DialogsList(byName));
|
||||
j = index.insert(*i, new DialogsList(sortMode));
|
||||
}
|
||||
if (sortMode == DialogsSortByDate) {
|
||||
history->dialogs.insert(*i, j.value()->addByPos(history));
|
||||
} else {
|
||||
j.value()->addToEnd(history);
|
||||
}
|
||||
history->dialogs.insert(*i, j.value()->addByPos(history));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -460,6 +482,8 @@ void Histories::regTyping(History *history, UserData *user) {
|
||||
uint64 ms = getms(true);
|
||||
history->typing[user] = ms + 6000;
|
||||
|
||||
user->madeAction();
|
||||
|
||||
TypingHistories::const_iterator i = typing.find(history);
|
||||
if (i == typing.cend()) {
|
||||
typing.insert(history, ms);
|
||||
@@ -527,7 +551,7 @@ HistoryItem *Histories::addToBack(const MTPmessage &msg, int msgState) {
|
||||
if (!h.value()->loadedAtBottom()) {
|
||||
HistoryItem *item = h.value()->addToHistory(msg);
|
||||
if (item) {
|
||||
h.value()->last = item;
|
||||
h.value()->lastMsg = item;
|
||||
if (msgState > 0) {
|
||||
h.value()->newItemAdded(item);
|
||||
}
|
||||
@@ -578,6 +602,9 @@ HistoryItem *History::createItem(HistoryBlock *block, const MTPmessage &msg, boo
|
||||
} else {
|
||||
result = new HistoryMessage(this, block, msg.c_message());
|
||||
}
|
||||
if (msg.c_message().has_reply_markup()) {
|
||||
App::feedReplyMarkup(msgId, msg.c_message().vreply_markup);
|
||||
}
|
||||
break;
|
||||
|
||||
case mtpc_messageService: {
|
||||
@@ -754,7 +781,7 @@ HistoryItem *History::doAddToBack(HistoryBlock *to, bool newBlock, HistoryItem *
|
||||
}
|
||||
}
|
||||
to->push_back(adding);
|
||||
last = adding;
|
||||
lastMsg = adding;
|
||||
adding->y = to->height;
|
||||
if (width) {
|
||||
int32 dh = adding->resize(width);
|
||||
@@ -777,12 +804,33 @@ HistoryItem *History::doAddToBack(HistoryBlock *to, bool newBlock, HistoryItem *
|
||||
}
|
||||
}
|
||||
}
|
||||
if (peer->chat && adding->from()->id) {
|
||||
QList<UserData*> *lastAuthors = &(peer->asChat()->lastAuthors);
|
||||
int prev = lastAuthors->indexOf(adding->from());
|
||||
if (prev > 0) {
|
||||
lastAuthors->removeAt(prev);
|
||||
lastAuthors->push_front(adding->from());
|
||||
if (adding->from()->id) {
|
||||
if (peer->chat) {
|
||||
QList<UserData*> *lastAuthors = &(peer->asChat()->lastAuthors);
|
||||
int prev = lastAuthors->indexOf(adding->from());
|
||||
if (prev > 0) {
|
||||
lastAuthors->removeAt(prev);
|
||||
}
|
||||
if (prev) {
|
||||
lastAuthors->push_front(adding->from());
|
||||
}
|
||||
}
|
||||
if (adding->hasReplyMarkup()) {
|
||||
if (peer->chat) {
|
||||
peer->asChat()->markupSenders.insert(adding->from(), true);
|
||||
}
|
||||
if (App::replyMarkup(adding->id).flags & MTPDreplyKeyboardMarkup_flag_ZERO) { // zero markup means replyKeyboardHide
|
||||
if (lastKeyboardFrom == adding->from()->id || (!lastKeyboardInited && !peer->chat && !adding->out())) {
|
||||
lastKeyboardInited = true;
|
||||
lastKeyboardId = 0;
|
||||
lastKeyboardFrom = 0;
|
||||
}
|
||||
} else {
|
||||
lastKeyboardInited = true;
|
||||
lastKeyboardId = adding->id;
|
||||
lastKeyboardFrom = adding->from()->id;
|
||||
lastKeyboardUsed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return adding;
|
||||
@@ -802,10 +850,7 @@ void History::newItemAdded(HistoryItem *item) {
|
||||
App::checkImageCacheSize();
|
||||
if (item->from()) {
|
||||
unregTyping(item->from());
|
||||
if (item->from()->onlineTill < 0) {
|
||||
item->from()->onlineTill = -unixtime() - HiddenIsOnlineAfterMessage; // pseudo-online
|
||||
if (App::main()) App::main()->peerUpdated(item->from());
|
||||
}
|
||||
item->from()->madeAction();
|
||||
}
|
||||
if (item->out()) {
|
||||
if (unreadBar) unreadBar->destroy();
|
||||
@@ -889,7 +934,43 @@ void History::addToFront(const QVector<MTPMessage> &slice) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastAuthors && item->from()->id && !lastAuthors->contains(item->from())) lastAuthors->push_back(item->from());
|
||||
if (item->from()->id) {
|
||||
if (lastAuthors) { // chats
|
||||
if (!lastAuthors->contains(item->from())) {
|
||||
lastAuthors->push_back(item->from());
|
||||
}
|
||||
if (!lastKeyboardInited && item->hasReplyMarkup() && !item->out()) { // chats with bots
|
||||
bool wasKeyboardHide = peer->asChat()->markupSenders.contains(item->from());
|
||||
if (!wasKeyboardHide) {
|
||||
peer->asChat()->markupSenders.insert(item->from(), true);
|
||||
}
|
||||
if (!(App::replyMarkup(item->id).flags & MTPDreplyKeyboardMarkup_flag_ZERO)) {
|
||||
if (!lastKeyboardInited) {
|
||||
lastKeyboardInited = true;
|
||||
if (wasKeyboardHide) {
|
||||
lastKeyboardId = 0;
|
||||
lastKeyboardFrom = 0;
|
||||
} else {
|
||||
lastKeyboardId = item->id;
|
||||
lastKeyboardFrom = item->from()->id;
|
||||
lastKeyboardUsed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!lastKeyboardInited && item->hasReplyMarkup() && !item->out()) { // conversations with bots
|
||||
lastKeyboardInited = true;
|
||||
if (App::replyMarkup(item->id).flags & MTPDreplyKeyboardMarkup_flag_ZERO) {
|
||||
lastKeyboardId = 0;
|
||||
lastKeyboardFrom = 0;
|
||||
} else {
|
||||
lastKeyboardInited = true;
|
||||
lastKeyboardId = item->id;
|
||||
lastKeyboardFrom = item->from()->id;
|
||||
lastKeyboardUsed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (App::wnd()) App::wnd()->mediaOverviewUpdated(peer);
|
||||
}
|
||||
@@ -1138,9 +1219,9 @@ void History::fixLastMessage(bool wasAtBottom) {
|
||||
wasAtBottom = false;
|
||||
}
|
||||
if (wasAtBottom) {
|
||||
last = back()->back();
|
||||
lastMsg = back()->back();
|
||||
} else {
|
||||
last = 0;
|
||||
lastMsg = 0;
|
||||
if (App::main()) {
|
||||
App::main()->checkPeerHistory(peer);
|
||||
}
|
||||
@@ -1156,12 +1237,12 @@ void History::loadAround(MsgId msgId) {
|
||||
if (!item || !item->block()) {
|
||||
clear(true);
|
||||
}
|
||||
newLoaded = last && !last->detached();
|
||||
newLoaded = lastMsg && !lastMsg->detached();
|
||||
} else {
|
||||
if (!loadedAtBottom()) {
|
||||
clear(true);
|
||||
}
|
||||
newLoaded = isEmpty() || (last && !last->detached());
|
||||
newLoaded = isEmpty() || (lastMsg && !lastMsg->detached());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1245,13 +1326,19 @@ void History::clear(bool leaveItems) {
|
||||
}
|
||||
Parent::clear();
|
||||
setMsgCount(0);
|
||||
if (!leaveItems) {
|
||||
if (leaveItems) {
|
||||
lastKeyboardInited = false;
|
||||
} else {
|
||||
setUnreadCount(0);
|
||||
last = 0;
|
||||
lastMsg = 0;
|
||||
}
|
||||
height = 0;
|
||||
oldLoaded = false;
|
||||
if (peer->chat) peer->asChat()->lastAuthors.clear();
|
||||
if (peer->chat) {
|
||||
peer->asChat()->lastAuthors.clear();
|
||||
peer->asChat()->markupSenders.clear();
|
||||
}
|
||||
if (leaveItems && App::main()) App::main()->historyCleared(this);
|
||||
}
|
||||
|
||||
History::Parent::iterator History::erase(History::Parent::iterator i) {
|
||||
@@ -1473,7 +1560,7 @@ void HistoryItem::destroy() {
|
||||
bool wasAtBottom = history()->loadedAtBottom();
|
||||
_history->removeNotification(this);
|
||||
detach();
|
||||
if (history()->last == this) {
|
||||
if (history()->lastMsg == this) {
|
||||
history()->fixLastMessage(wasAtBottom);
|
||||
}
|
||||
HistoryMedia *m = getMedia(true);
|
||||
@@ -1549,7 +1636,7 @@ HistoryPhoto::HistoryPhoto(const MTPDphoto &photo, const QString &caption, Histo
|
||||
, _caption(st::minPhotoSize)
|
||||
, openl(new PhotoLink(data)) {
|
||||
if (!caption.isEmpty()) {
|
||||
_caption.setText(st::msgFont, caption + textcmdSkipBlock(parent->timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()), _historyTextOptions);
|
||||
_caption.setText(st::msgFont, caption + textcmdSkipBlock(parent->timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()), itemTextParseOptions(parent));
|
||||
}
|
||||
init();
|
||||
}
|
||||
@@ -1807,14 +1894,7 @@ void HistoryPhoto::draw(QPainter &p, const HistoryItem *parent, bool selected, i
|
||||
height -= st::webPagePhotoSkip + _caption.countHeight(width);
|
||||
}
|
||||
} else {
|
||||
QPixmap **cors = App::corners(selected ? InSelectedShadowCorners : InShadowCorners);
|
||||
int32 cw = cors[0]->width() / cIntRetinaFactor(), ch = cors[0]->height() / cIntRetinaFactor();
|
||||
style::color shadow(selected ? st::msgInSelectShadow : st::msgInShadow);
|
||||
p.fillRect(cw, _height, width - 2 * cw, st::msgShadow, shadow->b);
|
||||
p.fillRect(0, _height - ch, cw, st::msgShadow, shadow->b);
|
||||
p.fillRect(width - cw, _height - ch, cw, st::msgShadow, shadow->b);
|
||||
p.drawPixmap(0, _height - ch + st::msgShadow, *cors[2]);
|
||||
p.drawPixmap(width - cw, _height - ch + st::msgShadow, *cors[3]);
|
||||
App::roundShadow(p, 0, 0, width, _height, selected ? st::msgInSelectShadow : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners);
|
||||
}
|
||||
data->full->load(false, false);
|
||||
bool full = data->full->loaded();
|
||||
@@ -1956,7 +2036,7 @@ HistoryVideo::HistoryVideo(const MTPDvideo &video, const QString &caption, Histo
|
||||
, _uplDone(0)
|
||||
{
|
||||
if (!caption.isEmpty()) {
|
||||
_caption.setText(st::msgFont, caption + textcmdSkipBlock(parent->timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()), _historyTextOptions);
|
||||
_caption.setText(st::msgFont, caption + textcmdSkipBlock(parent->timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()), itemTextParseOptions(parent));
|
||||
}
|
||||
|
||||
_size = formatDurationAndSizeText(data->duration, data->size);
|
||||
@@ -2358,8 +2438,7 @@ void HistoryAudio::draw(QPainter &p, const HistoryItem *parent, bool selected, i
|
||||
width = _maxw;
|
||||
}
|
||||
|
||||
bool mp3 = (data->mime == QLatin1String("audio/mp3"));
|
||||
if (!data->loader && !mp3 && data->status != FileFailed && !already && !hasdata && data->size < AudioVoiceMsgInMemory) {
|
||||
if (!data->loader && data->status != FileFailed && !already && !hasdata && data->size < AudioVoiceMsgInMemory) {
|
||||
data->save(QString());
|
||||
}
|
||||
|
||||
@@ -2399,18 +2478,47 @@ void HistoryAudio::draw(QPainter &p, const HistoryItem *parent, bool selected, i
|
||||
}
|
||||
|
||||
AudioData *playing = 0;
|
||||
VoiceMessageState playingState = VoiceMessageStopped;
|
||||
AudioPlayerState playingState = AudioPlayerStopped;
|
||||
int64 playingPosition = 0, playingDuration = 0;
|
||||
if (!mp3 && audioVoice()) {
|
||||
audioVoice()->currentState(&playing, &playingState, &playingPosition, &playingDuration);
|
||||
int32 playingFrequency = 0;
|
||||
if (audioPlayer()) {
|
||||
audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency);
|
||||
}
|
||||
|
||||
QRect img;
|
||||
if (!mp3 && (already || hasdata)) {
|
||||
bool showPause = (playing == data) && (playingState == VoiceMessagePlaying || playingState == VoiceMessageResuming || playingState == VoiceMessageStarting);
|
||||
QString statusText;
|
||||
if (data->status == FileFailed) {
|
||||
statusText = lang(lng_attach_failed);
|
||||
img = out ? st::mediaAudioOutImg : st::mediaAudioInImg;
|
||||
} else if (data->status == FileUploading) {
|
||||
if (_uplTextCache.isEmpty() || _uplDone != data->uploadOffset) {
|
||||
_uplDone = data->uploadOffset;
|
||||
_uplTextCache = formatDownloadText(_uplDone, data->size);
|
||||
}
|
||||
statusText = _uplTextCache;
|
||||
img = out ? st::mediaAudioOutImg : st::mediaAudioInImg;
|
||||
} else if (already || hasdata) {
|
||||
bool showPause = false;
|
||||
if (playing == data && playingState != AudioPlayerStopped && playingState != AudioPlayerStoppedAtStart) {
|
||||
statusText = formatDurationText(playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)) + qsl(" / ") + formatDurationText(playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency));
|
||||
showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting);
|
||||
} else {
|
||||
statusText = formatDurationText(data->duration);
|
||||
}
|
||||
img = out ? (showPause ? st::mediaPauseOutImg : st::mediaPlayOutImg) : (showPause ? st::mediaPauseInImg : st::mediaPlayInImg);
|
||||
} else {
|
||||
if (data->loader) {
|
||||
if (_dldTextCache.isEmpty() || _dldDone != data->loader->currentOffset()) {
|
||||
_dldDone = data->loader->currentOffset();
|
||||
_dldTextCache = formatDownloadText(_dldDone, data->size);
|
||||
}
|
||||
statusText = _dldTextCache;
|
||||
} else {
|
||||
statusText = _size;
|
||||
}
|
||||
img = out ? st::mediaAudioOutImg : st::mediaAudioInImg;
|
||||
}
|
||||
|
||||
p.drawPixmap(QPoint(st::mediaPadding.left(), skipy + st::mediaPadding.top()), App::sprite(), img);
|
||||
if (selected) {
|
||||
App::roundRect(p, st::mediaPadding.left(), skipy + st::mediaPadding.top(), st::mediaThumbSize, st::mediaThumbSize, textstyleCurrent()->selectOverlay, SelectedOverlayCorners);
|
||||
@@ -2425,37 +2533,8 @@ void HistoryAudio::draw(QPainter &p, const HistoryItem *parent, bool selected, i
|
||||
p.setPen(st::black->c);
|
||||
p.drawText(tleft, skipy + st::mediaPadding.top() + st::mediaNameTop + st::mediaFont->ascent, lang(lng_media_audio));
|
||||
|
||||
QString statusText;
|
||||
|
||||
style::color status(selected ? (out ? st::mediaOutSelectColor : st::mediaInSelectColor) : (out ? st::mediaOutColor : st::mediaInColor));
|
||||
p.setPen(status->p);
|
||||
if (!mp3 && (already || hasdata)) {
|
||||
if (playing == data && playingState != VoiceMessageStopped) {
|
||||
statusText = formatDurationText(playingPosition / AudioVoiceMsgFrequency) + qsl(" / ") + formatDurationText(playingDuration / AudioVoiceMsgFrequency);
|
||||
} else {
|
||||
statusText = formatDurationText(data->duration);
|
||||
}
|
||||
} else {
|
||||
if (data->loader) {
|
||||
if (_dldTextCache.isEmpty() || _dldDone != data->loader->currentOffset()) {
|
||||
_dldDone = data->loader->currentOffset();
|
||||
_dldTextCache = formatDownloadText(_dldDone, data->size);
|
||||
}
|
||||
statusText = _dldTextCache;
|
||||
} else {
|
||||
if (data->status == FileFailed) {
|
||||
statusText = lang(lng_attach_failed);
|
||||
} else if (data->status == FileUploading) {
|
||||
if (_uplTextCache.isEmpty() || _uplDone != data->uploadOffset) {
|
||||
_uplDone = data->uploadOffset;
|
||||
_uplTextCache = formatDownloadText(_uplDone, data->size);
|
||||
}
|
||||
statusText = _uplTextCache;
|
||||
} else {
|
||||
statusText = _size;
|
||||
}
|
||||
}
|
||||
}
|
||||
int32 texty = skipy + st::mediaPadding.top() + st::mediaThumbSize - st::mediaDetailsShift - st::mediaFont->height;
|
||||
p.drawText(tleft, texty + st::mediaFont->ascent, statusText);
|
||||
if (parent->isMediaUnread()) {
|
||||
@@ -2498,6 +2577,15 @@ void HistoryAudio::unregItem(HistoryItem *item) {
|
||||
App::unregAudioItem(data, item);
|
||||
}
|
||||
|
||||
void HistoryAudio::updateFrom(const MTPMessageMedia &media) {
|
||||
if (media.type() == mtpc_messageMediaAudio) {
|
||||
App::feedAudio(media.c_messageMediaAudio().vaudio, data);
|
||||
if (!data->data.isEmpty()) {
|
||||
Local::writeAudio(mediaKey(mtpToLocationType(mtpc_inputAudioFileLocation), data->dc, data->id), data->data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const QString HistoryAudio::inDialogsText() const {
|
||||
return lang(lng_in_dlg_audio);
|
||||
}
|
||||
@@ -2616,8 +2704,8 @@ HistoryDocument::HistoryDocument(DocumentData *document) : HistoryMedia()
|
||||
|
||||
void HistoryDocument::initDimensions(const HistoryItem *parent) {
|
||||
if (parent == animated.msg) {
|
||||
_maxw = animated.w;
|
||||
_minh = animated.h;
|
||||
_maxw = animated.w / cIntRetinaFactor();
|
||||
_minh = animated.h / cIntRetinaFactor();
|
||||
} else {
|
||||
_maxw = st::mediaMaxWidth;
|
||||
int32 tleft = st::mediaPadding.left() + st::mediaThumbSize + st::mediaPadding.right();
|
||||
@@ -2650,23 +2738,16 @@ void HistoryDocument::draw(QPainter &p, const HistoryItem *parent, bool selected
|
||||
|
||||
bool out = parent->out(), hovered, pressed;
|
||||
if (parent == animated.msg) {
|
||||
int32 pw = animated.w, ph = animated.h;
|
||||
int32 pw = animated.w / cIntRetinaFactor(), ph = animated.h / cIntRetinaFactor();
|
||||
if (width < pw) {
|
||||
pw = width;
|
||||
ph = (pw == w) ? _height : (pw * animated.h / animated.w);
|
||||
if (ph < 1) ph = 1;
|
||||
}
|
||||
|
||||
QPixmap **cors = App::corners(selected ? InSelectedShadowCorners : InShadowCorners);
|
||||
int32 cw = cors[0]->width() / cIntRetinaFactor(), ch = cors[0]->height() / cIntRetinaFactor();
|
||||
style::color shadow(selected ? st::msgInSelectShadow : st::msgInShadow);
|
||||
p.fillRect(cw, ph, pw - 2 * cw, st::msgShadow, shadow->b);
|
||||
p.fillRect(0, ph - ch, cw, st::msgShadow, shadow->b);
|
||||
p.fillRect(pw - cw, ph - ch, cw, st::msgShadow, shadow->b);
|
||||
p.drawPixmap(0, ph - ch + st::msgShadow, *cors[2]);
|
||||
p.drawPixmap(pw - cw, ph - ch + st::msgShadow, *cors[3]);
|
||||
App::roundShadow(p, 0, 0, pw, ph, selected ? st::msgInSelectShadow : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners);
|
||||
|
||||
p.drawPixmap(0, 0, animated.current(pw, ph, true));
|
||||
p.drawPixmap(0, 0, animated.current(pw * cIntRetinaFactor(), ph * cIntRetinaFactor(), true));
|
||||
if (selected) {
|
||||
App::roundRect(p, 0, 0, pw, ph, textstyleCurrent()->selectOverlay, SelectedOverlayCorners);
|
||||
}
|
||||
@@ -2757,24 +2838,22 @@ void HistoryDocument::draw(QPainter &p, const HistoryItem *parent, bool selected
|
||||
style::color status(selected ? (out ? st::mediaOutSelectColor : st::mediaInSelectColor) : (out ? st::mediaOutColor : st::mediaInColor));
|
||||
p.setPen(status->p);
|
||||
|
||||
if (data->loader) {
|
||||
if (data->status == FileFailed) {
|
||||
statusText = lang(lng_attach_failed);
|
||||
} else if (data->status == FileUploading) {
|
||||
if (_uplTextCache.isEmpty() || _uplDone != data->uploadOffset) {
|
||||
_uplDone = data->uploadOffset;
|
||||
_uplTextCache = formatDownloadText(_uplDone, data->size);
|
||||
}
|
||||
statusText = _uplTextCache;
|
||||
} else if (data->loader) {
|
||||
if (_dldTextCache.isEmpty() || _dldDone != data->loader->currentOffset()) {
|
||||
_dldDone = data->loader->currentOffset();
|
||||
_dldTextCache = formatDownloadText(_dldDone, data->size);
|
||||
}
|
||||
statusText = _dldTextCache;
|
||||
} else {
|
||||
if (data->status == FileFailed) {
|
||||
statusText = lang(lng_attach_failed);
|
||||
} else if (data->status == FileUploading) {
|
||||
if (_uplTextCache.isEmpty() || _uplDone != data->uploadOffset) {
|
||||
_uplDone = data->uploadOffset;
|
||||
_uplTextCache = formatDownloadText(_uplDone, data->size);
|
||||
}
|
||||
statusText = _uplTextCache;
|
||||
} else {
|
||||
statusText = _size;
|
||||
}
|
||||
statusText = _size;
|
||||
}
|
||||
p.drawText(tleft, skipy + st::mediaPadding.top() + st::mediaThumbSize - st::mediaDetailsShift - st::mediaFont->descent, statusText);
|
||||
|
||||
@@ -2820,9 +2899,9 @@ int32 HistoryDocument::resize(int32 width, bool dontRecountText, const HistoryIt
|
||||
if (w > st::maxMediaSize) {
|
||||
w = st::maxMediaSize;
|
||||
}
|
||||
_height = animated.h;
|
||||
if (animated.w > w) {
|
||||
_height = (w * _height / animated.w);
|
||||
_height = animated.h / cIntRetinaFactor();
|
||||
if (animated.w / cIntRetinaFactor() > w) {
|
||||
_height = (w * _height / (animated.w / cIntRetinaFactor()));
|
||||
if (_height <= 0) _height = 1;
|
||||
}
|
||||
} else {
|
||||
@@ -2985,7 +3064,7 @@ void HistorySticker::initDimensions(const HistoryItem *parent) {
|
||||
_maxw = qMax(pixw, int16(st::minPhotoSize));
|
||||
_minh = qMax(pixh, int16(st::minPhotoSize));
|
||||
if (const HistoryReply *reply = toHistoryReply(parent)) {
|
||||
_maxw += reply->replyToWidth();
|
||||
_maxw += st::msgReplyPadding.left() + reply->replyToWidth();
|
||||
}
|
||||
_height = _minh;
|
||||
w = qMin(lastw, _maxw);
|
||||
@@ -2999,7 +3078,7 @@ void HistorySticker::draw(QPainter &p, const HistoryItem *parent, bool selected,
|
||||
int32 usew = _maxw, usex = 0;
|
||||
const HistoryReply *reply = toHistoryReply(parent);
|
||||
if (reply) {
|
||||
usew -= reply->replyToWidth();
|
||||
usew -= st::msgReplyPadding.left() + reply->replyToWidth();
|
||||
if (parent->out()) {
|
||||
usex = width - usew;
|
||||
}
|
||||
@@ -3062,8 +3141,8 @@ void HistorySticker::draw(QPainter &p, const HistoryItem *parent, bool selected,
|
||||
}
|
||||
|
||||
if (reply) {
|
||||
int32 rw = width - usew, rh = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom();
|
||||
int32 rx = parent->out() ? 0 : usew, ry = _height - rh;
|
||||
int32 rw = width - usew - st::msgReplyPadding.left(), rh = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom();
|
||||
int32 rx = parent->out() ? 0 : (usew + st::msgReplyPadding.left()), ry = _height - rh;
|
||||
|
||||
App::roundRect(p, rx, ry, rw, rh, selected ? App::msgServiceSelectBg() : App::msgServiceBg(), selected ? ServiceSelectedCorners : ServiceCorners);
|
||||
|
||||
@@ -3088,7 +3167,9 @@ void HistorySticker::unregItem(HistoryItem *item) {
|
||||
void HistorySticker::updateFrom(const MTPMessageMedia &media) {
|
||||
if (media.type() == mtpc_messageMediaDocument) {
|
||||
App::feedDocument(media.c_messageMediaDocument().vdocument, data);
|
||||
if (App::main()) App::main()->incrementSticker(data);
|
||||
if (!data->data.isEmpty()) {
|
||||
Local::writeStickerImage(mediaKey(mtpToLocationType(mtpc_inputDocumentFileLocation), data->dc, data->id), data->data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3422,11 +3503,13 @@ void HistoryWebPage::initDimensions(const HistoryItem *parent) {
|
||||
}
|
||||
QString title(data->title.isEmpty() ? data->author : data->title);
|
||||
if (!title.isEmpty()) {
|
||||
_title.setText(st::webPageTitleFont, textClean(title), _webpageTitleOptions);
|
||||
title = textClean(title);
|
||||
if (!_asArticle && !data->photo && data->description.isEmpty()) title += textcmdSkipBlock(parent->timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y());
|
||||
_title.setText(st::webPageTitleFont, title, _webpageTitleOptions);
|
||||
if (_asArticle) {
|
||||
_maxw = qMax(_maxw, int32(st::webPageLeft + _title.maxWidth() + st::webPagePhotoDelta + st::webPagePhotoSize));
|
||||
} else {
|
||||
_maxw = qMax(_maxw, int32(st::webPageLeft + _title.maxWidth() + (data->photo ? parent->timeWidth(true) : 0)));
|
||||
_maxw = qMax(_maxw, int32(st::webPageLeft + _title.maxWidth()));
|
||||
_minh += qMin(_title.minHeight(), 2 * st::webPageTitleFont->height);
|
||||
}
|
||||
}
|
||||
@@ -4305,14 +4388,7 @@ void HistoryImageLink::draw(QPainter &p, const HistoryItem *parent, bool selecte
|
||||
}
|
||||
height -= skipy + st::mediaPadding.bottom();
|
||||
} else {
|
||||
QPixmap **cors = App::corners(selected ? InSelectedShadowCorners : InShadowCorners);
|
||||
int32 cw = cors[0]->width() / cIntRetinaFactor(), ch = cors[0]->height() / cIntRetinaFactor();
|
||||
style::color shadow(selected ? st::msgInSelectShadow : st::msgInShadow);
|
||||
p.fillRect(cw, _height, width - 2 * cw, st::msgShadow, shadow->b);
|
||||
p.fillRect(0, _height - ch, cw, st::msgShadow, shadow->b);
|
||||
p.fillRect(width - cw, _height - ch, cw, st::msgShadow, shadow->b);
|
||||
p.drawPixmap(0, _height - ch + st::msgShadow, *cors[2]);
|
||||
p.drawPixmap(width - cw, _height - ch + st::msgShadow, *cors[3]);
|
||||
App::roundShadow(p, 0, 0, width, _height, selected ? st::msgInSelectShadow : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners);
|
||||
}
|
||||
|
||||
data->load();
|
||||
@@ -4669,9 +4745,9 @@ void HistoryMessage::initMediaFromDocument(DocumentData *doc) {
|
||||
void HistoryMessage::initDimensions(const QString &text) {
|
||||
if (!_media || !text.isEmpty()) { // !justMedia()
|
||||
if (_media && _media->isDisplayed()) {
|
||||
_text.setText(st::msgFont, text, _historyTextOptions);
|
||||
_text.setText(st::msgFont, text, itemTextParseOptions(this));
|
||||
} else {
|
||||
_text.setText(st::msgFont, text + textcmdSkipBlock(timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()), _historyTextOptions);
|
||||
_text.setText(st::msgFont, text + textcmdSkipBlock(timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()), itemTextParseOptions(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4690,14 +4766,14 @@ void HistoryMessage::initDimensions(const HistoryItem *parent) {
|
||||
if (_media->isDisplayed() && _text.hasSkipBlock()) {
|
||||
QString was = HistoryMessage::selectedText(FullItemSel);
|
||||
if (!was.isEmpty()) {
|
||||
_text.setText(st::msgFont, was, _historyTextOptions); // without date skip
|
||||
_text.setText(st::msgFont, was, itemTextParseOptions(this)); // without date skip
|
||||
_textWidth = 0;
|
||||
_textHeight = 0;
|
||||
}
|
||||
} else if (!_media->isDisplayed() && !_text.hasSkipBlock()) {
|
||||
QString was = HistoryMessage::selectedText(FullItemSel);
|
||||
if (!was.isEmpty()) {
|
||||
_text.setText(st::msgFont, was + textcmdSkipBlock(timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()), _historyTextOptions); // without date skip
|
||||
_text.setText(st::msgFont, was + textcmdSkipBlock(timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()), itemTextParseOptions(this)); // without date skip
|
||||
_textWidth = 0;
|
||||
_textHeight = 0;
|
||||
}
|
||||
@@ -4754,14 +4830,14 @@ void HistoryMessage::setMedia(const MTPmessageMedia &media) {
|
||||
if (_media && _media->isDisplayed() && !mediaWasDisplayed) {
|
||||
QString was = HistoryMessage::selectedText(FullItemSel);
|
||||
if (!was.isEmpty()) {
|
||||
_text.setText(st::msgFont, was, _historyTextOptions); // without date skip
|
||||
_text.setText(st::msgFont, was, itemTextParseOptions(this)); // without date skip
|
||||
_textWidth = 0;
|
||||
_textHeight = 0;
|
||||
}
|
||||
} else if (mediaWasDisplayed && (!_media || !_media->isDisplayed())) {
|
||||
QString was = HistoryMessage::selectedText(FullItemSel);
|
||||
if (!was.isEmpty()) {
|
||||
_text.setText(st::msgFont, was + textcmdSkipBlock(timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()), _historyTextOptions); // without date skip
|
||||
_text.setText(st::msgFont, was + textcmdSkipBlock(timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()), itemTextParseOptions(this)); // without date skip
|
||||
_textWidth = 0;
|
||||
_textHeight = 0;
|
||||
}
|
||||
@@ -5070,6 +5146,9 @@ HistoryMessage::~HistoryMessage() {
|
||||
_media->unregItem(this);
|
||||
delete _media;
|
||||
}
|
||||
if (_flags & MTPDmessage::flag_reply_markup) {
|
||||
App::clearReplyMarkup(id);
|
||||
}
|
||||
}
|
||||
|
||||
HistoryForwarded::HistoryForwarded(History *history, HistoryBlock *block, const MTPDmessage &msg) : HistoryMessage(history, block, msg.vid.v, msg.vflags.v, ::date(msg.vdate), msg.vfrom_id.v, textClean(qs(msg.vmessage)), msg.vmedia)
|
||||
|
||||
@@ -30,11 +30,10 @@ static const uint32 FullItemSel = 0xFFFFFFFF;
|
||||
|
||||
typedef QMap<int32, HistoryItem*> SelectedItemSet;
|
||||
|
||||
extern TextParseOptions _textNameOptions, _textDlgOptions;
|
||||
extern TextParseOptions _textNameOptions, _textDlgOptions, _historyTextOptions, _historyBotOptions;
|
||||
|
||||
#include "structs.h"
|
||||
|
||||
|
||||
struct History;
|
||||
struct Histories : public QHash<PeerId, History*>, public Animated {
|
||||
typedef QHash<PeerId, History*> Parent;
|
||||
@@ -199,7 +198,7 @@ struct History : public QList<HistoryBlock*> {
|
||||
|
||||
PeerData *peer;
|
||||
bool oldLoaded, newLoaded;
|
||||
HistoryItem *last;
|
||||
HistoryItem *lastMsg;
|
||||
MsgId activeMsgId;
|
||||
|
||||
typedef QList<HistoryItem*> NotifyQueue;
|
||||
@@ -239,8 +238,8 @@ struct History : public QList<HistoryBlock*> {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (last == old) {
|
||||
last = item;
|
||||
if (lastMsg == old) {
|
||||
lastMsg = item;
|
||||
}
|
||||
// showFrom can't be detached
|
||||
}
|
||||
@@ -252,6 +251,10 @@ struct History : public QList<HistoryBlock*> {
|
||||
int32 lastWidth, lastScrollTop;
|
||||
bool mute;
|
||||
|
||||
bool lastKeyboardInited, lastKeyboardUsed;
|
||||
MsgId lastKeyboardId;
|
||||
PeerId lastKeyboardFrom;
|
||||
|
||||
mtpRequestId sendRequestId;
|
||||
|
||||
// for dialog drawing
|
||||
@@ -285,8 +288,14 @@ struct History : public QList<HistoryBlock*> {
|
||||
static const int32 ScrollMax = INT_MAX;
|
||||
};
|
||||
|
||||
enum DialogsSortMode {
|
||||
DialogsSortByDate,
|
||||
DialogsSortByName,
|
||||
DialogsSortByAdd
|
||||
};
|
||||
|
||||
struct DialogsList {
|
||||
DialogsList(bool sortByName) : begin(&last), end(&last), byName(sortByName), count(0), current(&last) {
|
||||
DialogsList(DialogsSortMode sortMode) : begin(&last), end(&last), sortMode(sortMode), count(0), current(&last) {
|
||||
}
|
||||
|
||||
void adjustCurrent(int32 y, int32 h) const {
|
||||
@@ -324,10 +333,10 @@ struct DialogsList {
|
||||
end->pos++;
|
||||
if (begin == end) {
|
||||
begin = current = result;
|
||||
if (!byName && updatePos) history->posInDialogs = 0;
|
||||
if (sortMode == DialogsSortByDate && updatePos) history->posInDialogs = 0;
|
||||
} else {
|
||||
end->prev->next = result;
|
||||
if (!byName && updatePos) history->posInDialogs = end->prev->history->posInDialogs + 1;
|
||||
if (sortMode == DialogsSortByDate && updatePos) history->posInDialogs = end->prev->history->posInDialogs + 1;
|
||||
}
|
||||
rowByPeer.insert(history->peer->id, result);
|
||||
++count;
|
||||
@@ -335,7 +344,7 @@ struct DialogsList {
|
||||
}
|
||||
|
||||
void bringToTop(DialogRow *row, bool updatePos = true) {
|
||||
if (!byName && updatePos && row != begin) {
|
||||
if (sortMode == DialogsSortByDate && updatePos && row != begin) {
|
||||
row->history->posInDialogs = begin->history->posInDialogs - 1;
|
||||
}
|
||||
insertBefore(row, begin);
|
||||
@@ -390,7 +399,7 @@ struct DialogsList {
|
||||
}
|
||||
|
||||
DialogRow *adjustByName(const PeerData *peer) {
|
||||
if (!byName) return 0;
|
||||
if (sortMode != DialogsSortByName) return 0;
|
||||
|
||||
RowByPeer::iterator i = rowByPeer.find(peer->id);
|
||||
if (i == rowByPeer.cend()) return 0;
|
||||
@@ -409,7 +418,7 @@ struct DialogsList {
|
||||
}
|
||||
|
||||
DialogRow *addByName(History *history) {
|
||||
if (!byName) return 0;
|
||||
if (sortMode != DialogsSortByName) return 0;
|
||||
|
||||
DialogRow *row = addToEnd(history), *change = row;
|
||||
const QString &peerName(history->peer->name);
|
||||
@@ -426,7 +435,7 @@ struct DialogsList {
|
||||
}
|
||||
|
||||
void adjustByPos(DialogRow *row) {
|
||||
if (byName) return;
|
||||
if (sortMode != DialogsSortByDate) return;
|
||||
|
||||
DialogRow *change = row;
|
||||
while (change->prev && change->prev->history->posInDialogs > row->history->posInDialogs) {
|
||||
@@ -441,7 +450,7 @@ struct DialogsList {
|
||||
}
|
||||
|
||||
DialogRow *addByPos(History *history) {
|
||||
if (byName) return 0;
|
||||
if (sortMode != DialogsSortByDate) return 0;
|
||||
|
||||
DialogRow *row = addToEnd(history, false);
|
||||
adjustByPos(row);
|
||||
@@ -476,7 +485,7 @@ struct DialogsList {
|
||||
|
||||
DialogRow last;
|
||||
DialogRow *begin, *end;
|
||||
bool byName;
|
||||
DialogsSortMode sortMode;
|
||||
int32 count;
|
||||
|
||||
typedef QHash<PeerId, DialogRow*> RowByPeer;
|
||||
@@ -486,7 +495,7 @@ struct DialogsList {
|
||||
};
|
||||
|
||||
struct DialogsIndexed {
|
||||
DialogsIndexed(bool sortByName) : byName(sortByName), list(byName) {
|
||||
DialogsIndexed(DialogsSortMode sortMode) : sortMode(sortMode), list(sortMode) {
|
||||
}
|
||||
|
||||
History::DialogLinks addToEnd(History *history) {
|
||||
@@ -500,7 +509,7 @@ struct DialogsIndexed {
|
||||
for (PeerData::NameFirstChars::const_iterator i = history->peer->chars.cbegin(), e = history->peer->chars.cend(); i != e; ++i) {
|
||||
DialogsIndex::iterator j = index.find(*i);
|
||||
if (j == index.cend()) {
|
||||
j = index.insert(*i, new DialogsList(byName));
|
||||
j = index.insert(*i, new DialogsList(sortMode));
|
||||
}
|
||||
result.insert(*i, j.value()->addToEnd(history));
|
||||
}
|
||||
@@ -518,7 +527,7 @@ struct DialogsIndexed {
|
||||
for (PeerData::NameFirstChars::const_iterator i = history->peer->chars.cbegin(), e = history->peer->chars.cend(); i != e; ++i) {
|
||||
DialogsIndex::iterator j = index.find(*i);
|
||||
if (j == index.cend()) {
|
||||
j = index.insert(*i, new DialogsList(byName));
|
||||
j = index.insert(*i, new DialogsList(sortMode));
|
||||
}
|
||||
j.value()->addByName(history);
|
||||
}
|
||||
@@ -557,7 +566,7 @@ struct DialogsIndexed {
|
||||
|
||||
void clear();
|
||||
|
||||
bool byName;
|
||||
DialogsSortMode sortMode;
|
||||
DialogsList list;
|
||||
typedef QMap<QChar, DialogsList*> DialogsIndex;
|
||||
DialogsIndex index;
|
||||
@@ -679,6 +688,9 @@ public:
|
||||
void markMediaRead() {
|
||||
_flags &= ~MTPDmessage_flag_media_unread;
|
||||
}
|
||||
bool hasReplyMarkup() const {
|
||||
return _flags & MTPDmessage::flag_reply_markup;
|
||||
}
|
||||
virtual bool needCheck() const {
|
||||
return true;
|
||||
}
|
||||
@@ -910,6 +922,7 @@ private:
|
||||
};
|
||||
|
||||
QString formatSizeText(qint64 size);
|
||||
QString formatDurationText(qint64 duration);
|
||||
|
||||
class HistoryVideo : public HistoryMedia {
|
||||
public:
|
||||
@@ -971,9 +984,15 @@ public:
|
||||
}
|
||||
HistoryMedia *clone() const;
|
||||
|
||||
AudioData *audio() {
|
||||
return data;
|
||||
}
|
||||
|
||||
void regItem(HistoryItem *item);
|
||||
void unregItem(HistoryItem *item);
|
||||
|
||||
void updateFrom(const MTPMessageMedia &media);
|
||||
|
||||
private:
|
||||
AudioData *data;
|
||||
TextLinkPtr _openl, _savel, _cancell;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -79,6 +79,8 @@ public:
|
||||
void itemRemoved(HistoryItem *item);
|
||||
void itemReplaced(HistoryItem *oldItem, HistoryItem *newItem);
|
||||
|
||||
void updateBotInfo(bool recount = true);
|
||||
|
||||
~HistoryList();
|
||||
|
||||
public slots:
|
||||
@@ -116,6 +118,12 @@ private:
|
||||
void applyDragSelection();
|
||||
|
||||
History *hist;
|
||||
|
||||
int32 ySkip;
|
||||
BotInfo *botInfo;
|
||||
int32 botDescWidth, botDescHeight;
|
||||
QRect botDescRect;
|
||||
|
||||
HistoryWidget *historyWidget;
|
||||
ScrollArea *scrollArea;
|
||||
int32 currentBlock, currentItem;
|
||||
@@ -172,6 +180,7 @@ public:
|
||||
void insertFromMimeData(const QMimeData *source);
|
||||
|
||||
void focusInEvent(QFocusEvent *e);
|
||||
void setMaxHeight(int32 maxHeight);
|
||||
|
||||
public slots:
|
||||
|
||||
@@ -185,6 +194,72 @@ signals:
|
||||
|
||||
private:
|
||||
HistoryWidget *history;
|
||||
int32 _maxHeight;
|
||||
|
||||
};
|
||||
|
||||
class BotKeyboard : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
BotKeyboard();
|
||||
|
||||
void paintEvent(QPaintEvent *e);
|
||||
void resizeEvent(QResizeEvent *e);
|
||||
void mousePressEvent(QMouseEvent *e);
|
||||
void mouseMoveEvent(QMouseEvent *e);
|
||||
void mouseReleaseEvent(QMouseEvent *e);
|
||||
void leaveEvent(QEvent *e);
|
||||
|
||||
bool updateMarkup(HistoryItem *last);
|
||||
bool hasMarkup() const;
|
||||
|
||||
bool hoverStep(float64 ms);
|
||||
void resizeToWidth(int32 width, int32 maxOuterHeight);
|
||||
|
||||
bool maximizeSize() const;
|
||||
bool singleUse() const;
|
||||
|
||||
MsgId forMsgId() const {
|
||||
return _wasForMsgId;
|
||||
}
|
||||
|
||||
public slots:
|
||||
|
||||
void showCommandTip();
|
||||
void updateSelected();
|
||||
|
||||
private:
|
||||
|
||||
void updateStyle(int32 w = -1);
|
||||
void clearSelection();
|
||||
|
||||
MsgId _wasForMsgId;
|
||||
int32 _height, _maxOuterHeight;
|
||||
bool _maximizeSize, _singleUse;
|
||||
QTimer _cmdTipTimer;
|
||||
|
||||
QPoint _lastMousePos;
|
||||
struct Button {
|
||||
Button(const QString &str = QString()) : cmd(str), text(1), cwidth(0), hover(0), full(true) {
|
||||
}
|
||||
QRect rect;
|
||||
QString cmd;
|
||||
Text text;
|
||||
int32 cwidth;
|
||||
float64 hover;
|
||||
bool full;
|
||||
};
|
||||
int32 _sel, _down;
|
||||
QList<QList<Button> > _btns;
|
||||
|
||||
typedef QMap<int32, uint64> Animations;
|
||||
Animations _animations;
|
||||
Animation _hoverAnim;
|
||||
|
||||
const style::botKeyboardButton *_st;
|
||||
|
||||
};
|
||||
|
||||
class HistoryHider : public QWidget, public Animated {
|
||||
@@ -250,7 +325,7 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class HistoryWidget : public QWidget, public RPCSender, public Animated {
|
||||
class HistoryWidget : public TWidget, public RPCSender {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
@@ -273,6 +348,8 @@ public:
|
||||
void leaveEvent(QEvent *e);
|
||||
void dropEvent(QDropEvent *e);
|
||||
void mouseReleaseEvent(QMouseEvent *e);
|
||||
void mouseMoveEvent(QMouseEvent *e);
|
||||
void leaveToChildEvent(QEvent *e);
|
||||
void contextMenuEvent(QContextMenuEvent *e);
|
||||
|
||||
void updateTopBarSelection();
|
||||
@@ -292,11 +369,11 @@ public:
|
||||
void newUnreadMsg(History *history, HistoryItem *item);
|
||||
void historyToDown(History *history);
|
||||
void historyWasRead(bool force = true);
|
||||
void historyCleared(History *history);
|
||||
|
||||
QRect historyRect() const;
|
||||
|
||||
void updateTyping(bool typing = true);
|
||||
// void updateStickerPan();
|
||||
void updateRecentStickers();
|
||||
void stickersInstalled(uint64 setId);
|
||||
void typingDone(const MTPBool &result, mtpRequestId req);
|
||||
@@ -317,7 +394,6 @@ public:
|
||||
void updateOnlineDisplay(int32 x, int32 w);
|
||||
void updateOnlineDisplayTimer();
|
||||
|
||||
// mtpRequestId onForward(const PeerId &peer, SelectedItemSet toForward);
|
||||
void onShareContact(const PeerId &peer, UserData *contact);
|
||||
void onSendPaths(const PeerId &peer);
|
||||
|
||||
@@ -330,7 +406,7 @@ public:
|
||||
int32 lastScrollTop() const;
|
||||
|
||||
void animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTopBarCache, bool back = false);
|
||||
bool animStep(float64 ms);
|
||||
bool showStep(float64 ms);
|
||||
void animStop();
|
||||
|
||||
QPoint clampMousePosition(QPoint point);
|
||||
@@ -367,6 +443,15 @@ public:
|
||||
void updatePreview();
|
||||
void previewCancel();
|
||||
|
||||
bool recordStep(float64 ms);
|
||||
bool recordingStep(float64 ms);
|
||||
void stopRecording(bool send);
|
||||
|
||||
void onListEscapePressed();
|
||||
|
||||
void sendBotCommand(const QString &cmd, MsgId replyTo);
|
||||
void insertBotCommand(const QString &cmd);
|
||||
|
||||
~HistoryWidget();
|
||||
|
||||
signals:
|
||||
@@ -387,27 +472,33 @@ public slots:
|
||||
void onPreviewTimeout();
|
||||
|
||||
void peerUpdated(PeerData *data);
|
||||
void onPeerLoaded(PeerData *data);
|
||||
void onFullPeerUpdated(PeerData *data);
|
||||
|
||||
void cancelTyping();
|
||||
|
||||
void onPhotoUploaded(MsgId msgId, const MTPInputFile &file);
|
||||
void onDocumentUploaded(MsgId msgId, const MTPInputFile &file);
|
||||
void onThumbDocumentUploaded(MsgId msgId, const MTPInputFile &file, const MTPInputFile &thumb);
|
||||
void onAudioUploaded(MsgId msgId, const MTPInputFile &file);
|
||||
|
||||
void onDocumentProgress(MsgId msgId);
|
||||
void onAudioProgress(MsgId msgId);
|
||||
|
||||
void onDocumentFailed(MsgId msgId);
|
||||
void onAudioFailed(MsgId msgId);
|
||||
|
||||
void onListScroll();
|
||||
void onHistoryToEnd();
|
||||
void onSend(bool ctrlShiftEnter = false, MsgId replyTo = -1);
|
||||
void onBotStart();
|
||||
|
||||
void onPhotoSelect();
|
||||
void onDocumentSelect();
|
||||
void onPhotoDrop(QDropEvent *e);
|
||||
void onDocumentDrop(QDropEvent *e);
|
||||
|
||||
void onKbToggle(bool manual = true);
|
||||
|
||||
void onPhotoReady();
|
||||
void onSendConfirmed();
|
||||
void onSendCancelled();
|
||||
@@ -443,6 +534,10 @@ public slots:
|
||||
|
||||
void updateStickers();
|
||||
|
||||
void onRecordError();
|
||||
void onRecordDone(QByteArray result, qint32 samples);
|
||||
void onRecordUpdate(qint16 level, qint32 samples);
|
||||
|
||||
private:
|
||||
|
||||
MsgId _replyToId;
|
||||
@@ -451,7 +546,11 @@ private:
|
||||
int32 _replyToNameVersion;
|
||||
IconedButton _replyForwardPreviewCancel;
|
||||
void updateReplyToName();
|
||||
void drawFieldBackground(QPainter &p);
|
||||
|
||||
void drawField(Painter &p);
|
||||
void drawRecordButton(Painter &p);
|
||||
void drawRecording(Painter &p);
|
||||
void updateField();
|
||||
|
||||
QString _previewLinks;
|
||||
WebPageData *_previewData;
|
||||
@@ -473,6 +572,8 @@ private:
|
||||
void addMessagesToFront(const QVector<MTPMessage> &messages);
|
||||
void addMessagesToBack(const QVector<MTPMessage> &messages);
|
||||
|
||||
void updateBotKeyboard();
|
||||
|
||||
void stickersGot(const MTPmessages_AllStickers &stickers);
|
||||
bool stickersFailed(const RPCError &error);
|
||||
|
||||
@@ -500,19 +601,30 @@ private:
|
||||
ScrollArea _scroll;
|
||||
HistoryList *_list;
|
||||
History *hist;
|
||||
bool _histInited; // initial updateListSize() called
|
||||
bool _histInited, _histNeedUpdate; // initial updateListSize() called
|
||||
|
||||
IconedButton _toHistoryEnd;
|
||||
|
||||
MentionsDropdown _attachMention;
|
||||
|
||||
FlatButton _send;
|
||||
IconedButton _attachDocument, _attachPhoto, _attachEmoji;
|
||||
FlatButton _send, _botStart;
|
||||
IconedButton _attachDocument, _attachPhoto, _attachEmoji, _kbShow, _kbHide;
|
||||
MessageField _field;
|
||||
Animation _recordAnim, _recordingAnim;
|
||||
bool _recording, _inRecord, _inField;
|
||||
anim::ivalue a_recordingLevel;
|
||||
int32 _recordingSamples;
|
||||
anim::fvalue a_recordOver, a_recordDown;
|
||||
anim::cvalue a_recordCancel;
|
||||
int32 _recordCancelWidth;
|
||||
|
||||
bool _kbShown, _kbWasHidden;
|
||||
HistoryItem *_kbReplyTo;
|
||||
ScrollArea _kbScroll;
|
||||
BotKeyboard _keyboard;
|
||||
|
||||
Dropdown _attachType;
|
||||
EmojiPan _emojiPan;
|
||||
// StickerPan _stickerPan;
|
||||
DragState _attachDrag;
|
||||
DragArea _attachDragDocument, _attachDragPhoto;
|
||||
|
||||
@@ -532,6 +644,7 @@ private:
|
||||
|
||||
bool hiderOffered;
|
||||
|
||||
Animation _showAnim;
|
||||
QPixmap _animCache, _bgAnimCache, _animTopBarCache, _bgAnimTopBarCache;
|
||||
anim::ivalue a_coord, a_bgCoord;
|
||||
anim::fvalue a_alpha, a_bgAlpha;
|
||||
|
||||
@@ -202,7 +202,7 @@ void IntroCode::codeSubmitDone(const MTPauth_Authorization &result) {
|
||||
stopCheck();
|
||||
code.setDisabled(false);
|
||||
const MTPDauth_authorization &d(result.c_auth_authorization());
|
||||
if (d.vuser.type() != mtpc_userSelf) { // wtf?
|
||||
if (d.vuser.type() != mtpc_user || !(d.vuser.c_user().vflags.v & MTPDuser_flag_self)) { // wtf?
|
||||
showError(lang(lng_server_error));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ void IntroPwdCheck::pwdSubmitDone(bool recover, const MTPauth_Authorization &res
|
||||
_pwdField.setDisabled(false);
|
||||
_codeField.setDisabled(false);
|
||||
const MTPDauth_authorization &d(result.c_auth_authorization());
|
||||
if (d.vuser.type() != mtpc_userSelf) { // wtf?
|
||||
if (d.vuser.type() != mtpc_user || !(d.vuser.c_user().vflags.v & MTPDuser_flag_self)) { // wtf?
|
||||
showError(lang(lng_server_error));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ void IntroSignup::nameSubmitDone(const MTPauth_Authorization &result) {
|
||||
first.setDisabled(false);
|
||||
last.setDisabled(false);
|
||||
const MTPDauth_authorization &d(result.c_auth_authorization());
|
||||
if (d.vuser.type() != mtpc_userSelf) { // wtf?
|
||||
if (d.vuser.type() != mtpc_user || !(d.vuser.c_user().vflags.v & MTPDuser_flag_self)) { // wtf?
|
||||
showError(lang(lng_server_error));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -69,6 +69,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
"lng_reconnecting_try_now" = "Jetzt versuchen";
|
||||
|
||||
"lng_status_service_notifications" = "Servicemeldungen";
|
||||
"lng_status_bot" = "Bot";
|
||||
"lng_status_bot_reads_all" = "hat Zugriff auf Nachrichten";
|
||||
"lng_status_bot_not_reads_all" = "hat keinen Zugriff auf Nachrichten";
|
||||
"lng_status_offline" = "vor langer Zeit gesehen";
|
||||
"lng_status_recently" = "kürzlich gesehen";
|
||||
"lng_status_last_week" = "innerhalb einer Woche gesehen";
|
||||
@@ -333,7 +336,10 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_profile_chat_unaccessible" = "Gruppe nicht verfügbar";
|
||||
"lng_topbar_info" = "Info";
|
||||
"lng_profile_about_section" = "Info";
|
||||
"lng_profile_settings_section" = "Einstellungen";
|
||||
"lng_profile_bot_settings" = "Einstellungen";
|
||||
"lng_profile_bot_help" = "Hilfe";
|
||||
"lng_profile_participants_section" = "Teilnehmer";
|
||||
"lng_profile_info" = "Kontaktinfo";
|
||||
"lng_profile_group_info" = "Gruppeninfo";
|
||||
@@ -343,6 +349,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
"lng_profile_clear_history" = "Chatverlauf löschen";
|
||||
"lng_profile_send_message" = "Nachricht senden";
|
||||
"lng_profile_share_contact" = "Kontakt teilen";
|
||||
"lng_profile_invite_to_group" = "In eine Gruppe einladen";
|
||||
"lng_profile_delete_contact" = "Löschen";
|
||||
"lng_profile_set_group_photo" = "Bild festlegen";
|
||||
"lng_profile_add_participant" = "Neues Mitglied";
|
||||
@@ -450,10 +457,19 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_send_button" = "Senden";
|
||||
"lng_message_ph" = "Schreibe deine Nachricht..";
|
||||
"lng_record_cancel" = "Zum Abbrechen rausbewegen";
|
||||
"lng_empty_history" = "";
|
||||
"lng_willbe_history" = "Wähle einen Chat aus, um zu schreiben";
|
||||
"lng_message_with_from" = "[c]{from}:[/c] {message}";
|
||||
"lng_from_you" = "Ich";
|
||||
"lng_bot_description" = "Was kann dieser Bot?";
|
||||
|
||||
"lng_bot_start" = "Starten";
|
||||
"lng_bot_choose_group" = "Gruppe auswählen";
|
||||
"lng_bot_no_groups" = "Du hast keine Gruppen";
|
||||
"lng_bot_groups_not_found" = "Keine Gruppen gefunden";
|
||||
"lng_bot_sure_invite" = "Den Bot zu «{group}» hinzufügen?";
|
||||
"lng_bot_already_in_group" = "Der Bot ist schon in dieser Gruppe.";
|
||||
|
||||
"lng_typing" = "tippt";
|
||||
"lng_user_typing" = "{user} tippt";
|
||||
@@ -584,7 +600,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_new_version_wrap" = "Telegram Desktop wurde aktualisiert auf Version {version}\n\n{changes}\n\nGesamter Versionsverlauf:\n{link}";
|
||||
"lng_new_version_minor" = "— Fehlerbehebungen und Softwareoptimierungen";
|
||||
"lng_new_version_text" = "— Installiere und teile benutzerdefinierte Sticker-Pakete\n— Neue Emoji und ein Panel für Sticker";
|
||||
"lng_new_version_text" = "Diese neue Version enthält Unterstützung für die neue Bot API, welche für alle kostenlos verfügbar ist. Kannst du programmieren? Erstelle deine eigenen Bots für Spiele, Dienste oder Integrationen.\n\nMehr dazu unter {blog_link}";
|
||||
|
||||
"lng_menu_insert_unicode" = "Unicode-Steuerzeichen einfügen";
|
||||
|
||||
|
||||
@@ -69,6 +69,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
"lng_reconnecting_try_now" = "Intentar ahora";
|
||||
|
||||
"lng_status_service_notifications" = "servicio de notificaciones";
|
||||
"lng_status_bot" = "bot";
|
||||
"lng_status_bot_reads_all" = "tiene acceso a los mensajes";
|
||||
"lng_status_bot_not_reads_all" = "no tiene acceso a los mensajes";
|
||||
"lng_status_offline" = "última vez hace mucho tiempo";
|
||||
"lng_status_recently" = "última vez recientemente";
|
||||
"lng_status_last_week" = "última vez hace unos días";
|
||||
@@ -333,7 +336,10 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_profile_chat_unaccessible" = "El grupo es inaccesible";
|
||||
"lng_topbar_info" = "Información";
|
||||
"lng_profile_about_section" = "Acerca de";
|
||||
"lng_profile_settings_section" = "Ajustes";
|
||||
"lng_profile_bot_settings" = "Ajustes";
|
||||
"lng_profile_bot_help" = "Ayuda";
|
||||
"lng_profile_participants_section" = "Miembros";
|
||||
"lng_profile_info" = "Información";
|
||||
"lng_profile_group_info" = "Información";
|
||||
@@ -343,6 +349,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
"lng_profile_clear_history" = "Borrar historial";
|
||||
"lng_profile_send_message" = "Enviar mensaje";
|
||||
"lng_profile_share_contact" = "Compartir contacto";
|
||||
"lng_profile_invite_to_group" = "Añadir al grupo";
|
||||
"lng_profile_delete_contact" = "Eliminar";
|
||||
"lng_profile_set_group_photo" = "Poner foto";
|
||||
"lng_profile_add_participant" = "Añadir miembro";
|
||||
@@ -396,7 +403,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
"lng_group_invite_link" = "Invitación";
|
||||
"lng_group_invite_create" = "Crear un enlace de invitación";
|
||||
"lng_group_invite_about" = "Los usuarios de Telegram podrán unirse\na tu grupo a través de este enlace.";
|
||||
"lng_group_invite_create_new" = "Anular enlace de invitación";
|
||||
"lng_group_invite_create_new" = "Anular enlace";
|
||||
"lng_group_invite_about_new" = "El enlace previo será desactivado\ny generaremos uno nuevo para ti.";
|
||||
"lng_group_invite_copied" = "Enlace de invitación copiado al portapapeles.";
|
||||
"lng_group_invite_no_room" = "No puedes unirte a este grupo porque\nhay muchos miembros en él.";
|
||||
@@ -420,13 +427,13 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
"lng_media_video" = "Vídeo";
|
||||
"lng_media_audio" = "Mensaje de voz";
|
||||
|
||||
"lng_emoji_category0" = "Usados frecuentemente";
|
||||
"lng_emoji_category1" = "Gente";
|
||||
"lng_emoji_category0" = "Usados con frecuencia";
|
||||
"lng_emoji_category1" = "Personas";
|
||||
"lng_emoji_category2" = "Naturaleza";
|
||||
"lng_emoji_category3" = "Comida y bebidas";
|
||||
"lng_emoji_category3" = "Comida y bebida";
|
||||
"lng_emoji_category4" = "Celebración";
|
||||
"lng_emoji_category5" = "Actividad";
|
||||
"lng_emoji_category6" = "Viajes y lugares";
|
||||
"lng_emoji_category6" = "Viajes y destinos";
|
||||
"lng_emoji_category7" = "Objetos y símbolos";
|
||||
|
||||
"lng_switch_stickers" = "Stickers";
|
||||
@@ -450,10 +457,19 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_send_button" = "Enviar";
|
||||
"lng_message_ph" = "Escribir un mensaje...";
|
||||
"lng_record_cancel" = "Suelta fuera de aquí para cancelar";
|
||||
"lng_empty_history" = "";
|
||||
"lng_willbe_history" = "Por favor, elige un chat para comenzar a conversar";
|
||||
"lng_message_with_from" = "[c]{from}:[/c] {message}";
|
||||
"lng_from_you" = "Tú";
|
||||
"lng_bot_description" = "¿Qué puede hacer este bot?";
|
||||
|
||||
"lng_bot_start" = "Empezar";
|
||||
"lng_bot_choose_group" = "Elegir grupo";
|
||||
"lng_bot_no_groups" = "No tienes grupos";
|
||||
"lng_bot_groups_not_found" = "No se encontraron grupos";
|
||||
"lng_bot_sure_invite" = "¿Añadir el bot a «{group}»?";
|
||||
"lng_bot_already_in_group" = "El bot ya es un miembro del grupo.";
|
||||
|
||||
"lng_typing" = "escribiendo";
|
||||
"lng_user_typing" = "{user} está escribiendo";
|
||||
@@ -584,7 +600,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_new_version_wrap" = "Telegram Desktop fue actualizada a la versión {version}\n\n{changes}\n\nEl historial completo está disponible aquí:\n{link}";
|
||||
"lng_new_version_minor" = "— Corrección de errores y otras mejoras menores";
|
||||
"lng_new_version_text" = "— Añadido el soporte para packs de stickers\n— Nuevas pestañas de emojis y stickers";
|
||||
"lng_new_version_text" = "Esta nueva versión incluye el soporte para bots, usando la nueva API para bots, gratis para todos. Si eres un ingeniero, crea tus propios bots para juegos, servicios o integraciones .\n\nConoce más en {blog_link}";
|
||||
|
||||
"lng_menu_insert_unicode" = "Insertar caracteres de control Unicode";
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
"lng_open_from_tray" = "Apri Telegram";
|
||||
"lng_minimize_to_tray" = "Minimizza";
|
||||
"lng_quit_from_tray" = "Chiudi Telegram";
|
||||
"lng_tray_icon_text" = "Telegram è ancora aperto qui,\npuoi cambiare questo nelle impostazioni.\n\nSe l'icona scompare dalla barra tray,\npuoi riportarla indietro dalle icone nascoste.";
|
||||
"lng_tray_icon_text" = "Telegram è ancora aperto qui,\npuoi cambiare questo nelle impostazioni.\n\nSe l'icona scompare dall'area di notifica,\npuoi riportarla indietro dalle icone nascoste.";
|
||||
|
||||
"lng_month1" = "Gennaio";
|
||||
"lng_month2" = "Febbraio";
|
||||
@@ -69,6 +69,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
"lng_reconnecting_try_now" = "Prova ora";
|
||||
|
||||
"lng_status_service_notifications" = "notifiche di servizio";
|
||||
"lng_status_bot" = "bot";
|
||||
"lng_status_bot_reads_all" = "ha accesso ai messaggi";
|
||||
"lng_status_bot_not_reads_all" = "non ha accesso ai messaggi";
|
||||
"lng_status_offline" = "ultimo accesso molto tempo fa";
|
||||
"lng_status_recently" = "ultimo accesso di recente";
|
||||
"lng_status_last_week" = "ultimo accesso entro una settimana";
|
||||
@@ -196,10 +199,10 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
"lng_settings_update_ready" = "La nuova versione è pronta";
|
||||
"lng_settings_update_now" = "Riavvia ora";
|
||||
"lng_settings_update_fail" = "Ricerca aggiornamenti fallita :(";
|
||||
"lng_settings_workmode_tray" = "Mostra icona nella barra tray";
|
||||
"lng_settings_workmode_tray" = "Mostra icona nell'area di notifica";
|
||||
"lng_settings_workmode_window" = "Mostra icona nella barra applicazioni";
|
||||
"lng_settings_auto_start" = "Avvia Telegram all'avvio del sistema";
|
||||
"lng_settings_start_min" = "Avvia minimizzato";
|
||||
"lng_settings_start_min" = "Avvia ridotto a icona";
|
||||
"lng_settings_add_sendto" = "Inserisci Telegram nel menu «Invia a»";
|
||||
"lng_settings_scale_label" = "Ridimensiona interfaccia";
|
||||
"lng_settings_scale_auto" = "Auto ({cur})";
|
||||
@@ -333,7 +336,10 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_profile_chat_unaccessible" = "Gruppo non accessibile";
|
||||
"lng_topbar_info" = "Info";
|
||||
"lng_profile_about_section" = "Info";
|
||||
"lng_profile_settings_section" = "Impostazioni";
|
||||
"lng_profile_bot_settings" = "Impostazioni";
|
||||
"lng_profile_bot_help" = "Aiuto";
|
||||
"lng_profile_participants_section" = "Membri";
|
||||
"lng_profile_info" = "Info contatto";
|
||||
"lng_profile_group_info" = "Nome gruppo";
|
||||
@@ -343,12 +349,13 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
"lng_profile_clear_history" = "Cancella la cronologia";
|
||||
"lng_profile_send_message" = "Invia messaggio";
|
||||
"lng_profile_share_contact" = "Condividi contatto";
|
||||
"lng_profile_invite_to_group" = "Aggiungi a un gruppo";
|
||||
"lng_profile_delete_contact" = "Elimina";
|
||||
"lng_profile_set_group_photo" = "Imposta foto";
|
||||
"lng_profile_add_participant" = "Aggiungi membro";
|
||||
"lng_profile_delete_and_exit" = "Esci";
|
||||
"lng_profile_kick" = "Rimuovi";
|
||||
"lng_profile_sure_kick" = "Espellere {user} dal gruppo?";
|
||||
"lng_profile_sure_kick" = "Rimuovere {user} dal gruppo?";
|
||||
"lng_profile_loading" = "Caricamento..";
|
||||
"lng_profile_shared_media" = "Media condivisi";
|
||||
"lng_profile_no_media" = "Nessun media in questa chat.";
|
||||
@@ -383,7 +390,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
"lng_action_user_left" = "{from} ha lasciato il gruppo";
|
||||
"lng_action_user_joined" = "{from} si è unito al gruppo";
|
||||
"lng_action_user_joined_by_link" = "{from} si è unito al gruppo tramite link di invito";
|
||||
"lng_action_user_registered" = "{from} ha iniziato a usare Telegram";
|
||||
"lng_action_user_registered" = "{from} si è unito a Telegram";
|
||||
"lng_action_removed_photo" = "{from} ha rimosso la foto del gruppo";
|
||||
"lng_action_changed_photo" = "{from} ha cambiato la foto del gruppo";
|
||||
"lng_action_changed_title" = "{from} ha cambiato il nome del gruppo in «{title}»";
|
||||
@@ -434,7 +441,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_custom_stickers" = "Sticker personalizzati";
|
||||
"lng_stickers_remove_pack" = "Rimuovere «{sticker_pack}»?";
|
||||
"lng_stickers_add_pack" = "Aggiungi {count:_not_used_|# sticker|# stickers}";
|
||||
"lng_stickers_add_pack" = "Aggiungi {count:_not_used_|# sticker|# sticker}";
|
||||
"lng_stickers_share_pack" = "Condividi sticker";
|
||||
"lng_stickers_not_found" = "Pacchetto di sticker non trovato.";
|
||||
"lng_stickers_copied" = "Link degli sticker copiato negli appunti.";
|
||||
@@ -450,10 +457,19 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_send_button" = "Invia";
|
||||
"lng_message_ph" = "Scrivi un messaggio..";
|
||||
"lng_record_cancel" = "Rilascia fuori da qui per annullare";
|
||||
"lng_empty_history" = "";
|
||||
"lng_willbe_history" = "Seleziona una chat per iniziare a messaggiare";
|
||||
"lng_message_with_from" = "[c]{from}:[/c] {message}";
|
||||
"lng_from_you" = "Tu";
|
||||
"lng_bot_description" = "Cosa può fare questo bot?";
|
||||
|
||||
"lng_bot_start" = "Avvia";
|
||||
"lng_bot_choose_group" = "Scegli gruppo";
|
||||
"lng_bot_no_groups" = "Non hai gruppi";
|
||||
"lng_bot_groups_not_found" = "Nessun gruppo trovato";
|
||||
"lng_bot_sure_invite" = "Aggiungere il bot a «{group}»?";
|
||||
"lng_bot_already_in_group" = "Questo bot è già membro del gruppo.";
|
||||
|
||||
"lng_typing" = "sta scrivendo";
|
||||
"lng_user_typing" = "{user} sta scrivendo";
|
||||
@@ -491,7 +507,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
"lng_context_save_video" = "Salva video come..";
|
||||
"lng_context_open_audio" = "Apri audio";
|
||||
"lng_context_save_audio" = "Salva audio come..";
|
||||
"lng_context_pack_info" = "Info pacchetto";
|
||||
"lng_context_pack_info" = "Mostra sticker";
|
||||
"lng_context_open_file" = "Apri file";
|
||||
"lng_context_save_file" = "Salva file come..";
|
||||
"lng_context_forward_file" = "Inoltra file";
|
||||
@@ -584,7 +600,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_new_version_wrap" = "Telegram Desktop si è aggiornato alla versione {version}\n\n{changes}\n\nLa cronologia degli update è disponibile qui:\n{link}";
|
||||
"lng_new_version_minor" = "— Bug fix e altri miglioramenti minori";
|
||||
"lng_new_version_text" = "— Aggiunto supporto ai pacchetti di sticker\n— Nuove emoji e pannello sticker";
|
||||
"lng_new_version_text" = "Questa nuova versione include il support per i bot usando la nuova API per i bot. Se sei un ingegnere, crea i tuoi bot per giochi, servizi o integrazioni. Scopri di più su {blog_link}";
|
||||
|
||||
"lng_menu_insert_unicode" = "Inserisci carattere di controllo Unicode";
|
||||
|
||||
|
||||
@@ -69,6 +69,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
"lng_reconnecting_try_now" = "다시 시도";
|
||||
|
||||
"lng_status_service_notifications" = "서비스 알림";
|
||||
"lng_status_bot" = "bot";
|
||||
"lng_status_bot_reads_all" = "메시지 접근 권한이 있습니다.";
|
||||
"lng_status_bot_not_reads_all" = "메시지 접근 권한이 없습니다.";
|
||||
"lng_status_offline" = "마지막으로 접속한 지 오래됨";
|
||||
"lng_status_recently" = "최근에 접속";
|
||||
"lng_status_last_week" = "일주일 이내 마지막으로 접속";
|
||||
@@ -333,7 +336,10 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_profile_chat_unaccessible" = "그룹에 접근할 수 없습니다.";
|
||||
"lng_topbar_info" = "정보";
|
||||
"lng_profile_about_section" = "취소";
|
||||
"lng_profile_settings_section" = "환경설정";
|
||||
"lng_profile_bot_settings" = "환경설정";
|
||||
"lng_profile_bot_help" = "도움말";
|
||||
"lng_profile_participants_section" = "사용자";
|
||||
"lng_profile_info" = "연락처 정보";
|
||||
"lng_profile_group_info" = "그룹 정보";
|
||||
@@ -343,6 +349,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
"lng_profile_clear_history" = "히스토리 초기화";
|
||||
"lng_profile_send_message" = "메세지 전송";
|
||||
"lng_profile_share_contact" = "연락처 공유";
|
||||
"lng_profile_invite_to_group" = "그룹에 추가";
|
||||
"lng_profile_delete_contact" = "삭제";
|
||||
"lng_profile_set_group_photo" = "사진 설정";
|
||||
"lng_profile_add_participant" = "대화상대 추가";
|
||||
@@ -450,10 +457,19 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_send_button" = "보내기";
|
||||
"lng_message_ph" = "메시지 쓰기";
|
||||
"lng_record_cancel" = "Release outside this field to cancel";
|
||||
"lng_empty_history" = "";
|
||||
"lng_willbe_history" = "대화하실 방을 선택해주세요.";
|
||||
"lng_message_with_from" = "[c]{from}:[/c] {message}";
|
||||
"lng_from_you" = "회원님";
|
||||
"lng_bot_description" = "봇이 할 수 있는 일은 무엇일까요?";
|
||||
|
||||
"lng_bot_start" = "Start";
|
||||
"lng_bot_choose_group" = "그룹 선택";
|
||||
"lng_bot_no_groups" = "그룹이 존재하지 않습니다.";
|
||||
"lng_bot_groups_not_found" = "그룹을 찾을 수 없습니다.";
|
||||
"lng_bot_sure_invite" = "<<{group}>>에 봇을 추가 하시겠습니까?";
|
||||
"lng_bot_already_in_group" = "봇이 이미 그룹의 멤버입니다.";
|
||||
|
||||
"lng_typing" = "입력중";
|
||||
"lng_user_typing" = "{user}님이 입력중입니다.";
|
||||
@@ -584,7 +600,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_new_version_wrap" = "텔레그램 데스크탑은 {version} 버전으로 업데이트 되었습니다.\n\n{changes}\n\n전체 버전 히스토리는 아래에서 확인 가능합니다:\n{link}";
|
||||
"lng_new_version_minor" = "— 버그 수정 및 일부 기능 향상";
|
||||
"lng_new_version_text" = "— 스티커 팩 기능\n— 신규 이모티콘 및 스티커 패널";
|
||||
"lng_new_version_text" = "This new version includes support for bots using the new bot API, free for everyone. If you're an engineer, create your own bots for games, services or integrations.\n\nLearn more at {blog_link}";
|
||||
|
||||
"lng_menu_insert_unicode" = "유니코드 문자를 입력하세요.";
|
||||
|
||||
|
||||
@@ -69,6 +69,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
"lng_reconnecting_try_now" = "Probeer nu";
|
||||
|
||||
"lng_status_service_notifications" = "servicemeldingen";
|
||||
"lng_status_bot" = "bot";
|
||||
"lng_status_bot_reads_all" = "toegang tot berichten";
|
||||
"lng_status_bot_not_reads_all" = "geen toegang tot berichten";
|
||||
"lng_status_offline" = "lang geleden gezien";
|
||||
"lng_status_recently" = "recent gezien";
|
||||
"lng_status_last_week" = "afgelopen week gezien";
|
||||
@@ -333,7 +336,10 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_profile_chat_unaccessible" = "Groep is ontoegankelijk";
|
||||
"lng_topbar_info" = "Info";
|
||||
"lng_profile_about_section" = "Over";
|
||||
"lng_profile_settings_section" = "Instellingen";
|
||||
"lng_profile_bot_settings" = "Instellingen";
|
||||
"lng_profile_bot_help" = "Help";
|
||||
"lng_profile_participants_section" = "Deelnemers";
|
||||
"lng_profile_info" = "Contactinformatie";
|
||||
"lng_profile_group_info" = "Groepsinformatie";
|
||||
@@ -343,6 +349,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
"lng_profile_clear_history" = "Geschiedenis wissen";
|
||||
"lng_profile_send_message" = "Bericht sturen";
|
||||
"lng_profile_share_contact" = "Contact delen";
|
||||
"lng_profile_invite_to_group" = "Groepslid maken";
|
||||
"lng_profile_delete_contact" = "Verwijder";
|
||||
"lng_profile_set_group_photo" = "Foto instellen";
|
||||
"lng_profile_add_participant" = "Lid toevoegen";
|
||||
@@ -396,7 +403,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
"lng_group_invite_link" = "Uitnodigingslink";
|
||||
"lng_group_invite_create" = "Uitnodigingslink maken";
|
||||
"lng_group_invite_about" = "Gebruikers kunnen aan je groep \ndeelnemen met deze link.";
|
||||
"lng_group_invite_create_new" = "Uitnodigingslink intrekken";
|
||||
"lng_group_invite_create_new" = "Intrekken";
|
||||
"lng_group_invite_about_new" = "Je uitnodigingslink zal inactief worden\neen nieuwe link zal worden gegenereerd.";
|
||||
"lng_group_invite_copied" = "Link gekopieerd naar klembord.";
|
||||
"lng_group_invite_no_room" = "Sorry, deze groep is al vol.";
|
||||
@@ -425,7 +432,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
"lng_emoji_category2" = "Natuur";
|
||||
"lng_emoji_category3" = "Eten & drinken";
|
||||
"lng_emoji_category4" = "Feestelijkheid";
|
||||
"lng_emoji_category5" = "Actviteit";
|
||||
"lng_emoji_category5" = "Activiteit";
|
||||
"lng_emoji_category6" = "Reizen & plekken";
|
||||
"lng_emoji_category7" = "Objecten & symbolen";
|
||||
|
||||
@@ -450,10 +457,19 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_send_button" = "Stuur";
|
||||
"lng_message_ph" = "Bericht schrijven";
|
||||
"lng_record_cancel" = "Annuleren: uit het vak loslaten";
|
||||
"lng_empty_history" = "";
|
||||
"lng_willbe_history" = "Kies een chat om te beginnen";
|
||||
"lng_message_with_from" = "[c]{from}:[/c] {message}";
|
||||
"lng_from_you" = "Jij";
|
||||
"lng_bot_description" = "Wat kan deze bot? ";
|
||||
|
||||
"lng_bot_start" = "Begin";
|
||||
"lng_bot_choose_group" = "Groep kiezen";
|
||||
"lng_bot_no_groups" = "Je hebt geen groepen";
|
||||
"lng_bot_groups_not_found" = "Geen groepen gevonden";
|
||||
"lng_bot_sure_invite" = "De bot toevoegen aan «{group}»?";
|
||||
"lng_bot_already_in_group" = "De bot neemt al deel aan de groep.";
|
||||
|
||||
"lng_typing" = "aan het typen";
|
||||
"lng_user_typing" = "{user} is aan het typen";
|
||||
@@ -584,7 +600,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_new_version_wrap" = "Telegram is bijgewerkt naar versie {version}\n\n{changes} \n\nVolledige versiegeschiedenis is hier te vinden:\n{link}";
|
||||
"lng_new_version_minor" = "— Probleemoplossing en andere kleine verbeteringen";
|
||||
"lng_new_version_text" = "— Ondersteuning voor stickerbundels toegevoegd\n— Nieuw emoji- en stickerspaneel";
|
||||
"lng_new_version_text" = "Deze versie heeft ondersteuning voor de nieuwe bot-API, gratis voor iedereen. Handig met programmeren? Maak dan je eigen bots voor spelletjes, diensten en integraties. \n\nMeer weten? kijk op: {blog_link}";
|
||||
|
||||
"lng_menu_insert_unicode" = "Unicode-besturingsteken invoegen";
|
||||
|
||||
|
||||
@@ -69,6 +69,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
"lng_reconnecting_try_now" = "Tentar agora";
|
||||
|
||||
"lng_status_service_notifications" = "notificações de serviço";
|
||||
"lng_status_bot" = "bot";
|
||||
"lng_status_bot_reads_all" = "tem acesso as mensagens";
|
||||
"lng_status_bot_not_reads_all" = "não tem acesso as mensagens";
|
||||
"lng_status_offline" = "visto há muito tempo";
|
||||
"lng_status_recently" = "visto recentemente";
|
||||
"lng_status_last_week" = "visto há uma semana";
|
||||
@@ -333,7 +336,10 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_profile_chat_unaccessible" = "Grupo inacessível";
|
||||
"lng_topbar_info" = "Info";
|
||||
"lng_profile_about_section" = "Sobre";
|
||||
"lng_profile_settings_section" = "Configurações";
|
||||
"lng_profile_bot_settings" = "Configurações";
|
||||
"lng_profile_bot_help" = "Ajuda";
|
||||
"lng_profile_participants_section" = "Membros";
|
||||
"lng_profile_info" = "Informação de contato";
|
||||
"lng_profile_group_info" = "Informação do grupo";
|
||||
@@ -342,7 +348,8 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
"lng_profile_enable_notifications" = "Notificações";
|
||||
"lng_profile_clear_history" = "Limpar histórico";
|
||||
"lng_profile_send_message" = "Enviar Mensagem";
|
||||
"lng_profile_share_contact" = "Compartilhar Contato";
|
||||
"lng_profile_share_contact" = "Compartilhar";
|
||||
"lng_profile_invite_to_group" = "Adicionar ao Grupo";
|
||||
"lng_profile_delete_contact" = "Apagar";
|
||||
"lng_profile_set_group_photo" = "Definir Foto";
|
||||
"lng_profile_add_participant" = "Adicionar Membro";
|
||||
@@ -450,10 +457,19 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_send_button" = "Enviar";
|
||||
"lng_message_ph" = "Escrever a mensagem..";
|
||||
"lng_record_cancel" = "Solte fora desse campo para cancelar";
|
||||
"lng_empty_history" = "";
|
||||
"lng_willbe_history" = "Selecione um chat para começar a conversar";
|
||||
"lng_message_with_from" = "[c]{from}:[/c] {message}";
|
||||
"lng_from_you" = "Você";
|
||||
"lng_bot_description" = "O que esse bot pode fazer?";
|
||||
|
||||
"lng_bot_start" = "Iniciar";
|
||||
"lng_bot_choose_group" = "Escolher Grupo";
|
||||
"lng_bot_no_groups" = "Você não possui grupos";
|
||||
"lng_bot_groups_not_found" = "Nenhum grupo encontrado";
|
||||
"lng_bot_sure_invite" = "Adicionar bot ao «{group}»?";
|
||||
"lng_bot_already_in_group" = "O bot já é um membro do grupo.";
|
||||
|
||||
"lng_typing" = "escrevendo";
|
||||
"lng_user_typing" = "{user} está escrevendo";
|
||||
@@ -584,7 +600,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
|
||||
"lng_new_version_wrap" = "Telegram Desktop foi atualizado para a versão {version}\n\n{changes}\n\nHistórico completo de mudanças disponível aqui:\n{link}";
|
||||
"lng_new_version_minor" = "— Resolução de bugs e outras menores melhorias";
|
||||
"lng_new_version_text" = "— Suporte para os pacotes de sticker adicionado\n— Novo painel de sticker e emoji";
|
||||
"lng_new_version_text" = "Essa nova versão inclui suporte para bots usando a nova API de bots. Se você for um engenheiro, crie seus próprios bots para jogos, serviços ou integrações.\n\nLeia mais em {blog_link}";
|
||||
|
||||
"lng_menu_insert_unicode" = "Inserir caractere de controle Unicode";
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
#include "gui/filedialog.h"
|
||||
|
||||
BackgroundWidget::BackgroundWidget(QWidget *parent, LayeredWidget *w) : QWidget(parent), w(w), _hidden(0),
|
||||
aBackground(0), aBackgroundFunc(anim::easeOutCirc), hiding(false), shadow(st::boxShadow) {
|
||||
aBackground(0), aBackgroundFunc(anim::easeOutCirc), hiding(false), shadow(st::boxShadow) {
|
||||
w->setParent(this);
|
||||
setGeometry(0, 0, App::wnd()->width(), App::wnd()->height());
|
||||
aBackground.start(1);
|
||||
@@ -54,7 +54,7 @@ void BackgroundWidget::paintEvent(QPaintEvent *e) {
|
||||
p.fillRect(rect(), st::layerBG->b);
|
||||
|
||||
p.setOpacity(aBackground.current());
|
||||
shadow.paint(p, w->boxRect());
|
||||
shadow.paint(p, w->boxRect(), st::boxShadowShift);
|
||||
}
|
||||
|
||||
void BackgroundWidget::keyPressEvent(QKeyEvent *e) {
|
||||
|
||||
@@ -37,6 +37,7 @@ void LocalImageLoaderPrivate::prepareImages() {
|
||||
QByteArray data;
|
||||
PeerId peer;
|
||||
uint64 id, thumbId = 0;
|
||||
int32 duration = 0;
|
||||
QString thumbExt = "jpg";
|
||||
ToPrepareMediaType type;
|
||||
bool animated = false;
|
||||
@@ -53,6 +54,7 @@ void LocalImageLoaderPrivate::prepareImages() {
|
||||
peer = list.front().peer;
|
||||
id = list.front().id;
|
||||
type = list.front().type;
|
||||
duration = list.front().duration;
|
||||
ctrlShiftEnter = list.front().ctrlShiftEnter;
|
||||
replyTo = list.front().replyTo;
|
||||
}
|
||||
@@ -85,29 +87,34 @@ void LocalImageLoaderPrivate::prepareImages() {
|
||||
filename = info.fileName();
|
||||
filesize = info.size();
|
||||
} else if (!data.isEmpty()) {
|
||||
img = App::readImage(data, 0, true, &animated);
|
||||
if (type == ToPrepareAuto) {
|
||||
if (!img.isNull() && data.size() < MaxUploadPhotoSize) {
|
||||
type = ToPreparePhoto;
|
||||
} else if (data.size() < MaxUploadDocumentSize) {
|
||||
type = ToPrepareDocument;
|
||||
} else {
|
||||
img = QImage();
|
||||
if (type != ToPrepareAudio) {
|
||||
img = App::readImage(data, 0, true, &animated);
|
||||
if (type == ToPrepareAuto) {
|
||||
if (!img.isNull() && data.size() < MaxUploadPhotoSize) {
|
||||
type = ToPreparePhoto;
|
||||
} else if (data.size() < MaxUploadDocumentSize) {
|
||||
type = ToPrepareDocument;
|
||||
} else {
|
||||
img = QImage();
|
||||
}
|
||||
}
|
||||
}
|
||||
MimeType mimeType = mimeTypeForData(data);
|
||||
if (type == ToPrepareDocument) {
|
||||
if (type == ToPrepareDocument || type == ToPrepareAudio) {
|
||||
mime = mimeType.name();
|
||||
}
|
||||
if (mime == "image/jpeg") {
|
||||
filename = filedialogDefaultName(qsl("image"), qsl(".jpg"), QString(), true);
|
||||
} else if (type == ToPrepareAudio) {
|
||||
filename = filedialogDefaultName(qsl("audio"), qsl(".ogg"), QString(), true);
|
||||
mime = "audio/ogg";
|
||||
} else {
|
||||
QString ext;
|
||||
QStringList patterns = mimeType.globPatterns();
|
||||
if (!patterns.isEmpty()) {
|
||||
ext = patterns.front().replace('*', QString());
|
||||
}
|
||||
filename = filedialogDefaultName(qsl("doc"), ext, QString(), true);
|
||||
filename = filedialogDefaultName((type == ToPrepareAudio) ? qsl("audio") : qsl("doc"), ext, QString(), true);
|
||||
}
|
||||
filesize = data.size();
|
||||
}
|
||||
@@ -136,7 +143,7 @@ void LocalImageLoaderPrivate::prepareImages() {
|
||||
}
|
||||
}
|
||||
|
||||
if ((img.isNull() && (type != ToPrepareDocument || !filesize)) || type == ToPrepareAuto || (img.isNull() && file.isEmpty() && data.isEmpty())) { // if could not decide what type
|
||||
if ((img.isNull() && ((type != ToPrepareDocument && type != ToPrepareAudio) || !filesize)) || type == ToPrepareAuto || (img.isNull() && file.isEmpty() && data.isEmpty())) { // if could not decide what type
|
||||
{
|
||||
QMutexLocker lock(loader->toPrepareMutex());
|
||||
ToPrepareMedias &list(loader->toPrepareMedias());
|
||||
@@ -155,6 +162,7 @@ void LocalImageLoaderPrivate::prepareImages() {
|
||||
MTPPhotoSize thumb(MTP_photoSizeEmpty(MTP_string("")));
|
||||
MTPPhoto photo(MTP_photoEmpty(MTP_long(0)));
|
||||
MTPDocument document(MTP_documentEmpty(MTP_long(0)));
|
||||
MTPAudio audio(MTP_audioEmpty(MTP_long(0)));
|
||||
|
||||
QByteArray jpeg;
|
||||
if (type == ToPreparePhoto) {
|
||||
@@ -210,11 +218,13 @@ void LocalImageLoaderPrivate::prepareImages() {
|
||||
|
||||
if (type == ToPrepareDocument) {
|
||||
document = MTP_document(MTP_long(id), MTP_long(0), MTP_int(unixtime()), MTP_string(mime), MTP_int(filesize), thumb, MTP_int(MTP::maindc()), MTP_vector<MTPDocumentAttribute>(attributes));
|
||||
} else if (type == ToPrepareAudio) {
|
||||
audio = MTP_audio(MTP_long(id), MTP_long(0), MTP_int(user), MTP_int(unixtime()), MTP_int(duration), MTP_string(mime), MTP_int(filesize), MTP_int(MTP::maindc()));
|
||||
}
|
||||
|
||||
{
|
||||
QMutexLocker lock(loader->readyMutex());
|
||||
loader->readyList().push_back(ReadyLocalMedia(type, file, filename, filesize, data, id, thumbId, thumbExt, peer, photo, photoThumbs, document, jpeg, ctrlShiftEnter, replyTo));
|
||||
loader->readyList().push_back(ReadyLocalMedia(type, file, filename, filesize, data, id, thumbId, thumbExt, peer, photo, audio, photoThumbs, document, jpeg, ctrlShiftEnter, replyTo));
|
||||
}
|
||||
|
||||
{
|
||||
@@ -267,6 +277,22 @@ PhotoId LocalImageLoader::append(const QByteArray &img, const PeerId &peer, MsgI
|
||||
return result;
|
||||
}
|
||||
|
||||
AudioId LocalImageLoader::append(const QByteArray &audio, int32 duration, const PeerId &peer, MsgId replyTo, ToPrepareMediaType t) {
|
||||
AudioId result = 0;
|
||||
{
|
||||
QMutexLocker lock(toPrepareMutex());
|
||||
toPrepare.push_back(ToPrepareMedia(audio, duration, peer, t, false, replyTo));
|
||||
result = toPrepare.back().id;
|
||||
}
|
||||
if (!thread) {
|
||||
thread = new QThread();
|
||||
priv = new LocalImageLoaderPrivate(MTP::authedId(), this, thread);
|
||||
thread->start();
|
||||
}
|
||||
emit needToPrepare();
|
||||
return result;
|
||||
}
|
||||
|
||||
PhotoId LocalImageLoader::append(const QImage &img, const PeerId &peer, MsgId replyTo, ToPrepareMediaType t, bool ctrlShiftEnter) {
|
||||
PhotoId result = 0;
|
||||
{
|
||||
|
||||
@@ -20,16 +20,19 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
enum ToPrepareMediaType {
|
||||
ToPrepareAuto,
|
||||
ToPreparePhoto,
|
||||
ToPrepareAudio,
|
||||
ToPrepareVideo,
|
||||
ToPrepareDocument,
|
||||
};
|
||||
|
||||
struct ToPrepareMedia {
|
||||
ToPrepareMedia(const QString &file, const PeerId &peer, ToPrepareMediaType t, bool ctrlShiftEnter, MsgId replyTo) : id(MTP::nonce<PhotoId>()), file(file), peer(peer), type(t), ctrlShiftEnter(ctrlShiftEnter), replyTo(replyTo) {
|
||||
ToPrepareMedia(const QString &file, const PeerId &peer, ToPrepareMediaType t, bool ctrlShiftEnter, MsgId replyTo) : id(MTP::nonce<PhotoId>()), file(file), peer(peer), type(t), duration(0), ctrlShiftEnter(ctrlShiftEnter), replyTo(replyTo) {
|
||||
}
|
||||
ToPrepareMedia(const QImage &img, const PeerId &peer, ToPrepareMediaType t, bool ctrlShiftEnter, MsgId replyTo) : id(MTP::nonce<PhotoId>()), img(img), peer(peer), type(t), ctrlShiftEnter(ctrlShiftEnter), replyTo(replyTo) {
|
||||
ToPrepareMedia(const QImage &img, const PeerId &peer, ToPrepareMediaType t, bool ctrlShiftEnter, MsgId replyTo) : id(MTP::nonce<PhotoId>()), img(img), peer(peer), type(t), duration(0), ctrlShiftEnter(ctrlShiftEnter), replyTo(replyTo) {
|
||||
}
|
||||
ToPrepareMedia(const QByteArray &data, const PeerId &peer, ToPrepareMediaType t, bool ctrlShiftEnter, MsgId replyTo) : id(MTP::nonce<PhotoId>()), data(data), peer(peer), type(t), ctrlShiftEnter(ctrlShiftEnter), replyTo(replyTo) {
|
||||
ToPrepareMedia(const QByteArray &data, const PeerId &peer, ToPrepareMediaType t, bool ctrlShiftEnter, MsgId replyTo) : id(MTP::nonce<PhotoId>()), data(data), peer(peer), type(t), duration(0), ctrlShiftEnter(ctrlShiftEnter), replyTo(replyTo) {
|
||||
}
|
||||
ToPrepareMedia(const QByteArray &data, int32 duration, const PeerId &peer, ToPrepareMediaType t, bool ctrlShiftEnter, MsgId replyTo) : id(MTP::nonce<PhotoId>()), data(data), peer(peer), type(t), duration(duration), ctrlShiftEnter(ctrlShiftEnter), replyTo(replyTo) {
|
||||
}
|
||||
PhotoId id;
|
||||
QString file;
|
||||
@@ -37,6 +40,7 @@ struct ToPrepareMedia {
|
||||
QByteArray data;
|
||||
PeerId peer;
|
||||
ToPrepareMediaType type;
|
||||
int32 duration;
|
||||
bool ctrlShiftEnter;
|
||||
MsgId replyTo;
|
||||
};
|
||||
@@ -44,8 +48,8 @@ typedef QList<ToPrepareMedia> ToPrepareMedias;
|
||||
|
||||
typedef QMap<int32, QByteArray> LocalFileParts;
|
||||
struct ReadyLocalMedia {
|
||||
ReadyLocalMedia(ToPrepareMediaType type, const QString &file, const QString &filename, int32 filesize, const QByteArray &data, const uint64 &id, const uint64 &thumbId, const QString &thumbExt, const PeerId &peer, const MTPPhoto &photo, const PreparedPhotoThumbs &photoThumbs, const MTPDocument &document, const QByteArray &jpeg, bool ctrlShiftEnter, MsgId replyTo) :
|
||||
replyTo(replyTo), type(type), file(file), filename(filename), filesize(filesize), data(data), thumbExt(thumbExt), id(id), thumbId(thumbId), peer(peer), photo(photo), document(document), photoThumbs(photoThumbs), ctrlShiftEnter(ctrlShiftEnter) {
|
||||
ReadyLocalMedia(ToPrepareMediaType type, const QString &file, const QString &filename, int32 filesize, const QByteArray &data, const uint64 &id, const uint64 &thumbId, const QString &thumbExt, const PeerId &peer, const MTPPhoto &photo, const MTPAudio &audio, const PreparedPhotoThumbs &photoThumbs, const MTPDocument &document, const QByteArray &jpeg, bool ctrlShiftEnter, MsgId replyTo) :
|
||||
replyTo(replyTo), type(type), file(file), filename(filename), filesize(filesize), data(data), thumbExt(thumbExt), id(id), thumbId(thumbId), peer(peer), photo(photo), document(document), audio(audio), photoThumbs(photoThumbs), ctrlShiftEnter(ctrlShiftEnter) {
|
||||
if (!jpeg.isEmpty()) {
|
||||
int32 size = jpeg.size();
|
||||
for (int32 i = 0, part = 0; i < size; i += UploadPartSize, ++part) {
|
||||
@@ -66,6 +70,7 @@ struct ReadyLocalMedia {
|
||||
|
||||
MTPPhoto photo;
|
||||
MTPDocument document;
|
||||
MTPAudio audio;
|
||||
PreparedPhotoThumbs photoThumbs;
|
||||
LocalFileParts parts;
|
||||
QByteArray jpeg_md5;
|
||||
@@ -107,6 +112,7 @@ public:
|
||||
LocalImageLoader(QObject *parent);
|
||||
void append(const QStringList &files, const PeerId &peer, MsgId replyTo, ToPrepareMediaType t);
|
||||
PhotoId append(const QByteArray &img, const PeerId &peer, MsgId replyTo, ToPrepareMediaType t);
|
||||
AudioId append(const QByteArray &audio, int32 duration, const PeerId &peer, MsgId replyTo, ToPrepareMediaType t);
|
||||
PhotoId append(const QImage &img, const PeerId &peer, MsgId replyTo, ToPrepareMediaType t, bool ctrlShiftEnter = false);
|
||||
PhotoId append(const QString &file, const PeerId &peer, MsgId replyTo, ToPrepareMediaType t);
|
||||
|
||||
|
||||
@@ -609,13 +609,22 @@ namespace {
|
||||
mtpDcOptions *_dcOpts = 0;
|
||||
bool _readSetting(quint32 blockId, QDataStream &stream, int version) {
|
||||
switch (blockId) {
|
||||
case dbiDcOption: {
|
||||
case dbiDcOptionOld: {
|
||||
quint32 dcId, port;
|
||||
QString host, ip;
|
||||
stream >> dcId >> host >> ip >> port;
|
||||
if (!_checkStreamStatus(stream)) return false;
|
||||
|
||||
if (_dcOpts) _dcOpts->insert(dcId, mtpDcOption(dcId, host.toUtf8().constData(), ip.toUtf8().constData(), port));
|
||||
if (_dcOpts) _dcOpts->insert(dcId, mtpDcOption(dcId, 0, ip.toUtf8().constData(), port));
|
||||
} break;
|
||||
|
||||
case dbiDcOption: {
|
||||
quint32 dcIdWithShift, flags, port;
|
||||
QString ip;
|
||||
stream >> dcIdWithShift >> flags >> ip >> port;
|
||||
if (!_checkStreamStatus(stream)) return false;
|
||||
|
||||
if (_dcOpts) _dcOpts->insert(dcIdWithShift, mtpDcOption(dcIdWithShift % _mtp_internal::dcShift, flags, ip.toUtf8().constData(), port));
|
||||
} break;
|
||||
|
||||
case dbiMaxGroupCount: {
|
||||
@@ -1723,9 +1732,16 @@ namespace Local {
|
||||
if (dcOpts.isEmpty()) {
|
||||
const BuiltInDc *bdcs = builtInDcs();
|
||||
for (int i = 0, l = builtInDcsCount(); i < l; ++i) {
|
||||
dcOpts.insert(bdcs[i].id, mtpDcOption(bdcs[i].id, "", bdcs[i].ip, bdcs[i].port));
|
||||
dcOpts.insert(bdcs[i].id, mtpDcOption(bdcs[i].id, 0, bdcs[i].ip, bdcs[i].port));
|
||||
DEBUG_LOG(("MTP Info: adding built in DC %1 connect option: %2:%3").arg(bdcs[i].id).arg(bdcs[i].ip).arg(bdcs[i].port));
|
||||
}
|
||||
|
||||
const BuiltInDc *bdcsipv6 = builtInDcsIPv6();
|
||||
for (int i = 0, l = builtInDcsCountIPv6(); i < l; ++i) {
|
||||
int32 flags = MTPDdcOption_flag_ipv6, idWithShift = bdcsipv6[i].id + (flags * _mtp_internal::dcShift);
|
||||
dcOpts.insert(idWithShift, mtpDcOption(bdcsipv6[i].id, flags, bdcsipv6[i].ip, bdcsipv6[i].port));
|
||||
DEBUG_LOG(("MTP Info: adding built in DC %1 IPv6 connect option: %2:%3").arg(bdcsipv6[i].id).arg(bdcsipv6[i].ip).arg(bdcsipv6[i].port));
|
||||
}
|
||||
}
|
||||
{
|
||||
QWriteLocker lock(MTP::dcOptionsMutex());
|
||||
@@ -1759,10 +1775,16 @@ namespace Local {
|
||||
if (dcOpts.isEmpty()) {
|
||||
const BuiltInDc *bdcs = builtInDcs();
|
||||
for (int i = 0, l = builtInDcsCount(); i < l; ++i) {
|
||||
dcOpts.insert(bdcs[i].id, mtpDcOption(bdcs[i].id, "", bdcs[i].ip, bdcs[i].port));
|
||||
dcOpts.insert(bdcs[i].id, mtpDcOption(bdcs[i].id, 0, bdcs[i].ip, bdcs[i].port));
|
||||
DEBUG_LOG(("MTP Info: adding built in DC %1 connect option: %2:%3").arg(bdcs[i].id).arg(bdcs[i].ip).arg(bdcs[i].port));
|
||||
}
|
||||
|
||||
const BuiltInDc *bdcsipv6 = builtInDcsIPv6();
|
||||
for (int i = 0, l = builtInDcsCountIPv6(); i < l; ++i) {
|
||||
dcOpts.insert(bdcsipv6[i].id + (MTPDdcOption_flag_ipv6 * _mtp_internal::dcShift), mtpDcOption(bdcsipv6[i].id, MTPDdcOption_flag_ipv6, bdcsipv6[i].ip, bdcsipv6[i].port));
|
||||
DEBUG_LOG(("MTP Info: adding built in DC %1 IPv6 connect option: %2:%3").arg(bdcsipv6[i].id).arg(bdcsipv6[i].ip).arg(bdcsipv6[i].port));
|
||||
}
|
||||
|
||||
QWriteLocker lock(MTP::dcOptionsMutex());
|
||||
cSetDcOptions(dcOpts);
|
||||
}
|
||||
@@ -1770,7 +1792,7 @@ namespace Local {
|
||||
quint32 size = 10 * (sizeof(quint32) + sizeof(qint32));
|
||||
for (mtpDcOptions::const_iterator i = dcOpts.cbegin(), e = dcOpts.cend(); i != e; ++i) {
|
||||
size += sizeof(quint32) + sizeof(quint32) + sizeof(quint32);
|
||||
size += _stringSize(QString::fromUtf8(i->host.data(), i->host.size())) + _stringSize(QString::fromUtf8(i->ip.data(), i->ip.size()));
|
||||
size += sizeof(quint32) + _stringSize(QString::fromUtf8(i->ip.data(), i->ip.size()));
|
||||
}
|
||||
size += sizeof(quint32) + _stringSize(cLangFile());
|
||||
|
||||
@@ -1794,8 +1816,8 @@ namespace Local {
|
||||
data.stream << quint32(dbiScale) << qint32(cConfigScale());
|
||||
data.stream << quint32(dbiLang) << qint32(cLang());
|
||||
for (mtpDcOptions::const_iterator i = dcOpts.cbegin(), e = dcOpts.cend(); i != e; ++i) {
|
||||
data.stream << quint32(dbiDcOption) << quint32(i->id);
|
||||
data.stream << QString::fromUtf8(i->host.data(), i->host.size()) << QString::fromUtf8(i->ip.data(), i->ip.size());
|
||||
data.stream << quint32(dbiDcOption) << quint32(i.key());
|
||||
data.stream << quint32(i->flags) << QString::fromUtf8(i->ip.data(), i->ip.size());
|
||||
data.stream << quint32(i->port);
|
||||
}
|
||||
data.stream << quint32(dbiLangFile) << cLangFile();
|
||||
@@ -2266,7 +2288,7 @@ namespace Local {
|
||||
}
|
||||
}
|
||||
EncryptedDescriptor data(size);
|
||||
data.stream << quint32(cStickerSetsOrder().size()) << cStickersHash();
|
||||
data.stream << quint32(cStickerSets().size()) << cStickersHash();
|
||||
_writeStickerSet(data.stream, DefaultStickerSetId);
|
||||
_writeStickerSet(data.stream, CustomStickerSetId);
|
||||
for (StickerSetsOrder::const_iterator i = cStickerSetsOrder().cbegin(), e = cStickerSetsOrder().cend(); i != e; ++i) {
|
||||
@@ -2287,7 +2309,7 @@ namespace Local {
|
||||
_writeMap();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
StickerSets &sets(cRefStickerSets());
|
||||
sets.clear();
|
||||
cSetStickerSetsOrder(StickerSetsOrder());
|
||||
@@ -2367,6 +2389,10 @@ namespace Local {
|
||||
quint32 cnt;
|
||||
QByteArray hash;
|
||||
stickers.stream >> cnt >> hash;
|
||||
if (stickers.version < 8019) {
|
||||
hash.clear(); // bad data in old caches
|
||||
cnt += 2; // try to read at least something
|
||||
}
|
||||
for (uint32 i = 0; i < cnt; ++i) {
|
||||
quint64 setId = 0, setAccess = 0;
|
||||
QString setTitle, setShortName;
|
||||
@@ -2377,8 +2403,10 @@ namespace Local {
|
||||
setTitle = lang(lng_stickers_default_set);
|
||||
} else if (setId == CustomStickerSetId) {
|
||||
setTitle = lang(lng_custom_stickers);
|
||||
} else {
|
||||
} else if (setId) {
|
||||
order.push_back(setId);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
StickerSet &set(sets.insert(setId, StickerSet(setId, setAccess, setTitle, setShortName)).value());
|
||||
set.stickers.reserve(scnt);
|
||||
|
||||
@@ -66,6 +66,7 @@ void debugLogWrite(const char *file, int32 line, const QString &v) {
|
||||
|
||||
{
|
||||
QMutexLocker lock(&debugLogMutex);
|
||||
if (!cDebug() || !debugLogStream) return;
|
||||
|
||||
logsInitDebug(); // maybe need to reopen new file
|
||||
|
||||
@@ -87,6 +88,7 @@ void tcpLogWrite(const QString &v) {
|
||||
|
||||
{
|
||||
QMutexLocker lock(&debugLogMutex);
|
||||
if (!cDebug() || !tcpLogStream) return;
|
||||
|
||||
logsInitDebug(); // maybe need to reopen new file
|
||||
|
||||
@@ -100,6 +102,7 @@ void mtpLogWrite(int32 dc, const QString &v) {
|
||||
|
||||
{
|
||||
QMutexLocker lock(&debugLogMutex);
|
||||
if (!cDebug() || !mtpLogStream) return;
|
||||
|
||||
logsInitDebug(); // maybe need to reopen new file
|
||||
|
||||
@@ -109,7 +112,7 @@ void mtpLogWrite(int32 dc, const QString &v) {
|
||||
}
|
||||
|
||||
void logWrite(const QString &v) {
|
||||
if (!mainLog.isOpen()) return;
|
||||
if (!mainLogStream) return;
|
||||
|
||||
time_t t = time(NULL);
|
||||
struct tm tm;
|
||||
@@ -117,6 +120,8 @@ void logWrite(const QString &v) {
|
||||
|
||||
{
|
||||
QMutexLocker lock(&mainLogMutex);
|
||||
if (!mainLogStream) return;
|
||||
|
||||
QString msg(QString("[%1.%2.%3 %4:%5:%6] %7\n").arg(tm.tm_year + 1900).arg(tm.tm_mon + 1, 2, 10, zero).arg(tm.tm_mday, 2, 10, zero).arg(tm.tm_hour, 2, 10, zero).arg(tm.tm_min, 2, 10, zero).arg(tm.tm_sec, 2, 10, zero).arg(v));
|
||||
(*mainLogStream) << msg;
|
||||
mainLogStream->flush();
|
||||
@@ -227,6 +232,8 @@ void logsInit() {
|
||||
logsInitDebug();
|
||||
cSetDebug(true);
|
||||
}
|
||||
|
||||
QDir().setCurrent(cWorkingDir());
|
||||
}
|
||||
|
||||
void logsInitDebug() {
|
||||
|
||||
@@ -26,6 +26,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
#include "mainwidget.h"
|
||||
#include "boxes/confirmbox.h"
|
||||
#include "boxes/stickersetbox.h"
|
||||
#include "boxes/contactsbox.h"
|
||||
|
||||
#include "localstorage.h"
|
||||
|
||||
@@ -373,9 +374,9 @@ _failDifferenceTimeout(1), _lastUpdateTime(0), _cachedX(0), _cachedY(0), _backgr
|
||||
connect(&history, SIGNAL(peerShown(PeerData*)), this, SLOT(onPeerShown(PeerData*)));
|
||||
connect(&updateNotifySettingTimer, SIGNAL(timeout()), this, SLOT(onUpdateNotifySettings()));
|
||||
connect(this, SIGNAL(showPeerAsync(quint64,qint32,bool,bool)), this, SLOT(showPeer(quint64,qint32,bool,bool)), Qt::QueuedConnection);
|
||||
if (audioVoice()) {
|
||||
connect(audioVoice(), SIGNAL(updated(AudioData*)), this, SLOT(audioPlayProgress(AudioData*)));
|
||||
connect(audioVoice(), SIGNAL(stopped(AudioData*)), this, SLOT(audioPlayProgress(AudioData*)));
|
||||
if (audioPlayer()) {
|
||||
connect(audioPlayer(), SIGNAL(updated(AudioData*)), this, SLOT(audioPlayProgress(AudioData*)));
|
||||
connect(audioPlayer(), SIGNAL(stopped(AudioData*)), this, SLOT(audioPlayProgress(AudioData*)));
|
||||
}
|
||||
connect(&_updateMutedTimer, SIGNAL(timeout()), this, SLOT(onUpdateMuted()));
|
||||
|
||||
@@ -494,9 +495,13 @@ void MainWidget::finishForwarding(History *hist) {
|
||||
if (_toForward.size() < 2) {
|
||||
uint64 randomId = MTP::nonce<uint64>();
|
||||
MsgId newId = clientMsgId();
|
||||
hist->addToBackForwarded(newId, static_cast<HistoryMessage*>(_toForward.cbegin().value()));
|
||||
HistoryMessage *msg = static_cast<HistoryMessage*>(_toForward.cbegin().value());
|
||||
hist->addToBackForwarded(newId, msg);
|
||||
App::historyRegRandom(randomId, newId);
|
||||
hist->sendRequestId = MTP::send(MTPmessages_ForwardMessage(hist->peer->input, MTP_int(_toForward.cbegin().key()), MTP_long(randomId)), rpcDone(&MainWidget::sentUpdatesReceived), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId);
|
||||
if (HistorySticker *sticker = dynamic_cast<HistorySticker*>(msg->getMedia())) {
|
||||
App::main()->incrementSticker(sticker->document());
|
||||
}
|
||||
} else {
|
||||
QVector<MTPint> ids;
|
||||
QVector<MTPlong> randomIds;
|
||||
@@ -762,19 +767,21 @@ void MainWidget::removeContact(UserData *user) {
|
||||
|
||||
void MainWidget::addParticipants(ChatData *chat, const QVector<UserData*> &users) {
|
||||
for (QVector<UserData*>::const_iterator i = users.cbegin(), e = users.cend(); i != e; ++i) {
|
||||
MTP::send(MTPmessages_AddChatUser(MTP_int(chat->id & 0xFFFFFFFF), (*i)->inputUser, MTP_int(ForwardOnAdd)), rpcDone(&MainWidget::sentUpdatesReceived), rpcFail(&MainWidget::addParticipantFail, chat), 0, 5);
|
||||
MTP::send(MTPmessages_AddChatUser(MTP_int(chat->id & 0xFFFFFFFF), (*i)->inputUser, MTP_int(ForwardOnAdd)), rpcDone(&MainWidget::sentUpdatesReceived), rpcFail(&MainWidget::addParticipantFail, *i), 0, 5);
|
||||
}
|
||||
App::wnd()->hideLayer();
|
||||
showPeer(chat->id, 0, false);
|
||||
}
|
||||
|
||||
bool MainWidget::addParticipantFail(ChatData *chat, const RPCError &error) {
|
||||
bool MainWidget::addParticipantFail(UserData *user, const RPCError &error) {
|
||||
if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false;
|
||||
|
||||
ConfirmBox *box = new ConfirmBox(lang(lng_failed_add_participant), true);
|
||||
App::wnd()->showLayer(box);
|
||||
QString text = lang(lng_failed_add_participant);
|
||||
if (error.type() == "USER_LEFT_CHAT") { // trying to return banned user to his group
|
||||
} else if (error.type() == "USER_ALREADY_PARTICIPANT" && user->botInfo) {
|
||||
text = lang(lng_bot_already_in_group);
|
||||
}
|
||||
App::wnd()->showLayer(new ConfirmBox(text, true));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -817,7 +824,7 @@ void MainWidget::checkedHistory(PeerData *peer, const MTPmessages_Messages &resu
|
||||
dialogs.removePeer(peer);
|
||||
} else {
|
||||
History *h = App::historyLoaded(peer->id);
|
||||
if (!h->last) {
|
||||
if (!h->lastMsg) {
|
||||
h->addToBack((*v)[0], 0);
|
||||
}
|
||||
}
|
||||
@@ -929,6 +936,10 @@ DialogsIndexed &MainWidget::contactsList() {
|
||||
return dialogs.contactsList();
|
||||
}
|
||||
|
||||
DialogsIndexed &MainWidget::dialogsList() {
|
||||
return dialogs.dialogsList();
|
||||
}
|
||||
|
||||
void MainWidget::sendPreparedText(History *hist, const QString &text, MsgId replyTo, WebPageId webPageId) {
|
||||
saveRecentHashtags(text);
|
||||
QString sendingText, leftText = text;
|
||||
@@ -940,7 +951,7 @@ void MainWidget::sendPreparedText(History *hist, const QString &text, MsgId repl
|
||||
App::historyRegRandom(randomId, newId);
|
||||
|
||||
MTPstring msgText(MTP_string(sendingText));
|
||||
int32 flags = (hist->peer->input.type() == mtpc_inputPeerSelf) ? 0 : (MTPDmessage_flag_unread | MTPDmessage_flag_out);
|
||||
int32 flags = newMessageFlags(hist->peer); // unread, out
|
||||
int32 sendFlags = 0;
|
||||
if (replyTo) {
|
||||
flags |= MTPDmessage::flag_reply_to_msg_id;
|
||||
@@ -953,8 +964,8 @@ void MainWidget::sendPreparedText(History *hist, const QString &text, MsgId repl
|
||||
WebPageData *page = App::webPage(webPageId);
|
||||
media = MTP_messageMediaWebPage(MTP_webPagePending(MTP_long(page->id), MTP_int(page->pendingTill)));
|
||||
}
|
||||
hist->addToBack(MTP_message(MTP_int(flags), MTP_int(newId), MTP_int(MTP::authedId()), App::peerToMTP(hist->peer->id), MTPint(), MTPint(), MTP_int(replyTo), MTP_int(unixtime()), msgText, media));
|
||||
hist->sendRequestId = MTP::send(MTPmessages_SendMessage(MTP_int(sendFlags), hist->peer->input, MTP_int(replyTo), msgText, MTP_long(randomId)), App::main()->rpcDone(&MainWidget::sentDataReceived, randomId), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId);
|
||||
hist->addToBack(MTP_message(MTP_int(flags), MTP_int(newId), MTP_int(MTP::authedId()), App::peerToMTP(hist->peer->id), MTPint(), MTPint(), MTP_int(replyTo), MTP_int(unixtime()), msgText, media, MTPnullMarkup));
|
||||
hist->sendRequestId = MTP::send(MTPmessages_SendMessage(MTP_int(sendFlags), hist->peer->input, MTP_int(replyTo), msgText, MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentDataReceived, randomId), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId);
|
||||
}
|
||||
|
||||
finishForwarding(hist);
|
||||
@@ -963,6 +974,7 @@ void MainWidget::sendPreparedText(History *hist, const QString &text, MsgId repl
|
||||
void MainWidget::sendMessage(History *hist, const QString &text, MsgId replyTo) {
|
||||
readServerHistory(hist, false);
|
||||
hist->loadAround(0);
|
||||
if (history.peer())
|
||||
sendPreparedText(hist, history.prepareMessage(text), replyTo);
|
||||
}
|
||||
|
||||
@@ -1012,6 +1024,14 @@ void MainWidget::stopAnimActive() {
|
||||
history.stopAnimActive();
|
||||
}
|
||||
|
||||
void MainWidget::sendBotCommand(const QString &cmd, MsgId replyTo) {
|
||||
history.sendBotCommand(cmd, replyTo);
|
||||
}
|
||||
|
||||
void MainWidget::insertBotCommand(const QString &cmd) {
|
||||
history.insertBotCommand(cmd);
|
||||
}
|
||||
|
||||
void MainWidget::searchMessages(const QString &query) {
|
||||
App::wnd()->hideMediaview();
|
||||
dialogs.searchMessages(query);
|
||||
@@ -1357,18 +1377,18 @@ void MainWidget::audioLoadProgress(mtpFileLoader *loader) {
|
||||
if (audio->loader) {
|
||||
if (audio->loader->done()) {
|
||||
audio->finish();
|
||||
bool mp3 = (audio->mime == QLatin1String("audio/mp3"));
|
||||
QString already = audio->already();
|
||||
bool play = !mp3 && audio->openOnSave > 0 && audioVoice();
|
||||
bool play = audio->openOnSave > 0 && audioPlayer();
|
||||
if ((!already.isEmpty() && audio->openOnSave) || (!audio->data.isEmpty() && play)) {
|
||||
if (play) {
|
||||
AudioData *playing = 0;
|
||||
VoiceMessageState state = VoiceMessageStopped;
|
||||
audioVoice()->currentState(&playing, &state);
|
||||
if (playing == audio && state != VoiceMessageStopped) {
|
||||
audioVoice()->pauseresume();
|
||||
AudioPlayerState state = AudioPlayerStopped;
|
||||
audioPlayer()->currentState(&playing, &state);
|
||||
if (playing == audio && state != AudioPlayerStopped) {
|
||||
audioPlayer()->pauseresume();
|
||||
} else {
|
||||
audioVoice()->play(audio);
|
||||
audioPlayer()->play(audio);
|
||||
if (App::main()) App::main()->audioMarkRead(audio);
|
||||
}
|
||||
} else {
|
||||
QPoint pos(QCursor::pos());
|
||||
@@ -1377,6 +1397,7 @@ void MainWidget::audioLoadProgress(mtpFileLoader *loader) {
|
||||
} else {
|
||||
psOpenFile(already, audio->openOnSave < 0);
|
||||
}
|
||||
if (App::main()) App::main()->audioMarkRead(audio);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1391,6 +1412,32 @@ void MainWidget::audioLoadProgress(mtpFileLoader *loader) {
|
||||
}
|
||||
|
||||
void MainWidget::audioPlayProgress(AudioData *audio) {
|
||||
AudioData *playing = 0;
|
||||
AudioPlayerState state = AudioPlayerStopped;
|
||||
audioPlayer()->currentState(&playing, &state);
|
||||
if (playing == audio && state == AudioPlayerStoppedAtStart) {
|
||||
audioPlayer()->clearStoppedAtStart(audio);
|
||||
QString already = audio->already(true);
|
||||
if (already.isEmpty() && !audio->data.isEmpty()) {
|
||||
bool mp3 = (audio->mime == QLatin1String("audio/mp3"));
|
||||
QString filename = saveFileName(lang(lng_save_audio), mp3 ? qsl("MP3 Audio (*.mp3);;All files (*.*)") : qsl("OGG Opus Audio (*.ogg);;All files (*.*)"), qsl("audio"), mp3 ? qsl(".mp3") : qsl(".ogg"), false);
|
||||
if (!filename.isEmpty()) {
|
||||
QFile f(filename);
|
||||
if (f.open(QIODevice::WriteOnly)) {
|
||||
if (f.write(audio->data) == audio->data.size()) {
|
||||
f.close();
|
||||
already = filename;
|
||||
audio->location = FileLocation(mtpToStorageType(mtpc_storage_filePartial), filename);
|
||||
Local::writeFileLocation(mediaKey(mtpToLocationType(mtpc_inputAudioFileLocation), audio->dc, audio->id), FileLocation(mtpToStorageType(mtpc_storage_filePartial), filename));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!already.isEmpty()) {
|
||||
psOpenFile(already);
|
||||
}
|
||||
}
|
||||
|
||||
const AudioItems &items(App::audioItems());
|
||||
AudioItems::const_iterator i = items.constFind(audio);
|
||||
if (i != items.cend()) {
|
||||
@@ -1428,8 +1475,10 @@ void MainWidget::documentLoadProgress(mtpFileLoader *loader) {
|
||||
HistoryItem *item = App::histItemById(document->openOnSaveMsgId);
|
||||
if (reader.supportsAnimation() && reader.imageCount() > 1 && item) {
|
||||
startGif(item, already);
|
||||
} else {
|
||||
} else if (item) {
|
||||
App::wnd()->showDocument(document, item);
|
||||
} else {
|
||||
psOpenFile(already);
|
||||
}
|
||||
} else {
|
||||
psOpenFile(already);
|
||||
@@ -1546,7 +1595,7 @@ void MainWidget::serviceNotification(const QString &msg, const MTPMessageMedia &
|
||||
QString sendingText, leftText = msg;
|
||||
HistoryItem *item = 0;
|
||||
while (textSplit(sendingText, leftText, MaxMessageSize)) {
|
||||
item = App::histories().addToBack(MTP_message(MTP_int(flags), MTP_int(clientMsgId()), MTP_int(ServiceUserId), MTP_peerUser(MTP_int(MTP::authedId())), MTPint(), MTPint(), MTPint(), MTP_int(unixtime()), MTP_string(sendingText), media), unread ? 1 : 2);
|
||||
item = App::histories().addToBack(MTP_message(MTP_int(flags), MTP_int(clientMsgId()), MTP_int(ServiceUserId), MTP_peerUser(MTP_int(MTP::authedId())), MTPint(), MTPint(), MTPint(), MTP_int(unixtime()), MTP_string(sendingText), media, MTPnullMarkup), unread ? 1 : 2);
|
||||
}
|
||||
if (item) {
|
||||
history.peerMessagesUpdated(item->history()->peer->id);
|
||||
@@ -1736,6 +1785,7 @@ void MainWidget::showPeer(quint64 peerId, qint32 msgId, bool back, bool force) {
|
||||
if (profile || overview) {
|
||||
if (profile) {
|
||||
profile->hide();
|
||||
profile->clear();
|
||||
profile->deleteLater();
|
||||
profile->rpcInvalidate();
|
||||
profile = 0;
|
||||
@@ -1859,6 +1909,7 @@ void MainWidget::showMediaOverview(PeerData *peer, MediaOverviewType type, bool
|
||||
}
|
||||
if (profile) {
|
||||
profile->hide();
|
||||
profile->clear();
|
||||
profile->deleteLater();
|
||||
profile->rpcInvalidate();
|
||||
profile = 0;
|
||||
@@ -1906,6 +1957,7 @@ void MainWidget::showPeerProfile(PeerData *peer, bool back, int32 lastScrollTop,
|
||||
}
|
||||
if (profile) {
|
||||
profile->hide();
|
||||
profile->clear();
|
||||
profile->deleteLater();
|
||||
profile->rpcInvalidate();
|
||||
}
|
||||
@@ -2030,6 +2082,10 @@ void MainWidget::historyWasRead() {
|
||||
history.historyWasRead(false);
|
||||
}
|
||||
|
||||
void MainWidget::historyCleared(History *hist) {
|
||||
history.historyCleared(hist);
|
||||
}
|
||||
|
||||
void MainWidget::animShow(const QPixmap &bgAnimCache, bool back) {
|
||||
_bgAnimCache = bgAnimCache;
|
||||
|
||||
@@ -2462,7 +2518,7 @@ void MainWidget::mtpPing() {
|
||||
}
|
||||
|
||||
void MainWidget::start(const MTPUser &user) {
|
||||
int32 uid = user.c_userSelf().vid.v;
|
||||
int32 uid = user.c_user().vid.v;
|
||||
if (MTP::authedId() != uid) {
|
||||
MTP::authed(uid);
|
||||
Local::writeMtpData();
|
||||
@@ -2490,35 +2546,42 @@ bool MainWidget::started() {
|
||||
void MainWidget::openLocalUrl(const QString &url) {
|
||||
QString u(url.trimmed());
|
||||
if (u.startsWith(QLatin1String("tg://resolve"), Qt::CaseInsensitive)) {
|
||||
QRegularExpressionMatch m = QRegularExpression(qsl("^tg://resolve/?\\?domain=([a-zA-Z0-9\\.\\_]+)$"), QRegularExpression::CaseInsensitiveOption).match(u);
|
||||
QRegularExpressionMatch m = QRegularExpression(qsl("^tg://resolve/?\\?domain=([a-zA-Z0-9\\.\\_]+)(&(start|startgroup)=([a-zA-Z0-9\\.\\_\\-]+))?(&|$)"), QRegularExpression::CaseInsensitiveOption).match(u);
|
||||
if (m.hasMatch()) {
|
||||
openUserByName(m.captured(1));
|
||||
QString start = m.captured(3), startToken = m.captured(4);
|
||||
openUserByName(m.captured(1), (start == qsl("startgroup")), startToken);
|
||||
}
|
||||
} else if (u.startsWith(QLatin1String("tg://join"), Qt::CaseInsensitive)) {
|
||||
QRegularExpressionMatch m = QRegularExpression(qsl("^tg://join/?\\?invite=([a-zA-Z0-9\\.\\_\\-]+)$"), QRegularExpression::CaseInsensitiveOption).match(u);
|
||||
QRegularExpressionMatch m = QRegularExpression(qsl("^tg://join/?\\?invite=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"), QRegularExpression::CaseInsensitiveOption).match(u);
|
||||
if (m.hasMatch()) {
|
||||
joinGroupByHash(m.captured(1));
|
||||
}
|
||||
} else if (u.startsWith(QLatin1String("tg://addstickers"), Qt::CaseInsensitive)) {
|
||||
QRegularExpressionMatch m = QRegularExpression(qsl("^tg://addstickers/?\\?set=([a-zA-Z0-9\\.\\_]+)$"), QRegularExpression::CaseInsensitiveOption).match(u);
|
||||
QRegularExpressionMatch m = QRegularExpression(qsl("^tg://addstickers/?\\?set=([a-zA-Z0-9\\.\\_]+)(&|$)"), QRegularExpression::CaseInsensitiveOption).match(u);
|
||||
if (m.hasMatch()) {
|
||||
stickersBox(MTP_inputStickerSetShortName(MTP_string(m.captured(1))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWidget::openUserByName(const QString &username, bool toProfile) {
|
||||
void MainWidget::openUserByName(const QString &username, bool toProfile, const QString &startToken) {
|
||||
App::wnd()->hideMediaview();
|
||||
|
||||
UserData *user = App::userByName(username);
|
||||
if (user) {
|
||||
if (toProfile) {
|
||||
showPeerProfile(user);
|
||||
if (user->botInfo && !user->botInfo->cantJoinGroups && !startToken.isEmpty()) {
|
||||
user->botInfo->startGroupToken = startToken;
|
||||
App::wnd()->showLayer(new ContactsBox(user));
|
||||
} else {
|
||||
showPeerProfile(user);
|
||||
}
|
||||
} else {
|
||||
if (user->botInfo) user->botInfo->startToken = startToken;
|
||||
emit showPeerAsync(user->id, 0, false, true);
|
||||
}
|
||||
} else {
|
||||
MTP::send(MTPcontacts_ResolveUsername(MTP_string(username)), rpcDone(&MainWidget::usernameResolveDone, toProfile), rpcFail(&MainWidget::usernameResolveFail, username));
|
||||
MTP::send(MTPcontacts_ResolveUsername(MTP_string(username)), rpcDone(&MainWidget::usernameResolveDone, qMakePair(toProfile, startToken)), rpcFail(&MainWidget::usernameResolveFail, username));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2539,13 +2602,19 @@ void MainWidget::onStickersInstalled(uint64 setId) {
|
||||
history.stickersInstalled(setId);
|
||||
}
|
||||
|
||||
void MainWidget::usernameResolveDone(bool toProfile, const MTPUser &user) {
|
||||
void MainWidget::usernameResolveDone(QPair<bool, QString> toProfileStartToken, const MTPUser &result) {
|
||||
App::wnd()->hideLayer();
|
||||
UserData *u = App::feedUsers(MTP_vector<MTPUser>(1, user));
|
||||
if (toProfile) {
|
||||
showPeerProfile(u);
|
||||
UserData *user = App::feedUsers(MTP_vector<MTPUser>(1, result));
|
||||
if (toProfileStartToken.first) {
|
||||
if (user->botInfo && !user->botInfo->cantJoinGroups && !toProfileStartToken.second.isEmpty()) {
|
||||
user->botInfo->startGroupToken = toProfileStartToken.second;
|
||||
App::wnd()->showLayer(new ContactsBox(user));
|
||||
} else {
|
||||
showPeerProfile(user);
|
||||
}
|
||||
} else {
|
||||
showPeer(u->id, 0, false, true);
|
||||
if (user->botInfo) user->botInfo->startToken = toProfileStartToken.second;
|
||||
showPeer(user->id, 0, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2637,7 +2706,7 @@ bool MainWidget::inviteImportFail(const RPCError &error) {
|
||||
|
||||
void MainWidget::startFull(const MTPVector<MTPUser> &users) {
|
||||
const QVector<MTPUser> &v(users.c_vector().v);
|
||||
if (v.isEmpty() || v[0].type() != mtpc_userSelf) { // wtf?..
|
||||
if (v.isEmpty() || v[0].type() != mtpc_user || !(v[0].c_user().vflags.v & MTPDuser_flag_self)) { // wtf?..
|
||||
return App::logOut();
|
||||
}
|
||||
start(v[0]);
|
||||
@@ -3025,7 +3094,7 @@ void MainWidget::handleUpdates(const MTPUpdates &updates) {
|
||||
return;
|
||||
}
|
||||
bool out = (d.vflags.v & MTPDmessage_flag_out);
|
||||
HistoryItem *item = App::histories().addToBack(MTP_message(d.vflags, d.vid, out ? MTP_int(MTP::authedId()) : d.vuser_id, MTP_peerUser(out ? d.vuser_id : MTP_int(MTP::authedId())), d.vfwd_from_id, d.vfwd_date, d.vreply_to_msg_id, d.vdate, d.vmessage, MTP_messageMediaEmpty()));
|
||||
HistoryItem *item = App::histories().addToBack(MTP_message(d.vflags, d.vid, out ? MTP_int(MTP::authedId()) : d.vuser_id, MTP_peerUser(out ? d.vuser_id : MTP_int(MTP::authedId())), d.vfwd_from_id, d.vfwd_date, d.vreply_to_msg_id, d.vdate, d.vmessage, MTP_messageMediaEmpty(), MTPnullMarkup));
|
||||
if (item) {
|
||||
history.peerMessagesUpdated(item->history()->peer->id);
|
||||
}
|
||||
@@ -3045,7 +3114,7 @@ void MainWidget::handleUpdates(const MTPUpdates &updates) {
|
||||
_byPtsUpdates.insert(ptsKey(SkippedUpdates), updates);
|
||||
return;
|
||||
}
|
||||
HistoryItem *item = App::histories().addToBack(MTP_message(d.vflags, d.vid, d.vfrom_id, MTP_peerChat(d.vchat_id), d.vfwd_from_id, d.vfwd_date, d.vreply_to_msg_id, d.vdate, d.vmessage, MTP_messageMediaEmpty()));
|
||||
HistoryItem *item = App::histories().addToBack(MTP_message(d.vflags, d.vid, d.vfrom_id, MTP_peerChat(d.vchat_id), d.vfwd_from_id, d.vfwd_date, d.vreply_to_msg_id, d.vdate, d.vmessage, MTP_messageMediaEmpty(), MTPnullMarkup));
|
||||
if (item) {
|
||||
history.peerMessagesUpdated(item->history()->peer->id);
|
||||
}
|
||||
@@ -3190,7 +3259,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
||||
|
||||
case mtpc_updateChatParticipants: {
|
||||
const MTPDupdateChatParticipants &d(update.c_updateChatParticipants());
|
||||
App::feedParticipants(d.vparticipants);
|
||||
App::feedParticipants(d.vparticipants, true);
|
||||
} break;
|
||||
|
||||
case mtpc_updateChatParticipantAdd: {
|
||||
|
||||
@@ -187,7 +187,7 @@ public:
|
||||
void start(const MTPUser &user);
|
||||
|
||||
void openLocalUrl(const QString &str);
|
||||
void openUserByName(const QString &name, bool toProfile = false);
|
||||
void openUserByName(const QString &name, bool toProfile = false, const QString &startToken = QString());
|
||||
void joinGroupByHash(const QString &hash);
|
||||
void stickersBox(const MTPInputStickerSet &set);
|
||||
|
||||
@@ -216,6 +216,7 @@ public:
|
||||
void dialogsToUp();
|
||||
void newUnreadMsg(History *history, HistoryItem *item);
|
||||
void historyWasRead();
|
||||
void historyCleared(History *history);
|
||||
|
||||
void peerBefore(const PeerData *inPeer, MsgId inMsg, PeerData *&outPeer, MsgId &outMsg);
|
||||
void peerAfter(const PeerData *inPeer, MsgId inMsg, PeerData *&outPeer, MsgId &outMsg);
|
||||
@@ -270,7 +271,7 @@ public:
|
||||
void removeContact(UserData *user);
|
||||
|
||||
void addParticipants(ChatData *chat, const QVector<UserData*> &users);
|
||||
bool addParticipantFail(ChatData *chat, const RPCError &e);
|
||||
bool addParticipantFail(UserData *user, const RPCError &e);
|
||||
|
||||
void kickParticipant(ChatData *chat, UserData *user);
|
||||
bool kickParticipantFail(ChatData *chat, const RPCError &e);
|
||||
@@ -285,6 +286,7 @@ public:
|
||||
void clearSelectedItems();
|
||||
|
||||
DialogsIndexed &contactsList();
|
||||
DialogsIndexed &dialogsList();
|
||||
|
||||
void sendMessage(History *history, const QString &text, MsgId replyTo);
|
||||
void sendPreparedText(History *hist, const QString &text, MsgId replyTo, WebPageId webPageId = 0);
|
||||
@@ -295,6 +297,9 @@ public:
|
||||
uint64 animActiveTime() const;
|
||||
void stopAnimActive();
|
||||
|
||||
void sendBotCommand(const QString &cmd, MsgId msgId);
|
||||
void insertBotCommand(const QString &cmd);
|
||||
|
||||
void searchMessages(const QString &query);
|
||||
void preloadOverviews(PeerData *peer);
|
||||
void mediaOverviewUpdated(PeerData *peer);
|
||||
@@ -447,7 +452,7 @@ private:
|
||||
void handleUpdates(const MTPUpdates &updates);
|
||||
bool updateFail(const RPCError &e);
|
||||
|
||||
void usernameResolveDone(bool toProfile, const MTPUser &user);
|
||||
void usernameResolveDone(QPair<bool, QString> toProfileStartToken, const MTPUser &result);
|
||||
bool usernameResolveFail(QString name, const RPCError &error);
|
||||
|
||||
void inviteCheckDone(QString hash, const MTPChatInvite &invite);
|
||||
|
||||
@@ -824,6 +824,7 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) {
|
||||
if (_current.isNull() && _currentGif.isNull()) {
|
||||
if (_doc->thumb->isNull()) {
|
||||
style::sprite thumbs[] = { st::mvDocBlue, st::mvDocGreen, st::mvDocRed, st::mvDocYellow };
|
||||
style::color colors[] = { st::mvDocBlueColor, st::mvDocGreenColor, st::mvDocRedColor, st::mvDocYellowColor };
|
||||
QString name = _doc->name.toLower(), mime = _doc->mime.toLower();
|
||||
if (name.endsWith(QLatin1String(".doc")) ||
|
||||
name.endsWith(QLatin1String(".txt")) ||
|
||||
@@ -831,17 +832,20 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) {
|
||||
mime.startsWith(QLatin1String("text/"))
|
||||
) {
|
||||
_docIcon = thumbs[0];
|
||||
_docIconColor = colors[0];
|
||||
} else if (
|
||||
name.endsWith(QLatin1String(".xls")) ||
|
||||
name.endsWith(QLatin1String(".csv"))
|
||||
) {
|
||||
_docIcon = thumbs[1];
|
||||
_docIconColor = colors[1];
|
||||
} else if (
|
||||
name.endsWith(QLatin1String(".pdf")) ||
|
||||
name.endsWith(QLatin1String(".ppt")) ||
|
||||
name.endsWith(QLatin1String(".key"))
|
||||
) {
|
||||
_docIcon = thumbs[2];
|
||||
_docIconColor = colors[2];
|
||||
} else if (
|
||||
name.endsWith(QLatin1String(".zip")) ||
|
||||
name.endsWith(QLatin1String(".rar")) ||
|
||||
@@ -851,10 +855,12 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) {
|
||||
name.endsWith(QLatin1String(".avi"))
|
||||
) {
|
||||
_docIcon = thumbs[3];
|
||||
_docIconColor = colors[3];
|
||||
} else {
|
||||
int ext = name.lastIndexOf('.');
|
||||
QChar ch = (ext >= 0 && ext + 1 < name.size()) ? name.at(ext + 1) : (name.isEmpty() ? (mime.isEmpty() ? '0' : mime.at(0)) : name.at(0));
|
||||
_docIcon = thumbs[ch.unicode() % 4];
|
||||
_docIconColor = colors[ch.unicode() % 4];
|
||||
}
|
||||
} else {
|
||||
_doc->thumb->load();
|
||||
@@ -1074,11 +1080,13 @@ void MediaView::paintEvent(QPaintEvent *e) {
|
||||
if (_docIconRect.intersects(r)) {
|
||||
icon = true;
|
||||
if (_doc->thumb->isNull()) {
|
||||
p.drawPixmap(_docIconRect.topLeft(), App::sprite(), _docIcon);
|
||||
if (!_doc->already().isEmpty() && (!_docRadialStart || _docRadialOpacity < 1)) {
|
||||
p.drawPixmap(_docIconRect.topLeft(), App::sprite(), _docIcon);
|
||||
p.setPen(st::mvDocExtColor->p);
|
||||
p.setFont(st::mvDocExtFont->f);
|
||||
p.drawText(_docIconRect.x() + (_docIconRect.width() - _docExtWidth) / 2, _docIconRect.y() + st::mvDocExtTop + st::mvDocExtFont->ascent, _docExt);
|
||||
} else {
|
||||
p.fillRect(_docIconRect, _docIconColor->b);
|
||||
}
|
||||
} else {
|
||||
int32 rf(cIntRetinaFactor());
|
||||
|
||||
@@ -140,6 +140,7 @@ private:
|
||||
int32 _full; // -1 - thumb, 0 - medium, 1 - full
|
||||
|
||||
style::sprite _docIcon;
|
||||
style::color _docIconColor;
|
||||
QString _docName, _docSize, _docExt;
|
||||
int32 _docNameWidth, _docSizeWidth, _docExtWidth;
|
||||
QRect _docRect, _docIconRect;
|
||||
|
||||
@@ -743,8 +743,9 @@ void MTPautoConnection::disconnectFromServer() {
|
||||
status = FinishedWork;
|
||||
}
|
||||
|
||||
void MTPautoConnection::connectToServer(const QString &addr, int32 port) {
|
||||
address = QUrl(qsl("http://%1:%2/api").arg(addr).arg(80));//not port - always 80 port for http transport
|
||||
void MTPautoConnection::connectToServer(const QString &addr, int32 port, int32 flags) {
|
||||
address = QUrl(((flags & MTPDdcOption_flag_ipv6) ? qsl("http://[%1]:%2/api") : qsl("http://%1:%2/api")).arg(addr).arg(80));//not p - always 80 port for http transport
|
||||
TCP_LOG(("HTTP Info: address is %1").arg(address.toDisplayString()));
|
||||
connect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*)));
|
||||
|
||||
_addr = addr;
|
||||
@@ -943,7 +944,7 @@ void MTPtcpConnection::disconnectFromServer() {
|
||||
sock.close();
|
||||
}
|
||||
|
||||
void MTPtcpConnection::connectToServer(const QString &addr, int32 port) {
|
||||
void MTPtcpConnection::connectToServer(const QString &addr, int32 port, int32 flags) {
|
||||
connect(&sock, SIGNAL(readyRead()), this, SLOT(socketRead()));
|
||||
sock.connectToHost(QHostAddress(addr), port);
|
||||
}
|
||||
@@ -1013,8 +1014,9 @@ void MTPhttpConnection::disconnectFromServer() {
|
||||
address = QUrl();
|
||||
}
|
||||
|
||||
void MTPhttpConnection::connectToServer(const QString &addr, int32 p) {
|
||||
address = QUrl(qsl("http://%1:%2/api").arg(addr).arg(80));//not p - always 80 port for http transport
|
||||
void MTPhttpConnection::connectToServer(const QString &addr, int32 p, int32 flags) {
|
||||
address = QUrl(((flags & MTPDdcOption_flag_ipv6) ? qsl("http://[%1]:%2/api") : qsl("http://%1:%2/api")).arg(addr).arg(80));//not p - always 80 port for http transport
|
||||
TCP_LOG(("HTTP Info: address is %1").arg(address.toDisplayString()));
|
||||
connect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*)));
|
||||
emit connected();
|
||||
}
|
||||
@@ -1062,19 +1064,30 @@ QString MTPhttpConnection::transport() const {
|
||||
return qsl("HTTP");
|
||||
}
|
||||
|
||||
void MTProtoConnectionPrivate::createConn() {
|
||||
if (conn) {
|
||||
conn->deleteLater();
|
||||
void MTProtoConnectionPrivate::createConn(bool createIPv4, bool createIPv6) {
|
||||
destroyConn();
|
||||
if (createIPv4) {
|
||||
if (cConnectionType() == dbictAuto) {
|
||||
_conn4 = new MTPautoConnection(thread());
|
||||
} else if (cConnectionType() == dbictTcpProxy) {
|
||||
_conn4 = new MTPtcpConnection(thread());
|
||||
} else {
|
||||
_conn4 = new MTPhttpConnection(thread());
|
||||
}
|
||||
connect(_conn4, SIGNAL(error(bool)), this, SLOT(onError4(bool)));
|
||||
connect(_conn4, SIGNAL(receivedSome()), this, SLOT(onReceivedSome()));
|
||||
}
|
||||
if (cConnectionType() == dbictAuto) {
|
||||
conn = new MTPautoConnection(thread());
|
||||
} else if (cConnectionType() == dbictTcpProxy) {
|
||||
conn = new MTPtcpConnection(thread());
|
||||
} else {
|
||||
conn = new MTPhttpConnection(thread());
|
||||
if (createIPv6) {
|
||||
if (cConnectionType() == dbictAuto) {
|
||||
_conn6 = new MTPautoConnection(thread());
|
||||
} else if (cConnectionType() == dbictTcpProxy) {
|
||||
_conn6 = new MTPtcpConnection(thread());
|
||||
} else {
|
||||
_conn6 = new MTPhttpConnection(thread());
|
||||
}
|
||||
connect(_conn6, SIGNAL(error(bool)), this, SLOT(onError6(bool)));
|
||||
connect(_conn6, SIGNAL(receivedSome()), this, SLOT(onReceivedSome()));
|
||||
}
|
||||
connect(conn, SIGNAL(error(bool)), this, SLOT(onError(bool)));
|
||||
connect(conn, SIGNAL(receivedSome()), this, SLOT(onReceivedSome()));
|
||||
firstSentAt = 0;
|
||||
if (oldConnection) {
|
||||
oldConnection = false;
|
||||
@@ -1083,16 +1096,35 @@ void MTProtoConnectionPrivate::createConn() {
|
||||
oldConnectionTimer.start(MTPConnectionOldTimeout);
|
||||
}
|
||||
|
||||
void MTProtoConnectionPrivate::destroyConn(MTPabstractConnection **conn) {
|
||||
if (conn) {
|
||||
if (*conn) {
|
||||
disconnect(*conn, SIGNAL(disconnected()), 0, 0);
|
||||
disconnect(*conn, SIGNAL(receivedData()), 0, 0);
|
||||
disconnect(*conn, SIGNAL(receivedSome()), 0, 0);
|
||||
|
||||
(*conn)->disconnectFromServer();
|
||||
(*conn)->deleteLater();
|
||||
*conn = 0;
|
||||
}
|
||||
} else {
|
||||
destroyConn(&_conn4);
|
||||
destroyConn(&_conn6);
|
||||
_conn = 0;
|
||||
}
|
||||
}
|
||||
|
||||
MTProtoConnectionPrivate::MTProtoConnectionPrivate(QThread *thread, MTProtoConnection *owner, MTPSessionData *data, uint32 _dc)
|
||||
: QObject(0)
|
||||
, _state(MTProtoConnection::Disconnected)
|
||||
, _needSessionReset(false)
|
||||
, dc(_dc)
|
||||
, _owner(owner)
|
||||
, conn(0)
|
||||
, _conn(0), _conn4(0), _conn6(0)
|
||||
, retryTimeout(1)
|
||||
, oldConnection(true)
|
||||
, receiveDelay(MTPMinReceiveDelay)
|
||||
, _waitForReceived(MTPMinReceiveDelay)
|
||||
, _waitForConnected(MTPMinConnectDelay)
|
||||
, firstSentAt(-1)
|
||||
, _pingId(0)
|
||||
, _pingIdToSend(0)
|
||||
@@ -1106,13 +1138,13 @@ MTProtoConnectionPrivate::MTProtoConnectionPrivate(QThread *thread, MTProtoConne
|
||||
, authKeyStrings(0) {
|
||||
|
||||
oldConnectionTimer.moveToThread(thread);
|
||||
connCheckTimer.moveToThread(thread);
|
||||
_waitForConnectedTimer.moveToThread(thread);
|
||||
_waitForReceivedTimer.moveToThread(thread);
|
||||
_waitForIPv4Timer.moveToThread(thread);
|
||||
_pingSender.moveToThread(thread);
|
||||
retryTimer.moveToThread(thread);
|
||||
moveToThread(thread);
|
||||
|
||||
// createConn();
|
||||
|
||||
if (!dc) {
|
||||
QReadLocker lock(mtpDcOptionsMutex());
|
||||
const mtpDcOptions &options(cDcOptions());
|
||||
@@ -1129,7 +1161,9 @@ MTProtoConnectionPrivate::MTProtoConnectionPrivate(QThread *thread, MTProtoConne
|
||||
connect(thread, SIGNAL(finished()), this, SLOT(doFinish()));
|
||||
|
||||
connect(&retryTimer, SIGNAL(timeout()), this, SLOT(retryByTimer()));
|
||||
connect(&connCheckTimer, SIGNAL(timeout()), this, SLOT(onBadConnection()));
|
||||
connect(&_waitForConnectedTimer, SIGNAL(timeout()), this, SLOT(onWaitConnectedFailed()));
|
||||
connect(&_waitForReceivedTimer, SIGNAL(timeout()), this, SLOT(onWaitReceivedFailed()));
|
||||
connect(&_waitForIPv4Timer, SIGNAL(timeout()), this, SLOT(onWaitIPv4Failed()));
|
||||
connect(&oldConnectionTimer, SIGNAL(timeout()), this, SLOT(onOldConnection()));
|
||||
connect(&_pingSender, SIGNAL(timeout()), this, SLOT(onPingSender()));
|
||||
connect(sessionData->owner(), SIGNAL(authKeyCreated()), this, SLOT(updateAuthKey()), Qt::QueuedConnection);
|
||||
@@ -1180,10 +1214,10 @@ int32 MTProtoConnectionPrivate::getState() const {
|
||||
}
|
||||
|
||||
QString MTProtoConnectionPrivate::transport() const {
|
||||
if (!conn || _state < 0) {
|
||||
if ((!_conn4 && !_conn6) || _state < 0) {
|
||||
return QString();
|
||||
}
|
||||
return conn->transport();
|
||||
return (_conn4 ? _conn4 : _conn6)->transport();
|
||||
}
|
||||
|
||||
bool MTProtoConnectionPrivate::setState(int32 state, int32 ifState) {
|
||||
@@ -1418,7 +1452,7 @@ mtpMsgId MTProtoConnectionPrivate::placeToContainer(mtpRequest &toSendRequest, m
|
||||
|
||||
void MTProtoConnectionPrivate::tryToSend() {
|
||||
QReadLocker lockFinished(&sessionDataMutex);
|
||||
if (!sessionData || !conn) {
|
||||
if (!sessionData || !_conn) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1510,7 +1544,7 @@ void MTProtoConnectionPrivate::tryToSend() {
|
||||
stateRequest->msDate = getms(true); // > 0 - can send without container
|
||||
stateRequest->requestId = reqid();// add to haveSent / wereAcked maps, but don't add to requestMap
|
||||
}
|
||||
if (conn->usingHttpWait()) {
|
||||
if (_conn->usingHttpWait()) {
|
||||
MTPHttpWait req(MTP_http_wait(MTP_int(100), MTP_int(30), MTP_int(25000)));
|
||||
|
||||
httpWaitRequest = mtpRequestData::prepare(req.innerLength() >> 2);
|
||||
@@ -1731,44 +1765,63 @@ void MTProtoConnectionPrivate::restartNow() {
|
||||
}
|
||||
|
||||
void MTProtoConnectionPrivate::socketStart(bool afterConfig) {
|
||||
if (!conn) createConn();
|
||||
|
||||
if (conn->isConnected()) {
|
||||
onConnected();
|
||||
return;
|
||||
}
|
||||
setState(MTProtoConnection::Connecting);
|
||||
_pingId = _pingMsgId = _pingIdToSend = _pingSendAt = 0;
|
||||
_pingSender.stop();
|
||||
|
||||
std::string ip;
|
||||
uint32 port = 0;
|
||||
int32 flags4 = 0, flags6 = 0;
|
||||
std::string ip4, ip6;
|
||||
uint32 port4 = 0, port6 = 0;
|
||||
{
|
||||
QReadLocker lock(mtpDcOptionsMutex());
|
||||
const mtpDcOptions &options(cDcOptions());
|
||||
mtpDcOptions::const_iterator dcIndex = options.constFind(dc % _mtp_internal::dcShift);
|
||||
DEBUG_LOG(("MTP Info: connecting to DC %1..").arg(dc));
|
||||
if (dcIndex != options.cend()) {
|
||||
ip = dcIndex->ip;
|
||||
port = dcIndex->port;
|
||||
mtpDcOptions::const_iterator dcIndex4 = options.constFind(dc % _mtp_internal::dcShift);
|
||||
if (dcIndex4 != options.cend()) {
|
||||
ip4 = dcIndex4->ip;
|
||||
flags4 = dcIndex4->flags;
|
||||
port4 = dcIndex4->port;
|
||||
}
|
||||
mtpDcOptions::const_iterator dcIndex6 = options.constFind((dc % _mtp_internal::dcShift) + (_mtp_internal::dcShift * MTPDdcOption_flag_ipv6));
|
||||
if (dcIndex6 != options.cend()) {
|
||||
ip6 = dcIndex6->ip;
|
||||
flags6 = dcIndex6->flags;
|
||||
port6 = dcIndex6->port;
|
||||
}
|
||||
}
|
||||
if (!port || ip.empty()) {
|
||||
bool noIPv4 = (!port4 || ip4.empty()), noIPv6 = (!port6 || ip6.empty());
|
||||
if (noIPv4 && noIPv6) {
|
||||
if (afterConfig) {
|
||||
LOG(("MTP Error: DC %1 options not found right after config load!").arg(dc));
|
||||
if (noIPv4) LOG(("MTP Error: DC %1 options for IPv4 not found right after config load!").arg(dc));
|
||||
if (noIPv6) LOG(("MTP Error: DC %1 options for IPv6 not found right after config load!").arg(dc));
|
||||
return restart();
|
||||
}
|
||||
DEBUG_LOG(("MTP Info: DC %1 options not found, waiting for config").arg(dc));
|
||||
if (noIPv4) DEBUG_LOG(("MTP Info: DC %1 options for IPv4 not found, waiting for config").arg(dc));
|
||||
if (noIPv6) DEBUG_LOG(("MTP Info: DC %1 options for IPv6 not found, waiting for config").arg(dc));
|
||||
connect(mtpConfigLoader(), SIGNAL(loaded()), this, SLOT(onConfigLoaded()));
|
||||
mtpConfigLoader()->load();
|
||||
return;
|
||||
}
|
||||
DEBUG_LOG(("MTP Info: socket connection to %1:%2..").arg(ip.c_str()).arg(port));
|
||||
|
||||
connect(conn, SIGNAL(connected()), this, SLOT(onConnected()));
|
||||
connect(conn, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
if (afterConfig && (_conn4 || _conn6)) return;
|
||||
|
||||
conn->connectToServer(ip.c_str(), port);
|
||||
createConn(!noIPv4, !noIPv6);
|
||||
retryTimer.stop();
|
||||
_waitForConnectedTimer.stop();
|
||||
|
||||
setState(MTProtoConnection::Connecting);
|
||||
_pingId = _pingMsgId = _pingIdToSend = _pingSendAt = 0;
|
||||
_pingSender.stop();
|
||||
|
||||
if (!noIPv4) DEBUG_LOG(("MTP Info: creating IPv4 connection to %1:%2..").arg(ip4.c_str()).arg(port4));
|
||||
if (!noIPv6) DEBUG_LOG(("MTP Info: creating IPv6 connection to [%1]:%2..").arg(ip6.c_str()).arg(port6));
|
||||
|
||||
_waitForConnectedTimer.start(_waitForConnected);
|
||||
if (_conn4) {
|
||||
connect(_conn4, SIGNAL(connected()), this, SLOT(onConnected4()));
|
||||
connect(_conn4, SIGNAL(disconnected()), this, SLOT(onDisconnected4()));
|
||||
_conn4->connectToServer(ip4.c_str(), port4, flags4);
|
||||
}
|
||||
if (_conn6) {
|
||||
connect(_conn6, SIGNAL(connected()), this, SLOT(onConnected6()));
|
||||
connect(_conn6, SIGNAL(disconnected()), this, SLOT(onDisconnected6()));
|
||||
_conn6->connectToServer(ip6.c_str(), port6, flags6);
|
||||
}
|
||||
}
|
||||
|
||||
void MTProtoConnectionPrivate::restart(bool maybeBadKey) {
|
||||
@@ -1777,7 +1830,8 @@ void MTProtoConnectionPrivate::restart(bool maybeBadKey) {
|
||||
|
||||
DEBUG_LOG(("MTP Info: restarting MTProtoConnection, maybe bad key = %1").arg(logBool(maybeBadKey)));
|
||||
|
||||
connCheckTimer.stop();
|
||||
_waitForReceivedTimer.stop();
|
||||
_waitForConnectedTimer.stop();
|
||||
|
||||
mtpAuthKeyPtr key(sessionData->getKey());
|
||||
if (key) {
|
||||
@@ -1805,12 +1859,12 @@ void MTProtoConnectionPrivate::restart(bool maybeBadKey) {
|
||||
}
|
||||
|
||||
void MTProtoConnectionPrivate::onSentSome(uint64 size) {
|
||||
if (!connCheckTimer.isActive()) {
|
||||
uint64 remain = receiveDelay;
|
||||
if (!_waitForReceivedTimer.isActive()) {
|
||||
uint64 remain = _waitForReceived;
|
||||
if (!oldConnection) {
|
||||
uint64 remainBySize = size * receiveDelay / 8192; // 8kb / sec, so 512 kb give 64 sec
|
||||
uint64 remainBySize = size * _waitForReceived / 8192; // 8kb / sec, so 512 kb give 64 sec
|
||||
remain = snap(remainBySize, remain, uint64(MTPMaxReceiveDelay));
|
||||
if (remain != receiveDelay) {
|
||||
if (remain != _waitForReceived) {
|
||||
DEBUG_LOG(("Checking connect for request with size %1 bytes, delay will be %2").arg(size).arg(remain));
|
||||
}
|
||||
}
|
||||
@@ -1819,7 +1873,7 @@ void MTProtoConnectionPrivate::onSentSome(uint64 size) {
|
||||
} else if (dc >= MTP::dld[0] && dc < MTP::dld[MTPDownloadSessionsCount - 1] + _mtp_internal::dcShift) {
|
||||
remain *= MTPDownloadSessionsCount;
|
||||
}
|
||||
connCheckTimer.start(remain);
|
||||
_waitForReceivedTimer.start(remain);
|
||||
}
|
||||
if (!firstSentAt) firstSentAt = getms(true);
|
||||
}
|
||||
@@ -1830,20 +1884,20 @@ void MTProtoConnectionPrivate::onReceivedSome() {
|
||||
DEBUG_LOG(("This connection marked as not old!"));
|
||||
}
|
||||
oldConnectionTimer.start(MTPConnectionOldTimeout);
|
||||
connCheckTimer.stop();
|
||||
_waitForReceivedTimer.stop();
|
||||
if (firstSentAt > 0) {
|
||||
int32 ms = getms(true) - firstSentAt;
|
||||
DEBUG_LOG(("MTP Info: response in %1ms, receiveDelay: %2ms").arg(ms).arg(receiveDelay));
|
||||
DEBUG_LOG(("MTP Info: response in %1ms, _waitForReceived: %2ms").arg(ms).arg(_waitForReceived));
|
||||
|
||||
if (ms > 0 && ms * 2 < int32(receiveDelay)) receiveDelay = qMax(ms * 2, int32(MTPMinReceiveDelay));
|
||||
if (ms > 0 && ms * 2 < int32(_waitForReceived)) _waitForReceived = qMax(ms * 2, int32(MTPMinReceiveDelay));
|
||||
firstSentAt = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void MTProtoConnectionPrivate::onOldConnection() {
|
||||
oldConnection = true;
|
||||
receiveDelay = MTPMinReceiveDelay;
|
||||
DEBUG_LOG(("This connection marked as old! delay now %1ms").arg(receiveDelay));
|
||||
_waitForReceived = MTPMinReceiveDelay;
|
||||
DEBUG_LOG(("This connection marked as old! _waitForReceived now %1ms").arg(_waitForReceived));
|
||||
}
|
||||
|
||||
void MTProtoConnectionPrivate::onPingSender() {
|
||||
@@ -1867,14 +1921,14 @@ void MTProtoConnectionPrivate::onPingSendForce() {
|
||||
}
|
||||
}
|
||||
|
||||
void MTProtoConnectionPrivate::onBadConnection() {
|
||||
void MTProtoConnectionPrivate::onWaitReceivedFailed() {
|
||||
if (cConnectionType() != dbictAuto && cConnectionType() != dbictTcpProxy) {
|
||||
return;
|
||||
}
|
||||
|
||||
DEBUG_LOG(("MTP Info: bad connection, delay: %1ms").arg(receiveDelay));
|
||||
if (receiveDelay < MTPMaxReceiveDelay) {
|
||||
receiveDelay *= 2;
|
||||
DEBUG_LOG(("MTP Info: bad connection, _waitForReceived: %1ms").arg(_waitForReceived));
|
||||
if (_waitForReceived < MTPMaxReceiveDelay) {
|
||||
_waitForReceived *= 2;
|
||||
}
|
||||
doDisconnect();
|
||||
restarted = true;
|
||||
@@ -1884,16 +1938,32 @@ void MTProtoConnectionPrivate::onBadConnection() {
|
||||
QTimer::singleShot(0, this, SLOT(socketStart()));
|
||||
}
|
||||
|
||||
void MTProtoConnectionPrivate::doDisconnect() {
|
||||
if (conn) {
|
||||
disconnect(conn, SIGNAL(disconnected()), 0, 0);
|
||||
disconnect(conn, SIGNAL(receivedData()), 0, 0);
|
||||
disconnect(conn, SIGNAL(receivedSome()), 0, 0);
|
||||
void MTProtoConnectionPrivate::onWaitConnectedFailed() {
|
||||
DEBUG_LOG(("MTP Info: can't connect in %1ms").arg(_waitForConnected));
|
||||
if (_waitForConnected < MTPMaxConnectDelay) _waitForConnected *= 2;
|
||||
|
||||
conn->disconnectFromServer();
|
||||
conn->deleteLater();
|
||||
conn = 0;
|
||||
doDisconnect();
|
||||
restarted = true;
|
||||
|
||||
DEBUG_LOG(("MTP Info: immediate restart!"));
|
||||
QTimer::singleShot(0, this, SLOT(socketStart()));
|
||||
}
|
||||
|
||||
void MTProtoConnectionPrivate::onWaitIPv4Failed() {
|
||||
_conn = _conn6;
|
||||
destroyConn(&_conn4);
|
||||
|
||||
if (_conn) {
|
||||
DEBUG_LOG(("MTP Info: can't connect through IPv4, using IPv6 connection."));
|
||||
|
||||
updateAuthKey();
|
||||
} else {
|
||||
restart();
|
||||
}
|
||||
}
|
||||
|
||||
void MTProtoConnectionPrivate::doDisconnect() {
|
||||
destroyConn();
|
||||
|
||||
unlockKey();
|
||||
|
||||
@@ -1928,9 +1998,8 @@ void MTProtoConnectionPrivate::handleReceived() {
|
||||
return restart();
|
||||
}
|
||||
|
||||
|
||||
while (conn->received().size()) {
|
||||
const mtpBuffer &encryptedBuf(conn->received().front());
|
||||
while (_conn->received().size()) {
|
||||
const mtpBuffer &encryptedBuf(_conn->received().front());
|
||||
uint32 len = encryptedBuf.size();
|
||||
const mtpPrime *encrypted(encryptedBuf.data());
|
||||
if (len < 18) { // 2 auth_key_id, 4 msg_key, 2 salt, 2 session, 2 msg_id, 1 seq_no, 1 length, (1 data + 3 padding) min
|
||||
@@ -1958,14 +2027,14 @@ void MTProtoConnectionPrivate::handleReceived() {
|
||||
if (uint32(dataBuffer.size()) < msgLen + 8 * sizeof(mtpPrime) || (msgLen & 0x03)) {
|
||||
LOG(("TCP Error: bad msg_len received %1, data size: %2").arg(msgLen).arg(dataBuffer.size()));
|
||||
TCP_LOG(("TCP Error: bad message %1").arg(mb(encrypted, len * sizeof(mtpPrime)).str()));
|
||||
conn->received().pop_front();
|
||||
_conn->received().pop_front();
|
||||
return restart();
|
||||
}
|
||||
uchar sha1Buffer[20];
|
||||
if (memcmp(&msgKey, hashSha1(data, msgLen + 8 * sizeof(mtpPrime), sha1Buffer) + 1, sizeof(msgKey))) {
|
||||
LOG(("TCP Error: bad SHA1 hash after aesDecrypt in message"));
|
||||
TCP_LOG(("TCP Error: bad message %1").arg(mb(encrypted, len * sizeof(mtpPrime)).str()));
|
||||
conn->received().pop_front();
|
||||
_conn->received().pop_front();
|
||||
return restart();
|
||||
}
|
||||
TCP_LOG(("TCP Info: decrypted message %1,%2,%3 is %4 len").arg(msgId).arg(seqNo).arg(logBool(needAck)).arg(msgLen + 8 * sizeof(mtpPrime)));
|
||||
@@ -1974,11 +2043,11 @@ void MTProtoConnectionPrivate::handleReceived() {
|
||||
if (session != serverSession) {
|
||||
LOG(("MTP Error: bad server session received"));
|
||||
TCP_LOG(("MTP Error: bad server session %1 instead of %2 in message received").arg(session).arg(serverSession));
|
||||
conn->received().pop_front();
|
||||
_conn->received().pop_front();
|
||||
return restart();
|
||||
}
|
||||
|
||||
conn->received().pop_front();
|
||||
_conn->received().pop_front();
|
||||
|
||||
int32 serverTime((int32)(msgId >> 32)), clientTime(unixtime());
|
||||
bool isReply = ((msgId & 0x03) == 1);
|
||||
@@ -2061,6 +2130,7 @@ void MTProtoConnectionPrivate::handleReceived() {
|
||||
_needSessionReset = (res < -1);
|
||||
return restart();
|
||||
}
|
||||
retryTimeout = 1; // reset restart() timer
|
||||
|
||||
if (!sessionData->isCheckedKey()) {
|
||||
DEBUG_LOG(("MTP Info: marked auth key as checked"));
|
||||
@@ -2073,7 +2143,7 @@ void MTProtoConnectionPrivate::handleReceived() {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (conn->needHttpWait()) {
|
||||
if (_conn->needHttpWait()) {
|
||||
emit sendHttpWaitAsync();
|
||||
}
|
||||
}
|
||||
@@ -2570,8 +2640,6 @@ int32 MTProtoConnectionPrivate::handleOneReceived(const mtpPrime *from, const mt
|
||||
}
|
||||
}
|
||||
requestsAcked(ids, true);
|
||||
|
||||
retryTimeout = 1; // reset restart() timer
|
||||
} return 1;
|
||||
|
||||
}
|
||||
@@ -2818,24 +2886,72 @@ void MTProtoConnectionPrivate::resendMany(QVector<quint64> msgIds, quint64 msCan
|
||||
emit resendManyAsync(msgIds, msCanWait, forceContainer, sendMsgStateInfo);
|
||||
}
|
||||
|
||||
void MTProtoConnectionPrivate::onConnected() {
|
||||
void MTProtoConnectionPrivate::onConnected4() {
|
||||
_waitForConnected = MTPMinConnectDelay;
|
||||
_waitForConnectedTimer.stop();
|
||||
|
||||
_waitForIPv4Timer.stop();
|
||||
|
||||
QReadLocker lockFinished(&sessionDataMutex);
|
||||
if (!sessionData) return;
|
||||
|
||||
disconnect(conn, SIGNAL(connected()), this, SLOT(onConnected()));
|
||||
if (!conn->isConnected()) {
|
||||
LOG(("Connection Error: not connected in onConnected(), state: %1").arg(conn->debugState()));
|
||||
disconnect(_conn4, SIGNAL(connected()), this, SLOT(onConnected4()));
|
||||
if (!_conn4->isConnected()) {
|
||||
LOG(("Connection Error: not connected in onConnected4(), state: %1").arg(_conn4->debugState()));
|
||||
return restart();
|
||||
}
|
||||
|
||||
TCP_LOG(("Connection Info: connection succeed."));
|
||||
_conn = _conn4;
|
||||
destroyConn(&_conn6);
|
||||
|
||||
DEBUG_LOG(("MTP Info: connection through IPv4 succeed."));
|
||||
|
||||
updateAuthKey();
|
||||
}
|
||||
|
||||
void MTProtoConnectionPrivate::onConnected6() {
|
||||
_waitForConnected = MTPMinConnectDelay;
|
||||
_waitForConnectedTimer.stop();
|
||||
|
||||
QReadLocker lockFinished(&sessionDataMutex);
|
||||
if (!sessionData) return;
|
||||
|
||||
disconnect(_conn6, SIGNAL(connected()), this, SLOT(onConnected()));
|
||||
if (!_conn6->isConnected()) {
|
||||
LOG(("Connection Error: not connected in onConnected(), state: %1").arg(_conn6->debugState()));
|
||||
return restart();
|
||||
}
|
||||
|
||||
DEBUG_LOG(("MTP Info: connection through IPv6 succeed, waiting IPv4 for %1ms.").arg(MTPIPv4ConnectionWaitTimeout));
|
||||
|
||||
_waitForIPv4Timer.start(MTPIPv4ConnectionWaitTimeout);
|
||||
}
|
||||
|
||||
void MTProtoConnectionPrivate::onDisconnected4() {
|
||||
if (_conn && _conn == _conn6) return; // disconnected the unused
|
||||
|
||||
if (_conn || !_conn6) {
|
||||
destroyConn();
|
||||
restart();
|
||||
} else {
|
||||
destroyConn(&_conn4);
|
||||
}
|
||||
}
|
||||
|
||||
void MTProtoConnectionPrivate::onDisconnected6() {
|
||||
if (_conn && _conn == _conn4) return; // disconnected the unused
|
||||
|
||||
if (_conn || !_conn4) {
|
||||
destroyConn();
|
||||
restart();
|
||||
} else {
|
||||
destroyConn(&_conn6);
|
||||
}
|
||||
}
|
||||
|
||||
void MTProtoConnectionPrivate::updateAuthKey() {
|
||||
QReadLocker lockFinished(&sessionDataMutex);
|
||||
if (!sessionData || !conn) return;
|
||||
if (!sessionData || !_conn) return;
|
||||
|
||||
DEBUG_LOG(("AuthKey Info: MTProtoConnection updating key from MTProtoSession, dc %1").arg(dc));
|
||||
uint64 newKeyId = 0;
|
||||
@@ -2878,20 +2994,20 @@ void MTProtoConnectionPrivate::updateAuthKey() {
|
||||
MTPReq_pq req_pq;
|
||||
req_pq.vnonce = authKeyData->nonce;
|
||||
|
||||
connect(conn, SIGNAL(receivedData()), this, SLOT(pqAnswered()));
|
||||
connect(_conn, SIGNAL(receivedData()), this, SLOT(pqAnswered()));
|
||||
|
||||
DEBUG_LOG(("AuthKey Info: sending Req_pq.."));
|
||||
sendRequestNotSecure(req_pq);
|
||||
}
|
||||
|
||||
void MTProtoConnectionPrivate::clearMessages() {
|
||||
if (keyId && keyId != mtpAuthKey::RecreateKeyId && conn) {
|
||||
conn->received().clear();
|
||||
if (keyId && keyId != mtpAuthKey::RecreateKeyId && _conn) {
|
||||
_conn->received().clear();
|
||||
}
|
||||
}
|
||||
|
||||
void MTProtoConnectionPrivate::pqAnswered() {
|
||||
disconnect(conn, SIGNAL(receivedData()), this, SLOT(pqAnswered()));
|
||||
disconnect(_conn, SIGNAL(receivedData()), this, SLOT(pqAnswered()));
|
||||
DEBUG_LOG(("AuthKey Info: receiving Req_pq answer.."));
|
||||
|
||||
MTPReq_pq::ResponseType res_pq;
|
||||
@@ -2987,14 +3103,14 @@ void MTProtoConnectionPrivate::pqAnswered() {
|
||||
return restart();
|
||||
}
|
||||
|
||||
connect(conn, SIGNAL(receivedData()), this, SLOT(dhParamsAnswered()));
|
||||
connect(_conn, SIGNAL(receivedData()), this, SLOT(dhParamsAnswered()));
|
||||
|
||||
DEBUG_LOG(("AuthKey Info: sending Req_DH_params.."));
|
||||
sendRequestNotSecure(req_DH_params);
|
||||
}
|
||||
|
||||
void MTProtoConnectionPrivate::dhParamsAnswered() {
|
||||
disconnect(conn, SIGNAL(receivedData()), this, SLOT(dhParamsAnswered()));
|
||||
disconnect(_conn, SIGNAL(receivedData()), this, SLOT(dhParamsAnswered()));
|
||||
DEBUG_LOG(("AuthKey Info: receiving Req_DH_params answer.."));
|
||||
|
||||
MTPReq_DH_params::ResponseType res_DH_params;
|
||||
@@ -3169,7 +3285,7 @@ void MTProtoConnectionPrivate::dhClientParamsSend() {
|
||||
|
||||
aesEncrypt(&encBuffer[0], &sdhEncString[0], encFullSize * sizeof(mtpPrime), authKeyData->aesKey, authKeyData->aesIV);
|
||||
|
||||
connect(conn, SIGNAL(receivedData()), this, SLOT(dhClientParamsAnswered()));
|
||||
connect(_conn, SIGNAL(receivedData()), this, SLOT(dhClientParamsAnswered()));
|
||||
|
||||
DEBUG_LOG(("AuthKey Info: sending Req_client_DH_params.."));
|
||||
sendRequestNotSecure(req_client_DH_params);
|
||||
@@ -3179,7 +3295,7 @@ void MTProtoConnectionPrivate::dhClientParamsAnswered() {
|
||||
QReadLocker lockFinished(&sessionDataMutex);
|
||||
if (!sessionData) return;
|
||||
|
||||
disconnect(conn, SIGNAL(receivedData()), this, SLOT(dhClientParamsAnswered()));
|
||||
disconnect(_conn, SIGNAL(receivedData()), this, SLOT(dhClientParamsAnswered()));
|
||||
DEBUG_LOG(("AuthKey Info: receiving Req_client_DH_params answer.."));
|
||||
|
||||
MTPSet_client_DH_params::ResponseType res_client_DH_params;
|
||||
@@ -3274,7 +3390,7 @@ void MTProtoConnectionPrivate::dhClientParamsAnswered() {
|
||||
void MTProtoConnectionPrivate::authKeyCreated() {
|
||||
clearAuthKeyData();
|
||||
|
||||
connect(conn, SIGNAL(receivedData()), this, SLOT(handleReceived()));
|
||||
connect(_conn, SIGNAL(receivedData()), this, SLOT(handleReceived()));
|
||||
|
||||
if (sessionData->getSalt()) { // else receive salt in bad_server_salt first, then try to send all the requests
|
||||
setState(MTProtoConnection::Connected);
|
||||
@@ -3307,9 +3423,32 @@ void MTProtoConnectionPrivate::clearAuthKeyData() {
|
||||
}
|
||||
}
|
||||
|
||||
void MTProtoConnectionPrivate::onError(bool mayBeBadKey) {
|
||||
MTP_LOG(dc, ("Restarting after error, maybe bad key: %1..").arg(logBool(mayBeBadKey)));
|
||||
return restart(mayBeBadKey);
|
||||
void MTProtoConnectionPrivate::onError4(bool mayBeBadKey) {
|
||||
if (_conn && _conn == _conn6) return; // error in the unused
|
||||
|
||||
if (_conn || !_conn6) {
|
||||
destroyConn();
|
||||
_waitForConnectedTimer.stop();
|
||||
|
||||
MTP_LOG(dc, ("Restarting after error in IPv4 connection, maybe bad key: %1..").arg(logBool(mayBeBadKey)));
|
||||
return restart(mayBeBadKey);
|
||||
} else {
|
||||
destroyConn(&_conn4);
|
||||
}
|
||||
}
|
||||
|
||||
void MTProtoConnectionPrivate::onError6(bool mayBeBadKey) {
|
||||
if (_conn && _conn == _conn4) return; // error in the unused
|
||||
|
||||
if (_conn || !_conn4) {
|
||||
destroyConn();
|
||||
_waitForConnectedTimer.stop();
|
||||
|
||||
MTP_LOG(dc, ("Restarting after error in IPv6 connection, maybe bad key: %1..").arg(logBool(mayBeBadKey)));
|
||||
return restart(mayBeBadKey);
|
||||
} else {
|
||||
destroyConn(&_conn6);
|
||||
}
|
||||
}
|
||||
|
||||
void MTProtoConnectionPrivate::onReadyData() {
|
||||
@@ -3336,7 +3475,7 @@ void MTProtoConnectionPrivate::sendRequestNotSecure(const TRequest &request) {
|
||||
|
||||
DEBUG_LOG(("AuthKey Info: sending request, size: %1, num: %2, time: %3").arg(requestSize).arg(authKeyData->req_num).arg(buffer[5]));
|
||||
|
||||
conn->sendData(buffer);
|
||||
_conn->sendData(buffer);
|
||||
|
||||
onSentSome(buffer.size() * sizeof(mtpPrime));
|
||||
|
||||
@@ -3350,12 +3489,12 @@ bool MTProtoConnectionPrivate::readResponseNotSecure(TResponse &response) {
|
||||
onReceivedSome();
|
||||
|
||||
try {
|
||||
if (conn->received().isEmpty()) {
|
||||
if (_conn->received().isEmpty()) {
|
||||
LOG(("AuthKey Error: trying to read response from empty received list"));
|
||||
return false;
|
||||
}
|
||||
mtpBuffer buffer(conn->received().front());
|
||||
conn->received().pop_front();
|
||||
mtpBuffer buffer(_conn->received().front());
|
||||
_conn->received().pop_front();
|
||||
|
||||
const mtpPrime *answer(buffer.constData());
|
||||
uint32 len = buffer.size();
|
||||
@@ -3426,8 +3565,8 @@ bool MTProtoConnectionPrivate::sendRequest(mtpRequest &request, bool needAnyResp
|
||||
|
||||
DEBUG_LOG(("MTP Info: sending request, size: %1, num: %2, time: %3").arg(fullSize + 6).arg((*request)[4]).arg((*request)[5]));
|
||||
|
||||
conn->setSentEncrypted();
|
||||
conn->sendData(result);
|
||||
_conn->setSentEncrypted();
|
||||
_conn->sendData(result);
|
||||
|
||||
if (needAnyResponse) {
|
||||
onSentSome(result.size() * sizeof(mtpPrime));
|
||||
|
||||
@@ -25,9 +25,27 @@ enum {
|
||||
MTPDmessage_flag_out = (1 << 1),
|
||||
MTPDmessage_flag_notify_by_from = (1 << 4),
|
||||
MTPDmessage_flag_media_unread = (1 << 5),
|
||||
|
||||
MTPmessages_SendMessage_flag_skipWebPage = (1 << 1),
|
||||
|
||||
MTPDdcOption_flag_ipv6 = (1 << 0),
|
||||
MTPDdcOption_flag_files = (1 << 1),
|
||||
|
||||
MTPDuser_flag_self = (1 << 10),
|
||||
MTPDuser_flag_contact = (1 << 11),
|
||||
MTPDuser_flag_mutual_contact = (1 << 12),
|
||||
MTPDuser_flag_deleted = (1 << 13),
|
||||
MTPDuser_flag_bot = (1 << 14),
|
||||
MTPDuser_flag_bot_reads_all = (1 << 15),
|
||||
MTPDuser_flag_bot_cant_join = (1 << 16),
|
||||
|
||||
MTPDreplyKeyboardMarkup_flag_resize = (1 << 0),
|
||||
MTPDreplyKeyboardMarkup_flag_single_use = (1 << 1),
|
||||
MTPDreplyKeyboardMarkup_flag_ZERO = (1 << 31) // client side flag for zeroMarkup
|
||||
};
|
||||
|
||||
static const MTPReplyMarkup MTPnullMarkup = MTP_replyKeyboardMarkup(MTP_int(0), MTP_vector<MTPKeyboardButtonRow>(0));
|
||||
|
||||
#include "mtproto/mtpPublicRSA.h"
|
||||
#include "mtproto/mtpAuthKey.h"
|
||||
|
||||
@@ -97,11 +115,6 @@ public:
|
||||
int32 state() const;
|
||||
QString transport() const;
|
||||
|
||||
/*template <typename TRequest> // not used
|
||||
uint64 sendAsync(const TRequest &request) {
|
||||
return data->sendAsync(request);
|
||||
}*/
|
||||
|
||||
private:
|
||||
|
||||
QThread *thread;
|
||||
@@ -125,7 +138,7 @@ public:
|
||||
|
||||
virtual void sendData(mtpBuffer &buffer) = 0; // has size + 3, buffer[0] = len, buffer[1] = packetnum, buffer[last] = crc32
|
||||
virtual void disconnectFromServer() = 0;
|
||||
virtual void connectToServer(const QString &addr, int32 port) = 0;
|
||||
virtual void connectToServer(const QString &addr, int32 port, int32 flags) = 0;
|
||||
virtual bool isConnected() = 0;
|
||||
virtual bool usingHttpWait() {
|
||||
return false;
|
||||
@@ -193,7 +206,7 @@ public:
|
||||
|
||||
void sendData(mtpBuffer &buffer);
|
||||
void disconnectFromServer();
|
||||
void connectToServer(const QString &addr, int32 port);
|
||||
void connectToServer(const QString &addr, int32 port, int32 flags);
|
||||
bool isConnected();
|
||||
bool usingHttpWait();
|
||||
bool needHttpWait();
|
||||
@@ -255,7 +268,7 @@ public:
|
||||
|
||||
void sendData(mtpBuffer &buffer);
|
||||
void disconnectFromServer();
|
||||
void connectToServer(const QString &addr, int32 port);
|
||||
void connectToServer(const QString &addr, int32 port, int32 flags);
|
||||
bool isConnected();
|
||||
|
||||
int32 debugState() const;
|
||||
@@ -281,7 +294,7 @@ public:
|
||||
|
||||
void sendData(mtpBuffer &buffer);
|
||||
void disconnectFromServer();
|
||||
void connectToServer(const QString &addr, int32 port);
|
||||
void connectToServer(const QString &addr, int32 port, int32 flags);
|
||||
bool isConnected();
|
||||
bool usingHttpWait();
|
||||
bool needHttpWait();
|
||||
@@ -343,15 +356,25 @@ public slots:
|
||||
|
||||
void onPingSender();
|
||||
void onPingSendForce();
|
||||
void onBadConnection();
|
||||
|
||||
void onWaitConnectedFailed();
|
||||
void onWaitReceivedFailed();
|
||||
void onWaitIPv4Failed();
|
||||
|
||||
void onOldConnection();
|
||||
void onSentSome(uint64 size);
|
||||
void onReceivedSome();
|
||||
|
||||
void onError(bool maybeBadKey = false);
|
||||
void onReadyData();
|
||||
void socketStart(bool afterConfig = false);
|
||||
void onConnected();
|
||||
|
||||
void onConnected4();
|
||||
void onConnected6();
|
||||
void onDisconnected4();
|
||||
void onDisconnected6();
|
||||
void onError4(bool maybeBadKey = false);
|
||||
void onError6(bool maybeBadKey = false);
|
||||
|
||||
void doDisconnect();
|
||||
void doFinish();
|
||||
|
||||
@@ -372,7 +395,8 @@ public slots:
|
||||
|
||||
private:
|
||||
|
||||
void createConn();
|
||||
void createConn(bool createIPv4, bool createIPv6);
|
||||
void destroyConn(MTPabstractConnection **conn = 0); // 0 - destory all
|
||||
|
||||
mtpMsgId placeToContainer(mtpRequest &toSendRequest, mtpMsgId &bigMsgId, mtpMsgId *&haveSentArr, mtpRequest &req);
|
||||
mtpMsgId prepareToSend(mtpRequest &request, mtpMsgId currentLastId);
|
||||
@@ -396,7 +420,7 @@ private:
|
||||
|
||||
uint32 dc;
|
||||
MTProtoConnection *_owner;
|
||||
MTPabstractConnection *conn;
|
||||
MTPabstractConnection *_conn, *_conn4, *_conn6;
|
||||
|
||||
SingleTimer retryTimer; // exp retry timer
|
||||
uint32 retryTimeout;
|
||||
@@ -405,8 +429,8 @@ private:
|
||||
SingleTimer oldConnectionTimer;
|
||||
bool oldConnection;
|
||||
|
||||
SingleTimer connCheckTimer;
|
||||
uint32 receiveDelay;
|
||||
SingleTimer _waitForConnectedTimer, _waitForReceivedTimer, _waitForIPv4Timer;
|
||||
uint32 _waitForReceived, _waitForConnected;
|
||||
int64 firstSentAt;
|
||||
|
||||
QVector<MTPlong> ackRequestData, resendRequestData;
|
||||
|
||||
@@ -366,7 +366,7 @@ static const mtpTypeId mtpLayers[] = {
|
||||
mtpc_invokeWithLayer17,
|
||||
mtpc_invokeWithLayer18,
|
||||
}, mtpLayerMaxSingle = sizeof(mtpLayers) / sizeof(mtpLayers[0]);
|
||||
static const mtpPrime mtpCurrentLayer = 29;
|
||||
static const mtpPrime mtpCurrentLayer = 31;
|
||||
|
||||
template <typename bareT>
|
||||
class MTPBoxed : public bareT {
|
||||
|
||||
@@ -175,15 +175,16 @@ void mtpUpdateDcOptions(const QVector<MTPDcOption> &options) {
|
||||
}
|
||||
for (QVector<MTPDcOption>::const_iterator i = options.cbegin(), e = options.cend(); i != e; ++i) {
|
||||
const MTPDdcOption &optData(i->c_dcOption());
|
||||
if (already.constFind(optData.vid.v) == already.cend()) {
|
||||
already.insert(optData.vid.v);
|
||||
mtpDcOptions::const_iterator a = opts.constFind(optData.vid.v);
|
||||
int32 id = optData.vid.v, idWithShift = id + (optData.vflags.v * _mtp_internal::dcShift);
|
||||
if (already.constFind(idWithShift) == already.cend()) {
|
||||
already.insert(idWithShift);
|
||||
mtpDcOptions::const_iterator a = opts.constFind(idWithShift);
|
||||
if (a != opts.cend()) {
|
||||
if (a.value().ip != optData.vip_address.c_string().v || a.value().port != optData.vport.v) {
|
||||
restart.insert(optData.vid.v);
|
||||
restart.insert(id);
|
||||
}
|
||||
}
|
||||
opts.insert(optData.vid.v, mtpDcOption(optData.vid.v, optData.vhostname.c_string().v, optData.vip_address.c_string().v, optData.vport.v));
|
||||
opts.insert(idWithShift, mtpDcOption(id, optData.vflags.v, optData.vip_address.c_string().v, optData.vport.v));
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
@@ -1109,96 +1109,24 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
|
||||
}
|
||||
break;
|
||||
|
||||
case mtpc_userSelf:
|
||||
case mtpc_user:
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
} else {
|
||||
to.add("{ userSelf");
|
||||
to.add("{ user");
|
||||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 1: to.add(" first_name: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 2: to.add(" last_name: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 3: to.add(" username: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 4: to.add(" phone: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 5: to.add(" photo: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 6: to.add(" status: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
break;
|
||||
|
||||
case mtpc_userContact:
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
} else {
|
||||
to.add("{ userContact");
|
||||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 1: to.add(" first_name: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 2: to.add(" last_name: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 3: to.add(" username: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 4: to.add(" access_hash: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 5: to.add(" phone: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 6: to.add(" photo: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 7: to.add(" status: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
break;
|
||||
|
||||
case mtpc_userRequest:
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
} else {
|
||||
to.add("{ userRequest");
|
||||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 1: to.add(" first_name: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 2: to.add(" last_name: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 3: to.add(" username: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 4: to.add(" access_hash: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 5: to.add(" phone: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 6: to.add(" photo: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 7: to.add(" status: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
break;
|
||||
|
||||
case mtpc_userForeign:
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
} else {
|
||||
to.add("{ userForeign");
|
||||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 1: to.add(" first_name: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 2: to.add(" last_name: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 3: to.add(" username: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 4: to.add(" access_hash: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 5: to.add(" photo: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 6: to.add(" status: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
break;
|
||||
|
||||
case mtpc_userDeleted:
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
} else {
|
||||
to.add("{ userDeleted");
|
||||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 1: to.add(" first_name: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 2: to.add(" last_name: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 3: to.add(" username: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 1: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 2: to.add(" access_hash: "); ++stages.back(); if (flag & MTPDuser::flag_access_hash) { types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break;
|
||||
case 3: to.add(" first_name: "); ++stages.back(); if (flag & MTPDuser::flag_first_name) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break;
|
||||
case 4: to.add(" last_name: "); ++stages.back(); if (flag & MTPDuser::flag_last_name) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break;
|
||||
case 5: to.add(" username: "); ++stages.back(); if (flag & MTPDuser::flag_username) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 3 IN FIELD flags ]"); } break;
|
||||
case 6: to.add(" phone: "); ++stages.back(); if (flag & MTPDuser::flag_phone) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 4 IN FIELD flags ]"); } break;
|
||||
case 7: to.add(" photo: "); ++stages.back(); if (flag & MTPDuser::flag_photo) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 5 IN FIELD flags ]"); } break;
|
||||
case 8: to.add(" status: "); ++stages.back(); if (flag & MTPDuser::flag_status) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 6 IN FIELD flags ]"); } break;
|
||||
case 9: to.add(" bot_info_version: "); ++stages.back(); if (flag & MTPDuser::flag_bot_info_version) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 14 IN FIELD flags ]"); } break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
break;
|
||||
@@ -1347,6 +1275,7 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
|
||||
case 2: to.add(" chat_photo: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 3: to.add(" notify_settings: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 4: to.add(" exported_invite: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 5: to.add(" bot_info: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
break;
|
||||
@@ -1444,6 +1373,7 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
|
||||
case 7: to.add(" date: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 8: to.add(" message: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 9: to.add(" media: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 10: to.add(" reply_markup: "); ++stages.back(); if (flag & MTPDmessage::flag_reply_markup) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 6 IN FIELD flags ]"); } break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
break;
|
||||
@@ -1893,8 +1823,7 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
|
||||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" expires: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 1: to.add(" user: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 0: to.add(" user: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
break;
|
||||
@@ -2048,8 +1977,7 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
|
||||
case 2: to.add(" profile_photo: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 3: to.add(" notify_settings: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 4: to.add(" blocked: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 5: to.add(" real_first_name: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 6: to.add(" real_last_name: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 5: to.add(" bot_info: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
break;
|
||||
@@ -2404,6 +2332,10 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
|
||||
to.add("{ inputMessagesFilterAudio }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
|
||||
break;
|
||||
|
||||
case mtpc_inputMessagesFilterAudioDocuments:
|
||||
to.add("{ inputMessagesFilterAudioDocuments }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
|
||||
break;
|
||||
|
||||
case mtpc_updateNewMessage:
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
@@ -3057,8 +2989,8 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
|
||||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 1: to.add(" hostname: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 1: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 2: to.add(" ip_address: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 3: to.add(" port: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
@@ -4353,6 +4285,86 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
|
||||
}
|
||||
break;
|
||||
|
||||
case mtpc_botCommand:
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
} else {
|
||||
to.add("{ botCommand");
|
||||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" command: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 1: to.add(" params: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 2: to.add(" description: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
break;
|
||||
|
||||
case mtpc_botInfoEmpty:
|
||||
to.add("{ botInfoEmpty }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
|
||||
break;
|
||||
|
||||
case mtpc_botInfo:
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
} else {
|
||||
to.add("{ botInfo");
|
||||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" user_id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 1: to.add(" version: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 2: to.add(" share_text: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 3: to.add(" description: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 4: to.add(" commands: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
break;
|
||||
|
||||
case mtpc_keyboardButton:
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
} else {
|
||||
to.add("{ keyboardButton");
|
||||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" text: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
break;
|
||||
|
||||
case mtpc_keyboardButtonRow:
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
} else {
|
||||
to.add("{ keyboardButtonRow");
|
||||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" buttons: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
break;
|
||||
|
||||
case mtpc_replyKeyboardHide:
|
||||
to.add("{ replyKeyboardHide }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
|
||||
break;
|
||||
|
||||
case mtpc_replyKeyboardMarkup:
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
} else {
|
||||
to.add("{ replyKeyboardMarkup");
|
||||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 1: to.add(" rows: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
break;
|
||||
|
||||
case mtpc_req_pq:
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
@@ -5018,6 +5030,22 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
|
||||
}
|
||||
break;
|
||||
|
||||
case mtpc_auth_importBotAuthorization:
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
} else {
|
||||
to.add("{ auth_importBotAuthorization");
|
||||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" flags: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 1: to.add(" api_id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 2: to.add(" api_hash: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 3: to.add(" bot_auth_token: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
break;
|
||||
|
||||
case mtpc_auth_checkPassword:
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
@@ -5388,6 +5416,7 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
|
||||
case 2: to.add(" reply_to_msg_id: "); ++stages.back(); if (flag & MTPmessages_sendMessage::flag_reply_to_msg_id) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break;
|
||||
case 3: to.add(" message: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 4: to.add(" random_id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 5: to.add(" reply_markup: "); ++stages.back(); if (flag & MTPmessages_sendMessage::flag_reply_markup) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
break;
|
||||
@@ -5405,6 +5434,7 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
|
||||
case 2: to.add(" reply_to_msg_id: "); ++stages.back(); if (flag & MTPmessages_sendMedia::flag_reply_to_msg_id) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break;
|
||||
case 3: to.add(" media: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 4: to.add(" random_id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 5: to.add(" reply_markup: "); ++stages.back(); if (flag & MTPmessages_sendMedia::flag_reply_markup) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
break;
|
||||
@@ -5539,6 +5569,22 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
|
||||
}
|
||||
break;
|
||||
|
||||
case mtpc_messages_startBot:
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
} else {
|
||||
to.add("{ messages_startBot");
|
||||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" bot: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 1: to.add(" chat_id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 2: to.add(" random_id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 3: to.add(" start_param: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
break;
|
||||
|
||||
case mtpc_messages_getChats:
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -193,11 +193,6 @@ fileLocationUnavailable#7c596b46 volume_id:long local_id:int secret:long = FileL
|
||||
fileLocation#53d69076 dc_id:int volume_id:long local_id:int secret:long = FileLocation;
|
||||
|
||||
userEmpty#200250ba id:int = User;
|
||||
userSelf#1c60e608 id:int first_name:string last_name:string username:string phone:string photo:UserProfilePhoto status:UserStatus = User;
|
||||
userContact#cab35e18 id:int first_name:string last_name:string username:string access_hash:long phone:string photo:UserProfilePhoto status:UserStatus = User;
|
||||
userRequest#d9ccc4ef id:int first_name:string last_name:string username:string access_hash:long phone:string photo:UserProfilePhoto status:UserStatus = User;
|
||||
userForeign#75cf7a8 id:int first_name:string last_name:string username:string access_hash:long photo:UserProfilePhoto status:UserStatus = User;
|
||||
userDeleted#d6016d7a id:int first_name:string last_name:string username:string = User;
|
||||
|
||||
userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto;
|
||||
userProfilePhoto#d559d8c8 photo_id:long photo_small:FileLocation photo_big:FileLocation = UserProfilePhoto;
|
||||
@@ -210,7 +205,7 @@ chatEmpty#9ba2d800 id:int = Chat;
|
||||
chat#6e9c9bc7 id:int title:string photo:ChatPhoto participants_count:int date:int left:Bool version:int = Chat;
|
||||
chatForbidden#fb0ccc41 id:int title:string date:int = Chat;
|
||||
|
||||
chatFull#cade0791 id:int participants:ChatParticipants chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite = ChatFull;
|
||||
chatFull#2e02a614 id:int participants:ChatParticipants chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector<BotInfo> = ChatFull;
|
||||
|
||||
chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant;
|
||||
|
||||
@@ -221,7 +216,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto;
|
||||
chatPhoto#6153276a photo_small:FileLocation photo_big:FileLocation = ChatPhoto;
|
||||
|
||||
messageEmpty#83e5de54 id:int = Message;
|
||||
message#a7ab1991 flags:# id:int from_id:int to_id:Peer fwd_from_id:flags.2?int fwd_date:flags.2?int reply_to_msg_id:flags.3?int date:int message:string media:MessageMedia = Message;
|
||||
message#c3060325 flags:# id:int from_id:int to_id:Peer fwd_from_id:flags.2?int fwd_date:flags.2?int reply_to_msg_id:flags.3?int date:int message:string media:MessageMedia reply_markup:flags.6?ReplyMarkup = Message;
|
||||
messageService#1d86f70e flags:int id:int from_id:int to_id:Peer date:int action:MessageAction = Message;
|
||||
|
||||
messageMediaEmpty#3ded6320 = MessageMedia;
|
||||
@@ -258,7 +253,7 @@ auth.checkedPhone#811ea28e phone_registered:Bool = auth.CheckedPhone;
|
||||
|
||||
auth.sentCode#efed51d9 phone_registered:Bool phone_code_hash:string send_call_timeout:int is_password:Bool = auth.SentCode;
|
||||
|
||||
auth.authorization#f6b673a4 expires:int user:User = auth.Authorization;
|
||||
auth.authorization#ff036af1 user:User = auth.Authorization;
|
||||
|
||||
auth.exportedAuthorization#df969c2d id:int bytes:bytes = auth.ExportedAuthorization;
|
||||
|
||||
@@ -280,7 +275,7 @@ peerNotifySettings#8d5e11ee mute_until:int sound:string show_previews:Bool event
|
||||
|
||||
wallPaper#ccb03657 id:int title:string sizes:Vector<PhotoSize> color:int = WallPaper;
|
||||
|
||||
userFull#771095da user:User link:contacts.Link profile_photo:Photo notify_settings:PeerNotifySettings blocked:Bool real_first_name:string real_last_name:string = UserFull;
|
||||
userFull#5a89ac5b user:User link:contacts.Link profile_photo:Photo notify_settings:PeerNotifySettings blocked:Bool bot_info:BotInfo = UserFull;
|
||||
|
||||
contact#f911c994 user_id:int mutual:Bool = Contact;
|
||||
|
||||
@@ -329,6 +324,7 @@ inputMessagesFilterPhotoVideo#56e9f0e4 = MessagesFilter;
|
||||
inputMessagesFilterPhotoVideoDocuments#d95e73bb = MessagesFilter;
|
||||
inputMessagesFilterDocument#9eddf188 = MessagesFilter;
|
||||
inputMessagesFilterAudio#cfc87522 = MessagesFilter;
|
||||
inputMessagesFilterAudioDocuments#5afbf764 = MessagesFilter;
|
||||
|
||||
updateNewMessage#1f2b0afd message:Message pts:int pts_count:int = Update;
|
||||
updateMessageID#4e90bfd6 id:int random_id:long = Update;
|
||||
@@ -363,7 +359,7 @@ photos.photo#20212ca8 photo:Photo users:Vector<User> = photos.Photo;
|
||||
|
||||
upload.file#96a18d5 type:storage.FileType mtime:int bytes:bytes = upload.File;
|
||||
|
||||
dcOption#2ec2a43c id:int hostname:string ip_address:string port:int = DcOption;
|
||||
dcOption#5d8c6cc flags:# id:int ip_address:string port:int = DcOption;
|
||||
|
||||
config#4e32b894 date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> chat_size_max:int broadcast_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int chat_big_size:int push_chat_period_ms:int push_chat_limit:int disabled_features:Vector<DisabledFeature> = Config;
|
||||
|
||||
@@ -596,6 +592,20 @@ stickerSet#a7a43b17 id:long access_hash:long title:string short_name:string = St
|
||||
|
||||
messages.stickerSet#b60a24a6 set:StickerSet packs:Vector<StickerPack> documents:Vector<Document> = messages.StickerSet;
|
||||
|
||||
user#22e49072 flags:# id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int = User;
|
||||
|
||||
botCommand#b79d22ab command:string params:string description:string = BotCommand;
|
||||
|
||||
botInfoEmpty#bb2e37ce = BotInfo;
|
||||
botInfo#9cf585d user_id:int version:int share_text:string description:string commands:Vector<BotCommand> = BotInfo;
|
||||
|
||||
keyboardButton#a2fa4880 text:string = KeyboardButton;
|
||||
|
||||
keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;
|
||||
|
||||
replyKeyboardHide#ced6ebbc = ReplyMarkup;
|
||||
replyKeyboardMarkup#3502758c flags:# rows:Vector<KeyboardButtonRow> = ReplyMarkup;
|
||||
|
||||
---functions---
|
||||
|
||||
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
|
||||
@@ -647,8 +657,8 @@ messages.deleteHistory#f4f8fb61 peer:InputPeer offset:int = messages.AffectedHis
|
||||
messages.deleteMessages#a5f18925 id:Vector<int> = messages.AffectedMessages;
|
||||
messages.receivedMessages#5a954c0 max_id:int = Vector<ReceivedNotifyMessage>;
|
||||
messages.setTyping#a3825e50 peer:InputPeer action:SendMessageAction = Bool;
|
||||
messages.sendMessage#9add8f26 flags:# peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long = messages.SentMessage;
|
||||
messages.sendMedia#2d7923b1 flags:# peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia random_id:long = Updates;
|
||||
messages.sendMessage#fc55e6b5 flags:# peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup = messages.SentMessage;
|
||||
messages.sendMedia#c8f16791 flags:# peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia random_id:long reply_markup:flags.2?ReplyMarkup = Updates;
|
||||
messages.forwardMessages#55e1728d peer:InputPeer id:Vector<int> random_id:Vector<long> = Updates;
|
||||
messages.getChats#3c6aa187 id:Vector<int> = messages.Chats;
|
||||
messages.getFullChat#3b831c66 chat_id:int = messages.ChatFull;
|
||||
@@ -736,6 +746,8 @@ messages.getAllStickers#aa3bc868 hash:string = messages.AllStickers;
|
||||
|
||||
account.updateDeviceLocked#38df3532 period:int = Bool;
|
||||
|
||||
auth.importBotAuthorization#67a3ff2c flags:int api_id:int api_hash:string bot_auth_token:string = auth.Authorization;
|
||||
|
||||
messages.getWebPagePreview#25223e24 message:string = MessageMedia;
|
||||
|
||||
account.getAuthorizations#e320c158 = account.Authorizations;
|
||||
@@ -756,3 +768,4 @@ messages.importChatInvite#6c50051c hash:string = Updates;
|
||||
messages.getStickerSet#2619a90e stickerset:InputStickerSet = messages.StickerSet;
|
||||
messages.installStickerSet#efbbfae9 stickerset:InputStickerSet = Bool;
|
||||
messages.uninstallStickerSet#f96e55de stickerset:InputStickerSet = Bool;
|
||||
messages.startBot#1b3e0ffc bot:InputUser chat_id:int random_id:long start_param:string = Updates;
|
||||
|
||||
@@ -39,9 +39,16 @@ ProfileInner::ProfileInner(ProfileWidget *profile, ScrollArea *scroll, const Pee
|
||||
_addParticipant(this, lang(lng_profile_add_participant), st::btnShareContact),
|
||||
_sendMessage(this, lang(lng_profile_send_message), st::btnShareContact),
|
||||
_shareContact(this, lang(lng_profile_share_contact), st::btnShareContact),
|
||||
_inviteToGroup(this, lang(lng_profile_invite_to_group), st::btnShareContact),
|
||||
_cancelPhoto(this, lang(lng_cancel)),
|
||||
_createInvitationLink(this, lang(lng_group_invite_create)),
|
||||
_invitationLink(this, qsl("telegram.me/joinchat/")),
|
||||
_botSettings(this, lang(lng_profile_bot_settings)),
|
||||
_botHelp(this, lang(lng_profile_bot_help)),
|
||||
|
||||
// about
|
||||
_about(st::wndMinWidth - st::profilePadding.left() - st::profilePadding.right()),
|
||||
_aboutTop(0), _aboutHeight(0),
|
||||
|
||||
a_photo(0),
|
||||
_photoOver(false),
|
||||
@@ -66,7 +73,7 @@ ProfileInner::ProfileInner(ProfileWidget *profile, ScrollArea *scroll, const Pee
|
||||
|
||||
_menu(0) {
|
||||
|
||||
connect(App::api(), SIGNAL(fullPeerLoaded(PeerData*)), this, SLOT(onFullPeerLoaded(PeerData*)));
|
||||
connect(App::api(), SIGNAL(fullPeerUpdated(PeerData*)), this, SLOT(onFullPeerUpdated(PeerData*)));
|
||||
|
||||
if (_peerUser) {
|
||||
_phoneText = _peerUser->phone.isEmpty() ? QString() : App::formatPhone(_peerUser->phone);
|
||||
@@ -87,12 +94,34 @@ ProfileInner::ProfileInner(ProfileWidget *profile, ScrollArea *scroll, const Pee
|
||||
connect(&_addParticipant, SIGNAL(clicked()), this, SLOT(onAddParticipant()));
|
||||
connect(&_sendMessage, SIGNAL(clicked()), this, SLOT(onSendMessage()));
|
||||
connect(&_shareContact, SIGNAL(clicked()), this, SLOT(onShareContact()));
|
||||
connect(&_inviteToGroup, SIGNAL(clicked()), this, SLOT(onInviteToGroup()));
|
||||
connect(&_cancelPhoto, SIGNAL(clicked()), this, SLOT(onUpdatePhotoCancel()));
|
||||
connect(&_createInvitationLink, SIGNAL(clicked()), this, SLOT(onCreateInvitationLink()));
|
||||
connect(&_invitationLink, SIGNAL(clicked()), this, SLOT(onInvitationLink()));
|
||||
_invitationLink.setAcceptBoth(true);
|
||||
updateInvitationLink();
|
||||
|
||||
if (_peerChat) {
|
||||
QString maxStr = lang(_uploadPhoto.textWidth() > _addParticipant.textWidth() ? lng_profile_set_group_photo : lng_profile_add_participant);
|
||||
_uploadPhoto.setAutoFontSize(st::profileMinBtnPadding, maxStr);
|
||||
_uploadPhoto.setAutoFontSize(st::profileMinBtnPadding, maxStr);
|
||||
} else if (_peerUser) {
|
||||
QString maxStr;
|
||||
if (_peerUser->botInfo && !_peerUser->botInfo->cantJoinGroups) {
|
||||
maxStr = lang(_sendMessage.textWidth() > _inviteToGroup.textWidth() ? lng_profile_send_message : lng_profile_invite_to_group);
|
||||
} else if (!_peerUser->phone.isEmpty()) {
|
||||
maxStr = lang(_sendMessage.textWidth() > _shareContact.textWidth() ? lng_profile_send_message : lng_profile_share_contact);
|
||||
} else {
|
||||
maxStr = lang(lng_profile_send_message);
|
||||
}
|
||||
_sendMessage.setAutoFontSize(st::profileMinBtnPadding, maxStr);
|
||||
_shareContact.setAutoFontSize(st::profileMinBtnPadding, maxStr);
|
||||
_inviteToGroup.setAutoFontSize(st::profileMinBtnPadding, maxStr);
|
||||
}
|
||||
|
||||
connect(&_botSettings, SIGNAL(clicked()), this, SLOT(onBotSettings()));
|
||||
connect(&_botHelp, SIGNAL(clicked()), this, SLOT(onBotHelp()));
|
||||
|
||||
connect(App::app(), SIGNAL(peerPhotoDone(PeerId)), this, SLOT(onPhotoUpdateDone(PeerId)));
|
||||
connect(App::app(), SIGNAL(peerPhotoFail(PeerId)), this, SLOT(onPhotoUpdateFail(PeerId)));
|
||||
|
||||
@@ -100,6 +129,17 @@ ProfileInner::ProfileInner(ProfileWidget *profile, ScrollArea *scroll, const Pee
|
||||
connect(App::main(), SIGNAL(peerUpdated(PeerData *)), this, SLOT(peerUpdated(PeerData *)));
|
||||
connect(App::main(), SIGNAL(peerNameChanged(PeerData *, const PeerData::Names &, const PeerData::NameFirstChars &)), this, SLOT(peerUpdated(PeerData *)));
|
||||
|
||||
// about
|
||||
if (_peerUser && _peerUser->botInfo) {
|
||||
if (!_peerUser->botInfo->shareText.isEmpty()) {
|
||||
_about.setText(st::linkFont, _peerUser->botInfo->shareText, _historyBotOptions);
|
||||
}
|
||||
updateBotLinksVisibility();
|
||||
} else {
|
||||
_botSettings.hide();
|
||||
_botHelp.hide();
|
||||
}
|
||||
|
||||
// settings
|
||||
connect(&_enableNotifications, SIGNAL(clicked()), this, SLOT(onEnableNotifications()));
|
||||
connect(&_clearHistory, SIGNAL(clicked()), this, SLOT(onClearHistory()));
|
||||
@@ -126,6 +166,10 @@ void ProfileInner::onShareContact() {
|
||||
App::main()->shareContactLayer(_peerUser);
|
||||
}
|
||||
|
||||
void ProfileInner::onInviteToGroup() {
|
||||
App::wnd()->showLayer(new ContactsBox(_peerUser));
|
||||
}
|
||||
|
||||
void ProfileInner::onSendMessage() {
|
||||
App::main()->showPeer(_peer->id);
|
||||
}
|
||||
@@ -279,7 +323,7 @@ void ProfileInner::chatInviteDone(const MTPExportedChatInvite &result) {
|
||||
App::wnd()->hideLayer();
|
||||
}
|
||||
|
||||
void ProfileInner::onFullPeerLoaded(PeerData *peer) {
|
||||
void ProfileInner::onFullPeerUpdated(PeerData *peer) {
|
||||
if (peer != _peer) return;
|
||||
if (_peerUser) {
|
||||
PhotoData *userPhoto = _peerUser->photoId ? App::photo(_peerUser->photoId) : 0;
|
||||
@@ -288,6 +332,15 @@ void ProfileInner::onFullPeerLoaded(PeerData *peer) {
|
||||
} else {
|
||||
_photoLink = TextLinkPtr();
|
||||
}
|
||||
if (_peerUser->botInfo) {
|
||||
if (_peerUser->botInfo->shareText.isEmpty()) {
|
||||
_about = Text(st::wndMinWidth - st::profilePadding.left() - st::profilePadding.right());
|
||||
} else {
|
||||
_about.setText(st::linkFont, _peerUser->botInfo->shareText, _historyBotOptions);
|
||||
}
|
||||
updateBotLinksVisibility();
|
||||
resizeEvent(0);
|
||||
}
|
||||
} else if (_peerChat) {
|
||||
updateInvitationLink();
|
||||
showAll();
|
||||
@@ -295,6 +348,30 @@ void ProfileInner::onFullPeerLoaded(PeerData *peer) {
|
||||
}
|
||||
}
|
||||
|
||||
void ProfileInner::onBotSettings() {
|
||||
for (int32 i = 0, l = _peerUser->botInfo->commands.size(); i != l; ++i) {
|
||||
QString cmd = _peerUser->botInfo->commands.at(i).command;
|
||||
if (!cmd.compare(qsl("settings"), Qt::CaseInsensitive)) {
|
||||
App::main()->showPeer(_peer->id);
|
||||
App::main()->sendBotCommand('/' + cmd, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
updateBotLinksVisibility();
|
||||
}
|
||||
|
||||
void ProfileInner::onBotHelp() {
|
||||
for (int32 i = 0, l = _peerUser->botInfo->commands.size(); i != l; ++i) {
|
||||
QString cmd = _peerUser->botInfo->commands.at(i).command;
|
||||
if (!cmd.compare(qsl("help"), Qt::CaseInsensitive)) {
|
||||
App::main()->showPeer(_peer->id);
|
||||
App::main()->sendBotCommand('/' + cmd, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
updateBotLinksVisibility();
|
||||
}
|
||||
|
||||
void ProfileInner::peerUpdated(PeerData *data) {
|
||||
if (data == _peer) {
|
||||
PhotoData *photo = 0;
|
||||
@@ -325,13 +402,13 @@ void ProfileInner::updateOnlineDisplayTimer() {
|
||||
if (_peerChat->participants.isEmpty()) return;
|
||||
|
||||
for (ChatData::Participants::const_iterator i = _peerChat->participants.cbegin(), e = _peerChat->participants.cend(); i != e; ++i) {
|
||||
int32 onlineWillChangeIn = App::onlineWillChangeIn(i.key()->onlineTill, t);
|
||||
int32 onlineWillChangeIn = App::onlineWillChangeIn(i.key(), t);
|
||||
if (onlineWillChangeIn < minIn) {
|
||||
minIn = onlineWillChangeIn;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
minIn = App::onlineWillChangeIn(_peerUser->onlineTill, t);
|
||||
minIn = App::onlineWillChangeIn(_peerUser, t);
|
||||
}
|
||||
App::main()->updateOnlineDisplayIn(minIn * 1000);
|
||||
}
|
||||
@@ -354,13 +431,13 @@ void ProfileInner::reorderParticipants() {
|
||||
bool onlyMe = true;
|
||||
for (ChatData::Participants::const_iterator i = _peerChat->participants.cbegin(), e = _peerChat->participants.cend(); i != e; ++i) {
|
||||
UserData *user = i.key();
|
||||
int32 until = App::onlineForSort(user->onlineTill, t);
|
||||
int32 until = App::onlineForSort(user, t);
|
||||
Participants::iterator before = _participants.begin();
|
||||
if (user != self) {
|
||||
if (before != _participants.end() && (*before) == self) {
|
||||
++before;
|
||||
}
|
||||
while (before != _participants.end() && App::onlineForSort((*before)->onlineTill, t) >= until) {
|
||||
while (before != _participants.end() && App::onlineForSort(*before, t) >= until) {
|
||||
++before;
|
||||
}
|
||||
if (until > t && onlyMe) onlyMe = false;
|
||||
@@ -392,6 +469,9 @@ void ProfileInner::reorderParticipants() {
|
||||
}
|
||||
}
|
||||
|
||||
void ProfileInner::start() {
|
||||
}
|
||||
|
||||
bool ProfileInner::event(QEvent *e) {
|
||||
if (e->type() == QEvent::MouseMove) {
|
||||
_lastPos = static_cast<QMouseEvent*>(e)->globalPos();
|
||||
@@ -446,7 +526,7 @@ void ProfileInner::paintEvent(QPaintEvent *e) {
|
||||
if (!_errorText.isEmpty()) {
|
||||
p.setFont(st::setErrFont->f);
|
||||
p.setPen(st::setErrColor->p);
|
||||
p.drawText(_left + st::profilePhotoSize + st::profilePhoneLeft, top + addbyname + st::profilePhoneTop + st::profilePhoneFont->ascent, _errorText);
|
||||
p.drawText(_left + st::profilePhotoSize + st::profilePhoneLeft, _cancelPhoto.y() + addbyname + st::profilePhoneFont->ascent, _errorText);
|
||||
}
|
||||
if (!_phoneText.isEmpty()) {
|
||||
p.setPen(st::black->p);
|
||||
@@ -464,6 +544,17 @@ void ProfileInner::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
top += _shareContact.height();
|
||||
|
||||
// about
|
||||
if (!_about.isEmpty()) {
|
||||
p.setFont(st::profileHeaderFont->f);
|
||||
p.setPen(st::profileHeaderColor->p);
|
||||
p.drawText(_left + st::profileHeaderLeft, top + st::profileHeaderTop + st::profileHeaderFont->ascent, lang(lng_profile_about_section));
|
||||
top += st::profileHeaderSkip;
|
||||
|
||||
_about.draw(p, _left, top, _width);
|
||||
top += _aboutHeight;
|
||||
}
|
||||
|
||||
// settings
|
||||
p.setFont(st::profileHeaderFont->f);
|
||||
p.setPen(st::profileHeaderColor->p);
|
||||
@@ -537,7 +628,15 @@ void ProfileInner::paintEvent(QPaintEvent *e) {
|
||||
if (!data) {
|
||||
data = _participantsData[cnt] = new ParticipantData();
|
||||
data->name.setText(st::profileListNameFont, user->name, _textNameOptions);
|
||||
data->online = App::onlineText(user, l_time);
|
||||
if (user->botInfo) {
|
||||
if (user->botInfo->readsAllHistory) {
|
||||
data->online = lang(lng_status_bot_reads_all);
|
||||
} else {
|
||||
data->online = lang(lng_status_bot_not_reads_all);
|
||||
}
|
||||
} else {
|
||||
data->online = App::onlineText(user, l_time);
|
||||
}
|
||||
data->cankick = (user != App::self()) && (_chatAdmin || (_peerChat->cankick.constFind(user) != _peerChat->cankick.cend()));
|
||||
}
|
||||
p.setPen(st::profileListNameColor->p);
|
||||
@@ -555,7 +654,7 @@ void ProfileInner::paintEvent(QPaintEvent *e) {
|
||||
} else {
|
||||
p.setPen(st::btnDefLink.color->p);
|
||||
}
|
||||
p.drawText(_left + _width - _kickWidth, top + (_pHeight - st::linkFont->height) / 2 + st::linkFont->ascent, lang(lng_profile_kick));
|
||||
p.drawText(_left + _width - _kickWidth, top + st::profileListNameTop + st::linkFont->ascent, lang(lng_profile_kick));
|
||||
}
|
||||
}
|
||||
top += fullCnt * _pHeight;
|
||||
@@ -579,9 +678,9 @@ void ProfileInner::mouseMoveEvent(QMouseEvent *e) {
|
||||
}
|
||||
}
|
||||
if (!_photoLink && (!_peerChat || _peerChat->forbidden)) {
|
||||
setCursor((_kickOver || _kickDown) ? style::cur_pointer : style::cur_default);
|
||||
setCursor((_kickOver || _kickDown || textlnkOver()) ? style::cur_pointer : style::cur_default);
|
||||
} else {
|
||||
setCursor((_kickOver || _kickDown || _photoOver) ? style::cur_pointer : style::cur_default);
|
||||
setCursor((_kickOver || _kickDown || _photoOver || textlnkOver()) ? style::cur_pointer : style::cur_default);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -590,6 +689,16 @@ void ProfileInner::updateSelected() {
|
||||
|
||||
QPoint lp = mapFromGlobal(_lastPos);
|
||||
|
||||
TextLinkPtr lnk;
|
||||
bool inText = false;
|
||||
if (!_about.isEmpty() && lp.y() >= _aboutTop && lp.y() < _aboutTop + _aboutHeight && lp.x() >= _left && lp.x() < _left + _width) {
|
||||
_about.getState(lnk, inText, lp.x() - _left, lp.y() - _aboutTop, _width);
|
||||
}
|
||||
if (textlnkOver() != lnk) {
|
||||
textlnkOver(lnk);
|
||||
update(QRect(_left, _aboutTop, _width, _aboutHeight));
|
||||
}
|
||||
|
||||
int32 partfrom = _mediaAudios.y() + _mediaAudios.height() + st::profileHeaderSkip;
|
||||
int32 newSelected = (lp.x() >= _left - st::profileListPadding.width() && lp.x() < _left + _width + st::profileListPadding.width() && lp.y() >= partfrom) ? (lp.y() - partfrom) / _pHeight : -1;
|
||||
|
||||
@@ -597,7 +706,7 @@ void ProfileInner::updateSelected() {
|
||||
if (newSelected >= 0 && newSelected < _participants.size()) {
|
||||
ParticipantData *data = _participantsData[newSelected];
|
||||
if (data && data->cankick) {
|
||||
int32 top = partfrom + newSelected * _pHeight + (_pHeight - st::linkFont->height) / 2;
|
||||
int32 top = partfrom + newSelected * _pHeight + st::profileListNameTop;
|
||||
if ((lp.x() >= _left + _width - _kickWidth) && (lp.x() < _left + _width) && (lp.y() >= top) && (lp.y() < top + st::linkFont->height)) {
|
||||
newKickOver = _participants[newSelected];
|
||||
}
|
||||
@@ -631,18 +740,31 @@ void ProfileInner::mousePressEvent(QMouseEvent *e) {
|
||||
onUpdatePhoto();
|
||||
}
|
||||
}
|
||||
textlnkDown(textlnkOver());
|
||||
}
|
||||
}
|
||||
|
||||
void ProfileInner::mouseReleaseEvent(QMouseEvent *e) {
|
||||
_lastPos = e->globalPos();
|
||||
updateSelected();
|
||||
if (_kickDown && _kickDown == _kickOver) {
|
||||
_kickConfirm = _kickOver;
|
||||
ConfirmBox *box = new ConfirmBox(lng_profile_sure_kick(lt_user, _kickOver->firstName));
|
||||
connect(box, SIGNAL(confirmed()), this, SLOT(onKickConfirm()));
|
||||
App::wnd()->showLayer(box);
|
||||
}
|
||||
if (textlnkDown()) {
|
||||
TextLinkPtr lnk = textlnkDown();
|
||||
textlnkDown(TextLinkPtr());
|
||||
if (lnk == textlnkOver()) {
|
||||
if (reBotCommand().match(lnk->encoded()).hasMatch()) {
|
||||
App::main()->showPeer(_peer->id);
|
||||
}
|
||||
lnk->onClick(e->button());
|
||||
}
|
||||
}
|
||||
_kickDown = 0;
|
||||
setCursor(_kickOver ? style::cur_pointer : style::cur_default);
|
||||
setCursor((_kickOver || textlnkOver()) ? style::cur_pointer : style::cur_default);
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -697,16 +819,30 @@ void ProfileInner::resizeEvent(QResizeEvent *e) {
|
||||
_cancelPhoto.move(_left + _width - _cancelPhoto.width(), top + st::profilePhotoSize - st::linkFont->height);
|
||||
} else {
|
||||
_cancelPhoto.move(_left + _width - _cancelPhoto.width(), top + st::profilePhoneTop);
|
||||
_botSettings.move(_left + st::profilePhotoSize + st::profilePhoneLeft, top + st::profileStatusTop + st::linkFont->ascent - (st::profileNameTop + st::profileNameFont->ascent) + st::profilePhoneTop);
|
||||
_botHelp.move(_botSettings.x() + (_botSettings.isHidden() ? 0 : _botSettings.width() + st::profilePhoneLeft), _botSettings.y());
|
||||
}
|
||||
top += st::profilePhotoSize;
|
||||
|
||||
top += st::profileButtonTop;
|
||||
|
||||
_uploadPhoto.setGeometry(_left, top, btnWidth, _uploadPhoto.height());
|
||||
_sendMessage.setGeometry(_left, top, btnWidth, _sendMessage.height());
|
||||
_addParticipant.setGeometry(_left + _width - btnWidth, top, btnWidth, _addParticipant.height());
|
||||
|
||||
_sendMessage.setGeometry(_left, top, btnWidth, _sendMessage.height());
|
||||
_shareContact.setGeometry(_left + _width - btnWidth, top, btnWidth, _shareContact.height());
|
||||
_inviteToGroup.setGeometry(_left + _width - btnWidth, top, btnWidth, _inviteToGroup.height());
|
||||
|
||||
top += _shareContact.height();
|
||||
|
||||
// about
|
||||
if (!_about.isEmpty()) {
|
||||
top += st::profileHeaderSkip;
|
||||
_aboutTop = top; _aboutHeight = _about.countHeight(_width); top += _aboutHeight;
|
||||
} else {
|
||||
_aboutTop = _aboutHeight = 0;
|
||||
}
|
||||
|
||||
// settings
|
||||
top += st::profileHeaderSkip;
|
||||
_enableNotifications.move(_left, top); top += _enableNotifications.height();
|
||||
@@ -829,6 +965,7 @@ void ProfileInner::showAll() {
|
||||
if (_peerChat) {
|
||||
_sendMessage.hide();
|
||||
_shareContact.hide();
|
||||
_inviteToGroup.hide();
|
||||
if (_peerChat->forbidden) {
|
||||
_uploadPhoto.hide();
|
||||
_cancelPhoto.hide();
|
||||
@@ -871,8 +1008,14 @@ void ProfileInner::showAll() {
|
||||
_sendMessage.show();
|
||||
if (_peerUser->phone.isEmpty()) {
|
||||
_shareContact.hide();
|
||||
if (_peerUser->botInfo && !_peerUser->botInfo->cantJoinGroups) {
|
||||
_inviteToGroup.show();
|
||||
} else {
|
||||
_inviteToGroup.hide();
|
||||
}
|
||||
} else {
|
||||
_shareContact.show();
|
||||
_inviteToGroup.hide();
|
||||
}
|
||||
_enableNotifications.show();
|
||||
_clearHistory.show();
|
||||
@@ -944,6 +1087,23 @@ void ProfileInner::updateInvitationLink() {
|
||||
}
|
||||
}
|
||||
|
||||
void ProfileInner::updateBotLinksVisibility() {
|
||||
if (!_peerUser || !_peerUser->botInfo || _peerUser->botInfo->commands.isEmpty()) {
|
||||
_botSettings.hide();
|
||||
_botHelp.hide();
|
||||
return;
|
||||
}
|
||||
bool hasSettings = false, hasHelp = false;
|
||||
for (int32 i = 0, l = _peerUser->botInfo->commands.size(); i != l; ++i) {
|
||||
QString cmd = _peerUser->botInfo->commands.at(i).command;
|
||||
hasSettings |= !cmd.compare(qsl("settings"), Qt::CaseInsensitive);
|
||||
hasHelp |= !cmd.compare(qsl("help"), Qt::CaseInsensitive);
|
||||
if (hasSettings && hasHelp) break;
|
||||
}
|
||||
_botSettings.setVisible(hasSettings);
|
||||
_botHelp.setVisible(hasHelp);
|
||||
}
|
||||
|
||||
QString ProfileInner::overviewLinkText(int32 type, int32 count) {
|
||||
switch (type) {
|
||||
case OverviewPhotos: return lng_profile_photos(lt_count, count);
|
||||
@@ -1063,6 +1223,7 @@ bool ProfileWidget::animStep(float64 ms) {
|
||||
_bgAnimCache = _animCache = _animTopBarCache = _bgAnimTopBarCache = QPixmap();
|
||||
App::main()->topBar()->stopAnim();
|
||||
_scroll.show();
|
||||
_inner.start();
|
||||
activate();
|
||||
} else {
|
||||
a_bgCoord.update(dt1, st::introHideFunc);
|
||||
@@ -1092,6 +1253,12 @@ void ProfileWidget::mediaOverviewUpdated(PeerData *peer) {
|
||||
_inner.mediaOverviewUpdated(peer);
|
||||
}
|
||||
|
||||
void ProfileWidget::clear() {
|
||||
if (_inner.peer() && !_inner.peer()->chat && _inner.peer()->asUser()->botInfo) {
|
||||
_inner.peer()->asUser()->botInfo->startGroupToken = QString();
|
||||
}
|
||||
}
|
||||
|
||||
ProfileWidget::~ProfileWidget() {
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ public:
|
||||
|
||||
ProfileInner(ProfileWidget *profile, ScrollArea *scroll, const PeerData *peer);
|
||||
|
||||
void start();
|
||||
|
||||
bool event(QEvent *e);
|
||||
void paintEvent(QPaintEvent *e);
|
||||
void mouseMoveEvent(QMouseEvent *e);
|
||||
@@ -64,6 +66,7 @@ public slots:
|
||||
void deleteContextImage();
|
||||
|
||||
void onShareContact();
|
||||
void onInviteToGroup();
|
||||
void onSendMessage();
|
||||
void onEnableNotifications();
|
||||
|
||||
@@ -94,12 +97,16 @@ public slots:
|
||||
void onCreateInvitationLink();
|
||||
void onCreateInvitationLinkSure();
|
||||
|
||||
void onFullPeerLoaded(PeerData *peer);
|
||||
void onFullPeerUpdated(PeerData *peer);
|
||||
|
||||
void onBotSettings();
|
||||
void onBotHelp();
|
||||
|
||||
private:
|
||||
|
||||
void showAll();
|
||||
void updateInvitationLink();
|
||||
void updateBotLinksVisibility();
|
||||
|
||||
void chatInviteDone(const MTPExportedChatInvite &result);
|
||||
|
||||
@@ -120,9 +127,13 @@ private:
|
||||
QString _phoneText;
|
||||
TextLinkPtr _photoLink;
|
||||
FlatButton _uploadPhoto, _addParticipant;
|
||||
FlatButton _sendMessage, _shareContact;
|
||||
FlatButton _sendMessage, _shareContact, _inviteToGroup;
|
||||
LinkButton _cancelPhoto, _createInvitationLink, _invitationLink;
|
||||
QString _invitationText;
|
||||
LinkButton _botSettings, _botHelp;
|
||||
|
||||
Text _about;
|
||||
int32 _aboutTop, _aboutHeight;
|
||||
|
||||
anim::fvalue a_photo;
|
||||
bool _photoOver;
|
||||
@@ -191,6 +202,7 @@ public:
|
||||
void updateNotifySettings();
|
||||
void mediaOverviewUpdated(PeerData *peer);
|
||||
|
||||
void clear();
|
||||
~ProfileWidget();
|
||||
|
||||
public slots:
|
||||
|
||||
@@ -43,6 +43,29 @@ extern "C" {
|
||||
#include <unity/unity/unity.h>
|
||||
|
||||
namespace {
|
||||
QString escapeShell(const QString &str) {
|
||||
QString result;
|
||||
const QChar *b = str.constData(), *e = str.constEnd();
|
||||
for (const QChar *ch = b; ch != e; ++ch) {
|
||||
if (*ch == ' ' || *ch == '"' || *ch == '\'' || *ch == '\\') {
|
||||
if (result.isEmpty()) {
|
||||
result.reserve(str.size() * 2);
|
||||
}
|
||||
if (ch > b) {
|
||||
result.append(b, ch - b);
|
||||
}
|
||||
result.append('\\');
|
||||
b = ch;
|
||||
}
|
||||
}
|
||||
if (result.isEmpty()) return str;
|
||||
|
||||
if (e > b) {
|
||||
result.append(b, e - b);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool frameless = true;
|
||||
bool finished = true;
|
||||
bool noQtTrayIcon = false;
|
||||
@@ -923,183 +946,6 @@ PsApplication::~PsApplication() {
|
||||
_psEventFilter = 0;
|
||||
}
|
||||
|
||||
PsUpdateDownloader::PsUpdateDownloader(QThread *thread, const MTPDhelp_appUpdate &update) : reply(0), already(0), full(0) {
|
||||
updateUrl = qs(update.vurl);
|
||||
moveToThread(thread);
|
||||
manager.moveToThread(thread);
|
||||
App::setProxySettings(manager);
|
||||
|
||||
connect(thread, SIGNAL(started()), this, SLOT(start()));
|
||||
initOutput();
|
||||
}
|
||||
|
||||
PsUpdateDownloader::PsUpdateDownloader(QThread *thread, const QString &url) : reply(0), already(0), full(0) {
|
||||
updateUrl = url;
|
||||
moveToThread(thread);
|
||||
manager.moveToThread(thread);
|
||||
App::setProxySettings(manager);
|
||||
|
||||
connect(thread, SIGNAL(started()), this, SLOT(start()));
|
||||
initOutput();
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::initOutput() {
|
||||
QString fileName;
|
||||
QRegularExpressionMatch m = QRegularExpression(qsl("/([^/\\?]+)(\\?|$)")).match(updateUrl);
|
||||
if (m.hasMatch()) {
|
||||
fileName = m.captured(1).replace(QRegularExpression(qsl("[^a-zA-Z0-9_\\-]")), QString());
|
||||
}
|
||||
if (fileName.isEmpty()) {
|
||||
fileName = qsl("tupdate-%1").arg(rand());
|
||||
}
|
||||
QString dirStr = cWorkingDir() + qsl("tupdates/");
|
||||
fileName = dirStr + fileName;
|
||||
QFileInfo file(fileName);
|
||||
|
||||
QDir dir(dirStr);
|
||||
if (dir.exists()) {
|
||||
QFileInfoList all = dir.entryInfoList(QDir::Files);
|
||||
for (QFileInfoList::iterator i = all.begin(), e = all.end(); i != e; ++i) {
|
||||
if (i->absoluteFilePath() != file.absoluteFilePath()) {
|
||||
QFile::remove(i->absoluteFilePath());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dir.mkdir(dir.absolutePath());
|
||||
}
|
||||
outputFile.setFileName(fileName);
|
||||
if (file.exists()) {
|
||||
uint64 fullSize = file.size();
|
||||
if (fullSize < INT_MAX) {
|
||||
int32 goodSize = (int32)fullSize;
|
||||
if (goodSize % UpdateChunk) {
|
||||
goodSize = goodSize - (goodSize % UpdateChunk);
|
||||
if (goodSize) {
|
||||
if (outputFile.open(QIODevice::ReadOnly)) {
|
||||
QByteArray goodData = outputFile.readAll().mid(0, goodSize);
|
||||
outputFile.close();
|
||||
if (outputFile.open(QIODevice::WriteOnly)) {
|
||||
outputFile.write(goodData);
|
||||
outputFile.close();
|
||||
|
||||
QMutexLocker lock(&mutex);
|
||||
already = goodSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QMutexLocker lock(&mutex);
|
||||
already = goodSize;
|
||||
}
|
||||
}
|
||||
if (!already) {
|
||||
QFile::remove(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::start() {
|
||||
sendRequest();
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::sendRequest() {
|
||||
QNetworkRequest req(updateUrl);
|
||||
QByteArray rangeHeaderValue = "bytes=" + QByteArray::number(already) + "-";// + QByteArray::number(already + cUpdateChunk() - 1);
|
||||
req.setRawHeader("Range", rangeHeaderValue);
|
||||
req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
|
||||
if (reply) reply->deleteLater();
|
||||
reply = manager.get(req);
|
||||
connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(partFinished(qint64,qint64)));
|
||||
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(partFailed(QNetworkReply::NetworkError)));
|
||||
connect(reply, SIGNAL(metaDataChanged()), this, SLOT(partMetaGot()));
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::partMetaGot() {
|
||||
typedef QList<QNetworkReply::RawHeaderPair> Pairs;
|
||||
Pairs pairs = reply->rawHeaderPairs();
|
||||
for (Pairs::iterator i = pairs.begin(), e = pairs.end(); i != e; ++i) {
|
||||
if (QString::fromUtf8(i->first).toLower() == "content-range") {
|
||||
QRegularExpressionMatch m = QRegularExpression(qsl("/(\\d+)([^\\d]|$)")).match(QString::fromUtf8(i->second));
|
||||
if (m.hasMatch()) {
|
||||
{
|
||||
QMutexLocker lock(&mutex);
|
||||
full = m.captured(1).toInt();
|
||||
}
|
||||
emit App::app()->updateDownloading(already, full);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32 PsUpdateDownloader::ready() {
|
||||
QMutexLocker lock(&mutex);
|
||||
return already;
|
||||
}
|
||||
|
||||
int32 PsUpdateDownloader::size() {
|
||||
QMutexLocker lock(&mutex);
|
||||
return full;
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::partFinished(qint64 got, qint64 total) {
|
||||
if (!reply) return;
|
||||
|
||||
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||
if (statusCode.isValid()) {
|
||||
int status = statusCode.toInt();
|
||||
if (status != 200 && status != 206 && status != 416) {
|
||||
LOG(("Update Error: Bad HTTP status received in partFinished(): %1").arg(status));
|
||||
return fatalFail();
|
||||
}
|
||||
}
|
||||
|
||||
if (!already && !full) {
|
||||
QMutexLocker lock(&mutex);
|
||||
full = total;
|
||||
}
|
||||
DEBUG_LOG(("Update Info: part %1 of %2").arg(got).arg(total));
|
||||
|
||||
if (!outputFile.isOpen()) {
|
||||
if (!outputFile.open(QIODevice::Append)) {
|
||||
LOG(("Update Error: Could not open output file '%1' for appending").arg(outputFile.fileName()));
|
||||
return fatalFail();
|
||||
}
|
||||
}
|
||||
QByteArray r = reply->readAll();
|
||||
if (!r.isEmpty()) {
|
||||
outputFile.write(r);
|
||||
|
||||
QMutexLocker lock(&mutex);
|
||||
already += r.size();
|
||||
}
|
||||
if (got >= total) {
|
||||
reply->deleteLater();
|
||||
reply = 0;
|
||||
outputFile.close();
|
||||
unpackUpdate();
|
||||
} else {
|
||||
emit App::app()->updateDownloading(already, full);
|
||||
}
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::partFailed(QNetworkReply::NetworkError e) {
|
||||
if (!reply) return;
|
||||
|
||||
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||
reply->deleteLater();
|
||||
reply = 0;
|
||||
if (statusCode.isValid()) {
|
||||
int status = statusCode.toInt();
|
||||
if (status == 416) { // Requested range not satisfiable
|
||||
outputFile.close();
|
||||
unpackUpdate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
LOG(("Update Error: failed to download part starting from %1, error %2").arg(already).arg(e));
|
||||
emit App::app()->updateFailed();
|
||||
}
|
||||
|
||||
bool _removeDirectory(const QString &path) { // from http://stackoverflow.com/questions/2256945/removing-a-non-empty-directory-programmatically-in-c-or-c
|
||||
QByteArray pathRaw = QFile::encodeName(path);
|
||||
DIR *d = opendir(pathRaw.constData());
|
||||
@@ -1131,236 +977,8 @@ bool _removeDirectory(const QString &path) { // from http://stackoverflow.com/qu
|
||||
return !rmdir(pathRaw.constData());
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::deleteDir(const QString &dir) {
|
||||
_removeDirectory(dir);
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::fatalFail() {
|
||||
clearAll();
|
||||
emit App::app()->updateFailed();
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::clearAll() {
|
||||
deleteDir(cWorkingDir() + qsl("tupdates"));
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
typedef DWORD VerInt;
|
||||
typedef WCHAR VerChar;
|
||||
#else
|
||||
typedef int VerInt;
|
||||
typedef wchar_t VerChar;
|
||||
#endif
|
||||
|
||||
void PsUpdateDownloader::unpackUpdate() {
|
||||
QByteArray packed;
|
||||
if (!outputFile.open(QIODevice::ReadOnly)) {
|
||||
LOG(("Update Error: cant read updates file!"));
|
||||
return fatalFail();
|
||||
}
|
||||
#ifdef Q_OS_WIN // use Lzma SDK for win
|
||||
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header
|
||||
#else
|
||||
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = 0, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hOriginalSizeLen; // header
|
||||
#endif
|
||||
QByteArray compressed = outputFile.readAll();
|
||||
int32 compressedLen = compressed.size() - hSize;
|
||||
if (compressedLen <= 0) {
|
||||
LOG(("Update Error: bad compressed size: %1").arg(compressed.size()));
|
||||
return fatalFail();
|
||||
}
|
||||
outputFile.close();
|
||||
|
||||
QString tempDirPath = cWorkingDir() + qsl("tupdates/temp"), readyDirPath = cWorkingDir() + qsl("tupdates/ready");
|
||||
deleteDir(tempDirPath);
|
||||
deleteDir(readyDirPath);
|
||||
|
||||
QDir tempDir(tempDirPath), readyDir(readyDirPath);
|
||||
if (tempDir.exists() || readyDir.exists()) {
|
||||
LOG(("Update Error: cant clear tupdates/temp or tupdates/ready dir!"));
|
||||
return fatalFail();
|
||||
}
|
||||
|
||||
uchar sha1Buffer[20];
|
||||
bool goodSha1 = !memcmp(compressed.constData() + hSigLen, hashSha1(compressed.constData() + hSigLen + hShaLen, compressedLen + hPropsLen + hOriginalSizeLen, sha1Buffer), hShaLen);
|
||||
if (!goodSha1) {
|
||||
LOG(("Update Error: bad SHA1 hash of update file!"));
|
||||
return fatalFail();
|
||||
}
|
||||
|
||||
RSA *pbKey = PEM_read_bio_RSAPublicKey(BIO_new_mem_buf(const_cast<char*>(DevChannel ? UpdatesPublicDevKey : UpdatesPublicKey), -1), 0, 0, 0);
|
||||
if (!pbKey) {
|
||||
LOG(("Update Error: cant read public rsa key!"));
|
||||
return fatalFail();
|
||||
}
|
||||
if (RSA_verify(NID_sha1, (const uchar*)(compressed.constData() + hSigLen), hShaLen, (const uchar*)(compressed.constData()), hSigLen, pbKey) != 1) { // verify signature
|
||||
RSA_free(pbKey);
|
||||
LOG(("Update Error: bad RSA signature of update file!"));
|
||||
return fatalFail();
|
||||
}
|
||||
RSA_free(pbKey);
|
||||
|
||||
QByteArray uncompressed;
|
||||
|
||||
int32 uncompressedLen;
|
||||
memcpy(&uncompressedLen, compressed.constData() + hSigLen + hShaLen + hPropsLen, hOriginalSizeLen);
|
||||
uncompressed.resize(uncompressedLen);
|
||||
|
||||
size_t resultLen = uncompressed.size();
|
||||
#ifdef Q_OS_WIN // use Lzma SDK for win
|
||||
SizeT srcLen = compressedLen;
|
||||
int uncompressRes = LzmaUncompress((uchar*)uncompressed.data(), &resultLen, (const uchar*)(compressed.constData() + hSize), &srcLen, (const uchar*)(compressed.constData() + hSigLen + hShaLen), LZMA_PROPS_SIZE);
|
||||
if (uncompressRes != SZ_OK) {
|
||||
LOG(("Update Error: could not uncompress lzma, code: %1").arg(uncompressRes));
|
||||
return fatalFail();
|
||||
}
|
||||
#else
|
||||
lzma_stream stream = LZMA_STREAM_INIT;
|
||||
|
||||
lzma_ret ret = lzma_stream_decoder(&stream, UINT64_MAX, LZMA_CONCATENATED);
|
||||
if (ret != LZMA_OK) {
|
||||
const char *msg;
|
||||
switch (ret) {
|
||||
case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break;
|
||||
case LZMA_OPTIONS_ERROR: msg = "Specified preset is not supported"; break;
|
||||
case LZMA_UNSUPPORTED_CHECK: msg = "Specified integrity check is not supported"; break;
|
||||
default: msg = "Unknown error, possibly a bug"; break;
|
||||
}
|
||||
LOG(("Error initializing the decoder: %1 (error code %2)").arg(msg).arg(ret));
|
||||
return fatalFail();
|
||||
}
|
||||
|
||||
stream.avail_in = compressedLen;
|
||||
stream.next_in = (uint8_t*)(compressed.constData() + hSize);
|
||||
stream.avail_out = resultLen;
|
||||
stream.next_out = (uint8_t*)uncompressed.data();
|
||||
|
||||
lzma_ret res = lzma_code(&stream, LZMA_FINISH);
|
||||
if (stream.avail_in) {
|
||||
LOG(("Error in decompression, %1 bytes left in _in of %2 whole.").arg(stream.avail_in).arg(compressedLen));
|
||||
return fatalFail();
|
||||
} else if (stream.avail_out) {
|
||||
LOG(("Error in decompression, %1 bytes free left in _out of %2 whole.").arg(stream.avail_out).arg(resultLen));
|
||||
return fatalFail();
|
||||
}
|
||||
lzma_end(&stream);
|
||||
if (res != LZMA_OK && res != LZMA_STREAM_END) {
|
||||
const char *msg;
|
||||
switch (res) {
|
||||
case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break;
|
||||
case LZMA_FORMAT_ERROR: msg = "The input data is not in the .xz format"; break;
|
||||
case LZMA_OPTIONS_ERROR: msg = "Unsupported compression options"; break;
|
||||
case LZMA_DATA_ERROR: msg = "Compressed file is corrupt"; break;
|
||||
case LZMA_BUF_ERROR: msg = "Compressed data is truncated or otherwise corrupt"; break;
|
||||
default: msg = "Unknown error, possibly a bug"; break;
|
||||
}
|
||||
LOG(("Error in decompression: %1 (error code %2)").arg(msg).arg(res));
|
||||
return fatalFail();
|
||||
}
|
||||
#endif
|
||||
|
||||
tempDir.mkdir(tempDir.absolutePath());
|
||||
|
||||
quint32 version;
|
||||
{
|
||||
QBuffer buffer(&uncompressed);
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
QDataStream stream(&buffer);
|
||||
stream.setVersion(QDataStream::Qt_5_1);
|
||||
|
||||
stream >> version;
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("Update Error: cant read version from downloaded stream, status: %1").arg(stream.status()));
|
||||
return fatalFail();
|
||||
}
|
||||
if (int32(version) <= AppVersion) {
|
||||
LOG(("Update Error: downloaded version %1 is not greater, than mine %2").arg(version).arg(AppVersion));
|
||||
return fatalFail();
|
||||
}
|
||||
|
||||
quint32 filesCount;
|
||||
stream >> filesCount;
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("Update Error: cant read files count from downloaded stream, status: %1").arg(stream.status()));
|
||||
return fatalFail();
|
||||
}
|
||||
if (!filesCount) {
|
||||
LOG(("Update Error: update is empty!"));
|
||||
return fatalFail();
|
||||
}
|
||||
for (uint32 i = 0; i < filesCount; ++i) {
|
||||
QString relativeName;
|
||||
quint32 fileSize;
|
||||
QByteArray fileInnerData;
|
||||
bool executable = false;
|
||||
|
||||
stream >> relativeName >> fileSize >> fileInnerData;
|
||||
#if defined Q_OS_MAC || defined Q_OS_LINUX
|
||||
stream >> executable;
|
||||
#endif
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("Update Error: cant read file from downloaded stream, status: %1").arg(stream.status()));
|
||||
return fatalFail();
|
||||
}
|
||||
if (fileSize != quint32(fileInnerData.size())) {
|
||||
LOG(("Update Error: bad file size %1 not matching data size %2").arg(fileSize).arg(fileInnerData.size()));
|
||||
return fatalFail();
|
||||
}
|
||||
|
||||
QFile f(tempDirPath + '/' + relativeName);
|
||||
if (!QDir().mkpath(QFileInfo(f).absolutePath())) {
|
||||
LOG(("Update Error: cant mkpath for file '%1'").arg(tempDirPath + '/' + relativeName));
|
||||
return fatalFail();
|
||||
}
|
||||
if (!f.open(QIODevice::WriteOnly)) {
|
||||
LOG(("Update Error: cant open file '%1' for writing").arg(tempDirPath + '/' + relativeName));
|
||||
return fatalFail();
|
||||
}
|
||||
if (f.write(fileInnerData) != fileSize) {
|
||||
f.close();
|
||||
LOG(("Update Error: cant write file '%1'").arg(tempDirPath + '/' + relativeName));
|
||||
return fatalFail();
|
||||
}
|
||||
f.close();
|
||||
if (executable) {
|
||||
QFileDevice::Permissions p = f.permissions();
|
||||
p |= QFileDevice::ExeOwner | QFileDevice::ExeUser | QFileDevice::ExeGroup | QFileDevice::ExeOther;
|
||||
f.setPermissions(p);
|
||||
}
|
||||
}
|
||||
|
||||
// create tdata/version file
|
||||
tempDir.mkdir(QDir(tempDirPath + qsl("/tdata")).absolutePath());
|
||||
std::wstring versionString = ((version % 1000) ? QString("%1.%2.%3").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000)).arg(int(version % 1000)) : QString("%1.%2").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000))).toStdWString();
|
||||
|
||||
VerInt versionNum = VerInt(version), versionLen = VerInt(versionString.size() * sizeof(VerChar));
|
||||
VerChar versionStr[32];
|
||||
memcpy(versionStr, versionString.c_str(), versionLen);
|
||||
|
||||
QFile fVersion(tempDirPath + qsl("/tdata/version"));
|
||||
if (!fVersion.open(QIODevice::WriteOnly)) {
|
||||
LOG(("Update Error: cant write version file '%1'").arg(tempDirPath + qsl("/version")));
|
||||
return fatalFail();
|
||||
}
|
||||
fVersion.write((const char*)&versionNum, sizeof(VerInt));
|
||||
fVersion.write((const char*)&versionLen, sizeof(VerInt));
|
||||
fVersion.write((const char*)&versionStr[0], versionLen);
|
||||
fVersion.close();
|
||||
}
|
||||
|
||||
if (!tempDir.rename(tempDir.absolutePath(), readyDir.absolutePath())) {
|
||||
LOG(("Update Error: cant rename temp dir '%1' to ready dir '%2'").arg(tempDir.absolutePath()).arg(readyDir.absolutePath()));
|
||||
return fatalFail();
|
||||
}
|
||||
deleteDir(tempDirPath);
|
||||
outputFile.remove();
|
||||
|
||||
emit App::app()->updateReady();
|
||||
}
|
||||
|
||||
PsUpdateDownloader::~PsUpdateDownloader() {
|
||||
delete reply;
|
||||
reply = 0;
|
||||
void psDeleteDir(const QString &dir) {
|
||||
_removeDirectory(dir);
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -1474,129 +1092,6 @@ int psFixPrevious() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
bool moveFile(const char *from, const char *to) {
|
||||
FILE *ffrom = fopen(from, "rb"), *fto = fopen(to, "wb");
|
||||
if (!ffrom) {
|
||||
if (fto) fclose(fto);
|
||||
return false;
|
||||
}
|
||||
if (!fto) {
|
||||
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
|
||||
if (fstat(fileno(ffrom), &fst) != 0) {
|
||||
fclose(ffrom);
|
||||
fclose(fto);
|
||||
return false;
|
||||
}
|
||||
//update to the same uid/gid
|
||||
if (fchown(fileno(fto), fst.st_uid, fst.st_gid) != 0) {
|
||||
fclose(ffrom);
|
||||
fclose(fto);
|
||||
return false;
|
||||
}
|
||||
//update the permissions
|
||||
if (fchmod(fileno(fto), fst.st_mode) != 0) {
|
||||
fclose(ffrom);
|
||||
fclose(fto);
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose(ffrom);
|
||||
fclose(fto);
|
||||
|
||||
if (unlink(from)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool psCheckReadyUpdate() {
|
||||
QString readyPath = cWorkingDir() + qsl("tupdates/ready");
|
||||
if (!QDir(readyPath).exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check ready version
|
||||
QString versionPath = readyPath + qsl("/tdata/version");
|
||||
{
|
||||
QFile fVersion(versionPath);
|
||||
if (!fVersion.open(QIODevice::ReadOnly)) {
|
||||
LOG(("Update Error: cant read version file '%1'").arg(versionPath));
|
||||
PsUpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
VerInt versionNum;
|
||||
if (fVersion.read((char*)&versionNum, sizeof(VerInt)) != sizeof(VerInt)) {
|
||||
LOG(("Update Error: cant read version from file '%1'").arg(versionPath));
|
||||
PsUpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
fVersion.close();
|
||||
if (versionNum <= AppVersion) {
|
||||
LOG(("Update Error: cant install version %1 having version %2").arg(versionNum).arg(AppVersion));
|
||||
PsUpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
QString curUpdater = (cExeDir() + "Updater.exe");
|
||||
QFileInfo updater(cWorkingDir() + "tupdates/ready/Updater.exe");
|
||||
#elif defined Q_OS_MAC
|
||||
QString curUpdater = (cExeDir() + "Telegram.app/Contents/Frameworks/Updater");
|
||||
QFileInfo updater(cWorkingDir() + "tupdates/ready/Telegram.app/Contents/Frameworks/Updater");
|
||||
#elif defined Q_OS_LINUX
|
||||
QString curUpdater = (cExeDir() + "Updater");
|
||||
QFileInfo updater(cWorkingDir() + "tupdates/ready/Updater");
|
||||
#endif
|
||||
if (!updater.exists()) {
|
||||
QFileInfo current(curUpdater);
|
||||
if (!current.exists()) {
|
||||
PsUpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
if (!QFile(current.absoluteFilePath()).copy(updater.absoluteFilePath())) {
|
||||
PsUpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#ifdef Q_OS_WIN
|
||||
if (CopyFile(updater.absoluteFilePath().toStdWString().c_str(), curUpdater.toStdWString().c_str(), FALSE) == FALSE) {
|
||||
PsUpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
if (DeleteFile(updater.absoluteFilePath().toStdWString().c_str()) == FALSE) {
|
||||
PsUpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
#elif defined Q_OS_MAC
|
||||
QFileInfo to(curUpdater);
|
||||
QDir().mkpath(to.absolutePath());
|
||||
if (!objc_moveFile(updater.absoluteFilePath(), curUpdater)) {
|
||||
PsUpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
#elif defined Q_OS_LINUX
|
||||
if (!moveFile(QFile::encodeName(updater.absoluteFilePath()).constData(), QFile::encodeName(curUpdater).constData())) {
|
||||
PsUpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void psPostprocessFile(const QString &name) {
|
||||
}
|
||||
|
||||
@@ -1606,7 +1101,7 @@ void psOpenFile(const QString &name, bool openWith) {
|
||||
|
||||
void psShowInFolder(const QString &name) {
|
||||
App::wnd()->layerHidden();
|
||||
system(("nautilus \"" + QFileInfo(name).absoluteDir().absolutePath() + "\"").toLocal8Bit().constData());
|
||||
system((qsl("xdg-open ") + escapeShell(QFileInfo(name).absoluteDir().absolutePath())).toUtf8().constData());
|
||||
}
|
||||
|
||||
void psStart() {
|
||||
@@ -1617,12 +1112,12 @@ void psFinish() {
|
||||
|
||||
namespace {
|
||||
bool _psRunCommand(const QString &command) {
|
||||
int result = system(command.toLocal8Bit().constData());
|
||||
int result = system(command.toUtf8().constData());
|
||||
if (result) {
|
||||
DEBUG_LOG(("App Error: command failed, code: %1, command: %2").arg(result).arg(command.toLocal8Bit().constData()));
|
||||
DEBUG_LOG(("App Error: command failed, code: %1, command (in utf8): %2").arg(result).arg(command));
|
||||
return false;
|
||||
}
|
||||
DEBUG_LOG(("App Info: command succeeded, command: %1").arg(command.toLocal8Bit().constData()));
|
||||
DEBUG_LOG(("App Info: command succeeded, command (in utf8): %1").arg(command));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1655,19 +1150,19 @@ void psRegisterCustomScheme() {
|
||||
s << "Version=1.0\n";
|
||||
s << "Name=Telegram Desktop\n";
|
||||
s << "Comment=Official desktop version of Telegram messaging app\n";
|
||||
s << "Exec=" << cExeDir().toLocal8Bit().constData() << cExeName().toLocal8Bit().constData() << " -- %u\n";
|
||||
s << "Icon=" << icon.toLocal8Bit().constData() << "\n";
|
||||
s << "Exec=" << escapeShell(cExeDir() + cExeName()) << " -- %u\n";
|
||||
s << "Icon=" << icon << "\n";
|
||||
s << "Terminal=false\n";
|
||||
s << "Type=Application\n";
|
||||
s << "Categories=Network;\n";
|
||||
s << "MimeType=application/x-xdg-protocol-tg;x-scheme-handler/tg;\n";
|
||||
f.close();
|
||||
|
||||
if (_psRunCommand(qsl("desktop-file-install --dir=%1.local/share/applications --delete-original \"%2\"").arg(home).arg(file))) {
|
||||
if (_psRunCommand(qsl("desktop-file-install --dir=%1 --delete-original %2").arg(escapeShell(home + qsl(".local/share/applications"))).arg(escapeShell(file)))) {
|
||||
DEBUG_LOG(("App Info: removing old .desktop file"));
|
||||
QFile(qsl("%1.local/share/applications/telegram.desktop").arg(home)).remove();
|
||||
|
||||
_psRunCommand(qsl("update-desktop-database %1.local/share/applications").arg(home));
|
||||
_psRunCommand(qsl("update-desktop-database %1").arg(escapeShell(home + qsl(".local/share/applications"))));
|
||||
_psRunCommand(qsl("xdg-mime default telegramdesktop.desktop x-scheme-handler/tg"));
|
||||
}
|
||||
} else {
|
||||
@@ -1676,7 +1171,7 @@ void psRegisterCustomScheme() {
|
||||
}
|
||||
|
||||
DEBUG_LOG(("App Info: registerting for Gnome"));
|
||||
if (_psRunCommand(qsl("gconftool-2 -t string -s /desktop/gnome/url-handlers/tg/command \"%1 -- %s\"").arg(cExeDir() + cExeName()))) {
|
||||
if (_psRunCommand(qsl("gconftool-2 -t string -s /desktop/gnome/url-handlers/tg/command %1").arg(escapeShell(qsl("%1 -- %s").arg(escapeShell(cExeDir() + cExeName())))))) {
|
||||
_psRunCommand(qsl("gconftool-2 -t bool -s /desktop/gnome/url-handlers/tg/needs_terminal false"));
|
||||
_psRunCommand(qsl("gconftool-2 -t bool -s /desktop/gnome/url-handlers/tg/enabled true"));
|
||||
}
|
||||
@@ -1697,7 +1192,7 @@ void psRegisterCustomScheme() {
|
||||
QTextStream s(&f);
|
||||
s.setCodec("UTF-8");
|
||||
s << "[Protocol]\n";
|
||||
s << "exec=" << cExeDir().toLocal8Bit().constData() << cExeName().toLocal8Bit().constData() << " -- %u\n";
|
||||
s << "exec=" << escapeShell(cExeDir() + cExeName()) << " -- %u\n";
|
||||
s << "protocol=tg\n";
|
||||
s << "input=none\n";
|
||||
s << "output=none\n";
|
||||
@@ -1741,7 +1236,7 @@ bool _execUpdater(bool update = true) {
|
||||
args[argIndex++] = p_datafile;
|
||||
}
|
||||
}
|
||||
QByteArray pathf = cWorkingDir().toLocal8Bit();
|
||||
QByteArray pathf = cWorkingDir().toUtf8();
|
||||
if (pathf.size() < MaxLen) {
|
||||
memcpy(p_pathbuf, pathf.constData(), pathf.size());
|
||||
args[argIndex++] = p_path;
|
||||
@@ -1758,8 +1253,7 @@ bool _execUpdater(bool update = true) {
|
||||
|
||||
void psExecUpdater() {
|
||||
if (!_execUpdater()) {
|
||||
QString readyPath = cWorkingDir() + qsl("tupdates/ready");
|
||||
PsUpdateDownloader::deleteDir(readyPath);
|
||||
psDeleteDir(cWorkingDir() + qsl("tupdates/temp"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1779,3 +1273,49 @@ void psSendToMenu(bool send, bool silent) {
|
||||
|
||||
void psUpdateOverlayed(QWidget *widget) {
|
||||
}
|
||||
|
||||
bool linuxMoveFile(const char *from, const char *to) {
|
||||
FILE *ffrom = fopen(from, "rb"), *fto = fopen(to, "wb");
|
||||
if (!ffrom) {
|
||||
if (fto) fclose(fto);
|
||||
return false;
|
||||
}
|
||||
if (!fto) {
|
||||
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
|
||||
if (fstat(fileno(ffrom), &fst) != 0) {
|
||||
fclose(ffrom);
|
||||
fclose(fto);
|
||||
return false;
|
||||
}
|
||||
//update to the same uid/gid
|
||||
if (fchown(fileno(fto), fst.st_uid, fst.st_gid) != 0) {
|
||||
fclose(ffrom);
|
||||
fclose(fto);
|
||||
return false;
|
||||
}
|
||||
//update the permissions
|
||||
if (fchmod(fileno(fto), fst.st_mode) != 0) {
|
||||
fclose(ffrom);
|
||||
fclose(fto);
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose(ffrom);
|
||||
fclose(fto);
|
||||
|
||||
if (unlink(from)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -116,55 +116,9 @@ public:
|
||||
void psInstallEventFilter();
|
||||
~PsApplication();
|
||||
|
||||
signals:
|
||||
|
||||
void updateChecking();
|
||||
void updateLatest();
|
||||
void updateDownloading(qint64 ready, qint64 total);
|
||||
void updateReady();
|
||||
void updateFailed();
|
||||
|
||||
};
|
||||
|
||||
class PsUpdateDownloader : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PsUpdateDownloader(QThread *thread, const MTPDhelp_appUpdate &update);
|
||||
PsUpdateDownloader(QThread *thread, const QString &url);
|
||||
|
||||
void unpackUpdate();
|
||||
|
||||
int32 ready();
|
||||
int32 size();
|
||||
|
||||
static void deleteDir(const QString &dir);
|
||||
static void clearAll();
|
||||
|
||||
~PsUpdateDownloader();
|
||||
|
||||
public slots:
|
||||
|
||||
void start();
|
||||
void partMetaGot();
|
||||
void partFinished(qint64 got, qint64 total);
|
||||
void partFailed(QNetworkReply::NetworkError e);
|
||||
void sendRequest();
|
||||
|
||||
private:
|
||||
void initOutput();
|
||||
|
||||
void fatalFail();
|
||||
|
||||
QString updateUrl;
|
||||
QNetworkAccessManager manager;
|
||||
QNetworkReply *reply;
|
||||
int32 already, full;
|
||||
QFile outputFile;
|
||||
|
||||
QMutex mutex;
|
||||
|
||||
};
|
||||
void psDeleteDir(const QString &dir);
|
||||
|
||||
void psUserActionDone();
|
||||
bool psIdleSupported();
|
||||
@@ -194,7 +148,6 @@ void psBringToBack(QWidget *w);
|
||||
int psCleanup();
|
||||
int psFixPrevious();
|
||||
|
||||
bool psCheckReadyUpdate();
|
||||
void psExecUpdater();
|
||||
void psExecTelegram();
|
||||
|
||||
@@ -212,3 +165,5 @@ void psUpdateOverlayed(QWidget *widget);
|
||||
inline QString psConvertFileUrl(const QString &url) {
|
||||
return url;
|
||||
}
|
||||
|
||||
bool linuxMoveFile(const char *from, const char *to);
|
||||
|
||||
@@ -422,7 +422,7 @@ void PsMainWindow::psMacUpdateMenu() {
|
||||
canPaste = !App::app()->clipboard()->text().isEmpty();
|
||||
} else if (FlatTextarea *edit = qobject_cast<FlatTextarea*>(focused)) {
|
||||
canCut = canCopy = canDelete = edit->textCursor().hasSelection();
|
||||
canSelectAll = edit->hasText();
|
||||
canSelectAll = !edit->getLastText().isEmpty();
|
||||
canUndo = edit->isUndoAvailable();
|
||||
canRedo = edit->isRedoAvailable();
|
||||
canPaste = !App::app()->clipboard()->text().isEmpty();
|
||||
@@ -522,415 +522,10 @@ PsApplication::~PsApplication() {
|
||||
_psEventFilter = 0;
|
||||
}
|
||||
|
||||
PsUpdateDownloader::PsUpdateDownloader(QThread *thread, const MTPDhelp_appUpdate &update) : reply(0), already(0), full(0) {
|
||||
updateUrl = qs(update.vurl);
|
||||
moveToThread(thread);
|
||||
manager.moveToThread(thread);
|
||||
App::setProxySettings(manager);
|
||||
|
||||
connect(thread, SIGNAL(started()), this, SLOT(start()));
|
||||
initOutput();
|
||||
}
|
||||
|
||||
PsUpdateDownloader::PsUpdateDownloader(QThread *thread, const QString &url) : reply(0), already(0), full(0) {
|
||||
updateUrl = url;
|
||||
moveToThread(thread);
|
||||
manager.moveToThread(thread);
|
||||
App::setProxySettings(manager);
|
||||
|
||||
connect(thread, SIGNAL(started()), this, SLOT(start()));
|
||||
initOutput();
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::initOutput() {
|
||||
QString fileName;
|
||||
QRegularExpressionMatch m = QRegularExpression(qsl("/([^/\\?]+)(\\?|$)")).match(updateUrl);
|
||||
if (m.hasMatch()) {
|
||||
fileName = m.captured(1).replace(QRegularExpression(qsl("[^a-zA-Z0-9_\\-]")), QString());
|
||||
}
|
||||
if (fileName.isEmpty()) {
|
||||
fileName = qsl("tupdate-%1").arg(rand());
|
||||
}
|
||||
QString dirStr = cWorkingDir() + qsl("tupdates/");
|
||||
fileName = dirStr + fileName;
|
||||
QFileInfo file(fileName);
|
||||
|
||||
QDir dir(dirStr);
|
||||
if (dir.exists()) {
|
||||
QFileInfoList all = dir.entryInfoList(QDir::Files);
|
||||
for (QFileInfoList::iterator i = all.begin(), e = all.end(); i != e; ++i) {
|
||||
if (i->absoluteFilePath() != file.absoluteFilePath()) {
|
||||
QFile::remove(i->absoluteFilePath());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dir.mkdir(dir.absolutePath());
|
||||
}
|
||||
outputFile.setFileName(fileName);
|
||||
if (file.exists()) {
|
||||
uint64 fullSize = file.size();
|
||||
if (fullSize < INT_MAX) {
|
||||
int32 goodSize = (int32)fullSize;
|
||||
if (goodSize % UpdateChunk) {
|
||||
goodSize = goodSize - (goodSize % UpdateChunk);
|
||||
if (goodSize) {
|
||||
if (outputFile.open(QIODevice::ReadOnly)) {
|
||||
QByteArray goodData = outputFile.readAll().mid(0, goodSize);
|
||||
outputFile.close();
|
||||
if (outputFile.open(QIODevice::WriteOnly)) {
|
||||
outputFile.write(goodData);
|
||||
outputFile.close();
|
||||
|
||||
QMutexLocker lock(&mutex);
|
||||
already = goodSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QMutexLocker lock(&mutex);
|
||||
already = goodSize;
|
||||
}
|
||||
}
|
||||
if (!already) {
|
||||
QFile::remove(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::start() {
|
||||
sendRequest();
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::sendRequest() {
|
||||
QNetworkRequest req(updateUrl);
|
||||
QByteArray rangeHeaderValue = "bytes=" + QByteArray::number(already) + "-";// + QByteArray::number(already + cUpdateChunk() - 1);
|
||||
req.setRawHeader("Range", rangeHeaderValue);
|
||||
req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
|
||||
if (reply) reply->deleteLater();
|
||||
reply = manager.get(req);
|
||||
connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(partFinished(qint64,qint64)));
|
||||
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(partFailed(QNetworkReply::NetworkError)));
|
||||
connect(reply, SIGNAL(metaDataChanged()), this, SLOT(partMetaGot()));
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::partMetaGot() {
|
||||
typedef QList<QNetworkReply::RawHeaderPair> Pairs;
|
||||
Pairs pairs = reply->rawHeaderPairs();
|
||||
for (Pairs::iterator i = pairs.begin(), e = pairs.end(); i != e; ++i) {
|
||||
if (QString::fromUtf8(i->first).toLower() == "content-range") {
|
||||
QRegularExpressionMatch m = QRegularExpression(qsl("/(\\d+)([^\\d]|$)")).match(QString::fromUtf8(i->second));
|
||||
if (m.hasMatch()) {
|
||||
{
|
||||
QMutexLocker lock(&mutex);
|
||||
full = m.captured(1).toInt();
|
||||
}
|
||||
emit App::app()->updateDownloading(already, full);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32 PsUpdateDownloader::ready() {
|
||||
QMutexLocker lock(&mutex);
|
||||
return already;
|
||||
}
|
||||
|
||||
int32 PsUpdateDownloader::size() {
|
||||
QMutexLocker lock(&mutex);
|
||||
return full;
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::partFinished(qint64 got, qint64 total) {
|
||||
if (!reply) return;
|
||||
|
||||
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||
if (statusCode.isValid()) {
|
||||
int status = statusCode.toInt();
|
||||
if (status != 200 && status != 206 && status != 416) {
|
||||
LOG(("Update Error: Bad HTTP status received in partFinished(): %1").arg(status));
|
||||
return fatalFail();
|
||||
}
|
||||
}
|
||||
|
||||
if (!already && !full) {
|
||||
QMutexLocker lock(&mutex);
|
||||
full = total;
|
||||
}
|
||||
DEBUG_LOG(("Update Info: part %1 of %2").arg(got).arg(total));
|
||||
|
||||
if (!outputFile.isOpen()) {
|
||||
if (!outputFile.open(QIODevice::Append)) {
|
||||
LOG(("Update Error: Could not open output file '%1' for appending").arg(outputFile.fileName()));
|
||||
return fatalFail();
|
||||
}
|
||||
}
|
||||
QByteArray r = reply->readAll();
|
||||
if (!r.isEmpty()) {
|
||||
outputFile.write(r);
|
||||
|
||||
QMutexLocker lock(&mutex);
|
||||
already += r.size();
|
||||
}
|
||||
if (got >= total) {
|
||||
reply->deleteLater();
|
||||
reply = 0;
|
||||
outputFile.close();
|
||||
unpackUpdate();
|
||||
} else {
|
||||
emit App::app()->updateDownloading(already, full);
|
||||
}
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::partFailed(QNetworkReply::NetworkError e) {
|
||||
if (!reply) return;
|
||||
|
||||
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||
reply->deleteLater();
|
||||
reply = 0;
|
||||
if (statusCode.isValid()) {
|
||||
int status = statusCode.toInt();
|
||||
if (status == 416) { // Requested range not satisfiable
|
||||
outputFile.close();
|
||||
unpackUpdate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
LOG(("Update Error: failed to download part starting from %1, error %2").arg(already).arg(e));
|
||||
emit App::app()->updateFailed();
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::deleteDir(const QString &dir) {
|
||||
void psDeleteDir(const QString &dir) {
|
||||
objc_deleteDir(dir);
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::fatalFail() {
|
||||
clearAll();
|
||||
emit App::app()->updateFailed();
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::clearAll() {
|
||||
deleteDir(cWorkingDir() + qsl("tupdates"));
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
typedef DWORD VerInt;
|
||||
typedef WCHAR VerChar;
|
||||
#else
|
||||
typedef int VerInt;
|
||||
typedef wchar_t VerChar;
|
||||
#endif
|
||||
|
||||
void PsUpdateDownloader::unpackUpdate() {
|
||||
QByteArray packed;
|
||||
if (!outputFile.open(QIODevice::ReadOnly)) {
|
||||
LOG(("Update Error: cant read updates file!"));
|
||||
return fatalFail();
|
||||
}
|
||||
#ifdef Q_OS_WIN // use Lzma SDK for win
|
||||
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header
|
||||
#else
|
||||
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = 0, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hOriginalSizeLen; // header
|
||||
#endif
|
||||
QByteArray compressed = outputFile.readAll();
|
||||
int32 compressedLen = compressed.size() - hSize;
|
||||
if (compressedLen <= 0) {
|
||||
LOG(("Update Error: bad compressed size: %1").arg(compressed.size()));
|
||||
return fatalFail();
|
||||
}
|
||||
outputFile.close();
|
||||
|
||||
QString tempDirPath = cWorkingDir() + qsl("tupdates/temp"), readyDirPath = cWorkingDir() + qsl("tupdates/ready");
|
||||
deleteDir(tempDirPath);
|
||||
deleteDir(readyDirPath);
|
||||
|
||||
QDir tempDir(tempDirPath), readyDir(readyDirPath);
|
||||
if (tempDir.exists() || readyDir.exists()) {
|
||||
LOG(("Update Error: cant clear tupdates/temp or tupdates/ready dir!"));
|
||||
return fatalFail();
|
||||
}
|
||||
|
||||
uchar sha1Buffer[20];
|
||||
bool goodSha1 = !memcmp(compressed.constData() + hSigLen, hashSha1(compressed.constData() + hSigLen + hShaLen, compressedLen + hPropsLen + hOriginalSizeLen, sha1Buffer), hShaLen);
|
||||
if (!goodSha1) {
|
||||
LOG(("Update Error: bad SHA1 hash of update file!"));
|
||||
return fatalFail();
|
||||
}
|
||||
|
||||
RSA *pbKey = PEM_read_bio_RSAPublicKey(BIO_new_mem_buf(const_cast<char*>(DevChannel ? UpdatesPublicDevKey : UpdatesPublicKey), -1), 0, 0, 0);
|
||||
if (!pbKey) {
|
||||
LOG(("Update Error: cant read public rsa key!"));
|
||||
return fatalFail();
|
||||
}
|
||||
if (RSA_verify(NID_sha1, (const uchar*)(compressed.constData() + hSigLen), hShaLen, (const uchar*)(compressed.constData()), hSigLen, pbKey) != 1) { // verify signature
|
||||
RSA_free(pbKey);
|
||||
LOG(("Update Error: bad RSA signature of update file!"));
|
||||
return fatalFail();
|
||||
}
|
||||
RSA_free(pbKey);
|
||||
|
||||
QByteArray uncompressed;
|
||||
|
||||
int32 uncompressedLen;
|
||||
memcpy(&uncompressedLen, compressed.constData() + hSigLen + hShaLen + hPropsLen, hOriginalSizeLen);
|
||||
uncompressed.resize(uncompressedLen);
|
||||
|
||||
size_t resultLen = uncompressed.size();
|
||||
#ifdef Q_OS_WIN // use Lzma SDK for win
|
||||
SizeT srcLen = compressedLen;
|
||||
int uncompressRes = LzmaUncompress((uchar*)uncompressed.data(), &resultLen, (const uchar*)(compressed.constData() + hSize), &srcLen, (const uchar*)(compressed.constData() + hSigLen + hShaLen), LZMA_PROPS_SIZE);
|
||||
if (uncompressRes != SZ_OK) {
|
||||
LOG(("Update Error: could not uncompress lzma, code: %1").arg(uncompressRes));
|
||||
return fatalFail();
|
||||
}
|
||||
#else
|
||||
lzma_stream stream = LZMA_STREAM_INIT;
|
||||
|
||||
lzma_ret ret = lzma_stream_decoder(&stream, UINT64_MAX, LZMA_CONCATENATED);
|
||||
if (ret != LZMA_OK) {
|
||||
const char *msg;
|
||||
switch (ret) {
|
||||
case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break;
|
||||
case LZMA_OPTIONS_ERROR: msg = "Specified preset is not supported"; break;
|
||||
case LZMA_UNSUPPORTED_CHECK: msg = "Specified integrity check is not supported"; break;
|
||||
default: msg = "Unknown error, possibly a bug"; break;
|
||||
}
|
||||
LOG(("Error initializing the decoder: %1 (error code %2)").arg(msg).arg(ret));
|
||||
return fatalFail();
|
||||
}
|
||||
|
||||
stream.avail_in = compressedLen;
|
||||
stream.next_in = (uint8_t*)(compressed.constData() + hSize);
|
||||
stream.avail_out = resultLen;
|
||||
stream.next_out = (uint8_t*)uncompressed.data();
|
||||
|
||||
lzma_ret res = lzma_code(&stream, LZMA_FINISH);
|
||||
if (stream.avail_in) {
|
||||
LOG(("Error in decompression, %1 bytes left in _in of %2 whole.").arg(stream.avail_in).arg(compressedLen));
|
||||
return fatalFail();
|
||||
} else if (stream.avail_out) {
|
||||
LOG(("Error in decompression, %1 bytes free left in _out of %2 whole.").arg(stream.avail_out).arg(resultLen));
|
||||
return fatalFail();
|
||||
}
|
||||
lzma_end(&stream);
|
||||
if (res != LZMA_OK && res != LZMA_STREAM_END) {
|
||||
const char *msg;
|
||||
switch (res) {
|
||||
case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break;
|
||||
case LZMA_FORMAT_ERROR: msg = "The input data is not in the .xz format"; break;
|
||||
case LZMA_OPTIONS_ERROR: msg = "Unsupported compression options"; break;
|
||||
case LZMA_DATA_ERROR: msg = "Compressed file is corrupt"; break;
|
||||
case LZMA_BUF_ERROR: msg = "Compressed data is truncated or otherwise corrupt"; break;
|
||||
default: msg = "Unknown error, possibly a bug"; break;
|
||||
}
|
||||
LOG(("Error in decompression: %1 (error code %2)").arg(msg).arg(res));
|
||||
return fatalFail();
|
||||
}
|
||||
#endif
|
||||
|
||||
tempDir.mkdir(tempDir.absolutePath());
|
||||
|
||||
quint32 version;
|
||||
{
|
||||
QBuffer buffer(&uncompressed);
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
QDataStream stream(&buffer);
|
||||
stream.setVersion(QDataStream::Qt_5_1);
|
||||
|
||||
stream >> version;
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("Update Error: cant read version from downloaded stream, status: %1").arg(stream.status()));
|
||||
return fatalFail();
|
||||
}
|
||||
if (version <= AppVersion) {
|
||||
LOG(("Update Error: downloaded version %1 is not greater, than mine %2").arg(version).arg(AppVersion));
|
||||
return fatalFail();
|
||||
}
|
||||
|
||||
quint32 filesCount;
|
||||
stream >> filesCount;
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("Update Error: cant read files count from downloaded stream, status: %1").arg(stream.status()));
|
||||
return fatalFail();
|
||||
}
|
||||
if (!filesCount) {
|
||||
LOG(("Update Error: update is empty!"));
|
||||
return fatalFail();
|
||||
}
|
||||
for (uint32 i = 0; i < filesCount; ++i) {
|
||||
QString relativeName;
|
||||
quint32 fileSize;
|
||||
QByteArray fileInnerData;
|
||||
bool executable = false;
|
||||
|
||||
stream >> relativeName >> fileSize >> fileInnerData;
|
||||
#if defined Q_OS_MAC || defined Q_OS_LINUX
|
||||
stream >> executable;
|
||||
#endif
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("Update Error: cant read file from downloaded stream, status: %1").arg(stream.status()));
|
||||
return fatalFail();
|
||||
}
|
||||
if (fileSize != quint32(fileInnerData.size())) {
|
||||
LOG(("Update Error: bad file size %1 not matching data size %2").arg(fileSize).arg(fileInnerData.size()));
|
||||
return fatalFail();
|
||||
}
|
||||
|
||||
QFile f(tempDirPath + '/' + relativeName);
|
||||
if (!QDir().mkpath(QFileInfo(f).absolutePath())) {
|
||||
LOG(("Update Error: cant mkpath for file '%1'").arg(tempDirPath + '/' + relativeName));
|
||||
return fatalFail();
|
||||
}
|
||||
if (!f.open(QIODevice::WriteOnly)) {
|
||||
LOG(("Update Error: cant open file '%1' for writing").arg(tempDirPath + '/' + relativeName));
|
||||
return fatalFail();
|
||||
}
|
||||
if (f.write(fileInnerData) != fileSize) {
|
||||
f.close();
|
||||
LOG(("Update Error: cant write file '%1'").arg(tempDirPath + '/' + relativeName));
|
||||
return fatalFail();
|
||||
}
|
||||
f.close();
|
||||
if (executable) {
|
||||
QFileDevice::Permissions p = f.permissions();
|
||||
p |= QFileDevice::ExeOwner | QFileDevice::ExeUser | QFileDevice::ExeGroup | QFileDevice::ExeOther;
|
||||
f.setPermissions(p);
|
||||
}
|
||||
}
|
||||
|
||||
// create tdata/version file
|
||||
tempDir.mkdir(QDir(tempDirPath + qsl("/tdata")).absolutePath());
|
||||
std::wstring versionString = ((version % 1000) ? QString("%1.%2.%3").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000)).arg(int(version % 1000)) : QString("%1.%2").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000))).toStdWString();
|
||||
|
||||
VerInt versionNum = VerInt(version), versionLen = VerInt(versionString.size() * sizeof(VerChar));
|
||||
VerChar versionStr[32];
|
||||
memcpy(versionStr, versionString.c_str(), versionLen);
|
||||
|
||||
QFile fVersion(tempDirPath + qsl("/tdata/version"));
|
||||
if (!fVersion.open(QIODevice::WriteOnly)) {
|
||||
LOG(("Update Error: cant write version file '%1'").arg(tempDirPath + qsl("/version")));
|
||||
return fatalFail();
|
||||
}
|
||||
fVersion.write((const char*)&versionNum, sizeof(VerInt));
|
||||
fVersion.write((const char*)&versionLen, sizeof(VerInt));
|
||||
fVersion.write((const char*)&versionStr[0], versionLen);
|
||||
fVersion.close();
|
||||
}
|
||||
|
||||
if (!tempDir.rename(tempDir.absolutePath(), readyDir.absolutePath())) {
|
||||
LOG(("Update Error: cant rename temp dir '%1' to ready dir '%2'").arg(tempDir.absolutePath()).arg(readyDir.absolutePath()));
|
||||
return fatalFail();
|
||||
}
|
||||
deleteDir(tempDirPath);
|
||||
outputFile.remove();
|
||||
|
||||
emit App::app()->updateReady();
|
||||
}
|
||||
|
||||
PsUpdateDownloader::~PsUpdateDownloader() {
|
||||
delete reply;
|
||||
reply = 0;
|
||||
}
|
||||
|
||||
namespace {
|
||||
uint64 _lastUserAction = 0;
|
||||
}
|
||||
@@ -1029,73 +624,6 @@ int psFixPrevious() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool psCheckReadyUpdate() {
|
||||
QString readyPath = cWorkingDir() + qsl("tupdates/ready");
|
||||
if (!QDir(readyPath).exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check ready version
|
||||
QString versionPath = readyPath + qsl("/tdata/version");
|
||||
{
|
||||
QFile fVersion(versionPath);
|
||||
if (!fVersion.open(QIODevice::ReadOnly)) {
|
||||
LOG(("Update Error: cant read version file '%1'").arg(versionPath));
|
||||
PsUpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
VerInt versionNum;
|
||||
if (fVersion.read((char*)&versionNum, sizeof(VerInt)) != sizeof(VerInt)) {
|
||||
LOG(("Update Error: cant read version from file '%1'").arg(versionPath));
|
||||
PsUpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
fVersion.close();
|
||||
if (versionNum <= AppVersion) {
|
||||
LOG(("Update Error: cant install version %1 having version %2").arg(versionNum).arg(AppVersion));
|
||||
PsUpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
QString curUpdater = (cExeDir() + qsl("Updater.exe"));
|
||||
QFileInfo updater(cWorkingDir() + qsl("tupdates/ready/Updater.exe"));
|
||||
#elif defined Q_OS_MAC
|
||||
QString curUpdater = (cExeDir() + cExeName() + qsl("/Contents/Frameworks/Updater"));
|
||||
QFileInfo updater(cWorkingDir() + qsl("tupdates/ready/Telegram.app/Contents/Frameworks/Updater"));
|
||||
#endif
|
||||
if (!updater.exists()) {
|
||||
QFileInfo current(curUpdater);
|
||||
if (!current.exists()) {
|
||||
PsUpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
if (!QFile(current.absoluteFilePath()).copy(updater.absoluteFilePath())) {
|
||||
PsUpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#ifdef Q_OS_WIN
|
||||
if (CopyFile(updater.absoluteFilePath().toStdWString().c_str(), curUpdater.toStdWString().c_str(), FALSE) == FALSE) {
|
||||
PsUpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
if (DeleteFile(updater.absoluteFilePath().toStdWString().c_str()) == FALSE) {
|
||||
PsUpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
#elif defined Q_OS_MAC
|
||||
QDir().mkpath(QFileInfo(curUpdater).absolutePath());
|
||||
DEBUG_LOG(("Update Info: moving %1 to %2..").arg(updater.absoluteFilePath()).arg(curUpdater));
|
||||
if (!objc_moveFile(updater.absoluteFilePath(), curUpdater)) {
|
||||
PsUpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool psShowOpenWithMenu(int x, int y, const QString &file) {
|
||||
return objc_showOpenWithMenu(x, y, file);
|
||||
}
|
||||
@@ -1125,8 +653,7 @@ void psRegisterCustomScheme() {
|
||||
|
||||
void psExecUpdater() {
|
||||
if (!objc_execUpdater()) {
|
||||
QString readyPath = cWorkingDir() + qsl("tupdates/ready");
|
||||
PsUpdateDownloader::deleteDir(readyPath);
|
||||
psDeleteDir(cWorkingDir() + qsl("tupdates/temp"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -144,55 +144,9 @@ public:
|
||||
void psInstallEventFilter();
|
||||
~PsApplication();
|
||||
|
||||
signals:
|
||||
|
||||
void updateChecking();
|
||||
void updateLatest();
|
||||
void updateDownloading(qint64 ready, qint64 total);
|
||||
void updateReady();
|
||||
void updateFailed();
|
||||
|
||||
};
|
||||
|
||||
class PsUpdateDownloader : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PsUpdateDownloader(QThread *thread, const MTPDhelp_appUpdate &update);
|
||||
PsUpdateDownloader(QThread *thread, const QString &url);
|
||||
|
||||
void unpackUpdate();
|
||||
|
||||
int32 ready();
|
||||
int32 size();
|
||||
|
||||
static void deleteDir(const QString &dir);
|
||||
static void clearAll();
|
||||
|
||||
~PsUpdateDownloader();
|
||||
|
||||
public slots:
|
||||
|
||||
void start();
|
||||
void partMetaGot();
|
||||
void partFinished(qint64 got, qint64 total);
|
||||
void partFailed(QNetworkReply::NetworkError e);
|
||||
void sendRequest();
|
||||
|
||||
private:
|
||||
void initOutput();
|
||||
|
||||
void fatalFail();
|
||||
|
||||
QString updateUrl;
|
||||
QNetworkAccessManager manager;
|
||||
QNetworkReply *reply;
|
||||
int32 already, full;
|
||||
QFile outputFile;
|
||||
|
||||
QMutex mutex;
|
||||
|
||||
};
|
||||
void psDeleteDir(const QString &dir);
|
||||
|
||||
void psUserActionDone();
|
||||
bool psIdleSupported();
|
||||
@@ -222,7 +176,6 @@ void psBringToBack(QWidget *w);
|
||||
int psCleanup();
|
||||
int psFixPrevious();
|
||||
|
||||
bool psCheckReadyUpdate();
|
||||
void psExecUpdater();
|
||||
void psExecTelegram();
|
||||
|
||||
|
||||
@@ -1061,10 +1061,6 @@ void PsMainWindow::psInitSize() {
|
||||
setMinimumHeight(st::wndMinHeight);
|
||||
|
||||
TWindowPos pos(cWindowPos());
|
||||
if (cDebug()) { // temp while design
|
||||
pos.w = st::wndDefWidth;
|
||||
pos.h = st::wndDefHeight;
|
||||
}
|
||||
QRect avail(App::app() ? App::app()->desktop()->availableGeometry() : QDesktopWidget().availableGeometry());
|
||||
bool maximized = false;
|
||||
QRect geom(avail.x() + (avail.width() - st::wndDefWidth) / 2, avail.y() + (avail.height() - st::wndDefHeight) / 2, st::wndDefWidth, st::wndDefHeight);
|
||||
@@ -1389,184 +1385,7 @@ PsApplication::~PsApplication() {
|
||||
_psEventFilter = 0;
|
||||
}
|
||||
|
||||
PsUpdateDownloader::PsUpdateDownloader(QThread *thread, const MTPDhelp_appUpdate &update) : already(0), reply(0), full(0) {
|
||||
updateUrl = qs(update.vurl);
|
||||
moveToThread(thread);
|
||||
manager.moveToThread(thread);
|
||||
App::setProxySettings(manager);
|
||||
|
||||
connect(thread, SIGNAL(started()), this, SLOT(start()));
|
||||
initOutput();
|
||||
}
|
||||
|
||||
PsUpdateDownloader::PsUpdateDownloader(QThread *thread, const QString &url) : already(0), reply(0), full(0) {
|
||||
updateUrl = url;
|
||||
moveToThread(thread);
|
||||
manager.moveToThread(thread);
|
||||
App::setProxySettings(manager);
|
||||
|
||||
connect(thread, SIGNAL(started()), this, SLOT(start()));
|
||||
initOutput();
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::initOutput() {
|
||||
QString fileName;
|
||||
QRegularExpressionMatch m = QRegularExpression(qsl("/([^/\\?]+)(\\?|$)")).match(updateUrl);
|
||||
if (m.hasMatch()) {
|
||||
fileName = m.captured(1).replace(QRegularExpression(qsl("[^a-zA-Z0-9_\\-]")), QString());
|
||||
}
|
||||
if (fileName.isEmpty()) {
|
||||
fileName = qsl("tupdate-%1").arg(rand());
|
||||
}
|
||||
QString dirStr = cWorkingDir() + qsl("tupdates/");
|
||||
fileName = dirStr + fileName;
|
||||
QFileInfo file(fileName);
|
||||
|
||||
QDir dir(dirStr);
|
||||
if (dir.exists()) {
|
||||
QFileInfoList all = dir.entryInfoList(QDir::Files);
|
||||
for (QFileInfoList::iterator i = all.begin(), e = all.end(); i != e; ++i) {
|
||||
if (i->absoluteFilePath() != file.absoluteFilePath()) {
|
||||
QFile::remove(i->absoluteFilePath());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dir.mkdir(dir.absolutePath());
|
||||
}
|
||||
outputFile.setFileName(fileName);
|
||||
if (file.exists()) {
|
||||
uint64 fullSize = file.size();
|
||||
if (fullSize < INT_MAX) {
|
||||
int32 goodSize = (int32)fullSize;
|
||||
if (goodSize % UpdateChunk) {
|
||||
goodSize = goodSize - (goodSize % UpdateChunk);
|
||||
if (goodSize) {
|
||||
if (outputFile.open(QIODevice::ReadOnly)) {
|
||||
QByteArray goodData = outputFile.readAll().mid(0, goodSize);
|
||||
outputFile.close();
|
||||
if (outputFile.open(QIODevice::WriteOnly)) {
|
||||
outputFile.write(goodData);
|
||||
outputFile.close();
|
||||
|
||||
QMutexLocker lock(&mutex);
|
||||
already = goodSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QMutexLocker lock(&mutex);
|
||||
already = goodSize;
|
||||
}
|
||||
}
|
||||
if (!already) {
|
||||
QFile::remove(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::start() {
|
||||
sendRequest();
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::sendRequest() {
|
||||
QNetworkRequest req(updateUrl);
|
||||
QByteArray rangeHeaderValue = "bytes=" + QByteArray::number(already) + "-";// + QByteArray::number(already + cUpdateChunk() - 1);
|
||||
req.setRawHeader("Range", rangeHeaderValue);
|
||||
req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
|
||||
if (reply) reply->deleteLater();
|
||||
reply = manager.get(req);
|
||||
connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(partFinished(qint64,qint64)));
|
||||
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(partFailed(QNetworkReply::NetworkError)));
|
||||
connect(reply, SIGNAL(metaDataChanged()), this, SLOT(partMetaGot()));
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::partMetaGot() {
|
||||
typedef QList<QNetworkReply::RawHeaderPair> Pairs;
|
||||
Pairs pairs = reply->rawHeaderPairs();
|
||||
for (Pairs::iterator i = pairs.begin(), e = pairs.end(); i != e; ++i) {
|
||||
if (QString::fromUtf8(i->first).toLower() == "content-range") {
|
||||
QRegularExpressionMatch m = QRegularExpression(qsl("/(\\d+)([^\\d]|$)")).match(QString::fromUtf8(i->second));
|
||||
if (m.hasMatch()) {
|
||||
{
|
||||
QMutexLocker lock(&mutex);
|
||||
full = m.captured(1).toInt();
|
||||
}
|
||||
emit App::app()->updateDownloading(already, full);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32 PsUpdateDownloader::ready() {
|
||||
QMutexLocker lock(&mutex);
|
||||
return already;
|
||||
}
|
||||
|
||||
int32 PsUpdateDownloader::size() {
|
||||
QMutexLocker lock(&mutex);
|
||||
return full;
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::partFinished(qint64 got, qint64 total) {
|
||||
if (!reply) return;
|
||||
|
||||
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||
if (statusCode.isValid()) {
|
||||
int status = statusCode.toInt();
|
||||
if (status != 200 && status != 206 && status != 416) {
|
||||
LOG(("Update Error: Bad HTTP status received in partFinished(): %1").arg(status));
|
||||
return fatalFail();
|
||||
}
|
||||
}
|
||||
|
||||
if (!already && !full) {
|
||||
QMutexLocker lock(&mutex);
|
||||
full = total;
|
||||
}
|
||||
DEBUG_LOG(("Update Info: part %1 of %2").arg(got).arg(total));
|
||||
|
||||
if (!outputFile.isOpen()) {
|
||||
if (!outputFile.open(QIODevice::Append)) {
|
||||
LOG(("Update Error: Could not open output file '%1' for appending").arg(outputFile.fileName()));
|
||||
return fatalFail();
|
||||
}
|
||||
}
|
||||
QByteArray r = reply->readAll();
|
||||
if (!r.isEmpty()) {
|
||||
outputFile.write(r);
|
||||
|
||||
QMutexLocker lock(&mutex);
|
||||
already += r.size();
|
||||
}
|
||||
if (got >= total) {
|
||||
reply->deleteLater();
|
||||
reply = 0;
|
||||
outputFile.close();
|
||||
unpackUpdate();
|
||||
} else {
|
||||
emit App::app()->updateDownloading(already, full);
|
||||
}
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::partFailed(QNetworkReply::NetworkError e) {
|
||||
if (!reply) return;
|
||||
|
||||
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||
reply->deleteLater();
|
||||
reply = 0;
|
||||
if (statusCode.isValid()) {
|
||||
int status = statusCode.toInt();
|
||||
if (status == 416) { // Requested range not satisfiable
|
||||
outputFile.close();
|
||||
unpackUpdate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
LOG(("Update Error: failed to download part starting from %1, error %2").arg(already).arg(e));
|
||||
emit App::app()->updateFailed();
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::deleteDir(const QString &dir) {
|
||||
void psDeleteDir(const QString &dir) {
|
||||
std::wstring wDir = QDir::toNativeSeparators(dir).toStdWString();
|
||||
WCHAR path[4096];
|
||||
memcpy(path, wDir.c_str(), (wDir.size() + 1) * sizeof(WCHAR));
|
||||
@@ -1586,185 +1405,6 @@ void PsUpdateDownloader::deleteDir(const QString &dir) {
|
||||
int res = SHFileOperation(&file_op);
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::fatalFail() {
|
||||
clearAll();
|
||||
emit App::app()->updateFailed();
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::clearAll() {
|
||||
deleteDir(cWorkingDir() + qsl("tupdates"));
|
||||
}
|
||||
|
||||
QString winapiErrorWrap() {
|
||||
WCHAR errMsg[2048];
|
||||
DWORD errorCode = GetLastError();
|
||||
LPTSTR errorText = NULL, errorTextDefault = L"(Unknown error)";
|
||||
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&errorText, 0, 0);
|
||||
if (!errorText) {
|
||||
errorText = errorTextDefault;
|
||||
}
|
||||
StringCbPrintf(errMsg, sizeof(errMsg), L"Error code: %d, error message: %s", errorCode, errorText);
|
||||
if (errorText != errorTextDefault) {
|
||||
LocalFree(errorText);
|
||||
}
|
||||
return QString::fromWCharArray(errMsg);
|
||||
}
|
||||
|
||||
void PsUpdateDownloader::unpackUpdate() {
|
||||
QByteArray packed;
|
||||
if (!outputFile.open(QIODevice::ReadOnly)) {
|
||||
LOG(("Update Error: cant read updates file!"));
|
||||
return fatalFail();
|
||||
}
|
||||
|
||||
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header
|
||||
|
||||
QByteArray compressed = outputFile.readAll();
|
||||
int32 compressedLen = compressed.size() - hSize;
|
||||
if (compressedLen <= 0) {
|
||||
LOG(("Update Error: bad compressed size: %1").arg(compressed.size()));
|
||||
return fatalFail();
|
||||
}
|
||||
outputFile.close();
|
||||
|
||||
QString tempDirPath = cWorkingDir() + qsl("tupdates/temp"), readyDirPath = cWorkingDir() + qsl("tupdates/ready");
|
||||
deleteDir(tempDirPath);
|
||||
deleteDir(readyDirPath);
|
||||
|
||||
{
|
||||
QDir tempDir(tempDirPath), readyDir(readyDirPath);
|
||||
if (tempDir.exists() || readyDir.exists()) {
|
||||
LOG(("Update Error: cant clear tupdates/temp or tupdates/ready dir!"));
|
||||
return fatalFail();
|
||||
}
|
||||
|
||||
tempDirPath = tempDir.absolutePath();
|
||||
readyDirPath = readyDir.absolutePath();
|
||||
|
||||
uchar sha1Buffer[20];
|
||||
bool goodSha1 = !memcmp(compressed.constData() + hSigLen, hashSha1(compressed.constData() + hSigLen + hShaLen, compressedLen + hPropsLen + hOriginalSizeLen, sha1Buffer), hShaLen);
|
||||
if (!goodSha1) {
|
||||
LOG(("Update Error: bad SHA1 hash of update file!"));
|
||||
return fatalFail();
|
||||
}
|
||||
|
||||
RSA *pbKey = PEM_read_bio_RSAPublicKey(BIO_new_mem_buf(const_cast<char*>(DevChannel ? UpdatesPublicDevKey : UpdatesPublicKey), -1), 0, 0, 0);
|
||||
if (!pbKey) {
|
||||
LOG(("Update Error: cant read public rsa key!"));
|
||||
return fatalFail();
|
||||
}
|
||||
if (RSA_verify(NID_sha1, (const uchar*)(compressed.constData() + hSigLen), hShaLen, (const uchar*)(compressed.constData()), hSigLen, pbKey) != 1) { // verify signature
|
||||
RSA_free(pbKey);
|
||||
LOG(("Update Error: bad RSA signature of update file!"));
|
||||
return fatalFail();
|
||||
}
|
||||
RSA_free(pbKey);
|
||||
|
||||
QByteArray uncompressed;
|
||||
|
||||
int32 uncompressedLen;
|
||||
memcpy(&uncompressedLen, compressed.constData() + hSigLen + hShaLen + hPropsLen, hOriginalSizeLen);
|
||||
uncompressed.resize(uncompressedLen);
|
||||
|
||||
size_t resultLen = uncompressed.size();
|
||||
SizeT srcLen = compressedLen;
|
||||
int uncompressRes = LzmaUncompress((uchar*)uncompressed.data(), &resultLen, (const uchar*)(compressed.constData() + hSize), &srcLen, (const uchar*)(compressed.constData() + hSigLen + hShaLen), LZMA_PROPS_SIZE);
|
||||
if (uncompressRes != SZ_OK) {
|
||||
LOG(("Update Error: could not uncompress lzma, code: %1").arg(uncompressRes));
|
||||
return fatalFail();
|
||||
}
|
||||
|
||||
QDir().mkdir(tempDirPath);
|
||||
|
||||
quint32 version;
|
||||
|
||||
QBuffer buffer(&uncompressed);
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
QDataStream stream(&buffer);
|
||||
stream.setVersion(QDataStream::Qt_5_1);
|
||||
|
||||
stream >> version;
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("Update Error: cant read version from downloaded stream, status: %1").arg(stream.status()));
|
||||
return fatalFail();
|
||||
}
|
||||
if (version <= AppVersion) {
|
||||
LOG(("Update Error: downloaded version %1 is not greater, than mine %2").arg(version).arg(AppVersion));
|
||||
return fatalFail();
|
||||
}
|
||||
|
||||
quint32 filesCount;
|
||||
stream >> filesCount;
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("Update Error: cant read files count from downloaded stream, status: %1").arg(stream.status()));
|
||||
return fatalFail();
|
||||
}
|
||||
if (!filesCount) {
|
||||
LOG(("Update Error: update is empty!"));
|
||||
return fatalFail();
|
||||
}
|
||||
for (int32 i = 0; i < filesCount; ++i) {
|
||||
QString relativeName;
|
||||
quint32 fileSize;
|
||||
QByteArray fileInnerData;
|
||||
|
||||
stream >> relativeName >> fileSize >> fileInnerData;
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("Update Error: cant read file from downloaded stream, status: %1").arg(stream.status()));
|
||||
return fatalFail();
|
||||
}
|
||||
if (fileSize != fileInnerData.size()) {
|
||||
LOG(("Update Error: bad file size %1 not matching data size %2").arg(fileSize).arg(fileInnerData.size()));
|
||||
return fatalFail();
|
||||
}
|
||||
|
||||
QFile f(tempDirPath + '/' + relativeName);
|
||||
if (!f.open(QIODevice::WriteOnly)) {
|
||||
LOG(("Update Error: cant open file '%1' for writing").arg(tempDirPath + '/' + relativeName));
|
||||
return fatalFail();
|
||||
}
|
||||
if (f.write(fileInnerData) != fileSize) {
|
||||
f.close();
|
||||
LOG(("Update Error: cant write file '%1'").arg(tempDirPath + '/' + relativeName));
|
||||
return fatalFail();
|
||||
}
|
||||
f.close();
|
||||
}
|
||||
|
||||
// create tdata/version file
|
||||
QDir().mkdir(tempDirPath + qsl("/tdata"));
|
||||
std::wstring versionString = ((version % 1000) ? QString("%1.%2.%3").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000)).arg(int(version % 1000)) : QString("%1.%2").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000))).toStdWString();
|
||||
DWORD versionNum = DWORD(version), versionLen = DWORD(versionString.size() * sizeof(WCHAR));
|
||||
WCHAR versionStr[32];
|
||||
memcpy(versionStr, versionString.c_str(), versionLen);
|
||||
|
||||
QFile fVersion(tempDirPath + qsl("/tdata/version"));
|
||||
if (!fVersion.open(QIODevice::WriteOnly)) {
|
||||
LOG(("Update Error: cant write version file '%1'").arg(tempDirPath + qsl("/tdata/version")));
|
||||
return fatalFail();
|
||||
}
|
||||
fVersion.write((const char*)&versionNum, sizeof(DWORD));
|
||||
fVersion.write((const char*)&versionLen, sizeof(DWORD));
|
||||
fVersion.write((const char*)&versionStr[0], versionLen);
|
||||
fVersion.close();
|
||||
}
|
||||
|
||||
std::wstring tempDirNative = QDir::toNativeSeparators(tempDirPath).toStdWString(), readyDirNative = QDir::toNativeSeparators(readyDirPath).toStdWString();
|
||||
if (!MoveFile(tempDirNative.c_str(), readyDirNative.c_str())) {
|
||||
LOG(("Update Error: cant rename temp dir '%1' to ready dir '%2'. %3").arg(QString::fromStdWString(tempDirNative)).arg(QString::fromStdWString(readyDirNative)).arg(winapiErrorWrap()));
|
||||
return fatalFail();
|
||||
}
|
||||
deleteDir(tempDirPath);
|
||||
outputFile.remove();
|
||||
|
||||
emit App::app()->updateReady();
|
||||
}
|
||||
|
||||
PsUpdateDownloader::~PsUpdateDownloader() {
|
||||
delete reply;
|
||||
reply = 0;
|
||||
}
|
||||
|
||||
namespace {
|
||||
BOOL CALLBACK _ActivateProcess(HWND hWnd, LPARAM lParam) {
|
||||
uint64 &processId(*(uint64*)lParam);
|
||||
@@ -2138,65 +1778,6 @@ int psFixPrevious() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool psCheckReadyUpdate() {
|
||||
QString readyPath = cWorkingDir() + qsl("tupdates/ready");
|
||||
if (!QDir(readyPath).exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check ready version
|
||||
QString versionPath = readyPath + qsl("/tdata/version");
|
||||
{
|
||||
QFile fVersion(versionPath);
|
||||
if (!fVersion.open(QIODevice::ReadOnly)) {
|
||||
LOG(("Update Error: cant read version file '%1'").arg(versionPath));
|
||||
PsUpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
DWORD versionNum;
|
||||
if (fVersion.read((char*)&versionNum, sizeof(DWORD)) != sizeof(DWORD)) {
|
||||
LOG(("Update Error: cant read version from file '%1'").arg(versionPath));
|
||||
PsUpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
fVersion.close();
|
||||
if (versionNum <= AppVersion) {
|
||||
LOG(("Update Error: cant install version %1 having version %2").arg(versionNum).arg(AppVersion));
|
||||
PsUpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QString curUpdater = (cExeDir() + qsl("Updater.exe"));
|
||||
QFileInfo updater(cWorkingDir() + qsl("tupdates/ready/Updater.exe"));
|
||||
if (!updater.exists()) {
|
||||
QFileInfo current(curUpdater);
|
||||
if (!current.exists()) {
|
||||
PsUpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
if (CopyFile(current.absoluteFilePath().toStdWString().c_str(), updater.absoluteFilePath().toStdWString().c_str(), TRUE) == FALSE) {
|
||||
PsUpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (CopyFile(updater.absoluteFilePath().toStdWString().c_str(), curUpdater.toStdWString().c_str(), FALSE) == FALSE) {
|
||||
DWORD errorCode = GetLastError();
|
||||
if (errorCode == ERROR_ACCESS_DENIED) { // we are in write-protected dir, like Program Files
|
||||
cSetWriteProtected(true);
|
||||
return true;
|
||||
} else {
|
||||
PsUpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (DeleteFile(updater.absoluteFilePath().toStdWString().c_str()) == FALSE) {
|
||||
PsUpdateDownloader::clearAll();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void psPostprocessFile(const QString &name) {
|
||||
std::wstring zoneFile = QDir::toNativeSeparators(name).toStdWString() + L":Zone.Identifier";
|
||||
HANDLE f = CreateFile(zoneFile.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
|
||||
@@ -2478,7 +2059,7 @@ void psExecUpdater() {
|
||||
if (cStartInTray()) targs += qsl(" -startintray");
|
||||
if (cWriteProtected()) targs += qsl(" -writeprotected \"") + cExeDir() + '"';
|
||||
|
||||
QString updaterPath = cWriteProtected() ? (cWorkingDir() + qsl("tupdates/ready/Updater.exe")) : (cExeDir() + qsl("Updater.exe"));
|
||||
QString updaterPath = cWriteProtected() ? (cWorkingDir() + qsl("tupdates/temp/Updater.exe")) : (cExeDir() + qsl("Updater.exe"));
|
||||
|
||||
QString updater(QDir::toNativeSeparators(updaterPath)), wdir(QDir::toNativeSeparators(cWorkingDir()));
|
||||
|
||||
@@ -2486,8 +2067,7 @@ void psExecUpdater() {
|
||||
HINSTANCE r = ShellExecute(0, cWriteProtected() ? L"runas" : 0, updater.toStdWString().c_str(), targs.toStdWString().c_str(), wdir.isEmpty() ? 0 : wdir.toStdWString().c_str(), SW_SHOWNORMAL);
|
||||
if (long(r) < 32) {
|
||||
DEBUG_LOG(("Application Error: failed to execute %1, working directory: '%2', result: %3").arg(updater).arg(wdir).arg(long(r)));
|
||||
QString readyPath = cWorkingDir() + qsl("tupdates/ready");
|
||||
PsUpdateDownloader::deleteDir(readyPath);
|
||||
psDeleteDir(cWorkingDir() + qsl("tupdates/temp"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -117,55 +117,9 @@ public:
|
||||
void psInstallEventFilter();
|
||||
~PsApplication();
|
||||
|
||||
signals:
|
||||
|
||||
void updateChecking();
|
||||
void updateLatest();
|
||||
void updateDownloading(qint64 ready, qint64 total);
|
||||
void updateReady();
|
||||
void updateFailed();
|
||||
|
||||
};
|
||||
|
||||
class PsUpdateDownloader : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PsUpdateDownloader(QThread *thread, const MTPDhelp_appUpdate &update);
|
||||
PsUpdateDownloader(QThread *thread, const QString &url);
|
||||
|
||||
void unpackUpdate();
|
||||
|
||||
int32 ready();
|
||||
int32 size();
|
||||
|
||||
static void deleteDir(const QString &dir);
|
||||
static void clearAll();
|
||||
|
||||
~PsUpdateDownloader();
|
||||
|
||||
public slots:
|
||||
|
||||
void start();
|
||||
void partMetaGot();
|
||||
void partFinished(qint64 got, qint64 total);
|
||||
void partFailed(QNetworkReply::NetworkError e);
|
||||
void sendRequest();
|
||||
|
||||
private:
|
||||
void initOutput();
|
||||
|
||||
void fatalFail();
|
||||
|
||||
QString updateUrl;
|
||||
QNetworkAccessManager manager;
|
||||
QNetworkReply *reply;
|
||||
int32 already, full;
|
||||
QFile outputFile;
|
||||
|
||||
QMutex mutex;
|
||||
|
||||
};
|
||||
void psDeleteDir(const QString &dir);
|
||||
|
||||
void psUserActionDone();
|
||||
bool psIdleSupported();
|
||||
@@ -196,7 +150,6 @@ void psBringToBack(QWidget *w);
|
||||
int psCleanup();
|
||||
int psFixPrevious();
|
||||
|
||||
bool psCheckReadyUpdate();
|
||||
void psExecUpdater();
|
||||
void psExecTelegram();
|
||||
|
||||
|
||||
@@ -84,6 +84,9 @@ QString gTimeFormat = qsl("hh:mm");
|
||||
int32 gAutoLock = 3600;
|
||||
bool gHasPasscode = false;
|
||||
|
||||
bool gHasAudioPlayer = true;
|
||||
bool gHasAudioCapture = true;
|
||||
|
||||
DBIEmojiTab gEmojiTab = dbietRecent;
|
||||
RecentEmojiPack gRecentEmojis;
|
||||
RecentEmojisPreload gRecentEmojisPreload;
|
||||
@@ -136,6 +139,7 @@ QUrl gUpdateURL = QUrl(qsl("http://tdesktop.com/linux/tupdates/current"));
|
||||
#endif
|
||||
|
||||
bool gContactsReceived = false;
|
||||
bool gDialogsReceived = false;
|
||||
|
||||
bool gWideMode = true;
|
||||
|
||||
|
||||
@@ -53,11 +53,11 @@ inline bool rtl() {
|
||||
}
|
||||
|
||||
struct mtpDcOption {
|
||||
mtpDcOption(int _id, const string &_host, const string &_ip, int _port) : id(_id), host(_host), ip(_ip), port(_port) {
|
||||
mtpDcOption(int id, int flags, const string &ip, int port) : id(id), flags(flags), ip(ip), port(port) {
|
||||
}
|
||||
|
||||
int id;
|
||||
string host;
|
||||
int flags;
|
||||
string ip;
|
||||
int port;
|
||||
};
|
||||
@@ -134,6 +134,9 @@ DeclareSetting(QString, TimeFormat);
|
||||
DeclareSetting(int32, AutoLock);
|
||||
DeclareSetting(bool, HasPasscode);
|
||||
|
||||
DeclareSetting(bool, HasAudioPlayer);
|
||||
DeclareSetting(bool, HasAudioCapture);
|
||||
|
||||
inline void cChangeTimeFormat(const QString &newFormat) {
|
||||
if (!newFormat.isEmpty()) cSetTimeFormat(newFormat);
|
||||
}
|
||||
@@ -287,6 +290,7 @@ DeclareReadSetting(DBIPlatform, Platform);
|
||||
DeclareReadSetting(QUrl, UpdateURL);
|
||||
|
||||
DeclareSetting(bool, ContactsReceived);
|
||||
DeclareSetting(bool, DialogsReceived);
|
||||
|
||||
DeclareSetting(bool, WideMode);
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
#include "langloaderplain.h"
|
||||
#include "gui/filedialog.h"
|
||||
|
||||
#include "autoupdater.h"
|
||||
|
||||
#include "localstorage.h"
|
||||
|
||||
Slider::Slider(QWidget *parent, const style::slider &st, int32 count, int32 sel) : QWidget(parent),
|
||||
@@ -1172,7 +1174,7 @@ void SettingsInner::onCheckNow() {
|
||||
}
|
||||
|
||||
void SettingsInner::onRestartNow() {
|
||||
psCheckReadyUpdate();
|
||||
checkReadyUpdate();
|
||||
if (_updatingState == UpdatingReady) {
|
||||
cSetRestartingUpdate(true);
|
||||
} else {
|
||||
|
||||
@@ -22,7 +22,6 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
|
||||
Q_IMPORT_PLUGIN(QWebpPlugin)
|
||||
#elif defined Q_OS_MAC
|
||||
Q_IMPORT_PLUGIN(QCoreWlanEnginePlugin)
|
||||
Q_IMPORT_PLUGIN(QGenericEnginePlugin)
|
||||
Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin)
|
||||
Q_IMPORT_PLUGIN(QDDSPlugin)
|
||||
|
||||
@@ -201,10 +201,73 @@ void UserData::setPhone(const QString &newPhone) {
|
||||
++nameVersion;
|
||||
}
|
||||
|
||||
void UserData::setBotInfoVersion(int32 version) {
|
||||
if (version < 0) {
|
||||
delete botInfo;
|
||||
botInfo = 0;
|
||||
} else if (!botInfo) {
|
||||
botInfo = new BotInfo();
|
||||
botInfo->version = version;
|
||||
} else if (botInfo->version < version) {
|
||||
botInfo->commands.clear();
|
||||
botInfo->description.clear();
|
||||
botInfo->shareText.clear();
|
||||
botInfo->version = version;
|
||||
botInfo->inited = false;
|
||||
}
|
||||
}
|
||||
void UserData::setBotInfo(const MTPBotInfo &info) {
|
||||
switch (info.type()) {
|
||||
case mtpc_botInfoEmpty:
|
||||
delete botInfo;
|
||||
botInfo = 0;
|
||||
break;
|
||||
case mtpc_botInfo: {
|
||||
const MTPDbotInfo &d(info.c_botInfo());
|
||||
if (App::peerFromUser(d.vuser_id.v) != id) return;
|
||||
|
||||
if (botInfo) {
|
||||
botInfo->version = d.vversion.v;
|
||||
} else {
|
||||
setBotInfoVersion(d.vversion.v);
|
||||
}
|
||||
|
||||
QString desc = qs(d.vdescription);
|
||||
if (botInfo->description != desc) {
|
||||
botInfo->description = desc;
|
||||
botInfo->text = Text(st::msgMinWidth);
|
||||
}
|
||||
botInfo->shareText = qs(d.vshare_text);
|
||||
|
||||
const QVector<MTPBotCommand> &v(d.vcommands.c_vector().v);
|
||||
botInfo->commands.clear();
|
||||
botInfo->commands.reserve(v.size());
|
||||
for (int32 i = 0, l = v.size(); i < l; ++i) {
|
||||
if (v.at(i).type() == mtpc_botCommand) {
|
||||
botInfo->commands.push_back(BotCommand(qs(v.at(i).c_botCommand().vcommand), qs(v.at(i).c_botCommand().vparams), qs(v.at(i).c_botCommand().vdescription)));
|
||||
}
|
||||
}
|
||||
|
||||
botInfo->inited = true;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void UserData::nameUpdated() {
|
||||
nameText.setText(st::msgNameFont, name, _textNameOptions);
|
||||
}
|
||||
|
||||
void UserData::madeAction() {
|
||||
int32 t = unixtime();
|
||||
if (onlineTill <= 0 && -onlineTill < t) {
|
||||
onlineTill = -t - SetOnlineAfterActivity;
|
||||
if (App::main()) App::main()->peerUpdated(this);
|
||||
} else if (onlineTill > 0 && onlineTill < t + 1) {
|
||||
onlineTill = t + SetOnlineAfterActivity;
|
||||
if (App::main()) App::main()->peerUpdated(this);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatData::setPhoto(const MTPChatPhoto &p, const PhotoId &phId) {
|
||||
switch (p.type()) {
|
||||
case mtpc_chatPhoto: {
|
||||
@@ -303,7 +366,7 @@ void VideoOpenLink::onClick(Qt::MouseButton button) const {
|
||||
QString filename = saveFileName(lang(lng_save_video), qsl("MOV Video (*.mov);;All files (*.*)"), qsl("video"), qsl(".mov"), false);
|
||||
if (!filename.isEmpty()) {
|
||||
data->openOnSave = 1;
|
||||
data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0;
|
||||
data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : (App::contextItem() ? App::contextItem()->id : 0);
|
||||
data->save(filename);
|
||||
}
|
||||
}
|
||||
@@ -369,19 +432,17 @@ void AudioOpenLink::onClick(Qt::MouseButton button) const {
|
||||
AudioData *data = audio();
|
||||
if ((!data->user && !data->date) || button != Qt::LeftButton) return;
|
||||
|
||||
bool mp3 = (data->mime == QLatin1String("audio/mp3"));
|
||||
|
||||
QString already = data->already(true);
|
||||
bool play = !mp3 && audioVoice();
|
||||
bool play = audioPlayer();
|
||||
if (!already.isEmpty() || (!data->data.isEmpty() && play)) {
|
||||
if (play) {
|
||||
AudioData *playing = 0;
|
||||
VoiceMessageState playingState = VoiceMessageStopped;
|
||||
audioVoice()->currentState(&playing, &playingState);
|
||||
if (playing == data && playingState != VoiceMessageStopped) {
|
||||
audioVoice()->pauseresume();
|
||||
AudioPlayerState playingState = AudioPlayerStopped;
|
||||
audioPlayer()->currentState(&playing, &playingState);
|
||||
if (playing == data && playingState != AudioPlayerStopped) {
|
||||
audioPlayer()->pauseresume();
|
||||
} else {
|
||||
audioVoice()->play(data);
|
||||
audioPlayer()->play(data);
|
||||
if (App::main()) App::main()->audioMarkRead(data);
|
||||
}
|
||||
} else {
|
||||
@@ -393,10 +454,11 @@ void AudioOpenLink::onClick(Qt::MouseButton button) const {
|
||||
|
||||
if (data->status != FileReady) return;
|
||||
|
||||
bool mp3 = (data->mime == QLatin1String("audio/mp3"));
|
||||
QString filename = saveFileName(lang(lng_save_audio), mp3 ? qsl("MP3 Audio (*.mp3);;All files (*.*)") : qsl("OGG Opus Audio (*.ogg);;All files (*.*)"), qsl("audio"), mp3 ? qsl(".mp3") : qsl(".ogg"), false);
|
||||
if (!filename.isEmpty()) {
|
||||
data->openOnSave = 1;
|
||||
data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0;
|
||||
data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : (App::contextItem() ? App::contextItem()->id : 0);
|
||||
data->save(filename);
|
||||
}
|
||||
}
|
||||
@@ -421,7 +483,7 @@ void AudioSaveLink::doSave(AudioData *data, bool forceSavingAs) {
|
||||
data->cancel();
|
||||
} else if (!already.isEmpty()) {
|
||||
data->openOnSave = -1;
|
||||
data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0;
|
||||
data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : (App::contextItem() ? App::contextItem()->id : 0);
|
||||
}
|
||||
data->save(filename);
|
||||
}
|
||||
@@ -470,8 +532,10 @@ void DocumentOpenLink::onClick(Qt::MouseButton button) const {
|
||||
if (reader.canRead()) {
|
||||
if (reader.supportsAnimation() && reader.imageCount() > 1 && App::hoveredLinkItem()) {
|
||||
startGif(App::hoveredLinkItem(), already);
|
||||
} else if (App::hoveredLinkItem() || App::contextItem()) {
|
||||
App::wnd()->showDocument(data, App::hoveredLinkItem() ? App::hoveredLinkItem() : App::contextItem());
|
||||
} else {
|
||||
App::wnd()->showDocument(data, App::hoveredLinkItem());
|
||||
psOpenFile(already);
|
||||
}
|
||||
} else {
|
||||
psOpenFile(already);
|
||||
@@ -501,7 +565,7 @@ void DocumentOpenLink::onClick(Qt::MouseButton button) const {
|
||||
QString filename = saveFileName(lang(lng_save_file), filter, qsl("doc"), name, false);
|
||||
if (!filename.isEmpty()) {
|
||||
data->openOnSave = 1;
|
||||
data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0;
|
||||
data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : (App::contextItem() ? App::contextItem()->id : 0);
|
||||
data->save(filename);
|
||||
}
|
||||
}
|
||||
@@ -538,7 +602,7 @@ void DocumentSaveLink::doSave(DocumentData *data, bool forceSavingAs) {
|
||||
data->cancel();
|
||||
} else if (!already.isEmpty()) {
|
||||
data->openOnSave = -1;
|
||||
data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0;
|
||||
data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : (App::contextItem() ? App::contextItem()->id : 0);
|
||||
}
|
||||
data->save(filename);
|
||||
}
|
||||
|
||||
@@ -118,15 +118,37 @@ private:
|
||||
PeerData *_peer;
|
||||
};
|
||||
|
||||
struct BotCommand {
|
||||
BotCommand(const QString &command, const QString ¶ms, const QString &description) : command(command), params(params), description(description) {
|
||||
}
|
||||
QString command, params, description;
|
||||
};
|
||||
struct BotInfo {
|
||||
BotInfo() : inited(false), readsAllHistory(false), cantJoinGroups(false), version(0), text(st::msgMinWidth) {
|
||||
}
|
||||
bool inited;
|
||||
bool readsAllHistory, cantJoinGroups;
|
||||
int32 version;
|
||||
QString shareText, description;
|
||||
QList<BotCommand> commands;
|
||||
Text text; // description
|
||||
|
||||
QString startToken, startGroupToken;
|
||||
};
|
||||
|
||||
struct PhotoData;
|
||||
struct UserData : public PeerData {
|
||||
UserData(const PeerId &id) : PeerData(id), lnk(new PeerLink(this)), onlineTill(0), contact(-1), photosCount(-1) {
|
||||
UserData(const PeerId &id) : PeerData(id), photoId(0), lnk(new PeerLink(this)), onlineTill(0), contact(-1), photosCount(-1), botInfo(0) {
|
||||
}
|
||||
void setPhoto(const MTPUserProfilePhoto &photo);
|
||||
void setName(const QString &first, const QString &last, const QString &phoneName, const QString &username);
|
||||
void setPhone(const QString &newPhone);
|
||||
void setBotInfoVersion(int32 version);
|
||||
void setBotInfo(const MTPBotInfo &info);
|
||||
void nameUpdated();
|
||||
|
||||
void madeAction(); // pseudo-online
|
||||
|
||||
QString firstName;
|
||||
QString lastName;
|
||||
QString username;
|
||||
@@ -140,10 +162,12 @@ struct UserData : public PeerData {
|
||||
typedef QList<PhotoData*> Photos;
|
||||
Photos photos;
|
||||
int32 photosCount; // -1 not loaded, 0 all loaded
|
||||
|
||||
BotInfo *botInfo;
|
||||
};
|
||||
|
||||
struct ChatData : public PeerData {
|
||||
ChatData(const PeerId &id) : PeerData(id), count(0), date(0), version(0), left(false), forbidden(true), photoId(0) {
|
||||
ChatData(const PeerId &id) : PeerData(id), count(0), date(0), version(0), left(false), forbidden(true), botStatus(0), photoId(0) {
|
||||
}
|
||||
void setPhoto(const MTPChatPhoto &photo, const PhotoId &phId = 0);
|
||||
int32 count;
|
||||
@@ -158,12 +182,19 @@ struct ChatData : public PeerData {
|
||||
CanKick cankick;
|
||||
typedef QList<UserData*> LastAuthors;
|
||||
LastAuthors lastAuthors;
|
||||
typedef QMap<UserData*, bool> MarkupSenders;
|
||||
MarkupSenders markupSenders;
|
||||
int32 botStatus; // -1 - no bots, 0 - unknown, 1 - one bot, that sees all history, 2 - other
|
||||
ImagePtr photoFull;
|
||||
PhotoId photoId;
|
||||
QString invitationUrl;
|
||||
// geo
|
||||
};
|
||||
|
||||
inline int32 newMessageFlags(PeerData *p) {
|
||||
return (p->input.type() == mtpc_inputPeerSelf) ? 0 : (((p->chat || !p->asUser()->botInfo) ? MTPDmessage_flag_unread : 0) | MTPDmessage_flag_out);
|
||||
}
|
||||
|
||||
typedef QMap<char, QPixmap> PreparedPhotoThumbs;
|
||||
struct PhotoData {
|
||||
PhotoData(const PhotoId &id, const uint64 &access = 0, int32 user = 0, int32 date = 0, const ImagePtr &thumb = ImagePtr(), const ImagePtr &medium = ImagePtr(), const ImagePtr &full = ImagePtr()) :
|
||||
|
||||
@@ -23,6 +23,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
||||
#include "passcodewidget.h"
|
||||
#include "window.h"
|
||||
#include "application.h"
|
||||
#include "autoupdater.h"
|
||||
|
||||
SysBtn::SysBtn(QWidget *parent, const style::sysButton &st, const QString &text) : Button(parent),
|
||||
_st(st), a_color(_st.color->c), _overLevel(0), _text(text) {
|
||||
@@ -142,7 +143,7 @@ UpdateBtn::UpdateBtn(QWidget *parent, Window *window, const QString &text) : Sys
|
||||
}
|
||||
|
||||
void UpdateBtn::onClick() {
|
||||
psCheckReadyUpdate();
|
||||
checkReadyUpdate();
|
||||
if (App::app()->updatingState() == Application::UpdatingReady) {
|
||||
cSetRestartingUpdate(true);
|
||||
} else {
|
||||
|
||||
@@ -233,7 +233,7 @@ QString rusKeyboardLayoutSwitch(const QString &from);
|
||||
enum DataBlockId {
|
||||
dbiKey = 0x00,
|
||||
dbiUser = 0x01,
|
||||
dbiDcOption = 0x02,
|
||||
dbiDcOptionOld = 0x02,
|
||||
dbiMaxGroupCount = 0x03,
|
||||
dbiMutePeer = 0x04,
|
||||
dbiSendKey = 0x05,
|
||||
@@ -270,6 +270,7 @@ enum DataBlockId {
|
||||
dbiRecentEmojis = 0x24,
|
||||
dbiEmojiVariants = 0x25,
|
||||
dbiRecentStickers = 0x26,
|
||||
dbiDcOption = 0x27,
|
||||
|
||||
dbiEncryptedWithSalt = 333,
|
||||
dbiEncrypted = 444,
|
||||
@@ -326,6 +327,8 @@ enum DBIScale {
|
||||
dbisScaleCount = 5,
|
||||
};
|
||||
|
||||
static const int MatrixRowShift = 40000;
|
||||
|
||||
enum DBIEmojiTab {
|
||||
dbietRecent = -1,
|
||||
dbietPeople = 0,
|
||||
@@ -338,7 +341,6 @@ enum DBIEmojiTab {
|
||||
dbietStickers = 666,
|
||||
};
|
||||
static const int emojiTabCount = 8;
|
||||
static const int emojiTabShift = 100000;
|
||||
inline DBIEmojiTab emojiTabAtIndex(int index) {
|
||||
return (index < 0 || index >= emojiTabCount) ? dbietRecent : DBIEmojiTab(index - 1);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ void ConnectingWidget::set(const QString &text, const QString &reconnect) {
|
||||
void ConnectingWidget::paintEvent(QPaintEvent *e) {
|
||||
QPainter p(this);
|
||||
|
||||
_shadow.paint(p, QRect(0, st::boxShadow.pxHeight(), width() - st::boxShadow.pxWidth(), height() - st::boxShadow.pxHeight()), QPoint(0, 0), BoxShadow::Top | BoxShadow::Right);
|
||||
_shadow.paint(p, QRect(0, st::boxShadow.pxHeight(), width() - st::boxShadow.pxWidth(), height() - st::boxShadow.pxHeight()), 0, BoxShadow::Top | BoxShadow::Right);
|
||||
p.fillRect(0, st::boxShadow.pxHeight(), width() - st::boxShadow.pxWidth(), height() - st::boxShadow.pxHeight(), st::connectingBG->b);
|
||||
p.setFont(st::linkFont->f);
|
||||
p.setPen(st::connectingColor->p);
|
||||
@@ -557,6 +557,7 @@ void Window::checkAutoLock() {
|
||||
|
||||
void Window::setupIntro(bool anim) {
|
||||
cSetContactsReceived(false);
|
||||
cSetDialogsReceived(false);
|
||||
if (intro && (intro->animating() || intro->isVisible()) && !main) return;
|
||||
|
||||
QPixmap bg = anim ? myGrab(this, QRect(0, st::titleHeight, width(), height() - st::titleHeight)) : QPixmap();
|
||||
@@ -606,7 +607,8 @@ void Window::sendServiceHistoryRequest() {
|
||||
|
||||
UserData *user = App::userLoaded(ServiceUserId);
|
||||
if (!user) {
|
||||
user = App::feedUsers(MTP_vector<MTPUser>(1, MTP_userRequest(MTP_int(ServiceUserId), MTP_string("Telegram"), MTP_string(""), MTP_string(""), MTP_long(-1), MTP_string("42777"), MTP_userProfilePhotoEmpty(), MTP_userStatusRecently())));
|
||||
int32 userFlags = MTPDuser::flag_first_name | MTPDuser::flag_phone | MTPDuser::flag_status;
|
||||
user = App::feedUsers(MTP_vector<MTPUser>(1, MTP_user(MTP_int(userFlags), MTP_int(ServiceUserId), MTPlong(), MTP_string("Telegram"), MTPstring(), MTPstring(), MTP_string("42777"), MTP_userProfilePhotoEmpty(), MTP_userStatusRecently(), MTPint())));
|
||||
}
|
||||
_serviceHistoryRequest = MTP::send(MTPmessages_GetHistory(user->input, MTP_int(0), MTP_int(0), MTP_int(1)), main->rpcDone(&MainWidget::serviceHistoryDone), main->rpcFail(&MainWidget::serviceHistoryFail));
|
||||
}
|
||||
@@ -1415,7 +1417,8 @@ void Window::notifyShowNext(NotifyWindow *remove) {
|
||||
while (count > 0) {
|
||||
uint64 next = 0;
|
||||
HistoryItem *notifyItem = 0;
|
||||
NotifyWaiters::iterator notifyWaiter;
|
||||
History *notifyHistory = 0;
|
||||
NotifyWaiters::iterator notifyWaiter = notifyWaiters.end();
|
||||
for (NotifyWaiters::iterator i = notifyWaiters.begin(); i != notifyWaiters.end(); ++i) {
|
||||
History *history = i.key();
|
||||
if (history->currentNotification() && history->currentNotification()->id != i.value().msg) {
|
||||
@@ -1423,6 +1426,7 @@ void Window::notifyShowNext(NotifyWindow *remove) {
|
||||
if (j == notifyWhenMaps.end()) {
|
||||
history->clearNotifications();
|
||||
i = notifyWaiters.erase(i);
|
||||
if (notifyHistory) notifyWaiter = notifyWaiters.find(notifyHistory);
|
||||
continue;
|
||||
}
|
||||
do {
|
||||
@@ -1438,12 +1442,14 @@ void Window::notifyShowNext(NotifyWindow *remove) {
|
||||
if (!history->currentNotification()) {
|
||||
notifyWhenMaps.remove(history);
|
||||
i = notifyWaiters.erase(i);
|
||||
if (notifyHistory) notifyWaiter = notifyWaiters.find(notifyHistory);
|
||||
continue;
|
||||
}
|
||||
uint64 when = i.value().when;
|
||||
if (!notifyItem || next > when) {
|
||||
next = when;
|
||||
notifyItem = history->currentNotification();
|
||||
notifyHistory = history;
|
||||
notifyWaiter = i;
|
||||
}
|
||||
}
|
||||
@@ -1510,8 +1516,8 @@ void Window::notifyShowNext(NotifyWindow *remove) {
|
||||
}
|
||||
|
||||
if (!history->hasNotification()) {
|
||||
notifyWaiters.erase(notifyWaiter);
|
||||
if (j != notifyWhenMaps.end()) notifyWhenMaps.erase(j);
|
||||
if (notifyWaiter != notifyWaiters.cend()) notifyWaiters.erase(notifyWaiter);
|
||||
if (j != notifyWhenMaps.cend()) notifyWhenMaps.erase(j);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.8.17</string>
|
||||
<string>0.8.27</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -83,6 +83,7 @@ SOURCES += \
|
||||
./SourceFiles/app.cpp \
|
||||
./SourceFiles/application.cpp \
|
||||
./SourceFiles/audio.cpp \
|
||||
./SourceFiles/autoupdater.cpp \
|
||||
./SourceFiles/dialogswidget.cpp \
|
||||
./SourceFiles/dropdown.cpp \
|
||||
./SourceFiles/fileuploader.cpp \
|
||||
@@ -166,6 +167,7 @@ HEADERS += \
|
||||
./SourceFiles/app.h \
|
||||
./SourceFiles/application.h \
|
||||
./SourceFiles/audio.h \
|
||||
./SourceFiles/autoupdater.h \
|
||||
./SourceFiles/config.h \
|
||||
./SourceFiles/countries.h \
|
||||
./SourceFiles/dialogswidget.h \
|
||||
@@ -300,7 +302,7 @@ INCLUDEPATH += "/usr/include/atk-1.0"
|
||||
INCLUDEPATH += "/usr/include/dee-1.0"
|
||||
INCLUDEPATH += "/usr/include/libdbusmenu-glib-0.4"
|
||||
|
||||
LIBS += -lcrypto -lssl -lz -ldl -llzma -lexif -lopus -lopusfile -logg -lopenal
|
||||
LIBS += -lcrypto -lssl -lz -ldl -llzma -lexif -lopenal -lavformat -lavcodec -lswresample -lavutil -lopus
|
||||
LIBS += ./../../../Libraries/QtStatic/qtbase/plugins/platforminputcontexts/libcomposeplatforminputcontextplugin.a
|
||||
|
||||
RESOURCES += \
|
||||
|
||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user