Compare commits

...

29 Commits

Author SHA1 Message Date
John Preston
3ce8d9f0b7 version 0.6.13 - new icon, critical bug fix in win version 2014-11-24 11:30:43 +03:00
John Preston
b23ffe6c94 Merge branch 'master' of https://github.com/telegramdesktop/tdesktop 2014-11-24 11:23:47 +03:00
John Preston
276ef42c8f fixed crash in win version 2014-11-24 11:23:39 +03:00
John Preston
79a41d541d Merge branch 'master' of https://github.com/telegramdesktop/tdesktop 2014-11-24 11:23:20 +03:00
John Preston
61e3f9000b icon changed in mac version 2014-11-24 11:23:12 +03:00
John Preston
a4c13e0720 fixed Download button in mediaview 2014-11-24 10:54:06 +03:00
John Preston
a09460dc84 fixed emojis, now work like on iOS 2014-11-24 00:49:14 +03:00
John Preston
4bcfee22ef icon changed in win version 2014-11-23 14:20:40 +03:00
John Preston
880c2697d1 Merge branch 'master' of https://github.com/telegramdesktop/tdesktop 2014-11-22 13:23:40 +03:00
John Preston
188e1b61c5 improved deploy bat 2014-11-22 13:23:30 +03:00
John Preston
696c5df092 improved deploy sh 2014-11-22 13:21:50 +03:00
John Preston
408b38b41f added localstorage to xcode project 2014-11-22 13:01:28 +03:00
John Preston
4a6b6fad77 Merge branch 'master' of https://github.com/telegramdesktop/tdesktop 2014-11-22 12:46:12 +03:00
John Preston
f7fa13899f default recent emojis fixed 2014-11-22 12:45:54 +03:00
John Preston
f370e2b85d version 0.6.12 - local image cache, drafts, shared contact fix, some network fixes 2014-11-22 12:45:04 +03:00
John Preston
5d649f750b fixed maximize button in windows 2014-11-18 18:22:05 +03:00
John Preston
cfcf4d2336 Merge branch 'master' of https://github.com/telegramdesktop/tdesktop 2014-11-18 16:59:38 +03:00
John Preston
922ab40c75 version 0.6.11 fixed photos click area 2014-11-18 16:59:18 +03:00
John Preston
2532245413 fixed xcode warning 2014-11-18 16:18:42 +03:00
John Preston
85ca7e0f05 version 0.6.10 prepared 2014-11-18 15:59:16 +03:00
John Preston
28c8d125cf Merge branch 'master' of https://github.com/telegramdesktop/tdesktop 2014-11-18 15:41:51 +03:00
John Preston
799a81966a min connection timeout 4secs, fixed photo display, thumbs size is less now, update button text and animation added 2014-11-18 15:41:33 +03:00
John Preston
6333bc59b1 fixed os x dark theme tray icon, added os x main menu 2014-11-18 15:40:43 +03:00
John Preston
d953f894a1 Merge branch 'master' of https://github.com/telegramdesktop/tdesktop 2014-11-15 02:24:00 +03:00
John Preston
868b9843b0 version 0.6.9 - some network and protocol improvements, checkboxes in photos overview, other fixes 2014-11-15 02:23:35 +03:00
John Preston
c89f13bb53 Merge branch 'master' of https://github.com/telegramdesktop/tdesktop 2014-11-13 21:32:24 +03:00
John Preston
722c801f8a fixed pspecific_linux 2014-11-13 10:32:06 -08:00
John Preston
4ee33d3bd9 added os x window icon and qt os x build fix to git 2014-11-13 21:26:39 +03:00
John Preston
85285d9862 fixed os x mouse input, window icon, qt os x build-from-source 2014-11-13 21:26:17 +03:00
147 changed files with 10956 additions and 7189 deletions

View File

@@ -1,5 +1,5 @@
AppVersionStr=0.6.8
AppVersion=6008
AppVersion=`./Version.sh | awk -F " " '{print $1}'`
AppVersionStr=`./Version.sh | awk -F " " '{print $2}'`
if [ ! -f "./../Linux/Release/deploy/$AppVersionStr/tlinuxupd$AppVersion" ]; then
echo "tlinuxupd$AppVersion not found!";

View File

@@ -1,5 +1,5 @@
AppVersionStr=0.6.8
AppVersion=6008
AppVersion=`./Version.sh | awk -F " " '{print $1}'`
AppVersionStr=`./Version.sh | awk -F " " '{print $2}'`
if [ ! -f "./../Linux/Release/deploy/$AppVersionStr/tlinux32upd$AppVersion" ]; then
echo "tlinux32upd$AppVersion not found!"

View File

@@ -1,5 +1,5 @@
AppVersionStr=0.6.8
AppVersion=6008
AppVersion=`./Version.sh | awk -F " " '{print $1}'`
AppVersionStr=`./Version.sh | awk -F " " '{print $2}'`
if [ ! -f "./../Mac/Release/deploy/$AppVersionStr/tmacupd$AppVersion" ]; then
echo "tmacupd$AppVersion not found!"

View File

@@ -1,5 +1,5 @@
AppVersionStr=0.6.8
AppVersion=6008
AppVersion=`./Version.sh | awk -F " " '{print $1}'`
AppVersionStr=`./Version.sh | awk -F " " '{print $2}'`
if [ ! -f "./../Win32/Deploy/deploy/$AppVersionStr/tupdate$AppVersion" ]; then
echo "tupdate$AppVersion not found!"

View File

@@ -1,5 +1,5 @@
AppVersionStr=0.6.8
AppVersion=6008
AppVersion=`./Version.sh | awk -F " " '{print $1}'`
AppVersionStr=`./Version.sh | awk -F " " '{print $2}'`
if [ -d "./../Linux/Release/deploy/$AppVersionStr" ]; then
echo "Deploy folder for version $AppVersionStr already exists!"

View File

@@ -1,5 +1,5 @@
AppVersionStr=0.6.8
AppVersion=6008
AppVersion=`./Version.sh | awk -F " " '{print $1}'`
AppVersionStr=`./Version.sh | awk -F " " '{print $2}'`
if [ -d "./../Linux/Release/deploy/$AppVersionStr" ]; then
echo "Deploy folder for version $AppVersionStr already exists!"

View File

@@ -1,5 +1,5 @@
AppVersionStr=0.6.8
AppVersion=6008
AppVersion=`./Version.sh | awk -F " " '{print $1}'`
AppVersionStr=`./Version.sh | awk -F " " '{print $2}'`
echo ""
echo "Preparing version $AppVersionStr.."
@@ -20,11 +20,6 @@ if [ ! -d "./../Mac/Release/Telegram.app" ]; then
exit 1
fi
if [ ! -d "./../Mac/Release/Telegram.app/Contents/_CodeSignature" ]; then
echo "Telegram signature not found!"
exit 1
fi
if [ ! -f "./../Mac/Release/Telegram.app/Contents/Resources/Icon.icns" ]; then
echo "Icon.icns not found in Resources!"
exit 1
@@ -41,6 +36,12 @@ if [ ! -f "./../Mac/Release/Telegram.app/Contents/Frameworks/Updater" ]; then
fi
cd ./../Mac/Release && codesign --force --deep --sign "Developer ID Application: John Preston" Telegram.app && cd ./../../Telegram
if [ ! -d "./../Mac/Release/Telegram.app/Contents/_CodeSignature" ]; then
echo "Telegram signature not found!"
exit 1
fi
cd ./../Mac/Release
temppath=`hdiutil attach -readwrite tsetup.dmg | awk -F "\t" 'END {print $3}'`
cp -R ./Telegram.app "$temppath/"

View File

@@ -1,6 +1,9 @@
@echo OFF
set "AppVersionStr=0.6.8"
set "AppVersionStrSmall=0.6.13"
set "AppVersionStr=0.6.13"
set "AppVersionStrFull=0.6.13.0"
echo.
echo Preparing version %AppVersionStr%..
echo.
@@ -14,7 +17,7 @@ if %errorlevel% neq 0 goto error1
call ..\..\..\TelegramPrivate\Sign.bat Updater.exe
if %errorlevel% neq 0 goto error1
iscc ..\..\Telegram\Setup.iss
iscc /dMyAppVersion=%AppVersionStrSmall% /dMyAppVersionZero=%AppVersionStr% /dMyAppFullVersion=%AppVersionStrFull% ..\..\Telegram\Setup.iss
if %errorlevel% neq 0 goto error1
call ..\..\..\TelegramPrivate\Sign.bat tsetup.%AppVersionStr%.exe

View File

@@ -22,6 +22,7 @@ lng_maintitle: "Telegram D";
lng_menu_contacts: "Contacts";
lng_menu_settings: "Settings";
lng_menu_about: "About";
lng_menu_update: "Update";
lng_open_from_tray: "Open Telegram";
lng_minimize_to_tray: "Minimize to tray";
@@ -61,7 +62,11 @@ lng_connecting: "Connecting..";
lng_reconnecting: "Reconnect in %1 s..";
lng_reconnecting_try_now: "Try now";
lng_status_offline: "offline";
lng_status_service_notifications: "service notifications";
lng_status_offline: "last seen a long time ago";
lng_status_recently: "last seen recently";
lng_status_last_week: "last seen within a week";
lng_status_last_month: "last seen within a month";
lng_status_invisible: "invisible";
lng_status_lastseen: "last seen {when}";
lng_status_lastseen_now: "just now";
@@ -215,6 +220,15 @@ lng_download_path_clearing: "Clearing..";
lng_download_path_cleared: "Cleared!";
lng_download_path_clear_failed: "Clear failed :(";
lng_settings_section_cache: "Local storage";
lng_settings_no_images_cached: "No cached images found!";
lng_settings_image_cached: "Cached: {count} image, {size}";
lng_settings_images_cached: "Cached: {count} images, {size}";
lng_local_images_clear: "Clear All";
lng_local_images_clearing: "Clearing..";
lng_local_images_cleared: "Cleared!";
lng_local_images_clear_failed: "Clear failed :(";
lng_settings_section_advanced: "Advanced";
lng_connection_type: "Connection type:";
lng_connection_auto_connecting: "Default (connecting..)";
@@ -233,6 +247,7 @@ lng_connection_save: "Save";
lng_settings_reset: "Reset other sessions";
lng_settings_reset_done: "Sessions reset done";
lng_settings_logout: "Log Out";
lng_sure_logout: "Are you sure you want to log out?";
lng_settings_need_restart: "You need to restart for applying
some of the new settings. Restart now?";
@@ -473,5 +488,21 @@ lng_mac_always_open_with: "Always Open With";
lng_mac_this_app_can_open: "This application can open \"{file}\".";
lng_mac_not_known_app: "It's not known if this application can open \"{file}\".";
// Keys finished
lng_mac_menu_about: "About Telegram";
lng_mac_menu_preferences: "Preferences...";
lng_mac_menu_file: "File";
lng_mac_menu_logout: "Log Out";
lng_mac_menu_edit: "Edit";
lng_mac_menu_undo: "Undo";
lng_mac_menu_redo: "Redo";
lng_mac_menu_cut: "Cut";
lng_mac_menu_copy: "Copy";
lng_mac_menu_paste: "Paste";
lng_mac_menu_delete: "Delete";
lng_mac_menu_select_all: "Select All";
lng_mac_menu_window: "Window";
lng_mac_menu_contacts: "Contacts";
lng_mac_menu_new_group: "New Group";
lng_mac_menu_show: "Show Telegram";
// Keys finished

View File

@@ -29,7 +29,7 @@ emojiPadding: 0px;
counterBG: #f23c34;
counterMuteBG: #888;
counterColor: #fff;
counterMacInvColor: #045fd5;
counterMacInvColor: #ffffff01;
lineWidth: 1px;
@@ -86,6 +86,7 @@ sysUpd: sysButton {
overColor: white;
duration: 150;
}
updateBlinkDuration: 500;
sysMin: sysButton(sysUpd) {
img: sprite(207px, 1px, 19px, 19px);
}
@@ -1572,6 +1573,9 @@ medviewPhotoSpritePos: point(14px, 14px);
overviewPhotoSkip: 10px;
overviewPhotoMinSize: 100px;
overviewPhotoCheck: sprite(245px, 308px, 32px, 32px);
overviewPhotoChecked: sprite(278px, 308px, 32px, 32px);
overviewPhotoSelectOverlay: #0a7bb03f;
// Mac specific
@@ -1621,6 +1625,7 @@ mediaviewLoaderSkip: 9px;
minPhotoWidth: 90px;
minPhotoHeight: 90px;
maxMediaSize: 420px;
usernameFont: font(14px);
usernameColor: #777;
@@ -1637,5 +1642,5 @@ usernameCancel: flatButton(btnSelectCancel) {
}
youtubeIcon: sprite(336px, 221px, 60px, 60px);
instagramIcon: sprite(336px, 283px, 60px, 60px);
vimeoIcon: sprite(336px, 283px, 60px, 60px);
locationSize: size(320, 240);

View File

@@ -3,9 +3,6 @@
#define MyAppShortName "Telegram"
#define MyAppName "Telegram Desktop"
#define MyAppVersion "0.6.8"
#define MyAppVersionZero "0.6.8"
#define MyAppFullVersion "0.6.8.0"
#define MyAppPublisher "Telegram Messenger LLP"
#define MyAppURL "https://tdesktop.com"
#define MyAppExeName "Telegram.exe"
@@ -28,7 +25,7 @@ DefaultGroupName={#MyAppName}
AllowNoIcons=yes
OutputDir=.\..\Win32\Deploy
OutputBaseFilename=tsetup.{#MyAppVersionZero}
SetupIconFile=.\SourceFiles\art\iconround256.ico
SetupIconFile=.\SourceFiles\art\icon256.ico
UninstallDisplayIcon={app}\Telegram.exe
Compression=lzma
SolidCompression=yes

View File

@@ -78,11 +78,11 @@ EmojiReplace replaces[] = {
{0xD83DDE37U, ":X"},
{0xD83DDE1AU, ":-*"},
{0xD83DDE08U, "}:)"},
{0x2764FE0FU, "<3"},
{0x2764U, "<3"},
{0xD83DDC4DU, ":like:"},
{0xD83DDC4EU, ":dislike:"},
{0x261DFE0FU, ":up:"},
{0x270CFE0FU, ":v:"},
{0x261DU, ":up:"},
{0x270CU, ":v:"},
{0xD83DDC4CU, ":ok:"}
};
const uint32 replacesCount = sizeof(replaces) / sizeof(EmojiReplace);
@@ -102,7 +102,7 @@ uint64 emojiCategory0[] = {
0xD83DDE03LLU,
0xD83DDE00LLU,
0xD83DDE0ALLU,
0x263AFE0FLLU,
0x263ALLU,
0xD83DDE09LLU,
0xD83DDE0DLLU,
0xD83DDE18LLU,
@@ -208,7 +208,7 @@ uint64 emojiCategory0[] = {
0xD83DDC4CLLU,
0xD83DDC4ALLU,
0x270ALLU,
0x270CFE0FLLU,
0x270CLLU,
0xD83DDC4BLLU,
0x270BLLU,
0xD83DDC50LLU,
@@ -218,7 +218,7 @@ uint64 emojiCategory0[] = {
0xD83DDC48LLU,
0xD83DDE4CLLU,
0xD83DDE4FLLU,
0x261DFE0FLLU,
0x261DLLU,
0xD83DDC4FLLU,
0xD83DDCAALLU,
0xD83DDEB6LLU,
@@ -270,7 +270,7 @@ uint64 emojiCategory0[] = {
0xD83DDC99LLU,
0xD83DDC9CLLU,
0xD83DDC9ALLU,
0x2764FE0FLLU,
0x2764LLU,
0xD83DDC94LLU,
0xD83DDC97LLU,
0xD83DDC93LLU,
@@ -395,12 +395,12 @@ uint64 emojiCategory1[] = {
0xD83CDF0CLLU,
0xD83CDF20LLU,
0x2B50LLU,
0x2600FE0FLLU,
0x2600LLU,
0x26C5LLU,
0x2601FE0FLLU,
0x2601LLU,
0x26A1LLU,
0x2614LLU,
0x2744FE0FLLU,
0x2744LLU,
0x26C4LLU,
0xD83CDF00LLU,
0xD83CDF01LLU,
@@ -440,7 +440,7 @@ uint64 emojiCategory2[] = {
0xD83DDCBELLU,
0xD83DDCBBLLU,
0xD83DDCF1LLU,
0x260EFE0FLLU,
0x260ELLU,
0xD83DDCDELLU,
0xD83DDCDFLLU,
0xD83DDCE0LLU,
@@ -497,7 +497,7 @@ uint64 emojiCategory2[] = {
0xD83DDCE7LLU,
0xD83DDCE5LLU,
0xD83DDCE4LLU,
0x2709FE0FLLU,
0x2709LLU,
0xD83DDCE9LLU,
0xD83DDCE8LLU,
0xD83DDCEFLLU,
@@ -521,11 +521,11 @@ uint64 emojiCategory2[] = {
0xD83DDCC7LLU,
0xD83DDCC1LLU,
0xD83DDCC2LLU,
0x2702FE0FLLU,
0x2702LLU,
0xD83DDCCCLLU,
0xD83DDCCELLU,
0x2712FE0FLLU,
0x270FFE0FLLU,
0x2712LLU,
0x270FLLU,
0xD83DDCCFLLU,
0xD83DDCD0LLU,
0xD83DDCD5LLU,
@@ -564,7 +564,7 @@ uint64 emojiCategory2[] = {
0xD83CDFC8LLU,
0xD83CDFC0LLU,
0x26BDLLU,
0x26BEFE0FLLU,
0x26BELLU,
0xD83CDFBELLU,
0xD83CDFB1LLU,
0xD83CDFC9LLU,
@@ -680,7 +680,7 @@ uint64 emojiCategory3[] = {
0xD83DDEA3LLU,
0x2693LLU,
0xD83DDE80LLU,
0x2708FE0FLLU,
0x2708LLU,
0xD83DDCBALLU,
0xD83DDE81LLU,
0xD83DDE82LLU,
@@ -721,13 +721,13 @@ uint64 emojiCategory3[] = {
0xD83CDFABLLU,
0xD83DDEA6LLU,
0xD83DDEA5LLU,
0x26A0FE0FLLU,
0x26A0LLU,
0xD83DDEA7LLU,
0xD83DDD30LLU,
0x26FDLLU,
0xD83CDFEELLU,
0xD83CDFB0LLU,
0x2668FE0FLLU,
0x2668LLU,
0xD83DDDFFLLU,
0xD83CDFAALLU,
0xD83CDFADLLU,
@@ -760,33 +760,33 @@ uint64 emojiCategory4[] = {
0xD83DDD22LLU,
0x2320E3LLU,
0xD83DDD23LLU,
0x2B06FE0FLLU,
0x2B07FE0FLLU,
0x2B05FE0FLLU,
0x27A1FE0FLLU,
0x2B06LLU,
0x2B07LLU,
0x2B05LLU,
0x27A1LLU,
0xD83DDD20LLU,
0xD83DDD21LLU,
0xD83DDD24LLU,
0x2197FE0FLLU,
0x2196FE0FLLU,
0x2198FE0FLLU,
0x2199FE0FLLU,
0x2194FE0FLLU,
0x2195FE0FLLU,
0x2197LLU,
0x2196LLU,
0x2198LLU,
0x2199LLU,
0x2194LLU,
0x2195LLU,
0xD83DDD04LLU,
0x25C0FE0FLLU,
0x25B6FE0FLLU,
0x25C0LLU,
0x25B6LLU,
0xD83DDD3CLLU,
0xD83DDD3DLLU,
0x21A9FE0FLLU,
0x21AAFE0FLLU,
0x2139FE0FLLU,
0x21A9LLU,
0x21AALLU,
0x2139LLU,
0x23EALLU,
0x23E9LLU,
0x23EBLLU,
0x23ECLLU,
0x2935FE0FLLU,
0x2934FE0FLLU,
0x2935LLU,
0x2934LLU,
0xD83CDD97LLU,
0xD83DDD00LLU,
0xD83DDD01LLU,
@@ -802,8 +802,8 @@ uint64 emojiCategory4[] = {
0xD83CDE2FLLU,
0xD83CDE33LLU,
0xD83CDE35LLU,
0xD83CDE32LLU,
0xD83CDE34LLU,
0xD83CDE32LLU,
0xD83CDE50LLU,
0xD83CDE39LLU,
0xD83CDE3ALLU,
@@ -822,14 +822,14 @@ uint64 emojiCategory4[] = {
0xD83CDE37LLU,
0xD83CDE38LLU,
0xD83CDE02LLU,
0x24C2FE0FLLU,
0x24C2LLU,
0xD83DDEC2LLU,
0xD83DDEC4LLU,
0xD83DDEC5LLU,
0xD83DDEC3LLU,
0xD83CDE51LLU,
0x3299FE0FLLU,
0x3297FE0FLLU,
0x3299LLU,
0x3297LLU,
0xD83CDD91LLU,
0xD83CDD98LLU,
0xD83CDD94LLU,
@@ -842,11 +842,11 @@ uint64 emojiCategory4[] = {
0xD83DDEB7LLU,
0xD83DDEB8LLU,
0x26D4LLU,
0x2733FE0FLLU,
0x2747FE0FLLU,
0x2733LLU,
0x2747LLU,
0x274ELLU,
0x2705LLU,
0x2734FE0FLLU,
0x2734LLU,
0xD83DDC9FLLU,
0xD83CDD9ALLU,
0xD83DDCF3LLU,
@@ -857,7 +857,7 @@ uint64 emojiCategory4[] = {
0xD83CDD7ELLU,
0xD83DDCA0LLU,
0x27BFLLU,
0x267BFE0FLLU,
0x267BLLU,
0x2648LLU,
0x2649LLU,
0x264ALLU,
@@ -880,8 +880,8 @@ uint64 emojiCategory4[] = {
0xAELLU,
0x2122LLU,
0x274CLLU,
0x203CFE0FLLU,
0x2049FE0FLLU,
0x203CLLU,
0x2049LLU,
0x2757LLU,
0x2753LLU,
0x2755LLU,
@@ -921,26 +921,26 @@ uint64 emojiCategory4[] = {
0x2795LLU,
0x2796LLU,
0x2797LLU,
0x2660FE0FLLU,
0x2665FE0FLLU,
0x2663FE0FLLU,
0x2666FE0FLLU,
0x2660LLU,
0x2665LLU,
0x2663LLU,
0x2666LLU,
0xD83DDCAELLU,
0xD83DDCAFLLU,
0x2714FE0FLLU,
0x2611FE0FLLU,
0x2714LLU,
0x2611LLU,
0xD83DDD18LLU,
0xD83DDD17LLU,
0x27B0LLU,
0x3030LLU,
0x303DFE0FLLU,
0x303DLLU,
0xD83DDD31LLU,
0x25FCFE0FLLU,
0x25FBFE0FLLU,
0x25FCLLU,
0x25FBLLU,
0x25FELLU,
0x25FDLLU,
0x25AAFE0FLLU,
0x25ABFE0FLLU,
0x25AALLU,
0x25ABLLU,
0xD83DDD3ALLU,
0xD83DDD32LLU,
0xD83DDD33LLU,
@@ -957,61 +957,105 @@ uint64 emojiCategory4[] = {
0xD83DDD39LLU,
};
uint64 emojiClones[] = {
0x263ALLU,
0x270CLLU,
0x261DLLU,
0x2764LLU,
0x2600LLU,
0x2601LLU,
0x2744LLU,
0x260ELLU,
0x2709LLU,
0x2702LLU,
0x2712LLU,
0x270FLLU,
0x26BELLU,
0x2708LLU,
0x26A0LLU,
0x2668LLU,
0x2B06LLU,
0x2B07LLU,
0x2B05LLU,
0x27A1LLU,
0x2197LLU,
0x2196LLU,
0x2198LLU,
0x2199LLU,
0x2194LLU,
0x2195LLU,
0x25C0LLU,
0x25B6LLU,
0x21A9LLU,
0x21AALLU,
0x2139LLU,
0x2935LLU,
0x2934LLU,
0x24C2LLU,
0x3299LLU,
0x3297LLU,
0x2733LLU,
0x2747LLU,
0x2734LLU,
0x267BLLU,
uint64 emojiPostfixed[] = {
0x203CLLU,
0x2049LLU,
0x2660LLU,
0x2665LLU,
0x2663LLU,
0x2666LLU,
0x2714LLU,
0x2611LLU,
0x303DLLU,
0x25FCLLU,
0x25FBLLU,
0x2139LLU,
0x2194LLU,
0x2195LLU,
0x2196LLU,
0x2197LLU,
0x2198LLU,
0x2199LLU,
0x21A9LLU,
0x21AALLU,
0x231ALLU,
0x231BLLU,
0x24C2LLU,
0x25AALLU,
0x25ABLLU,
0x25B6LLU,
0x25C0LLU,
0x25FBLLU,
0x25FCLLU,
0x25FDLLU,
0x25FELLU,
0x2600LLU,
0x2601LLU,
0x260ELLU,
0x2611LLU,
0x2614LLU,
0x2615LLU,
0x261DLLU,
0x263ALLU,
0x2648LLU,
0x2649LLU,
0x264ALLU,
0x264BLLU,
0x264CLLU,
0x264DLLU,
0x264ELLU,
0x264FLLU,
0x2650LLU,
0x2651LLU,
0x2652LLU,
0x2653LLU,
0x2660LLU,
0x2663LLU,
0x2665LLU,
0x2666LLU,
0x2668LLU,
0x267BLLU,
0x267FLLU,
0x2693LLU,
0x26A0LLU,
0x26A1LLU,
0x26AALLU,
0x26ABLLU,
0x26BDLLU,
0x26BELLU,
0x26C4LLU,
0x26C5LLU,
0x26D4LLU,
0x26EALLU,
0x26F2LLU,
0x26F3LLU,
0x26F5LLU,
0x26FALLU,
0x26FDLLU,
0x2702LLU,
0x2708LLU,
0x2709LLU,
0x270CLLU,
0x270FLLU,
0x2712LLU,
0x2714LLU,
0x2716LLU,
0x2733LLU,
0x2734LLU,
0x2744LLU,
0x2747LLU,
0x2757LLU,
0x2764LLU,
0x27A1LLU,
0x2934LLU,
0x2935LLU,
0x2B05LLU,
0x2B06LLU,
0x2B07LLU,
0x2B50LLU,
0x2B55LLU,
0x2B1BLLU,
0x2B1CLLU,
0x303DLLU,
0x3297LLU,
0x3299LLU,
0xD83CDC04LLU,
0xD83CDD7FLLU,
0xD83CDE1ALLU,
0xD83CDE2FLLU,
};
QMap<uint64, bool> emojiWithPostfixes;
uint32 firstCode(uint64 fullCode) {
return (fullCode > 0xFFFFFFFFLLU) ? uint32(fullCode >> 32) : (fullCode & 0xFFFFFFFFU);
@@ -1046,7 +1090,7 @@ void writeEmojiCategory(QTextStream &tcpp, uint64 *emojiCategory, uint32 size, c
bool genEmoji(QString emoji_in, const QString &emoji_out, const QString &emoji_png) {
int currentRow = 0, currentColumn = 0;
uint32 min1 = 0xFFFFFFFFU, max1 = 0, min2 = 0xFFFFFFFFU, max2 = 0, min3 = 0xFFFFFFFFU, max3 = 0;
uint32 min1 = 0xFFFFFFFFU, max1 = 0, min2 = 0xFFFFFFFFU, max2 = 0;
QImage sprites[5];
int emojisInRow[] = { 27, 29, 33, 34, 34 }; // [[7,27],[4,29],[7,33],[3,34],[6,34]]
@@ -1100,13 +1144,8 @@ bool genEmoji(QString emoji_in, const QString &emoji_out, const QString &emoji_p
}
} else if (high == 35 || (high >= 48 && high < 58)) { // digits
} else {
if (high < 0xD000) {
if (data.code < min2) min2 = data.code;
if (data.code > max2) max2 = data.code;
} else {
if (data.code < min3) min3 = data.code;
if (data.code > max3) max3 = data.code;
}
if (data.code < min2) min2 = data.code;
if (data.code > max2) max2 = data.code;
}
EmojisData::const_iterator k = emojisData.constFind(data.code);
if (k != emojisData.cend()) {
@@ -1170,23 +1209,8 @@ bool genEmoji(QString emoji_in, const QString &emoji_out, const QString &emoji_p
}
}
for (int i = 0, l = sizeof(emojiClones) / sizeof(emojiClones[0]); i < l; ++i) {
uint64 cloneCode = emojiClones[i], originalCode = (cloneCode << 16) | 0xFE0F;
EmojisData::const_iterator j = emojisData.constFind(originalCode);
if (j == emojisData.cend()) {
cout << "Could not find data for emoji clone 0x" << QString::number(cloneCode, 16).toUpper().toUtf8().constData() << "!\n";
return false;
}
EmojiData clone;
clone.code = firstCode(cloneCode);
clone.code2 = secondCode(cloneCode);
clone.category = -1;
clone.index = -1;
clone.x = j->x;
clone.y = j->y;
emojisData.insert(cloneCode, clone);
if (clone.code < min1) min1 = clone.code;
if (clone.code > max1) max1 = clone.code;
for (int i = 0, l = sizeof(emojiPostfixed) / sizeof(emojiPostfixed[0]); i < l; ++i) {
emojiWithPostfixes.insert(emojiPostfixed[i], true);
}
try {
@@ -1229,7 +1253,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com\n\
tcpp << "\tEmojiData *toFill = emojis = (EmojiData*)emojisData;\n\n";
for (EmojisData::const_iterator i = emojisData.cbegin(), e = emojisData.cend(); i != e; ++i) {
int len = i->code2 ? 4 : ((i->code >> 16) ? 2 : 1);
tcpp << "\tnew (toFill++) EmojiData(" << (i->x * imSize) << ", " << (i->y * imSize) << ", 0x" << QString("%1").arg(i->code, 0, 16).toUpper().toUtf8().constData() << "U, 0x" << QString("%1").arg(i->code2, 0, 16).toUpper().toUtf8().constData() << "U, " << len << ");\n";
bool withPostfix = emojiWithPostfixes.constFind(i->code) != emojiWithPostfixes.constEnd();
tcpp << "\tnew (toFill++) EmojiData(" << (i->x * imSize) << ", " << (i->y * imSize) << ", 0x" << QString("%1").arg(i->code, 0, 16).toUpper().toUtf8().constData() << "U, 0x" << QString("%1").arg(i->code2, 0, 16).toUpper().toUtf8().constData() << "U, " << len << (withPostfix ? ", 0xFE0F" : "") << ");\n";
}
tcpp << "}\n\n";
}
@@ -1283,18 +1308,7 @@ Copyright (c) 2014 John Preston, https://tdesktop.com\n\
tcpp << "\t\treturn 0;\n";
tcpp << "\t}\n\n";
tcpp << "\tif (code >= 0x" << QString("%1").arg(min2, 0, 16).toUpper().toUtf8().constData() << "U && code <= 0x" << QString("%1").arg(max2, 0, 16).toUpper().toUtf8().constData() << "U) {\n";
tcpp << "\t\tif ((code & 0xFFFFU) != 0xFE0F) return 0;\n\n";
tcpp << "\t\tswitch (code) {\n";
for (; i != e; ++i) {
if (i->code2 || ((i->code >> 16) >= 0xD000)) break;
tcpp << "\t\t\tcase 0x" << QString("%1").arg(i->code, 0, 16).toUpper().toUtf8().constData() << "U: return &emojis[" << (index++) << "];\n";
}
tcpp << "\t\t}\n\n";
tcpp << "\t\treturn 0;\n";
tcpp << "\t}\n\n";
tcpp << "\tif (code < 0x" << QString("%1").arg(min3, 0, 16).toUpper().toUtf8().constData() << "U || code > 0x" << QString("%1").arg(max3, 0, 16).toUpper().toUtf8().constData() << "U) return 0;\n\n";
tcpp << "\tif (code < 0x" << QString("%1").arg(min2, 0, 16).toUpper().toUtf8().constData() << "U || code > 0x" << QString("%1").arg(max2, 0, 16).toUpper().toUtf8().constData() << "U) return 0;\n\n";
tcpp << "\tswitch (code) {\n";
for (; i != e; ++i) {
tcpp << "\tcase 0x" << QString("%1").arg(i->code, 0, 16).toUpper().toUtf8().constData() << "U: return &emojis[" << (index++) << "];\n";

View File

@@ -26,6 +26,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "mainwidget.h"
#include <libexif/exif-data.h>
#include "localstorage.h"
namespace {
bool quiting = false;
@@ -114,7 +116,7 @@ namespace App {
bool loggedOut() {
Window *w(wnd());
if (w) {
w->tempDirDelete();
w->tempDirDelete(Local::ClearManagerAll);
w->notifyClearFast();
w->setupIntro(true);
}
@@ -165,6 +167,31 @@ namespace App {
return (peer_id & 0x100000000L) ? int32(peer_id & 0xFFFFFFFFL) : 0;
}
int32 onlineForSort(int32 online, int32 now) {
if (online <= 0) {
switch (online) {
case -2: {
QDate yesterday(date(now).date());
yesterday.addDays(-3);
return int32(QDateTime(yesterday).toTime_t());
} break;
case -3: {
QDate weekago(date(now).date());
weekago.addDays(-7);
return int32(QDateTime(weekago).toTime_t());
} break;
case -4: {
QDate monthago(date(now).date());
monthago.addDays(-30);
return int32(QDateTime(monthago).toTime_t());
} break;
}
}
return online;
}
int32 onlineWillChangeIn(int32 online, int32 now) {
if (online <= 0) return 86400;
if (online > now) {
@@ -182,9 +209,20 @@ namespace App {
return dNow.secsTo(dTomorrow);
}
QString onlineText(int32 online, int32 now, bool precise) {
if (!online) return lang(lng_status_offline);
if (online < 0) return lang(lng_status_invisible);
QString onlineText(UserData *user, int32 now, bool precise) {
if (isServiceUser(user->id)) {
return lang(lng_status_service_notifications);
}
int32 online = user->onlineTill;
if (online <= 0) {
switch (online) {
case 0: return lang(lng_status_offline);
case -2: return lang(lng_status_recently);
case -3: return lang(lng_status_last_week);
case -4: return lang(lng_status_last_month);
}
return lang(lng_status_invisible);
}
if (online > now) {
return lang(lng_status_online);
}
@@ -277,7 +315,10 @@ namespace App {
data->contact = -1;
status = &d.vstatus;
::self = data;
if (::self != data) {
::self = data;
if (App::wnd()) App::wnd()->updateGlobalMenu();
}
} break;
case mtpc_userContact: {
const MTPDuserContact &d(user.c_userContact());
@@ -302,7 +343,7 @@ namespace App {
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)), (data->id != 333000 && !data->phone.isEmpty()) ? formatPhone(data->phone) : QString(), textOneLine(qs(d.vusername)));
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);
@@ -329,6 +370,10 @@ namespace App {
data->loaded = true;
if (status) switch (status->type()) {
case mtpc_userStatusEmpty: data->onlineTill = 0; break;
case mtpc_userStatusRecently: data->onlineTill = -2; break;
case mtpc_userStatusLastWeek: data->onlineTill = -3; break;
case mtpc_userStatusLastMonth: data->onlineTill = -4; break;
case mtpc_userStatusOffline: data->onlineTill = status->c_userStatusOffline().vwas_online.v; break;
case mtpc_userStatusOnline: data->onlineTill = status->c_userStatusOnline().vexpires.v; break;
}
@@ -408,7 +453,7 @@ namespace App {
if (!data) continue;
data->loaded = true;
data->updateName(title.trimmed(), QString());
data->updateName(title.trimmed(), QString(), QString());
if (App::main()) App::main()->peerUpdated(data);
}
@@ -644,7 +689,7 @@ namespace App {
App::main()->removeContact(user);
}
}
user->setName(textOneLine(user->firstName), textOneLine(user->lastName), (user->contact || user->id == 333000 || user->phone.isEmpty()) ? QString() : App::formatPhone(user->phone), textOneLine(user->username));
user->setName(textOneLine(user->firstName), textOneLine(user->lastName), (user->contact || isServiceUser(user->id) || user->phone.isEmpty()) ? QString() : App::formatPhone(user->phone), textOneLine(user->username));
if (App::main()) App::main()->peerUpdated(user);
}
}
@@ -1064,7 +1109,6 @@ namespace App {
result = new ImageLinkData(imageLink);
imageLinksData.insert(imageLink, result);
result->type = type;
result->openl = TextLinkPtr(new TextLink(url));
} else {
result = i.value();
}
@@ -1242,6 +1286,7 @@ namespace App {
lastPhotos.clear();
lastPhotosMap.clear();
::self = 0;
if (App::wnd()) App::wnd()->updateGlobalMenu();
}
/* // don't delete history without deleting its' peerdata
void deleteHistory(const PeerId &peer) {
@@ -1615,7 +1660,7 @@ namespace App {
}
QByteArray encrypted(16 + fullSize, Qt::Uninitialized); // 128bit of sha1 - key128, sizeof(data), data
hashSha1(toEncrypt.constData(), toEncrypt.size(), encrypted.data());
aesEncryptLocal(toEncrypt.constData(), encrypted.data() + 16, fullSize, &MTP::localKey(), encrypted.constData());
aesEncryptLocal(toEncrypt.constData(), encrypted.data() + 16, fullSize, &Local::oldKey(), encrypted.constData());
DEBUG_LOG(("App Info: writing user config file"));
QDataStream configStream(&configFile);
@@ -1666,7 +1711,7 @@ namespace App {
}
cSetLocalSalt(salt);
MTP::createLocalKey(QByteArray(), &salt);
Local::createOldKey(&salt);
if (data.size() <= 16 || (data.size() & 0x0F)) {
LOG(("App Error: bad encrypted part size: %1").arg(data.size()));
@@ -1675,7 +1720,7 @@ namespace App {
uint32 fullDataLen = data.size() - 16;
decrypted.resize(fullDataLen);
const char *dataKey = data.constData(), *encrypted = data.constData() + 16;
aesDecryptLocal(encrypted, decrypted.data(), fullDataLen, &MTP::localKey(), dataKey);
aesDecryptLocal(encrypted, decrypted.data(), fullDataLen, &Local::oldKey(), dataKey);
uchar sha1Buffer[20];
if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), dataKey, 16)) {
LOG(("App Error: bad decrypt key, data from user-config not decrypted"));
@@ -1902,7 +1947,6 @@ namespace App {
::quiting = true;
}
QImage readImage(QByteArray data, QByteArray *format) {
QByteArray tmpFormat;
QImage result;

View File

@@ -63,8 +63,9 @@ 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);
QString onlineText(int32 onlineOnServer, int32 nowOnServer, bool precise = false);
QString onlineText(UserData *user, int32 nowOnServer, bool precise = false);
void feedUsers(const MTPVector<MTPUser> &users);
void feedChats(const MTPVector<MTPChat> &chats);

View File

@@ -28,6 +28,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "boxes/confirmbox.h"
#include "langloaderplain.h"
#include "localstorage.h"
namespace {
Application *mainApp = 0;
FileUploader *uploader = 0;
@@ -131,6 +133,7 @@ Application::Application(int &argc, char **argv) : PsApplication(argc, argv),
}
}
Local::start();
style::startManager();
anim::startManager();
historyInit();
@@ -617,9 +620,11 @@ void Application::socketError(QLocalSocket::LocalSocketError e) {
}
void Application::startApp() {
Local::ReadMapState state = Local::readMap(QByteArray());
App::readUserConfig();
if (!MTP::localKey().created()) {
MTP::createLocalKey(QByteArray());
if (!Local::oldKey().created()) {
Local::createOldKey();
cSetNeedConfigResave(true);
}
if (cNeedConfigResave()) {
@@ -633,7 +638,6 @@ void Application::startApp() {
readSupportTemplates();
MTP::setLayer(mtpLayerMax);
MTP::start();
MTP::setStateChangedHandler(mtpStateChanged);
@@ -766,6 +770,7 @@ Application::~Application() {
delete window;
style::stopManager();
Local::stop();
}
Application *Application::app() {

View File

@@ -80,6 +80,8 @@ signals:
void peerPhotoDone(PeerId peer);
void peerPhotoFail(PeerId peer);
void adjustSingleTimers();
public slots:
void startUpdateCheck(bool forceWait = false);
@@ -139,5 +141,5 @@ private:
PsUpdateDownloader *updateDownloader;
QTimer writeUserConfigTimer;
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

View File

@@ -281,7 +281,7 @@ bool AddContactBox::onSaveFail(const RPCError &error) {
QString err(error.type());
QString firstName = _firstInput.text().trimmed(), lastName = _lastInput.text().trimmed();
if (err == "CHAT_TITLE_NOT_MODIFIED") {
_peer->updateName(firstName, QString());
_peer->updateName(firstName, QString(), QString());
emit closed();
return true;
} else if (err == "NO_CHAT_TITLE") {

View File

@@ -118,7 +118,7 @@ AddParticipantInner::ContactData *AddParticipantInner::contactData(DialogRow *ro
data->inchat = _chat->participants.constFind(user) != _chat->participants.cend();
data->check = false;
data->name.setText(st::profileListNameFont, user->name, _textNameOptions);
data->online = App::onlineText(user->onlineTill, _time);
data->online = App::onlineText(user, _time);
} else {
data = i.value();
}
@@ -231,6 +231,7 @@ void AddParticipantInner::mousePressEvent(QMouseEvent *e) {
}
void AddParticipantInner::chooseParticipant() {
_time = unixtime();
int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2, from;
if (_filter.isEmpty()) {
if (!_sel || contactData(_sel)->inchat) return;
@@ -293,6 +294,7 @@ void AddParticipantInner::updateSel() {
}
void AddParticipantInner::updateFilter(QString filter) {
_time = unixtime();
QStringList f;
if (!filter.isEmpty()) {
QStringList filterList = filter.split(cWordSplit(), QString::SkipEmptyParts);
@@ -405,6 +407,7 @@ AddParticipantInner::~AddParticipantInner() {
}
void AddParticipantInner::selectSkip(int32 dir) {
_time = unixtime();
_mouseSel = false;
int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2, origDir = dir;
if (_filter.isEmpty()) {

View File

@@ -98,7 +98,7 @@ ContactsInner::ContactData *ContactsInner::contactData(DialogRow *row) {
if (i == _contactsData.cend()) {
_contactsData.insert(user, data = new ContactData());
data->name.setText(st::profileListNameFont, user->name, _textNameOptions);
data->online = App::onlineText(user->onlineTill, _time);
data->online = App::onlineText(user, _time);
} else {
data = i.value();
}

View File

@@ -99,7 +99,7 @@ NewGroupInner::ContactData *NewGroupInner::contactData(DialogRow *row) {
_contactsData.insert(user, data = new ContactData());
data->check = false;
data->name.setText(st::profileListNameFont, user->name, _textNameOptions);
data->online = App::onlineText(user->onlineTill, _time);
data->online = App::onlineText(user, _time);
} else {
data = i.value();
}

View File

@@ -17,8 +17,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#pragma once
static const int32 AppVersion = 6008;
static const wchar_t *AppVersionStr = L"0.6.8";
static const int32 AppVersion = 6013;
static const wchar_t *AppVersionStr = L"0.6.13";
static const wchar_t *AppNameOld = L"Telegram Win (Unofficial)";
static const wchar_t *AppName = L"Telegram Desktop";
@@ -37,6 +37,7 @@ enum {
MTPAckSendWaiting = 10000, // how much time to wait for some more requests, when sending msg acks
MTPResendThreshold = 1, // how much ints should message contain for us not to resend, but to check it's state
MTPContainerLives = 600, // container lives 10 minutes in haveSent map
MTPMinReceiveDelay = 4000, // 4 seconds
MTPMaxReceiveDelay = 64000, // 64 seconds
MTPConnectionOldTimeout = 192000, // 192 seconds
MTPTcpConnectionWaitTimeout = 3000, // 3 seconds waiting for tcp, until we accept http
@@ -50,7 +51,6 @@ enum {
MTPDebugBufferSize = 1024 * 1024, // 1 mb start size
MinReceiveDelay = 1000, // 1 seconds
MaxSelectedItems = 100,
MaxPhoneTailLength = 18, // rest of the phone number, without country code (seen 12 at least)
@@ -101,8 +101,16 @@ enum {
MaxMessageSize = 4096,
MaxHttpRedirects = 5, // when getting external data/images
WriteMapTimeout = 1000,
SaveDraftTimeout = 1000, // save draft after 1 secs of not changing text
SaveDraftAnywayTimeout = 5000, // or save anyway each 5 secs
};
inline bool isServiceUser(uint64 id) {
return (id == 333000) || (id == 777000);
}
#ifdef Q_OS_WIN
inline const GUID &cGUID() {
static const GUID gGuid = { 0x87a94ab0, 0xe370, 0x4cde, { 0x98, 0xd3, 0xac, 0xc1, 0x10, 0xc5, 0x96, 0x7d } };

File diff suppressed because it is too large Load Diff

View File

@@ -19,6 +19,7 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "style.h"
#include "flatinput.h"
#include "window.h"
namespace {
class FlatInputStyle : public QCommonStyle {
@@ -57,6 +58,7 @@ FlatInput::FlatInput(QWidget *parent, const style::flatInput &st, const QString
connect(this, SIGNAL(textChanged(const QString &)), this, SLOT(onTextChange(const QString &)));
connect(this, SIGNAL(textEdited(const QString &)), this, SLOT(onTextEdited()));
if (App::wnd()) connect(this, SIGNAL(selectionChanged()), App::wnd(), SLOT(updateGlobalMenu()));
setStyle(&_flatInputStyle);
setTextMargins(0, 0, 0, 0);
@@ -262,10 +264,12 @@ void FlatInput::onTextEdited() {
_oldtext = text();
if (was != _oldtext) emit changed();
updatePlaceholder();
if (App::wnd()) App::wnd()->updateGlobalMenu();
}
void FlatInput::onTextChange(const QString &text) {
_oldtext = text;
if (App::wnd()) App::wnd()->updateGlobalMenu();
}
void FlatInput::notaBene() {

View File

@@ -19,11 +19,12 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "style.h"
#include "flattextarea.h"
#include "window.h"
FlatTextarea::FlatTextarea(QWidget *parent, const style::flatTextarea &st, const QString &pholder, const QString &v) : QTextEdit(v, parent),
_ph(pholder), _oldtext(v), _phVisible(!v.length()),
a_phLeft(_phVisible ? 0 : st.phShift), a_phAlpha(_phVisible ? 1 : 0), a_phColor(st.phColor->c),
_st(st), _fakeMargin(0),
_st(st), _undoAvailable(false), _redoAvailable(false), _fakeMargin(0),
_touchPress(false), _touchRightButton(false), _touchMove(false), _replacingEmojis(false) {
setAcceptRichText(false);
resize(_st.width, _st.font->height);
@@ -58,6 +59,9 @@ FlatTextarea::FlatTextarea(QWidget *parent, const style::flatTextarea &st, const
connect(document(), SIGNAL(contentsChange(int, int, int)), this, SLOT(onDocumentContentsChange(int, int, int)));
connect(document(), SIGNAL(contentsChanged()), this, SLOT(onDocumentContentsChanged()));
connect(this, SIGNAL(undoAvailable(bool)), this, SLOT(onUndoAvailable(bool)));
connect(this, SIGNAL(redoAvailable(bool)), this, SLOT(onRedoAvailable(bool)));
if (App::wnd()) connect(this, SIGNAL(selectionChanged()), App::wnd(), SLOT(updateGlobalMenu()));
}
void FlatTextarea::onTouchTimer() {
@@ -223,7 +227,7 @@ QString FlatTextarea::getText(int32 start, int32 end) const {
uint32 index = imageName.mid(8).toUInt(0, 16);
const EmojiData *emoji = getEmoji(index);
if (emoji) {
emojiText.reserve(emoji->len);
emojiText.reserve(emoji->len + (emoji->postfix ? 1 : 0));
switch (emoji->len) {
case 1: emojiText.append(QChar(emoji->code & 0xFFFF)); break;
case 2:
@@ -237,6 +241,7 @@ QString FlatTextarea::getText(int32 start, int32 end) const {
emojiText.append(QChar(emoji->code2 & 0xFFFF));
break;
}
if (emoji->postfix) emojiText.append(QChar(emoji->postfix));
}
}
}
@@ -268,6 +273,14 @@ bool FlatTextarea::hasText() const {
return (from.next() != till);
}
bool FlatTextarea::isUndoAvailable() const {
return _undoAvailable;
}
bool FlatTextarea::isRedoAvailable() const {
return _redoAvailable;
}
void FlatTextarea::insertEmoji(EmojiPtr emoji, QTextCursor c) {
c.removeSelectedText();
@@ -283,7 +296,7 @@ void FlatTextarea::insertEmoji(EmojiPtr emoji, QTextCursor c) {
}
void FlatTextarea::processDocumentContentsChange(int position, int charsAdded) {
int32 emojiPosition = 0;
int32 emojiPosition = 0, emojiLen = 0;
const EmojiData *emoji = 0;
QTextDocument *doc(document());
@@ -305,13 +318,14 @@ void FlatTextarea::processDocumentContentsChange(int position, int charsAdded) {
QString t(fragment.text());
for (const QChar *ch = t.constData(), *e = ch + t.size(); ch != e; ++ch) {
if (ch + 1 < e && (ch->isHighSurrogate() || (((ch->unicode() >= 48 && ch->unicode() < 58) || ch->unicode() == 35) && (ch + 1)->unicode() == 0x20E3) || (ch + 1)->unicode() == 0xFE0F)) {
if (ch + 1 < e && (ch->isHighSurrogate() || (((ch->unicode() >= 48 && ch->unicode() < 58) || ch->unicode() == 35) && (ch + 1)->unicode() == 0x20E3))) {
emoji = getEmoji((ch->unicode() << 16) | (ch + 1)->unicode());
if (emoji) {
if (emoji->len == 4 && (ch + 3 >= e || ((uint32((ch + 2)->unicode()) << 16) | uint32((ch + 3)->unicode())) != emoji->code2)) {
emoji = 0;
} else {
emojiPosition = p + (ch - t.constData());
emojiLen = emoji->len + ((ch + emoji->len < e && (ch + emoji->len)->unicode() == 0xFE0F) ? 1 : 0);
break;
}
}
@@ -320,6 +334,7 @@ void FlatTextarea::processDocumentContentsChange(int position, int charsAdded) {
emoji = getEmoji(ch->unicode());
if (emoji) {
emojiPosition = p + (ch - t.constData());
emojiLen = emoji->len + ((ch + emoji->len < e && (ch + emoji->len)->unicode() == 0xFE0F) ? 1 : 0);
break;
}
}
@@ -330,7 +345,7 @@ void FlatTextarea::processDocumentContentsChange(int position, int charsAdded) {
}
if (emoji) {
QTextCursor c(doc->docHandle(), emojiPosition);
c.setPosition(emojiPosition + emoji->len, QTextCursor::KeepAnchor);
c.setPosition(emojiPosition + emojiLen, QTextCursor::KeepAnchor);
int32 removedUpto = c.position();
insertEmoji(emoji, c);
@@ -398,6 +413,17 @@ void FlatTextarea::onDocumentContentsChanged() {
emit changed();
}
updatePlaceholder();
if (App::wnd()) App::wnd()->updateGlobalMenu();
}
void FlatTextarea::onUndoAvailable(bool avail) {
_undoAvailable = avail;
if (App::wnd()) App::wnd()->updateGlobalMenu();
}
void FlatTextarea::onRedoAvailable(bool avail) {
_redoAvailable = avail;
if (App::wnd()) App::wnd()->updateGlobalMenu();
}
bool FlatTextarea::animStep(float64 ms) {

View File

@@ -51,6 +51,9 @@ public:
QString getText(int32 start = 0, int32 end = -1) const;
bool hasText() const;
bool isUndoAvailable() const;
bool isRedoAvailable() const;
public slots:
void onTouchTimer();
@@ -58,6 +61,9 @@ public slots:
void onDocumentContentsChange(int position, int charsRemoved, int charsAdded);
void onDocumentContentsChanged();
void onUndoAvailable(bool avail);
void onRedoAvailable(bool avail);
signals:
void changed();
@@ -82,6 +88,8 @@ private:
anim::cvalue a_phColor;
style::flatTextarea _st;
bool _undoAvailable, _redoAvailable;
int32 _fakeMargin;
QTimer _touchTimer;

View File

@@ -29,18 +29,9 @@ namespace {
return img;
}
typedef QMap<QByteArray, StorageImage*> StorageImages;
typedef QMap<StorageKey, StorageImage*> StorageImages;
StorageImages storageImages;
QByteArray storageKey(int32 dc, const int64 &volume, int32 local, const int64 &secret) {
QByteArray result(24, Qt::Uninitialized);
memcpy(result.data(), &dc, 4);
memcpy(result.data() + 4, &volume, 8);
memcpy(result.data() + 12, &local, 4);
memcpy(result.data() + 16, &secret, 8);
return result;
}
int64 globalAquiredSize = 0;
}
@@ -480,7 +471,7 @@ bool StorageImage::loaded() const {
}
StorageImage *getImage(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret, int32 size) {
QByteArray key(storageKey(dc, volume, local, secret));
StorageKey key(storageKey(dc, volume, local));
StorageImages::const_iterator i = storageImages.constFind(key);
if (i == storageImages.cend()) {
i = storageImages.insert(key, new StorageImage(width, height, dc, volume, local, secret, size));
@@ -489,7 +480,7 @@ StorageImage *getImage(int32 width, int32 height, int32 dc, const int64 &volume,
}
StorageImage *getImage(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret, const QByteArray &bytes) {
QByteArray key(storageKey(dc, volume, local, secret));
StorageKey key(storageKey(dc, volume, local));
StorageImages::const_iterator i = storageImages.constFind(key);
if (i == storageImages.cend()) {
QByteArray bytesArr(bytes);

View File

@@ -53,6 +53,13 @@ public:
void forget() const;
void restore() const;
QByteArray savedFormat() const {
return format;
}
QByteArray savedData() const {
return saved;
}
virtual ~Image() {
invalidateSizeCache();
}
@@ -106,6 +113,24 @@ private:
LocalImage *getImage(const QString &file);
LocalImage *getImage(const QPixmap &pixmap, QByteArray format);
typedef QPair<uint64, uint64> StorageKey;
inline uint64 storageMix32To64(int32 a, int32 b) {
return (uint64(*reinterpret_cast<uint32*>(&a)) << 32) | uint64(*reinterpret_cast<uint32*>(&b));
}
inline StorageKey storageKey(int32 dc, const int64 &volume, int32 local) {
return StorageKey(storageMix32To64(dc, local), volume);
}
inline StorageKey storageKey(const MTPDfileLocation &location) {
return storageKey(location.vdc_id.v, location.vvolume_id.v, location.vlocal_id.v);
}
struct StorageImageSaved {
StorageImageSaved() : type(mtpc_storage_fileUnknown) {
}
StorageImageSaved(mtpTypeId type, const QByteArray &data) : type(type), data(data) {
}
mtpTypeId type;
QByteArray data;
};
class StorageImage : public Image {
public:
@@ -123,7 +148,7 @@ public:
void load(bool loadFirst = false, bool prior = true) {
if (loader) {
loader->start(loadFirst, prior);
check();
if (loader) check();
}
}
void checkload() const {
@@ -131,7 +156,7 @@ public:
if (!loader->loading()) {
loader->start(true);
}
check();
if (loader) check();
}
}

View File

@@ -563,12 +563,6 @@ public:
ch = *ptr;
chInt = (chInt << 16) | 0x20E3;
}
} else if (ptr + 1 < end && (ptr + 1)->unicode() == 0xFE0F) { // check for 32bit not surrogate emoji
_t->_text.push_back(ch);
skipBack = -1;
++ptr;
ch = *ptr;
chInt = (chInt << 16) | 0xFE0F;
}
lastSkipped = skip;
@@ -602,8 +596,13 @@ public:
_t->_text.push_back(*++ptr);
}
}
int emojiLen = e->len;
if (ptr + 1 < end && (ptr + 1)->unicode() == 0xFE0F) {
_t->_text.push_back(*++ptr);
++emojiLen;
}
createBlock(-e->len);
createBlock(-emojiLen);
emoji = e;
}
@@ -3975,7 +3974,7 @@ QString textAccentFold(const QString &text) {
continue;
}
if (ch->isHighSurrogate() && ch + 1 < e && (ch + 1)->isLowSurrogate()) {
QChar noAccent = QChar::surrogateToUcs4(*ch, *(ch + 1));
QChar noAccent = chNoAccent(QChar::surrogateToUcs4(*ch, *(ch + 1)));
if (noAccent.unicode() > 0) {
copying = true;
result[i] = noAccent;
@@ -4045,18 +4044,30 @@ bool textSplit(QString &sendingText, QString &leftText, int32 limit) {
}
}
}
EmojiPtr e = 0;
if (ch->isHighSurrogate()) {
if (ch + 1 < end && (ch + 1)->isLowSurrogate()) {
++ch;
e = getEmoji((ch->unicode() << 16) | (ch + 1)->unicode());
if (!e) {
++ch;
}
}
} else {
if (ch + 1 < end && ((((ch->unicode() >= 48 && ch->unicode() < 58) || ch->unicode() == 35) && (ch + 1)->unicode() == 0x20E3) || (ch + 1)->unicode() == 0xFE0F)) {
if (getEmoji((ch->unicode() << 16) | (ch + 1)->unicode())) {
++ch;
++s;
if (ch + 1 < end) {
if (((ch->unicode() >= 48 && ch->unicode() < 58) || ch->unicode() == 35) && (ch + 1)->unicode() == 0x20E3) {
e = getEmoji((ch->unicode() << 16) | (ch + 1)->unicode());
} else if ((ch + 1)->unicode() == 0xFE0F) {
e = getEmoji(ch->unicode());
}
}
}
if (e) {
ch += (e->len - 1);
if (ch + 1 < end && (ch + 1)->unicode() == 0xFE0F) {
++ch;
++s;
}
}
if (s >= limit) {
sendingText = leftText.mid(0, good - start);
leftText = leftText.mid(good - start);

View File

@@ -27,6 +27,7 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "gui/filedialog.h"
#include "audio.h"
#include "localstorage.h"
TextParseOptions _textNameOptions = {
0, // flags
@@ -296,12 +297,13 @@ const ChatData *PeerData::asChat() const {
return chat ? static_cast<const ChatData *>(this) : App::chat(id | 0x100000000L);
}
void PeerData::updateName(const QString &newName, const QString &newNameOrPhone) {
if (name == newName && nameOrPhone == newNameOrPhone) return;
void PeerData::updateName(const QString &newName, const QString &newNameOrPhone, const QString &newUsername) {
if (name == newName && nameOrPhone == newNameOrPhone && (chat || asUser()->username == newUsername)) return;
++nameVersion;
name = newName;
nameOrPhone = newNameOrPhone;
if (!chat) asUser()->username = newUsername;
Names oldNames = names;
NameFirstChars oldChars = chars;
fillNames();
@@ -352,24 +354,23 @@ void PeerData::fillNames() {
void UserData::setName(const QString &first, const QString &last, const QString &phoneName, const QString &usern) {
bool updName = !first.isEmpty() || !last.isEmpty();
if (username != usern) {
username = usern;
if (App::main()) {
App::main()->peerUsernameChanged(this);
}
}
bool updName = !first.isEmpty() || !last.isEmpty(), updUsername = (username != usern);
if (updName && first.trimmed().isEmpty()) {
firstName = last;
lastName = QString();
updateName(firstName, phoneName);
updateName(firstName, phoneName, usern);
} else {
if (updName) {
firstName = first;
lastName = last;
}
updateName(firstName + ' ' + lastName, phoneName);
updateName(firstName + ' ' + lastName, phoneName, usern);
}
if (updUsername) {
if (App::main()) {
App::main()->peerUsernameChanged(this);
}
}
}
@@ -1168,7 +1169,7 @@ HistoryItem *History::createItem(HistoryBlock *block, const MTPmessage &msg, boo
case mtpc_messageActionChatEditTitle: {
const MTPDmessageActionChatEditTitle &d(action.c_messageActionChatEditTitle());
ChatData *chat = peer->asChat();
if (chat) chat->updateName(qs(d.vtitle), QString());
if (chat) chat->updateName(qs(d.vtitle), QString(), QString());
} break;
}
}
@@ -1755,7 +1756,7 @@ void History::clear(bool leaveItems) {
_overview[i].clear();
_overviewIds[i].clear();
}
if (App::wnd()) App::wnd()->mediaOverviewUpdated(peer);
if (App::wnd() && !App::quiting()) App::wnd()->mediaOverviewUpdated(peer);
for (Parent::const_iterator i = cbegin(), e = cend(); i != e; ++i) {
if (leaveItems) {
(*i)->clear(true);
@@ -2059,15 +2060,15 @@ HistoryItem *regItem(HistoryItem *item, bool returnExisting) {
return item;
}
HistoryPhoto::HistoryPhoto(const MTPDphoto &photo, int32 width) : data(App::feedPhoto(photo))
, openl(new PhotoLink(data))
, w(width) {
HistoryPhoto::HistoryPhoto(const MTPDphoto &photo, int32 width) : HistoryMedia(width)
, data(App::feedPhoto(photo))
, openl(new PhotoLink(data)) {
init();
}
HistoryPhoto::HistoryPhoto(PeerData *chat, const MTPDphoto &photo, int32 width) : data(App::feedPhoto(photo))
, openl(new PhotoLink(data, chat))
, w(width) {
HistoryPhoto::HistoryPhoto(PeerData *chat, const MTPDphoto &photo, int32 width) : HistoryMedia(width)
, data(App::feedPhoto(photo))
, openl(new PhotoLink(data, chat)) {
init();
}
@@ -2080,29 +2081,35 @@ void HistoryPhoto::initDimensions(const HistoryItem *parent) {
if (!tw || !th) {
tw = th = 1;
}
int32 thumbw = qMax(tw, int32(st::minPhotoWidth)), maxthumbh = thumbw;
int32 thumbh = qRound(th * float64(thumbw) / tw);
if (thumbh > maxthumbh) {
thumbw = qRound(thumbw * float64(maxthumbh) / thumbh);
thumbh = maxthumbh;
if (thumbw < st::minPhotoWidth) {
thumbw = st::minPhotoWidth;
}
}
if (thumbh < st::minPhotoHeight) {
thumbh = st::minPhotoHeight;
}
int32 thumbw = tw;
int32 thumbh = th;
if (!w) {
w = thumbw;
} else {
thumbh = w; // square chat photo updates
}
_maxw = w;
_height = _minh = thumbh;
if (_maxw < st::minPhotoWidth) {
_maxw = st::minPhotoWidth;
}
if (_height < st::minPhotoHeight) {
_height = st::minPhotoHeight;
}
}
int32 HistoryPhoto::resize(int32 width, bool dontRecountText, const HistoryItem *parent) {
w = width;
w = qMin(width, _maxw);
int32 tw = convertScale(data->full->width()), th = convertScale(data->full->height());
if (tw > st::maxMediaSize) {
th = (st::maxMediaSize * th) / tw;
tw = st::maxMediaSize;
}
if (th > st::maxMediaSize) {
tw = (st::maxMediaSize * tw) / th;
th = st::maxMediaSize;
}
_height = th;
if (tw > w) {
_height = (w * _height / tw);
@@ -2126,6 +2133,10 @@ const QString HistoryPhoto::inDialogsText() const {
return lang(lng_in_dlg_photo);
}
const QString HistoryPhoto::inHistoryText() const {
return qsl("[ ") + lang(lng_in_dlg_photo) + qsl(" ]");
}
bool HistoryPhoto::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const {
if (width < 0) width = w;
return (x >= 0 && y >= 0 && x < width && y < _height);
@@ -2143,6 +2154,40 @@ HistoryMedia *HistoryPhoto::clone() const {
return new HistoryPhoto(*this);
}
void HistoryPhoto::updateFrom(const MTPMessageMedia &media) {
if (media.type() == mtpc_messageMediaPhoto) {
const MTPPhoto &photo(media.c_messageMediaPhoto().vphoto);
if (photo.type() == mtpc_photo) {
const QVector<MTPPhotoSize> &sizes(photo.c_photo().vsizes.c_vector().v);
for (QVector<MTPPhotoSize>::const_iterator i = sizes.cbegin(), e = sizes.cend(); i != e; ++i) {
char size = 0;
const MTPFileLocation *loc = 0;
switch (i->type()) {
case mtpc_photoSize: {
const string &s(i->c_photoSize().vtype.c_string().v);
loc = &i->c_photoSize().vlocation;
if (s.size()) size = s[0];
} break;
case mtpc_photoCachedSize: {
const string &s(i->c_photoCachedSize().vtype.c_string().v);
loc = &i->c_photoSize().vlocation;
if (s.size()) size = s[0];
} break;
}
if (!loc || loc->type() != mtpc_fileLocation) continue;
if (size == 's') {
Local::writeImage(storageKey(loc->c_fileLocation()), data->thumb);
} else if (size == 'm') {
Local::writeImage(storageKey(loc->c_fileLocation()), data->medium);
} else if (size == 'x') {
Local::writeImage(storageKey(loc->c_fileLocation()), data->full);
}
}
}
}
}
void HistoryPhoto::draw(QPainter &p, const HistoryItem *parent, bool selected, int32 width) const {
if (width < 0) width = w;
data->full->load(false, false);
@@ -2254,11 +2299,11 @@ QString formatDurationAndSizeText(qint64 duration, qint64 size) {
int32 _downloadWidth = 0, _openWithWidth = 0, _cancelWidth = 0, _buttonWidth = 0;
HistoryVideo::HistoryVideo(const MTPDvideo &video, int32 width) : data(App::feedVideo(video))
HistoryVideo::HistoryVideo(const MTPDvideo &video, int32 width) : HistoryMedia(width)
, data(App::feedVideo(video))
, _openl(new VideoOpenLink(data))
, _savel(new VideoSaveLink(data))
, _cancell(new VideoCancelLink(data))
, w(width)
, _dldDone(0)
, _uplDone(0)
{
@@ -2304,15 +2349,14 @@ void HistoryVideo::unregItem(HistoryItem *item) {
App::unregVideoItem(data, item);
}
int32 HistoryVideo::resize(int32 width, bool dontRecountText, const HistoryItem *parent) {
w = width;
return _height;
}
const QString HistoryVideo::inDialogsText() const {
return lang(lng_in_dlg_video);
}
const QString HistoryVideo::inHistoryText() const {
return qsl("[ ") + lang(lng_in_dlg_video) + qsl(" ]");
}
bool HistoryVideo::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const {
if (width < 0) width = w;
if (width >= _maxw) {
@@ -2451,11 +2495,11 @@ void HistoryVideo::draw(QPainter &p, const HistoryItem *parent, bool selected, i
}
}
HistoryAudio::HistoryAudio(const MTPDaudio &audio, int32 width) : data(App::feedAudio(audio))
HistoryAudio::HistoryAudio(const MTPDaudio &audio, int32 width) : HistoryMedia(width)
, data(App::feedAudio(audio))
, _openl(new AudioOpenLink(data))
, _savel(new AudioSaveLink(data))
, _cancell(new AudioCancelLink(data))
, w(width)
, _dldDone(0)
, _uplDone(0)
{
@@ -2606,15 +2650,14 @@ void HistoryAudio::unregItem(HistoryItem *item) {
App::unregAudioItem(data, item);
}
int32 HistoryAudio::resize(int32 width, bool dontRecountText, const HistoryItem *parent) {
w = width;
return _height;
}
const QString HistoryAudio::inDialogsText() const {
return lang(lng_in_dlg_audio);
}
const QString HistoryAudio::inHistoryText() const {
return qsl("[ ") + lang(lng_in_dlg_audio) + qsl(" ]");
}
bool HistoryAudio::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const {
if (width < 0) width = w;
if (width >= _maxw) {
@@ -2650,11 +2693,11 @@ HistoryMedia *HistoryAudio::clone() const {
return new HistoryAudio(*this);
}
HistoryDocument::HistoryDocument(const MTPDdocument &document, int32 width) : data(App::feedDocument(document))
HistoryDocument::HistoryDocument(const MTPDdocument &document, int32 width) : HistoryMedia(width)
, data(App::feedDocument(document))
, _openl(new DocumentOpenLink(data))
, _savel(new DocumentSaveLink(data))
, _cancell(new DocumentCancelLink(data))
, w(width)
, _name(data->name)
, _dldDone(0)
, _uplDone(0)
@@ -2848,8 +2891,11 @@ void HistoryDocument::updateFrom(const MTPMessageMedia &media) {
}
int32 HistoryDocument::resize(int32 width, bool dontRecountText, const HistoryItem *parent) {
w = width;
w = qMin(width, _maxw);
if (parent == animated.msg) {
if (w > st::maxMediaSize) {
w = st::maxMediaSize;
}
_height = animated.h;
if (animated.w > w) {
_height = (w * _height / animated.w);
@@ -2863,6 +2909,10 @@ const QString HistoryDocument::inDialogsText() const {
return data->name.isEmpty() ? lang(lng_in_dlg_document) : data->name;
}
const QString HistoryDocument::inHistoryText() const {
return qsl("[ ") + lang(lng_in_dlg_document) + (data->name.isEmpty() ? QString() : (qsl(" : ") + data->name)) + qsl(" ]");
}
bool HistoryDocument::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const {
if (width < 0) width = w;
if (width >= _maxw) {
@@ -2921,8 +2971,8 @@ HistoryMedia *HistoryDocument::clone() const {
return new HistoryDocument(*this);
}
HistoryContact::HistoryContact(int32 userId, const QString &first, const QString &last, const QString &phone) : userId(userId)
, w(0)
HistoryContact::HistoryContact(int32 userId, const QString &first, const QString &last, const QString &phone) : HistoryMedia(0)
, userId(userId)
, phone(App::formatPhone(phone))
, contact(App::userLoaded(userId))
{
@@ -2955,7 +3005,7 @@ void HistoryContact::initDimensions(const HistoryItem *parent) {
}
int32 HistoryContact::resize(int32 width, bool dontRecountText, const HistoryItem *parent) {
w = width;
w = qMin(width, _maxw);
return _height;
}
@@ -2963,6 +3013,10 @@ const QString HistoryContact::inDialogsText() const {
return lang(lng_in_dlg_contact);
}
const QString HistoryContact::inHistoryText() const {
return qsl("[ ") + lang(lng_in_dlg_contact) + qsl(" : ") + name.original(0, 0xFFFF, false) + qsl(", ") + phone + qsl(" ]");
}
bool HistoryContact::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const {
if (width < 0) width = w;
return (x >= 0 && y <= 0 && x < w && y < _height);
@@ -3061,9 +3115,10 @@ void HistoryContact::updateFrom(const MTPMessageMedia &media) {
}
namespace {
QRegularExpression reYouTube1(qsl("^(https?://)?(www\\.)?youtube\\.com/watch\\?v=([a-z0-9_-]+)(&|$)"), QRegularExpression::CaseInsensitiveOption);
QRegularExpression reYouTube2(qsl("^(https?://)?(www\\.)?youtu\\.be/([a-z0-9_-]+)(\\?|$)"), QRegularExpression::CaseInsensitiveOption);
QRegularExpression reInstagram(qsl("^(https?://)?(www\\.)?instagram\\.com/p/([a-z0-9_-]+)(/|$)"), QRegularExpression::CaseInsensitiveOption);
QRegularExpression reYouTube1(qsl("^(https?://)?(www\\.|m\\.)?youtube\\.com/watch\\?([^#]+&)?v=([a-z0-9_-]+)(&[^\\s]*)?$"), QRegularExpression::CaseInsensitiveOption);
QRegularExpression reYouTube2(qsl("^(https?://)?(www\\.)?youtu\\.be/([a-z0-9_-]+)([/\\?][^\\s]*)?$"), QRegularExpression::CaseInsensitiveOption);
QRegularExpression reInstagram(qsl("^(https?://)?(www\\.)?instagram\\.com/p/([a-z0-9_-]+)([/\\?][^\\s]*)?$"), QRegularExpression::CaseInsensitiveOption);
QRegularExpression reVimeo(qsl("^(https?://)?(www\\.)?vimeo\\.com/([0-9]+)([/\\?][^\\s]*)?$"), QRegularExpression::CaseInsensitiveOption);
ImageLinkManager manager;
}
@@ -3074,7 +3129,7 @@ void ImageLinkManager::init() {
App::setProxySettings(*manager);
connect(manager, SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)), this, SLOT(onFailed(QNetworkReply*)));
connect(manager, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError>&errors)), this, SLOT(onFailed(QNetworkReply*)));
connect(manager, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError>&)), this, SLOT(onFailed(QNetworkReply*)));
connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(onFinished(QNetworkReply*)));
if (black) delete black;
@@ -3129,6 +3184,11 @@ void ImageLinkManager::getData(ImageLinkData *data) {
QNetworkReply *reply = manager->get(QNetworkRequest(QUrl(url)));
dataLoadings[reply] = data;
} break;
case VimeoLink: {
url = qsl("https://vimeo.com/api/v2/video/") + data->id.mid(6) + qsl(".json");
QNetworkReply *reply = manager->get(QNetworkRequest(QUrl(url)));
dataLoadings[reply] = data;
} break;
case InstagramLink: {
//url = qsl("https://api.instagram.com/oembed?url=http://instagr.am/p/") + data->id.mid(10) + '/';
url = qsl("https://instagram.com/p/") + data->id.mid(10) + qsl("/media/?size=l");
@@ -3208,9 +3268,9 @@ void ImageLinkManager::onFinished(QNetworkReply *reply) {
DEBUG_LOG(("JSON Error: Bad json received in onFinished() for image link"));
return onFailed(reply);
}
QJsonObject obj = doc.object();
switch (d->type) {
case YouTubeLink: {
QJsonObject obj = doc.object();
QString thumb;
int32 seconds = 0;
QJsonObject::const_iterator entryIt = obj.constFind(qsl("entry"));
@@ -3306,6 +3366,39 @@ void ImageLinkManager::onFinished(QNetworkReply *reply) {
imageLoadings.insert(manager->get(QNetworkRequest(thumb)), d);
}
} break;
case VimeoLink: {
QString thumb;
int32 seconds = 0;
QJsonArray arr = doc.array();
if (!arr.isEmpty()) {
QJsonObject obj = arr.at(0).toObject();
QJsonObject::const_iterator titleIt = obj.constFind(qsl("title"));
if (titleIt != obj.constEnd() && titleIt.value().isString()) {
d->title = titleIt.value().toString();
}
QJsonObject::const_iterator thumbnailsIt = obj.constFind(qsl("thumbnail_large"));
if (thumbnailsIt != obj.constEnd() && thumbnailsIt.value().isString()) {
thumb = thumbnailsIt.value().toString();
}
QJsonObject::const_iterator secondsIt = obj.constFind(qsl("duration"));
if (secondsIt != obj.constEnd()) {
if (secondsIt.value().isDouble()) {
seconds = qRound(secondsIt.value().toDouble());
} else if (secondsIt.value().isString()) {
seconds = qRound(secondsIt.value().toString().toDouble());
}
}
}
if (seconds > 0) {
d->duration = formatDurationText(seconds);
}
if (thumb.isEmpty()) {
failed(d);
} else {
imageLoadings.insert(manager->get(QNetworkRequest(thumb)), d);
}
} break;
case InstagramLink: failed(d); break;
case GoogleMapsLink: failed(d); break;
@@ -3372,20 +3465,35 @@ void ImageLinkData::load() {
manager.getData(this);
}
HistoryImageLink::HistoryImageLink(const QString &url, int32 width) : w(width) {
HistoryImageLink::HistoryImageLink(const QString &url, int32 width) : HistoryMedia(width) {
if (url.startsWith(qsl("location:"))) {
data = App::imageLink(url, GoogleMapsLink, qsl("https://maps.google.com/maps?q=") + url.mid(9) + qsl("&ll=") + url.mid(9) + qsl("&z=17"));
QString lnk = qsl("https://maps.google.com/maps?q=") + url.mid(9) + qsl("&ll=") + url.mid(9) + qsl("&z=17");
link.reset(new TextLink(lnk));
data = App::imageLink(url, GoogleMapsLink, lnk);
} else {
link.reset(new TextLink(url));
int matchIndex = 4;
QRegularExpressionMatch m = reYouTube1.match(url);
if (!m.hasMatch()) m = reYouTube2.match(url);
if (!m.hasMatch()) {
m = reYouTube2.match(url);
matchIndex = 3;
}
if (m.hasMatch()) {
data = App::imageLink(qsl("youtube:") + m.captured(3), YouTubeLink, url);
data = App::imageLink(qsl("youtube:") + m.captured(matchIndex), YouTubeLink, url);
} else {
m = reInstagram.match(url);
m = reVimeo.match(url);
if (m.hasMatch()) {
data = App::imageLink(qsl("instagram:") + m.captured(3), InstagramLink, url);
data = App::imageLink(qsl("vimeo:") + m.captured(3), VimeoLink, url);
} else {
data = 0;
m = reInstagram.match(url);
if (m.hasMatch()) {
data = App::imageLink(qsl("instagram:") + m.captured(3), InstagramLink, url);
data->title = qsl("instagram.com/p/") + m.captured(3);
} else {
data = 0;
}
}
}
}
@@ -3395,6 +3503,7 @@ int32 HistoryImageLink::fullWidth() const {
if (data) {
switch (data->type) {
case YouTubeLink: return 640;
case VimeoLink: return 640;
case InstagramLink: return 640;
case GoogleMapsLink: return st::locationSize.width();
}
@@ -3406,6 +3515,7 @@ int32 HistoryImageLink::fullHeight() const {
if (data) {
switch (data->type) {
case YouTubeLink: return 480;
case VimeoLink: return 480;
case InstagramLink: return 640;
case GoogleMapsLink: return st::locationSize.height();
}
@@ -3460,7 +3570,7 @@ void HistoryImageLink::draw(QPainter &p, const HistoryItem *parent, bool selecte
if (data) {
switch (data->type) {
case YouTubeLink: p.drawPixmap(QPoint((width - st::youtubeIcon.pxWidth()) / 2, (_height - st::youtubeIcon.pxHeight()) / 2), App::sprite(), st::youtubeIcon); break;
case InstagramLink: p.drawPixmap(QPoint((width - st::instagramIcon.pxWidth()) / 2, (_height - st::instagramIcon.pxHeight()) / 2), App::sprite(), st::instagramIcon); break;
case VimeoLink: p.drawPixmap(QPoint((width - st::youtubeIcon.pxWidth()) / 2, (_height - st::youtubeIcon.pxHeight()) / 2), App::sprite(), st::vimeoIcon); break;
}
if (!data->title.isEmpty() || !data->duration.isEmpty()) {
p.fillRect(0, 0, width, st::msgDateFont->height + 2 * st::msgDateImgPadding.y(), st::msgDateImgBg->b);
@@ -3515,9 +3625,13 @@ void HistoryImageLink::draw(QPainter &p, const HistoryItem *parent, bool selecte
}
int32 HistoryImageLink::resize(int32 width, bool dontRecountText, const HistoryItem *parent) {
w = width;
w = qMin(width, _maxw);
int32 tw = convertScale(fullWidth()), th = convertScale(fullHeight());
if (tw > st::maxMediaSize) {
th = (st::maxMediaSize * th) / tw;
tw = st::maxMediaSize;
}
_height = th;
if (tw > w) {
_height = (w * _height / tw);
@@ -3541,6 +3655,7 @@ const QString HistoryImageLink::inDialogsText() const {
if (data) {
switch (data->type) {
case YouTubeLink: return qsl("YouTube Video");
case VimeoLink: return qsl("Vimeo Video");
case InstagramLink: return qsl("Instagram Link");
case GoogleMapsLink: return lang(lng_maps_point);
}
@@ -3548,6 +3663,18 @@ const QString HistoryImageLink::inDialogsText() const {
return QString();
}
const QString HistoryImageLink::inHistoryText() const {
if (data) {
switch (data->type) {
case YouTubeLink: return qsl("[ YouTube Video : ") + link->text() + qsl(" ]");
case VimeoLink: return qsl("[ Vimeo Video : ") + link->text() + qsl(" ]");
case InstagramLink: return qsl("[ Instagram Link : ") + link->text() + qsl(" ]");
case GoogleMapsLink: return qsl("[ ") + lang(lng_maps_point) + qsl(" : ") + link->text() + qsl(" ]");
}
}
return qsl("[ Link : ") + link->text() + qsl(" ]");
}
bool HistoryImageLink::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const {
if (width < 0) width = w;
return (x >= 0 && y >= 0 && x < width && y < _height);
@@ -3556,7 +3683,7 @@ bool HistoryImageLink::hasPoint(int32 x, int32 y, const HistoryItem *parent, int
TextLinkPtr HistoryImageLink::getLink(int32 x, int32 y, const HistoryItem *parent, int32 width) const {
if (width < 0) width = w;
if (x >= 0 && y >= 0 && x < width && y < _height && data) {
return data->openl;
return link;
}
return TextLinkPtr();
}
@@ -3620,7 +3747,7 @@ void HistoryMessage::initMedia(const MTPMessageMedia &media, QString &currentTex
switch (media.type()) {
case mtpc_messageMediaEmpty: {
QString lnk = currentText.trimmed();
if (reYouTube1.match(currentText).hasMatch() || reYouTube2.match(currentText).hasMatch() || reInstagram.match(currentText).hasMatch()) {
if (reYouTube1.match(currentText).hasMatch() || reYouTube2.match(currentText).hasMatch() || reInstagram.match(currentText).hasMatch() || reVimeo.match(currentText).hasMatch()) {
_media = new HistoryImageLink(lnk);
currentText = QString();
}
@@ -3701,7 +3828,7 @@ bool HistoryMessage::uploading() const {
QString HistoryMessage::selectedText(uint32 selection) const {
if (_media && selection == FullItemSel) {
return _text.original(0, 0xFFFF) + '[' + _media->inDialogsText() + ']';
return _text.original(0, 0xFFFF) + _media->inHistoryText();
}
uint16 selectedFrom = (selection == FullItemSel) ? 0 : (selection >> 16) & 0xFFFF;
uint16 selectedTo = (selection == FullItemSel) ? 0xFFFF : (selection & 0xFFFF);
@@ -3736,7 +3863,10 @@ void HistoryMessage::draw(QPainter &p, uint32 selection) const {
_fromVersion = _from->nameVersion;
}
int32 left = _out ? st::msgMargin.right() : st::msgMargin.left(), width = _history->width - st::msgMargin.left() - st::msgMargin.right(), mwidth = st::msgMaxWidth;
if (_media && _media->maxWidth() > mwidth) mwidth = _media->maxWidth();
if (_media) {
if (_media->maxWidth() > mwidth) mwidth = _media->maxWidth();
if (_media->currentWidth() < mwidth) mwidth = _media->currentWidth();
}
if (width > mwidth) {
if (_out) left += width - mwidth;
width = mwidth;
@@ -3842,7 +3972,10 @@ int32 HistoryMessage::resize(int32 width, bool dontRecountText, const HistoryIte
bool HistoryMessage::hasPoint(int32 x, int32 y) const {
int32 left = _out ? st::msgMargin.right() : st::msgMargin.left(), width = _history->width - st::msgMargin.left() - st::msgMargin.right(), mwidth = st::msgMaxWidth;
if (_media && _media->maxWidth() > mwidth) mwidth = _media->maxWidth();
if (_media) {
if (_media->maxWidth() > mwidth) mwidth = _media->maxWidth();
if (_media->currentWidth() < mwidth) mwidth = _media->currentWidth();
}
if (width > mwidth) {
if (_out) left += width - mwidth;
width = mwidth;
@@ -3869,7 +4002,10 @@ void HistoryMessage::getState(TextLinkPtr &lnk, bool &inText, int32 x, int32 y)
lnk = TextLinkPtr();
int32 left = _out ? st::msgMargin.right() : st::msgMargin.left(), width = _history->width - st::msgMargin.left() - st::msgMargin.right(), mwidth = st::msgMaxWidth;
if (_media && _media->maxWidth() > mwidth) mwidth = _media->maxWidth();
if (_media) {
if (_media->maxWidth() > mwidth) mwidth = _media->maxWidth();
if (_media->currentWidth() < mwidth) mwidth = _media->currentWidth();
}
if (width > mwidth) {
if (_out) left += width - mwidth;
width = mwidth;

View File

@@ -82,7 +82,7 @@ struct PeerData {
ChatData *asChat();
const ChatData *asChat() const;
void updateName(const QString &newName, const QString &newNameOrPhone);
void updateName(const QString &newName, const QString &newNameOrPhone, const QString &newUsername);
void fillNames();
@@ -629,6 +629,34 @@ inline MTPMessagesFilter typeToMediaFilter(MediaOverviewType &type) {
return MTPMessagesFilter();
}
struct MessageCursor {
MessageCursor() : position(0), anchor(0), scroll(QFIXED_MAX) {
}
MessageCursor(int position, int anchor, int scroll) : position(position), anchor(anchor), scroll(scroll) {
}
MessageCursor(const QTextEdit &edit) {
fillFrom(edit);
}
void fillFrom(const QTextEdit &edit) {
QTextCursor c = edit.textCursor();
position = c.position();
anchor = c.anchor();
QScrollBar *s = edit.verticalScrollBar();
scroll = s ? s->value() : QFIXED_MAX;
}
void applyTo(QTextEdit &edit, bool *lock = 0) {
if (lock) *lock = true;
QTextCursor c = edit.textCursor();
c.setPosition(anchor, QTextCursor::MoveAnchor);
c.setPosition(position, QTextCursor::KeepAnchor);
edit.setTextCursor(c);
QScrollBar *s = edit.verticalScrollBar();
if (s) s->setValue(scroll);
if (lock) *lock = false;
}
int position, anchor, scroll;
};
class HistoryMedia;
class HistoryMessage;
class HistoryUnreadBar;
@@ -732,7 +760,7 @@ struct History : public QList<HistoryBlock*> {
}
QString draft;
QTextCursor draftCur;
MessageCursor draftCursor;
int32 lastWidth, lastScrollTop;
bool mute;
@@ -1218,12 +1246,20 @@ HistoryItem *regItem(HistoryItem *item, bool returnExisting = false);
class HistoryMedia : public HistoryElem {
public:
HistoryMedia(int32 width = 0) : w(width) {
}
virtual HistoryMediaType type() const = 0;
virtual const QString inDialogsText() const = 0;
virtual const QString inHistoryText() const = 0;
virtual bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const = 0;
virtual int32 countHeight(const HistoryItem *parent, int32 width = -1) const {
return height();
}
virtual int32 resize(int32 width, bool dontRecountText = false, const HistoryItem *parent = 0) {
w = qMin(width, _maxw);
return _height;
}
virtual TextLinkPtr getLink(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const = 0;
virtual void draw(QPainter &p, const HistoryItem *parent, bool selected, int32 width = -1) const = 0;
virtual bool uploading() const {
@@ -1244,6 +1280,14 @@ public:
return false;
}
int32 currentWidth() const {
return w;
}
protected:
int32 w;
};
class HistoryPhoto : public HistoryMedia {
@@ -1261,6 +1305,7 @@ public:
return MediaTypePhoto;
}
const QString inDialogsText() const;
const QString inHistoryText() const;
bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
TextLinkPtr getLink(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
HistoryMedia *clone() const;
@@ -1269,6 +1314,8 @@ public:
return data;
}
void updateFrom(const MTPMessageMedia &media);
TextLinkPtr lnk() const {
return openl;
}
@@ -1281,7 +1328,7 @@ public:
private:
PhotoData *data;
TextLinkPtr openl;
int32 w;
};
QString formatSizeText(qint64 size);
@@ -1293,11 +1340,11 @@ public:
void initDimensions(const HistoryItem *parent);
void draw(QPainter &p, const HistoryItem *parent, bool selected, int32 width = -1) const;
int32 resize(int32 width, bool dontRecountText = false, const HistoryItem *parent = 0);
HistoryMediaType type() const {
return MediaTypeVideo;
}
const QString inDialogsText() const;
const QString inHistoryText() const;
bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
TextLinkPtr getLink(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
bool uploading() const {
@@ -1311,8 +1358,7 @@ public:
private:
VideoData *data;
TextLinkPtr _openl, _savel, _cancell;
int32 w;
QString _size;
int32 _thumbw, _thumbx, _thumby;
@@ -1327,11 +1373,11 @@ public:
void initDimensions(const HistoryItem *parent);
void draw(QPainter &p, const HistoryItem *parent, bool selected, int32 width = -1) const;
int32 resize(int32 width, bool dontRecountText = false, const HistoryItem *parent = 0);
HistoryMediaType type() const {
return MediaTypeAudio;
}
const QString inDialogsText() const;
const QString inHistoryText() const;
bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
TextLinkPtr getLink(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
bool uploading() const {
@@ -1345,7 +1391,6 @@ public:
private:
AudioData *data;
TextLinkPtr _openl, _savel, _cancell;
int32 w;
QString _size;
@@ -1365,6 +1410,7 @@ public:
return MediaTypeDocument;
}
const QString inDialogsText() const;
const QString inHistoryText() const;
bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
int32 countHeight(const HistoryItem *parent, int32 width = -1) const;
bool uploading() const {
@@ -1386,7 +1432,6 @@ private:
DocumentData *data;
TextLinkPtr _openl, _savel, _cancell;
int32 w;
int32 _namew;
QString _name, _size;
@@ -1408,6 +1453,7 @@ public:
return MediaTypeContact;
}
const QString inDialogsText() const;
const QString inHistoryText() const;
bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const;
TextLinkPtr getLink(int32 x, int32 y, const HistoryItem *parent, int32 width) const;
HistoryMedia *clone() const;
@@ -1416,7 +1462,7 @@ public:
private:
int32 userId;
int32 w, phonew;
int32 phonew;
Text name;
QString phone;
UserData *contact;
@@ -1429,6 +1475,7 @@ void deinitImageLinkManager();
enum ImageLinkType {
InvalidImageLink = 0,
YouTubeLink,
VimeoLink,
InstagramLink,
GoogleMapsLink
};
@@ -1439,7 +1486,6 @@ struct ImageLinkData {
QString id;
QString title, duration;
ImagePtr thumb;
TextLinkPtr openl;
ImageLinkType type;
bool loading;
@@ -1488,13 +1534,15 @@ public:
return MediaTypeImageLink;
}
const QString inDialogsText() const;
const QString inHistoryText() const;
bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
TextLinkPtr getLink(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
HistoryMedia *clone() const;
private:
ImageLinkData *data;
int32 w;
TextLinkPtr link;
};
class HistoryMessage : public HistoryItem {

View File

@@ -28,6 +28,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "fileuploader.h"
#include "supporttl.h"
#include "localstorage.h"
// flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html
HistoryList::HistoryList(HistoryWidget *historyWidget, ScrollArea *scroll, History *history) : QWidget(0)
@@ -633,106 +635,98 @@ void HistoryList::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}
_contextMenuLnk = textlnkOver();
if (_contextMenuLnk && dynamic_cast<TextLink*>(_contextMenuLnk.data())) {
PhotoLink *lnkPhoto = dynamic_cast<PhotoLink*>(_contextMenuLnk.data());
VideoLink *lnkVideo = dynamic_cast<VideoLink*>(_contextMenuLnk.data());
AudioLink *lnkAudio = dynamic_cast<AudioLink*>(_contextMenuLnk.data());
DocumentLink *lnkDocument = dynamic_cast<DocumentLink*>(_contextMenuLnk.data());
if (lnkPhoto || lnkVideo || lnkAudio || lnkDocument) {
_menu = new ContextMenu(historyWidget);
if (isUponSelected > 0) {
_menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true);
}
_menu->addAction(lang(lng_context_open_link), this, SLOT(openContextUrl()))->setEnabled(true);
_menu->addAction(lang(lng_context_copy_link), this, SLOT(copyContextUrl()))->setEnabled(true);
} else if (_contextMenuLnk && dynamic_cast<EmailLink*>(_contextMenuLnk.data())) {
_menu = new ContextMenu(historyWidget);
if (isUponSelected > 0) {
_menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true);
}
_menu->addAction(lang(lng_context_open_email), this, SLOT(openContextUrl()))->setEnabled(true);
_menu->addAction(lang(lng_context_copy_email), this, SLOT(copyContextUrl()))->setEnabled(true);
} else if (_contextMenuLnk && dynamic_cast<HashtagLink*>(_contextMenuLnk.data())) {
_menu = new ContextMenu(historyWidget);
if (isUponSelected > 0) {
_menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true);
}
_menu->addAction(lang(lng_context_open_hashtag), this, SLOT(openContextUrl()))->setEnabled(true);
_menu->addAction(lang(lng_context_copy_hashtag), this, SLOT(copyContextUrl()))->setEnabled(true);
} else {
PhotoLink *lnkPhoto = dynamic_cast<PhotoLink*>(_contextMenuLnk.data());
VideoLink *lnkVideo = dynamic_cast<VideoLink*>(_contextMenuLnk.data());
AudioLink *lnkAudio = dynamic_cast<AudioLink*>(_contextMenuLnk.data());
DocumentLink *lnkDocument = dynamic_cast<DocumentLink*>(_contextMenuLnk.data());
if (lnkPhoto || lnkVideo || lnkAudio || lnkDocument) {
_menu = new ContextMenu(historyWidget);
if (isUponSelected > 0) {
_menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true);
}
if (lnkPhoto) {
_menu->addAction(lang(lng_context_open_image), this, SLOT(openContextUrl()))->setEnabled(true);
_menu->addAction(lang(lng_context_save_image), this, SLOT(saveContextImage()))->setEnabled(true);
_menu->addAction(lang(lng_context_copy_image), this, SLOT(copyContextImage()))->setEnabled(true);
if (lnkPhoto) {
_menu->addAction(lang(lng_context_open_image), this, SLOT(openContextUrl()))->setEnabled(true);
_menu->addAction(lang(lng_context_save_image), this, SLOT(saveContextImage()))->setEnabled(true);
_menu->addAction(lang(lng_context_copy_image), this, SLOT(copyContextImage()))->setEnabled(true);
} else {
if ((lnkVideo && lnkVideo->video()->loader) || (lnkAudio && lnkAudio->audio()->loader) || (lnkDocument && lnkDocument->document()->loader)) {
_menu->addAction(lang(lng_context_cancel_download), this, SLOT(cancelContextDownload()))->setEnabled(true);
} else {
if ((lnkVideo && lnkVideo->video()->loader) || (lnkAudio && lnkAudio->audio()->loader) || (lnkDocument && lnkDocument->document()->loader)) {
_menu->addAction(lang(lng_context_cancel_download), this, SLOT(cancelContextDownload()))->setEnabled(true);
} else {
if ((lnkVideo && !lnkVideo->video()->already(true).isEmpty()) || (lnkAudio && !lnkAudio->audio()->already(true).isEmpty()) || (lnkDocument && !lnkDocument->document()->already(true).isEmpty())) {
_menu->addAction(lang(cPlatform() == dbipMac ? lng_context_show_in_finder : lng_context_show_in_folder), this, SLOT(showContextInFolder()))->setEnabled(true);
}
_menu->addAction(lang(lnkVideo ? lng_context_open_video : (lnkAudio ? lng_context_open_audio : lng_context_open_document)), this, SLOT(openContextFile()))->setEnabled(true);
_menu->addAction(lang(lnkVideo ? lng_context_save_video : (lnkAudio ? lng_context_save_audio : lng_context_save_document)), this, SLOT(saveContextFile()))->setEnabled(true);
if ((lnkVideo && !lnkVideo->video()->already(true).isEmpty()) || (lnkAudio && !lnkAudio->audio()->already(true).isEmpty()) || (lnkDocument && !lnkDocument->document()->already(true).isEmpty())) {
_menu->addAction(lang(cPlatform() == dbipMac ? lng_context_show_in_finder : lng_context_show_in_folder), this, SLOT(showContextInFolder()))->setEnabled(true);
}
_menu->addAction(lang(lnkVideo ? lng_context_open_video : (lnkAudio ? lng_context_open_audio : lng_context_open_document)), this, SLOT(openContextFile()))->setEnabled(true);
_menu->addAction(lang(lnkVideo ? lng_context_save_video : (lnkAudio ? lng_context_save_audio : lng_context_save_document)), this, SLOT(saveContextFile()))->setEnabled(true);
}
if (isUponSelected > 1) {
_menu->addAction(lang(lng_context_forward_selected), historyWidget, SLOT(onForwardSelected()));
_menu->addAction(lang(lng_context_delete_selected), historyWidget, SLOT(onDeleteSelected()));
_menu->addAction(lang(lng_context_clear_selection), historyWidget, SLOT(onClearSelected()));
} else if (App::hoveredLinkItem()) {
if (isUponSelected != -2) {
if (dynamic_cast<HistoryMessage*>(App::hoveredLinkItem())) {
_menu->addAction(lang(lng_context_forward_msg), historyWidget, SLOT(forwardMessage()))->setEnabled(true);
}
_menu->addAction(lang(lng_context_delete_msg), historyWidget, SLOT(deleteMessage()))->setEnabled(true);
}
_menu->addAction(lang(lng_context_select_msg), historyWidget, SLOT(selectMessage()))->setEnabled(true);
App::contextItem(App::hoveredLinkItem());
}
} else { // maybe cursor on some text history item?
HistoryItem *item = App::hoveredItem() ? App::hoveredItem() : App::hoveredLinkItem();
bool canDelete = (item && item->itemType() == HistoryItem::MsgType);
bool canForward = canDelete && (item->id > 0) && !item->serviceMsg();
HistoryMessage *msg = dynamic_cast<HistoryMessage*>(item);
HistoryServiceMsg *srv = dynamic_cast<HistoryServiceMsg*>(item);
if (isUponSelected > 0) {
if (!_menu) _menu = new ContextMenu(this);
_menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true);
} else if (item && !isUponSelected && !_contextMenuLnk) {
QString contextMenuText = item->selectedText(FullItemSel);
if (!contextMenuText.isEmpty()) {
if (!_menu) _menu = new ContextMenu(this);
_menu->addAction(lang(lng_context_copy_text), this, SLOT(copyContextText()))->setEnabled(true);
}
}
if (isUponSelected > 1) {
if (!_menu) _menu = new ContextMenu(this);
_menu->addAction(lang(lng_context_forward_selected), historyWidget, SLOT(onForwardSelected()));
_menu->addAction(lang(lng_context_delete_selected), historyWidget, SLOT(onDeleteSelected()));
_menu->addAction(lang(lng_context_clear_selection), historyWidget, SLOT(onClearSelected()));
} else {
if (!_menu) _menu = new ContextMenu(this);
if (isUponSelected != -2) {
if (canForward) {
_menu->addAction(lang(lng_context_forward_msg), historyWidget, SLOT(forwardMessage()))->setEnabled(true);
}
if (canDelete) {
_menu->addAction(lang((msg && msg->uploading()) ? lng_context_cancel_upload : lng_context_delete_msg), historyWidget, SLOT(deleteMessage()))->setEnabled(true);
}
}
_menu->addAction(lang(lng_context_select_msg), historyWidget, SLOT(selectMessage()))->setEnabled(true);
}
App::contextItem(item);
}
if (isUponSelected > 1) {
_menu->addAction(lang(lng_context_forward_selected), historyWidget, SLOT(onForwardSelected()));
_menu->addAction(lang(lng_context_delete_selected), historyWidget, SLOT(onDeleteSelected()));
_menu->addAction(lang(lng_context_clear_selection), historyWidget, SLOT(onClearSelected()));
} else if (App::hoveredLinkItem()) {
if (isUponSelected != -2) {
if (dynamic_cast<HistoryMessage*>(App::hoveredLinkItem())) {
_menu->addAction(lang(lng_context_forward_msg), historyWidget, SLOT(forwardMessage()))->setEnabled(true);
}
_menu->addAction(lang(lng_context_delete_msg), historyWidget, SLOT(deleteMessage()))->setEnabled(true);
}
_menu->addAction(lang(lng_context_select_msg), historyWidget, SLOT(selectMessage()))->setEnabled(true);
App::contextItem(App::hoveredLinkItem());
}
} else { // maybe cursor on some text history item?
HistoryItem *item = App::hoveredItem() ? App::hoveredItem() : App::hoveredLinkItem();
bool canDelete = (item && item->itemType() == HistoryItem::MsgType);
bool canForward = canDelete && (item->id > 0) && !item->serviceMsg();
HistoryMessage *msg = dynamic_cast<HistoryMessage*>(item);
HistoryServiceMsg *srv = dynamic_cast<HistoryServiceMsg*>(item);
if (isUponSelected > 0) {
if (!_menu) _menu = new ContextMenu(this);
_menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true);
} else if (item && !isUponSelected && !_contextMenuLnk) {
QString contextMenuText = item->selectedText(FullItemSel);
if (!contextMenuText.isEmpty()) {
if (!_menu) _menu = new ContextMenu(this);
_menu->addAction(lang(lng_context_copy_text), this, SLOT(copyContextText()))->setEnabled(true);
}
}
if (_contextMenuLnk && dynamic_cast<TextLink*>(_contextMenuLnk.data())) {
if (!_menu) _menu = new ContextMenu(historyWidget);
_menu->addAction(lang(lng_context_open_link), this, SLOT(openContextUrl()))->setEnabled(true);
_menu->addAction(lang(lng_context_copy_link), this, SLOT(copyContextUrl()))->setEnabled(true);
} else if (_contextMenuLnk && dynamic_cast<EmailLink*>(_contextMenuLnk.data())) {
if (!_menu) _menu = new ContextMenu(historyWidget);
_menu->addAction(lang(lng_context_open_email), this, SLOT(openContextUrl()))->setEnabled(true);
_menu->addAction(lang(lng_context_copy_email), this, SLOT(copyContextUrl()))->setEnabled(true);
} else if (_contextMenuLnk && dynamic_cast<HashtagLink*>(_contextMenuLnk.data())) {
if (!_menu) _menu = new ContextMenu(historyWidget);
_menu->addAction(lang(lng_context_open_hashtag), this, SLOT(openContextUrl()))->setEnabled(true);
_menu->addAction(lang(lng_context_copy_hashtag), this, SLOT(copyContextUrl()))->setEnabled(true);
} else {
}
if (isUponSelected > 1) {
if (!_menu) _menu = new ContextMenu(this);
_menu->addAction(lang(lng_context_forward_selected), historyWidget, SLOT(onForwardSelected()));
_menu->addAction(lang(lng_context_delete_selected), historyWidget, SLOT(onDeleteSelected()));
_menu->addAction(lang(lng_context_clear_selection), historyWidget, SLOT(onClearSelected()));
} else {
if (!_menu) _menu = new ContextMenu(this);
if (isUponSelected != -2) {
if (canForward) {
_menu->addAction(lang(lng_context_forward_msg), historyWidget, SLOT(forwardMessage()))->setEnabled(true);
}
if (canDelete) {
_menu->addAction(lang((msg && msg->uploading()) ? lng_context_cancel_upload : lng_context_delete_msg), historyWidget, SLOT(deleteMessage()))->setEnabled(true);
}
}
_menu->addAction(lang(lng_context_select_msg), historyWidget, SLOT(selectMessage()))->setEnabled(true);
}
App::contextItem(item);
}
if (_menu) {
_menu->deleteOnHide();
connect(_menu, SIGNAL(destroyed(QObject*)), this, SLOT(onMenuDestroy(QObject*)));
@@ -976,6 +970,14 @@ HistoryItem *HistoryList::nextItem(HistoryItem *item) {
return 0;
}
bool HistoryList::canCopySelected() const {
return !_selected.isEmpty();
}
bool HistoryList::canDeleteSelected() const {
return !_selected.isEmpty() && (_selected.cbegin().value() == FullItemSel);
}
void HistoryList::getSelectionState(int32 &selectedForForward, int32 &selectedForDelete) const {
selectedForForward = selectedForDelete = 0;
for (SelectedItems::const_iterator i = _selected.cbegin(), e = _selected.cend(); i != e; ++i) {
@@ -1527,7 +1529,7 @@ HistoryWidget::HistoryWidget(QWidget *parent) : QWidget(parent)
, _attachDragDocument(this)
, _attachDragPhoto(this)
, imageLoader(this)
, noTypingUpdate(false)
, _synthedTextUpdate(false)
, loadingChatId(0)
, loadingRequestId(0)
, serviceImageCacheSize(0)
@@ -1537,7 +1539,9 @@ HistoryWidget::HistoryWidget(QWidget *parent) : QWidget(parent)
, bg(st::msgBG)
, hiderOffered(false)
, _scrollDelta(0)
, _typingRequest(0) {
, _typingRequest(0)
, _saveDraftStart(0)
, _saveDraftText(false) {
_scroll.setFocusPolicy(Qt::NoFocus);
setAcceptDrops(true);
@@ -1548,7 +1552,7 @@ HistoryWidget::HistoryWidget(QWidget *parent) : QWidget(parent)
connect(&_attachDocument, SIGNAL(clicked()), this, SLOT(onDocumentSelect()));
connect(&_attachPhoto, SIGNAL(clicked()), this, SLOT(onPhotoSelect()));
connect(&_field, SIGNAL(submitted(bool)), this, SLOT(onSend(bool)));
connect(&_field, SIGNAL(cancelled()), this, SIGNAL(cancelled()));
connect(&_field, SIGNAL(cancelled()), this, SLOT(onCancel()));
connect(&_field, SIGNAL(tabbed()), this, SLOT(onFieldTabbed()));
connect(&_field, SIGNAL(resized()), this, SLOT(onFieldResize()));
connect(&_field, SIGNAL(focused()), this, SLOT(onFieldFocused()));
@@ -1567,6 +1571,11 @@ HistoryWidget::HistoryWidget(QWidget *parent) : QWidget(parent)
_animActiveTimer.setSingleShot(false);
connect(&_animActiveTimer, SIGNAL(timeout()), this, SLOT(onAnimActiveStep()));
_saveDraftTimer.setSingleShot(true);
connect(&_saveDraftTimer, SIGNAL(timeout()), this, SLOT(onDraftSave()));
connect(_field.verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(onDraftSaveDelayed()));
connect(&_field, SIGNAL(cursorPositionChanged()), this, SLOT(onDraftSaveDelayed()));
_scroll.hide();
_scroll.move(0, 0);
@@ -1597,6 +1606,43 @@ HistoryWidget::HistoryWidget(QWidget *parent) : QWidget(parent)
void HistoryWidget::onTextChange() {
updateTyping();
if (!hist || _synthedTextUpdate) return;
_saveDraftText = true;
onDraftSave(true);
}
void HistoryWidget::onDraftSaveDelayed() {
if (!hist || _synthedTextUpdate) return;
if (!_field.textCursor().anchor() && !_field.textCursor().position() && !_field.verticalScrollBar()->value()) {
if (!Local::hasDraftPositions(hist->peer->id)) return;
}
onDraftSave(true);
}
void HistoryWidget::onDraftSave(bool delayed) {
if (!hist) return;
if (delayed) {
uint64 ms = getms();
if (!_saveDraftStart) {
_saveDraftStart = ms;
return _saveDraftTimer.start(SaveDraftTimeout);
} else if (ms - _saveDraftStart < SaveDraftAnywayTimeout) {
return _saveDraftTimer.start(SaveDraftTimeout);
}
}
writeDraft();
}
void HistoryWidget::writeDraft(const QString *text, const MessageCursor *cursor) {
bool save = hist && (_saveDraftStart > 0);
_saveDraftStart = 0;
_saveDraftTimer.stop();
if (_saveDraftText) {
if (save) Local::writeDraft(hist->peer->id, text ? (*text) : _field.getText());
_saveDraftText = false;
}
if (save) Local::writeDraftPositions(hist->peer->id, cursor ? (*cursor) : MessageCursor(_field));
}
void HistoryWidget::cancelTyping() {
@@ -1608,7 +1654,7 @@ void HistoryWidget::cancelTyping() {
void HistoryWidget::updateTyping(bool typing) {
uint64 ms = getms(true) + 10000;
if (noTypingUpdate || !hist || (typing && (hist->myTyping + 5000 > ms)) || (!typing && (hist->myTyping + 5000 <= ms))) return;
if (_synthedTextUpdate || !hist || (typing && (hist->myTyping + 5000 > ms)) || (!typing && (hist->myTyping + 5000 <= ms))) return;
hist->myTyping = typing ? ms : 0;
cancelTyping();
@@ -1717,7 +1763,10 @@ void HistoryWidget::showPeer(const PeerId &peer, MsgId msgId, bool force, bool l
}
if (hist) {
hist->draft = _field.getText();
hist->draftCur = _field.textCursor();
hist->draftCursor.fillFrom(_field);
writeDraft(&hist->draft, &hist->draftCursor);
if (hist->readyForWork() && _scroll.scrollTop() + 1 <= _scroll.scrollTopMax()) {
hist->lastWidth = _list->width();
} else {
@@ -1795,13 +1844,19 @@ void HistoryWidget::showPeer(const PeerId &peer, MsgId msgId, bool force, bool l
App::main()->peerUpdated(histPeer);
noTypingUpdate = true;
setFieldText(hist->draft);
_field.setFocus();
if (!hist->draft.isEmpty()) {
_field.setTextCursor(hist->draftCur);
setFieldText(hist->draft);
_field.setFocus();
hist->draftCursor.applyTo(_field, &_synthedTextUpdate);
} else {
QString draft = Local::readDraft(hist->peer->id);
setFieldText(draft);
_field.setFocus();
if (!draft.isEmpty()) {
MessageCursor cur = Local::readDraftPositions(hist->peer->id);
cur.applyTo(_field, &_synthedTextUpdate);
}
}
noTypingUpdate = false;
connect(&_scroll, SIGNAL(geometryChanged()), _list, SLOT(onParentGeometryChanged()));
connect(&_scroll, SIGNAL(scrolled()), _list, SLOT(onUpdateSelected()));
@@ -2215,6 +2270,10 @@ void HistoryWidget::onSend(bool ctrlShiftEnter) {
App::main()->sendPreparedText(hist, prepareMessage(_field.getText()));
setFieldText(QString());
_saveDraftText = true;
_saveDraftStart = getms();
onDraftSave();
if (!_attachType.isHidden()) _attachType.hideStart();
if (!_emojiPan.isHidden()) _emojiPan.hideStart();
}
@@ -2680,7 +2739,7 @@ void HistoryWidget::updateOnlineDisplay(int32 x, int32 w) {
}
}
} else {
text = App::onlineText(histPeer->asUser()->onlineTill, t);
text = App::onlineText(histPeer->asUser(), t);
}
if (titlePeerText != text) {
titlePeerText = text;
@@ -3156,9 +3215,14 @@ void HistoryWidget::onFieldTabbed() {
}
void HistoryWidget::setFieldText(const QString &text) {
noTypingUpdate = true;
_synthedTextUpdate = true;
_field.setPlainText(text);
noTypingUpdate = false;
_synthedTextUpdate = false;
}
void HistoryWidget::onCancel() {
showPeer(0);
emit cancelled();
}
void HistoryWidget::peerUpdated(PeerData *data) {

View File

@@ -68,6 +68,9 @@ public:
void updateMsg(const HistoryItem *msg);
bool canCopySelected() const;
bool canDeleteSelected() const;
void getSelectionState(int32 &selectedForForward, int32 &selectedForDelete) const;
void clearSelectedItems(bool onlyTextSelection = false);
void fillSelectedItems(SelectedItemSet &sel, bool forDelete = true);
@@ -343,6 +346,8 @@ signals:
public slots:
void onCancel();
void peerUpdated(PeerData *data);
void cancelTyping();
@@ -391,6 +396,9 @@ public slots:
void onAnimActiveStep();
void onDraftSaveDelayed();
void onDraftSave(bool delayed = false);
private:
bool messagesFailed(const RPCError &error, mtpRequestId requestId);
@@ -399,6 +407,7 @@ private:
void addMessagesToBack(const QVector<MTPMessage> &messages);
void chatLoaded(const MTPmessages_ChatFull &res);
void writeDraft(const QString *text = 0, const MessageCursor *cursor = 0);
void setFieldText(const QString &text);
QStringList getMediasFromMime(const QMimeData *d);
@@ -435,7 +444,7 @@ private:
int32 _selCount; // < 0 - text selected, focus list, not _field
LocalImageLoader imageLoader;
bool noTypingUpdate;
bool _synthedTextUpdate;
PeerId loadingChatId;
mtpRequestId loadingRequestId;
@@ -465,5 +474,9 @@ private:
mtpRequestId _typingRequest;
QTimer _typingStopTimer;
uint64 _saveDraftStart;
bool _saveDraftText;
QTimer _saveDraftTimer;
};

View File

@@ -0,0 +1,988 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop 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://tdesktop.com
*/
#include "stdafx.h"
#include "localstorage.h"
namespace {
typedef quint64 FileKey;
static const char tdfMagic[] = { 'T', 'D', 'F', '$' };
static const int32 tdfMagicLen = sizeof(tdfMagic);
QString toFilePart(FileKey val) {
QString result;
result.reserve(0x10);
for (int32 i = 0; i < 0x10; ++i) {
uchar v = (val & 0x0F);
result.push_back((v < 0x0A) ? ('0' + v) : ('A' + (v - 0x0A)));
val >>= 4;
}
return result;
}
FileKey fromFilePart(const QString &val) {
FileKey result = 0;
int32 i = val.size();
if (i != 0x10) return 0;
while (i > 0) {
--i;
result <<= 4;
uint16 ch = val.at(i).unicode();
if (ch >= 'A' && ch <= 'F') {
result |= (ch - 'A') + 0x0A;
} else if (ch >= '0' && ch <= '9') {
result |= (ch - '0');
} else {
return 0;
}
}
return result;
}
QString _basePath;
bool _started = false;
_local_inner::Manager *_manager = 0;
bool _working() {
return _manager && !_basePath.isEmpty();
}
bool keyAlreadyUsed(QString &name) {
name += '0';
if (QFileInfo(name).exists()) return true;
name[name.size() - 1] = '1';
return QFileInfo(name).exists();
}
FileKey genKey() {
if (!_working()) return 0;
FileKey result;
QString path;
path.reserve(_basePath.size() + 0x11);
path += _basePath;
do {
result = MTP::nonce<FileKey>();
path.resize(_basePath.size());
path += toFilePart(result);
} while (keyAlreadyUsed(path));
return result;
}
void clearKey(const FileKey &key, bool safe = true) {
if (!_working()) return;
QString name;
name.reserve(_basePath.size() + 0x11);
name += _basePath;
name += toFilePart(key);
name += '0';
QFile::remove(name);
if (safe) {
name[name.size() - 1] = '1';
QFile::remove(name);
}
}
QByteArray _passKeySalt, _passKeyEncrypted;
mtpAuthKey _oldKey, _passKey, _localKey;
void createLocalKey(const QByteArray &pass, QByteArray *salt, mtpAuthKey *result) {
uchar key[LocalEncryptKeySize] = { 0 };
int32 iterCount = pass.size() ? LocalEncryptIterCount : LocalEncryptNoPwdIterCount; // dont slow down for no password
QByteArray newSalt;
if (!salt) {
newSalt.resize(LocalEncryptSaltSize);
memset_rand(newSalt.data(), newSalt.size());
salt = &newSalt;
cSetLocalSalt(newSalt);
}
PKCS5_PBKDF2_HMAC_SHA1(pass.constData(), pass.size(), (uchar*)salt->data(), salt->size(), iterCount, LocalEncryptKeySize, key);
result->setKey(key);
}
struct FileReadDescriptor {
FileReadDescriptor() : version(0) {
}
int32 version;
QByteArray data;
QBuffer buffer;
QDataStream stream;
~FileReadDescriptor() {
if (version) {
stream.setDevice(0);
if (buffer.isOpen()) buffer.close();
buffer.setBuffer(0);
}
}
};
struct EncryptedDescriptor {
EncryptedDescriptor() {
}
EncryptedDescriptor(uint32 size) {
uint32 fullSize = sizeof(uint32) + size;
if (fullSize & 0x0F) fullSize += 0x10 - (fullSize & 0x0F);
data.reserve(fullSize);
data.resize(sizeof(uint32));
buffer.setBuffer(&data);
buffer.open(QIODevice::WriteOnly);
buffer.seek(sizeof(uint32));
stream.setDevice(&buffer);
stream.setVersion(QDataStream::Qt_5_1);
}
QByteArray data;
QBuffer buffer;
QDataStream stream;
void finish() {
if (stream.device()) stream.setDevice(0);
if (buffer.isOpen()) buffer.close();
buffer.setBuffer(0);
}
~EncryptedDescriptor() {
finish();
}
};
struct FileWriteDescriptor {
FileWriteDescriptor(const FileKey &key, bool safe = true) : dataSize(0) {
init(toFilePart(key), safe);
}
FileWriteDescriptor(const QString &name, bool safe = true) : dataSize(0) {
init(name, safe);
}
void init(const QString &name, bool safe) {
if (!_working()) return;
// detect order of read attempts and file version
QString toTry[2];
toTry[0] = _basePath + name + '0';
if (safe) {
toTry[1] = _basePath + name + '1';
QFileInfo toTry0(toTry[0]);
QFileInfo toTry1(toTry[1]);
if (toTry0.exists()) {
if (toTry1.exists()) {
QDateTime mod0 = toTry0.lastModified(), mod1 = toTry1.lastModified();
if (mod0 > mod1) {
qSwap(toTry[0], toTry[1]);
}
} else {
qSwap(toTry[0], toTry[1]);
}
toDelete = toTry[1];
} else if (toTry1.exists()) {
toDelete = toTry[1];
}
}
file.setFileName(toTry[0]);
if (file.open(QIODevice::WriteOnly)) {
file.write(tdfMagic, tdfMagicLen);
qint32 version = AppVersion;
file.write((const char*)&version, sizeof(version));
stream.setDevice(&file);
stream.setVersion(QDataStream::Qt_5_1);
}
}
bool writeData(const QByteArray &data) {
if (!file.isOpen()) return false;
stream << data;
quint32 len = data.isNull() ? 0xffffffff : data.size();
if (QSysInfo::ByteOrder != QSysInfo::BigEndian) {
len = qbswap(len);
}
md5.feed(&len, sizeof(len));
md5.feed(data.constData(), data.size());
dataSize += sizeof(len) + data.size();
return true;
}
QByteArray prepareEncrypted(EncryptedDescriptor &data, const mtpAuthKey &key = _localKey) {
data.finish();
QByteArray &toEncrypt(data.data);
// prepare for encryption
uint32 size = toEncrypt.size(), fullSize = size;
if (fullSize & 0x0F) {
fullSize += 0x10 - (fullSize & 0x0F);
toEncrypt.resize(fullSize);
memset_rand(toEncrypt.data() + size, fullSize - size);
}
*(uint32*)toEncrypt.data() = size;
QByteArray encrypted(0x10 + fullSize, Qt::Uninitialized); // 128bit of sha1 - key128, sizeof(data), data
hashSha1(toEncrypt.constData(), toEncrypt.size(), encrypted.data());
aesEncryptLocal(toEncrypt.constData(), encrypted.data() + 0x10, fullSize, &key, encrypted.constData());
return encrypted;
}
bool writeEncrypted(EncryptedDescriptor &data, const mtpAuthKey &key = _localKey) {
return writeData(prepareEncrypted(data, key));
}
void finish() {
if (!file.isOpen()) return;
stream.setDevice(0);
md5.feed(&dataSize, sizeof(dataSize));
qint32 version = AppVersion;
md5.feed(&version, sizeof(version));
md5.feed(tdfMagic, tdfMagicLen);
file.write((const char*)md5.result(), 0x10);
file.close();
if (!toDelete.isEmpty()) {
QFile::remove(toDelete);
}
}
QFile file;
QDataStream stream;
QString toDelete;
HashMd5 md5;
int32 dataSize;
~FileWriteDescriptor() {
finish();
}
};
bool readFile(FileReadDescriptor &result, const QString &name, bool safe = true) {
if (!_working()) return false;
// detect order of read attempts
QString toTry[2];
toTry[0] = _basePath + name + '0';
if (safe) {
QFileInfo toTry0(toTry[0]);
if (toTry0.exists()) {
toTry[1] = _basePath + name + '1';
QFileInfo toTry1(toTry[1]);
if (toTry1.exists()) {
QDateTime mod0 = toTry0.lastModified(), mod1 = toTry1.lastModified();
if (mod0 < mod1) {
qSwap(toTry[0], toTry[1]);
}
} else {
toTry[1] = QString();
}
} else {
toTry[0][toTry[0].size() - 1] = '1';
}
}
for (int32 i = 0; i < 2; ++i) {
QString fname(toTry[i]);
if (fname.isEmpty()) break;
QFile f(fname);
if (!f.open(QIODevice::ReadOnly)) {
DEBUG_LOG(("App Info: failed to open '%1' for reading").arg(name));
continue;
}
// check magic
char magic[tdfMagicLen];
if (f.read(magic, tdfMagicLen) != tdfMagicLen) {
DEBUG_LOG(("App Info: failed to read magic from '%1'").arg(name));
continue;
}
if (memcmp(magic, tdfMagic, tdfMagicLen)) {
DEBUG_LOG(("App Info: bad magic %1 in '%2'").arg(mb(magic, tdfMagicLen).str()).arg(name));
continue;
}
// read app version
qint32 version;
if (f.read((char*)&version, sizeof(version)) != sizeof(version)) {
DEBUG_LOG(("App Info: failed to read version from '%1'").arg(name));
continue;
}
if (version > AppVersion) {
DEBUG_LOG(("App Info: version too big %1 for '%2', my version %3").arg(version).arg(name).arg(AppVersion));
continue;
}
// read data
QByteArray bytes = f.read(f.size());
int32 dataSize = bytes.size() - 16;
if (dataSize < 0) {
DEBUG_LOG(("App Info: bad file '%1', could not read sign part").arg(name));
continue;
}
// check signature
HashMd5 md5;
md5.feed(bytes.constData(), dataSize);
md5.feed(&dataSize, sizeof(dataSize));
md5.feed(&version, sizeof(version));
md5.feed(magic, tdfMagicLen);
if (memcmp(md5.result(), bytes.constData() + dataSize, 16)) {
DEBUG_LOG(("App Info: bad file '%1', signature did not match").arg(name));
continue;
}
bytes.resize(dataSize);
result.data = bytes;
bytes = QByteArray();
result.version = version;
result.buffer.setBuffer(&result.data);
result.buffer.open(QIODevice::ReadOnly);
result.stream.setDevice(&result.buffer);
result.stream.setVersion(QDataStream::Qt_5_1);
if ((i == 0 && !toTry[1].isEmpty()) || i == 1) {
QFile::remove(toTry[1 - i]);
}
return true;
}
return false;
}
bool decryptLocal(EncryptedDescriptor &result, const QByteArray &encrypted, const mtpAuthKey &key = _localKey) {
if (encrypted.size() <= 16 || (encrypted.size() & 0x0F)) {
LOG(("App Error: bad encrypted part size: %1").arg(encrypted.size()));
return false;
}
uint32 fullLen = encrypted.size() - 16;
QByteArray decrypted;
decrypted.resize(fullLen);
const char *encryptedKey = encrypted.constData(), *encryptedData = encrypted.constData() + 16;
aesDecryptLocal(encryptedData, decrypted.data(), fullLen, &key, encryptedKey);
uchar sha1Buffer[20];
if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), encryptedKey, 16)) {
LOG(("App Error: bad decrypt key, data not decrypted"));
return false;
}
uint32 dataLen = *(const uint32*)decrypted.constData();
if (dataLen > uint32(decrypted.size()) || dataLen <= fullLen - 16 || dataLen < sizeof(uint32)) {
LOG(("App Error: bad decrypted part size: %1, fullLen: %2, decrypted size: %3").arg(dataLen).arg(fullLen).arg(decrypted.size()));
return false;
}
decrypted.resize(dataLen);
result.data = decrypted;
decrypted = QByteArray();
result.buffer.setBuffer(&result.data);
result.buffer.open(QIODevice::ReadOnly);
result.buffer.seek(sizeof(uint32)); // skip len
result.stream.setDevice(&result.buffer);
result.stream.setVersion(QDataStream::Qt_5_1);
return true;
}
bool readEncryptedFile(FileReadDescriptor &result, const QString &name, bool safe = true) {
if (!readFile(result, name, safe)) {
return false;
}
QByteArray encrypted;
result.stream >> encrypted;
EncryptedDescriptor data;
if (!decryptLocal(data, encrypted)) {
result.stream.setDevice(0);
if (result.buffer.isOpen()) result.buffer.close();
result.buffer.setBuffer(0);
result.data = QByteArray();
result.version = 0;
return false;
}
result.stream.setDevice(0);
if (result.buffer.isOpen()) result.buffer.close();
result.buffer.setBuffer(0);
result.data = data.data;
result.buffer.setBuffer(&result.data);
result.buffer.open(QIODevice::ReadOnly);
result.buffer.seek(data.buffer.pos());
result.stream.setDevice(&result.buffer);
result.stream.setVersion(QDataStream::Qt_5_1);
return true;
}
enum { // Local Storage Keys
lskUserMap = 0,
lskDraft, // data: PeerId peer
lskDraftPosition, // data: PeerId peer
lskStorage, // data: StorageKey location
};
typedef QMap<PeerId, FileKey> DraftsMap;
DraftsMap _draftsMap, _draftsPositionsMap;
typedef QMap<PeerId, bool> DraftsNotReadMap;
DraftsNotReadMap _draftsNotReadMap;
typedef QPair<FileKey, qint32> FileDesc; // file, size
typedef QMap<StorageKey, FileDesc> StorageMap;
StorageMap _storageMap;
int32 _storageFilesSize = 0;
bool _mapChanged = false;
Local::ReadMapState _readMap(const QByteArray &pass) {
uint64 ms = getms();
QByteArray dataNameUtf8 = cDataFile().toUtf8();
uint64 dataNameHash[2];
hashMd5(dataNameUtf8.constData(), dataNameUtf8.size(), dataNameHash);
_basePath = cWorkingDir() + qsl("tdata/") + toFilePart(dataNameHash[0]) + QChar('/');
FileReadDescriptor mapData;
if (!readFile(mapData, qsl("map"))) {
return Local::ReadMapFailed;
}
QByteArray salt, keyEncrypted, mapEncrypted;
mapData.stream >> salt >> keyEncrypted >> mapEncrypted;
if (mapData.stream.status() != QDataStream::Ok) {
LOG(("App Error: could not read salt / key from map file - corrupted?..").arg(mapData.stream.status()));
return Local::ReadMapFailed;
}
if (salt.size() != LocalEncryptSaltSize) {
LOG(("App Error: bad salt in map file, size: %1").arg(salt.size()));
return Local::ReadMapFailed;
}
createLocalKey(pass, &salt, &_passKey);
EncryptedDescriptor keyData, map;
if (!decryptLocal(keyData, keyEncrypted, _passKey)) {
LOG(("App Error: could not decrypt pass-protected key from map file, maybe bad password.."));
return Local::ReadMapPassNeeded;
}
uchar key[LocalEncryptKeySize] = { 0 };
if (keyData.stream.readRawData((char*)key, LocalEncryptKeySize) != LocalEncryptKeySize || !keyData.stream.atEnd()) {
LOG(("App Error: could not read pass-protected key from map file"));
return Local::ReadMapFailed;
}
_localKey.setKey(key);
_passKeyEncrypted = keyEncrypted;
_passKeySalt = salt;
if (!decryptLocal(map, mapEncrypted)) {
LOG(("App Error: could not decrypt map."));
return Local::ReadMapFailed;
}
DraftsMap draftsMap, draftsPositionsMap;
DraftsNotReadMap draftsNotReadMap;
StorageMap storageMap;
qint64 storageFilesSize = 0;
while (!map.stream.atEnd()) {
quint32 keyType;
map.stream >> keyType;
switch (keyType) {
case lskDraft: {
quint32 count = 0;
map.stream >> count;
for (quint32 i = 0; i < count; ++i) {
FileKey key;
quint64 p;
map.stream >> key >> p;
draftsMap.insert(p, key);
draftsNotReadMap.insert(p, true);
}
} break;
case lskDraftPosition: {
quint32 count = 0;
map.stream >> count;
for (quint32 i = 0; i < count; ++i) {
FileKey key;
quint64 p;
map.stream >> key >> p;
draftsPositionsMap.insert(p, key);
}
} break;
case lskStorage: {
quint32 count = 0;
map.stream >> count;
for (quint32 i = 0; i < count; ++i) {
FileKey key;
quint64 first, second;
qint32 size;
map.stream >> key >> first >> second >> size;
storageMap.insert(StorageKey(first, second), FileDesc(key, size));
storageFilesSize += size;
}
} break;
default:
LOG(("App Error: unknown key type in encrypted map: %1").arg(keyType));
return Local::ReadMapFailed;
}
if (map.stream.status() != QDataStream::Ok) {
LOG(("App Error: reading encrypted map bad status: %1").arg(map.stream.status()));
return Local::ReadMapFailed;
}
}
_draftsMap = draftsMap;
_draftsPositionsMap = draftsPositionsMap;
_draftsNotReadMap = draftsNotReadMap;
_storageMap = storageMap;
_storageFilesSize = storageFilesSize;
_mapChanged = false;
LOG(("Map read time: %1").arg(getms() - ms));
return Local::ReadMapDone;
}
enum WriteMapWhen {
WriteMapNow,
WriteMapFast,
WriteMapSoon,
};
void _writeMap(WriteMapWhen when = WriteMapSoon) {
if (when != WriteMapNow) {
_manager->writeMap(when == WriteMapFast);
return;
}
_manager->writingMap();
if (!_mapChanged) return;
if (_basePath.isEmpty()) {
LOG(("App Error: _basePath is empty in writeMap()"));
return;
}
QDir().mkpath(_basePath);
FileWriteDescriptor map(qsl("map"));
if (_passKeySalt.isEmpty() || _passKeyEncrypted.isEmpty()) {
uchar local5Key[LocalEncryptKeySize] = { 0 };
QByteArray pass(LocalEncryptKeySize, Qt::Uninitialized), salt(LocalEncryptSaltSize, Qt::Uninitialized);
memset_rand(pass.data(), pass.size());
memset_rand(salt.data(), salt.size());
createLocalKey(pass, &salt, &_localKey);
_passKeySalt.resize(LocalEncryptSaltSize);
memset_rand(_passKeySalt.data(), _passKeySalt.size());
createLocalKey(QByteArray(), &_passKeySalt, &_passKey);
EncryptedDescriptor passKeyData(LocalEncryptKeySize);
_localKey.write(passKeyData.stream);
_passKeyEncrypted = map.prepareEncrypted(passKeyData, _passKey);
}
map.writeData(_passKeySalt);
map.writeData(_passKeyEncrypted);
uint32 mapSize = 0;
if (!_draftsMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _draftsMap.size() * sizeof(quint64) * 2;
if (!_draftsPositionsMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _draftsPositionsMap.size() * sizeof(quint64) * 2;
if (!_storageMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _storageMap.size() * (sizeof(quint64) * 3 + sizeof(qint32));
EncryptedDescriptor mapData(mapSize);
if (!_draftsMap.isEmpty()) {
mapData.stream << quint32(lskDraft) << quint32(_draftsMap.size());
for (DraftsMap::const_iterator i = _draftsMap.cbegin(), e = _draftsMap.cend(); i != e; ++i) {
mapData.stream << quint64(i.value()) << quint64(i.key());
}
}
if (!_draftsPositionsMap.isEmpty()) {
mapData.stream << quint32(lskDraftPosition) << quint32(_draftsPositionsMap.size());
for (DraftsMap::const_iterator i = _draftsPositionsMap.cbegin(), e = _draftsPositionsMap.cend(); i != e; ++i) {
mapData.stream << quint64(i.value()) << quint64(i.key());
}
}
if (!_storageMap.isEmpty()) {
mapData.stream << quint32(lskStorage) << quint32(_storageMap.size());
for (StorageMap::const_iterator i = _storageMap.cbegin(), e = _storageMap.cend(); i != e; ++i) {
mapData.stream << quint64(i.value().first) << quint64(i.key().first) << quint64(i.key().second) << qint32(i.value().second);
}
}
map.writeEncrypted(mapData);
map.finish();
_mapChanged = false;
}
}
namespace _local_inner {
Manager::Manager() {
_mapWriteTimer.setSingleShot(true);
connect(&_mapWriteTimer, SIGNAL(timeout()), this, SLOT(mapWriteTimeout()));
}
void Manager::writeMap(bool fast) {
if (!_mapWriteTimer.isActive() || fast) {
_mapWriteTimer.start(fast ? 1 : WriteMapTimeout);
} else if (_mapWriteTimer.remainingTime() <= 0) {
mapWriteTimeout();
}
}
void Manager::writingMap() {
_mapWriteTimer.stop();
}
void Manager::mapWriteTimeout() {
_writeMap(WriteMapNow);
}
void Manager::finish() {
if (_mapWriteTimer.isActive()) {
mapWriteTimeout();
_mapWriteTimer.stop();
}
}
}
namespace Local {
mtpAuthKey &oldKey() {
return _oldKey;
}
void createOldKey(QByteArray *salt) {
createLocalKey(QByteArray(), salt, &_oldKey);
}
void start() {
if (!_started) {
_started = true;
_manager = new _local_inner::Manager();
}
}
void stop() {
if (_manager) {
_writeMap(WriteMapNow);
_manager->finish();
_manager->deleteLater();
_manager = 0;
}
}
ReadMapState readMap(const QByteArray &pass) {
ReadMapState result = _readMap(pass);
if (result == ReadMapFailed) {
_mapChanged = true;
_writeMap(WriteMapNow);
}
return result;
}
void writeDraft(const PeerId &peer, const QString &text) {
if (!_working()) return;
if (text.isEmpty()) {
DraftsMap::iterator i = _draftsMap.find(peer);
if (i != _draftsMap.cend()) {
clearKey(i.value());
_draftsMap.erase(i);
_mapChanged = true;
_writeMap();
}
_draftsNotReadMap.remove(peer);
} else {
DraftsMap::const_iterator i = _draftsMap.constFind(peer);
if (i == _draftsMap.cend()) {
i = _draftsMap.insert(peer, genKey());
_mapChanged = true;
_writeMap(WriteMapFast);
}
QString to = _basePath + toFilePart(i.value());
EncryptedDescriptor data(sizeof(quint64) + sizeof(quint32) + text.size() * sizeof(QChar));
data.stream << quint64(peer) << text;
FileWriteDescriptor file(i.value());
file.writeEncrypted(data);
_draftsNotReadMap.remove(peer);
}
}
QString readDraft(const PeerId &peer) {
if (!_draftsNotReadMap.remove(peer)) return QString();
DraftsMap::iterator j = _draftsMap.find(peer);
if (j == _draftsMap.cend()) {
return QString();
}
FileReadDescriptor draft;
if (!readEncryptedFile(draft, toFilePart(j.value()))) {
clearKey(j.value());
_draftsMap.erase(j);
return QString();
}
quint64 draftPeer;
QString draftText;
draft.stream >> draftPeer >> draftText;
return (draftPeer == peer) ? draftText : QString();
}
void writeDraftPositions(const PeerId &peer, const MessageCursor &cur) {
if (!_working()) return;
if (cur.position == 0 && cur.anchor == 0 && cur.scroll == 0) {
DraftsMap::iterator i = _draftsPositionsMap.find(peer);
if (i != _draftsPositionsMap.cend()) {
clearKey(i.value());
_draftsPositionsMap.erase(i);
_mapChanged = true;
_writeMap();
}
} else {
DraftsMap::const_iterator i = _draftsPositionsMap.constFind(peer);
if (i == _draftsPositionsMap.cend()) {
i = _draftsPositionsMap.insert(peer, genKey());
_mapChanged = true;
_writeMap(WriteMapFast);
}
QString to = _basePath + toFilePart(i.value());
EncryptedDescriptor data(sizeof(quint64) + sizeof(qint32) * 3);
data.stream << quint64(peer) << qint32(cur.position) << qint32(cur.anchor) << qint32(cur.scroll);
FileWriteDescriptor file(i.value());
file.writeEncrypted(data);
}
}
MessageCursor readDraftPositions(const PeerId &peer) {
DraftsMap::iterator j = _draftsPositionsMap.find(peer);
if (j == _draftsPositionsMap.cend()) {
return MessageCursor();
}
FileReadDescriptor draft;
if (!readEncryptedFile(draft, toFilePart(j.value()))) {
clearKey(j.value());
_draftsPositionsMap.erase(j);
return MessageCursor();
}
quint64 draftPeer;
qint32 curPosition, curAnchor, curScroll;
draft.stream >> draftPeer >> curPosition >> curAnchor >> curScroll;
return (draftPeer == peer) ? MessageCursor(curPosition, curAnchor, curScroll) : MessageCursor();
}
bool hasDraftPositions(const PeerId &peer) {
return (_draftsPositionsMap.constFind(peer) != _draftsPositionsMap.cend());
}
qint32 _storageImageSize(qint32 rawlen) {
// fulllen + storagekey + type + len + data
qint32 result = sizeof(uint32) + sizeof(quint64) * 2 + sizeof(quint32) + sizeof(quint32) + rawlen;
if (result & 0x0F) result += 0x10 - (result & 0x0F);
result += tdfMagicLen + sizeof(qint32) + sizeof(quint32) + 0x10 + 0x10; // magic + version + len of encrypted + part of sha1 + md5
return result;
}
void writeImage(const StorageKey &location, const ImagePtr &image) {
if (image->isNull() || !image->loaded()) return;
if (_storageMap.constFind(location) != _storageMap.cend()) return;
QByteArray fmt = image->savedFormat();
mtpTypeId format = 0;
if (fmt == "JPG") {
format = mtpc_storage_fileJpeg;
} else if (fmt == "PNG") {
format = mtpc_storage_filePng;
} else if (fmt == "GIF") {
format = mtpc_storage_fileGif;
}
if (format) {
image->forget();
writeImage(location, StorageImageSaved(format, image->savedData()), false);
}
}
void writeImage(const StorageKey &location, const StorageImageSaved &image, bool overwrite) {
if (!_working()) return;
qint32 size = _storageImageSize(image.data.size());
StorageMap::const_iterator i = _storageMap.constFind(location);
if (i == _storageMap.cend()) {
i = _storageMap.insert(location, FileDesc(genKey(), size));
_storageFilesSize += size;
_mapChanged = true;
_writeMap();
} else if (!overwrite) {
return;
}
EncryptedDescriptor data(sizeof(quint64) * 2 + sizeof(quint32) + sizeof(quint32) + image.data.size());
data.stream << quint64(location.first) << quint64(location.second) << quint32(image.type) << image.data;
FileWriteDescriptor file(i.value().first, false);
file.writeEncrypted(data);
if (i.value().second != size) {
_storageFilesSize += size;
_storageFilesSize -= i.value().second;
_storageMap[location].second = size;
}
}
StorageImageSaved readImage(const StorageKey &location) {
StorageMap::iterator j = _storageMap.find(location);
if (j == _storageMap.cend()) {
return StorageImageSaved();
}
FileReadDescriptor draft;
if (!readEncryptedFile(draft, toFilePart(j.value().first), false)) {
clearKey(j.value().first, false);
_storageFilesSize -= j.value().second;
_storageMap.erase(j);
return StorageImageSaved();
}
QByteArray imageData;
quint64 locFirst, locSecond;
quint32 imageType;
draft.stream >> locFirst >> locSecond >> imageType >> imageData;
return (locFirst == location.first && locSecond == location.second) ? StorageImageSaved(imageType, imageData) : StorageImageSaved();
}
int32 hasImages() {
return _storageMap.size();
}
qint64 storageFilesSize() {
return _storageFilesSize;
}
struct ClearManagerData {
QThread *thread;
StorageMap images;
QMutex mutex;
QList<int> tasks;
bool working;
};
ClearManager::ClearManager() : data(new ClearManagerData()) {
data->thread = new QThread();
data->working = true;
}
bool ClearManager::addTask(int task) {
QMutexLocker lock(&data->mutex);
if (!data->working) return false;
if (!data->tasks.isEmpty() && (data->tasks.at(0) == ClearManagerAll)) return true;
if (task == ClearManagerAll) {
data->tasks.clear();
} else {
if (task & ClearManagerImages) {
if (data->images.isEmpty()) {
data->images = _storageMap;
} else {
for (StorageMap::const_iterator i = _storageMap.cbegin(), e = _storageMap.cend(); i != e; ++i) {
StorageKey k = i.key();
while (data->images.constFind(k) != data->images.cend()) {
++k.second;
}
data->images.insert(k, i.value());
}
}
_storageMap.clear();
_storageFilesSize = 0;
_mapChanged = true;
_writeMap();
}
for (int32 i = 0, l = data->tasks.size(); i < l; ++i) {
if (data->tasks.at(i) == task) return true;
}
}
data->tasks.push_back(task);
return true;
}
bool ClearManager::hasTask(ClearManagerTask task) {
QMutexLocker lock(&data->mutex);
if (data->tasks.isEmpty()) return false;
if (data->tasks.at(0) == ClearManagerAll) return true;
for (int32 i = 0, l = data->tasks.size(); i < l; ++i) {
if (data->tasks.at(i) == task) return true;
}
return false;
}
void ClearManager::start() {
moveToThread(data->thread);
connect(data->thread, SIGNAL(started()), this, SLOT(onStart()));
data->thread->start();
}
ClearManager::~ClearManager() {
data->thread->deleteLater();
delete data;
}
void ClearManager::onStart() {
while (true) {
int task = 0;
bool result = false;
StorageMap images;
{
QMutexLocker lock(&data->mutex);
if (data->tasks.isEmpty()) {
data->working = false;
break;
}
task = data->tasks.at(0);
images = data->images;
}
switch (task) {
case ClearManagerAll:
result = (QDir(cTempDir()).removeRecursively() && QDir(_basePath).removeRecursively());
break;
case ClearManagerDownloads:
result = QDir(cTempDir()).removeRecursively();
break;
case ClearManagerImages:
for (StorageMap::const_iterator i = images.cbegin(), e = images.cend(); i != e; ++i) {
clearKey(i.value().first, false);
}
result = true;
break;
}
{
QMutexLocker lock(&data->mutex);
if (data->tasks.at(0) == task) {
data->tasks.pop_front();
if (data->tasks.isEmpty()) {
data->working = false;
}
}
if (result) {
emit succeed(task, data->working ? 0 : this);
} else {
emit failed(task, data->working ? 0 : this);
}
if (!data->working) break;
}
}
}
}

View File

@@ -0,0 +1,102 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop 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://tdesktop.com
*/
#pragma once
#include "types.h"
namespace _local_inner {
class Manager : public QObject {
Q_OBJECT
public:
Manager();
void writeMap(bool fast);
void writingMap();
void finish();
public slots:
void mapWriteTimeout();
private:
QTimer _mapWriteTimer;
};
}
namespace Local {
mtpAuthKey &oldKey();
void createOldKey(QByteArray *salt = 0);
void start();
void stop();
enum ClearManagerTask {
ClearManagerAll = 0xFFFF,
ClearManagerDownloads = 0x01,
ClearManagerImages = 0x02,
};
struct ClearManagerData;
class ClearManager : public QObject {
Q_OBJECT
public:
ClearManager();
bool addTask(int task);
bool hasTask(ClearManagerTask task);
void start();
~ClearManager();
public slots:
void onStart();
signals:
void succeed(int task, void *manager);
void failed(int task, void *manager);
private:
ClearManagerData *data;
};
enum ReadMapState {
ReadMapFailed = 0,
ReadMapDone = 1,
ReadMapPassNeeded = 2,
};
ReadMapState readMap(const QByteArray &pass);
void writeDraft(const PeerId &peer, const QString &text);
QString readDraft(const PeerId &peer);
void writeDraftPositions(const PeerId &peer, const MessageCursor &cur);
MessageCursor readDraftPositions(const PeerId &peer);
bool hasDraftPositions(const PeerId &peer);
void writeImage(const StorageKey &location, const ImagePtr &img);
void writeImage(const StorageKey &location, const StorageImageSaved &jpeg, bool overwrite = true);
StorageImageSaved readImage(const StorageKey &location);
int32 hasImages();
qint64 storageFilesSize();
};

View File

@@ -73,7 +73,7 @@ void debugLogWrite(const char *file, int32 line, const QString &v) {
(*debugLogStream) << msg;
debugLogStream->flush();
#ifdef Q_OS_WIN
OutputDebugString(reinterpret_cast<const wchar_t *>(msg.utf16()));
// OutputDebugString(reinterpret_cast<const wchar_t *>(msg.utf16()));
#elif defined Q_OS_MAC
objc_outputDebugString(msg);
#elif defined Q_OS_LINUX && defined _DEBUG

View File

@@ -143,7 +143,7 @@ void TopBarWidget::enableShadow(bool enable) {
void TopBarWidget::paintEvent(QPaintEvent *e) {
QPainter p(this);
if (e->rect().top() < st::topBarHeight) {
if (e->rect().top() < st::topBarHeight) { // optimize shadow-only drawing
p.fillRect(QRect(0, 0, width(), st::topBarHeight), st::topBarBG->b);
if (_clearSelection.isHidden()) {
p.save();
@@ -154,8 +154,6 @@ void TopBarWidget::paintEvent(QPaintEvent *e) {
p.setPen(st::btnDefLink.color->p);
p.drawText(st::topBarSelectedPos.x(), st::topBarSelectedPos.y() + st::linkFont->ascent, _selStr);
}
} else {
int a = 0; // optimize shadow-only drawing
}
if (_drawShadow) {
p.fillRect(st::titleShadow, st::topBarHeight, width() - st::titleShadow, st::titleShadow, st::titleShadowColor->b);
@@ -766,12 +764,15 @@ void MainWidget::peerUsernameChanged(PeerData *peer) {
void MainWidget::checkLastUpdate(bool afterSleep) {
uint64 n = getms(true);
LOG(("Checking last update!.. last update %1, now %2, noUpdatesTimer %3, remains %4").arg(_lastUpdateTime).arg(n).arg(noUpdatesTimer.isActive() ? 1 : 0).arg(noUpdatesTimer.remainingTime()));
if (_lastUpdateTime && n > _lastUpdateTime + (afterSleep ? NoUpdatesAfterSleepTimeout : NoUpdatesTimeout)) {
getDifference();
}
}
void MainWidget::showNewGroup() {
dialogs.onNewGroup();
}
void MainWidget::photosLoaded(History *h, const MTPmessages_Messages &msgs, mtpRequestId req) {
OverviewsPreload::iterator it;
MediaOverviewType type = OverviewCount;
@@ -1731,7 +1732,6 @@ void MainWidget::gotState(const MTPupdates_State &state) {
MTP::setGlobalDoneHandler(rpcDone(&MainWidget::updateReceived));
_lastUpdateTime = getms(true);
noUpdatesTimer.start(NoUpdatesTimeout);
LOG(("Started no updates timeout, %1").arg(_lastUpdateTime));
updInited = true;
dialogs.loadDialogs();
@@ -1749,7 +1749,7 @@ void MainWidget::gotDifference(const MTPupdates_Difference &diff) {
MTP::setGlobalDoneHandler(rpcDone(&MainWidget::updateReceived));
_lastUpdateTime = getms(true);
noUpdatesTimer.start(NoUpdatesTimeout);
LOG(("Started no updates timeout, %1").arg(_lastUpdateTime));
updInited = true;
} break;
case mtpc_updates_differenceSlice: {
@@ -1997,6 +1997,8 @@ int32 MainWidget::dlgsWidth() const {
}
MainWidget::~MainWidget() {
if (App::main() == this) history.showPeer(0, 0, true);
delete hider;
MTP::clearGlobalHandlers();
App::deinitMedia(false);
@@ -2040,7 +2042,6 @@ void MainWidget::updateReceived(const mtpPrime *from, const mtpPrime *end) {
_lastUpdateTime = getms(true);
noUpdatesTimer.start(NoUpdatesTimeout);
LOG(("Started no updates timeout, %1").arg(_lastUpdateTime));
handleUpdates(updates);
} catch(mtpErrorUnexpected &e) { // just some other type
@@ -2252,6 +2253,9 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
if (user) {
switch (d.vstatus.type()) {
case mtpc_userStatusEmpty: user->onlineTill = 0; break;
case mtpc_userStatusRecently: user->onlineTill = -2; break;
case mtpc_userStatusLastWeek: user->onlineTill = -3; break;
case mtpc_userStatusLastMonth: user->onlineTill = -4; break;
case mtpc_userStatusOffline: user->onlineTill = d.vstatus.c_userStatusOffline().vwas_online.v; break;
case mtpc_userStatusOnline: user->onlineTill = d.vstatus.c_userStatusOnline().vexpires.v; break;
}
@@ -2263,8 +2267,12 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
case mtpc_updateUserName: {
const MTPDupdateUserName &d(update.c_updateUserName());
UserData *user = App::userLoaded(d.vuser_id.v);
if (user && user->contact <= 0) {
user->setName(textOneLine(qs(d.vfirst_name)), textOneLine(qs(d.vlast_name)), user->nameOrPhone, textOneLine(qs(d.vusername)));
if (user) {
if (user->contact <= 0) {
user->setName(textOneLine(qs(d.vfirst_name)), textOneLine(qs(d.vlast_name)), user->nameOrPhone, textOneLine(qs(d.vusername)));
} else {
user->setName(textOneLine(user->firstName), textOneLine(user->lastName), user->nameOrPhone, textOneLine(qs(d.vusername)));
}
if (App::main()) App::main()->peerUpdated(user);
}
} break;

View File

@@ -285,6 +285,7 @@ public:
void peerUsernameChanged(PeerData *peer);
void checkLastUpdate(bool afterSleep);
void showNewGroup();
~MainWidget();

View File

@@ -273,6 +273,7 @@ void MediaView::onDownload() {
if (cur.isEmpty()) {
_save.hide();
} else {
if (!QDir().exists(path)) QDir().mkpath(path);
toName = filedialogNextFilename(_doc->name, cur, path);
if (toName != cur && !QFile(cur).copy(toName)) {
toName = QString();
@@ -282,6 +283,7 @@ void MediaView::onDownload() {
if (!_photo || !_photo->full->loaded()) {
_save.hide();
} else {
if (!QDir().exists(path)) QDir().mkpath(path);
toName = filedialogDefaultName(qsl("photo"), qsl(".jpg"), path);
if (!_photo->full->pix().toImage().save(toName, "JPG")) {
toName = QString();
@@ -468,7 +470,6 @@ void MediaView::showPhoto(PhotoData *photo) {
_doc = 0;
_zoom = 0;
MTP::clearLoaderPriorities();
_photo->full->load();
_full = -1;
_current = QPixmap();
_down = OverNone;
@@ -490,6 +491,7 @@ void MediaView::showPhoto(PhotoData *photo) {
_width = _w;
_from = App::user(_photo->user);
updateControls();
_photo->full->load();
if (isHidden()) {
psUpdateOverlayed(this);
show();

View File

@@ -177,11 +177,11 @@ with open('scheme.tl') as f:
funcsText += '\tMTP' + name + '(' + ', '.join(prmsStr) + ') : ' + ', '.join(prmsInit) + ' {\n\t}\n';
funcsText += '\n';
funcsText += '\tuint32 size() const {\n'; # count size
funcsText += '\tuint32 innerLength() const {\n'; # count size
size = [];
for k in prmsList:
v = prms[k];
size.append('v' + k + '.size()');
size.append('v' + k + '.innerLength()');
if (not len(size)):
size.append('0');
funcsText += '\t\treturn ' + ' + '.join(size) + ';\n';
@@ -402,7 +402,7 @@ for restype in typesList:
writeText += '\t\t';
readText += '\tv.v' + paramName + '.read(from, end);\n';
writeText += '\tv.v' + paramName + '.write(to);\n';
sizeList.append('v.v' + paramName + '.size()');
sizeList.append('v.v' + paramName + '.innerLength()');
forwards += 'class MTPD' + name + ';\n'; # data class forward declaration
@@ -505,8 +505,8 @@ for restype in typesList:
if (withData):
typesText += getters;
typesText += '\n\tuint32 size() const;\n'; # size method
inlineMethods += '\ninline uint32 MTP' + restype + '::size() const {\n';
typesText += '\n\tuint32 innerLength() const;\n'; # size method
inlineMethods += '\ninline uint32 MTP' + restype + '::innerLength() const {\n';
if (withType and sizeCases):
inlineMethods += '\tswitch (_type) {\n';
inlineMethods += sizeCases;

View File

@@ -18,6 +18,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "stdafx.h"
#include "mtp.h"
#include "localstorage.h"
namespace {
typedef QMap<int32, MTProtoSessionPtr> Sessions;
Sessions sessions;
@@ -61,8 +63,6 @@ namespace {
MTPSessionResetHandler sessionResetHandler = 0;
_mtp_internal::RequestResender *resender = 0;
mtpAuthKey _localKey;
void importDone(const MTPauth_Authorization &result, mtpRequestId req) {
QMutexLocker locker1(&requestByDCLock);
@@ -350,10 +350,6 @@ namespace _mtp_internal {
requestsByDC.remove(requestId);
}
uint32 getLayer() {
return layer;
}
mtpRequestId storeRequest(mtpRequest &request, const RPCResponseHandler &parser) {
mtpRequestId res = reqid();
request->requestId = res;
@@ -379,20 +375,25 @@ namespace _mtp_internal {
return req;
}
void wrapInvokeAfter(mtpRequest &to, const mtpRequest &from, const mtpRequestMap &haveSent) {
void wrapInvokeAfter(mtpRequest &to, const mtpRequest &from, const mtpRequestMap &haveSent, int32 skipBeforeRequest) {
mtpMsgId afterId(*(mtpMsgId*)(from->after->data() + 4));
mtpRequestMap::const_iterator i = afterId ? haveSent.constFind(afterId) : haveSent.cend();
int32 size = to->size(), len = (*from)[7] >> 2, headlen = 4, fulllen = headlen + len;
int32 size = to->size(), lenInInts = (from.innerLength() >> 2), headlen = 4, fulllen = headlen + lenInInts;
if (i == haveSent.constEnd()) { // no invoke after or such msg was not sent or was completed recently
to->resize(size + fulllen);
memcpy(to->data() + size, from->constData() + 4, fulllen * sizeof(mtpPrime));
to->resize(size + fulllen + skipBeforeRequest);
if (skipBeforeRequest) {
memcpy(to->data() + size, from->constData() + 4, headlen * sizeof(mtpPrime));
memcpy(to->data() + size + headlen + skipBeforeRequest, from->constData() + 4 + headlen, lenInInts * sizeof(mtpPrime));
} else {
memcpy(to->data() + size, from->constData() + 4, fulllen * sizeof(mtpPrime));
}
} else {
to->resize(size + fulllen + 3);
to->resize(size + fulllen + skipBeforeRequest + 3);
memcpy(to->data() + size, from->constData() + 4, headlen * sizeof(mtpPrime));
(*to)[size + 3] += 3 * sizeof(mtpPrime);
*((mtpTypeId*)&((*to)[size + headlen])) = mtpc_invokeAfterMsg;
memcpy(to->data() + size + headlen + 1, &afterId, 2 * sizeof(mtpPrime));
memcpy(to->data() + size + headlen + 3, from->constData() + 4 + headlen, len * sizeof(mtpPrime));
*((mtpTypeId*)&((*to)[size + headlen + skipBeforeRequest])) = mtpc_invokeAfterMsg;
memcpy(to->data() + size + headlen + skipBeforeRequest + 1, &afterId, 2 * sizeof(mtpPrime));
memcpy(to->data() + size + headlen + skipBeforeRequest + 3, from->constData() + 4 + headlen, lenInInts * sizeof(mtpPrime));
if (size + 3 != 7) (*to)[7] += 3 * sizeof(mtpPrime);
}
}
@@ -563,31 +564,11 @@ namespace _mtp_internal {
};
namespace MTP {
mtpAuthKey &localKey() {
return _localKey;
}
void createLocalKey(const QByteArray &pass, QByteArray *salt) {
uchar key[LocalEncryptKeySize] = { 0 };
int32 iterCount = pass.size() ? LocalEncryptIterCount : LocalEncryptNoPwdIterCount; // dont slow down for no password
QByteArray newSalt;
if (!salt) {
newSalt.resize(LocalEncryptSaltSize);
memset_rand(newSalt.data(), newSalt.size());
salt = &newSalt;
cSetLocalSalt(newSalt);
}
PKCS5_PBKDF2_HMAC_SHA1(pass.constData(), pass.size(), (uchar*)salt->data(), salt->size(), iterCount, LocalEncryptKeySize, key);
_localKey.setKey(key);
}
void start() {
unixtimeInit();
if (!localKey().created()) {
if (!Local::oldKey().created()) {
LOG(("App Error: trying to start MTP without local key!"));
return;
}
@@ -624,15 +605,6 @@ namespace MTP {
}
}
void setLayer(uint32 l) {
if (l > mtpLayerMax) {
l = mtpLayerMax;
} else if (!l) {
l = 1;
}
layer = l - 1;
}
void setdc(int32 dc, bool fromZeroOnly) {
if (!started) return;

View File

@@ -26,13 +26,11 @@ namespace _mtp_internal {
void registerRequest(mtpRequestId requestId, int32 dc);
void unregisterRequest(mtpRequestId requestId);
uint32 getLayer();
static const uint32 dcShift = 10000;
mtpRequestId storeRequest(mtpRequest &request, const RPCResponseHandler &parser);
mtpRequest getRequest(mtpRequestId req);
void wrapInvokeAfter(mtpRequest &to, const mtpRequest &from, const mtpRequestMap &haveSent);
void wrapInvokeAfter(mtpRequest &to, const mtpRequest &from, const mtpRequestMap &haveSent, int32 skipBeforeRequest = 0);
void clearCallbacks(mtpRequestId requestId, int32 errorCode = RPCError::NoError); // 0 - do not toggle onError callback
void clearCallbacksDelayed(const RPCCallbackClears &requestIds);
void performDelayedClear();
@@ -65,9 +63,6 @@ namespace _mtp_internal {
namespace MTP {
mtpAuthKey &localKey();
void createLocalKey(const QByteArray &pass, QByteArray *salt = 0);
static const uint32 cfg = 1 * _mtp_internal::dcShift; // send(MTPhelp_GetConfig(), MTP::cfg + dc) - for dc enum
static const uint32 dld[MTPDownloadSessionsCount] = { // send(req, callbacks, MTP::dld[i] + dc) - for download
0x10 * _mtp_internal::dcShift,
@@ -86,8 +81,6 @@ namespace MTP {
void restart();
void restart(int32 dcMask);
void setLayer(uint32 layer);
void setdc(int32 dc, bool fromZeroOnly = false);
int32 maindc();
int32 dcstate(int32 dc = 0);
@@ -95,7 +88,10 @@ namespace MTP {
void initdc(int32 dc);
template <typename TRequest>
inline mtpRequestId send(const TRequest &request, RPCResponseHandler callbacks = RPCResponseHandler(), int32 dc = 0, uint64 msCanWait = 0, mtpRequestId after = 0) {
return _mtp_internal::getSession(dc)->send(request, callbacks, msCanWait, _mtp_internal::getLayer(), !dc, after);
MTProtoSessionPtr session = _mtp_internal::getSession(dc);
if (!session) return 0;
return session->send(request, callbacks, msCanWait, true, !dc, after);
}
template <typename TRequest>
inline mtpRequestId send(const TRequest &request, RPCDoneHandlerPtr onDone, RPCFailHandlerPtr onFail = RPCFailHandlerPtr(), int32 dc = 0, uint64 msCanWait = 0, mtpRequestId after = 0) {

View File

@@ -48,7 +48,7 @@ public:
return _keyId;
}
void prepareAES(const MTPint128 &msgKey, MTPint256 &aesKey, MTPint256 &aesIV, bool send = true) {
void prepareAES(const MTPint128 &msgKey, MTPint256 &aesKey, MTPint256 &aesIV, bool send = true) const {
if (!_isset) throw mtpErrorKeyNotReady(QString("prepareAES(.., %1)").arg(logBool(send)));
uint32 x = send ? 0 : 8;
@@ -112,14 +112,14 @@ inline void aesEncrypt(const void *src, void *dst, uint32 len, void *key, void *
AES_ige_encrypt((const uchar*)src, (uchar*)dst, len, &aes, aes_iv, AES_ENCRYPT);
}
inline void aesEncrypt(const void *src, void *dst, uint32 len, mtpAuthKeyPtr authKey, const MTPint128 &msgKey) {
inline void aesEncrypt(const void *src, void *dst, uint32 len, const mtpAuthKeyPtr &authKey, const MTPint128 &msgKey) {
MTPint256 aesKey, aesIV;
authKey->prepareAES(msgKey, aesKey, aesIV);
return aesEncrypt(src, dst, len, &aesKey, &aesIV);
}
inline void aesEncryptLocal(const void *src, void *dst, uint32 len, mtpAuthKey *authKey, const void *key128) {
inline void aesEncryptLocal(const void *src, void *dst, uint32 len, const mtpAuthKey *authKey, const void *key128) {
MTPint256 aesKey, aesIV;
authKey->prepareAES(*(const MTPint128*)key128, aesKey, aesIV, false);
@@ -136,14 +136,14 @@ inline void aesDecrypt(const void *src, void *dst, uint32 len, void *key, void *
AES_ige_encrypt((const uchar*)src, (uchar*)dst, len, &aes, aes_iv, AES_DECRYPT);
}
inline void aesDecrypt(const void *src, void *dst, uint32 len, mtpAuthKeyPtr authKey, const MTPint128 &msgKey) {
inline void aesDecrypt(const void *src, void *dst, uint32 len, const mtpAuthKeyPtr &authKey, const MTPint128 &msgKey) {
MTPint256 aesKey, aesIV;
authKey->prepareAES(msgKey, aesKey, aesIV, false);
return aesDecrypt(src, dst, len, &aesKey, &aesIV);
}
inline void aesDecryptLocal(const void *src, void *dst, uint32 len, mtpAuthKey *authKey, const void *key128) {
inline void aesDecryptLocal(const void *src, void *dst, uint32 len, const mtpAuthKey *authKey, const void *key128) {
MTPint256 aesKey, aesIV;
authKey->prepareAES(*(const MTPint128*)key128, aesKey, aesIV, false);

View File

@@ -309,8 +309,8 @@ void MTProtoConnection::restart() {
}
void MTProtoConnection::stop() {
data->stop();
thread->quit();
if (data) data->stop();
if (thread) thread->quit();
}
void MTProtoConnection::stopped() {
@@ -473,7 +473,7 @@ namespace {
mtpBuffer _preparePQFake(const MTPint128 &nonce) {
MTPReq_pq req_pq(nonce);
mtpBuffer buffer;
uint32 requestSize = req_pq.size() >> 2;
uint32 requestSize = req_pq.innerLength() >> 2;
buffer.resize(0);
buffer.reserve(8 + requestSize);
@@ -604,7 +604,7 @@ void MTPabstractTcpConnection::socketRead() {
}
MTPautoConnection::MTPautoConnection(QThread *thread) : status(WaitingBoth),
tcpNonce(MTP::nonce<MTPint128>()), httpNonce(MTP::nonce<MTPint128>()) {
tcpNonce(MTP::nonce<MTPint128>()), httpNonce(MTP::nonce<MTPint128>()), _tcpTimeout(MTPMinReceiveDelay) {
moveToThread(thread);
manager.moveToThread(thread);
@@ -613,6 +613,9 @@ tcpNonce(MTP::nonce<MTPint128>()), httpNonce(MTP::nonce<MTPint128>()) {
httpStartTimer.moveToThread(thread);
httpStartTimer.setSingleShot(true);
connect(&httpStartTimer, SIGNAL(timeout()), this, SLOT(onHttpStart()));
tcpTimeoutTimer.moveToThread(thread);
tcpTimeoutTimer.setSingleShot(true);
connect(&tcpTimeoutTimer, SIGNAL(timeout()), this, SLOT(onTcpTimeoutTimer()));
sock.moveToThread(thread);
sock.setProxy(QNetworkProxy(QNetworkProxy::NoProxy));
@@ -627,7 +630,7 @@ void MTPautoConnection::onHttpStart() {
if (status == HttpReady) {
DEBUG_LOG(("Connection Info: Http-transport chosen by timer"));
status = UsingHttp;
sock.disconnect();
sock.disconnectFromHost();
emit connected();
}
}
@@ -638,13 +641,37 @@ void MTPautoConnection::onSocketConnected() {
DEBUG_LOG(("Connection Info: sending fake req_pq through tcp transport"));
if (_tcpTimeout < 0) _tcpTimeout = -_tcpTimeout;
tcpTimeoutTimer.start(_tcpTimeout);
tcpSend(buffer);
} else if (status == WaitingHttp || status == UsingHttp) {
sock.disconnect();
sock.disconnectFromHost();
}
}
void MTPautoConnection::onTcpTimeoutTimer() {
if (status == HttpReady || status == WaitingBoth || status == WaitingTcp) {
if (_tcpTimeout < MTPMaxReceiveDelay) _tcpTimeout *= 2;
_tcpTimeout = -_tcpTimeout;
QAbstractSocket::SocketState state = sock.state();
if (state == QAbstractSocket::ConnectedState || state == QAbstractSocket::ConnectingState || state == QAbstractSocket::HostLookupState) {
sock.disconnectFromHost();
} else if (state != QAbstractSocket::ClosingState) {
sock.connectToHost(QHostAddress(_addr), _port);
}
}
}
void MTPautoConnection::onSocketDisconnected() {
if (_tcpTimeout < 0) {
_tcpTimeout = -_tcpTimeout;
if (status == HttpReady || status == WaitingBoth || status == WaitingTcp) {
sock.connectToHost(QHostAddress(_addr), _port);
return;
}
}
if (status == WaitingBoth) {
status = WaitingHttp;
} else if (status == WaitingTcp || status == UsingTcp) {
@@ -680,15 +707,6 @@ void MTPautoConnection::tcpSend(mtpBuffer &buffer) {
TCP_LOG(("TCP Info: write %1 packet %2 bytes").arg(packetNum).arg(len));
sock.write((const char*)&buffer[0], len);
//int64 b = sock.bytesToWrite();
//if (b > 100000) {
// int a = 0;
//}
//sock.flush();
//int64 b2 = sock.bytesToWrite();
//if (b2 > 0) {
// TCP_LOG(("TCP Info: writing many, %1 left to write").arg(b2));
//}
}
void MTPautoConnection::httpSend(mtpBuffer &buffer) {
@@ -725,14 +743,17 @@ 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
connect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*)));
_addr = addr;
_port = port;
connect(&sock, SIGNAL(readyRead()), this, SLOT(socketRead()));
sock.connectToHost(QHostAddress(_addr), _port);
mtpBuffer buffer(_preparePQFake(httpNonce));
DEBUG_LOG(("Connection Info: sending fake req_pq through http transport"));
httpSend(buffer);
sock.connectToHost(QHostAddress(addr), port);
connect(&sock, SIGNAL(readyRead()), this, SLOT(socketRead()));
}
bool MTPautoConnection::isConnected() {
@@ -766,7 +787,7 @@ void MTPautoConnection::requestFinished(QNetworkReply *reply) {
} else {
DEBUG_LOG(("Connection Info: Http-transport chosen by pq-response, awaited"));
status = UsingHttp;
sock.disconnect();
sock.disconnectFromHost();
emit connected();
}
}
@@ -786,7 +807,7 @@ void MTPautoConnection::requestFinished(QNetworkReply *reply) {
return;
}
bool mayBeBadKey = _handleHttpError(reply);
bool mayBeBadKey = _handleHttpError(reply) && _sentEncrypted;
if (status == WaitingBoth) {
status = WaitingTcp;
} else if (status == WaitingHttp || status == UsingHttp) {
@@ -802,14 +823,15 @@ void MTPautoConnection::socketPacket(mtpPrime *packet, uint32 size) {
if (data.size() == 1) {
if (status == WaitingBoth) {
status = WaitingHttp;
sock.disconnect();
sock.disconnectFromHost();
} else if (status == HttpReady) {
DEBUG_LOG(("Connection Info: Http-transport chosen by bad tcp response, ready"));
status = UsingHttp;
sock.disconnect();
sock.disconnectFromHost();
emit connected();
} else if (status == WaitingTcp || status == UsingTcp) {
emit error(data[0] == -404);
bool mayBeBadKey = (data[0] == -404) && _sentEncrypted;
emit error(mayBeBadKey);
} else {
LOG(("Strange Tcp Error; status %1").arg(status));
}
@@ -817,6 +839,7 @@ void MTPautoConnection::socketPacket(mtpPrime *packet, uint32 size) {
receivedQueue.push_back(data);
emit receivedData();
} else if (status == WaitingBoth || status == WaitingTcp || status == HttpReady) {
tcpTimeoutTimer.stop();
try {
MTPResPQ res_pq = _readPQFakeReply(data);
const MTPDresPQ &res_pq_data(res_pq.c_resPQ());
@@ -828,11 +851,11 @@ void MTPautoConnection::socketPacket(mtpPrime *packet, uint32 size) {
} catch (Exception &e) {
if (status == WaitingBoth) {
status = WaitingHttp;
sock.disconnect();
sock.disconnectFromHost();
} else if (status == HttpReady) {
DEBUG_LOG(("Connection Info: Http-transport chosen by bad tcp response, awaited"));
status = UsingHttp;
sock.disconnect();
sock.disconnectFromHost();
emit connected();
} else {
emit error();
@@ -907,14 +930,15 @@ void MTPtcpConnection::disconnectFromServer() {
}
void MTPtcpConnection::connectToServer(const QString &addr, int32 port) {
sock.connectToHost(QHostAddress(addr), port);
connect(&sock, SIGNAL(readyRead()), this, SLOT(socketRead()));
sock.connectToHost(QHostAddress(addr), port);
}
void MTPtcpConnection::socketPacket(mtpPrime *packet, uint32 size) {
mtpBuffer data = _handleTcpResponse(packet, size);
if (data.size() == 1) {
emit error(data[0] == -404);
bool mayBeBadKey = (data[0] == -404) && _sentEncrypted;
emit error(mayBeBadKey);
}
receivedQueue.push_back(data);
@@ -1002,7 +1026,7 @@ void MTPhttpConnection::requestFinished(QNetworkReply *reply) {
return;
}
bool mayBeBadKey = _handleHttpError(reply);
bool mayBeBadKey = _handleHttpError(reply) && _sentEncrypted;
emit error(mayBeBadKey);
}
@@ -1050,7 +1074,7 @@ MTProtoConnectionPrivate::MTProtoConnectionPrivate(QThread *thread, MTProtoConne
, conn(0)
, retryTimeout(1)
, oldConnection(true)
, receiveDelay(MinReceiveDelay)
, receiveDelay(MTPMinReceiveDelay)
, firstSentAt(-1)
, pingId(0)
, toSendPingId(0)
@@ -1094,7 +1118,9 @@ MTProtoConnectionPrivate::MTProtoConnectionPrivate(QThread *thread, MTProtoConne
connect(this, SIGNAL(needToReceive()), sessionData->owner(), SLOT(tryToReceive()));
connect(this, SIGNAL(stateChanged(qint32)), sessionData->owner(), SLOT(onConnectionStateChange(qint32)));
connect(sessionData->owner(), SIGNAL(needToSend()), this, SLOT(tryToSend()));
connect(this, SIGNAL(needToSendAsync()), sessionData->owner(), SIGNAL(needToSend()));
connect(this, SIGNAL(sessionResetDone()), sessionData->owner(), SLOT(onResetDone()));
connect(this, SIGNAL(sendAnythingAsync(quint64)), sessionData->owner(), SLOT(sendAnything(quint64)));
}
void MTProtoConnectionPrivate::onConfigLoaded() {
@@ -1362,6 +1388,7 @@ void MTProtoConnectionPrivate::tryToSend() {
return;
}
bool needsLayer = !sessionData->layerWasInited();
bool prependOnly = false;
mtpRequest pingRequest;
if (toSendPingId) {
@@ -1370,7 +1397,7 @@ void MTProtoConnectionPrivate::tryToSend() {
prependOnly = (getState() != MTProtoConnection::Connected);
DEBUG_LOG(("MTP Info: sending ping, ping_id: %1, prepend_only: %2").arg(ping.vping_id.v).arg(prependOnly ? "[TRUE]" : "[FALSE]"));
uint32 pingSize = ping.size() >> 2; // copy from MTProtoSession::send
uint32 pingSize = ping.innerLength() >> 2; // copy from MTProtoSession::send
pingRequest = mtpRequestData::prepare(pingSize);
ping.write(*pingRequest);
@@ -1381,7 +1408,7 @@ void MTProtoConnectionPrivate::tryToSend() {
toSendPingId = 0;
} else {
int32 st = getState();
DEBUG_LOG(("MTP Info: trying to send after ping, state: %1").arg(st));
DEBUG_LOG(("MTP Info: dc %1 trying to send after ping, state: %2").arg(dc).arg(st));
if (st != MTProtoConnection::Connected) {
return; // just do nothing, if is not connected yet
}
@@ -1391,7 +1418,7 @@ void MTProtoConnectionPrivate::tryToSend() {
if (!prependOnly && !ackRequestData.isEmpty()) {
MTPMsgsAck ack(MTP_msgs_ack(MTP_vector<MTPlong>(ackRequestData)));
ackRequest = mtpRequestData::prepare(ack.size() >> 2);
ackRequest = mtpRequestData::prepare(ack.innerLength() >> 2);
ack.write(*ackRequest);
ackRequest->msDate = getms(true); // > 0 - can send without container
@@ -1402,7 +1429,7 @@ void MTProtoConnectionPrivate::tryToSend() {
if (!prependOnly && !resendRequestData.isEmpty()) {
MTPMsgResendReq resend(MTP_msg_resend_req(MTP_vector<MTPlong>(resendRequestData)));
resendRequest = mtpRequestData::prepare(resend.size() >> 2);
resendRequest = mtpRequestData::prepare(resend.innerLength() >> 2);
resend.write(*resendRequest);
resendRequest->msDate = getms(true); // > 0 - can send without container
@@ -1426,7 +1453,7 @@ void MTProtoConnectionPrivate::tryToSend() {
if (!stateReq.isEmpty()) {
MTPMsgsStateReq req(MTP_msgs_state_req(MTP_vector<MTPlong>(stateReq)));
stateRequest = mtpRequestData::prepare(req.size() >> 2);
stateRequest = mtpRequestData::prepare(req.innerLength() >> 2);
req.write(*stateRequest);
stateRequest->msDate = getms(true); // > 0 - can send without container
@@ -1434,6 +1461,14 @@ void MTProtoConnectionPrivate::tryToSend() {
}
}
MTPInitConnection<mtpRequest> initWrapperImpl, *initWrapper = &initWrapperImpl;
int32 initSize = 0, initSizeInInts = 0;
if (needsLayer) {
initWrapperImpl = MTPInitConnection<mtpRequest>(MTP_int(ApiId), MTP_string(cApiDeviceModel()), MTP_string(cApiSystemVersion()), MTP_string(cApiAppVersion()), MTP_string(ApiLang), mtpRequest());
initSizeInInts = (initWrapper->innerLength() >> 2) + 2;
initSize = initSizeInInts * sizeof(mtpPrime);
}
bool needAnyResponse = false;
mtpRequest toSendRequest;
{
@@ -1473,14 +1508,27 @@ void MTProtoConnectionPrivate::tryToSend() {
QWriteLocker locker2(sessionData->haveSentMutex());
mtpRequestMap &haveSent(sessionData->haveSentMap());
haveSent.insert(msgId, toSendRequest);
if (needsLayer && !toSendRequest->needsLayer) needsLayer = false;
if (toSendRequest->after) {
int32 toSendSize = toSendRequest->at(7) >> 2;
int32 toSendSize = toSendRequest.innerLength() >> 2;
mtpRequest wrappedRequest(mtpRequestData::prepare(toSendSize, toSendSize + 3)); // cons + msg_id
wrappedRequest->resize(4);
memcpy(wrappedRequest->data(), toSendRequest->constData(), 4 * sizeof(mtpPrime));
_mtp_internal::wrapInvokeAfter(wrappedRequest, toSendRequest, haveSent);
toSendRequest = wrappedRequest;
}
if (needsLayer) {
int32 noWrapSize = (toSendRequest.innerLength() >> 2), toSendSize = noWrapSize + initSizeInInts;
mtpRequest wrappedRequest(mtpRequestData::prepare(toSendSize));
memcpy(wrappedRequest->data(), toSendRequest->constData(), 7 * sizeof(mtpPrime)); // all except length
wrappedRequest->push_back(mtpc_invokeWithLayer);
wrappedRequest->push_back(mtpCurrentLayer);
initWrapper->write(*wrappedRequest);
wrappedRequest->resize(wrappedRequest->size() + noWrapSize);
memcpy(wrappedRequest->data() + wrappedRequest->size() - noWrapSize, toSendRequest->constData() + 8, noWrapSize * sizeof(mtpPrime));
toSendRequest = wrappedRequest;
}
needAnyResponse = true;
} else {
@@ -1489,6 +1537,7 @@ void MTProtoConnectionPrivate::tryToSend() {
}
}
} else { // send in container
bool willNeedInit = false;
uint32 containerSize = 1 + 1, idsWrapSize = (toSendCount << 1); // cons + vector size, idsWrapSize - size of "request-like" wrap for msgId vector
if (pingRequest) containerSize += mtpRequestData::messageSize(pingRequest);
if (ackRequest) containerSize += mtpRequestData::messageSize(ackRequest);
@@ -1496,6 +1545,17 @@ void MTProtoConnectionPrivate::tryToSend() {
if (stateRequest) containerSize += mtpRequestData::messageSize(stateRequest);
for (mtpPreRequestMap::iterator i = toSend.begin(), e = toSend.end(); i != e; ++i) {
containerSize += mtpRequestData::messageSize(i.value());
if (needsLayer && i.value()->needsLayer) {
containerSize += initSizeInInts;
willNeedInit = true;
}
}
mtpBuffer initSerialized;
if (willNeedInit) {
initSerialized.reserve(initSizeInInts);
initSerialized.push_back(mtpc_invokeWithLayer);
initSerialized.push_back(mtpCurrentLayer);
initWrapper->write(initSerialized);
}
toSendRequest = mtpRequestData::prepare(containerSize, containerSize + 3 * toSend.size()); // prepare container + each in invoke after
toSendRequest->push_back(mtpc_msg_container);
@@ -1530,8 +1590,20 @@ void MTProtoConnectionPrivate::tryToSend() {
if (req->requestId) {
if (mtpRequestData::needAck(req)) {
req->msDate = mtpRequestData::isStateRequest(req) ? 0 : getms(true);
int32 reqNeedsLayer = (needsLayer && req->needsLayer) ? toSendRequest->size() : 0;
if (req->after) {
_mtp_internal::wrapInvokeAfter(toSendRequest, req, haveSent);
_mtp_internal::wrapInvokeAfter(toSendRequest, req, haveSent, reqNeedsLayer ? initSizeInInts : 0);
if (reqNeedsLayer) {
memcpy(toSendRequest->data() + reqNeedsLayer + 4, initSerialized.constData(), initSize);
*(toSendRequest->data() + reqNeedsLayer + 3) += initSize;
}
added = true;
} else if (reqNeedsLayer) {
toSendRequest->resize(reqNeedsLayer + initSizeInInts + mtpRequestData::messageSize(req));
memcpy(toSendRequest->data() + reqNeedsLayer, req->constData() + 4, 4 * sizeof(mtpPrime));
memcpy(toSendRequest->data() + reqNeedsLayer + 4, initSerialized.constData(), initSize);
memcpy(toSendRequest->data() + reqNeedsLayer + 4 + initSizeInInts, req->constData() + 8, req.innerLength());
*(toSendRequest->data() + reqNeedsLayer + 3) += initSize;
added = true;
}
haveSent.insert(msgId, req);
@@ -1695,14 +1767,14 @@ void MTProtoConnectionPrivate::onReceivedSome() {
int32 ms = getms(true) - firstSentAt;
DEBUG_LOG(("MTP Info: response in %1ms, receiveDelay: %2ms").arg(ms).arg(receiveDelay));
if (ms > 0 && ms * 2 < int32(receiveDelay)) receiveDelay = qMax(ms * 2, int32(MinReceiveDelay));
if (ms > 0 && ms * 2 < int32(receiveDelay)) receiveDelay = qMax(ms * 2, int32(MTPMinReceiveDelay));
firstSentAt = -1;
}
}
void MTProtoConnectionPrivate::onOldConnection() {
oldConnection = true;
receiveDelay = MinReceiveDelay;
receiveDelay = MTPMinReceiveDelay;
DEBUG_LOG(("This connection marked as old! delay now %1ms").arg(receiveDelay));
}
@@ -1881,7 +1953,7 @@ void MTProtoConnectionPrivate::handleReceived() {
uint32 toAckSize = ackRequestData.size();
if (toAckSize) {
DEBUG_LOG(("MTP Info: will send %1 acks, ids: %2").arg(toAckSize).arg(logVectorLong(ackRequestData)));
sessionData->owner()->sendAnything(MTPAckSendWaiting);
emit sendAnythingAsync(MTPAckSendWaiting);
}
bool emitSignal = false;
@@ -1909,7 +1981,7 @@ void MTProtoConnectionPrivate::handleReceived() {
if (!wasConnected) {
if (getState() == MTProtoConnection::Connected) {
emit sessionData->owner()->needToSendAsync();
emit needToSendAsync();
}
}
}
@@ -2329,6 +2401,10 @@ int32 MTProtoConnectionPrivate::handleOneReceived(const mtpPrime *from, const mt
response.resize(end - from);
memcpy(response.data(), from, (end - from) * sizeof(mtpPrime));
}
if (!sessionData->layerWasInited()) {
sessionData->setLayerWasInited(true);
sessionData->owner()->notifyLayerInited(true);
}
mtpRequestId requestId = wasSent(reqMsgId.v);
if (requestId && requestId != mtpRequestId(0xFFFFFFFF)) {
@@ -2787,7 +2863,7 @@ void MTProtoConnectionPrivate::pqAnswered() {
string &dhEncString(req_DH_params.vencrypted_data._string().v);
uint32 p_q_inner_size = p_q_inner.size(), encSize = (p_q_inner_size >> 2) + 6;
uint32 p_q_inner_size = p_q_inner.innerLength(), encSize = (p_q_inner_size >> 2) + 6;
if (encSize >= 65) {
mtpBuffer tmp;
tmp.reserve(encSize);
@@ -2854,7 +2930,7 @@ void MTProtoConnectionPrivate::dhParamsAnswered() {
return restart();
}
uint32 nlen = authKeyData->new_nonce.size(), slen = authKeyData->server_nonce.size();
uint32 nlen = authKeyData->new_nonce.innerLength(), slen = authKeyData->server_nonce.innerLength();
uchar tmp_aes[1024], sha1ns[20], sha1sn[20], sha1nn[20];
memcpy(tmp_aes, &authKeyData->new_nonce, nlen);
memcpy(tmp_aes + nlen, &authKeyData->server_nonce, slen);
@@ -2979,7 +3055,7 @@ void MTProtoConnectionPrivate::dhClientParamsSend() {
string &sdhEncString(req_client_DH_params.vencrypted_data._string().v);
uint32 client_dh_inner_size = client_dh_inner.size(), encSize = (client_dh_inner_size >> 2) + 5, encFullSize = encSize;
uint32 client_dh_inner_size = client_dh_inner.innerLength(), encSize = (client_dh_inner_size >> 2) + 5, encFullSize = encSize;
if (encSize & 0x03) {
encFullSize += 4 - (encSize & 0x03);
}
@@ -3047,7 +3123,7 @@ void MTProtoConnectionPrivate::dhClientParamsAnswered() {
DEBUG_LOG(("AuthKey Info: auth key gen succeed, id: %1, server salt: %2, auth key: %3").arg(authKey->keyId()).arg(serverSalt).arg(mb(authKeyData->auth_key, 256).str()));
sessionData->owner()->keyCreated(authKey); // slot will call authKeyCreated()
sessionData->owner()->notifyKeyCreated(authKey); // slot will call authKeyCreated()
sessionData->clear();
unlockKey();
} return;
@@ -3116,7 +3192,7 @@ void MTProtoConnectionPrivate::authKeyCreated() {
toSendPingId = MTP::nonce<uint64>(); // get server_salt
emit sessionData->owner()->needToSendAsync();
emit needToSendAsync();
// disconnect(&pinger, SIGNAL(timeout()), 0, 0);
// connect(&pinger, SIGNAL(timeout()), this, SLOT(sendPing()));
@@ -3146,7 +3222,7 @@ void MTProtoConnectionPrivate::sendPing() {
}
void MTProtoConnectionPrivate::onError(bool mayBeBadKey) {
MTP_LOG(dc, ("Restarting after error.."));
MTP_LOG(dc, ("Restarting after error, maybe bad key: %1..").arg(logBool(mayBeBadKey)));
return restart(mayBeBadKey);
}
@@ -3157,7 +3233,7 @@ template <typename TRequest>
void MTProtoConnectionPrivate::sendRequestNotSecure(const TRequest &request) {
try {
mtpBuffer buffer;
uint32 requestSize = request.size() >> 2;
uint32 requestSize = request.innerLength() >> 2;
buffer.resize(0);
buffer.reserve(8 + requestSize);
@@ -3264,6 +3340,7 @@ 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);
if (needAnyResponse) {

View File

@@ -106,6 +106,13 @@ class MTPabstractConnection : public QObject {
public:
MTPabstractConnection() : _sentEncrypted(false) {
}
void setSentEncrypted() {
_sentEncrypted = true;
}
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;
@@ -135,6 +142,7 @@ signals:
protected:
BuffersQueue receivedQueue; // list of received packets, not processed yet
bool _sentEncrypted;
};
@@ -189,6 +197,8 @@ public slots:
void onSocketDisconnected();
void onHttpStart();
void onTcpTimeoutTimer();
protected:
void socketPacket(mtpPrime *packet, uint32 packetSize);
@@ -215,6 +225,10 @@ private:
typedef QSet<QNetworkReply*> Requests;
Requests requests;
QString _addr;
int32 _port, _tcpTimeout;
QTimer tcpTimeoutTimer;
};
class MTPtcpConnection : public MTPabstractTcpConnection {
@@ -295,6 +309,8 @@ signals:
void needToRestart();
void stateChanged(qint32 newState);
void sessionResetDone();
void needToSendAsync();
void sendAnythingAsync(quint64);
public slots:

View File

@@ -172,12 +172,20 @@ void mtpTextSerializeCore(MTPStringLogger &to, const mtpPrime *&from, const mtpP
} break;
default: {
for (uint32 i = 1; i < mtpLayerMax; ++i) {
for (uint32 i = 1; i < mtpLayerMaxSingle; ++i) {
if (cons == mtpLayers[i]) {
to.add("[LAYER").add(mtpWrapNumber(i + 1)).add("] "); mtpTextSerializeType(to, from, end, 0, level);
return;
}
}
if (cons == mtpc_invokeWithLayer) {
if (from >= end) {
throw Exception("from >= end in invokeWithLayer");
}
int32 layer = *(from++);
to.add("[LAYER").add(mtpWrapNumber(layer)).add("] "); mtpTextSerializeType(to, from, end, 0, level);
return;
}
throw Exception(QString("unknown cons 0x%1").arg(cons, 0, 16));
} break;
}

View File

@@ -62,7 +62,7 @@ public:
explicit mtpRequest(mtpRequestData *ptr) : QSharedPointer<mtpRequestData>(ptr) {
}
uint32 size() const;
uint32 innerLength() const;
void write(mtpBuffer &to) const;
typedef void ResponseType; // don't know real response type =(
@@ -74,10 +74,12 @@ public:
// in toSend: = 0 - must send in container, > 0 - can send without container
// in haveSent: = 0 - container with msgIds, > 0 - when was sent
uint64 msDate;
mtpRequestId requestId;
mtpRequest after;
bool needsLayer;
mtpRequestData(bool/* sure*/) : msDate(0), requestId(0) {
mtpRequestData(bool/* sure*/) : msDate(0), requestId(0), needsLayer(false) {
}
static mtpRequest prepare(uint32 requestSize, uint32 maxSize = 0) {
@@ -92,7 +94,7 @@ public:
static void padding(mtpRequest &request) {
if (request->size() < 9) return;
uint32 requestSize = ((*request)[7] >> 2), padding = _padding(requestSize), fullSize = 8 + requestSize + padding; // 2: salt, 2: session_id, 2: msg_id, 1: seq_no, 1: message_length
uint32 requestSize = (request.innerLength() >> 2), padding = _padding(requestSize), fullSize = 8 + requestSize + padding; // 2: salt, 2: session_id, 2: msg_id, 1: seq_no, 1: message_length
if (uint32(request->size()) != fullSize) {
request->resize(fullSize);
if (padding) {
@@ -103,7 +105,7 @@ public:
static uint32 messageSize(const mtpRequest &request) {
if (request->size() < 9) return 0;
return 4 + ((*request)[7] >> 2); // 2: msg_id, 1: seq_no, q: message_length
return 4 + (request.innerLength() >> 2); // 2: msg_id, 1: seq_no, q: message_length
}
static bool isSentContainer(const mtpRequest &request); // "request-like" wrap for msgIds vector
@@ -119,7 +121,7 @@ private:
};
inline uint32 mtpRequest::size() const { // for template MTP requests and MTPBoxed instanciation
inline uint32 mtpRequest::innerLength() const { // for template MTP requests and MTPBoxed instanciation
mtpRequestData *value = data();
if (!value || value->size() < 9) return 0;
return value->at(7);
@@ -128,7 +130,7 @@ inline uint32 mtpRequest::size() const { // for template MTP requests and MTPBox
inline void mtpRequest::write(mtpBuffer &to) const {
mtpRequestData *value = data();
if (!value || value->size() < 9) return;
uint32 was = to.size(), s = size() / sizeof(mtpPrime);
uint32 was = to.size(), s = innerLength() / sizeof(mtpPrime);
to.resize(was + s);
memcpy(to.data() + was, value->constData() + 8, s * sizeof(mtpPrime));
}
@@ -335,6 +337,8 @@ enum {
mtpc_invokeWithLayer17 = 0x50858a19,
mtpc_invokeWithLayer18 = 0x1c900537,
mtpc_invokeWithLayer = 0xda9b0d0d, // after 18 layer
// manually parsed
mtpc_rpc_result = 0xf35c6d01,
mtpc_msg_container = 0x73f1f8dc,
@@ -362,7 +366,8 @@ static const mtpTypeId mtpLayers[] = {
mtpc_invokeWithLayer16,
mtpc_invokeWithLayer17,
mtpc_invokeWithLayer18,
}, mtpLayerMax = sizeof(mtpLayers) / sizeof(mtpLayers[0]);
}, mtpLayerMaxSingle = sizeof(mtpLayers) / sizeof(mtpLayers[0]);
static const mtpPrime mtpCurrentLayer = 19;
template <typename bareT>
class MTPBoxed : public bareT {
@@ -386,8 +391,8 @@ public:
return *this;
}
uint32 size() const {
return sizeof(mtpTypeId) + bareT::size();
uint32 innerLength() const {
return sizeof(mtpTypeId) + bareT::innerLength();
}
void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) {
if (from + 1 > end) throw mtpErrorInsufficient();
@@ -414,7 +419,7 @@ public:
read(from, end, cons);
}
uint32 size() const {
uint32 innerLength() const {
return sizeof(int32);
}
mtpTypeId type() const {
@@ -457,7 +462,7 @@ public:
read(from, end, cons);
}
uint32 size() const {
uint32 innerLength() const {
return sizeof(uint64);
}
mtpTypeId type() const {
@@ -503,7 +508,7 @@ public:
read(from, end, cons);
}
uint32 size() const {
uint32 innerLength() const {
return sizeof(uint64) + sizeof(uint64);
}
mtpTypeId type() const {
@@ -552,8 +557,8 @@ public:
read(from, end, cons);
}
uint32 size() const {
return l.size() + h.size();
uint32 innerLength() const {
return l.innerLength() + h.innerLength();
}
mtpTypeId type() const {
return mtpc_int256;
@@ -596,7 +601,7 @@ public:
read(from, end, cons);
}
uint32 size() const {
uint32 innerLength() const {
return sizeof(float64);
}
mtpTypeId type() const {
@@ -666,7 +671,7 @@ public:
return *(const MTPDstring*)data;
}
uint32 size() const {
uint32 innerLength() const {
uint32 l = c_string().v.length();
if (l < 254) {
l += 1;
@@ -770,7 +775,7 @@ public:
read(from, end, cons);
}
uint32 size() const {
uint32 innerLength() const {
return 0;
}
mtpTypeId type() const {
@@ -858,10 +863,10 @@ public:
return *(const MTPDvector<T>*)data;
}
uint32 size() const {
uint32 innerLength() const {
uint32 result(sizeof(uint32));
for (typename VType::const_iterator i = c_vector().v.cbegin(), e = c_vector().v.cend(); i != e; ++i) {
result += i->size();
result += i->innerLength();
}
return result;
}
@@ -961,8 +966,8 @@ public:
return *(const MTPDerror*)data;
}
uint32 size() const {
return c_error().vcode.size() + c_error().vtext.size();
uint32 innerLength() const {
return c_error().vcode.innerLength() + c_error().vtext.innerLength();
}
mtpTypeId type() const {
return mtpc_error;
@@ -999,7 +1004,7 @@ public:
read(from, end, cons);
}
uint32 size() const {
uint32 innerLength() const {
return 0;
}
mtpTypeId type() const {

View File

@@ -19,6 +19,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "mtpDC.h"
#include "mtp.h"
#include "localstorage.h"
namespace {
MTProtoDCMap gDCs;
@@ -65,7 +67,7 @@ namespace {
QByteArray data, decrypted;
stream >> data;
if (!MTP::localKey().created()) {
if (!Local::oldKey().created()) {
LOG(("MTP Error: reading encrypted keys without local key!"));
continue;
}
@@ -77,7 +79,7 @@ namespace {
uint32 fullDataLen = data.size() - 16;
decrypted.resize(fullDataLen);
const char *dataKey = data.constData(), *encrypted = data.constData() + 16;
aesDecryptLocal(encrypted, decrypted.data(), fullDataLen, &MTP::localKey(), dataKey);
aesDecryptLocal(encrypted, decrypted.data(), fullDataLen, &Local::oldKey(), dataKey);
uchar sha1Buffer[20];
if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), dataKey, 16)) {
LOG(("MTP Error: bad decrypt key, data from user-config not decrypted"));
@@ -271,7 +273,7 @@ namespace {
}
QByteArray encrypted(16 + fullSize, Qt::Uninitialized); // 128bit of sha1 - key128, sizeof(data), data
hashSha1(toEncrypt.constData(), toEncrypt.size(), encrypted.data());
aesEncryptLocal(toEncrypt.constData(), encrypted.data() + 16, fullSize, &MTP::localKey(), encrypted.constData());
aesEncryptLocal(toEncrypt.constData(), encrypted.data() + 16, fullSize, &Local::oldKey(), encrypted.constData());
DEBUG_LOG(("MTP Info: keys file opened for writing %1 keys").arg(keysToWrite.size()));
QDataStream keysStream(&keysFile);
@@ -350,7 +352,7 @@ void mtpSetDC(int32 dc) {
}
}
MTProtoDC::MTProtoDC(int32 id, const mtpAuthKeyPtr &key) : _id(id), _key(key), _connectionInited(false), _connectionInitSent(false) {
MTProtoDC::MTProtoDC(int32 id, const mtpAuthKeyPtr &key) : _id(id), _key(key), _connectionInited(false) {
connect(this, SIGNAL(authKeyCreated()), this, SLOT(authKeyWrite()), Qt::QueuedConnection);
QMutexLocker lock(&_keysMapForWriteMutex);
@@ -371,6 +373,7 @@ void MTProtoDC::authKeyWrite() {
void MTProtoDC::setKey(const mtpAuthKeyPtr &key) {
DEBUG_LOG(("AuthKey Info: MTProtoDC::setKey(%1), emitting authKeyCreated, dc %2").arg(key ? key->keyId() : 0).arg(_id));
_key = key;
_connectionInited = false;
emit authKeyCreated();
QMutexLocker lock(&_keysMapForWriteMutex);

View File

@@ -29,13 +29,6 @@ public:
void setKey(const mtpAuthKeyPtr &key);
void destroyKey();
bool needConnectionInit() {
QMutexLocker lock(&initLock);
if (_connectionInited || _connectionInitSent) return false;
_connectionInitSent = true;
return true;
}
bool connectionInited() const {
QMutexLocker lock(&initLock);
bool res = _connectionInited;
@@ -49,6 +42,7 @@ public:
signals:
void authKeyCreated();
void layerWasInited(bool was);
private slots:
@@ -61,7 +55,6 @@ private:
int32 _id;
mtpAuthKeyPtr _key;
bool _connectionInited;
bool _connectionInitSent;
};
typedef QSharedPointer<MTProtoDC> MTProtoDCPtr;

View File

@@ -20,6 +20,7 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "window.h"
#include "application.h"
#include "localstorage.h"
namespace {
int32 _priority = 1;
@@ -44,9 +45,9 @@ namespace {
}
mtpFileLoader::mtpFileLoader(int32 dc, const int64 &volume, int32 local, const int64 &secret, int32 size) : prev(0), next(0),
priority(0), inQueue(false), complete(false), skippedBytes(0), nextRequestOffset(0), lastComplete(false),
priority(0), inQueue(false), complete(false), triedLocal(false), skippedBytes(0), nextRequestOffset(0), lastComplete(false),
dc(dc), locationType(0), volume(volume), local(local), secret(secret),
id(0), access(0), size(size), type(MTP_storage_fileUnknown()) {
id(0), access(0), fileIsOpen(false), size(size), type(mtpc_storage_fileUnknown) {
LoaderQueues::iterator i = queues.find(dc);
if (i == queues.cend()) {
i = queues.insert(dc, mtpFileLoaderQueue());
@@ -55,9 +56,9 @@ id(0), access(0), size(size), type(MTP_storage_fileUnknown()) {
}
mtpFileLoader::mtpFileLoader(int32 dc, const uint64 &id, const uint64 &access, mtpTypeId locType, const QString &to, int32 size) : prev(0), next(0),
priority(0), inQueue(false), complete(false), skippedBytes(0), nextRequestOffset(0), lastComplete(false),
priority(0), inQueue(false), complete(false), triedLocal(false), skippedBytes(0), nextRequestOffset(0), lastComplete(false),
dc(dc), locationType(locType),
id(id), access(access), file(to), duplicateInData(false), size(size), type(MTP_storage_fileUnknown()) {
id(id), access(access), file(to), fname(to), fileIsOpen(false), duplicateInData(false), size(size), type(mtpc_storage_fileUnknown) {
LoaderQueues::iterator i = queues.find(MTP::dld[0] + dc);
if (i == queues.cend()) {
i = queues.insert(MTP::dld[0] + dc, mtpFileLoaderQueue());
@@ -66,9 +67,9 @@ id(id), access(access), file(to), duplicateInData(false), size(size), type(MTP_s
}
mtpFileLoader::mtpFileLoader(int32 dc, const uint64 &id, const uint64 &access, mtpTypeId locType, const QString &to, int32 size, bool todata) : prev(0), next(0),
priority(0), inQueue(false), complete(false), skippedBytes(0), nextRequestOffset(0), lastComplete(false),
priority(0), inQueue(false), complete(false), triedLocal(false), skippedBytes(0), nextRequestOffset(0), lastComplete(false),
dc(dc), locationType(locType),
id(id), access(access), file(to), duplicateInData(todata), size(size), type(MTP_storage_fileUnknown()) {
id(id), access(access), file(to), fname(to), fileIsOpen(false), duplicateInData(todata), size(size), type(mtpc_storage_fileUnknown) {
LoaderQueues::iterator i = queues.find(MTP::dld[0] + dc);
if (i == queues.cend()) {
i = queues.insert(MTP::dld[0] + dc, mtpFileLoaderQueue());
@@ -77,7 +78,7 @@ id(id), access(access), file(to), duplicateInData(todata), size(size), type(MTP_
}
QString mtpFileLoader::fileName() const {
return file.fileName();
return fname;
}
bool mtpFileLoader::done() const {
@@ -85,7 +86,7 @@ bool mtpFileLoader::done() const {
}
mtpTypeId mtpFileLoader::fileType() const {
return type.type();
return type;
}
const QByteArray &mtpFileLoader::bytes() const {
@@ -99,16 +100,16 @@ float64 mtpFileLoader::currentProgress() const {
}
int32 mtpFileLoader::currentOffset(bool includeSkipped) const {
return (file.isOpen() ? file.size() : data.size()) - (includeSkipped ? 0 : skippedBytes);
return (fileIsOpen ? file.size() : data.size()) - (includeSkipped ? 0 : skippedBytes);
}
int32 mtpFileLoader::fullSize() const {
return size;
}
void mtpFileLoader::setFileName(const QString &fname) {
if (duplicateInData && file.fileName().isEmpty()) {
file.setFileName(fname);
void mtpFileLoader::setFileName(const QString &fileName) {
if (duplicateInData && fname.isEmpty()) {
file.setFileName(fname = fileName);
}
}
@@ -130,15 +131,16 @@ void mtpFileLoader::loadNext() {
void mtpFileLoader::finishFail() {
bool started = currentOffset(true) > 0;
cancelRequests();
type = MTP_storage_fileUnknown();
type = mtpc_storage_fileUnknown;
complete = true;
if (file.isOpen()) {
if (fileIsOpen) {
file.close();
fileIsOpen = false;
file.remove();
}
data = QByteArray();
emit failed(this, started);
file.setFileName(QString());
file.setFileName(fname = QString());
loadNext();
}
@@ -173,8 +175,7 @@ bool mtpFileLoader::loadPart() {
App::app()->killDownloadSessionsStop(dc);
}
MTPupload_GetFile request(MTPupload_getFile(loc, MTP_int(offset), MTP_int(limit)));
mtpRequestId reqId = MTP::send(request, rpcDone(&mtpFileLoader::partLoaded, offset), rpcFail(&mtpFileLoader::partFailed), MTP::dld[dcIndex] + dc, 50);
mtpRequestId reqId = MTP::send(MTPupload_GetFile(MTPupload_getFile(loc, MTP_int(offset), MTP_int(limit))), rpcDone(&mtpFileLoader::partLoaded, offset), rpcFail(&mtpFileLoader::partFailed), MTP::dld[dcIndex] + dc, 50);
++queue->queries;
dr.v[dcIndex] += limit;
@@ -186,7 +187,7 @@ bool mtpFileLoader::loadPart() {
void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRequestId req) {
Requests::iterator i = requests.find(req);
if (i == requests.cend()) return;
if (i == requests.cend()) return loadNext();
int32 limit = locationType ? DocumentDownloadPartSize : DownloadPartSize;
int32 dcIndex = i.value();
@@ -198,7 +199,7 @@ void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRe
const MTPDupload_file &d(result.c_upload_file());
const string &bytes(d.vbytes.c_string().v);
if (bytes.size()) {
if (file.isOpen()) {
if (fileIsOpen) {
int64 fsize = file.size();
if (offset < fsize) {
skippedBytes -= bytes.size();
@@ -230,27 +231,32 @@ void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRe
lastComplete = true;
}
if (requests.isEmpty() && (lastComplete || (size && nextRequestOffset >= size))) {
if (duplicateInData && !file.fileName().isEmpty()) {
if (!file.open(QIODevice::WriteOnly)) {
if (!fname.isEmpty() && duplicateInData) {
if (!fileIsOpen) fileIsOpen = file.open(QIODevice::WriteOnly);
if (!fileIsOpen) {
return finishFail();
}
if (file.write(data) != qint64(data.size())) {
return finishFail();
}
}
type = d.vtype;
type = d.vtype.type();
complete = true;
if (file.isOpen()) {
if (fileIsOpen) {
file.close();
fileIsOpen = false;
psPostprocessFile(QFileInfo(file).absoluteFilePath());
}
removeFromQueue();
App::wnd()->update();
App::wnd()->notifyUpdateAllPhotos();
if (!queue->queries && dcIndex) {
App::app()->killDownloadSessionsStart(dc);
}
if (!locationType && triedLocal && (fname.isEmpty() || duplicateInData)) {
Local::writeImage(storageKey(dc, volume, local), StorageImageSaved(type, data));
}
}
emit progress(this);
loadNext();
@@ -285,11 +291,38 @@ void mtpFileLoader::pause() {
void mtpFileLoader::start(bool loadFirst, bool prior) {
if (complete) return;
if (!locationType && !triedLocal) {
triedLocal = true;
StorageImageSaved cached = Local::readImage(storageKey(dc, volume, local));
if (cached.type != mtpc_storage_fileUnknown) {
data = cached.data;
if (!fname.isEmpty() && duplicateInData) {
if (!fileIsOpen) fileIsOpen = file.open(QIODevice::WriteOnly);
if (!fileIsOpen) {
return finishFail();
}
if (file.write(data) != qint64(data.size())) {
return finishFail();
}
}
type = cached.type;
complete = true;
if (fileIsOpen) {
file.close();
fileIsOpen = false;
psPostprocessFile(QFileInfo(file).absoluteFilePath());
}
App::wnd()->update();
App::wnd()->notifyUpdateAllPhotos();
emit progress(this);
return loadNext();
}
}
if (!file.fileName().isEmpty() && !duplicateInData) {
if (!file.open(QIODevice::WriteOnly)) {
finishFail();
return;
if (!fname.isEmpty() && !duplicateInData && !fileIsOpen) {
fileIsOpen = file.open(QIODevice::WriteOnly);
if (!fileIsOpen) {
return finishFail();
}
}
@@ -383,10 +416,11 @@ void mtpFileLoader::start(bool loadFirst, bool prior) {
void mtpFileLoader::cancel() {
cancelRequests();
type = MTP_storage_fileUnknown();
type = mtpc_storage_fileUnknown;
complete = true;
if (file.isOpen()) {
if (fileIsOpen) {
file.close();
fileIsOpen = false;
file.remove();
}
data = QByteArray();

View File

@@ -60,7 +60,7 @@ signals:
private:
mtpFileLoaderQueue *queue;
bool inQueue, complete;
bool inQueue, complete, triedLocal;
void cancelRequests();
@@ -89,11 +89,13 @@ private:
uint64 id; // for other locations
uint64 access;
QFile file;
QString fname;
bool fileIsOpen;
bool duplicateInData;
QByteArray data;
int32 size;
MTPstorage_FileType type;
mtpTypeId type;
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -83,9 +83,6 @@ void MTProtoSession::start(int32 dcenter, uint32 connects) {
timeouter.start(1000);
connect(&sender, SIGNAL(timeout()), this, SIGNAL(needToSend()));
connect(this, SIGNAL(startSendTimer(int)), &sender, SLOT(start(int)));
connect(this, SIGNAL(stopSendTimer()), &sender, SLOT(stop()));
connect(this, SIGNAL(needToSendAsync()), this, SIGNAL(needToSend()));
MTProtoDCMap &dcs(mtpDCMap());
@@ -113,8 +110,11 @@ void MTProtoSession::start(int32 dcenter, uint32 connects) {
ReadLockerAttempt lock(keyMutex());
data.setKey(lock ? dc->getKey() : mtpAuthKeyPtr(0));
if (lock && dc->connectionInited()) {
data.setLayerWasInited(true);
}
connect(dc.data(), SIGNAL(authKeyCreated()), this, SLOT(authKeyCreatedForDC()));
connect(dc.data(), SIGNAL(layerWasInited(bool)), this, SLOT(layerWasInitedForDC(bool)));
}
}
}
@@ -126,13 +126,13 @@ void MTProtoSession::restart() {
}
void MTProtoSession::stop() {
while (connections.size()) {
while (!connections.isEmpty()) {
connections.back()->stop();
connections.pop_back();
}
}
void MTProtoSession::sendAnything(uint64 msCanWait) {
void MTProtoSession::sendAnything(quint64 msCanWait) {
uint64 ms = getms(true);
if (msSendCall) {
if (ms > msSendCall + msWait) {
@@ -147,13 +147,14 @@ void MTProtoSession::sendAnything(uint64 msCanWait) {
msWait = msCanWait;
}
if (msWait) {
DEBUG_LOG(("MTP Info: dc %1 can wait for %2ms from current %3").arg(dcId).arg(msWait).arg(msSendCall));
msSendCall = ms;
emit startSendTimer(msWait);
DEBUG_LOG(("MTP Info: can wait for %1ms from current %2").arg(msWait).arg(msSendCall));
sender.start(msWait);
} else {
emit stopSendTimer();
DEBUG_LOG(("MTP Info: dc %1 stopped send timer, can wait for %2ms from current %3").arg(dcId).arg(msWait).arg(msSendCall));
sender.stop();
msSendCall = 0;
emit needToSendAsync();
emit needToSend();
}
}
@@ -376,21 +377,9 @@ void MTProtoSession::sendPrepared(const mtpRequest &request, uint64 msCanWait, b
sendAnything(msCanWait);
}
void MTProtoSession::sendPreparedWithInit(const mtpRequest &request, uint64 msCanWait) { // returns true, if emit of needToSend() is needed
if (request->size() > 8 && request->at(8) == mtpc_initConnection) {
sendPrepared(request, msCanWait, false);
return;
}
{
MTPInitConnection<mtpRequest> requestWrap(MTPinitConnection<mtpRequest>(MTP_int(ApiId), MTP_string(cApiDeviceModel()), MTP_string(cApiSystemVersion()), MTP_string(cApiAppVersion()), MTP_string(ApiLang), request));
uint32 requestSize = requestWrap.size() >> 2;
mtpRequest reqSerialized(mtpRequestData::prepare(requestSize));
requestWrap.write(*reqSerialized);
request->resize(reqSerialized->size());
memcpy(request->data(), reqSerialized->constData(), reqSerialized->size());
}
request->msDate = getms(true); // > 0 - can send without container
sendPrepared(request, msCanWait);
void MTProtoSession::sendPreparedWithInit(const mtpRequest &request, uint64 msCanWait) {
request->needsLayer = true;
sendPrepared(request, msCanWait, false);
}
QReadWriteLock *MTProtoSession::keyMutex() const {
@@ -403,11 +392,22 @@ void MTProtoSession::authKeyCreatedForDC() {
emit authKeyCreated();
}
void MTProtoSession::keyCreated(const mtpAuthKeyPtr &key) {
void MTProtoSession::notifyKeyCreated(const mtpAuthKeyPtr &key) {
DEBUG_LOG(("AuthKey Info: MTProtoSession::keyCreated(), setting, dc %1").arg(dcId));
dc->setKey(key);
}
void MTProtoSession::layerWasInitedForDC(bool wasInited) {
DEBUG_LOG(("MTP Info: MTProtoSession::layerWasInitedForDC slot, dc %1").arg(dcId));
data.setLayerWasInited(wasInited);
}
void MTProtoSession::notifyLayerInited(bool wasInited) {
DEBUG_LOG(("MTP Info: emitting MTProtoDC::layerWasInited(%1), dc %2").arg(logBool(wasInited)).arg(dcId));
dc->setConnectionInited(wasInited);
emit dc->layerWasInited(wasInited);
}
void MTProtoSession::destroyKey() {
if (!dc) return;

View File

@@ -28,8 +28,8 @@ public:
MTPSessionData(MTProtoSession *creator)
: _session(0), _salt(0)
, _messagesSent(0), fakeRequestId(-2000000000)
, _owner(creator), keyChecked(false) {
, _messagesSent(0), _fakeRequestId(-2000000000)
, _owner(creator), _keyChecked(false), _layerInited(false) {
}
void setSession(uint64 session) {
@@ -45,6 +45,14 @@ public:
QReadLocker locker(&lock);
return _session;
}
bool layerWasInited() const {
QReadLocker locker(&lock);
return _layerInited;
}
void setLayerWasInited(bool was) {
QWriteLocker locker(&lock);
_layerInited = was;
}
void setSalt(uint64 salt) {
QWriteLocker locker(&lock);
@@ -56,26 +64,31 @@ public:
}
const mtpAuthKeyPtr &getKey() const {
return authKey;
return _authKey;
}
void setKey(const mtpAuthKeyPtr &key) {
if (authKey != key) {
if (_authKey != key) {
uint64 session;
memsetrnd(session);
authKey = key;
_authKey = key;
DEBUG_LOG(("MTP Info: new auth key set in SessionData, id %1, setting random server_session %2").arg(key ? key->keyId() : 0).arg(session));
setSession(session);
QWriteLocker locker(&lock);
if (_session != session) {
_session = session;
_messagesSent = 0;
}
_layerInited = false;
}
}
bool isCheckedKey() const {
QReadLocker locker(&lock);
return keyChecked;
return _keyChecked;
}
void setCheckedKey(bool checked) {
QWriteLocker locker(&lock);
keyChecked = checked;
_keyChecked = checked;
}
QReadWriteLock *keyMutex() const;
@@ -147,11 +160,11 @@ public:
mtpRequestId nextFakeRequestId() { // must be locked by haveReceivedMutex()
if (haveReceived.isEmpty() || haveReceived.cbegin().key() > 0) {
fakeRequestId = -2000000000;
_fakeRequestId = -2000000000;
} else {
++fakeRequestId;
++_fakeRequestId;
}
return fakeRequestId;
return _fakeRequestId;
}
MTProtoSession *owner() {
@@ -174,12 +187,12 @@ private:
uint64 _session, _salt;
uint32 _messagesSent;
mtpRequestId fakeRequestId;
mtpRequestId _fakeRequestId;
MTProtoSession *_owner;
mtpAuthKeyPtr authKey;
bool keyChecked;
mtpAuthKeyPtr _authKey;
bool _keyChecked, _layerInited;
mtpPreRequestMap toSend; // map of request_id -> request, that is waiting to be sent
mtpRequestMap haveSent; // map of msg_id -> request, that was sent, msDate = 0 for msgs_state_req (no resend / state req), msDate = 0, seqNo = 0 for containers
@@ -216,12 +229,12 @@ public:
~MTProtoSession();
QReadWriteLock *keyMutex() const;
void keyCreated(const mtpAuthKeyPtr &key);
void notifyKeyCreated(const mtpAuthKeyPtr &key);
void destroyKey();
void notifyLayerInited(bool wasInited);
template <typename TRequest>
mtpRequestId send(const TRequest &request, RPCResponseHandler callbacks = RPCResponseHandler(), uint64 msCanWait = 0, uint32 layer = 0, bool toMainDC = false, mtpRequestId after = 0); // send mtp request
void sendAnything(uint64 msCanWait);
mtpRequestId send(const TRequest &request, RPCResponseHandler callbacks = RPCResponseHandler(), uint64 msCanWait = 0, bool needsLayer = false, bool toMainDC = false, mtpRequestId after = 0); // send mtp request
void cancel(mtpRequestId requestId, mtpMsgId msgId);
int32 requestState(mtpRequestId requestId) const;
@@ -239,25 +252,21 @@ signals:
void authKeyCreated();
void needToSend();
void needToSendAsync(); // emit this signal, to emit needToSend() in MTProtoSession thread
void startSendTimer(int msec); // manipulating timer from all threads
void stopSendTimer();
public slots:
void authKeyCreatedForDC();
void layerWasInitedForDC(bool wasInited);
void tryToReceive();
void checkRequestsByTimer();
void onConnectionStateChange(qint32 newState);
void onResetDone();
void sendAnything(quint64 msCanWait);
private:
template <typename TRequest>
mtpRequestId sendFirst(const MTPInitConnection<TRequest> &request, RPCResponseHandler callbacks = RPCResponseHandler(), uint64 msCanWait = 0, uint32 layer = 0, bool toMainDC = false, mtpRequestId after = 0); // send first mtp request
typedef QList<MTProtoConnection*> MTProtoConnections;
MTProtoConnections connections;

View File

@@ -18,22 +18,17 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#pragma once
template <typename TRequest>
mtpRequestId MTProtoSession::send(const TRequest &request, RPCResponseHandler callbacks, uint64 msCanWait, uint32 layer, bool toMainDC, mtpRequestId after) {
mtpRequestId MTProtoSession::send(const TRequest &request, RPCResponseHandler callbacks, uint64 msCanWait, bool needsLayer, bool toMainDC, mtpRequestId after) {
mtpRequestId requestId = 0;
if (layer && dc->needConnectionInit()) {
MTPInitConnection<TRequest> requestWrap(MTPinitConnection<TRequest>(MTP_int(ApiId), MTP_string(cApiDeviceModel()), MTP_string(cApiSystemVersion()), MTP_string(cApiAppVersion()), MTP_string(ApiLang), request));
return sendFirst(requestWrap, callbacks, msCanWait, layer, toMainDC, after);
}
try {
uint32 requestSize = request.size() >> 2;
if (dc->connectionInited()) layer = 0;
mtpRequest reqSerialized(mtpRequestData::prepare(requestSize + (layer ? 1 : 0)));
if (layer) reqSerialized->push_back(mtpLayers[layer]);
uint32 requestSize = request.innerLength() >> 2;
mtpRequest reqSerialized(mtpRequestData::prepare(requestSize));
request.write(*reqSerialized);
DEBUG_LOG(("MTP Info: adding request to toSendMap, msCanWait %1").arg(msCanWait));
reqSerialized->msDate = getms(true); // > 0 - can send without container
reqSerialized->needsLayer = needsLayer;
if (after) reqSerialized->after = _mtp_internal::getRequest(after);
requestId = _mtp_internal::storeRequest(reqSerialized, callbacks);
@@ -45,44 +40,3 @@ mtpRequestId MTProtoSession::send(const TRequest &request, RPCResponseHandler ca
if (requestId) _mtp_internal::registerRequest(requestId, toMainDC ? -getDC() : getDC());
return requestId;
}
class RPCWrappedDcDoneHandler : public RPCAbstractDoneHandler {
public:
RPCWrappedDcDoneHandler(const MTProtoDCPtr &dc, const RPCDoneHandlerPtr &ondone) : _dc(dc), _ondone(ondone) {
}
void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) const {
_dc->setConnectionInited();
if (_ondone) (*_ondone)(requestId, from, end);
}
private:
MTProtoDCPtr _dc;
RPCDoneHandlerPtr _ondone;
};
template <typename TRequest>
mtpRequestId MTProtoSession::sendFirst(const MTPInitConnection<TRequest> &request, RPCResponseHandler callbacks, uint64 msCanWait, uint32 layer, bool toMainDC, mtpRequestId after) {
mtpRequestId requestId = 0;
try {
uint32 requestSize = request.size() >> 2;
mtpRequest reqSerialized(mtpRequestData::prepare(requestSize + (layer ? 1 : 0)));
if (layer) reqSerialized->push_back(mtpLayers[layer]);
request.write(*reqSerialized);
DEBUG_LOG(("MTP Info: adding wrapped to init connection request to toSendMap, msCanWait %1").arg(msCanWait));
callbacks.onDone = RPCDoneHandlerPtr(new RPCWrappedDcDoneHandler(dc, callbacks.onDone));
reqSerialized->msDate = getms(true); // > 0 - can send without container
if (after) reqSerialized->after = _mtp_internal::getRequest(after);
requestId = _mtp_internal::storeRequest(reqSerialized, callbacks);
sendPrepared(reqSerialized, msCanWait);
} catch (Exception &e) {
requestId = 0;
_mtp_internal::rpcErrorOccured(requestId, callbacks, rpcClientError("NO_REQUEST_ID", QString("sendFirst() failed to queue request, exception: %1").arg(e.what())));
}
if (requestId) {
_mtp_internal::registerRequest(requestId, toMainDC ? -getDC() : getDC());
}
return requestId;
}

View File

@@ -13,7 +13,6 @@
/////////////////// Layer cons
///////////////////////////////
//invokeAfterMsg#cb9f372d msg_id:long query:!X = X;
//invokeAfterMsgs#3dc4b4f0 msg_ids:Vector<long> query:!X = X;
//invokeWithLayer1#53835315 query:!X = X;
@@ -34,6 +33,7 @@
//invokeWithLayer16#cf5f0987 query:!X = X;
//invokeWithLayer17#50858a19 query:!X = X;
//invokeWithLayer18#1c900537 query:!X = X;
//invokeWithLayer#da9b0d0d layer:int query:!X = X; // after 18 layer
///////////////////////////////
/// Authorization key creation
@@ -287,7 +287,7 @@ contactBlocked#561bc879 user_id:int date:int = ContactBlocked;
contactSuggested#3de191a1 user_id:int mutual_contacts:int = ContactSuggested;
contactStatus#aa77b873 user_id:int expires:int = ContactStatus;
contactStatus#d3680c61 user_id:int status:UserStatus = ContactStatus;
chatLocated#3631cf4c chat_id:int distance:int = ChatLocated;
@@ -510,6 +510,34 @@ contacts.found#566000e results:Vector<ContactFound> users:Vector<User> = contact
updateServiceNotification#382dd3e4 type:string message:string media:MessageMedia popup:Bool = Update;
userStatusRecently#e26f42f1 = UserStatus;
userStatusLastWeek#7bf09fc = UserStatus;
userStatusLastMonth#77ebc742 = UserStatus;
updatePrivacy#ee3b272a key:PrivacyKey rules:Vector<PrivacyRule> = Update;
inputPrivacyKeyStatusTimestamp#4f96cb18 = InputPrivacyKey;
privacyKeyStatusTimestamp#bc2eab30 = PrivacyKey;
inputPrivacyValueAllowContacts#d09e07b = InputPrivacyRule;
inputPrivacyValueAllowAll#184b35ce = InputPrivacyRule;
inputPrivacyValueAllowUsers#131cc67f users:Vector<InputUser> = InputPrivacyRule;
inputPrivacyValueDisallowContacts#ba52007 = InputPrivacyRule;
inputPrivacyValueDisallowAll#d66b66c9 = InputPrivacyRule;
inputPrivacyValueDisallowUsers#90110467 users:Vector<InputUser> = InputPrivacyRule;
privacyValueAllowContacts#fffe1bac = PrivacyRule;
privacyValueAllowAll#65427b82 = PrivacyRule;
privacyValueAllowUsers#4d5bbe0c users:Vector<int> = PrivacyRule;
privacyValueDisallowContacts#f888fa1a = PrivacyRule;
privacyValueDisallowAll#8b73e763 = PrivacyRule;
privacyValueDisallowUsers#c7f49b7 users:Vector<int> = PrivacyRule;
account.privacyRules#554abb6f rules:Vector<PrivacyRule> users:Vector<User> = account.PrivacyRules;
accountDaysTTL#b8d0afdf days:int = AccountDaysTTL;
---functions---
invokeAfterMsg#cb9f372d msg_id:long query:!X = X;
@@ -631,3 +659,9 @@ account.checkUsername#2714d86c username:string = Bool;
account.updateUsername#3e0bdd7c username:string = User;
contacts.search#11f812d8 q:string limit:int = contacts.Found;
account.getPrivacy#dadbc950 key:InputPrivacyKey = account.PrivacyRules;
account.setPrivacy#c9f81ce8 key:InputPrivacyKey rules:Vector<InputPrivacyRule> = account.PrivacyRules;
account.deleteAccount#418d4e0b reason:string = Bool;
account.getAccountTTL#8fc711d = AccountDaysTTL;
account.setAccountTTL#2442485e ttl:AccountDaysTTL = Bool;

View File

@@ -40,6 +40,7 @@ OverviewInner::OverviewInner(OverviewWidget *overview, ScrollArea *scroll, const
, _hist(App::history(peer->id))
, _photosInRow(1)
, _photosToAdd(0)
, _selMode(false)
, _width(0)
, _height(0)
, _minHeight(0)
@@ -425,7 +426,7 @@ void OverviewInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton bu
dragActionUpdate(screenPos);
if (textlnkOver()) {
if (textlnkDown() == textlnkOver() && _dragAction != Dragging) {
if (textlnkDown() == textlnkOver() && _dragAction != Dragging && !_selMode) {
needClick = textlnkDown();
}
}
@@ -553,10 +554,14 @@ QPixmap OverviewInner::genPix(PhotoData *photo, int32 size) {
if (!photo->full->loaded() && !photo->medium->loaded()) {
img = imageBlur(img);
}
if (img.width() > img.height()) {
img = img.scaled(img.width() * size / img.height(), size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
if (img.width() == img.height()) {
if (img.width() != size) {
img = img.scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
}
} else if (img.width() > img.height()) {
img = img.copy((img.width() - img.height()) / 2, 0, img.height(), img.height()).scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
} else {
img = img.scaled(size, img.height() * size / img.width(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
img = img.copy(0, (img.height() - img.width()) / 2, img.width(), img.width()).scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
}
img.setDevicePixelRatio(cRetinaFactor());
photo->forget();
@@ -625,26 +630,13 @@ void OverviewInner::paintEvent(QPaintEvent *e) {
it->vsize = _vsize;
it->pix = genPix(photo, _vsize);
}
QPixmap &pix(it->pix);
QPoint pos(int32(i * w + st::overviewPhotoSkip), _addToY + row * (_vsize + st::overviewPhotoSkip) + st::overviewPhotoSkip);
int32 w = pix.width(), h = pix.height(), size;
if (w == h) {
p.drawPixmap(pos, pix);
size = w;
} else if (w > h) {
p.drawPixmap(pos, pix, QRect((w - h) / 2, 0, h, h));
size = h;
} else {
p.drawPixmap(pos, pix, QRect(0, (h - w) / 2, w, w));
size = w;
}
size /= cIntRetinaFactor();
p.drawPixmap(pos, it->pix);
if (!quality) {
uint64 dt = itemAnimations().animate(item, getms());
int32 cnt = int32(st::photoLoaderCnt), period = int32(st::photoLoaderPeriod), t = dt % period, delta = int32(st::photoLoaderDelta);
int32 x = pos.x() + (size - st::overviewLoader.width()) / 2, y = pos.y() + (size - st::overviewLoader.height()) / 2;
int32 x = pos.x() + (_vsize - st::overviewLoader.width()) / 2, y = pos.y() + (_vsize - st::overviewLoader.height()) / 2;
p.fillRect(x, y, st::overviewLoader.width(), st::overviewLoader.height(), st::photoLoaderBg->b);
x += (st::overviewLoader.width() - cnt * st::overviewLoaderPoint.width() - (cnt - 1) * st::overviewLoaderSkip) / 2;
y += (st::overviewLoader.height() - st::overviewLoaderPoint.height()) / 2;
@@ -671,7 +663,10 @@ void OverviewInner::paintEvent(QPaintEvent *e) {
}
}
if (sel == FullItemSel) {
p.fillRect(QRect(pos.x(), pos.y(), size, size), st::msgInSelectOverlay->b);
p.fillRect(QRect(pos.x(), pos.y(), _vsize, _vsize), st::overviewPhotoSelectOverlay->b);
p.drawPixmap(QPoint(pos.x() + _vsize - st::overviewPhotoChecked.pxWidth(), pos.y() + _vsize - st::overviewPhotoChecked.pxHeight()), App::sprite(), st::overviewPhotoChecked);
} else if (_selMode/* || (selfrom < count && selfrom <= selto && 0 <= selto)*/) {
p.drawPixmap(QPoint(pos.x() + _vsize - st::overviewPhotoChecked.pxWidth(), pos.y() + _vsize - st::overviewPhotoChecked.pxHeight()), App::sprite(), st::overviewPhotoCheck);
}
} break;
}
@@ -1145,6 +1140,10 @@ void OverviewInner::switchType(MediaOverviewType type) {
if (App::wnd()) App::wnd()->update();
}
void OverviewInner::setSelectMode(bool enabled) {
_selMode = enabled;
}
void OverviewInner::openContextUrl() {
HistoryItem *was = App::hoveredLinkItem();
App::hoveredLinkItem(App::contextItem());
@@ -1629,6 +1628,8 @@ MediaOverviewType OverviewWidget::type() const {
}
void OverviewWidget::switchType(MediaOverviewType type) {
_selCount = 0;
_inner.setSelectMode(false);
_inner.switchType(type);
switch (type) {
case OverviewPhotos: _header = lang(lng_profile_photos_header); break;
@@ -1637,7 +1638,6 @@ void OverviewWidget::switchType(MediaOverviewType type) {
case OverviewAudios: _header = lang(lng_profile_audios_header); break;
}
noSelectingScroll();
_selCount = 0;
App::main()->topBar()->showSelected(0);
updateTopBarSelection();
_scroll.scrollToY(_scroll.scrollTopMax());
@@ -1648,6 +1648,7 @@ void OverviewWidget::updateTopBarSelection() {
int32 selectedForForward, selectedForDelete;
_inner.getSelectionState(selectedForForward, selectedForDelete);
_selCount = selectedForDelete ? selectedForDelete : selectedForForward;
_inner.setSelectMode(_selCount > 0);
if (App::main()) {
App::main()->topBar()->showSelected(_selCount > 0 ? _selCount : 0);
App::main()->topBar()->update();

View File

@@ -55,6 +55,8 @@ public:
MediaOverviewType type() const;
void switchType(MediaOverviewType type);
void setSelectMode(bool enabled);
void mediaOverviewUpdated();
void changingMsgId(HistoryItem *row, MsgId newId);
void msgUpdated(const HistoryItem *msg);
@@ -124,6 +126,7 @@ private:
} CachedSize;
typedef QMap<PhotoData*, CachedSize> CachedSizes;
CachedSizes _cached;
bool _selMode;
// other
typedef struct _CachedItem {

View File

@@ -340,13 +340,13 @@ void ProfileInner::reorderParticipants() {
UserData *self = App::self();
for (ChatData::Participants::const_iterator i = _peerChat->participants.cbegin(), e = _peerChat->participants.cend(); i != e; ++i) {
UserData *user = i.key();
int32 until = user->onlineTill;
int32 until = App::onlineForSort(user->onlineTill, t);
Participants::iterator before = _participants.begin();
if (user != self) {
if (before != _participants.end() && (*before) == self) {
++before;
}
while (before != _participants.end() && (*before)->onlineTill >= until) {
while (before != _participants.end() && App::onlineForSort((*before)->onlineTill, t) >= until) {
++before;
}
}
@@ -367,7 +367,7 @@ void ProfileInner::reorderParticipants() {
} else {
_participants.clear();
if (_peerUser) {
_onlineText = App::onlineText(_peerUser->onlineTill, t, true);
_onlineText = App::onlineText(_peerUser, t, true);
} else {
_onlineText = lang(lng_chat_no_members);
}
@@ -520,7 +520,7 @@ 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->onlineTill, l_time);
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);

View File

@@ -50,7 +50,7 @@ namespace {
};
PsMainWindow::PsMainWindow(QWidget *parent) : QMainWindow(parent),
posInited(false), trayIcon(0), trayIconMenu(0), icon256(qsl(":/gui/art/iconround256.png")) {
posInited(false), trayIcon(0), trayIconMenu(0), icon256(qsl(":/gui/art/icon256.png")), wndIcon(QPixmap::fromImage(icon256)) {
connect(&psIdleTimer, SIGNAL(timeout()), this, SLOT(psIdleTimeout()));
psIdleTimer.setSingleShot(false);
}
@@ -115,6 +115,8 @@ void PsMainWindow::psUpdateWorkmode() {
}
void PsMainWindow::psUpdateCounter() {
setWindowIcon(wndIcon);
int32 counter = App::histories().unreadFull;
setWindowTitle((counter > 0) ? qsl("Telegram (%1)").arg(counter) : qsl("Telegram"));

View File

@@ -88,6 +88,7 @@ protected:
QSystemTrayIcon *trayIcon;
QMenu *trayIconMenu;
QImage icon256;
QIcon wndIcon;
virtual void setupTrayIcon() = 0;
virtual QImage iconWithCounter(int size, int count, style::color bg, bool smallIcon) = 0;

View File

@@ -21,6 +21,7 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "lang.h"
#include "application.h"
#include "mainwidget.h"
#include "historywidget.h"
namespace {
bool frameless = true;
@@ -48,6 +49,12 @@ void MacPrivate::activeSpaceChanged() {
}
}
void MacPrivate::darkModeChanged() {
if (App::wnd()) {
App::wnd()->psUpdateCounter();
}
}
void MacPrivate::notifyClicked(unsigned long long peer) {
History *history = App::history(PeerId(peer));
@@ -64,7 +71,8 @@ void MacPrivate::notifyReplied(unsigned long long peer, const char *str) {
}
PsMainWindow::PsMainWindow(QWidget *parent) : QMainWindow(parent),
posInited(false), trayIcon(0), trayIconMenu(0), icon256(qsl(":/gui/art/iconround256.png")) {
posInited(false), trayIcon(0), trayIconMenu(0), icon256(qsl(":/gui/art/icon256.png")), wndIcon(QPixmap(qsl(":/gui/art/icon256.png"))),
psLogout(0), psUndo(0), psRedo(0), psCut(0), psCopy(0), psPaste(0), psDelete(0), psSelectAll(0), psContacts(0), psNewGroup(0), psShowTelegram(0) {
QImage tray(qsl(":/gui/art/osxtray.png"));
trayImg = tray.copy(0, cRetina() ? 0 : tray.width() / 2, tray.width() / (cRetina() ? 2 : 4), tray.width() / (cRetina() ? 2 : 4));
trayImgSel = tray.copy(tray.width() / (cRetina() ? 2 : 4), cRetina() ? 0 : tray.width() / 2, tray.width() / (cRetina() ? 2 : 4), tray.width() / (cRetina() ? 2 : 4));
@@ -141,6 +149,7 @@ void PsMainWindow::psUpdateWorkmode() {
}
trayIcon = 0;
}
setWindowIcon(wndIcon);
}
void _placeCounter(QImage &img, int size, int count, style::color bg, style::color color) {
@@ -182,18 +191,20 @@ void PsMainWindow::psUpdateCounter() {
int32 counter = App::histories().unreadFull;
setWindowTitle((counter > 0) ? qsl("Telegram (%1)").arg(counter) : qsl("Telegram"));
setWindowIcon(wndIcon);
QString cnt = (counter < 1000) ? QString("%1").arg(counter) : QString("..%1").arg(counter % 100, 2, 10, QChar('0'));
_private.setWindowBadge(counter ? cnt : QString());
if (trayIcon) {
style::color bg = (App::histories().unreadMuted < counter) ? st::counterBG : st::counterMuteBG;
bool dm = objc_darkMode(), important = (App::histories().unreadMuted < counter);
style::color bg = important ? st::counterBG : st::counterMuteBG;
QIcon icon;
QImage img(psTrayIcon()), imgsel(psTrayIcon(true));
QImage img(psTrayIcon(dm)), imgsel(psTrayIcon(true));
img.detach();
imgsel.detach();
int32 size = cRetina() ? 44 : 22;
_placeCounter(img, size, counter, bg, st::counterColor);
_placeCounter(img, size, counter, bg, (dm && !important) ? st::counterMacInvColor : st::counterColor);
_placeCounter(imgsel, size, counter, st::white, st::counterMacInvColor);
icon.addPixmap(QPixmap::fromImage(img));
icon.addPixmap(QPixmap::fromImage(imgsel), QIcon::Selected);
@@ -335,6 +346,81 @@ void PsMainWindow::psFirstShow() {
show();
}
posInited = true;
// init global menu
QMenu *main = psMainMenu.addMenu(qsl("Telegram"));
main->addAction(lang(lng_mac_menu_about), App::wnd()->getTitle(), SLOT(onAbout()))->setMenuRole(QAction::AboutQtRole);
main->addSeparator();
QAction *prefs = main->addAction(lang(lng_mac_menu_preferences), App::wnd(), SLOT(showSettings()));
prefs->setMenuRole(QAction::PreferencesRole);
QMenu *file = psMainMenu.addMenu(lang(lng_mac_menu_file));
psLogout = file->addAction(lang(lng_mac_menu_logout), App::wnd(), SLOT(onLogout()));
QMenu *edit = psMainMenu.addMenu(lang(lng_mac_menu_edit));
psUndo = edit->addAction(lang(lng_mac_menu_undo), this, SLOT(psMacUndo()), QKeySequence::Undo);
psRedo = edit->addAction(lang(lng_mac_menu_redo), this, SLOT(psMacRedo()), QKeySequence::Redo);
edit->addSeparator();
psCut = edit->addAction(lang(lng_mac_menu_cut), this, SLOT(psMacCut()), QKeySequence::Cut);
psCopy = edit->addAction(lang(lng_mac_menu_copy), this, SLOT(psMacCopy()), QKeySequence::Copy);
psPaste = edit->addAction(lang(lng_mac_menu_paste), this, SLOT(psMacPaste()), QKeySequence::Paste);
psDelete = edit->addAction(lang(lng_mac_menu_delete), this, SLOT(psMacDelete()), QKeySequence(Qt::ControlModifier | Qt::Key_Backspace));
edit->addSeparator();
psSelectAll = edit->addAction(lang(lng_mac_menu_select_all), this, SLOT(psMacSelectAll()), QKeySequence::SelectAll);
QMenu *window = psMainMenu.addMenu(lang(lng_mac_menu_window));
psContacts = window->addAction(lang(lng_mac_menu_contacts), App::wnd()->getTitle(), SLOT(onContacts()));
window->addSeparator();
psNewGroup = window->addAction(lang(lng_mac_menu_new_group), App::wnd(), SLOT(onShowNewGroup()));
window->addSeparator();
psShowTelegram = window->addAction(lang(lng_mac_menu_show), App::wnd(), SLOT(showFromTray()));
psMacUpdateMenu();
}
namespace {
void _sendKeySequence(Qt::Key key, Qt::KeyboardModifiers modifiers = Qt::NoModifier) {
QWidget *focused = QApplication::focusWidget();
if (qobject_cast<QLineEdit*>(focused) || qobject_cast<FlatTextarea*>(focused) || qobject_cast<HistoryList*>(focused)) {
QApplication::postEvent(focused, new QKeyEvent(QEvent::KeyPress, key, modifiers));
QApplication::postEvent(focused, new QKeyEvent(QEvent::KeyRelease, key, modifiers));
}
}
void _forceDisabled(QAction *action, bool disabled) {
if (action->isEnabled()) {
if (disabled) action->setDisabled(true);
} else if (!disabled) {
action->setDisabled(false);
}
}
}
void PsMainWindow::psMacUndo() {
_sendKeySequence(Qt::Key_Z, Qt::ControlModifier);
}
void PsMainWindow::psMacRedo() {
_sendKeySequence(Qt::Key_Z, Qt::ControlModifier | Qt::ShiftModifier);
}
void PsMainWindow::psMacCut() {
_sendKeySequence(Qt::Key_X, Qt::ControlModifier);
}
void PsMainWindow::psMacCopy() {
_sendKeySequence(Qt::Key_C, Qt::ControlModifier);
}
void PsMainWindow::psMacPaste() {
_sendKeySequence(Qt::Key_V, Qt::ControlModifier);
}
void PsMainWindow::psMacDelete() {
_sendKeySequence(Qt::Key_Delete);
}
void PsMainWindow::psMacSelectAll() {
_sendKeySequence(Qt::Key_A, Qt::ControlModifier);
}
bool PsMainWindow::psHandleTitle() {
@@ -350,6 +436,40 @@ void PsMainWindow::psUpdateSysMenu(Qt::WindowState state) {
void PsMainWindow::psUpdateMargins() {
}
void PsMainWindow::psMacUpdateMenu() {
if (!posInited) return;
QWidget *focused = QApplication::focusWidget();
bool isLogged = !!App::self(), canUndo = false, canRedo = false, canCut = false, canCopy = false, canPaste = false, canDelete = false, canSelectAll = false;
if (QLineEdit *edit = qobject_cast<QLineEdit*>(focused)) {
canCut = canCopy = canDelete = edit->hasSelectedText();
canSelectAll = !edit->text().isEmpty();
canUndo = edit->isUndoAvailable();
canRedo = edit->isRedoAvailable();
canPaste = !App::app()->clipboard()->text().isEmpty();
} else if (FlatTextarea *edit = qobject_cast<FlatTextarea*>(focused)) {
canCut = canCopy = canDelete = edit->textCursor().hasSelection();
canSelectAll = edit->hasText();
canUndo = edit->isUndoAvailable();
canRedo = edit->isRedoAvailable();
canPaste = !App::app()->clipboard()->text().isEmpty();
} else if (HistoryList *list = qobject_cast<HistoryList*>(focused)) {
canCopy = list->canCopySelected();
canDelete = list->canDeleteSelected();
}
_forceDisabled(psLogout, !isLogged);
_forceDisabled(psUndo, !canUndo);
_forceDisabled(psRedo, !canRedo);
_forceDisabled(psCut, !canCut);
_forceDisabled(psCopy, !canCopy);
_forceDisabled(psPaste, !canPaste);
_forceDisabled(psDelete, !canDelete);
_forceDisabled(psSelectAll, !canSelectAll);
_forceDisabled(psContacts, !isLogged);
_forceDisabled(psNewGroup, !isLogged);
_forceDisabled(psShowTelegram, psIsActive());
}
void PsMainWindow::psFlash() {
_private.startBounce();
}
@@ -402,6 +522,16 @@ void PsMainWindow::psPlatformNotify(HistoryItem *item) {
_private.showNotify(item->history()->peer->id, title, subtitle, msg, (cNotifyView() <= dbinvShowPreview));
}
bool PsMainWindow::eventFilter(QObject *obj, QEvent *evt) {
QEvent::Type t = evt->type();
if (t == QEvent::FocusIn || t == QEvent::FocusOut) {
if (qobject_cast<QLineEdit*>(obj) || qobject_cast<FlatTextarea*>(obj) || qobject_cast<HistoryList*>(obj)) {
psMacUpdateMenu();
}
}
return QMainWindow::eventFilter(obj, evt);
}
PsApplication::PsApplication(int &argc, char **argv) : QApplication(argc, argv) {
}

View File

@@ -33,6 +33,7 @@ class MacPrivate : public PsMacWindowPrivate {
public:
void activeSpaceChanged();
void darkModeChanged();
void notifyClicked(unsigned long long peer);
void notifyReplied(unsigned long long peer, const char *str);
@@ -79,6 +80,8 @@ public:
void psNotifyShown(NotifyWindow *w);
void psPlatformNotify(HistoryItem *item);
bool eventFilter(QObject *obj, QEvent *evt);
~PsMainWindow();
public slots:
@@ -90,15 +93,26 @@ public slots:
void psIdleTimeout();
void psShowTrayMenu();
void psMacUndo();
void psMacRedo();
void psMacCut();
void psMacCopy();
void psMacPaste();
void psMacDelete();
void psMacSelectAll();
protected:
void psNotIdle() const;
QImage psTrayIcon(bool selected = false) const;
void psMacUpdateMenu();
bool posInited;
QSystemTrayIcon *trayIcon;
QMenu *trayIconMenu;
QImage icon256;
QIcon wndIcon;
QImage trayImg, trayImgSel;
@@ -113,6 +127,10 @@ private:
mutable bool psIdle;
mutable QTimer psIdleTimer;
QMenuBar psMainMenu;
QAction *psLogout, *psUndo, *psRedo, *psCut, *psCopy, *psPaste, *psDelete, *psSelectAll, *psContacts, *psNewGroup, *psShowTelegram;
};

View File

@@ -36,6 +36,8 @@ public:
virtual void activeSpaceChanged() {
}
virtual void darkModeChanged() {
}
virtual void notifyClicked(unsigned long long peer) {
}
virtual void notifyReplied(unsigned long long peer, const char *str) {
@@ -48,6 +50,7 @@ public:
};
void objc_holdOnTop(WId winId);
bool objc_darkMode();
void objc_showOverAll(WId winId, bool canFocus = true);
void objc_bringToBack(WId winId);
void objc_activateWnd(WId winId);

View File

@@ -85,6 +85,7 @@ QNSString objc_lang(LangKey key) {
- (id) init:(PsMacWindowPrivate *)aWnd;
- (void) activeSpaceDidChange:(NSNotification *)aNotification;
- (void) darkModeChanged:(NSNotification *)aNotification;
@end
@@ -145,6 +146,10 @@ public:
wnd->activeSpaceChanged();
}
- (void) darkModeChanged:(NSNotification *)aNotification {
wnd->darkModeChanged();
}
@end
@implementation NotifyHandler {
@@ -180,6 +185,7 @@ public:
PsMacWindowPrivate::PsMacWindowPrivate() : data(new PsMacWindowData(this)) {
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:data->observerHelper selector:@selector(activeSpaceDidChange:) name:NSWorkspaceActiveSpaceDidChangeNotification object:nil];
[[NSDistributedNotificationCenter defaultCenter] addObserver:data->observerHelper selector:@selector(darkModeChanged:) name:@"AppleInterfaceThemeChangedNotification" object:nil];
}
void PsMacWindowPrivate::setWindowBadge(const QString &str) {
@@ -200,6 +206,13 @@ void objc_holdOnTop(WId winId) {
[wnd setHidesOnDeactivate:NO];
}
bool objc_darkMode() {
NSDictionary *dict = [[NSUserDefaults standardUserDefaults] persistentDomainForName:NSGlobalDomain];
id style = [dict objectForKey:@"AppleInterfaceStyle"];
BOOL darkModeOn = ( style && [style isKindOfClass:[NSString class]] && NSOrderedSame == [style caseInsensitiveCompare:@"dark"] );
return darkModeOn ? true : false;
}
void objc_showOverAll(WId winId, bool canFocus) {
NSWindow *wnd = [reinterpret_cast<NSView *>(winId) window];
[wnd setLevel:NSPopUpMenuWindowLevel];

View File

@@ -860,7 +860,7 @@ namespace {
};
PsMainWindow::PsMainWindow(QWidget *parent) : QMainWindow(parent), ps_hWnd(0), ps_menu(0), icon256(qsl(":/gui/art/iconround256.png")),
PsMainWindow::PsMainWindow(QWidget *parent) : QMainWindow(parent), ps_hWnd(0), ps_menu(0), icon256(qsl(":/gui/art/icon256.png")), wndIcon(QPixmap::fromImage(icon256)),
ps_iconBig(0), ps_iconSmall(0), ps_iconOverlay(0), trayIcon(0), trayIconMenu(0), posInited(false), ps_tbHider_hWnd(createTaskbarHider()), psIdle(false) {
tbCreatedMsgId = RegisterWindowMessage(L"TaskbarButtonCreated");
connect(&psIdleTimer, SIGNAL(timeout()), this, SLOT(psIdleTimeout()));

View File

@@ -87,6 +87,7 @@ protected:
QSystemTrayIcon *trayIcon;
ContextMenu *trayIconMenu;
QImage icon256;
QIcon wndIcon;
virtual void setupTrayIcon() = 0;
virtual QImage iconWithCounter(int size, int count, style::color bg, bool smallIcon) = 0;

View File

@@ -137,47 +137,59 @@ const RecentEmojiPack &cGetRecentEmojis() {
cSetRecentEmojisPreload(RecentEmojiPreload());
r.reserve(p.size());
for (RecentEmojiPreload::const_iterator i = p.cbegin(), e = p.cend(); i != e; ++i) {
EmojiPtr ep(getEmoji(i->first));
if (ep) {
r.push_back(qMakePair(ep, i->second));
uint32 code = ((i->first & 0xFFFFU) == 0xFE0FU) ? ((i->first >> 16) & 0xFFFFU) : i->first;
EmojiPtr ep(getEmoji(code));
if (!ep) continue;
if (ep->postfix) {
int32 j = 0, l = r.size();
for (; j < l; ++j) {
if (r[j].first->code == code) {
break;
}
}
if (j < l) {
continue;
}
}
r.push_back(qMakePair(ep, i->second));
}
}
uint32 defaultRecent[] = {
0xD83DDE02,
0xD83DDE18,
0x2764,
0xD83DDE0D,
0xD83DDE0A,
0xD83DDE01,
0xD83DDC4D,
0x263A,
0xD83DDE14,
0xD83DDE04,
0xD83DDE2D,
0xD83DDC8B,
0xD83DDE12,
0xD83DDE33,
0xD83DDE1C,
0xD83DDE48,
0xD83DDE09,
0xD83DDE03,
0xD83DDE22,
0xD83DDE1D,
0xD83DDE31,
0xD83DDE21,
0xD83DDE0F,
0xD83DDE1E,
0xD83DDE05,
0xD83DDE1A,
0xD83DDE4A,
0xD83DDE0C,
0xD83DDE00,
0xD83DDE0B,
0xD83DDE06,
0xD83DDC4C,
0xD83DDE10,
0xD83DDE15,
0xD83DDE02U,
0xD83DDE18U,
0x2764U,
0xD83DDE0DU,
0xD83DDE0AU,
0xD83DDE01U,
0xD83DDC4DU,
0x263AU,
0xD83DDE14U,
0xD83DDE04U,
0xD83DDE2DU,
0xD83DDC8BU,
0xD83DDE12U,
0xD83DDE33U,
0xD83DDE1CU,
0xD83DDE48U,
0xD83DDE09U,
0xD83DDE03U,
0xD83DDE22U,
0xD83DDE1DU,
0xD83DDE31U,
0xD83DDE21U,
0xD83DDE0FU,
0xD83DDE1EU,
0xD83DDE05U,
0xD83DDE1AU,
0xD83DDE4AU,
0xD83DDE0CU,
0xD83DDE00U,
0xD83DDE0BU,
0xD83DDE06U,
0xD83DDC4CU,
0xD83DDE10U,
0xD83DDE15U,
};
for (int32 i = 0, s = sizeof(defaultRecent) / sizeof(defaultRecent[0]); i < s; ++i) {
if (r.size() >= EmojiPadPerRow * EmojiPadRowsPerPage) break;

View File

@@ -124,11 +124,12 @@ T convertScale(T v) {
DeclareSetting(DBIEmojiTab, EmojiTab);
struct EmojiData {
EmojiData(int32 x, int32 y, uint32 code, uint32 code2, int32 len) : x(x), y(y), code(code), code2(code2), len(len) {
EmojiData(uint16 x, uint16 y, uint32 code, uint32 code2, uint16 len, uint16 postfix = 0) : x(x), y(y), code(code), code2(code2), len(len), postfix(postfix) {
}
int32 x, y;
uint16 x, y;
uint32 code, code2;
int32 len;
uint16 len;
uint16 postfix;
};
typedef const EmojiData *EmojiPtr;

View File

@@ -31,6 +31,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "boxes/usernamebox.h"
#include "gui/filedialog.h"
#include "localstorage.h"
Slider::Slider(QWidget *parent, const style::slider &st, int32 count, int32 sel) : QWidget(parent),
_count(count), _sel(snap(sel, 0, _count)), _wasSel(_sel), _st(st), _pressed(false) {
resize(_st.width, _st.bar.pxHeight());
@@ -154,6 +156,12 @@ SettingsInner::SettingsInner(SettingsWidget *parent) : QWidget(parent),
_catsAndDogs(this, lang(lng_settings_cats_and_dogs), cCatsAndDogs()),
// local storage
_localImagesClear(this, lang(lng_local_images_clear)),
_imagesClearingWidth(st::linkFont->m.width(lang(lng_local_images_clearing))),
_imagesClearedWidth(st::linkFont->m.width(lang(lng_local_images_cleared))),
_imagesClearFailedWidth(st::linkFont->m.width(lang(lng_local_images_clear_failed))),
// advanced
_connectionType(this, lang(lng_connection_auto)),
_resetSessions(this, lang(lng_settings_reset)),
@@ -231,15 +239,23 @@ SettingsInner::SettingsInner(SettingsWidget *parent) : QWidget(parent),
case Window::TempDirExists: _tempDirClearState = TempDirExists; break;
case Window::TempDirRemoving: _tempDirClearState = TempDirClearing; break;
}
connect(App::wnd(), SIGNAL(tempDirCleared()), this, SLOT(onTempDirCleared()));
connect(App::wnd(), SIGNAL(tempDirClearFailed()), this, SLOT(onTempDirClearFailed()));
connect(App::wnd(), SIGNAL(tempDirCleared(int)), this, SLOT(onTempDirCleared(int)));
connect(App::wnd(), SIGNAL(tempDirClearFailed(int)), this, SLOT(onTempDirClearFailed(int)));
connect(&_catsAndDogs, SIGNAL(changed()), this, SLOT(onCatsAndDogs()));
// local storage
connect(&_localImagesClear, SIGNAL(clicked()), this, SLOT(onLocalImagesClear()));
switch (App::wnd()->localImagesState()) {
case Window::TempDirEmpty: _imagesClearState = TempDirEmpty; break;
case Window::TempDirExists: _imagesClearState = TempDirExists; break;
case Window::TempDirRemoving: _imagesClearState = TempDirClearing; break;
}
// advanced
connect(&_connectionType, SIGNAL(clicked()), this, SLOT(onConnectionType()));
connect(&_resetSessions, SIGNAL(clicked()), this, SLOT(onResetSessions()));
connect(&_logOut, SIGNAL(clicked()), this, SLOT(onLogout()));
connect(&_logOut, SIGNAL(clicked()), App::wnd(), SLOT(onLogout()));
_connectionTypeText = lang(lng_connection_type) + ' ';
_connectionTypeWidth = st::linkFont->m.width(_connectionTypeText);
@@ -456,14 +472,40 @@ void SettingsInner::paintEvent(QPaintEvent *e) {
top += st::setSectionSkip;
top += _catsAndDogs.height();
// local storage
p.setFont(st::setHeaderFont->f);
p.setPen(st::setHeaderColor->p);
p.drawText(_left + st::setHeaderLeft, top + st::setHeaderTop + st::setHeaderFont->ascent, lang(lng_settings_section_cache));
top += st::setHeaderSkip;
QString localImagesText = lang(lng_settings_no_images_cached);
int32 cnt = Local::hasImages();
if (cnt) {
localImagesText = lang((cnt > 1) ? lng_settings_images_cached : lng_settings_image_cached).replace(qsl("{count}"), QString::number(cnt)).replace(qsl("{size}"), formatSizeText(Local::storageFilesSize()));
}
p.setFont(st::linkFont->f);
p.setPen(st::black->p);
p.drawText(_left + st::setHeaderLeft, top + st::linkFont->ascent, localImagesText);
QString clearText;
int32 clearWidth = 0;
switch (_imagesClearState) {
case TempDirClearing: clearText = lang(lng_local_images_clearing); clearWidth = _imagesClearingWidth; break;
case TempDirCleared: clearText = lang(lng_local_images_cleared); clearWidth = _imagesClearedWidth; break;
case TempDirClearFailed: clearText = lang(lng_local_images_clear_failed); clearWidth = _imagesClearFailedWidth; break;
}
if (clearWidth) {
p.drawText(_left + st::setWidth - clearWidth, top + st::linkFont->ascent, clearText);
}
top += _localImagesClear.height();
}
// advanced
p.setFont(st::setHeaderFont->f);
p.setPen(st::setHeaderColor->p);
p.drawText(_left + st::setHeaderLeft, top + st::setHeaderTop + st::setHeaderFont->ascent, lang(lng_settings_section_advanced));
top += st::setHeaderSkip;
p.setFont(st::linkFont->f);
p.setPen(st::black->p);
p.drawText(_left + st::setHeaderLeft, _connectionType.y() + st::linkFont->ascent, _connectionTypeText);
@@ -541,6 +583,10 @@ void SettingsInner::resizeEvent(QResizeEvent *e) {
}
top += st::setSectionSkip;
_catsAndDogs.move(_left, top); top += _catsAndDogs.height();
// local storage
top += st::setHeaderSkip;
_localImagesClear.move(_left + st::setWidth - _localImagesClear.width(), top); top += _localImagesClear.height();
}
// advanced
@@ -754,6 +800,7 @@ void SettingsInner::showAll() {
_downloadPathClear.hide();
}
}
} else {
_replaceEmojis.hide();
_viewEmojis.hide();
@@ -763,6 +810,14 @@ void SettingsInner::showAll() {
_dontAskDownloadPath.hide();
_downloadPathEdit.hide();
_downloadPathClear.hide();
_localImagesClear.hide();
}
// local storage
if (self() && _imagesClearState == TempDirExists) {
_localImagesClear.show();
} else {
_localImagesClear.hide();
}
// advanced
@@ -823,10 +878,6 @@ void SettingsInner::onUpdatePhoto() {
App::wnd()->showLayer(box);
}
void SettingsInner::onLogout() {
App::logOut();
}
void SettingsInner::onResetSessions() {
MTP::send(MTPauth_ResetAuthorizations(), rpcDone(&SettingsInner::doneResetSessions));
}
@@ -1107,20 +1158,35 @@ void SettingsInner::onDownloadPathClear() {
void SettingsInner::onDownloadPathClearSure() {
App::wnd()->hideLayer();
App::wnd()->tempDirDelete();
App::wnd()->tempDirDelete(Local::ClearManagerDownloads);
_tempDirClearState = TempDirClearing;
showAll();
update();
}
void SettingsInner::onTempDirCleared() {
_tempDirClearState = TempDirCleared;
void SettingsInner::onLocalImagesClear() {
App::wnd()->tempDirDelete(Local::ClearManagerImages);
_imagesClearState = TempDirClearing;
showAll();
update();
}
void SettingsInner::onTempDirClearFailed() {
_tempDirClearState = TempDirClearFailed;
void SettingsInner::onTempDirCleared(int task) {
if (task & Local::ClearManagerDownloads) {
_tempDirClearState = TempDirCleared;
} else if (task & Local::ClearManagerImages) {
_imagesClearState = TempDirCleared;
}
showAll();
update();
}
void SettingsInner::onTempDirClearFailed(int task) {
if (task & Local::ClearManagerDownloads) {
_tempDirClearState = TempDirClearFailed;
} else if (task & Local::ClearManagerImages) {
_imagesClearState = TempDirClearFailed;
}
showAll();
update();
}

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