Compare commits

...

42 Commits

Author SHA1 Message Date
John Preston
4f7df6987c Alpha version 1.2.22.
- Use markdown in media captions
(**bold**, __italic__, `tag` and ```code```).
- Use emoji replacement in media captions,
group and channel titles and descriptions (:like: etc.)
- Markdown replacement now happens immediately
after typing (instead of after sending) and can be
rolled back using Backspace or Ctrl/Cmd + Z.
Replacement no longer happens when pasting text.
2018-05-24 20:25:05 +03:00
John Preston
3d75d21a3e Fix build in Linux 32bit. 2018-05-24 20:25:04 +03:00
John Preston
250718e766 Fix build for Xcode. 2018-05-24 20:25:04 +03:00
John Preston
a5cbade8ec Update libtgvoip. 2018-05-24 20:25:04 +03:00
John Preston
3ac50cf77f Move message text to send media box caption field. 2018-05-24 17:57:41 +03:00
John Preston
f35bf41d26 Enable instant replaces in title edit fields. 2018-05-24 17:57:35 +03:00
John Preston
f1816815a9 Refresh last post on proxy promotion refresh. 2018-05-24 17:12:57 +03:00
John Preston
37bf9ffcff Show bad proxy configuration error. 2018-05-24 16:40:19 +03:00
John Preston
5e7642b42a Support markdown and replaces in media captions. 2018-05-24 16:03:21 +03:00
John Preston
6f6ec217e3 Support markdown replaces in Ui::InputField. 2018-05-24 15:31:48 +03:00
John Preston
017ec87d60 Replace FlatTextarea with InputField. 2018-05-22 13:45:22 +03:00
John Preston
30dd8fe070 Unite InputField and InputArea.
Also support and use instant replaces in InputField-s.
2018-05-20 23:37:59 +03:00
John Preston
8e442563f2 Enable checked auth key creation. 2018-05-20 23:20:48 +03:00
John Preston
c43dcf0567 Alpha version 1.2.21: Add some setup langs. 2018-05-18 21:57:06 +03:00
John Preston
f2a5a29d12 Alpha version 1.2.21: Fix build for Xcode. 2018-05-18 21:07:31 +03:00
John Preston
678b9a8eb5 Alpha version 1.2.21.
- Support domain names in mtproto proxy.
- Bug fixes and other minor improvements.
2018-05-18 20:36:21 +03:00
John Preston
96f7c0c02e Don't try to logout CDN dcs. 2018-05-18 19:41:59 +03:00
John Preston
66b7b6da2a Replace xD by emoji only after space. 2018-05-18 18:25:05 +03:00
John Preston
38daffdbfe Handle the ADMINS_TOO_MUCH error for channels. 2018-05-18 17:16:14 +03:00
John Preston
0238c03956 Return emoji replace by ":o" after a space.
Fixes #4700.
2018-05-18 17:15:02 +03:00
John Preston
425e56b3ea Apply proxy settings in mtproto-key-destroyer. 2018-05-18 15:20:28 +03:00
John Preston
4478c0a143 Resolve domain names for proxy servers.
Also use dc_id-checked auth key creation.

Fixes #4695.
2018-05-18 10:57:11 +03:00
John Preston
a053384618 Fix mtproto-proxy working with domain names.
Also refactor a bit TcpConnection and HttpConnection classes.
2018-05-17 13:27:49 +03:00
John Preston
694e8cd19f Remove MSVC compiler bug workaround.
It works fine in Visual Studio 2017 15.7.1.
2018-05-17 11:15:54 +03:00
John Preston
4bcd1e3c59 Allow editing last sent saved message by up key.
Also update libtgvoip.
Also replace 🤷 with a shrug.
2018-05-15 21:13:37 +03:00
John Preston
5f063c0151 Display information about proxy sponsor. 2018-05-15 20:22:28 +03:00
John Preston
5a1d4d55c6 Display a disclaimer about proxy sponsor. 2018-05-15 19:38:27 +03:00
John Preston
d3f85b4c4e Display channels promoted by proxy on top. 2018-05-13 21:26:35 +03:00
John Preston
df9ec4b466 Alpha version 1.2.20.
- Emoji and text replacements are done while you type the message.
- Revert emoji and text replacements by pressing backspace.
- Disable emoji replacements or suggestions in Settings.
- Some critical bug fixes.
2018-05-13 21:25:31 +03:00
John Preston
4f9507ed97 Fix build for Xcode 9.3.1 and OS X 10.6-10.7. 2018-05-13 21:12:44 +03:00
John Preston
f761b6aa9e Backport critical bugfix from '4f959b6b30' commit to Qt patch. 2018-05-13 20:35:40 +03:00
John Preston
168a7ce2e5 Add "Suggest emoji replacements" checkbox.
Also emoji suggestions insert an instant emoji replacement.
2018-05-13 18:56:08 +03:00
John Preston
4b763a76df Instant in-field emoji and text replaces.
Fixes #4410. Fixes #522.
2018-05-13 18:14:02 +03:00
John Preston
8764da787b Don't logout on some server-side problems. 2018-05-13 12:19:34 +03:00
John Preston
7d8ba15252 Allow to report messages in supergroups. 2018-05-10 17:15:16 +03:00
John Preston
96c0c30f7c Fix possible crash. 2018-05-10 17:15:04 +03:00
John Preston
bb6ab5314c Fix recent stickers saving and possible crash.
Regression was introduced in 97c15865a5.
2018-05-10 15:03:02 +03:00
John Preston
e3c6abfc3d Fix possible crash in reply returns. 2018-05-10 14:56:36 +03:00
John Preston
5c5bccae0b Fix proxy icon on retina and on theme changes.
Fixes #4668.
2018-05-10 13:34:06 +03:00
John Preston
296e009808 Fix proxy icon doubling.
Fixes #4666.
2018-05-10 13:24:52 +03:00
John Preston
4d84781a65 Display connecting state in history top bar. 2018-05-10 13:16:21 +03:00
John Preston
710b9bf454 Fix build for MSVC 15.7.
Fixes #4661. Fixes #4667.
2018-05-10 11:13:13 +03:00
139 changed files with 5760 additions and 4806 deletions

View File

@@ -585,7 +585,7 @@ buildCustomQt() {
info_msg "Downloading and building patched qt"
if [ -d "$EXTERNAL/qt${QT_VERSION}" ]; then
rm -rf "$EXTERNAL/qt${QT_VERSION}"
sudo rm -rf "$EXTERNAL/qt${QT_VERSION}"
fi
cd $QT_PATH
rm -rf *

View File

@@ -1,5 +1,5 @@
diff --git a/configure b/configure
index cb8d78fd3c..cadb3f0a88 100755
index cb8d78fd3cb..cadb3f0a880 100755
--- a/configure
+++ b/configure
@@ -511,7 +511,8 @@ if [ "$BUILD_ON_MAC" = "yes" ]; then
@@ -13,7 +13,7 @@ index cb8d78fd3c..cadb3f0a88 100755
echo " Xcode not set up properly. You may need to confirm the license" >&2
echo " agreement by running /usr/bin/xcodebuild without arguments." >&2
diff --git a/mkspecs/common/g++-macx.conf b/mkspecs/common/g++-macx.conf
index 086510dd96..c485967863 100644
index 086510dd963..c485967863d 100644
--- a/mkspecs/common/g++-macx.conf
+++ b/mkspecs/common/g++-macx.conf
@@ -14,7 +14,13 @@ QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO += -gdwarf-2
@@ -32,7 +32,7 @@ index 086510dd96..c485967863 100644
QMAKE_XCODE_GCC_VERSION = com.apple.compilers.llvmgcc42
diff --git a/mkspecs/features/mac/default_pre.prf b/mkspecs/features/mac/default_pre.prf
index 0cc8cd6dfd..ca9725b779 100644
index 0cc8cd6dfdd..ca9725b7791 100644
--- a/mkspecs/features/mac/default_pre.prf
+++ b/mkspecs/features/mac/default_pre.prf
@@ -12,7 +12,9 @@ isEmpty(QMAKE_XCODE_DEVELOPER_PATH) {
@@ -47,7 +47,7 @@ index 0cc8cd6dfd..ca9725b779 100644
}
diff --git a/src/gui/image/qbmphandler.cpp b/src/gui/image/qbmphandler.cpp
index bb79a139b3..5d595bc3b3 100644
index bb79a139b3c..5d595bc3b34 100644
--- a/src/gui/image/qbmphandler.cpp
+++ b/src/gui/image/qbmphandler.cpp
@@ -220,6 +220,10 @@ static bool read_dib_body(QDataStream &s, const BMP_INFOHDR &bi, int offset, int
@@ -74,7 +74,7 @@ index bb79a139b3..5d595bc3b3 100644
if (ncols > 0) { // read color table
uchar rgb[4];
diff --git a/src/gui/painting/qpaintengine_p.h b/src/gui/painting/qpaintengine_p.h
index ebff9509ab..4300ca4c0f 100644
index ebff9509ab2..4300ca4c0f0 100644
--- a/src/gui/painting/qpaintengine_p.h
+++ b/src/gui/painting/qpaintengine_p.h
@@ -87,8 +87,18 @@ public:
@@ -98,7 +98,7 @@ index ebff9509ab..4300ca4c0f 100644
// Make sure we're inside the viewport.
diff --git a/src/gui/text/qtextlayout.cpp b/src/gui/text/qtextlayout.cpp
index 4879ae51d7..56cdcbaf01 100644
index 4879ae51d7d..56cdcbaf01c 100644
--- a/src/gui/text/qtextlayout.cpp
+++ b/src/gui/text/qtextlayout.cpp
@@ -654,6 +654,9 @@ int QTextLayout::nextCursorPosition(int oldPos, CursorMode mode) const
@@ -175,7 +175,7 @@ index 4879ae51d7..56cdcbaf01 100644
inline void resetRightBearing()
diff --git a/src/gui/text/qtextlayout.h b/src/gui/text/qtextlayout.h
index cbe42c3844..b273db7e78 100644
index cbe42c38444..b273db7e78c 100644
--- a/src/gui/text/qtextlayout.h
+++ b/src/gui/text/qtextlayout.h
@@ -194,6 +194,9 @@ private:
@@ -188,8 +188,21 @@ index cbe42c3844..b273db7e78 100644
};
diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp
index 360f9722c70..f28f289ef6a 100644
--- a/src/network/access/qhttpnetworkconnection.cpp
+++ b/src/network/access/qhttpnetworkconnection.cpp
@@ -118,6 +118,8 @@ QHttpNetworkConnectionPrivate::~QHttpNetworkConnectionPrivate()
{
for (int i = 0; i < channelCount; ++i) {
if (channels[i].socket) {
+ // Patch: backport critical bugfix from '4f959b6b30' commit.
+ QObject::disconnect(channels[i].socket, Q_NULLPTR, &channels[i], Q_NULLPTR);
channels[i].socket->close();
delete channels[i].socket;
}
diff --git a/src/platformsupport/fontdatabases/mac/qcoretextfontdatabase.mm b/src/platformsupport/fontdatabases/mac/qcoretextfontdatabase.mm
index ca7afb7d1b..25ae50008d 100644
index ca7afb7d1b9..25ae50008db 100644
--- a/src/platformsupport/fontdatabases/mac/qcoretextfontdatabase.mm
+++ b/src/platformsupport/fontdatabases/mac/qcoretextfontdatabase.mm
@@ -256,6 +256,13 @@ static void getFontDescription(CTFontDescriptorRef font, FontDescription *fd)
@@ -206,8 +219,22 @@ index ca7afb7d1b..25ae50008d 100644
fd->styleName = (CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontStyleNameAttribute);
fd->weight = QFont::Normal;
fd->style = QFont::StyleNormal;
diff --git a/src/platformsupport/fontdatabases/mac/qfontengine_coretext.mm b/src/platformsupport/fontdatabases/mac/qfontengine_coretext.mm
index 6e2c8a2a9af..3cace8abcbc 100644
--- a/src/platformsupport/fontdatabases/mac/qfontengine_coretext.mm
+++ b/src/platformsupport/fontdatabases/mac/qfontengine_coretext.mm
@@ -717,7 +717,8 @@ void QCoreTextFontEngine::getUnscaledGlyph(glyph_t, QPainterPath *, glyph_metric
QFixed QCoreTextFontEngine::emSquareSize() const
{
- return QFixed::QFixed(int(CTFontGetUnitsPerEm(ctfont)));
+ // Patch: Fix build for Xcode 9.3.1.
+ return QFixed(int(CTFontGetUnitsPerEm(ctfont)));
}
QFontEngine *QCoreTextFontEngine::cloneWithSize(qreal pixelSize) const
diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm
index 92358ecc74..694fee7350 100644
index 92358ecc745..694fee73507 100644
--- a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm
+++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm
@@ -213,7 +213,8 @@ static void cleanupCocoaApplicationDelegate()
@@ -244,7 +271,7 @@ index 92358ecc74..694fee7350 100644
- (void)appleEventQuit:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent
diff --git a/src/plugins/platforms/cocoa/qcocoacursor.mm b/src/plugins/platforms/cocoa/qcocoacursor.mm
index b81b9a0b1c..4e59e833b1 100644
index b81b9a0b1c2..4e59e833b1d 100644
--- a/src/plugins/platforms/cocoa/qcocoacursor.mm
+++ b/src/plugins/platforms/cocoa/qcocoacursor.mm
@@ -81,7 +81,7 @@ void QCocoaCursor::setPos(const QPoint &position)
@@ -257,7 +284,7 @@ index b81b9a0b1c..4e59e833b1 100644
CFRelease(e);
}
diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.mm b/src/plugins/platforms/cocoa/qcocoahelpers.mm
index 9850f83dea..218ed7e81c 100644
index 9850f83dea8..b2e1d3dfda7 100644
--- a/src/plugins/platforms/cocoa/qcocoahelpers.mm
+++ b/src/plugins/platforms/cocoa/qcocoahelpers.mm
@@ -649,9 +649,10 @@ OSStatus qt_mac_drawCGImage(CGContextRef inContext, const CGRect *inBounds, CGIm
@@ -290,7 +317,7 @@ index 9850f83dea..218ed7e81c 100644
}
diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm
index 9fd05a65ee..dea60720e7 100644
index 9fd05a65ee9..dea60720e78 100644
--- a/src/plugins/platforms/cocoa/qcocoaintegration.mm
+++ b/src/plugins/platforms/cocoa/qcocoaintegration.mm
@@ -402,14 +402,24 @@ void QCocoaIntegration::updateScreens()
@@ -321,7 +348,7 @@ index 9fd05a65ee..dea60720e7 100644
QCocoaScreen *QCocoaIntegration::screenAtIndex(int index)
diff --git a/src/plugins/platforms/cocoa/qcocoakeymapper.mm b/src/plugins/platforms/cocoa/qcocoakeymapper.mm
index e46eaff6be..c62db534a2 100644
index e46eaff6be3..c62db534a2d 100644
--- a/src/plugins/platforms/cocoa/qcocoakeymapper.mm
+++ b/src/plugins/platforms/cocoa/qcocoakeymapper.mm
@@ -382,6 +382,12 @@ bool QCocoaKeyMapper::updateKeyboard()
@@ -348,7 +375,7 @@ index e46eaff6be..c62db534a2 100644
}
return ret;
diff --git a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
index 83c960d931..03ae9696af 100755
index 83c960d9317..03ae9696afe 100755
--- a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
+++ b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
@@ -102,7 +102,10 @@ QT_USE_NAMESPACE
@@ -516,7 +543,7 @@ index 83c960d931..03ae9696af 100755
}
@end
diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm
index 4d0458a4aa..3357a5ef81 100644
index 4d0458a4aa2..3357a5ef817 100644
--- a/src/plugins/platforms/cocoa/qcocoawindow.mm
+++ b/src/plugins/platforms/cocoa/qcocoawindow.mm
@@ -167,7 +167,8 @@ static bool isMouseEvent(NSEvent *ev)
@@ -579,7 +606,7 @@ index 4d0458a4aa..3357a5ef81 100644
[iconButton setImage:image];
[image release];
diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm
index a18ee7ff71..1f91feb0ae 100644
index a18ee7ff71d..1f91feb0ae8 100644
--- a/src/plugins/platforms/cocoa/qnsview.mm
+++ b/src/plugins/platforms/cocoa/qnsview.mm
@@ -393,7 +393,9 @@ static NSString *_q_NSWindowDidChangeOcclusionStateNotification = nil;
@@ -648,7 +675,7 @@ index a18ee7ff71..1f91feb0ae 100644
}
return [super performKeyEquivalent:nsevent];
diff --git a/src/tools/qlalr/lalr.cpp b/src/tools/qlalr/lalr.cpp
index c68076477f..e2a7aafa58 100644
index c68076477f3..e2a7aafa586 100644
--- a/src/tools/qlalr/lalr.cpp
+++ b/src/tools/qlalr/lalr.cpp
@@ -246,11 +246,13 @@ void Grammar::buildExtendedGrammar ()
@@ -688,7 +715,7 @@ index c68076477f..e2a7aafa58 100644
continue;
diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp
index 7396808442..7178aecf80 100644
index 7396808442e..7178aecf800 100644
--- a/src/widgets/kernel/qwidget.cpp
+++ b/src/widgets/kernel/qwidget.cpp
@@ -4722,6 +4722,17 @@ void QWidget::render(QPainter *painter, const QPoint &targetOffset,
@@ -741,7 +768,7 @@ index 7396808442..7178aecf80 100644
|| (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier)))
res = focusNextPrevChild(false);
diff --git a/src/widgets/styles/qmacstyle_mac.mm b/src/widgets/styles/qmacstyle_mac.mm
index 0845a5eb02..5735cb6b39 100644
index 0845a5eb02f..5735cb6b396 100644
--- a/src/widgets/styles/qmacstyle_mac.mm
+++ b/src/widgets/styles/qmacstyle_mac.mm
@@ -3667,9 +3667,11 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter
@@ -759,7 +786,7 @@ index 0845a5eb02..5735cb6b39 100644
}
diff --git a/src/widgets/util/qsystemtrayicon_qpa.cpp b/src/widgets/util/qsystemtrayicon_qpa.cpp
index f98aeaf678..00c0734129 100644
index f98aeaf6782..00c0734129e 100644
--- a/src/widgets/util/qsystemtrayicon_qpa.cpp
+++ b/src/widgets/util/qsystemtrayicon_qpa.cpp
@@ -99,13 +99,18 @@ void QSystemTrayIconPrivate::updateIcon_sys()
@@ -788,7 +815,7 @@ index f98aeaf678..00c0734129 100644
}
diff --git a/src/widgets/widgets/qwidgetlinecontrol.cpp b/src/widgets/widgets/qwidgetlinecontrol.cpp
index 75f30599be..980f2be1e9 100644
index 75f30599be4..980f2be1e93 100644
--- a/src/widgets/widgets/qwidgetlinecontrol.cpp
+++ b/src/widgets/widgets/qwidgetlinecontrol.cpp
@@ -1867,7 +1867,8 @@ void QWidgetLineControl::processKeyEvent(QKeyEvent* event)
@@ -802,7 +829,7 @@ index 75f30599be..980f2be1e9 100644
#ifndef QT_NO_COMPLETER
complete(event->key());
diff --git a/src/widgets/widgets/qwidgettextcontrol.cpp b/src/widgets/widgets/qwidgettextcontrol.cpp
index 96438a0bdf..b0b7206405 100644
index 96438a0bdf7..b0b72064056 100644
--- a/src/widgets/widgets/qwidgettextcontrol.cpp
+++ b/src/widgets/widgets/qwidgettextcontrol.cpp
@@ -1342,7 +1342,8 @@ void QWidgetTextControlPrivate::keyPressEvent(QKeyEvent *e)

View File

@@ -221,6 +221,19 @@ index f74d4d4229..8ad672c9fe 100644
};
diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp
index c4cb8e65c0..45793e364f 100644
--- a/src/network/access/qhttpnetworkconnection.cpp
+++ b/src/network/access/qhttpnetworkconnection.cpp
@@ -110,6 +110,8 @@ QHttpNetworkConnectionPrivate::~QHttpNetworkConnectionPrivate()
{
for (int i = 0; i < channelCount; ++i) {
if (channels[i].socket) {
+ // Patch: backport critical bugfix from '4f959b6b30' commit.
+ QObject::disconnect(channels[i].socket, Q_NULLPTR, &channels[i], Q_NULLPTR);
channels[i].socket->close();
delete channels[i].socket;
}
diff --git a/src/network/socket/qnativesocketengine_win.cpp b/src/network/socket/qnativesocketengine_win.cpp
index 41834b21ae..8cdf4ab145 100644
--- a/src/network/socket/qnativesocketengine_win.cpp
@@ -417,6 +430,20 @@ index 566abf2126..5b9c714ffa 100644
fd->styleName = (CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontStyleNameAttribute);
fd->weight = QFont::Normal;
fd->style = QFont::StyleNormal;
diff --git a/src/platformsupport/fontdatabases/mac/qfontengine_coretext.mm b/src/platformsupport/fontdatabases/mac/qfontengine_coretext.mm
index 7b459584ea..2ed2fd9b3b 100644
--- a/src/platformsupport/fontdatabases/mac/qfontengine_coretext.mm
+++ b/src/platformsupport/fontdatabases/mac/qfontengine_coretext.mm
@@ -764,7 +764,8 @@ void QCoreTextFontEngine::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, gl
QFixed QCoreTextFontEngine::emSquareSize() const
{
- return QFixed::QFixed(int(CTFontGetUnitsPerEm(ctfont)));
+ // Patch: Fix build for Xcode 9.3.1.
+ return QFixed(int(CTFontGetUnitsPerEm(ctfont)));
}
QFontEngine *QCoreTextFontEngine::cloneWithSize(qreal pixelSize) const
diff --git a/src/plugins/platforminputcontexts/compose/compose.pro b/src/plugins/platforminputcontexts/compose/compose.pro
index 86bdd4729b..9b9c8ded08 100644
--- a/src/plugins/platforminputcontexts/compose/compose.pro

View File

@@ -40,7 +40,7 @@ lineWidth: 1px;
defaultTextPalette: TextPalette {
linkFg: windowActiveTextFg;
monoFg: windowSubTextFg;
monoFg: msgInMonoFg;
selectBg: msgInBgSelected;
selectFg: transparent; // use painter current pen instead
selectLinkFg: historyLinkInFgSelected;

View File

@@ -123,6 +123,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_error_cant_add_admin_invite" = "Sorry, you can't add this user as an admin because they are not a member of this group and you are not allowed to invite them.";
"lng_error_cant_add_admin_unban" = "Sorry, you can't add this user as an admin because they are in the blacklist and you can't unban them.";
"lng_error_cant_ban_admin" = "Sorry, you can't ban this user because they are an admin in this group and you are not allowed to demote them.";
"lng_error_admin_limit" = "Sorry, you've reached the maximum number of admins for this group.";
"lng_error_admin_limit_channel" = "Sorry, you've reached the maximum number of admins for this channel.";
"lng_sure_add_admin_invite" = "This user is not a member of this group. Add them to the group and promote them to admin?";
"lng_sure_add_admin_invite_channel" = "This user is not a subscriber of this channel. Add them to the channel and promote them to admin?";
"lng_sure_add_admin_unban" = "This user is currently restricted or banned. Are you sure you want to unban and promote them?";
@@ -296,6 +298,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_section_chat_settings" = "Chat Settings";
"lng_settings_replace_emojis" = "Replace emoji";
"lng_settings_suggest_emoji" = "Suggest emoji replacements";
"lng_settings_suggest_by_emoji" = "Suggest popular stickers by emoji";
"lng_settings_view_emojis" = "View list";
"lng_settings_send_enter" = "Send by Enter";
@@ -441,6 +444,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_proxy_credentials_optional" = "Credentials (optional)";
"lng_proxy_credentials" = "Credentials";
"lng_proxy_description" = "Your saved proxy list will be here.";
"lng_proxy_sponsor" = "Proxy sponsor";
"lng_proxy_sponsor_about" = "This channel is shown by your proxy server.\nTo remove this channel from your chats list,\ndisable the proxy in Telegram Settings.";
"lng_proxy_sponsor_warning" = "This proxy may display a sponsored channel in your chat list. This doesn't reveal any of your Telegram traffic.";
"lng_settings_blocked_users" = "Blocked users";
"lng_settings_last_seen_privacy" = "Last seen privacy";
@@ -690,6 +696,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_report_title" = "Report channel";
"lng_report_group_title" = "Report group";
"lng_report_bot_title" = "Report bot";
"lng_report_message_title" = "Report message";
"lng_report_reason_spam" = "Spam";
"lng_report_reason_violence" = "Violence";
"lng_report_reason_pornography" = "Pornography";
@@ -1105,6 +1112,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_forward_msg" = "Forward Message";
"lng_context_delete_msg" = "Delete Message";
"lng_context_select_msg" = "Select Message";
"lng_context_report_msg" = "Report Message";
"lng_context_pin_msg" = "Pin Message";
"lng_context_unpin_msg" = "Unpin Message";
"lng_context_cancel_upload" = "Cancel Upload";

View File

@@ -35,6 +35,9 @@
resPQ#05162463 nonce:int128 server_nonce:int128 pq:string server_public_key_fingerprints:Vector<long> = ResPQ;
p_q_inner_data#83c95aec pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 = P_Q_inner_data;
p_q_inner_data_dc#a9f55f95 pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 dc:int = P_Q_inner_data;
p_q_inner_data_temp#3c6a84d4 pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 expires_in:int = P_Q_inner_data;
p_q_inner_data_temp_dc#56fddf88 pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 dc:int expires_in:int = P_Q_inner_data;
server_DH_params_fail#79cb045d nonce:int128 server_nonce:int128 new_nonce_hash:int128 = Server_DH_Params;
server_DH_params_ok#d0e8075c nonce:int128 server_nonce:int128 encrypted_answer:string = Server_DH_Params;
@@ -959,11 +962,16 @@ messages.foundStickerSets#5108d648 hash:int sets:Vector<StickerSetCovered> = mes
fileHash#6242c773 offset:int limit:int hash:bytes = FileHash;
inputClientProxy#75588b3f address:string port:int = InputClientProxy;
help.proxyDataEmpty#e09e1fb8 expires:int = help.ProxyData;
help.proxyDataPromo#2bf7ee23 expires:int peer:Peer chats:Vector<Chat> users:Vector<User> = help.ProxyData;
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
invokeAfterMsgs#3dc4b4f0 {X:Type} msg_ids:Vector<long> query:!X = X;
initConnection#c7481da6 {X:Type} api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string query:!X = X;
initConnection#785188b8 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy query:!X = X;
invokeWithLayer#da9b0d0d {X:Type} layer:int query:!X = X;
invokeWithoutUpdates#bf9459b7 {X:Type} query:!X = X;
@@ -1161,6 +1169,7 @@ help.getTermsOfService#350170f3 = help.TermsOfService;
help.setBotUpdatesStatus#ec22cfcd pending_updates_count:int message:string = Bool;
help.getCdnConfig#52029342 = CdnConfig;
help.getRecentMeUrls#3dc0f114 referer:string = help.RecentMeUrls;
help.getProxyData#3d7758e1 = help.ProxyData;
channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool;
channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector<int> = messages.AffectedMessages;

View File

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

View File

@@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,2,19,0
PRODUCTVERSION 1,2,19,0
FILEVERSION 1,2,22,0
PRODUCTVERSION 1,2,22,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -52,10 +52,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram Messenger LLP"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "1.2.19.0"
VALUE "FileVersion", "1.2.22.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2018"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "1.2.19.0"
VALUE "ProductVersion", "1.2.22.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,2,19,0
PRODUCTVERSION 1,2,19,0
FILEVERSION 1,2,22,0
PRODUCTVERSION 1,2,22,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -43,10 +43,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram Messenger LLP"
VALUE "FileDescription", "Telegram Desktop Updater"
VALUE "FileVersion", "1.2.19.0"
VALUE "FileVersion", "1.2.22.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2018"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "1.2.19.0"
VALUE "ProductVersion", "1.2.22.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -50,6 +50,8 @@ namespace {
constexpr auto kReloadChannelMembersTimeout = 1000; // 1 second wait before reload members in channel after adding
constexpr auto kSaveCloudDraftTimeout = 1000; // save draft to the cloud with 1 sec extra delay
constexpr auto kSaveDraftBeforeQuitTimeout = 1500; // give the app 1.5 secs to save drafts to cloud when quitting
constexpr auto kProxyPromotionInterval = TimeId(60 * 60);
constexpr auto kProxyPromotionMinDelay = TimeId(10);
constexpr auto kSmallDelayMs = 5;
constexpr auto kUnreadMentionsPreloadIfLess = 5;
constexpr auto kUnreadMentionsFirstRequestLimit = 10;
@@ -134,12 +136,13 @@ FileLoadTo FileLoadTaskOptions(const ApiWrap::SendOptions &options) {
ApiWrap::ApiWrap(not_null<AuthSession*> session)
: _session(session)
, _messageDataResolveDelayed([this] { resolveMessageDatas(); })
, _webPagesTimer([this] { resolveWebPages(); })
, _draftsSaveTimer([this] { saveDraftsToCloud(); })
, _featuredSetsReadTimer([this] { readFeaturedSets(); })
, _messageDataResolveDelayed([=] { resolveMessageDatas(); })
, _webPagesTimer([=] { resolveWebPages(); })
, _draftsSaveTimer([=] { saveDraftsToCloud(); })
, _featuredSetsReadTimer([=] { readFeaturedSets(); })
, _fileLoader(std::make_unique<TaskQueue>(kFileLoaderQueueStopTimeout))
, _feedReadTimer([this] { readFeeds(); }) {
, _feedReadTimer([=] { readFeeds(); })
, _proxyPromotionTimer([=] { refreshProxyPromotion(); }) {
}
void ApiWrap::requestChangelog(
@@ -152,6 +155,82 @@ void ApiWrap::requestChangelog(
).send();
}
void ApiWrap::refreshProxyPromotion() {
const auto now = unixtime();
const auto next = (_proxyPromotionNextRequestTime != 0)
? _proxyPromotionNextRequestTime
: now;
if (_proxyPromotionRequestId) {
getProxyPromotionDelayed(now, next);
return;
}
const auto proxy = Global::UseProxy()
? Global::SelectedProxy()
: ProxyData();
const auto key = [&]() -> std::pair<QString, uint32> {
if (!Global::UseProxy()) {
return {};
}
const auto &proxy = Global::SelectedProxy();
if (proxy.type != ProxyData::Type::Mtproto) {
return {};
}
return { proxy.host, proxy.port };
}();
if (_proxyPromotionKey == key && now < next) {
getProxyPromotionDelayed(now, next);
return;
}
_proxyPromotionKey = key;
if (key.first.isEmpty() || !key.second) {
proxyPromotionDone(MTP_help_proxyDataEmpty(
MTP_int(unixtime() + kProxyPromotionInterval)));
return;
}
_proxyPromotionRequestId = request(MTPhelp_GetProxyData(
)).done([=](const MTPhelp_ProxyData &result) {
_proxyPromotionRequestId = 0;
proxyPromotionDone(result);
}).fail([=](const RPCError &error) {
_proxyPromotionRequestId = 0;
const auto now = unixtime();
const auto next = _proxyPromotionNextRequestTime = now
+ kProxyPromotionInterval;
if (!_proxyPromotionTimer.isActive()) {
getProxyPromotionDelayed(now, next);
}
}).send();
}
void ApiWrap::getProxyPromotionDelayed(TimeId now, TimeId next) {
_proxyPromotionTimer.callOnce(std::min(
std::max(next - now, kProxyPromotionMinDelay),
kProxyPromotionInterval) * TimeMs(1000));
};
void ApiWrap::proxyPromotionDone(const MTPhelp_ProxyData &proxy) {
if (proxy.type() == mtpc_help_proxyDataEmpty) {
const auto &data = proxy.c_help_proxyDataEmpty();
const auto next = _proxyPromotionNextRequestTime = data.vexpires.v;
getProxyPromotionDelayed(unixtime(), next);
_session->data().setProxyPromoted(nullptr);
return;
}
Assert(proxy.type() == mtpc_help_proxyDataPromo);
const auto &data = proxy.c_help_proxyDataPromo();
const auto next = _proxyPromotionNextRequestTime = data.vexpires.v;
getProxyPromotionDelayed(unixtime(), next);
App::feedChats(data.vchats);
App::feedUsers(data.vusers);
const auto peerId = peerFromMTP(data.vpeer);
const auto peer = App::peer(peerId);
_session->data().setProxyPromoted(peer);
if (const auto history = App::historyLoaded(peer)) {
_session->api().requestDialogEntry(history);
}
}
void ApiWrap::applyUpdates(
const MTPUpdates &updates,
uint64 sentMessageRandomId) {
@@ -3600,7 +3679,7 @@ void ApiWrap::sendVoiceMessage(
VoiceWaveform waveform,
int duration,
const SendOptions &options) {
const auto caption = QString();
const auto caption = TextWithTags();
const auto to = FileLoadTaskOptions(options);
_fileLoader->addTask(std::make_unique<FileLoadTask>(
result,
@@ -3613,16 +3692,16 @@ void ApiWrap::sendVoiceMessage(
void ApiWrap::sendFiles(
Storage::PreparedList &&list,
SendMediaType type,
QString caption,
TextWithTags &&caption,
std::shared_ptr<SendingAlbum> album,
const SendOptions &options) {
if (list.files.size() > 1 && !caption.isEmpty()) {
if (list.files.size() > 1 && !caption.text.isEmpty()) {
auto message = MainWidget::MessageToSend(options.history);
message.textWithTags = { caption, TextWithTags::Tags() };
message.textWithTags = std::move(caption);
message.replyTo = options.replyTo;
message.clearDraft = false;
App::main()->sendMessage(message);
caption = QString();
caption = TextWithTags();
}
const auto to = FileLoadTaskOptions(options);
@@ -3667,7 +3746,7 @@ void ApiWrap::sendFile(
SendMediaType type,
const SendOptions &options) {
auto to = FileLoadTaskOptions(options);
auto caption = QString();
auto caption = TextWithTags();
_fileLoader->addTask(std::make_unique<FileLoadTask>(
QString(),
fileContent,

View File

@@ -93,6 +93,7 @@ public:
void requestChangelog(
const QString &sinceVersion,
base::lambda<void(const MTPUpdates &result)> callback);
void refreshProxyPromotion();
void requestChannelMembersForAdd(
not_null<ChannelData*> channel,
@@ -252,7 +253,7 @@ public:
void sendFiles(
Storage::PreparedList &&list,
SendMediaType type,
QString caption,
TextWithTags &&caption,
std::shared_ptr<SendingAlbum> album,
const SendOptions &options);
void sendFile(
@@ -359,7 +360,6 @@ private:
not_null<ChannelData*> channel,
const QVector<MTPChannelParticipant> &participants);
void jumpToHistoryDate(not_null<PeerData*> peer, const QDate &date);
void jumpToFeedDate(not_null<Data::Feed*> feed, const QDate &date);
template <typename Callback>
@@ -437,6 +437,9 @@ private:
void readFeeds();
void getProxyPromotionDelayed(TimeId now, TimeId next);
void proxyPromotionDone(const MTPhelp_ProxyData &proxy);
not_null<AuthSession*> _session;
MessageDataRequests _messageDataRequests;
@@ -567,4 +570,10 @@ private:
base::flat_map<not_null<Data::Feed*>, mtpRequestId> _feedReadRequests;
base::Timer _feedReadTimer;
mtpRequestId _proxyPromotionRequestId = 0;
std::pair<QString, uint32> _proxyPromotionKey;
TimeId _proxyPromotionNextRequestTime = TimeId(0);
base::Timer _proxyPromotionTimer;
};

View File

@@ -321,7 +321,8 @@ void Application::refreshGlobalProxy() {
}();
if (proxy.type == ProxyData::Type::Socks5
|| proxy.type == ProxyData::Type::Http) {
QNetworkProxy::setApplicationProxy(ToNetworkProxy(proxy));
QNetworkProxy::setApplicationProxy(
ToNetworkProxy(ToDirectIpProxy(proxy)));
} else {
QNetworkProxyFactory::setUseSystemConfiguration(true);
}

View File

@@ -285,6 +285,11 @@ AuthSession::AuthSession(UserId userId)
_shouldLockAt = 0;
notifications().updateAll();
});
subscribe(Global::RefConnectionTypeChanged(), [=] {
_api->refreshProxyPromotion();
});
_api->refreshProxyPromotion();
Window::Theme::Background()->start();
}
@@ -304,7 +309,10 @@ base::Observable<void> &AuthSession::downloaderTaskFinished() {
}
bool AuthSession::validateSelf(const MTPUser &user) {
if (user.type() != mtpc_user || !user.c_user().is_self() || user.c_user().vid.v != userId()) {
if (user.type() != mtpc_user || !user.c_user().is_self()) {
LOG(("API Error: bad self user received."));
return false;
} else if (user.c_user().vid.v != userId()) {
LOG(("Auth Error: wrong self user received."));
App::logOutDelayed();
return false;
@@ -314,6 +322,7 @@ bool AuthSession::validateSelf(const MTPUser &user) {
void AuthSession::saveSettingsDelayed(TimeMs delay) {
Expects(this == &Auth());
_saveDataTimer.callOnce(delay);
}

View File

@@ -41,7 +41,7 @@ QMap<QString, QString> url_parse_params(
bool is_ipv6(const QString &ip) {
//static const auto regexp = QRegularExpression("^[a-fA-F0-9:]+$");
//return regexp.match(ip).hasMatch();
return ip.indexOf(':') >= 0;
return ip.indexOf('.') < 0 && ip.indexOf(':') >= 0;
}
} // namespace qthelp

View File

@@ -32,7 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
constexpr auto kMaxGroupChannelTitle = 255;
constexpr auto kMaxGroupChannelTitle = 255; // See also edit_peer_info_box.
constexpr auto kMaxChannelDescription = 255; // See also edit_peer_info_box.
constexpr auto kMaxBioLength = 70;
constexpr auto kMinUsernameLength = 5;
@@ -320,11 +320,20 @@ void GroupInfoBox::prepare() {
? lng_dlg_new_channel_name
: lng_dlg_new_group_name));
_title->setMaxLength(kMaxGroupChannelTitle);
_title->setInstantReplaces(Ui::InstantReplaces::Default());
_title ->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
if (_creating == CreatingGroupChannel) {
_description.create(this, st::newGroupDescription, langFactory(lng_create_group_description));
_description.create(
this,
st::newGroupDescription,
Ui::InputField::Mode::MultiLine,
langFactory(lng_create_group_description));
_description->show();
_description->setMaxLength(kMaxChannelDescription);
_description->setInstantReplaces(Ui::InstantReplaces::Default());
_description->setInstantReplacesEnabled(
Global::ReplaceEmojiValue());
connect(_description, SIGNAL(resized()), this, SLOT(onDescriptionResized()));
connect(_description, SIGNAL(submitted(bool)), this, SLOT(onNext()));
@@ -1052,7 +1061,12 @@ bool EditNameBox::saveSelfFail(const RPCError &error) {
EditBioBox::EditBioBox(QWidget*, not_null<UserData*> self) : BoxContent()
, _dynamicFieldStyle(CreateBioFieldStyle())
, _self(self)
, _bio(this, _dynamicFieldStyle, langFactory(lng_bio_placeholder), _self->about())
, _bio(
this,
_dynamicFieldStyle,
Ui::InputField::Mode::MultiLine,
langFactory(lng_bio_placeholder),
_self->about())
, _countdown(this, QString(), Ui::FlatLabel::InitType::Simple, st::editBioCountdownLabel)
, _about(this, lang(lng_bio_about), Ui::FlatLabel::InitType::Simple, st::aboutRevokePublicLabel) {
}
@@ -1063,13 +1077,15 @@ void EditBioBox::prepare() {
addButton(langFactory(lng_settings_save), [this] { save(); });
addButton(langFactory(lng_cancel), [this] { closeBox(); });
_bio->setMaxLength(kMaxBioLength);
_bio->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both);
_bio->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
auto cursor = _bio->textCursor();
cursor.setPosition(_bio->getLastText().size());
_bio->setTextCursor(cursor);
connect(_bio, &Ui::InputArea::submitted, this, [this](bool ctrlShiftEnter) { save(); });
connect(_bio, &Ui::InputArea::resized, this, [this] { updateMaxHeight(); });
connect(_bio, &Ui::InputArea::changed, this, [this] { handleBioUpdated(); });
connect(_bio, &Ui::InputField::submitted, this, [this](bool ctrlShiftEnter) { save(); });
connect(_bio, &Ui::InputField::resized, this, [this] { updateMaxHeight(); });
connect(_bio, &Ui::InputField::changed, this, [this] { handleBioUpdated(); });
_bio->setInstantReplaces(Ui::InstantReplaces::Default());
_bio->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
handleBioUpdated();
updateMaxHeight();
}
@@ -1122,7 +1138,12 @@ void EditBioBox::save() {
EditChannelBox::EditChannelBox(QWidget*, not_null<ChannelData*> channel)
: _channel(channel)
, _title(this, st::defaultInputField, langFactory(_channel->isMegagroup() ? lng_dlg_new_group_name : lng_dlg_new_channel_name), _channel->name)
, _description(this, st::newGroupDescription, langFactory(lng_create_group_description), _channel->about())
, _description(
this,
st::newGroupDescription,
Ui::InputField::Mode::MultiLine,
langFactory(lng_create_group_description),
_channel->about())
, _sign(this, lang(lng_edit_sign_messages), channel->addsSignature(), st::defaultBoxCheckbox)
, _inviteGroup(std::make_shared<Ui::RadioenumGroup<Invites>>(channel->anyoneCanAddMembers() ? Invites::Everybody : Invites::OnlyAdmins))
, _inviteEverybody(this, _inviteGroup, Invites::Everybody, lang(lng_edit_group_invites_everybody))
@@ -1145,7 +1166,11 @@ void EditChannelBox::prepare() {
setMouseTracking(true);
_title->setMaxLength(kMaxGroupChannelTitle);
_title->setInstantReplaces(Ui::InstantReplaces::Default());
_title->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
_description->setMaxLength(kMaxChannelDescription);
_description->setInstantReplaces(Ui::InstantReplaces::Default());
_description->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
connect(_description, SIGNAL(resized()), this, SLOT(onDescriptionResized()));
connect(_description, SIGNAL(submitted(bool)), this, SLOT(onSave()));

View File

@@ -18,7 +18,6 @@ namespace Ui {
class FlatLabel;
class InputField;
class PhoneInput;
class InputArea;
class UsernameInput;
class Checkbox;
template <typename Enum>
@@ -111,7 +110,7 @@ private:
object_ptr<Ui::UserpicButton> _photo = { nullptr };
object_ptr<Ui::InputField> _title = { nullptr };
object_ptr<Ui::InputArea> _description = { nullptr };
object_ptr<Ui::InputField> _description = { nullptr };
// group / channel creation
mtpRequestId _creationRequestId = 0;
@@ -231,7 +230,7 @@ private:
style::InputField _dynamicFieldStyle;
not_null<UserData*> _self;
object_ptr<Ui::InputArea> _bio;
object_ptr<Ui::InputField> _bio;
object_ptr<Ui::FlatLabel> _countdown;
object_ptr<Ui::FlatLabel> _about;
mtpRequestId _requestId = 0;
@@ -280,7 +279,7 @@ private:
not_null<ChannelData*> _channel;
object_ptr<Ui::InputField> _title;
object_ptr<Ui::InputArea> _description;
object_ptr<Ui::InputField> _description;
object_ptr<Ui::Checkbox> _sign;
enum class Invites {

View File

@@ -781,3 +781,4 @@ proxyDropdownDownPosition: point(-2px, 35px);
proxyDropdownUpPosition: point(-2px, 20px);
proxyAboutPadding: margins(22px, 7px, 22px, 14px);
proxyAboutSponsorPadding: margins(22px, 7px, 22px, 0px);

View File

@@ -7,9 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/change_phone_box.h"
#include <rpl/filter.h>
#include <rpl/mappers.h>
#include <rpl/take.h>
#include "lang/lang_keys.h"
#include "styles/style_boxes.h"
#include "ui/widgets/labels.h"

View File

@@ -155,6 +155,7 @@ private:
std::shared_ptr<Ui::RadioenumGroup<Type>> _type;
QPointer<Ui::SlideWrap<>> _aboutSponsored;
QPointer<Ui::InputField> _host;
QPointer<Ui::PortInput> _port;
QPointer<Ui::InputField> _user;
@@ -763,6 +764,16 @@ void ProxyBox::setupTypes() {
label),
st::proxyEditTypePadding);
}
_aboutSponsored = _content->add(object_ptr<Ui::SlideWrap<>>(
_content,
object_ptr<Ui::PaddingWrap<>>(
_content,
object_ptr<Ui::FlatLabel>(
_content,
lang(lng_proxy_sponsor_warning),
Ui::FlatLabel::InitType::Simple,
st::boxDividerLabel),
st::proxyAboutSponsorPadding)));
}
void ProxyBox::setupSocketAddress(const ProxyData &data) {
@@ -875,6 +886,9 @@ void ProxyBox::setupControls(const ProxyData &data) {
_mtprotoCredentials->toggle(
type == Type::Mtproto,
anim::type::instant);
_aboutSponsored->toggle(
type == Type::Mtproto,
anim::type::instant);
};
_type->setChangedCallback([=](Type type) {
handleType(type);
@@ -897,240 +911,6 @@ void ProxyBox::addLabel(
} // namespace
void ConnectionBox::ShowApplyProxyConfirmation(
Type type,
const QMap<QString, QString> &fields) {
const auto server = fields.value(qsl("server"));
const auto port = fields.value(qsl("port")).toUInt();
auto proxy = ProxyData();
proxy.type = type;
proxy.host = server;
proxy.port = port;
if (type == Type::Socks5) {
proxy.user = fields.value(qsl("user"));
proxy.password = fields.value(qsl("pass"));
} else if (type == Type::Mtproto) {
proxy.password = fields.value(qsl("secret"));
}
if (proxy) {
const auto box = std::make_shared<QPointer<ConfirmBox>>();
const auto text = lng_sure_enable_socks(
lt_server,
server,
lt_port,
QString::number(port));
*box = Ui::show(Box<ConfirmBox>(text, lang(lng_sure_enable), [=] {
auto &proxies = Global::RefProxiesList();
if (ranges::find(proxies, proxy) == end(proxies)) {
proxies.push_back(proxy);
}
Global::SetSelectedProxy(proxy);
Global::SetUseProxy(true);
Local::writeSettings();
Sandbox::refreshGlobalProxy();
Global::RefConnectionTypeChanged().notify();
MTP::restart();
if (const auto strong = box->data()) {
strong->closeBox();
}
}), LayerOption::KeepOther);
}
}
ConnectionBox::ConnectionBox(QWidget *parent)
: _hostInput(this, st::connectionHostInputField, langFactory(lng_connection_host_ph), Global::SelectedProxy().host)
, _portInput(this, st::connectionPortInputField, langFactory(lng_connection_port_ph), QString::number(Global::SelectedProxy().port))
, _userInput(this, st::connectionUserInputField, langFactory(lng_connection_user_ph), Global::SelectedProxy().user)
, _passwordInput(this, st::connectionPasswordInputField, langFactory(lng_connection_password_ph), Global::SelectedProxy().password)
, _typeGroup(std::make_shared<Ui::RadioenumGroup<Type>>(Global::SelectedProxy().type))
, _autoRadio(this, _typeGroup, Type::None, lang(lng_connection_auto_rb), st::defaultBoxCheckbox)
, _httpProxyRadio(this, _typeGroup, Type::Http, lang(lng_connection_http_proxy_rb), st::defaultBoxCheckbox)
, _tcpProxyRadio(this, _typeGroup, Type::Socks5, lang(lng_connection_tcp_proxy_rb), st::defaultBoxCheckbox)
, _tryIPv6(this, lang(lng_connection_try_ipv6), Global::TryIPv6(), st::defaultBoxCheckbox) {
}
void ConnectionBox::prepare() {
setTitle(langFactory(lng_connection_header));
addButton(langFactory(lng_connection_save), [this] { onSave(); });
addButton(langFactory(lng_cancel), [this] { closeBox(); });
_typeGroup->setChangedCallback([this](Type value) { typeChanged(value); });
connect(_hostInput, SIGNAL(submitted(bool)), this, SLOT(onSubmit()));
connect(_portInput, SIGNAL(submitted(bool)), this, SLOT(onSubmit()));
connect(_userInput, SIGNAL(submitted(bool)), this, SLOT(onSubmit()));
connect(_passwordInput, SIGNAL(submitted(bool)), this, SLOT(onSubmit()));
connect(_hostInput, SIGNAL(focused()), this, SLOT(onFieldFocus()));
connect(_portInput, SIGNAL(focused()), this, SLOT(onFieldFocus()));
connect(_userInput, SIGNAL(focused()), this, SLOT(onFieldFocus()));
connect(_passwordInput, SIGNAL(focused()), this, SLOT(onFieldFocus()));
updateControlsVisibility();
}
bool ConnectionBox::badProxyValue() const {
return (_hostInput->getLastText().isEmpty() || !_portInput->getLastText().toInt());
}
void ConnectionBox::updateControlsVisibility() {
auto newHeight = st::boxOptionListPadding.top() + _autoRadio->heightNoMargins() + st::boxOptionListSkip + _httpProxyRadio->heightNoMargins() + st::boxOptionListSkip + _tcpProxyRadio->heightNoMargins() + st::boxOptionListSkip + st::connectionIPv6Skip + _tryIPv6->heightNoMargins() + st::defaultCheckbox.margin.bottom() + st::boxOptionListPadding.bottom() + st::boxPadding.bottom();
if (!proxyFieldsVisible()) {
_hostInput->hide();
_portInput->hide();
_userInput->hide();
_passwordInput->hide();
} else {
newHeight += 2 * st::boxOptionInputSkip + 2 * _hostInput->height();
_hostInput->show();
_portInput->show();
_userInput->show();
_passwordInput->show();
}
setDimensions(st::boxWidth, newHeight);
updateControlsPosition();
}
bool ConnectionBox::proxyFieldsVisible() const {
return (_typeGroup->value() == Type::Http
|| _typeGroup->value() == Type::Socks5);
}
void ConnectionBox::setInnerFocus() {
if (proxyFieldsVisible()) {
_hostInput->setFocusFast();
} else {
setFocus();
}
}
void ConnectionBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
updateControlsPosition();
}
void ConnectionBox::updateControlsPosition() {
auto type = _typeGroup->value();
_autoRadio->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _autoRadio->getMargins().top() + st::boxOptionListPadding.top());
_httpProxyRadio->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _autoRadio->bottomNoMargins() + st::boxOptionListSkip);
auto inputy = 0;
auto fieldsVisible = proxyFieldsVisible();
auto fieldsBelowHttp = fieldsVisible && (type == Type::Http);
auto fieldsBelowTcp = fieldsVisible && (type == Type::Socks5);
if (fieldsBelowHttp) {
inputy = _httpProxyRadio->bottomNoMargins() + st::boxOptionInputSkip;
_tcpProxyRadio->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), inputy + st::boxOptionInputSkip + 2 * _hostInput->height() + st::boxOptionListSkip);
} else {
_tcpProxyRadio->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _httpProxyRadio->bottomNoMargins() + st::boxOptionListSkip);
if (fieldsBelowTcp) {
inputy = _tcpProxyRadio->bottomNoMargins() + st::boxOptionInputSkip;
}
}
if (inputy) {
_hostInput->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left() + st::defaultCheck.diameter + st::defaultBoxCheckbox.textPosition.x() - st::defaultInputField.textMargins.left(), inputy);
_portInput->moveToRight(st::boxPadding.right(), inputy);
_userInput->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left() + st::defaultCheck.diameter + st::defaultBoxCheckbox.textPosition.x() - st::defaultInputField.textMargins.left(), _hostInput->y() + _hostInput->height() + st::boxOptionInputSkip);
_passwordInput->moveToRight(st::boxPadding.right(), _userInput->y());
}
auto tryipv6y = (fieldsBelowTcp ? _userInput->bottomNoMargins() : _tcpProxyRadio->bottomNoMargins()) + st::boxOptionListSkip + st::connectionIPv6Skip;
_tryIPv6->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), tryipv6y);
}
void ConnectionBox::typeChanged(Type type) {
if (!proxyFieldsVisible()) {
setFocus();
}
updateControlsVisibility();
if (proxyFieldsVisible()) {
if (!_hostInput->hasFocus() && !_portInput->hasFocus() && !_userInput->hasFocus() && !_passwordInput->hasFocus()) {
_hostInput->setFocusFast();
}
if ((type == Type::Http) && !_portInput->getLastText().toInt()) {
_portInput->setText(qsl("80"));
_portInput->finishAnimating();
}
}
update();
}
void ConnectionBox::onFieldFocus() {
}
void ConnectionBox::onSubmit() {
onFieldFocus();
if (_hostInput->hasFocus()) {
if (!_hostInput->getLastText().trimmed().isEmpty()) {
_portInput->setFocus();
} else {
_hostInput->showError();
}
} else if (_portInput->hasFocus()) {
if (_portInput->getLastText().trimmed().toInt() > 0) {
_userInput->setFocus();
} else {
_portInput->showError();
}
} else if (_userInput->hasFocus()) {
_passwordInput->setFocus();
} else if (_passwordInput->hasFocus()) {
if (_hostInput->getLastText().trimmed().isEmpty()) {
_hostInput->setFocus();
_hostInput->showError();
} else if (_portInput->getLastText().trimmed().toInt() <= 0) {
_portInput->setFocus();
_portInput->showError();
} else {
onSave();
}
}
}
void ConnectionBox::onSave() {
auto proxy = ProxyData();
proxy.host = _hostInput->getLastText().trimmed();
proxy.user = _userInput->getLastText().trimmed();
proxy.password = _passwordInput->getLastText().trimmed();
proxy.port = _portInput->getLastText().toUInt();
auto type = _typeGroup->value();
if (type == Type::None) {
proxy = ProxyData();
} else if (type == Type::Mtproto) {
proxy = Global::SelectedProxy();
} else {
if (proxy.host.isEmpty()) {
_hostInput->showError();
return;
} else if (!proxy.port) {
_portInput->showError();
return;
}
proxy.type = type;
}
Global::SetSelectedProxy(proxy ? proxy : ProxyData());
Global::SetUseProxy(proxy ? true : false);
if (cPlatform() == dbipWindows && Global::TryIPv6() != _tryIPv6->checked()) {
Global::SetTryIPv6(_tryIPv6->checked());
Local::writeSettings();
Global::RefConnectionTypeChanged().notify();
App::restart();
} else {
Global::SetTryIPv6(_tryIPv6->checked());
Local::writeSettings();
Sandbox::refreshGlobalProxy();
Global::RefConnectionTypeChanged().notify();
MTP::restart();
closeBox();
}
}
AutoDownloadBox::AutoDownloadBox(QWidget *parent)
: _photoPrivate(this, lang(lng_media_auto_private_chats), !(cAutoDownloadPhoto() & dbiadNoPrivate), st::defaultBoxCheckbox)
, _photoGroups(this, lang(lng_media_auto_groups), !(cAutoDownloadPhoto() & dbiadNoGroups), st::defaultBoxCheckbox)
@@ -1259,6 +1039,45 @@ ProxiesBoxController::ProxiesBoxController()
}
}
void ProxiesBoxController::ShowApplyConfirmation(
Type type,
const QMap<QString, QString> &fields) {
const auto server = fields.value(qsl("server"));
const auto port = fields.value(qsl("port")).toUInt();
auto proxy = ProxyData();
proxy.type = type;
proxy.host = server;
proxy.port = port;
if (type == Type::Socks5) {
proxy.user = fields.value(qsl("user"));
proxy.password = fields.value(qsl("pass"));
} else if (type == Type::Mtproto) {
proxy.password = fields.value(qsl("secret"));
}
if (proxy) {
const auto box = std::make_shared<QPointer<ConfirmBox>>();
const auto text = lng_sure_enable_socks(
lt_server,
server,
lt_port,
QString::number(port))
+ (proxy.type == Type::Mtproto
? "\n\n" + lang(lng_proxy_sponsor_warning)
: QString());
*box = Ui::show(Box<ConfirmBox>(text, lang(lng_sure_enable), [=] {
auto &proxies = Global::RefProxiesList();
if (ranges::find(proxies, proxy) == end(proxies)) {
proxies.push_back(proxy);
}
Messenger::Instance().setCurrentProxy(proxy, true);
Local::writeSettings();
if (const auto strong = box->data()) {
strong->closeBox();
}
}), LayerOption::KeepOther);
}
}
rpl::producer<bool> ProxiesBoxController::proxyEnabledValue() const {
return _proxyEnabledChanges.events_starting_with_copy(
Global::UseProxy()
@@ -1275,15 +1094,16 @@ void ProxiesBoxController::refreshChecker(Item &item) {
item.state = ItemState::Checking;
const auto setup = [&](Checker &checker) {
checker = MTP::internal::AbstractConnection::create(
checker = MTP::internal::AbstractConnection::Create(
mtproto,
type,
QThread::currentThread());
QThread::currentThread(),
item.data);
setupChecker(item.id, checker);
};
setup(item.checker);
if (item.data.type == Type::Mtproto) {
item.checkerv6 = nullptr;
item.checker->setProxyOverride(item.data);
item.checker->connectToServer(
item.data.host,
item.data.port,
@@ -1312,7 +1132,6 @@ void ProxiesBoxController::refreshChecker(Item &item) {
const Checker &checker,
const std::vector<MTP::DcOptions::Endpoint> &endpoints) {
if (checker) {
checker->setProxyOverride(item.data);
checker->connectToServer(
QString::fromStdString(endpoints.front().ip),
endpoints.front().port,
@@ -1366,7 +1185,7 @@ object_ptr<BoxContent> ProxiesBoxController::CreateOwningBox() {
object_ptr<BoxContent> ProxiesBoxController::create() {
auto result = Box<ProxiesBox>(this);
for (const auto &item : base::reversed(_list)) {
for (const auto &item : _list) {
updateView(item);
}
return std::move(result);
@@ -1411,9 +1230,8 @@ void ProxiesBoxController::applyItem(int id) {
auto j = findByProxy(Global::SelectedProxy());
Global::SetSelectedProxy(item->data);
Global::SetUseProxy(true);
applyChanges();
Messenger::Instance().setCurrentProxy(item->data, true);
saveDelayed();
if (j != end(_list)) {
updateView(*j);
@@ -1433,8 +1251,10 @@ void ProxiesBoxController::setDeleted(int id, bool deleted) {
_lastSelectedProxy = base::take(Global::RefSelectedProxy());
if (Global::UseProxy()) {
_lastSelectedProxyUsed = true;
Global::SetUseProxy(false);
applyChanges();
Messenger::Instance().setCurrentProxy(
ProxyData(),
false);
saveDelayed();
} else {
_lastSelectedProxyUsed = false;
}
@@ -1455,10 +1275,12 @@ void ProxiesBoxController::setDeleted(int id, bool deleted) {
if (!Global::SelectedProxy() && _lastSelectedProxy == item->data) {
Assert(!Global::UseProxy());
Global::SetSelectedProxy(base::take(_lastSelectedProxy));
if (base::take(_lastSelectedProxyUsed)) {
Global::SetUseProxy(true);
applyChanges();
Messenger::Instance().setCurrentProxy(
base::take(_lastSelectedProxy),
true);
} else {
Global::SetSelectedProxy(base::take(_lastSelectedProxy));
}
}
}
@@ -1559,8 +1381,10 @@ bool ProxiesBoxController::setProxyEnabled(bool enabled) {
}
}
}
Global::SetUseProxy(enabled);
applyChanges();
Messenger::Instance().setCurrentProxy(
Global::SelectedProxy(),
enabled);
saveDelayed();
return true;
}
@@ -1580,13 +1404,8 @@ void ProxiesBoxController::setTryIPv6(bool enabled) {
return;
}
Global::SetTryIPv6(enabled);
applyChanges();
}
void ProxiesBoxController::applyChanges() {
Sandbox::refreshGlobalProxy();
Global::RefConnectionTypeChanged().notify();
MTP::restart();
Global::RefConnectionTypeChanged().notify();
saveDelayed();
}

View File

@@ -22,48 +22,6 @@ template <typename Enum>
class Radioenum;
} // namespace Ui
class ConnectionBox : public BoxContent {
Q_OBJECT
public:
using Type = ProxyData::Type;
ConnectionBox(QWidget *parent);
static void ShowApplyProxyConfirmation(
Type type,
const QMap<QString, QString> &fields);
protected:
void prepare() override;
void setInnerFocus() override;
void resizeEvent(QResizeEvent *e) override;
private slots:
void onSubmit();
void onFieldFocus();
void onSave();
private:
void typeChanged(Type type);
void updateControlsVisibility();
void updateControlsPosition();
bool badProxyValue() const;
bool proxyFieldsVisible() const;
object_ptr<Ui::InputField> _hostInput;
object_ptr<Ui::PortInput> _portInput;
object_ptr<Ui::InputField> _userInput;
object_ptr<Ui::PasswordInput> _passwordInput;
std::shared_ptr<Ui::RadioenumGroup<Type>> _typeGroup;
object_ptr<Ui::Radioenum<Type>> _autoRadio;
object_ptr<Ui::Radioenum<Type>> _httpProxyRadio;
object_ptr<Ui::Radioenum<Type>> _tcpProxyRadio;
object_ptr<Ui::Checkbox> _tryIPv6;
};
class AutoDownloadBox : public BoxContent {
Q_OBJECT
@@ -98,6 +56,10 @@ public:
ProxiesBoxController();
static void ShowApplyConfirmation(
Type type,
const QMap<QString, QString> &fields);
static object_ptr<BoxContent> CreateOwningBox();
object_ptr<BoxContent> create();
@@ -155,7 +117,6 @@ private:
void setDeleted(int id, bool deleted);
void updateView(const Item &item);
void share(const ProxyData &proxy);
void applyChanges();
void saveDelayed();
void refreshChecker(Item &item);
void setupChecker(int id, const Checker &checker);

View File

@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_photo.h"
#include "data/data_document.h"
#include "lang/lang_keys.h"
#include "chat_helpers/message_field.h"
#include "window/window_controller.h"
#include "mainwidget.h"
#include "layout.h"
@@ -50,7 +51,11 @@ EditCaptionBox::EditCaptionBox(
}
doc = document;
}
auto caption = item->originalText().text;
const auto original = item->originalText();
const auto editData = TextWithTags {
original.text,
ConvertEntitiesToTextTags(original.entities)
};
if (!_animated && (dimensions.isEmpty() || doc || image->isNull())) {
if (image->isNull()) {
@@ -130,9 +135,17 @@ EditCaptionBox::EditCaptionBox(
}
Assert(_animated || _photo || _doc);
_field.create(this, st::confirmCaptionArea, langFactory(lng_photo_caption), caption);
_field.create(
this,
st::confirmCaptionArea,
Ui::InputField::Mode::MultiLine,
langFactory(lng_photo_caption),
editData);
_field->setMaxLength(MaxPhotoCaption);
_field->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both);
_field->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
_field->setInstantReplaces(Ui::InstantReplaces::Default());
_field->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
_field->setMarkdownReplacesEnabled(Global::ReplaceEmojiValue());
}
void EditCaptionBox::prepareGifPreview(DocumentData *document) {
@@ -177,11 +190,11 @@ void EditCaptionBox::prepare() {
addButton(langFactory(lng_cancel), [this] { closeBox(); });
updateBoxSize();
connect(_field, &Ui::InputArea::submitted, this, [this] { save(); });
connect(_field, &Ui::InputArea::cancelled, this, [this] {
connect(_field, &Ui::InputField::submitted, this, [this] { save(); });
connect(_field, &Ui::InputField::cancelled, this, [this] {
closeBox();
});
connect(_field, &Ui::InputArea::resized, this, [this] {
connect(_field, &Ui::InputField::resized, this, [this] {
captionResized();
});
@@ -332,17 +345,29 @@ void EditCaptionBox::save() {
if (_previewCancelled) {
flags |= MTPmessages_EditMessage::Flag::f_no_webpage;
}
MTPVector<MTPMessageEntity> sentEntities;
const auto textWithTags = _field->getTextWithTags();
auto sending = TextWithEntities{
textWithTags.text,
ConvertTextTagsToEntities(textWithTags.tags)
};
const auto prepareFlags = Ui::ItemTextOptions(
item->history(),
App::self()).flags;
TextUtilities::PrepareForSending(sending, prepareFlags);
TextUtilities::Trim(sending);
const auto sentEntities = TextUtilities::EntitiesToMTP(
sending.entities,
TextUtilities::ConvertOption::SkipLocal);
if (!sentEntities.v.isEmpty()) {
flags |= MTPmessages_EditMessage::Flag::f_entities;
}
auto text = TextUtilities::PrepareForSending(_field->getLastText(), TextUtilities::PrepareTextOption::CheckLinks);
_saveRequestId = MTP::send(
MTPmessages_EditMessage(
MTP_flags(flags),
item->history()->peer->input,
MTP_int(item->id),
MTP_string(text),
MTP_string(sending.text),
MTPnullMarkup,
sentEntities,
MTP_inputGeoPointEmpty()),

View File

@@ -14,7 +14,7 @@ class Media;
} // namespace Data
namespace Ui {
class InputArea;
class InputField;
} // namespace Ui
class EditCaptionBox : public BoxContent, public RPCSender {
@@ -49,7 +49,7 @@ private:
QPixmap _thumb;
Media::Clip::ReaderPointer _gifPreview;
object_ptr<Ui::InputArea> _field = { nullptr };
object_ptr<Ui::InputField> _field = { nullptr };
int _thumbx = 0;
int _thumbw = 0;

View File

@@ -38,6 +38,7 @@ namespace {
constexpr auto kUsernameCheckTimeout = TimeMs(200);
constexpr auto kMinUsernameLength = 5;
constexpr auto kMaxGroupChannelTitle = 255; // See also add_contact_box.
constexpr auto kMaxChannelDescription = 255; // See also add_contact_box.
class Controller
@@ -71,7 +72,7 @@ private:
};
struct Controls {
Ui::InputField *title = nullptr;
Ui::InputArea *description = nullptr;
Ui::InputField *description = nullptr;
Ui::UserpicButton *photo = nullptr;
rpl::lifetime initialPhotoImageWaiting;
@@ -299,6 +300,10 @@ object_ptr<Ui::RpWidget> Controller::createTitleEdit() {
: lng_dlg_new_channel_name),
_peer->name),
st::editPeerTitleMargins);
result->entity()->setMaxLength(kMaxGroupChannelTitle);
result->entity()->setInstantReplaces(Ui::InstantReplaces::Default());
result->entity()->setInstantReplacesEnabled(
Global::ReplaceEmojiValue());
QObject::connect(
result->entity(),
@@ -317,19 +322,23 @@ object_ptr<Ui::RpWidget> Controller::createDescriptionEdit() {
return nullptr;
}
auto result = object_ptr<Ui::PaddingWrap<Ui::InputArea>>(
auto result = object_ptr<Ui::PaddingWrap<Ui::InputField>>(
_wrap,
object_ptr<Ui::InputArea>(
object_ptr<Ui::InputField>(
_wrap,
st::editPeerDescription,
Ui::InputField::Mode::MultiLine,
langFactory(lng_create_group_description),
channel->about()),
st::editPeerDescriptionMargins);
result->entity()->setMaxLength(kMaxChannelDescription);
result->entity()->setInstantReplaces(Ui::InstantReplaces::Default());
result->entity()->setInstantReplacesEnabled(
Global::ReplaceEmojiValue());
QObject::connect(
result->entity(),
&Ui::InputArea::submitted,
&Ui::InputField::submitted,
[this] { submitDescription(); });
_controls.description = result->entity();

View File

@@ -71,9 +71,13 @@ void RateCallBox::ratingChanged(int value) {
}
if (value < kMaxRating) {
if (!_comment) {
_comment.create(this, st::callRatingComment, langFactory(lng_call_rate_comment));
_comment.create(
this,
st::callRatingComment,
Ui::InputField::Mode::MultiLine,
langFactory(lng_call_rate_comment));
_comment->show();
_comment->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both);
_comment->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
_comment->setMaxLength(MaxPhotoCaption);
_comment->resize(width() - (st::callRatingPadding.left() + st::callRatingPadding.right()), _comment->height());

View File

@@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/sender.h"
namespace Ui {
class InputArea;
class InputField;
class FlatLabel;
class IconButton;
} // namespace Ui
@@ -44,7 +44,7 @@ private:
int _rating = 0;
std::vector<object_ptr<Ui::IconButton>> _stars;
object_ptr<Ui::InputArea> _comment = { nullptr };
object_ptr<Ui::InputField> _comment = { nullptr };
mtpRequestId _requestId = 0;

View File

@@ -14,23 +14,54 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/checkbox.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h"
#include "ui/toast/toast.h"
#include "mainwindow.h"
ReportBox::ReportBox(QWidget*, PeerData *peer) : _peer(peer)
, _reasonGroup(std::make_shared<Ui::RadioenumGroup<Reason>>(Reason::Spam))
, _reasonSpam(this, _reasonGroup, Reason::Spam, lang(lng_report_reason_spam), st::defaultBoxCheckbox)
, _reasonViolence(this, _reasonGroup, Reason::Violence, lang(lng_report_reason_violence), st::defaultBoxCheckbox)
, _reasonPornography(this, _reasonGroup, Reason::Pornography, lang(lng_report_reason_pornography), st::defaultBoxCheckbox)
, _reasonOther(this, _reasonGroup, Reason::Other, lang(lng_report_reason_other), st::defaultBoxCheckbox) {
ReportBox::ReportBox(QWidget*, not_null<PeerData*> peer)
: _peer(peer) {
}
ReportBox::ReportBox(QWidget*, not_null<PeerData*> peer, MessageIdsList ids)
: _peer(peer)
, _ids(std::move(ids)) {
}
void ReportBox::prepare() {
setTitle(langFactory(_peer->isUser() ? lng_report_bot_title : (_peer->isMegagroup() ? lng_report_group_title : lng_report_title)));
setTitle(langFactory([&] {
if (_ids) {
return lng_report_message_title;
} else if (_peer->isUser()) {
return lng_report_bot_title;
} else if (_peer->isMegagroup()) {
return lng_report_group_title;
} else {
return lng_report_title;
}
}()));
addButton(langFactory(lng_report_button), [this] { onReport(); });
addButton(langFactory(lng_cancel), [this] { closeBox(); });
_reasonGroup->setChangedCallback([this](Reason value) { reasonChanged(value); });
_reasonGroup = std::make_shared<Ui::RadioenumGroup<Reason>>(
Reason::Spam);
const auto createButton = [&](
object_ptr<Ui::Radioenum<Reason>> &button,
Reason reason,
LangKey key) {
button.create(
this,
_reasonGroup,
reason,
lang(key),
st::defaultBoxCheckbox);
};
createButton(_reasonSpam, Reason::Spam, lng_report_reason_spam);
createButton(_reasonViolence, Reason::Violence, lng_report_reason_violence);
createButton(_reasonPornography, Reason::Pornography, lng_report_reason_pornography);
createButton(_reasonOther, Reason::Other, lng_report_reason_other);
_reasonGroup->setChangedCallback([=](Reason value) {
reasonChanged(value);
});
updateMaxHeight();
}
@@ -51,9 +82,13 @@ void ReportBox::resizeEvent(QResizeEvent *e) {
void ReportBox::reasonChanged(Reason reason) {
if (reason == Reason::Other) {
if (!_reasonOtherText) {
_reasonOtherText.create(this, st::profileReportReasonOther, langFactory(lng_report_reason_description));
_reasonOtherText.create(
this,
st::profileReportReasonOther,
Ui::InputField::Mode::MultiLine,
langFactory(lng_report_reason_description));
_reasonOtherText->show();
_reasonOtherText->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both);
_reasonOtherText->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
_reasonOtherText->setMaxLength(MaxPhotoCaption);
_reasonOtherText->resize(width() - (st::boxPadding.left() + st::boxOptionListPadding.left() + st::boxPadding.right()), _reasonOtherText->height());
@@ -90,7 +125,7 @@ void ReportBox::onReport() {
return;
}
auto getReason = [this]() {
const auto reason = [&] {
switch (_reasonGroup->value()) {
case Reason::Spam: return MTP_inputReportReasonSpam();
case Reason::Violence: return MTP_inputReportReasonViolence();
@@ -98,17 +133,37 @@ void ReportBox::onReport() {
case Reason::Other: return MTP_inputReportReasonOther(MTP_string(_reasonOtherText->getLastText()));
}
Unexpected("Bad reason group value.");
};
_requestId = MTP::send(MTPaccount_ReportPeer(_peer->input, getReason()), rpcDone(&ReportBox::reportDone), rpcFail(&ReportBox::reportFail));
}();
if (_ids) {
auto ids = QVector<MTPint>();
for (const auto &fullId : *_ids) {
ids.push_back(MTP_int(fullId.msg));
}
_requestId = MTP::send(
MTPmessages_Report(
_peer->input,
MTP_vector<MTPint>(ids),
reason),
rpcDone(&ReportBox::reportDone),
rpcFail(&ReportBox::reportFail));
} else {
_requestId = MTP::send(
MTPaccount_ReportPeer(_peer->input, reason),
rpcDone(&ReportBox::reportDone),
rpcFail(&ReportBox::reportFail));
}
}
void ReportBox::reportDone(const MTPBool &result) {
_requestId = 0;
Ui::show(Box<InformBox>(lang(lng_report_thanks)));
Ui::Toast::Show(lang(lng_report_thanks));
closeBox();
}
bool ReportBox::reportFail(const RPCError &error) {
if (MTP::isDefaultHandledError(error)) return false;
if (MTP::isDefaultHandledError(error)) {
return false;
}
_requestId = 0;
if (_reasonOtherText) {

View File

@@ -14,14 +14,15 @@ template <typename Enum>
class RadioenumGroup;
template <typename Enum>
class Radioenum;
class InputArea;
class InputField;
} // namespace Ui
class ReportBox : public BoxContent, public RPCSender {
Q_OBJECT
public:
ReportBox(QWidget*, PeerData *peer);
ReportBox(QWidget*, not_null<PeerData*> peer);
ReportBox(QWidget*, not_null<PeerData*> peer, MessageIdsList ids);
private slots:
void onReport();
@@ -49,14 +50,15 @@ private:
void reportDone(const MTPBool &result);
bool reportFail(const RPCError &error);
PeerData *_peer;
not_null<PeerData*> _peer;
base::optional<MessageIdsList> _ids;
std::shared_ptr<Ui::RadioenumGroup<Reason>> _reasonGroup;
object_ptr<Ui::Radioenum<Reason>> _reasonSpam;
object_ptr<Ui::Radioenum<Reason>> _reasonViolence;
object_ptr<Ui::Radioenum<Reason>> _reasonPornography;
object_ptr<Ui::Radioenum<Reason>> _reasonOther;
object_ptr<Ui::InputArea> _reasonOtherText = { nullptr };
object_ptr<Ui::Radioenum<Reason>> _reasonSpam = { nullptr };
object_ptr<Ui::Radioenum<Reason>> _reasonViolence = { nullptr };
object_ptr<Ui::Radioenum<Reason>> _reasonPornography = { nullptr };
object_ptr<Ui::Radioenum<Reason>> _reasonOther = { nullptr };
object_ptr<Ui::InputField> _reasonOtherText = { nullptr };
mtpRequestId _requestId = 0;

View File

@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/storage_media_prepare.h"
#include "mainwidget.h"
#include "history/history_media_types.h"
#include "chat_helpers/message_field.h"
#include "core/file_utilities.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/buttons.h"
@@ -1319,10 +1320,17 @@ void SendFilesBox::AlbumPreview::mouseReleaseEvent(QMouseEvent *e) {
SendFilesBox::SendFilesBox(
QWidget*,
Storage::PreparedList &&list,
const TextWithTags &caption,
CompressConfirm compressed)
: _list(std::move(list))
, _compressConfirmInitial(compressed)
, _compressConfirm(compressed) {
, _compressConfirm(compressed)
, _caption(
this,
st::confirmCaptionArea,
Ui::InputField::Mode::MultiLine,
FieldPlaceholder(_list),
caption) {
}
void SendFilesBox::initPreview(rpl::producer<int> desiredPreviewHeight) {
@@ -1417,6 +1425,7 @@ void SendFilesBox::prepare() {
_send = addButton(langFactory(lng_send_button), [this] { send(); });
addButton(langFactory(lng_cancel), [this] { closeBox(); });
setupCaption();
initSendWay();
preparePreview();
subscribe(boxClosing, [this] {
@@ -1488,7 +1497,7 @@ void SendFilesBox::preparePreview() {
void SendFilesBox::setupControls() {
setupTitleText();
setupSendWayControls();
setupCaption();
_caption->setPlaceholder(FieldPlaceholder(_list));
}
void SendFilesBox::setupSendWayControls() {
@@ -1545,34 +1554,31 @@ void SendFilesBox::applyAlbumOrder() {
}
void SendFilesBox::setupCaption() {
if (_caption) {
_caption->setPlaceholder(FieldPlaceholder(_list));
return;
}
_caption.create(this, st::confirmCaptionArea, FieldPlaceholder(_list));
_caption->setMaxLength(MaxPhotoCaption);
_caption->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both);
connect(_caption, &Ui::InputArea::resized, this, [this] {
_caption->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
connect(_caption, &Ui::InputField::resized, this, [this] {
captionResized();
});
connect(_caption, &Ui::InputArea::submitted, this, [this](
bool ctrlShiftEnter) {
connect(_caption, &Ui::InputField::submitted, this, [this](
bool ctrlShiftEnter) {
send(ctrlShiftEnter);
});
connect(_caption, &Ui::InputArea::cancelled, this, [this] {
connect(_caption, &Ui::InputField::cancelled, this, [this] {
closeBox();
});
_caption->setMimeDataHook([this](
not_null<const QMimeData*> data,
Ui::InputArea::MimeAction action) {
if (action == Ui::InputArea::MimeAction::Check) {
Ui::InputField::MimeAction action) {
if (action == Ui::InputField::MimeAction::Check) {
return canAddFiles(data);
} else if (action == Ui::InputArea::MimeAction::Insert) {
} else if (action == Ui::InputField::MimeAction::Insert) {
return addFiles(data);
}
Unexpected("action in MimeData hook.");
});
_caption->setInstantReplaces(Ui::InstantReplaces::Default());
_caption->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
_caption->setMarkdownReplacesEnabled(Global::ReplaceEmojiValue());
}
void SendFilesBox::captionResized() {
@@ -1784,10 +1790,8 @@ void SendFilesBox::send(bool ctrlShiftEnter) {
_confirmed = true;
if (_confirmedCallback) {
auto caption = _caption
? TextUtilities::PrepareForSending(
_caption->getLastText(),
TextUtilities::PrepareTextOption::CheckLinks)
: QString();
? _caption->getTextWithTags()
: TextWithTags();
_confirmedCallback(
std::move(_list),
way,

View File

@@ -18,7 +18,7 @@ class Radioenum;
template <typename Enum>
class RadioenumGroup;
class RoundButton;
class InputArea;
class InputField;
struct GroupMediaLayout;
} // namespace Ui
@@ -33,13 +33,14 @@ public:
SendFilesBox(
QWidget*,
Storage::PreparedList &&list,
const TextWithTags &caption,
CompressConfirm compressed);
void setConfirmedCallback(
base::lambda<void(
Storage::PreparedList &&list,
SendFilesWay way,
const QString &caption,
TextWithTags &&caption,
bool ctrlShiftEnter)> callback) {
_confirmedCallback = std::move(callback);
}
@@ -98,12 +99,12 @@ private:
base::lambda<void(
Storage::PreparedList &&list,
SendFilesWay way,
const QString &caption,
TextWithTags &&caption,
bool ctrlShiftEnter)> _confirmedCallback;
base::lambda<void()> _cancelledCallback;
bool _confirmed = false;
object_ptr<Ui::InputArea> _caption = { nullptr };
object_ptr<Ui::InputField> _caption = { nullptr };
object_ptr<Ui::Radioenum<SendFilesWay>> _sendAlbum = { nullptr };
object_ptr<Ui::Radioenum<SendFilesWay>> _sendPhotos = { nullptr };
object_ptr<Ui::Radioenum<SendFilesWay>> _sendFiles = { nullptr };

View File

@@ -35,7 +35,7 @@ namespace Calls {
namespace {
constexpr auto kMinLayer = 65;
constexpr auto kMaxLayer = 65; // MTP::CurrentLayer?
constexpr auto kMaxLayer = 75;
constexpr auto kHangupTimeoutMs = 5000;
using tgvoip::Endpoint;
@@ -57,7 +57,7 @@ void ConvertEndpoint(
(uint16_t)mtc.vport.v,
ipv4,
ipv6,
EP_TYPE_UDP_RELAY,
tgvoip::Endpoint::TYPE_UDP_RELAY,
(unsigned char*)mtc.vpeer_tag.v.data()));
}
@@ -77,6 +77,46 @@ uint64 ComputeFingerprint(
} // namespace
void Call::ControllerPointer::create() {
Expects(_data == nullptr);
_data = std::make_unique<tgvoip::VoIPController>();
}
void Call::ControllerPointer::reset() {
if (const auto controller = base::take(_data)) {
controller->Stop();
}
}
bool Call::ControllerPointer::empty() const {
return (_data == nullptr);
}
bool Call::ControllerPointer::operator==(std::nullptr_t) const {
return empty();
}
Call::ControllerPointer::operator bool() const {
return !empty();
}
tgvoip::VoIPController *Call::ControllerPointer::operator->() const {
Expects(!empty());
return _data.get();
}
tgvoip::VoIPController &Call::ControllerPointer::operator*() const {
Expects(!empty());
return *_data;
}
Call::ControllerPointer::~ControllerPointer() {
reset();
}
Call::Call(
not_null<Delegate*> delegate,
not_null<UserData*> user,
@@ -481,7 +521,7 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
}
voip_config_t config = { 0 };
config.data_saving = DATA_SAVING_NEVER;
config.data_saving = tgvoip::DATA_SAVING_NEVER;
#ifdef Q_OS_MAC
config.enableAEC = (QSysInfo::macVersion() < QSysInfo::MV_10_7);
#else // Q_OS_MAC
@@ -504,23 +544,30 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
}
}
std::vector<Endpoint> endpoints;
const auto &protocol = call.vprotocol.c_phoneCallProtocol();
auto endpoints = std::vector<Endpoint>();
ConvertEndpoint(endpoints, call.vconnection.c_phoneConnection());
for (int i = 0; i < call.valternative_connections.v.length(); i++) {
ConvertEndpoint(endpoints, call.valternative_connections.v[i].c_phoneConnection());
}
_controller = std::make_unique<tgvoip::VoIPController>();
auto callbacks = tgvoip::VoIPController::Callbacks();
callbacks.connectionStateChanged = [](
tgvoip::VoIPController *controller,
int state) {
const auto call = static_cast<Call*>(controller->implData);
call->handleControllerStateChange(controller, state);
};
_controller.create();
if (_mute) {
_controller->SetMicMute(_mute);
}
_controller->implData = static_cast<void*>(this);
_controller->SetRemoteEndpoints(endpoints, true);
_controller->SetRemoteEndpoints(endpoints, true, protocol.vmax_layer.v);
_controller->SetConfig(&config);
_controller->SetEncryptionKey(reinterpret_cast<char*>(_authKey.data()), (_type == Type::Outgoing));
_controller->SetStateCallback([](tgvoip::VoIPController *controller, int state) {
static_cast<Call*>(controller->implData)->handleControllerStateChange(controller, state);
});
_controller->SetCallbacks(callbacks);
if (Global::UseProxy() && Global::UseProxyForCalls()) {
const auto proxy = Global::SelectedProxy();
if (proxy.supportsCalls()) {
@@ -543,22 +590,22 @@ void Call::handleControllerStateChange(tgvoip::VoIPController *controller, int s
Expects(controller->implData == static_cast<void*>(this));
switch (state) {
case STATE_WAIT_INIT: {
case tgvoip::STATE_WAIT_INIT: {
DEBUG_LOG(("Call Info: State changed to WaitingInit."));
setStateQueued(State::WaitingInit);
} break;
case STATE_WAIT_INIT_ACK: {
case tgvoip::STATE_WAIT_INIT_ACK: {
DEBUG_LOG(("Call Info: State changed to WaitingInitAck."));
setStateQueued(State::WaitingInitAck);
} break;
case STATE_ESTABLISHED: {
case tgvoip::STATE_ESTABLISHED: {
DEBUG_LOG(("Call Info: State changed to Established."));
setStateQueued(State::Established);
} break;
case STATE_FAILED: {
case tgvoip::STATE_FAILED: {
auto error = controller->GetLastError();
LOG(("Call Info: State changed to Failed, error: %1.").arg(error));
setFailedQueued(error);
@@ -714,9 +761,12 @@ void Call::handleRequestError(const RPCError &error) {
}
void Call::handleControllerError(int error) {
if (error == TGVOIP_ERROR_INCOMPATIBLE) {
Ui::show(Box<InformBox>(Lang::Hard::CallErrorIncompatible().replace("{user}", App::peerName(_user))));
} else if (error == TGVOIP_ERROR_AUDIO_IO) {
if (error == tgvoip::ERROR_INCOMPATIBLE) {
Ui::show(Box<InformBox>(
Lang::Hard::CallErrorIncompatible().replace(
"{user}",
App::peerName(_user))));
} else if (error == tgvoip::ERROR_AUDIO_IO) {
Ui::show(Box<InformBox>(lang(lng_call_error_audio_io)));
}
finish(FinishType::Failed);

View File

@@ -116,6 +116,23 @@ public:
~Call();
private:
class ControllerPointer {
public:
void create();
void reset();
bool empty() const;
bool operator==(std::nullptr_t) const;
explicit operator bool() const;
tgvoip::VoIPController *operator->() const;
tgvoip::VoIPController &operator*() const;
~ControllerPointer();
private:
std::unique_ptr<tgvoip::VoIPController> _data;
};
enum class FinishType {
None,
Ended,
@@ -170,7 +187,7 @@ private:
uint64 _accessHash = 0;
uint64 _keyFingerprint = 0;
std::unique_ptr<tgvoip::VoIPController> _controller;
ControllerPointer _controller;
std::unique_ptr<Media::Audio::Track> _waitingTrack;

View File

@@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Calls {
namespace {
ushort Data[] = {
const ushort Data[] = {
0xd83d, 0xde09, 0xd83d, 0xde0d, 0xd83d, 0xde1b, 0xd83d, 0xde2d, 0xd83d, 0xde31, 0xd83d, 0xde21,
0xd83d, 0xde0e, 0xd83d, 0xde34, 0xd83d, 0xde35, 0xd83d, 0xde08, 0xd83d, 0xde2c, 0xd83d, 0xde07,
0xd83d, 0xde0f, 0xd83d, 0xdc6e, 0xd83d, 0xdc77, 0xd83d, 0xdc82, 0xd83d, 0xdc76, 0xd83d, 0xdc68,
@@ -69,7 +69,7 @@ ushort Data[] = {
0x0030, 0x20e3, 0xd83d, 0xdd1f, 0x2757, 0x2753, 0x2665, 0x2666, 0xd83d, 0xdcaf, 0xd83d, 0xdd17,
0xd83d, 0xdd31, 0xd83d, 0xdd34, 0xd83d, 0xdd35, 0xd83d, 0xdd36, 0xd83d, 0xdd37 };
ushort Offsets[] = {
const ushort Offsets[] = {
0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22,
24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46,
48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70,
@@ -119,7 +119,9 @@ std::vector<EmojiPtr> ComputeEmojiFingerprint(not_null<Call*> call) {
for (auto index = 0; index != EmojiCount; ++index) {
auto offset = Offsets[index];
auto size = Offsets[index + 1] - offset;
auto string = QString::fromRawData(reinterpret_cast<QChar*>(Data + offset), size);
auto string = QString::fromRawData(
reinterpret_cast<const QChar*>(Data + offset),
size);
auto emoji = Ui::Emoji::Find(string);
Assert(emoji != nullptr);
}
@@ -131,7 +133,9 @@ std::vector<EmojiPtr> ComputeEmojiFingerprint(not_null<Call*> call) {
auto index = value % EmojiCount;
auto offset = Offsets[index];
auto size = Offsets[index + 1] - offset;
auto string = QString::fromRawData(reinterpret_cast<QChar*>(Data + offset), size);
auto string = QString::fromRawData(
reinterpret_cast<const QChar*>(Data + offset),
size);
auto emoji = Ui::Emoji::Find(string);
Assert(emoji != nullptr);
result.push_back(emoji);

View File

@@ -14,11 +14,15 @@ namespace Ui {
namespace Emoji {
inline utf16string QStringToUTF16(const QString &string) {
return utf16string(reinterpret_cast<const utf16char*>(string.constData()), string.size());
return utf16string(
reinterpret_cast<const utf16char*>(string.constData()),
string.size());
}
inline QString QStringFromUTF16(utf16string string) {
return QString::fromRawData(reinterpret_cast<const QChar*>(string.data()), string.size());
return QString::fromRawData(
reinterpret_cast<const QChar*>(string.data()),
string.size());
}
constexpr auto kSuggestionMaxLength = internal::kReplacementMaxLength;

View File

@@ -346,12 +346,15 @@ void SuggestionsWidget::leaveEventHook(QEvent *e) {
return TWidget::leaveEventHook(e);
}
SuggestionsController::SuggestionsController(QWidget *parent, not_null<QTextEdit*> field) : QObject(nullptr)
SuggestionsController::SuggestionsController(QWidget *parent, not_null<QTextEdit*> field)
: QObject(nullptr)
, _field(field)
, _container(parent, st::emojiSuggestionsDropdown)
, _suggestions(_container->setOwnedWidget(object_ptr<Ui::Emoji::SuggestionsWidget>(parent, st::emojiSuggestionsMenu))) {
_container->setAutoHiding(false);
setReplaceCallback(nullptr);
_field->installEventFilter(this);
connect(_field, &QTextEdit::textChanged, this, [this] { handleTextChange(); });
connect(_field, &QTextEdit::cursorPositionChanged, this, [this] { handleCursorPositionChange(); });
@@ -363,6 +366,23 @@ SuggestionsController::SuggestionsController(QWidget *parent, not_null<QTextEdit
handleTextChange();
}
void SuggestionsController::setReplaceCallback(
base::lambda<void(
int from,
int till,
const QString &replacement)> callback) {
if (callback) {
_replaceCallback = std::move(callback);
} else {
_replaceCallback = [=](int from, int till, const QString &replacement) {
auto cursor = _field->textCursor();
cursor.setPosition(from);
cursor.setPosition(till, QTextCursor::KeepAnchor);
cursor.insertText(replacement);
};
}
}
void SuggestionsController::handleTextChange() {
_ignoreCursorPositionChange = true;
InvokeQueued(this, [this] { _ignoreCursorPositionChange = false; });
@@ -374,7 +394,7 @@ void SuggestionsController::handleTextChange() {
}
QString SuggestionsController::getEmojiQuery() {
if (!cReplaceEmojis()) {
if (!Global::SuggestEmoji()) {
return QString();
}
@@ -471,23 +491,14 @@ QString SuggestionsController::getEmojiQuery() {
}
void SuggestionsController::replaceCurrent(const QString &replacement) {
auto cursor = _field->textCursor();
auto suggestion = getEmojiQuery();
if (suggestion.isEmpty()) {
_suggestions->showWithQuery(QString());
} else {
cursor.setPosition(cursor.position() - suggestion.size(), QTextCursor::KeepAnchor);
cursor.insertText(replacement);
}
if (auto emoji = Find(replacement)) {
if (emoji->hasVariants()) {
auto it = cEmojiVariants().constFind(emoji->nonColoredId());
if (it != cEmojiVariants().cend()) {
emoji = emoji->variant(it.value());
}
}
AddRecent(emoji);
const auto cursor = _field->textCursor();
const auto position = cursor.position();
const auto from = position - suggestion.size();
_replaceCallback(from, position, replacement);
}
}

View File

@@ -12,7 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Ui {
class InnerDropdown;
class FlatTextarea;
namespace Emoji {
@@ -69,6 +68,10 @@ public:
SuggestionsController(QWidget *parent, not_null<QTextEdit*> field);
void raise();
void setReplaceCallback(base::lambda<void(
int from,
int till,
const QString &replacement)> callback);
protected:
bool eventFilter(QObject *object, QEvent *event) override;
@@ -88,6 +91,10 @@ private:
bool _ignoreCursorPositionChange = false;
bool _textChangeAfterKeyPress = false;
QPointer<QTextEdit> _field;
base::lambda<void(
int from,
int till,
const QString &replacement)> _replaceCallback;
object_ptr<InnerDropdown> _container;
QPointer<SuggestionsWidget> _suggestions;

View File

@@ -523,10 +523,19 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
QRect r(e->rect());
if (r != rect()) p.setClipRect(r);
int32 atwidth = st::mentionFont->width('@'), hashwidth = st::mentionFont->width('#');
int32 mentionleft = 2 * st::mentionPadding.left() + st::mentionPhotoSize;
int32 mentionwidth = width() - mentionleft - 2 * st::mentionPadding.right();
int32 htagleft = st::historyAttach.width + st::historyComposeField.textMrg.left() - st::lineWidth, htagwidth = width() - st::mentionPadding.right() - htagleft - st::mentionScroll.width;
auto atwidth = st::mentionFont->width('@');
auto hashwidth = st::mentionFont->width('#');
auto mentionleft = 2 * st::mentionPadding.left() + st::mentionPhotoSize;
auto mentionwidth = width()
- mentionleft
- 2 * st::mentionPadding.right();
auto htagleft = st::historyAttach.width
+ st::historyComposeField.textMargins.left()
- st::lineWidth;
auto htagwidth = width()
- st::mentionPadding.right()
- htagleft
- st::mentionScroll.width;
if (!_srows->empty()) {
int32 rows = rowscount(_srows->size(), _stickersPerRow);

View File

@@ -9,15 +9,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_widget.h"
#include "base/qthelp_regex.h"
#include "styles/style_history.h"
#include "window/window_controller.h"
#include "mainwindow.h"
#include "auth_session.h"
#include "styles/style_history.h"
namespace {
constexpr auto kParseLinksTimeout = TimeMs(1000);
// For mention tags save and validate userId, ignore tags for different userId.
class FieldTagMimeProcessor : public Ui::FlatTextarea::TagMimeProcessor {
class FieldTagMimeProcessor : public Ui::InputField::TagMimeProcessor {
public:
QString mimeTagFromTag(const QString &tagId) override {
return ConvertTagToMimeTag(tagId);
@@ -26,7 +28,8 @@ public:
QString tagFromMimeTag(const QString &mimeTag) override {
if (mimeTag.startsWith(qstr("mention://"))) {
auto match = QRegularExpression(":(\\d+)$").match(mimeTag);
if (!match.hasMatch() || match.capturedRef(1).toInt() != Auth().userId()) {
if (!match.hasMatch()
|| match.capturedRef(1).toInt() != Auth().userId()) {
return QString();
}
return mimeTag.mid(0, mimeTag.size() - match.capturedLength());
@@ -53,11 +56,25 @@ EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) {
result.reserve(tags.size());
auto mentionStart = qstr("mention://user.");
for_const (auto &tag, tags) {
for (const auto &tag : tags) {
const auto push = [&](
EntityInTextType type,
const QString &data = QString()) {
result.push_back(
EntityInText(type, tag.offset, tag.length, data));
};
if (tag.id.startsWith(mentionStart)) {
if (auto match = qthelp::regex_match("^(\\d+\\.\\d+)(/|$)", tag.id.midRef(mentionStart.size()))) {
result.push_back(EntityInText(EntityInTextMentionName, tag.offset, tag.length, match->captured(1)));
push(EntityInTextMentionName, match->captured(1));
}
} else if (tag.id == Ui::InputField::kTagBold) {
push(EntityInTextBold);
} else if (tag.id == Ui::InputField::kTagItalic) {
push(EntityInTextItalic);
} else if (tag.id == Ui::InputField::kTagCode) {
push(EntityInTextCode);
} else if (tag.id == Ui::InputField::kTagPre) {
push(EntityInTextPre);
}
}
return result;
@@ -70,12 +87,21 @@ TextWithTags::Tags ConvertEntitiesToTextTags(const EntitiesInText &entities) {
}
result.reserve(entities.size());
for_const (auto &entity, entities) {
if (entity.type() == EntityInTextMentionName) {
for (const auto &entity : entities) {
const auto push = [&](const QString &tag) {
result.push_back({ entity.offset(), entity.length(), tag });
};
switch (entity.type()) {
case EntityInTextMentionName: {
auto match = QRegularExpression("^(\\d+\\.\\d+)$").match(entity.data());
if (match.hasMatch()) {
result.push_back({ entity.offset(), entity.length(), qstr("mention://user.") + entity.data() });
push(qstr("mention://user.") + entity.data());
}
} break;
case EntityInTextBold: push(Ui::InputField::kTagBold); break;
case EntityInTextItalic: push(Ui::InputField::kTagItalic); break;
case EntityInTextCode: push(Ui::InputField::kTagCode); break;
case EntityInTextPre: push(Ui::InputField::kTagPre); break;
}
}
return result;
@@ -95,8 +121,8 @@ std::unique_ptr<QMimeData> MimeDataFromTextWithEntities(
tag.id = ConvertTagToMimeTag(tag.id);
}
result->setData(
Ui::FlatTextarea::tagsMimeType(),
Ui::FlatTextarea::serializeTagsList(tags));
TextUtilities::TagsMimeType(),
TextUtilities::SerializeTags(tags));
}
return result;
}
@@ -109,59 +135,331 @@ void SetClipboardWithEntities(
}
}
MessageField::MessageField(QWidget *parent, not_null<Window::Controller*> controller, const style::FlatTextarea &st, base::lambda<QString()> placeholderFactory, const QString &val) : Ui::FlatTextarea(parent, st, std::move(placeholderFactory), val)
, _controller(controller) {
setMinHeight(st::historySendSize.height() - 2 * st::historySendPadding);
setMaxHeight(st::historyComposeFieldMaxHeight);
void InitMessageField(not_null<Ui::InputField*> field) {
field->setMinHeight(st::historySendSize.height() - 2 * st::historySendPadding);
field->setMaxHeight(st::historyComposeFieldMaxHeight);
setTagMimeProcessor(std::make_unique<FieldTagMimeProcessor>());
field->setTagMimeProcessor(std::make_unique<FieldTagMimeProcessor>());
field->document()->setDocumentMargin(4.);
field->setAdditionalMargin(convertScale(4) - 4);
field->customTab(true);
field->setInstantReplaces(Ui::InstantReplaces::Default());
field->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
field->setMarkdownReplacesEnabled(Global::ReplaceEmojiValue());
}
bool MessageField::hasSendText() const {
auto &text = getTextWithTags().text;
for (auto *ch = text.constData(), *e = ch + text.size(); ch != e; ++ch) {
auto code = ch->unicode();
if (code != ' ' && code != '\n' && code != '\r' && !chReplacedBySpace(code)) {
bool HasSendText(not_null<const Ui::InputField*> field) {
const auto &text = field->getTextWithTags().text;
for (const auto ch : text) {
const auto code = ch.unicode();
if (code != ' '
&& code != '\n'
&& code != '\r'
&& !chReplacedBySpace(code)) {
return true;
}
}
return false;
}
void MessageField::onEmojiInsert(EmojiPtr emoji) {
if (isHidden()) return;
insertEmoji(emoji, textCursor());
}
InlineBotQuery ParseInlineBotQuery(not_null<const Ui::InputField*> field) {
auto result = InlineBotQuery();
void MessageField::dropEvent(QDropEvent *e) {
FlatTextarea::dropEvent(e);
if (e->isAccepted()) {
_controller->window()->activateWindow();
const auto &text = field->getTextWithTags().text;
const auto textLength = text.size();
auto inlineUsernameStart = 1;
auto inlineUsernameLength = 0;
if (textLength > 2 && text[0] == '@' && text[1].isLetter()) {
inlineUsernameLength = 1;
for (auto i = inlineUsernameStart + 1; i != textLength; ++i) {
const auto ch = text[i];
if (ch.isLetterOrNumber() || ch.unicode() == '_') {
++inlineUsernameLength;
continue;
} else if (!ch.isSpace()) {
inlineUsernameLength = 0;
}
break;
}
auto inlineUsernameEnd = inlineUsernameStart + inlineUsernameLength;
auto inlineUsernameEqualsText = (inlineUsernameEnd == textLength);
auto validInlineUsername = false;
if (inlineUsernameEqualsText) {
validInlineUsername = text.endsWith(qstr("bot"));
} else if (inlineUsernameEnd < textLength && inlineUsernameLength) {
validInlineUsername = text[inlineUsernameEnd].isSpace();
}
if (validInlineUsername) {
auto username = text.midRef(inlineUsernameStart, inlineUsernameLength);
if (username != result.username) {
result.username = username.toString();
if (const auto peer = App::peerByName(result.username)) {
if (const auto user = peer->asUser()) {
result.bot = peer->asUser();
} else {
result.bot = nullptr;
}
result.lookingUpBot = false;
} else {
result.bot = nullptr;
result.lookingUpBot = true;
}
}
if (result.lookingUpBot) {
result.query = QString();
return result;
} else if (result.bot && (!result.bot->botInfo
|| result.bot->botInfo->inlinePlaceholder.isEmpty())) {
result.bot = nullptr;
} else {
result.query = inlineUsernameEqualsText
? QString()
: text.mid(inlineUsernameEnd + 1);
return result;
}
} else {
inlineUsernameLength = 0;
}
}
if (inlineUsernameLength < 3) {
result.bot = nullptr;
result.username = QString();
}
result.query = QString();
return result;
}
bool MessageField::canInsertFromMimeData(const QMimeData *source) const {
if (source->hasUrls()) {
int32 files = 0;
for (int32 i = 0; i < source->urls().size(); ++i) {
if (source->urls().at(i).isLocalFile()) {
++files;
AutocompleteQuery ParseMentionHashtagBotCommandQuery(
not_null<const Ui::InputField*> field) {
auto result = AutocompleteQuery();
const auto cursor = field->textCursor();
const auto position = cursor.position();
if (cursor.anchor() != position) {
return result;
}
const auto document = field->document();
const auto block = document->findBlock(position);
for (auto item = block.begin(); !item.atEnd(); ++item) {
const auto fragment = item.fragment();
if (!fragment.isValid()) {
continue;
}
const auto fragmentPosition = fragment.position();
const auto fragmentEnd = fragmentPosition + fragment.length();
if (fragmentPosition >= position || fragmentEnd < position) {
continue;
}
const auto format = fragment.charFormat();
if (format.isImageFormat()) {
continue;
}
bool mentionInCommand = false;
const auto text = fragment.text();
for (auto i = position - fragmentPosition; i != 0; --i) {
if (text[i - 1] == '@') {
if ((position - fragmentPosition - i < 1 || text[i].isLetter()) && (i < 2 || !(text[i - 2].isLetterOrNumber() || text[i - 2] == '_'))) {
result.fromStart = (i == 1) && (fragmentPosition == 0);
result.query = text.mid(i - 1, position - fragmentPosition - i + 1);
} else if ((position - fragmentPosition - i < 1 || text[i].isLetter()) && i > 2 && (text[i - 2].isLetterOrNumber() || text[i - 2] == '_') && !mentionInCommand) {
mentionInCommand = true;
--i;
continue;
}
return result;
} else if (text[i - 1] == '#') {
if (i < 2 || !(text[i - 2].isLetterOrNumber() || text[i - 2] == '_')) {
result.fromStart = (i == 1) && (fragmentPosition == 0);
result.query = text.mid(i - 1, position - fragmentPosition - i + 1);
}
return result;
} else if (text[i - 1] == '/') {
if (i < 2) {
result.fromStart = (i == 1) && (fragmentPosition == 0);
result.query = text.mid(i - 1, position - fragmentPosition - i + 1);
}
return result;
}
if (position - fragmentPosition - i > 127 || (!mentionInCommand && (position - fragmentPosition - i > 63))) {
break;
}
if (!text[i - 1].isLetterOrNumber() && text[i - 1] != '_') {
break;
}
}
if (files > 1) return false; // multiple confirm with "compressed" checkbox
break;
}
if (source->hasImage()) return true;
return FlatTextarea::canInsertFromMimeData(source);
return result;
}
void MessageField::insertFromMimeData(const QMimeData *source) {
if (_insertFromMimeDataHook && _insertFromMimeDataHook(source)) {
QtConnectionOwner::QtConnectionOwner(QMetaObject::Connection connection)
: _data(connection) {
}
QtConnectionOwner::QtConnectionOwner(QtConnectionOwner &&other)
: _data(base::take(other._data)) {
}
QtConnectionOwner &QtConnectionOwner::operator=(QtConnectionOwner &&other) {
disconnect();
_data = base::take(other._data);
return *this;
}
void QtConnectionOwner::disconnect() {
QObject::disconnect(base::take(_data));
}
QtConnectionOwner::~QtConnectionOwner() {
disconnect();
}
MessageLinksParser::MessageLinksParser(not_null<Ui::InputField*> field)
: _field(field)
, _timer([=] { parse(); }) {
_connection = QObject::connect(_field, &Ui::InputField::changed, [=] {
const auto length = _field->getTextWithTags().text.size();
const auto timeout = (std::abs(length - _lastLength) > 2)
? 0
: kParseLinksTimeout;
if (!_timer.isActive() || timeout < _timer.remainingTime()) {
_timer.callOnce(timeout);
}
_lastLength = length;
});
_field->installEventFilter(this);
}
bool MessageLinksParser::eventFilter(QObject *object, QEvent *event) {
if (object == _field) {
if (event->type() == QEvent::KeyPress) {
const auto text = static_cast<QKeyEvent*>(event)->text();
if (!text.isEmpty() && text.size() < 3) {
const auto ch = text[0];
if (false
|| ch == '\n'
|| ch == '\r'
|| ch.isSpace()
|| ch == QChar::LineSeparator) {
_timer.callOnce(0);
}
}
} else if (event->type() == QEvent::Drop) {
_timer.callOnce(0);
}
}
return QObject::eventFilter(object, event);
}
const rpl::variable<QStringList> &MessageLinksParser::list() const {
return _list;
}
void MessageLinksParser::parse() {
const auto &text = _field->getTextWithTags().text;
if (text.isEmpty()) {
_list = QStringList();
return;
}
FlatTextarea::insertFromMimeData(source);
auto ranges = QVector<LinkRange>();
const auto len = text.size();
const QChar *start = text.unicode(), *end = start + text.size();
for (auto offset = 0, matchOffset = offset; offset < len;) {
auto m = TextUtilities::RegExpDomain().match(text, matchOffset);
if (!m.hasMatch()) break;
auto domainOffset = m.capturedStart();
auto protocol = m.captured(1).toLower();
auto topDomain = m.captured(3).toLower();
auto isProtocolValid = protocol.isEmpty() || TextUtilities::IsValidProtocol(protocol);
auto isTopDomainValid = !protocol.isEmpty() || TextUtilities::IsValidTopDomain(topDomain);
if (protocol.isEmpty() && domainOffset > offset + 1 && *(start + domainOffset - 1) == QChar('@')) {
auto forMailName = text.mid(offset, domainOffset - offset - 1);
auto mMailName = TextUtilities::RegExpMailNameAtEnd().match(forMailName);
if (mMailName.hasMatch()) {
offset = matchOffset = m.capturedEnd();
continue;
}
}
if (!isProtocolValid || !isTopDomainValid) {
offset = matchOffset = m.capturedEnd();
continue;
}
QStack<const QChar*> parenth;
const QChar *domainEnd = start + m.capturedEnd(), *p = domainEnd;
for (; p < end; ++p) {
QChar ch(*p);
if (chIsLinkEnd(ch)) break; // link finished
if (chIsAlmostLinkEnd(ch)) {
const QChar *endTest = p + 1;
while (endTest < end && chIsAlmostLinkEnd(*endTest)) {
++endTest;
}
if (endTest >= end || chIsLinkEnd(*endTest)) {
break; // link finished at p
}
p = endTest;
ch = *p;
}
if (ch == '(' || ch == '[' || ch == '{' || ch == '<') {
parenth.push(p);
} else if (ch == ')' || ch == ']' || ch == '}' || ch == '>') {
if (parenth.isEmpty()) break;
const QChar *q = parenth.pop(), open(*q);
if ((ch == ')' && open != '(') || (ch == ']' && open != '[') || (ch == '}' && open != '{') || (ch == '>' && open != '<')) {
p = q;
break;
}
}
}
if (p > domainEnd) { // check, that domain ended
if (domainEnd->unicode() != '/' && domainEnd->unicode() != '?') {
matchOffset = domainEnd - start;
continue;
}
}
ranges.push_back({ domainOffset, static_cast<int>(p - start - domainOffset) });
offset = matchOffset = p - start;
}
apply(text, ranges);
}
void MessageField::focusInEvent(QFocusEvent *e) {
FlatTextarea::focusInEvent(e);
emit focused();
void MessageLinksParser::apply(
const QString &text,
const QVector<LinkRange> &ranges) {
const auto count = int(ranges.size());
const auto current = _list.current();
const auto changed = [&] {
if (current.size() != count) {
return true;
}
for (auto i = 0; i != count; ++i) {
const auto &range = ranges[i];
if (text.midRef(range.start, range.length) != current[i]) {
return true;
}
}
return false;
}();
if (!changed) {
return;
}
auto parsed = QStringList();
parsed.reserve(count);
for (const auto &range : ranges) {
parsed.push_back(text.mid(range.start, range.length));
}
_list = std::move(parsed);
}

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "ui/widgets/input_fields.h"
#include "base/timer.h"
class HistoryWidget;
namespace Window {
@@ -25,32 +26,66 @@ void SetClipboardWithEntities(
const TextWithEntities &forClipboard,
QClipboard::Mode mode = QClipboard::Clipboard);
class MessageField final : public Ui::FlatTextarea {
Q_OBJECT
void InitMessageField(not_null<Ui::InputField*> field);
bool HasSendText(not_null<const Ui::InputField*> field);
struct InlineBotQuery {
QString query;
QString username;
UserData *bot = nullptr;
bool lookingUpBot = false;
};
InlineBotQuery ParseInlineBotQuery(not_null<const Ui::InputField*> field);
struct AutocompleteQuery {
QString query;
bool fromStart = false;
};
AutocompleteQuery ParseMentionHashtagBotCommandQuery(
not_null<const Ui::InputField*> field);
class QtConnectionOwner {
public:
MessageField(QWidget *parent, not_null<Window::Controller*> controller, const style::FlatTextarea &st, base::lambda<QString()> placeholderFactory = base::lambda<QString()>(), const QString &val = QString());
bool hasSendText() const;
void setInsertFromMimeDataHook(base::lambda<bool(const QMimeData *data)> hook) {
_insertFromMimeDataHook = std::move(hook);
}
public slots:
void onEmojiInsert(EmojiPtr emoji);
signals:
void focused();
protected:
void focusInEvent(QFocusEvent *e) override;
void dropEvent(QDropEvent *e) override;
bool canInsertFromMimeData(const QMimeData *source) const override;
void insertFromMimeData(const QMimeData *source) override;
QtConnectionOwner(QMetaObject::Connection connection = {});
QtConnectionOwner(QtConnectionOwner &&other);
QtConnectionOwner &operator=(QtConnectionOwner &&other);
~QtConnectionOwner();
private:
not_null<Window::Controller*> _controller;
base::lambda<bool(const QMimeData *data)> _insertFromMimeDataHook;
void disconnect();
QMetaObject::Connection _data;
};
class MessageLinksParser : private QObject {
public:
MessageLinksParser(not_null<Ui::InputField*> field);
const rpl::variable<QStringList> &list() const;
protected:
bool eventFilter(QObject *object, QEvent *event) override;
private:
struct LinkRange {
int start;
int length;
};
friend inline bool operator==(const LinkRange &a, const LinkRange &b) {
return (a.start == b.start) && (a.length == b.length);
}
friend inline bool operator!=(const LinkRange &a, const LinkRange &b) {
return !(a == b);
}
void parse();
void apply(const QString &text, const QVector<LinkRange> &ranges);
not_null<Ui::InputField*> _field;
rpl::variable<QStringList> _list;
int _lastLength = 0;
base::Timer _timer;
QtConnectionOwner _connection;
};

View File

@@ -40,7 +40,7 @@ Replace Replaces[] = {
// { { 0xD83DDE01U }, ":grin:" },
// { { 0xD83DDE02U }, ":joy:" },
{ { 0xD83DDE1AU }, ":-*" },
{ { 0xD83DDE06U }, "xD" },
// { { 0xD83DDE06U }, "xD" }, // Conflicts with typing xDDD...
// { { 0xD83DDC4DU }, ":like:" },
// { { 0xD83DDC4EU }, ":dislike:" },
// { { 0x261DU }, ":up:" },
@@ -57,7 +57,7 @@ Replace Replaces[] = {
{ { 0xD83DDE22U }, ":'(" },
{ { 0xD83DDE2DU }, ":_(" },
{ { 0xD83DDE29U }, ":((" },
{ { 0xD83DDE28U }, ":o" },
// { { 0xD83DDE28U }, ":o" }, // Conflicts with typing :ok...
{ { 0xD83DDE10U }, ":|" },
{ { 0xD83DDE0CU }, "3-)" },
{ { 0xD83DDE20U }, ">(" },

View File

@@ -335,6 +335,10 @@ EmojiPtr FindReplace(const QChar *start, const QChar *end, int *outLength) {\n\
return index ? &Items[index - 1] : nullptr;\n\
}\n\
\n\
const std::vector<std::pair<QString, int>> GetReplacementPairs() {\n\
return ReplacementPairs;\n\
}\n\
\n\
EmojiPtr Find(const QChar *start, const QChar *end, int *outLength) {\n\
auto index = FindIndex(start, end, outLength);\n\
return index ? &Items[index - 1] : nullptr;\n\
@@ -389,6 +393,7 @@ inline bool IsReplaceEdge(const QChar *ch) {\n\
// return false;\n\
}\n\
\n\
const std::vector<std::pair<QString, int>> GetReplacementPairs();\n\
EmojiPtr FindReplace(const QChar *ch, const QChar *end, int *outLength = nullptr);\n\
\n";
header->popNamespace().stream() << "\
@@ -591,6 +596,14 @@ EmojiPack GetSection(Section section) {\n\
bool Generator::writeFindReplace() {
source_->stream() << "\
\n\
const std::vector<std::pair<QString, int>> ReplacementPairs = {\n";
for (const auto &[what, index] : data_.replaces) {
source_->stream() << "\
{ qsl(\"" << what << "\"), " << index << " },\n";
}
source_->stream() << "\
};\n\
\n\
int FindReplaceIndex(const QChar *start, const QChar *end, int *outLength) {\n\
auto ch = start;\n\
\n";
@@ -783,6 +796,7 @@ struct Replacement {\n\
constexpr auto kReplacementMaxLength = " << maxLength << ";\n\
\n\
void InitReplacements();\n\
const std::vector<Replacement> &GetAllReplacements();\n\
const std::vector<const Replacement*> *GetReplacements(utf16char first);\n\
utf16string GetReplacementEmoji(utf16string replacement);\n\
\n";
@@ -923,6 +937,10 @@ const std::vector<const Replacement*> *GetReplacements(utf16char first) {\n\
return (it == ReplacementsMap.cend()) ? nullptr : &it->second;\n\
}\n\
\n\
const std::vector<Replacement> &GetAllReplacements() {\n\
return Replacements;\n\
}\n\
\n\
utf16string GetReplacementEmoji(utf16string replacement) {\n\
auto code = internal::countChecksum(replacement.data(), replacement.size() * sizeof(utf16char));\n\
auto it = ReplacementsHash.find(code);\n\

View File

@@ -315,6 +315,13 @@ Replaces PrepareReplaces(const QString &filename) {
auto name = getString("name");
auto replacement = getString("alpha_code");
auto aliases = getString("aliases").split('|');
const auto Exceptions = { ":shrug:" };
for (const auto &exception : Exceptions) {
const auto index = aliases.indexOf(exception);
if (index >= 0) {
aliases.removeAt(index);
}
}
if (aliases.size() == 1 && aliases[0].isEmpty()) {
aliases.clear();
}

View File

@@ -429,7 +429,7 @@ for line in lines:
funcsList.append(restype);
funcsDict[restype] = [];
# TypesDict[restype] = resType;
funcsDict[restype].append([name, typeid, prmsList, prms, hasFlags, conditionsList, conditions, trivialConditions]);
funcsDict[restype].append([name, typeid, prmsList, prms, hasFlags, conditionsList, conditions, trivialConditions, isTemplate]);
else:
if (isTemplate != ''):
print('Template types not allowed: "' + resType + '" in line: ' + line);
@@ -438,7 +438,7 @@ for line in lines:
typesList.append(restype);
typesDict[restype] = [];
TypesDict[restype] = resType;
typesDict[restype].append([name, typeid, prmsList, prms, hasFlags, conditionsList, conditions, trivialConditions]);
typesDict[restype].append([name, typeid, prmsList, prms, hasFlags, conditionsList, conditions, trivialConditions, isTemplate]);
consts = consts + 1;
@@ -455,10 +455,15 @@ def addTextSerialize(lst, dct, dataLetter):
conditionsList = data[5];
conditions = data[6];
trivialConditions = data[7];
isTemplate = data[8];
templateArgument = ''
if (isTemplate != ''):
templateArgument = '<mtpRequest>'
result += 'void Serialize_' + name + '(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, uint32 iflag) {\n';
if (len(conditions)):
result += '\tauto flag = MTP' + dataLetter + name + '::Flags::from_raw(iflag);\n\n';
result += '\tauto flag = MTP' + dataLetter + name + templateArgument + '::Flags::from_raw(iflag);\n\n';
if (len(prms)):
result += '\tif (stage) {\n';
result += '\t\tto.add(",\\n").addSpaces(lev);\n';
@@ -474,12 +479,12 @@ def addTextSerialize(lst, dct, dataLetter):
if (k == hasFlags):
result += 'if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; ';
if (k in trivialConditions):
result += 'if (flag & MTP' + dataLetter + name + '::Flag::f_' + k + ') { ';
result += 'if (flag & MTP' + dataLetter + name + templateArgument + '::Flag::f_' + k + ') { ';
result += 'to.add("YES [ BY BIT ' + conditions[k] + ' IN FIELD ' + hasFlags + ' ]"); ';
result += '} else { to.add("[ SKIPPED BY BIT ' + conditions[k] + ' IN FIELD ' + hasFlags + ' ]"); } ';
else:
if (k in conditions):
result += 'if (flag & MTP' + dataLetter + name + '::Flag::f_' + k + ') { ';
result += 'if (flag & MTP' + dataLetter + name + templateArgument + '::Flag::f_' + k + ') { ';
result += 'types.push_back(';
vtypeget = re.match(r'^[Vv]ector<MTP([A-Za-z0-9\._]+)>', v);
if (vtypeget):

View File

@@ -17,8 +17,6 @@ constexpr str_const AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"; // used in
constexpr str_const AppFile = "Telegram";
enum {
MTPShortBufferSize = 65535, // of ints, 256 kb
MTPPacketSizeMax = 67108864, // 64 mb
MTPIdsBufferSize = 400, // received msgIds and wereAcked msgIds count stored
MTPCheckResendTimeout = 10000, // how much time passed from send till we resend request or check it's state, in ms
MTPCheckResendWaiting = 1000, // how much time to wait for some more requests, when resending request or checking it's state, in ms

View File

@@ -61,6 +61,32 @@ std::map<int, const char*> AlphaLogs() {
"\xE2\x80\x94 Enable proxy for calls in Settings.\n"
"\xE2\x80\x94 Bug fixes and other minor improvements."
},
{
1002020,
"\xE2\x80\x94 Emoji and text replacements are done "
"while you type the message.\n"
"\xE2\x80\x94 Revert emoji and text replacements "
"by pressing backspace.\n"
"\xE2\x80\x94 Disable emoji replacements or suggestions "
"in Settings.\n"
"\xE2\x80\x94 Some critical bug fixes."
},
{
1002022,
"\xE2\x80\x94 Use markdown in media captions "
"(**bold**, __italic__, `tag` and ```code```).\n"
"\xE2\x80\x94 Use emoji replacement in media captions, "
"group and channel titles and descriptions (:like: etc.)\n"
"\xE2\x80\x94 Markdown replacement now happens immediately "
"after typing (instead of after sending) and can be "
"rolled back using Backspace or Ctrl/Cmd + Z. "
"Replacement no longer happens when pasting text."
}
};
}

View File

@@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "core/utils.h"
#include "base/qthelp_url.h"
#include "application.h"
#include "platform/platform_specific.h"
#include <openssl/crypto.h>
#include <openssl/sha.h>
#include <openssl/err.h>
@@ -14,16 +18,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <openssl/engine.h>
#include <openssl/conf.h>
#include <openssl/ssl.h>
#include <openssl/rand.h>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}
#include "application.h"
#include "platform/platform_specific.h"
uint64 _SharedMemoryLocation[4] = { 0x00, 0x01, 0x02, 0x03 };
} // extern "C"
#ifdef Q_OS_WIN
#elif defined Q_OS_MAC
@@ -32,7 +32,7 @@ uint64 _SharedMemoryLocation[4] = { 0x00, 0x01, 0x02, 0x03 };
#include <time.h>
#endif
#include <openssl/rand.h>
uint64 _SharedMemoryLocation[4] = { 0x00, 0x01, 0x02, 0x03 };
// Base types compile-time check
static_assert(sizeof(char) == 1, "Basic types size check failed");
@@ -249,6 +249,14 @@ bool ProxyData::supportsCalls() const {
return (type == Type::Socks5);
}
bool ProxyData::tryCustomResolve() const {
return (type == Type::Socks5 || type == Type::Mtproto)
&& !qthelp::is_ipv6(host)
&& !QRegularExpression(
qsl("^\\d+\\.\\d+\\.\\d+\\.\\d+$")
).match(host).hasMatch();
}
ProxyData::operator bool() const {
return valid();
}
@@ -272,9 +280,25 @@ bool ProxyData::ValidSecret(const QString &secret) {
return QRegularExpression("^[a-fA-F0-9]{32}$").match(secret).hasMatch();
}
ProxyData ToDirectIpProxy(const ProxyData &proxy, int ipIndex) {
if (!proxy.tryCustomResolve()
|| ipIndex < 0
|| ipIndex >= proxy.resolvedIPs.size()) {
return proxy;
}
return {
proxy.type,
proxy.resolvedIPs[ipIndex],
proxy.port,
proxy.user,
proxy.password
};
}
QNetworkProxy ToNetworkProxy(const ProxyData &proxy) {
if (proxy.type == ProxyData::Type::None
|| proxy.type == ProxyData::Type::Mtproto) {
if (proxy.type == ProxyData::Type::None) {
return QNetworkProxy::DefaultProxy;
} else if (proxy.type == ProxyData::Type::Mtproto) {
return QNetworkProxy::NoProxy;
}
return QNetworkProxy(

View File

@@ -433,8 +433,12 @@ struct ProxyData {
uint32 port = 0;
QString user, password;
std::vector<QString> resolvedIPs;
TimeMs resolvedExpireAt = 0;
bool valid() const;
bool supportsCalls() const;
bool tryCustomResolve() const;
explicit operator bool() const;
bool operator==(const ProxyData &other) const;
bool operator!=(const ProxyData &other) const;
@@ -443,6 +447,7 @@ struct ProxyData {
};
ProxyData ToDirectIpProxy(const ProxyData &proxy, int ipIndex = 0);
QNetworkProxy ToNetworkProxy(const ProxyData &proxy);
enum DBIScale {

View File

@@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#define BETA_VERSION_MACRO (0ULL)
constexpr int AppVersion = 1002019;
constexpr str_const AppVersionStr = "1.2.19";
constexpr int AppVersion = 1002022;
constexpr str_const AppVersionStr = "1.2.22";
constexpr bool AppAlphaVersion = true;
constexpr uint64 AppBetaVersion = BETA_VERSION_MACRO;

View File

@@ -33,7 +33,7 @@ Draft::Draft(
}
Draft::Draft(
not_null<const Ui::FlatTextarea*> field,
not_null<const Ui::InputField*> field,
MsgId msgId,
bool previewCancelled,
mtpRequestId saveRequestId)
@@ -45,8 +45,13 @@ Draft::Draft(
void applyPeerCloudDraft(PeerId peerId, const MTPDdraftMessage &draft) {
auto history = App::history(peerId);
auto text = TextWithEntities { qs(draft.vmessage), draft.has_entities() ? TextUtilities::EntitiesFromMTP(draft.ventities.v) : EntitiesInText() };
auto textWithTags = TextWithTags { TextUtilities::ApplyEntities(text), ConvertEntitiesToTextTags(text.entities) };
auto textWithTags = TextWithTags {
qs(draft.vmessage),
ConvertEntitiesToTextTags(
draft.has_entities()
? TextUtilities::EntitiesFromMTP(draft.ventities.v)
: EntitiesInText())
};
auto replyTo = draft.has_reply_to_msg_id() ? draft.vreply_to_msg_id.v : MsgId(0);
auto cloudDraft = std::make_unique<Draft>(textWithTags, replyTo, MessageCursor(QFIXED_MAX, QFIXED_MAX, QFIXED_MAX), draft.is_no_webpage());
cloudDraft->date = draft.vdate.v;

View File

@@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
namespace Ui {
class FlatTextarea;
class InputField;
} // namespace Ui
namespace Data {
@@ -25,7 +25,7 @@ struct Draft {
bool previewCancelled,
mtpRequestId saveRequestId = 0);
Draft(
not_null<const Ui::FlatTextarea*> field,
not_null<const Ui::InputField*> field,
MsgId msgId,
bool previewCancelled,
mtpRequestId saveRequestId = 0);

View File

@@ -443,6 +443,10 @@ bool Feed::toImportant() const {
return false; // TODO feeds workmode
}
bool Feed::useProxyPromotion() const {
return false;
}
bool Feed::shouldBeInChatList() const {
return _channels.size() > 1;
}

View File

@@ -59,6 +59,7 @@ public:
int unreadCount() const;
bool unreadCountKnown() const;
bool useProxyPromotion() const override;
bool toImportant() const override;
bool shouldBeInChatList() const override;
int chatListUnreadCount() const override;

View File

@@ -83,10 +83,14 @@ void Session::setupChannelLeavingViewer() {
return update.peer->asChannel();
}) | rpl::filter([](ChannelData *channel) {
return (channel != nullptr)
&& !(channel->amIn())
&& (channel->feed() != nullptr);
&& !(channel->amIn());
}) | rpl::start_with_next([=](not_null<ChannelData*> channel) {
channel->clearFeed();
if (const auto history = App::historyLoaded(channel->id)) {
history->removeJoinedMessage();
history->updateChatListExistence();
history->updateChatListSortPosition();
}
}, _lifetime);
}
@@ -1592,4 +1596,32 @@ MessageIdsList Session::takeMimeForwardIds() {
return std::move(_mimeForwardIds);
}
void Session::setProxyPromoted(PeerData *promoted) {
if (_proxyPromoted != promoted) {
if (const auto history = App::historyLoaded(_proxyPromoted)) {
history->cacheProxyPromoted(false);
}
const auto old = std::exchange(_proxyPromoted, promoted);
if (_proxyPromoted) {
const auto history = App::history(_proxyPromoted);
history->cacheProxyPromoted(true);
if (!history->lastMessageKnown()) {
_session->api().requestDialogEntry(history);
}
Notify::peerUpdatedDelayed(
_proxyPromoted,
Notify::PeerUpdate::Flag::ChannelPromotedChanged);
}
if (old) {
Notify::peerUpdatedDelayed(
old,
Notify::PeerUpdate::Flag::ChannelPromotedChanged);
}
}
}
PeerData *Session::proxyPromoted() const {
return _proxyPromoted;
}
} // namespace Data

View File

@@ -363,6 +363,9 @@ public:
void setMimeForwardIds(MessageIdsList &&list);
MessageIdsList takeMimeForwardIds();
void setProxyPromoted(PeerData *promoted);
PeerData *proxyPromoted() const;
Groups &groups() {
return _groups;
}
@@ -543,6 +546,8 @@ private:
not_null<const HistoryItem*>,
std::vector<not_null<ViewElement*>>> _views;
PeerData *_proxyPromoted = nullptr;
MessageIdsList _mimeForwardIds;
rpl::lifetime _lifetime;

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_types.h"
#include "data/data_document.h"
#include "ui/widgets/input_fields.h"
void AudioMsgId::setTypeFromAudio() {
if (_audio->isVoiceMessage() || _audio->isVideoMessage()) {
@@ -21,24 +22,20 @@ void AudioMsgId::setTypeFromAudio() {
}
}
void MessageCursor::fillFrom(const QTextEdit *edit) {
QTextCursor c = edit->textCursor();
position = c.position();
anchor = c.anchor();
QScrollBar *s = edit->verticalScrollBar();
scroll = (s && (s->value() != s->maximum()))
? s->value()
: QFIXED_MAX;
void MessageCursor::fillFrom(not_null<const Ui::InputField*> field) {
const auto cursor = field->textCursor();
position = cursor.position();
anchor = cursor.anchor();
const auto top = field->scrollTop().current();
scroll = (top != field->scrollTopMax()) ? top : QFIXED_MAX;
}
void MessageCursor::applyTo(QTextEdit *edit) {
auto cursor = edit->textCursor();
void MessageCursor::applyTo(not_null<Ui::InputField*> field) {
auto cursor = field->textCursor();
cursor.setPosition(anchor, QTextCursor::MoveAnchor);
cursor.setPosition(position, QTextCursor::KeepAnchor);
edit->setTextCursor(cursor);
if (auto scrollbar = edit->verticalScrollBar()) {
scrollbar->setValue(scroll);
}
field->setTextCursor(cursor);
field->scrollTo(scroll);
}
HistoryItem *FileClickHandler::getActionItem() const {

View File

@@ -12,6 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class HistoryItem;
using HistoryItemsList = std::vector<not_null<HistoryItem*>>;
namespace Ui {
class InputField;
} // namespace Ui
namespace Data {
struct UploadState {
@@ -384,16 +388,16 @@ inline MsgId clientMsgId() {
struct MessageCursor {
MessageCursor() = default;
MessageCursor(int position, int anchor, int scroll)
: position(position)
, anchor(anchor)
, scroll(scroll) {
: position(position)
, anchor(anchor)
, scroll(scroll) {
}
MessageCursor(const QTextEdit *edit) {
fillFrom(edit);
MessageCursor(not_null<const Ui::InputField*> field) {
fillFrom(field);
}
void fillFrom(const QTextEdit *edit);
void applyTo(QTextEdit *edit);
void fillFrom(not_null<const Ui::InputField*> field);
void applyTo(not_null<Ui::InputField*> field);
int position = 0;
int anchor = 0;

View File

@@ -26,6 +26,10 @@ uint64 DialogPosFromDate(const QDateTime &date) {
return (uint64(date.toTime_t()) << 32) | (++DialogsPosToTopShift);
}
uint64 ProxyPromotedDialogPos() {
return 0xFFFFFFFFFFFF0001ULL;
}
uint64 PinnedDialogPos(int pinnedIndex) {
return 0xFFFFFFFF00000000ULL + pinnedIndex;
}
@@ -49,12 +53,25 @@ void Entry::cachePinnedIndex(int index) {
}
}
void Entry::cacheProxyPromoted(bool promoted) {
if (_isProxyPromoted != promoted) {
_isProxyPromoted = promoted;
updateChatListSortPosition();
updateChatListEntry();
if (!_isProxyPromoted) {
updateChatListExistence();
}
}
}
bool Entry::needUpdateInChatList() const {
return inChatList(Dialogs::Mode::All) || shouldBeInChatList();
}
void Entry::updateChatListSortPosition() {
_sortKeyInChatList = isPinnedDialog()
_sortKeyInChatList = useProxyPromotion()
? ProxyPromotedDialogPos()
: isPinnedDialog()
? PinnedDialogPos(_pinnedIndex)
: DialogPosFromDate(adjustChatListDate());
if (needUpdateInChatList()) {

View File

@@ -56,6 +56,11 @@ public:
return _pinnedIndex > 0;
}
void cachePinnedIndex(int index);
bool isProxyPromoted() const {
return _isProxyPromoted;
}
virtual bool useProxyPromotion() const = 0;
void cacheProxyPromoted(bool promoted);
uint64 sortKeyInChatList() const {
return _sortKeyInChatList;
}
@@ -111,6 +116,7 @@ private:
RowsByLetter _chatListLinks[2];
uint64 _sortKeyInChatList = 0;
int _pinnedIndex = 0;
bool _isProxyPromoted = false;
QDateTime _lastMessageDate;
};

View File

@@ -165,6 +165,22 @@ int DialogsInner::dialogsOffset() const {
return _dialogsImportant ? st::dialogsImportantBarHeight : 0;
}
int DialogsInner::proxyPromotedCount() const {
auto result = 0;
for_const (auto row, *shownDialogs()) {
if (row->entry()->useProxyPromotion()) {
++result;
} else {
break;
}
}
return result;
}
int DialogsInner::pinnedOffset() const {
return dialogsOffset() + proxyPromotedCount() * st::dialogsRowHeight;
}
int DialogsInner::filteredOffset() const {
return _hashtagResults.size() * st::mentionHeight;
}
@@ -231,13 +247,39 @@ void DialogsInner::paintRegion(Painter &p, const QRegion &region, bool paintingO
dialogsClip = dialogsClip.marginsAdded(QMargins(0, st::dialogsRowHeight, 0, st::dialogsRowHeight));
}
const auto promoted = proxyPromotedCount();
const auto paintDialog = [&](not_null<Dialogs::Row*> row) {
const auto pinned = row->pos() - promoted;
const auto count = _pinnedRows.size();
const auto xadd = 0;
const auto yadd = base::in_range(pinned, 0, count)
? qRound(_pinnedRows[pinned].yadd.current())
: 0;
if (xadd || yadd) {
p.translate(xadd, yadd);
}
const auto isActive = (row->key() == active);
const auto isSelected = (row->key() == selected);
Dialogs::Layout::RowPainter::paint(
p,
row,
fullWidth,
isActive,
isSelected,
paintingOther,
ms);
if (xadd || yadd) {
p.translate(-xadd, -yadd);
}
};
auto i = list.cfind(dialogsClip.top(), st::dialogsRowHeight);
if (i != list.cend()) {
auto lastPaintedPos = (*i)->pos();
// If we're reordering pinned chats we need to fill this area background first.
if (reorderingPinned) {
p.fillRect(0, 0, fullWidth, st::dialogsRowHeight * _pinnedRows.size(), st::dialogsBg);
p.fillRect(0, promoted * st::dialogsRowHeight, fullWidth, st::dialogsRowHeight * _pinnedRows.size(), st::dialogsBg);
}
p.translate(0, lastPaintedPos * st::dialogsRowHeight);
@@ -248,15 +290,9 @@ void DialogsInner::paintRegion(Painter &p, const QRegion &region, bool paintingO
}
// Skip currently dragged chat to paint it above others after.
if (lastPaintedPos != _aboveIndex) {
paintDialog(
p,
row,
fullWidth,
active,
selected,
paintingOther,
ms);
if (lastPaintedPos != promoted + _aboveIndex
|| _aboveIndex < 0) {
paintDialog(row);
}
p.translate(0, st::dialogsRowHeight);
@@ -265,11 +301,11 @@ void DialogsInner::paintRegion(Painter &p, const QRegion &region, bool paintingO
// Paint the dragged chat above all others.
if (_aboveIndex >= 0) {
auto i = list.cfind(_aboveIndex, 1);
auto i = list.cfind(promoted + _aboveIndex, 1);
auto pos = (i == list.cend()) ? -1 : (*i)->pos();
if (pos == _aboveIndex) {
if (pos == promoted + _aboveIndex) {
p.translate(0, (pos - lastPaintedPos) * st::dialogsRowHeight);
paintDialog(p, *i, fullWidth, active, selected, paintingOther, ms);
paintDialog(*i);
p.translate(0, (lastPaintedPos - pos) * st::dialogsRowHeight);
}
}
@@ -459,33 +495,6 @@ void DialogsInner::paintRegion(Painter &p, const QRegion &region, bool paintingO
}
}
void DialogsInner::paintDialog(
Painter &p,
not_null<Dialogs::Row*> row,
int fullWidth,
Dialogs::Key active,
Dialogs::Key selected,
bool onlyBackground,
TimeMs ms) {
auto pos = row->pos();
auto xadd = 0, yadd = 0;
if (pos < _pinnedRows.size()) {
yadd = qRound(_pinnedRows[pos].yadd.current());
}
if (xadd || yadd) p.translate(xadd, yadd);
const auto isActive = (row->key() == active);
const auto isSelected = (row->key() == selected);
Dialogs::Layout::RowPainter::paint(
p,
row,
fullWidth,
isActive,
isSelected,
onlyBackground,
ms);
if (xadd || yadd) p.translate(-xadd, -yadd);
}
void DialogsInner::paintPeerSearchResult(
Painter &p,
not_null<const PeerSearchResult*> result,
@@ -846,7 +855,9 @@ void DialogsInner::checkReorderPinnedStart(QPoint localPosition) {
int DialogsInner::shownPinnedCount() const {
auto result = 0;
for_const (auto row, *shownDialogs()) {
if (!row->entry()->isPinnedDialog()) {
if (row->entry()->useProxyPromotion()) {
continue;
} else if (!row->entry()->isPinnedDialog()) {
break;
}
++result;
@@ -860,7 +871,9 @@ int DialogsInner::countPinnedIndex(Dialogs::Row *ofRow) {
}
auto result = 0;
for_const (auto row, *shownDialogs()) {
if (!row->entry()->isPinnedDialog()) {
if (row->entry()->useProxyPromotion()) {
continue;
} else if (!row->entry()->isPinnedDialog()) {
break;
} else if (row == ofRow) {
return result;
@@ -1013,7 +1026,7 @@ void DialogsInner::step_pinnedShifting(TimeMs ms, bool timer) {
if (updateMax < _draggingIndex) updateMax = _draggingIndex;
}
if (updateMin >= 0) {
auto top = _dialogsImportant ? st::dialogsImportantBarHeight : 0;
auto top = pinnedOffset();
auto updateFrom = top + st::dialogsRowHeight * (updateMin - 1);
auto updateHeight = st::dialogsRowHeight * (updateMax - updateMin + 3);
if (base::in_range(_aboveIndex, 0, _pinnedRows.size())) {
@@ -1770,7 +1783,7 @@ void DialogsInner::applyDialog(const MTPDdialog &dialog) {
const auto history = App::history(peerId);
history->applyDialog(dialog);
if (!history->isPinnedDialog()) {
if (!history->useProxyPromotion() && !history->isPinnedDialog()) {
const auto date = history->chatsListDate();
if (!date.isNull()) {
addSavedPeersAfter(date);
@@ -1793,7 +1806,7 @@ void DialogsInner::applyDialog(const MTPDdialog &dialog) {
// const auto feed = Auth().data().feed(feedId);
// feed->applyDialog(dialog);
//
// if (!feed->isPinnedDialog()) {
// if (!feed->useProxyPromotion() && !feed->isPinnedDialog()) {
// const auto date = feed->chatsListDate();
// if (!date.isNull()) {
// addSavedPeersAfter(date);

View File

@@ -213,19 +213,13 @@ private:
UpdateRowSections sections = UpdateRowSection::All);
int dialogsOffset() const;
int proxyPromotedCount() const;
int pinnedOffset() const;
int filteredOffset() const;
int peerSearchOffset() const;
int searchedOffset() const;
int searchInChatSkip() const;
void paintDialog(
Painter &p,
not_null<Dialogs::Row*> row,
int fullWidth,
Dialogs::Key active,
Dialogs::Key selected,
bool onlyBackground,
TimeMs ms);
void paintPeerSearchResult(
Painter &p,
not_null<const PeerSearchResult*> result,

View File

@@ -25,6 +25,14 @@ namespace {
// Show all dates that are in the last 20 hours in time format.
constexpr int kRecentlyInSeconds = 20 * 3600;
void paintRowTopRight(Painter &p, const QString &text, QRect &rectForName, bool active, bool selected) {
const auto width = st::dialogsDateFont->width(text);
rectForName.setWidth(rectForName.width() - width - st::dialogsDateSkip);
p.setFont(st::dialogsDateFont);
p.setPen(active ? st::dialogsDateFgActive : (selected ? st::dialogsDateFgOver : st::dialogsDateFg));
p.drawText(rectForName.left() + rectForName.width() + st::dialogsDateSkip, rectForName.top() + st::msgNameFont->height - st::msgDateFont->descent, text);
}
void paintRowDate(Painter &p, QDateTime date, QRect &rectForName, bool active, bool selected) {
auto now = QDateTime::currentDateTime();
auto lastTime = date;
@@ -41,11 +49,7 @@ void paintRowDate(Painter &p, QDateTime date, QRect &rectForName, bool active, b
} else {
dt = lastDate.toString(qsl("d.MM.yy"));
}
int32 dtWidth = st::dialogsDateFont->width(dt);
rectForName.setWidth(rectForName.width() - dtWidth - st::dialogsDateSkip);
p.setFont(st::dialogsDateFont);
p.setPen(active ? st::dialogsDateFgActive : (selected ? st::dialogsDateFgOver : st::dialogsDateFg));
p.drawText(rectForName.left() + rectForName.width() + st::dialogsDateSkip, rectForName.top() + st::msgNameFont->height - st::msgDateFont->descent, dt);
paintRowTopRight(p, dt, rectForName, active, selected);
}
enum class Flag {
@@ -132,7 +136,11 @@ void paintRow(
namewidth,
st::msgNameFont->height);
if (from && !(flags & Flag::FeedSearchResult)) {
const auto promoted = chat.entry()->useProxyPromotion();
if (promoted) {
const auto text = lang(lng_proxy_sponsor);
paintRowTopRight(p, text, rectForName, active, selected);
} else if (from && !(flags & Flag::FeedSearchResult)) {
if (const auto chatTypeIcon = ChatTypeIcon(from, active, selected)) {
chatTypeIcon->paint(p, rectForName.topLeft(), fullWidth);
rectForName.setLeft(rectForName.left() + st::dialogsChatTypeSkip);
@@ -147,7 +155,9 @@ void paintRow(
+ st::msgNameFont->height
+ st::dialogsSkip;
if (draft) {
paintRowDate(p, date, rectForName, active, selected);
if (!promoted) {
paintRowDate(p, date, rectForName, active, selected);
}
auto availableWidth = namewidth;
if (entry->isPinnedDialog()) {
@@ -183,7 +193,9 @@ void paintRow(
// Empty history
}
} else if (!item->isEmpty()) {
paintRowDate(p, date, rectForName, active, selected);
if (!promoted) {
paintRowDate(p, date, rectForName, active, selected);
}
paintItemCallback(nameleft, namewidth);
} else if (entry->isPinnedDialog()) {

View File

@@ -553,7 +553,10 @@ struct Data {
QByteArray DownloadPathBookmark;
base::Observable<void> DownloadPathChanged;
bool ReplaceEmoji = true;
bool SuggestEmoji = true;
bool SuggestStickersByEmoji = true;
base::Observable<void> ReplaceEmojiChanged;
bool SoundNotify = true;
bool DesktopNotify = true;
bool RestoreSoundNotifyFromTray = false;
@@ -678,7 +681,10 @@ DefineVar(Global, QString, DownloadPath);
DefineVar(Global, QByteArray, DownloadPathBookmark);
DefineRefVar(Global, base::Observable<void>, DownloadPathChanged);
DefineVar(Global, bool, ReplaceEmoji);
DefineVar(Global, bool, SuggestEmoji);
DefineVar(Global, bool, SuggestStickersByEmoji);
DefineRefVar(Global, base::Observable<void>, ReplaceEmojiChanged);
DefineVar(Global, bool, SoundNotify);
DefineVar(Global, bool, DesktopNotify);
DefineVar(Global, bool, RestoreSoundNotifyFromTray);
@@ -705,4 +711,14 @@ DefineRefVar(Global, base::Variable<DBIWorkMode>, WorkMode);
DefineRefVar(Global, base::Observable<void>, UnreadCounterUpdate);
DefineRefVar(Global, base::Observable<void>, PeerChooseCancel);
rpl::producer<bool> ReplaceEmojiValue() {
return rpl::single(
Global::ReplaceEmoji()
) | rpl::then(base::ObservableViewer(
Global::RefReplaceEmojiChanged()
) | rpl::map([] {
return Global::ReplaceEmoji();
}));
}
} // namespace Global

View File

@@ -361,7 +361,10 @@ DeclareVar(QString, DownloadPath);
DeclareVar(QByteArray, DownloadPathBookmark);
DeclareRefVar(base::Observable<void>, DownloadPathChanged);
DeclareVar(bool, ReplaceEmoji);
DeclareVar(bool, SuggestEmoji);
DeclareVar(bool, SuggestStickersByEmoji);
DeclareRefVar(base::Observable<void>, ReplaceEmojiChanged);
DeclareVar(bool, SoundNotify);
DeclareVar(bool, DesktopNotify);
DeclareVar(bool, RestoreSoundNotifyFromTray);
@@ -388,6 +391,8 @@ DeclareRefVar(base::Variable<DBIWorkMode>, WorkMode);
DeclareRefVar(base::Observable<void>, UnreadCounterUpdate);
DeclareRefVar(base::Observable<void>, PeerChooseCancel);
rpl::producer<bool> ReplaceEmojiValue();
} // namespace Global
namespace Adaptive {

View File

@@ -2134,6 +2134,15 @@ void History::updateChatListExistence() {
}
}
bool History::useProxyPromotion() const {
if (!isProxyPromoted()) {
return false;
} else if (const auto channel = peer->asChannel()) {
return !isPinnedDialog() && !channel->amIn();
}
return false;
}
bool History::shouldBeInChatList() const {
if (peer->migrateTo()) {
return false;
@@ -2141,7 +2150,7 @@ bool History::shouldBeInChatList() const {
return true;
} else if (const auto channel = peer->asChannel()) {
if (!channel->amIn()) {
return false;
return isProxyPromoted();
} else if (const auto feed = channel->feed()) {
return !feed->needUpdateInChatList();
}
@@ -2275,7 +2284,8 @@ HistoryItem *History::lastSentMessage() const {
for (const auto &block : base::reversed(blocks)) {
for (const auto &message : base::reversed(block->messages)) {
const auto item = message->data();
if (IsServerMsgId(item->id) && item->out()) {
if (IsServerMsgId(item->id)
&& (item->out() || peer->isSelf())) {
return item;
}
}
@@ -2474,6 +2484,12 @@ void History::checkJoinedMessage(bool createUnread) {
}
}
void History::removeJoinedMessage() {
if (_joinedMessage) {
base::take(_joinedMessage)->destroy();
}
}
bool History::isEmpty() const {
return blocks.empty();
}

View File

@@ -123,6 +123,7 @@ public:
MsgRange rangeForDifferenceRequest() const;
HistoryService *insertJoinedMessage(bool unread);
void checkJoinedMessage(bool createUnread = false);
void removeJoinedMessage();
bool isEmpty() const;
bool isDisplayedEmpty() const;
@@ -318,6 +319,7 @@ public:
HistoryItemsList validateForwardDraft();
void setForwardDraft(MessageIdsList &&items);
bool useProxyPromotion() const override;
void updateChatListExistence() override;
bool shouldBeInChatList() const override;
bool toImportant() const override {

View File

@@ -131,19 +131,25 @@ historyViewsOutIcon: icon {{ "history_views", historyOutIconFg }};
historyViewsOutSelectedIcon: icon {{ "history_views", historyOutIconFgSelected }};
historyViewsInvertedIcon: icon {{ "history_views", historySendingInvertedIconFg }};
historyComposeField: FlatTextarea {
textColor: historyComposeAreaFg;
bgColor: historyComposeAreaBg;
align: align(left);
textMrg: margins(5px, 5px, 5px, 5px);
historyComposeField: InputField(defaultInputField) {
font: msgFont;
phColor: placeholderFg;
phFocusColor: placeholderFgActive;
phAlign: align(topleft);
phPos: point(2px, 0px);
phShift: 50px;
phDuration: 100;
textMargins: margins(0px, 0px, 0px, 0px);
textAlign: align(left);
textFg: historyComposeAreaFg;
textBg: historyComposeAreaBg;
heightMin: 36px;
heightMax: 72px;
placeholderFg: placeholderFg;
placeholderFgActive: placeholderFgActive;
placeholderFgError: placeholderFgActive;
placeholderMargins: margins(7px, 5px, 7px, 5px);
placeholderAlign: align(topleft);
placeholderScale: 0.;
placeholderFont: normalFont;
placeholderShift: -50px;
border: 0px;
borderActive: 0px;
duration: 100;
}
historyComposeFieldMaxHeight: 224px;
// historyMinHeight: 56px;
@@ -456,3 +462,9 @@ historyGroupWidthMin: minPhotoSize;
historyGroupSkip: 4px;
historyGroupRadialSize: 44px;
historyGroupRadialLine: 3px;
historyAboutProxy: FlatLabel(defaultFlatLabel) {
align: align(top);
textFg: windowSubTextFg;
}
historyAboutProxyPadding: margins(20px, 10px, 20px, 10px);

View File

@@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_controller.h"
#include "window/window_peer_menu.h"
#include "boxes/confirm_box.h"
#include "boxes/report_box.h"
#include "chat_helpers/message_field.h"
#include "chat_helpers/stickers.h"
#include "history/history_widget.h"
@@ -1564,6 +1565,11 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
deleteItem(itemId);
});
}
if (item->suggestReport()) {
_menu->addAction(lang(lng_context_report_msg), [=] {
reportItem(itemId);
});
}
}
if (IsServerMsgId(item->id) && !item->serviceMsg()) {
_menu->addAction(lang(lng_context_select_msg), [=] {
@@ -1596,6 +1602,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
&& item->canDelete()
&& (item->id > 0 || !item->serviceMsg());
const auto canForward = item && item->allowsForward();
const auto canReport = item && item->suggestReport();
const auto view = item ? item->mainView() : nullptr;
const auto msg = dynamic_cast<HistoryMessage*>(item);
@@ -1674,12 +1681,16 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
forwardAsGroup(itemId);
});
}
if (canDelete) {
_menu->addAction(lang((msg && msg->uploading()) ? lng_context_cancel_upload : lng_context_delete_msg), [=] {
deleteAsGroup(itemId);
});
}
if (canReport) {
_menu->addAction(lang(lng_context_report_msg), [=] {
reportAsGroup(itemId);
});
}
}
if (item->id > 0 && !item->serviceMsg()) {
_menu->addAction(lang(lng_context_select_msg), [=] {
@@ -2899,6 +2910,22 @@ void HistoryInner::deleteAsGroup(FullMsgId itemId) {
}
}
void HistoryInner::reportItem(FullMsgId itemId) {
Ui::show(Box<ReportBox>(_peer, MessageIdsList(1, itemId)));
}
void HistoryInner::reportAsGroup(FullMsgId itemId) {
if (const auto item = App::histItemById(itemId)) {
const auto group = Auth().data().groups().find(item);
if (!group) {
return reportItem(itemId);
}
Ui::show(Box<ReportBox>(
_peer,
Auth().data().itemsToIds(group->items)));
}
}
void HistoryInner::addSelectionRange(
not_null<SelectedItems*> toItems,
not_null<History*> history,

View File

@@ -38,6 +38,7 @@ class HistoryInner
: public Ui::RpWidget
, public Ui::AbstractTooltipShower
, private base::Subscriber {
// The Q_OBJECT meta info is used for qobject_cast to HistoryInner!
Q_OBJECT
public:
@@ -281,6 +282,8 @@ private:
void deleteItem(not_null<HistoryItem*> item);
void deleteItem(FullMsgId itemId);
void deleteAsGroup(FullMsgId itemId);
void reportItem(FullMsgId itemId);
void reportAsGroup(FullMsgId itemId);
void copySelectedText();
// Does any of the shown histories has this flag set.

View File

@@ -407,6 +407,17 @@ bool HistoryItem::canDeleteForEveryone(TimeId now) const {
return true;
}
bool HistoryItem::suggestReport() const {
if (out() || serviceMsg() || !IsServerMsgId(id)) {
return false;
} else if (const auto channel = history()->peer->asChannel()) {
return true;
} else if (const auto user = history()->peer->asUser()) {
return user->botInfo != nullptr;
}
return false;
}
bool HistoryItem::suggestBanReport() const {
auto channel = history()->peer->asChannel();
auto fromUser = from()->asUser();

View File

@@ -215,6 +215,7 @@ public:
virtual bool allowsEdit(TimeId now) const;
bool canDelete() const;
bool canDeleteForEveryone(TimeId now) const;
bool suggestReport() const;
bool suggestBanReport() const;
bool suggestDeleteAllReport() const;

View File

@@ -110,6 +110,12 @@ void ActivateWindowDelayed(not_null<Window::Controller*> controller) {
});
}
void InsertEmojiToField(not_null<Ui::InputField*> field, EmojiPtr emoji) {
if (!field->isHidden()) {
Ui::InsertEmojiAtCursor(field->textCursor(), emoji);
}
}
} // namespace
ReportSpamPanel::ReportSpamPanel(QWidget *parent) : TWidget(parent),
@@ -406,6 +412,7 @@ HistoryWidget::HistoryWidget(
not_null<Window::Controller*> controller)
: Window::AbstractSectionWidget(parent, controller)
, _fieldBarCancel(this, st::historyReplyCancel)
, _previewTimer([=] { requestPreview(); })
, _topBar(this, controller)
, _scroll(this, st::historyScroll, false)
, _historyDown(_scroll, st::historyToDown)
@@ -421,7 +428,11 @@ HistoryWidget::HistoryWidget(
, _botKeyboardShow(this, st::historyBotKeyboardShow)
, _botKeyboardHide(this, st::historyBotKeyboardHide)
, _botCommandStart(this, st::historyBotCommandStart)
, _field(this, controller, st::historyComposeField, langFactory(lng_message_ph))
, _field(
this,
st::historyComposeField,
Ui::InputField::Mode::MultiLine,
langFactory(lng_message_ph))
, _recordCancelWidth(st::historyRecordFont->width(lang(lng_record_cancel)))
, _a_recording(animation(this, &HistoryWidget::step_recording))
, _kbScroll(this, st::botKbScroll)
@@ -444,21 +455,22 @@ HistoryWidget::HistoryWidget(
connect(_botStart, SIGNAL(clicked()), this, SLOT(onBotStart()));
connect(_joinChannel, SIGNAL(clicked()), this, SLOT(onJoinChannel()));
connect(_muteUnmute, SIGNAL(clicked()), this, SLOT(onMuteUnmute()));
connect(_field, SIGNAL(submitted(bool)), this, SLOT(onSend(bool)));
connect(_field, SIGNAL(submitted(bool)), this, SLOT(onSend()));
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()));
connect(_field, SIGNAL(changed()), this, SLOT(onTextChange()));
connect(_field, SIGNAL(spacedReturnedPasted()), this, SLOT(onPreviewParse()));
connect(_field, SIGNAL(linksChanged()), this, SLOT(onPreviewCheck()));
connect(App::wnd()->windowHandle(), SIGNAL(visibleChanged(bool)), this, SLOT(onWindowVisibleChanged()));
connect(&_scrollTimer, SIGNAL(timeout()), this, SLOT(onScrollTimer()));
connect(_tabbedSelector, SIGNAL(emojiSelected(EmojiPtr)), _field, SLOT(onEmojiInsert(EmojiPtr)));
connect(
_tabbedSelector,
&TabbedSelector::emojiSelected,
_field,
[=](EmojiPtr emoji) { InsertEmojiToField(_field, emoji); });
connect(_tabbedSelector, SIGNAL(stickerSelected(DocumentData*)), this, SLOT(onStickerSend(DocumentData*)));
connect(_tabbedSelector, SIGNAL(photoSelected(PhotoData*)), this, SLOT(onPhotoSend(PhotoData*)));
connect(_tabbedSelector, SIGNAL(inlineResultSelected(InlineBots::Result*,UserData*)), this, SLOT(onInlineResultSend(InlineBots::Result*,UserData*)));
connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreviewTimeout()));
connect(Media::Capture::instance(), SIGNAL(error()), this, SLOT(onRecordError()));
connect(Media::Capture::instance(), SIGNAL(updated(quint16,qint32)), this, SLOT(onRecordUpdate(quint16,qint32)));
connect(Media::Capture::instance(), SIGNAL(done(QByteArray,VoiceWaveform,qint32)), this, SLOT(onRecordDone(QByteArray,VoiceWaveform,qint32)));
@@ -481,9 +493,12 @@ HistoryWidget::HistoryWidget(
connect(&_saveDraftTimer, SIGNAL(timeout()), this, SLOT(onDraftSave()));
_saveCloudDraftTimer.setSingleShot(true);
connect(&_saveCloudDraftTimer, SIGNAL(timeout()), this, SLOT(onCloudDraftSave()));
connect(_field->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(onDraftSaveDelayed()));
connect(_field, SIGNAL(cursorPositionChanged()), this, SLOT(onDraftSaveDelayed()));
connect(_field, SIGNAL(cursorPositionChanged()), this, SLOT(onCheckFieldAutocomplete()), Qt::QueuedConnection);
_field->scrollTop().changes(
) | rpl::start_with_next([=] {
onDraftSaveDelayed();
}, _field->lifetime());
connect(_field->rawTextEdit(), SIGNAL(cursorPositionChanged()), this, SLOT(onDraftSaveDelayed()));
connect(_field->rawTextEdit(), SIGNAL(cursorPositionChanged()), this, SLOT(onCheckFieldAutocomplete()), Qt::QueuedConnection);
_fieldBarCancel->hide();
@@ -498,20 +513,41 @@ HistoryWidget::HistoryWidget(
_historyDown->installEventFilter(this);
_unreadMentions->installEventFilter(this);
InitMessageField(_field);
_fieldAutocomplete->hide();
connect(_fieldAutocomplete, SIGNAL(mentionChosen(UserData*,FieldAutocomplete::ChooseMethod)), this, SLOT(onMentionInsert(UserData*)));
connect(_fieldAutocomplete, SIGNAL(hashtagChosen(QString,FieldAutocomplete::ChooseMethod)), this, SLOT(onHashtagOrBotCommandInsert(QString,FieldAutocomplete::ChooseMethod)));
connect(_fieldAutocomplete, SIGNAL(botCommandChosen(QString,FieldAutocomplete::ChooseMethod)), this, SLOT(onHashtagOrBotCommandInsert(QString,FieldAutocomplete::ChooseMethod)));
connect(_fieldAutocomplete, SIGNAL(stickerChosen(DocumentData*,FieldAutocomplete::ChooseMethod)), this, SLOT(onStickerSend(DocumentData*)));
connect(_fieldAutocomplete, SIGNAL(moderateKeyActivate(int,bool*)), this, SLOT(onModerateKeyActivate(int,bool*)));
_field->installEventFilter(_fieldAutocomplete);
_field->setInsertFromMimeDataHook([this](const QMimeData *data) {
return confirmSendingFiles(
data,
CompressConfirm::Auto,
data->text());
_fieldLinksParser = std::make_unique<MessageLinksParser>(_field);
_fieldLinksParser->list().changes(
) | rpl::start_with_next([=](QStringList &&parsed) {
_parsedLinks = std::move(parsed);
checkPreview();
}, lifetime());
_field->rawTextEdit()->installEventFilter(_fieldAutocomplete);
_field->setMimeDataHook([=](
not_null<const QMimeData*> data,
Ui::InputField::MimeAction action) {
if (action == Ui::InputField::MimeAction::Check) {
return canSendFiles(data);
} else if (action == Ui::InputField::MimeAction::Insert) {
return confirmSendingFiles(
data,
CompressConfirm::Auto,
data->text());
}
Unexpected("action in MimeData hook.");
});
_emojiSuggestions.create(this, _field->rawTextEdit());
_emojiSuggestions->setReplaceCallback([=](
int from,
int till,
const QString &replacement) {
_field->commitInstantReplacement(from, till, replacement);
});
_emojiSuggestions.create(this, _field.data());
updateFieldSubmitSettings();
_field->hide();
@@ -611,11 +647,12 @@ HistoryWidget::HistoryWidget(
| UpdateFlag::MembersChanged
| UpdateFlag::UserOnlineChanged
| UpdateFlag::NotificationsEnabled
| UpdateFlag::ChannelAmIn;
| UpdateFlag::ChannelAmIn
| UpdateFlag::ChannelPromotedChanged;
subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(changes, [this](const Notify::PeerUpdate &update) {
if (update.peer == _peer) {
if (update.flags & UpdateFlag::ChannelRightsChanged) {
onPreviewCheck();
checkPreview();
}
if (update.flags & UpdateFlag::UnreadMentionsChanged) {
updateUnreadMentionsVisibility();
@@ -646,6 +683,13 @@ HistoryWidget::HistoryWidget(
this->update();
}
}
if (update.flags & UpdateFlag::ChannelPromotedChanged) {
refreshAboutProxyPromotion();
updateHistoryGeometry();
updateControlsVisibility();
updateControlsGeometry();
this->update();
}
if (update.flags & (UpdateFlag::UserIsBlocked
| UpdateFlag::AdminsChanged
| UpdateFlag::MembersChanged
@@ -981,36 +1025,41 @@ void HistoryWidget::onHashtagOrBotCommandInsert(
}
void HistoryWidget::updateInlineBotQuery() {
UserData *bot = nullptr;
QString inlineBotUsername;
QString query = _field->getInlineBotQuery(&bot, &inlineBotUsername);
if (inlineBotUsername != _inlineBotUsername) {
_inlineBotUsername = inlineBotUsername;
const auto query = ParseInlineBotQuery(_field);
if (_inlineBotUsername != query.username) {
_inlineBotUsername = query.username;
if (_inlineBotResolveRequestId) {
// Notify::inlineBotRequesting(false);
MTP::cancel(_inlineBotResolveRequestId);
_inlineBotResolveRequestId = 0;
}
if (bot == Ui::LookingUpInlineBot) {
_inlineBot = Ui::LookingUpInlineBot;
if (query.lookingUpBot) {
_inlineBot = nullptr;
_inlineLookingUpBot = true;
// Notify::inlineBotRequesting(true);
_inlineBotResolveRequestId = MTP::send(MTPcontacts_ResolveUsername(MTP_string(_inlineBotUsername)), rpcDone(&HistoryWidget::inlineBotResolveDone), rpcFail(&HistoryWidget::inlineBotResolveFail, _inlineBotUsername));
return;
_inlineBotResolveRequestId = MTP::send(
MTPcontacts_ResolveUsername(MTP_string(_inlineBotUsername)),
rpcDone(&HistoryWidget::inlineBotResolveDone),
rpcFail(
&HistoryWidget::inlineBotResolveFail,
_inlineBotUsername));
} else {
applyInlineBotQuery(query.bot, query.query);
}
} else if (bot == Ui::LookingUpInlineBot) {
if (_inlineBot == Ui::LookingUpInlineBot) {
return;
} else if (query.lookingUpBot) {
if (!_inlineLookingUpBot) {
applyInlineBotQuery(_inlineBot, query.query);
}
bot = _inlineBot;
} else {
applyInlineBotQuery(query.bot, query.query);
}
applyInlineBotQuery(bot, query);
}
void HistoryWidget::applyInlineBotQuery(UserData *bot, const QString &query) {
if (bot) {
if (_inlineBot != bot) {
_inlineBot = bot;
_inlineLookingUpBot = false;
inlineBotChanged();
}
if (!_inlineResults) {
@@ -1111,15 +1160,21 @@ void HistoryWidget::onTextChange() {
}
_saveCloudDraftTimer.stop();
if (!_peer || !(_textUpdateEvents & TextUpdateEvent::SaveDraft)) return;
if (!_peer || !(_textUpdateEvents & TextUpdateEvent::SaveDraft)) {
return;
}
_saveDraftText = true;
onDraftSave(true);
}
void HistoryWidget::onDraftSaveDelayed() {
if (!_peer || !(_textUpdateEvents & TextUpdateEvent::SaveDraft)) return;
if (!_field->textCursor().anchor() && !_field->textCursor().position() && !_field->verticalScrollBar()->value()) {
if (!_peer || !(_textUpdateEvents & TextUpdateEvent::SaveDraft)) {
return;
}
if (!_field->textCursor().anchor()
&& !_field->textCursor().position()
&& !_field->scrollTop().current()) {
if (!Local::hasDraftCursors(_peer->id)) {
return;
}
@@ -1147,7 +1202,7 @@ void HistoryWidget::saveFieldToHistoryLocalDraft() {
if (_editMsgId) {
_history->setEditDraft(std::make_unique<Data::Draft>(_field, _editMsgId, _previewCancelled, _saveEditMsgRequestId));
} else {
if (_replyToId || !_field->isEmpty()) {
if (_replyToId || !_field->empty()) {
_history->setLocalDraft(std::make_unique<Data::Draft>(_field, _replyToId, _previewCancelled));
} else {
_history->clearLocalDraft();
@@ -1558,7 +1613,9 @@ void HistoryWidget::calcNextReplyReturn() {
_replyReturn = App::histItemById(_channel, _replyReturns.back());
}
}
if (!_replyReturn) updateControlsVisibility();
if (!_replyReturn) {
updateControlsVisibility();
}
}
void HistoryWidget::fastShowAtEnd(not_null<History*> history) {
@@ -1579,12 +1636,12 @@ void HistoryWidget::fastShowAtEnd(not_null<History*> history) {
}
}
void HistoryWidget::applyDraft(bool parseLinks, Ui::FlatTextarea::UndoHistoryAction undoHistoryAction) {
void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
auto draft = _history ? _history->draft() : nullptr;
auto fieldAvailable = canWriteMessage();
if (!draft || (!_history->editDraft() && !fieldAvailable)) {
auto fieldWillBeHiddenAfterEdit = (!fieldAvailable && _editMsgId != 0);
clearFieldText(0, undoHistoryAction);
clearFieldText(0, fieldHistoryAction);
_field->setFocus();
_replyEditMsg = nullptr;
_editMsgId = _replyToId = 0;
@@ -1596,7 +1653,7 @@ void HistoryWidget::applyDraft(bool parseLinks, Ui::FlatTextarea::UndoHistoryAct
}
_textUpdateEvents = 0;
setFieldText(draft->textWithTags, 0, undoHistoryAction);
setFieldText(draft->textWithTags, 0, fieldHistoryAction);
_field->setFocus();
draft->cursor.applyTo(_field);
_textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping;
@@ -1612,9 +1669,6 @@ void HistoryWidget::applyDraft(bool parseLinks, Ui::FlatTextarea::UndoHistoryAct
updateControlsVisibility();
updateControlsGeometry();
if (parseLinks) {
onPreviewParse();
}
if (_editMsgId || _replyToId) {
updateReplyEditTexts();
if (!_replyEditMsg) {
@@ -1628,7 +1682,7 @@ void HistoryWidget::applyDraft(bool parseLinks, Ui::FlatTextarea::UndoHistoryAct
void HistoryWidget::applyCloudDraft(History *history) {
if (_history == history && !_editMsgId) {
applyDraft(true, Ui::FlatTextarea::AddToUndoHistory);
applyDraft(Ui::InputField::HistoryAction::NewEntry);
updateControlsVisibility();
updateControlsGeometry();
@@ -1659,11 +1713,13 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re
}
clearDelayedShowAt();
if (_replyReturn) {
while (_replyReturn) {
if (_replyReturn->history() == _history && _replyReturn->id == showAtMsgId) {
calcNextReplyReturn();
} else if (_replyReturn->history() == _migrated && -_replyReturn->id == showAtMsgId) {
calcNextReplyReturn();
} else {
break;
}
}
@@ -1727,11 +1783,14 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re
_membersDropdown.destroy();
_scrollToAnimation.finish();
_history = _migrated = nullptr;
_list = nullptr;
_peer = nullptr;
_channel = NoChannel;
_canSendMessages = false;
_silent.destroy();
updateBotKeyboard();
} else {
Assert(_list == nullptr);
}
App::clearMousedItems();
@@ -1746,7 +1805,6 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re
_membersDropdownShowTimer.stop();
_scroll->takeWidget<HistoryInner>().destroy();
_list = nullptr;
clearInlineBot();
@@ -1829,15 +1887,12 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re
_migrated->clearEditDraft();
_history->takeLocalDraft(_migrated);
}
applyDraft(false);
applyDraft();
_send->finishAnimating();
_tabbedSelector->showMegagroupSet(_peer->asMegagroup());
updateControlsGeometry();
if (!_previewCancelled) {
onPreviewParse();
}
connect(_scroll, SIGNAL(geometryChanged()), _list, SLOT(onParentGeometryChanged()));
@@ -1890,11 +1945,11 @@ void HistoryWidget::clearAllLoadRequests() {
}
void HistoryWidget::updateFieldSubmitSettings() {
auto settings = Ui::FlatTextarea::SubmitSettings::Enter;
auto settings = Ui::InputField::SubmitSettings::Enter;
if (_isInlineBot) {
settings = Ui::FlatTextarea::SubmitSettings::None;
settings = Ui::InputField::SubmitSettings::None;
} else if (cCtrlEnter()) {
settings = Ui::FlatTextarea::SubmitSettings::CtrlEnter;
settings = Ui::InputField::SubmitSettings::CtrlEnter;
}
_field->setSubmitSettings(settings);
}
@@ -2073,6 +2128,7 @@ void HistoryWidget::updateControlsVisibility() {
if (_reportSpamPanel) {
_reportSpamPanel->show();
}
refreshAboutProxyPromotion();
if (!editingMessage() && (isBlocked() || isJoinChannel() || isMuteUnmute() || isBotStart())) {
if (isBlocked()) {
_joinChannel->hide();
@@ -2234,6 +2290,24 @@ void HistoryWidget::updateControlsVisibility() {
updateMouseTracking();
}
void HistoryWidget::refreshAboutProxyPromotion() {
if (_history->useProxyPromotion()) {
if (!_aboutProxyPromotion) {
_aboutProxyPromotion = object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>(
this,
object_ptr<Ui::FlatLabel>(
this,
lang(lng_proxy_sponsor_about),
Ui::FlatLabel::InitType::Simple,
st::historyAboutProxy),
st::historyAboutProxyPadding);
}
_aboutProxyPromotion->show();
} else {
_aboutProxyPromotion.destroy();
}
}
void HistoryWidget::updateMouseTracking() {
bool trackMouse = !_fieldBarCancel->isHidden() || _pinnedBar;
setMouseTracking(trackMouse);
@@ -2431,11 +2505,15 @@ void HistoryWidget::messagesReceived(PeerData *peer, const MTPmessages_Messages
return;
}
}
if (_replyReturn) {
if (_replyReturn->history() == _history && _replyReturn->id == _delayedShowAtMsgId) {
while (_replyReturn) {
if (_replyReturn->history() == _history
&& _replyReturn->id == _delayedShowAtMsgId) {
calcNextReplyReturn();
} else if (_replyReturn->history() == _migrated && -_replyReturn->id == _delayedShowAtMsgId) {
} else if (_replyReturn->history() == _migrated
&& -_replyReturn->id == _delayedShowAtMsgId) {
calcNextReplyReturn();
} else {
break;
}
}
@@ -2776,9 +2854,14 @@ void HistoryWidget::checkReplyReturns() {
void HistoryWidget::onInlineBotCancel() {
auto &textWithTags = _field->getTextWithTags();
if (textWithTags.text.size() > _inlineBotUsername.size() + 2) {
setFieldText({ '@' + _inlineBotUsername + ' ', TextWithTags::Tags() }, TextUpdateEvent::SaveDraft, Ui::FlatTextarea::AddToUndoHistory);
setFieldText(
{ '@' + _inlineBotUsername + ' ', TextWithTags::Tags() },
TextUpdateEvent::SaveDraft,
Ui::InputField::HistoryAction::NewEntry);
} else {
clearFieldText(TextUpdateEvent::SaveDraft, Ui::FlatTextarea::AddToUndoHistory);
clearFieldText(
TextUpdateEvent::SaveDraft,
Ui::InputField::HistoryAction::NewEntry);
}
}
@@ -2895,7 +2978,7 @@ void HistoryWidget::hideSelectorControlsAnimated() {
}
}
void HistoryWidget::onSend(bool ctrlShiftEnter) {
void HistoryWidget::onSend() {
if (!_history) return;
if (_editMsgId) {
@@ -3482,7 +3565,10 @@ bool HistoryWidget::insertBotCommand(const QString &cmd) {
cur.movePosition(QTextCursor::End);
_field->setTextCursor(cur);
} else {
setFieldText({ toInsert, TextWithTags::Tags() }, TextUpdateEvent::SaveDraft, Ui::FlatTextarea::AddToUndoHistory);
setFieldText(
{ toInsert, TextWithTags::Tags() },
TextUpdateEvent::SaveDraft,
Ui::InputField::HistoryAction::NewEntry);
_field->setFocus();
return true;
}
@@ -3544,33 +3630,29 @@ bool HistoryWidget::hasSilentToggle() const {
&& !_peer->notifySettingsUnknown();
}
void HistoryWidget::inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result) {
void HistoryWidget::inlineBotResolveDone(
const MTPcontacts_ResolvedPeer &result) {
Expects(result.type() == mtpc_contacts_resolvedPeer);
_inlineBotResolveRequestId = 0;
const auto &data = result.c_contacts_resolvedPeer();
// Notify::inlineBotRequesting(false);
UserData *resolvedBot = nullptr;
if (result.type() == mtpc_contacts_resolvedPeer) {
const auto &d(result.c_contacts_resolvedPeer());
resolvedBot = App::feedUsers(d.vusers);
if (resolvedBot) {
if (!resolvedBot->botInfo || resolvedBot->botInfo->inlinePlaceholder.isEmpty()) {
resolvedBot = nullptr;
const auto resolvedBot = [&]() -> UserData* {
if (const auto result = App::feedUsers(data.vusers)) {
if (result->botInfo
&& !result->botInfo->inlinePlaceholder.isEmpty()) {
return result;
}
}
App::feedChats(d.vchats);
}
return nullptr;
}();
App::feedChats(data.vchats);
UserData *bot = nullptr;
QString inlineBotUsername;
auto query = _field->getInlineBotQuery(&bot, &inlineBotUsername);
if (inlineBotUsername == _inlineBotUsername) {
if (bot == Ui::LookingUpInlineBot) {
bot = resolvedBot;
}
} else {
bot = nullptr;
}
if (bot) {
applyInlineBotQuery(bot, query);
const auto query = ParseInlineBotQuery(_field);
if (_inlineBotUsername == query.username) {
applyInlineBotQuery(
query.lookingUpBot ? resolvedBot : query.bot,
query.query);
} else {
clearInlineBot();
}
@@ -3614,11 +3696,11 @@ bool HistoryWidget::isMuteUnmute() const {
}
bool HistoryWidget::showRecordButton() const {
return Media::Capture::instance()->available() && !_field->hasSendText() && !readyToForward() && !_editMsgId;
return Media::Capture::instance()->available() && !HasSendText(_field) && !readyToForward() && !_editMsgId;
}
bool HistoryWidget::showInlineBotCancel() const {
return _inlineBot && (_inlineBot != Ui::LookingUpInlineBot);
return _inlineBot && !_inlineLookingUpBot;
}
void HistoryWidget::updateSendButtonType() {
@@ -3640,7 +3722,7 @@ bool HistoryWidget::updateCmdStartShown() {
bool cmdStartShown = false;
if (_history && _peer && ((_peer->isChat() && _peer->asChat()->botStatus > 0) || (_peer->isMegagroup() && _peer->asChannel()->mgInfo->botStatus > 0) || (_peer->isUser() && _peer->asUser()->botInfo))) {
if (!isBotStart() && !isBlocked() && !_keyboard->hasMarkup() && !_keyboard->forceReply()) {
if (!_field->hasSendText()) {
if (!HasSendText(_field)) {
cmdStartShown = true;
}
}
@@ -3748,7 +3830,10 @@ void HistoryWidget::onKbToggle(bool manual) {
}
void HistoryWidget::onCmdStart() {
setFieldText({ qsl("/"), TextWithTags::Tags() }, 0, Ui::FlatTextarea::AddToUndoHistory);
setFieldText(
{ qsl("/"), TextWithTags::Tags() },
0,
Ui::InputField::HistoryAction::NewEntry);
}
void HistoryWidget::setMembersShowAreaActive(bool active) {
@@ -3895,6 +3980,12 @@ void HistoryWidget::moveFieldControls() {
_unblock->setGeometry(fullWidthButtonRect);
_joinChannel->setGeometry(fullWidthButtonRect);
_muteUnmute->setGeometry(fullWidthButtonRect);
if (_aboutProxyPromotion) {
_aboutProxyPromotion->moveToLeft(
0,
fullWidthButtonRect.y() - _aboutProxyPromotion->height());
}
}
void HistoryWidget::updateTabbedSelectorToggleTooltipGeometry() {
@@ -3925,8 +4016,9 @@ void HistoryWidget::updateFieldSize() {
void HistoryWidget::clearInlineBot() {
if (_inlineBot) {
_inlineBot = nullptr;
_inlineLookingUpBot = false;
inlineBotChanged();
_field->finishPlaceholder();
_field->finishAnimating();
}
if (_inlineResults) {
_inlineResults->clearInlineBot();
@@ -3957,24 +4049,39 @@ void HistoryWidget::onFieldFocused() {
}
void HistoryWidget::onCheckFieldAutocomplete() {
if (!_history || _a_show.animating()) return;
auto start = false;
auto isInlineBot = _inlineBot && (_inlineBot != Ui::LookingUpInlineBot);
auto query = isInlineBot ? QString() : _field->getMentionHashtagBotCommandPart(start);
if (!query.isEmpty()) {
if (query.at(0) == '#' && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) Local::readRecentHashtagsAndBots();
if (query.at(0) == '@' && cRecentInlineBots().isEmpty()) Local::readRecentHashtagsAndBots();
if (query.at(0) == '/' && _peer->isUser() && !_peer->asUser()->botInfo) return;
if (!_history || _a_show.animating()) {
return;
}
_fieldAutocomplete->showFiltered(_peer, query, start);
const auto isInlineBot = _inlineBot && !_inlineLookingUpBot;
const auto autocomplete = isInlineBot
? AutocompleteQuery()
: ParseMentionHashtagBotCommandQuery(_field);
if (!autocomplete.query.isEmpty()) {
if (autocomplete.query[0] == '#'
&& cRecentWriteHashtags().isEmpty()
&& cRecentSearchHashtags().isEmpty()) {
Local::readRecentHashtagsAndBots();
} else if (autocomplete.query[0] == '@'
&& cRecentInlineBots().isEmpty()) {
Local::readRecentHashtagsAndBots();
} else if (autocomplete.query[0] == '/'
&& _peer->isUser()
&& !_peer->asUser()->botInfo) {
return;
}
}
_fieldAutocomplete->showFiltered(
_peer,
autocomplete.query,
autocomplete.fromStart);
}
void HistoryWidget::updateFieldPlaceholder() {
if (_editMsgId) {
_field->setPlaceholder(langFactory(lng_edit_message_text));
} else {
if (_inlineBot && _inlineBot != Ui::LookingUpInlineBot) {
if (_inlineBot && !_inlineLookingUpBot) {
auto text = _inlineBot->botInfo->inlinePlaceholder.mid(1);
_field->setPlaceholder([text] { return text; }, _inlineBot->username.size() + 2);
} else {
@@ -4027,7 +4134,7 @@ bool HistoryWidget::confirmSendingFiles(const QStringList &files) {
return confirmSendingFiles(files, CompressConfirm::Auto);
}
bool HistoryWidget::confirmSendingFiles(const QMimeData *data) {
bool HistoryWidget::confirmSendingFiles(not_null<const QMimeData*> data) {
return confirmSendingFiles(data, CompressConfirm::Auto);
}
@@ -4056,11 +4163,19 @@ bool HistoryWidget::confirmSendingFiles(
? CompressConfirm::None
: compressed;
auto box = Box<SendFilesBox>(std::move(list), boxCompressConfirm);
const auto cursor = _field->textCursor();
const auto position = cursor.position();
const auto anchor = cursor.anchor();
const auto text = _field->getTextWithTags();
auto box = Box<SendFilesBox>(
std::move(list),
text,
boxCompressConfirm);
_field->setTextWithTags({});
box->setConfirmedCallback(base::lambda_guarded(this, [=](
Storage::PreparedList &&list,
SendFilesWay way,
const QString &caption,
TextWithTags &&caption,
bool ctrlShiftEnter) {
if (showSendingFilesError(list)) {
return;
@@ -4074,15 +4189,22 @@ bool HistoryWidget::confirmSendingFiles(
uploadFilesAfterConfirmation(
std::move(list),
type,
caption,
std::move(caption),
replyToId(),
album);
}));
if (!insertTextOnCancel.isEmpty()) {
box->setCancelledCallback(base::lambda_guarded(this, [=] {
box->setCancelledCallback(base::lambda_guarded(this, [=] {
_field->setTextWithTags(text);
auto cursor = _field->textCursor();
cursor.setPosition(anchor);
if (position != anchor) {
cursor.setPosition(position, QTextCursor::KeepAnchor);
}
_field->setTextCursor(cursor);
if (!insertTextOnCancel.isEmpty()) {
_field->textCursor().insertText(insertTextOnCancel);
}));
}
}
}));
ActivateWindowDelayed(controller());
Ui::show(std::move(box));
@@ -4108,8 +4230,29 @@ bool HistoryWidget::confirmSendingFiles(
insertTextOnCancel);
}
bool HistoryWidget::canSendFiles(not_null<const QMimeData*> data) const {
if (!canWriteMessage()) {
return false;
}
if (const auto urls = data->urls(); !urls.empty()) {
if (ranges::find_if(
urls,
[](const QUrl &url) { return !url.isLocalFile(); }
) == urls.end()) {
return true;
}
}
if (data->hasImage()) {
const auto image = qvariant_cast<QImage>(data->imageData());
if (!image.isNull()) {
return true;
}
}
return false;
}
bool HistoryWidget::confirmSendingFiles(
const QMimeData *data,
not_null<const QMimeData*> data,
CompressConfirm compressed,
const QString &insertTextOnCancel) {
if (!canWriteMessage()) {
@@ -4154,18 +4297,17 @@ void HistoryWidget::uploadFiles(
SendMediaType type) {
ActivateWindowDelayed(controller());
auto caption = QString();
uploadFilesAfterConfirmation(
std::move(list),
type,
caption,
TextWithTags(),
replyToId());
}
void HistoryWidget::uploadFilesAfterConfirmation(
Storage::PreparedList &&list,
SendMediaType type,
QString caption,
TextWithTags &&caption,
MsgId replyTo,
std::shared_ptr<SendingAlbum> album) {
Assert(canWriteMessage());
@@ -4175,7 +4317,7 @@ void HistoryWidget::uploadFilesAfterConfirmation(
Auth().api().sendFiles(
std::move(list),
type,
caption,
std::move(caption),
album,
options);
}
@@ -4228,8 +4370,23 @@ void HistoryWidget::sendFileConfirmed(
options.generateLocal = true;
Auth().api().sendAction(options);
auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media;
if (file->to.replyTo) flags |= MTPDmessage::Flag::f_reply_to_msg_id;
auto caption = TextWithEntities{
file->caption.text,
ConvertTextTagsToEntities(file->caption.tags)
};
const auto prepareFlags = Ui::ItemTextOptions(
history,
App::self()).flags;
TextUtilities::PrepareForSending(caption, prepareFlags);
TextUtilities::Trim(caption);
auto localEntities = TextUtilities::EntitiesToMTP(caption.entities);
auto flags = NewMessageFlags(peer)
| MTPDmessage::Flag::f_entities
| MTPDmessage::Flag::f_media;
if (file->to.replyTo) {
flags |= MTPDmessage::Flag::f_reply_to_msg_id;
}
bool channelPost = peer->isChannel() && !peer->isMegagroup();
bool silentPost = channelPost && file->to.silent;
if (channelPost) {
@@ -4265,10 +4422,10 @@ void HistoryWidget::sendFileConfirmed(
MTPint(),
MTP_int(file->to.replyTo),
MTP_int(unixtime()),
MTP_string(file->caption),
MTP_string(caption.text),
photo,
MTPnullMarkup,
MTPnullEntities, // #TODO caption entities
localEntities,
MTP_int(1),
MTPint(),
MTP_string(messagePostAuthor),
@@ -4290,10 +4447,10 @@ void HistoryWidget::sendFileConfirmed(
MTPint(),
MTP_int(file->to.replyTo),
MTP_int(unixtime()),
MTP_string(file->caption),
MTP_string(caption.text),
document,
MTPnullMarkup,
MTPnullEntities, // #TODO caption entities
localEntities,
MTP_int(1),
MTPint(),
MTP_string(messagePostAuthor),
@@ -4318,10 +4475,10 @@ void HistoryWidget::sendFileConfirmed(
MTPint(),
MTP_int(file->to.replyTo),
MTP_int(unixtime()),
MTP_string(file->caption),
MTP_string(caption.text),
document,
MTPnullMarkup,
MTPnullEntities, // #TODO caption entities
localEntities,
MTP_int(1),
MTPint(),
MTP_string(messagePostAuthor),
@@ -4615,7 +4772,7 @@ void HistoryWidget::itemRemoved(not_null<const HistoryItem*> item) {
cancelReply();
}
}
if (item == _replyReturn) {
while (item == _replyReturn) {
calcNextReplyReturn();
}
if (_pinnedBar && item->id == _pinnedBar->msgId) {
@@ -4713,6 +4870,10 @@ void HistoryWidget::updateHistoryGeometry(bool initial, bool loadedDown, const S
auto newScrollHeight = height() - _topBar->height();
if (!editingMessage() && (isBlocked() || isBotStart() || isJoinChannel() || isMuteUnmute())) {
newScrollHeight -= _unblock->height();
if (_aboutProxyPromotion) {
_aboutProxyPromotion->resizeToWidth(width());
newScrollHeight -= _aboutProxyPromotion->height();
}
} else {
if (editingMessage() || _canSendMessages) {
newScrollHeight -= (_field->height() + 2 * st::historySendPadding);
@@ -4908,7 +5069,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) {
if (_keyboard->singleUse() && _keyboard->hasMarkup() && _keyboard->forMsgId() == FullMsgId(_channel, _history->lastKeyboardId) && _history->lastKeyboardUsed) {
_history->lastKeyboardHiddenId = _history->lastKeyboardId;
}
if (!isBotStart() && !isBlocked() && _canSendMessages && (wasVisible || (_replyToId && _replyEditMsg) || (!_field->hasSendText() && !kbWasHidden()))) {
if (!isBotStart() && !isBlocked() && _canSendMessages && (wasVisible || (_replyToId && _replyEditMsg) || (!HasSendText(_field) && !kbWasHidden()))) {
if (!_a_show.animating()) {
if (hasMarkup) {
_kbScroll->show();
@@ -5102,7 +5263,7 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) {
: nullptr;
if (item
&& item->allowsEdit(unixtime())
&& _field->isEmpty()
&& _field->empty()
&& !_editMsgId
&& !_replyToId) {
editMessage(item);
@@ -5585,21 +5746,30 @@ void HistoryWidget::sendExistingPhoto(
_field->setFocus();
}
void HistoryWidget::setFieldText(const TextWithTags &textWithTags, TextUpdateEvents events, Ui::FlatTextarea::UndoHistoryAction undoHistoryAction) {
void HistoryWidget::setFieldText(
const TextWithTags &textWithTags,
TextUpdateEvents events,
FieldHistoryAction fieldHistoryAction) {
_textUpdateEvents = events;
_field->setTextWithTags(textWithTags, undoHistoryAction);
_field->moveCursor(QTextCursor::End);
_textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping;
_field->setTextWithTags(textWithTags, fieldHistoryAction);
auto cursor = _field->textCursor();
cursor.movePosition(QTextCursor::End);
_field->setTextCursor(cursor);
_textUpdateEvents = TextUpdateEvent::SaveDraft
| TextUpdateEvent::SendTyping;
_previewCancelled = false;
_previewData = nullptr;
if (_previewRequest) {
MTP::cancel(_previewRequest);
_previewRequest = 0;
}
MTP::cancel(base::take(_previewRequest));
_previewLinks.clear();
}
void HistoryWidget::clearFieldText(
TextUpdateEvents events,
FieldHistoryAction fieldHistoryAction) {
setFieldText(TextWithTags(), events, fieldHistoryAction);
}
void HistoryWidget::replyToMessage(FullMsgId itemId) {
if (const auto item = App::histItemById(itemId)) {
replyToMessage(item);
@@ -5686,7 +5856,7 @@ void HistoryWidget::editMessage(not_null<HistoryItem*> item) {
_send->clearState();
}
if (!_editMsgId) {
if (_replyToId || !_field->isEmpty()) {
if (_replyToId || !_field->empty()) {
_history->setLocalDraft(std::make_unique<Data::Draft>(_field, _replyToId, _previewCancelled));
} else {
_history->clearLocalDraft();
@@ -5695,7 +5865,7 @@ void HistoryWidget::editMessage(not_null<HistoryItem*> item) {
const auto original = item->originalText();
const auto editData = TextWithTags {
TextUtilities::ApplyEntities(original),
original.text,
ConvertEntitiesToTextTags(original.entities)
};
const auto cursor = MessageCursor {
@@ -5708,7 +5878,7 @@ void HistoryWidget::editMessage(not_null<HistoryItem*> item) {
item->id,
cursor,
false));
applyDraft(false);
applyDraft();
_previewData = nullptr;
if (const auto media = item->media()) {
@@ -5717,9 +5887,6 @@ void HistoryWidget::editMessage(not_null<HistoryItem*> item) {
updatePreview();
}
}
if (!_previewData) {
onPreviewParse();
}
updateBotKeyboard();
@@ -5943,12 +6110,7 @@ void HistoryWidget::previewCancel() {
}
}
void HistoryWidget::onPreviewParse() {
if (_previewCancelled) return;
_field->parseLinks();
}
void HistoryWidget::onPreviewCheck() {
void HistoryWidget::checkPreview() {
auto previewRestricted = [this] {
if (auto megagroup = _peer ? _peer->asMegagroup() : nullptr) {
if (megagroup->restricted(ChannelRestriction::f_embed_links)) {
@@ -5964,15 +6126,16 @@ void HistoryWidget::onPreviewCheck() {
update();
return;
}
auto linksList = _field->linksList();
auto newLinks = linksList.join(' ');
if (newLinks != _previewLinks) {
const auto newLinks = _parsedLinks.join(' ');
if (_previewLinks != newLinks) {
MTP::cancel(base::take(_previewRequest));
_previewLinks = newLinks;
if (_previewLinks.isEmpty()) {
if (_previewData && _previewData->pendingTill >= 0) previewCancel();
if (_previewData && _previewData->pendingTill >= 0) {
previewCancel();
}
} else {
PreviewCache::const_iterator i = _previewCache.constFind(_previewLinks);
const auto i = _previewCache.constFind(_previewLinks);
if (i == _previewCache.cend()) {
_previewRequest = MTP::send(
MTPmessages_GetWebPagePreview(
@@ -5990,17 +6153,18 @@ void HistoryWidget::onPreviewCheck() {
}
}
void HistoryWidget::onPreviewTimeout() {
if (_previewData
&& (_previewData->pendingTill > 0)
&& !_previewLinks.isEmpty()) {
_previewRequest = MTP::send(
MTPmessages_GetWebPagePreview(
MTP_flags(0),
MTP_string(_previewLinks),
MTPnullEntities),
rpcDone(&HistoryWidget::gotPreview, _previewLinks));
void HistoryWidget::requestPreview() {
if (!_previewData
|| (_previewData->pendingTill <= 0)
|| _previewLinks.isEmpty()) {
return;
}
_previewRequest = MTP::send(
MTPmessages_GetWebPagePreview(
MTP_flags(0),
MTP_string(_previewLinks),
MTPnullEntities),
rpcDone(&HistoryWidget::gotPreview, _previewLinks));
}
void HistoryWidget::gotPreview(QString links, const MTPMessageMedia &result, mtpRequestId req) {
@@ -6031,7 +6195,7 @@ void HistoryWidget::gotPreview(QString links, const MTPMessageMedia &result, mtp
}
void HistoryWidget::updatePreview() {
_previewTimer.stop();
_previewTimer.cancel();
if (_previewData && _previewData->pendingTill >= 0) {
_fieldBarCancel->show();
updateMouseTracking();
@@ -6050,9 +6214,8 @@ void HistoryWidget::updatePreview() {
TextUtilities::Clean(linkText),
Ui::DialogTextOptions());
int32 t = (_previewData->pendingTill - unixtime()) * 1000;
if (t <= 0) t = 1;
_previewTimer.start(t);
const auto timeout = (_previewData->pendingTill - unixtime());
_previewTimer.callOnce(std::max(timeout, 0) * TimeMs(1000));
} else {
QString title, desc;
if (_previewData->siteName.isEmpty()) {
@@ -6101,7 +6264,10 @@ void HistoryWidget::onCancel() {
onInlineBotCancel();
} else if (_editMsgId) {
auto original = _replyEditMsg ? _replyEditMsg->originalText() : TextWithEntities();
auto editData = TextWithTags { TextUtilities::ApplyEntities(original), ConvertEntitiesToTextTags(original.entities) };
auto editData = TextWithTags {
original.text,
ConvertEntitiesToTextTags(original.entities)
};
if (_replyEditMsg && editData != _field->getTextWithTags()) {
Ui::show(Box<ConfirmBox>(
lang(lng_cancel_edit_post_sure),
@@ -6647,6 +6813,9 @@ void HistoryWidget::paintEvent(QPaintEvent *e) {
} else if (isRestrictedWrite()) {
drawRestrictedWrite(p);
}
if (_aboutProxyPromotion) {
p.fillRect(_aboutProxyPromotion->geometry(), st::historyReplyBg);
}
if (_pinnedBar && !_pinnedBar->cancel->isHidden()) {
drawPinnedBar(p);
}

View File

@@ -22,6 +22,7 @@ struct FileMediaInformation;
struct SendingAlbum;
enum class SendMediaType;
enum class CompressConfirm;
class MessageLinksParser;
namespace InlineBots {
namespace Layout {
@@ -176,6 +177,8 @@ class HistoryWidget final : public Window::AbstractSectionWidget, public RPCSend
Q_OBJECT
public:
using FieldHistoryAction = Ui::InputField::HistoryAction;
HistoryWidget(QWidget *parent, not_null<Window::Controller*> controller);
void start();
@@ -219,7 +222,7 @@ public:
void updateStickersByEmoji();
bool confirmSendingFiles(const QStringList &files);
bool confirmSendingFiles(const QMimeData *data);
bool confirmSendingFiles(not_null<const QMimeData*> data);
void sendFileConfirmed(const std::shared_ptr<FileLoadResult> &file);
void updateControlsVisibility();
@@ -297,7 +300,8 @@ public:
void updateBotKeyboard(History *h = nullptr, bool force = false);
void fastShowAtEnd(not_null<History*> history);
void applyDraft(bool parseLinks = true, Ui::FlatTextarea::UndoHistoryAction undoHistoryAction = Ui::FlatTextarea::ClearUndoHistory);
void applyDraft(
FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear);
void showHistory(const PeerId &peer, MsgId showAtMsgId, bool reload = false);
void clearDelayedShowAt();
void clearAllLoadRequests();
@@ -374,10 +378,6 @@ public slots:
void onPinnedHide();
void onFieldBarCancel();
void onPreviewParse();
void onPreviewCheck();
void onPreviewTimeout();
void onPhotoUploaded(const FullMsgId &msgId, bool silent, const MTPInputFile &file);
void onDocumentUploaded(const FullMsgId &msgId, bool silent, const MTPInputFile &file);
void onThumbDocumentUploaded(const FullMsgId &msgId, bool silent, const MTPInputFile &file, const MTPInputFile &thumb);
@@ -433,7 +433,7 @@ public slots:
void preloadHistoryIfNeeded();
private slots:
void onSend(bool ctrlShiftEnter = false);
void onSend();
void onHashtagOrBotCommandInsert(QString str, FieldAutocomplete::ChooseMethod method);
void onMentionInsert(UserData *user);
@@ -461,6 +461,7 @@ private:
void setMembersShowAreaActive(bool active);
void forwardItems(MessageIdsList &&items);
void handleHistoryChange(not_null<const History*> history);
void refreshAboutProxyPromotion();
void highlightMessage(MsgId universalMessageId);
void adjustHighlightedMessageToMigrated();
@@ -489,6 +490,7 @@ private:
void unreadMentionsAnimationFinish();
void sendButtonClicked();
bool canSendFiles(not_null<const QMimeData*> data) const;
bool confirmSendingFiles(
const QStringList &files,
CompressConfirm compressed,
@@ -499,7 +501,7 @@ private:
CompressConfirm compressed,
const QString &insertTextOnCancel = QString());
bool confirmSendingFiles(
const QMimeData *data,
not_null<const QMimeData*> data,
CompressConfirm compressed,
const QString &insertTextOnCancel = QString());
bool confirmSendingFiles(
@@ -514,7 +516,7 @@ private:
void uploadFilesAfterConfirmation(
Storage::PreparedList &&list,
SendMediaType type,
QString caption,
TextWithTags &&caption,
MsgId replyTo,
std::shared_ptr<SendingAlbum> album = nullptr);
@@ -606,14 +608,20 @@ private:
void saveEditMsgDone(History *history, const MTPUpdates &updates, mtpRequestId req);
bool saveEditMsgFail(History *history, const RPCError &error, mtpRequestId req);
static const mtpRequestId ReportSpamRequestNeeded = -1;
DBIPeerReportSpamStatus _reportSpamStatus = dbiprsUnknown;
mtpRequestId _reportSpamSettingRequestId = ReportSpamRequestNeeded;
void updateReportSpamStatus();
void requestReportSpamSetting();
void reportSpamSettingDone(const MTPPeerSettings &result, mtpRequestId req);
bool reportSpamSettingFail(const RPCError &error, mtpRequestId req);
void checkPreview();
void requestPreview();
void gotPreview(QString links, const MTPMessageMedia &media, mtpRequestId req);
static const mtpRequestId ReportSpamRequestNeeded = -1;
DBIPeerReportSpamStatus _reportSpamStatus = dbiprsUnknown;
mtpRequestId _reportSpamSettingRequestId = ReportSpamRequestNeeded;
QStringList _parsedLinks;
QString _previewLinks;
WebPageData *_previewData = nullptr;
typedef QMap<QString, WebPageId> PreviewCache;
@@ -621,9 +629,8 @@ private:
mtpRequestId _previewRequest = 0;
Text _previewTitle;
Text _previewDescription;
SingleTimer _previewTimer;
base::Timer _previewTimer;
bool _previewCancelled = false;
void gotPreview(QString links, const MTPMessageMedia &media, mtpRequestId req);
bool _replyForwardPressed = false;
@@ -694,10 +701,13 @@ private:
void writeDrafts(Data::Draft **localDraft, Data::Draft **editDraft);
void writeDrafts(History *history);
void setFieldText(const TextWithTags &textWithTags, TextUpdateEvents events = 0, Ui::FlatTextarea::UndoHistoryAction undoHistoryAction = Ui::FlatTextarea::ClearUndoHistory);
void clearFieldText(TextUpdateEvents events = 0, Ui::FlatTextarea::UndoHistoryAction undoHistoryAction = Ui::FlatTextarea::ClearUndoHistory) {
setFieldText(TextWithTags(), events, undoHistoryAction);
}
void setFieldText(
const TextWithTags &textWithTags,
TextUpdateEvents events = 0,
FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear);
void clearFieldText(
TextUpdateEvents events = 0,
FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear);
HistoryItem *getItemFromHistoryOrMigrated(MsgId genericMsgId) const;
void animatedScrollToItem(MsgId msgId);
@@ -758,9 +768,11 @@ private:
object_ptr<Ui::HistoryDownButton> _unreadMentions;
object_ptr<FieldAutocomplete> _fieldAutocomplete;
std::unique_ptr<MessageLinksParser> _fieldLinksParser;
UserData *_inlineBot = nullptr;
QString _inlineBotUsername;
bool _inlineLookingUpBot = false;
mtpRequestId _inlineBotResolveRequestId = 0;
bool _isInlineBot = false;
void inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result);
@@ -783,6 +795,7 @@ private:
object_ptr<Ui::FlatButton> _botStart;
object_ptr<Ui::FlatButton> _joinChannel;
object_ptr<Ui::FlatButton> _muteUnmute;
object_ptr<Ui::RpWidget> _aboutProxyPromotion = { nullptr };
mtpRequestId _unblockRequest = 0;
mtpRequestId _reportSpamRequest = 0;
object_ptr<Ui::IconButton> _attachToggle;
@@ -794,7 +807,7 @@ private:
object_ptr<Ui::IconButton> _botCommandStart;
object_ptr<Ui::SilentToggle> _silent = { nullptr };
bool _cmdStartShown = false;
object_ptr<MessageField> _field;
object_ptr<Ui::InputField> _field;
bool _recording = false;
bool _inField = false;
bool _inReplyEditForward = false;

View File

@@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/unread_badge.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/dropdown_menu.h"
#include "ui/effects/radial_animation.h"
#include "window/window_controller.h"
#include "window/window_peer_menu.h"
#include "calls/calls_instance.h"
@@ -124,10 +125,38 @@ TopBarWidget::TopBarWidget(
[this] { updateInfoToggleActive(); },
lifetime());
rpl::single(rpl::empty_value()) | rpl::then(
base::ObservableViewer(Global::RefConnectionTypeChanged())
) | rpl::start_with_next(
[=] { updateConnectingState(); },
lifetime());
setCursor(style::cur_pointer);
updateControlsVisibility();
}
void TopBarWidget::updateConnectingState() {
const auto mtp = MTP::dcstate();
if (mtp == MTP::ConnectedState) {
if (_connecting) {
_connecting = nullptr;
update();
}
} else if (!_connecting) {
_connecting = std::make_unique<Ui::InfiniteRadialAnimation>(
animation(this, &TopBarWidget::step_connecting),
st::topBarConnectingAnimation);
_connecting->start();
update();
}
}
void TopBarWidget::step_connecting(TimeMs ms, bool timer) {
if (timer) {
update();
}
}
void TopBarWidget::refreshLang() {
InvokeQueued(this, [this] { updateControlsGeometry(); });
}
@@ -300,24 +329,71 @@ void TopBarWidget::paintTopBar(Painter &p, TimeMs ms) {
history->peer->dialogName().drawElided(p, nameleft, nametop, namewidth);
p.setFont(st::dialogsTextFont);
if (!history->paintSendAction(p, nameleft, statustop, namewidth, width(), st::historyStatusFgTyping, ms)) {
auto statustext = _titlePeerText;
auto statuswidth = _titlePeerTextWidth;
if (statuswidth > namewidth) {
statustext = st::dialogsTextFont->elided(
statustext,
namewidth,
Qt::ElideLeft);
statuswidth = st::dialogsTextFont->width(statustext);
}
p.setPen(_titlePeerTextOnline
? st::historyStatusFgActive
: st::historyStatusFg);
p.drawTextLeft(nameleft, statustop, width(), statustext, statuswidth);
if (paintConnectingState(p, nameleft, statustop, width(), ms)) {
return;
} else if (history->paintSendAction(
p,
nameleft,
statustop,
namewidth,
width(),
st::historyStatusFgTyping,
ms)) {
return;
} else {
paintStatus(p, nameleft, statustop, namewidth, width());
}
}
}
bool TopBarWidget::paintConnectingState(
Painter &p,
int left,
int top,
int outerWidth,
TimeMs ms) {
if (_connecting) {
_connecting->step(ms);
}
if (!_connecting) {
return false;
}
_connecting->draw(
p,
{
st::topBarConnectingPosition.x() + left,
st::topBarConnectingPosition.y() + top
},
outerWidth);
left += st::topBarConnectingPosition.x()
+ st::topBarConnectingAnimation.size.width()
+ st::topBarConnectingSkip;
p.setPen(st::historyStatusFg);
p.drawTextLeft(left, top, outerWidth, lang(lng_status_connecting));
return true;
}
void TopBarWidget::paintStatus(
Painter &p,
int left,
int top,
int availableWidth,
int outerWidth) {
auto statustext = _titlePeerText;
auto statuswidth = _titlePeerTextWidth;
if (statuswidth > availableWidth) {
statustext = st::dialogsTextFont->elided(
statustext,
availableWidth,
Qt::ElideLeft);
statuswidth = st::dialogsTextFont->width(statustext);
}
p.setPen(_titlePeerTextOnline
? st::historyStatusFgActive
: st::historyStatusFg);
p.drawTextLeft(left, top, outerWidth, statustext, statuswidth);
}
QRect TopBarWidget::getMembersShowAreaGeometry() const {
int membersTextLeft = _leftTaken;
int membersTextTop = st::topBarHeight - st::topBarArrowPadding.bottom() - st::dialogsTextFont->height;
@@ -765,4 +841,6 @@ void TopBarWidget::updateOnlineDisplayIn(TimeMs timeout) {
_onlineUpdater.callOnce(timeout);
}
TopBarWidget::~TopBarWidget() = default;
} // namespace HistoryView

View File

@@ -17,6 +17,7 @@ class RoundButton;
class IconButton;
class DropdownMenu;
class UnreadBadge;
class InfiniteRadialAnimation;
} // namespace Ui
namespace Window {
@@ -38,6 +39,8 @@ public:
int canForwardCount = 0;
};
~TopBarWidget();
void updateControlsVisibility();
void finishAnimating();
void showSelected(SelectedState state);
@@ -78,10 +81,24 @@ private:
void showMenu();
void toggleInfoSection();
void updateConnectingState();
void updateAdaptiveLayout();
int countSelectedButtonsTop(float64 selectedShown);
void step_connecting(TimeMs ms, bool timer);
void paintTopBar(Painter &p, TimeMs ms);
void paintStatus(
Painter &p,
int left,
int top,
int availableWidth,
int outerWidth);
bool paintConnectingState(
Painter &p,
int left,
int top,
int outerWidth,
TimeMs ms);
QRect getMembersShowAreaGeometry() const;
void updateMembersShowArea();
void updateOnlineDisplay();
@@ -125,6 +142,7 @@ private:
int _leftTaken = 0;
int _rightTaken = 0;
bool _animatingMode = false;
std::unique_ptr<Ui::InfiniteRadialAnimation> _connecting;
int _unreadCounterSubscription = 0;
base::Timer _onlineUpdater;

View File

@@ -704,5 +704,12 @@ topBarFeedInfoButton: FeedUserpicButton(defaultFeedUserpicButton) {
photoSize: 20px;
}
}
topBarConnectingPosition: point(2px, 5px);
topBarConnectingSkip: 6px;
topBarConnectingAnimation: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {
color: windowSubTextFg;
thickness: 1px;
size: size(8px, 8px);
}
infoFeedLeaveIconMargins: margins(10px, 12px, 20px, 10px);

View File

@@ -7,9 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "info/info_wrap_widget.h"
#include <rpl/flatten_latest.h>
#include <rpl/take.h>
#include <rpl/combine.h>
#include "info/profile/info_profile_widget.h"
#include "info/profile/info_profile_values.h"
#include "info/media/info_media_widget.h"

View File

@@ -248,21 +248,11 @@ Cover::Cover(
void Cover::setupChildGeometry() {
using namespace rpl::mappers;
//
// Visual Studio 2017 15.5.1 internal compiler error here.
// See https://developercommunity.visualstudio.com/content/problem/165155/ice-regression-in-1551-after-successfull-build-in.html
//
//rpl::combine(
// toggleShownValue(),
// widthValue(),
// _2
//) | rpl::map([](bool shown, int width) {
rpl::combine(
toggleShownValue(),
widthValue()
) | rpl::map([](bool shown, int width) {
return width;
}) | rpl::start_with_next([this](int newWidth) {
widthValue(),
_2
) | rpl::start_with_next([this](int newWidth) {
_userpic->moveToLeft(
st::infoProfilePhotoLeft,
st::infoProfilePhotoTop,
@@ -453,21 +443,12 @@ void SharedMediaCover::createLabel() {
Lang::Viewer(lng_profile_shared_media) | ToUpperValue(),
st::infoBlockHeaderLabel);
label->setAttribute(Qt::WA_TransparentForMouseEvents);
//
// Visual Studio 2017 15.5.1 internal compiler error here.
// See https://developercommunity.visualstudio.com/content/problem/165155/ice-regression-in-1551-after-successfull-build-in.html
//
//rpl::combine(
// toggleShownValue(),
// widthValue(),
// _2
//) | rpl::map([](bool shown, int width) {
rpl::combine(
toggleShownValue(),
widthValue()
) | rpl::map([](bool shown, int width) {
return width;
}) | rpl::start_with_next([this, weak = label.data()](int newWidth) {
widthValue(),
_2
) | rpl::start_with_next([this, weak = label.data()](int newWidth) {
auto availableWidth = newWidth
- st::infoBlockHeaderPosition.x()
- st::infoSharedMediaButton.padding.right()

View File

@@ -26,5 +26,9 @@ inline QString ClearPathFailed() {
return qsl("Clear failed :(");
}
inline QString ProxyConfigError() {
return qsl("The proxy you are using is not configured correctly and will be disabled. Please find another one.");
}
} // namespace Hard
} // namespace Lang

View File

@@ -3717,8 +3717,11 @@ void MainWidget::start(const MTPUser *self) {
if (!self) {
MTP::send(MTPusers_GetFullUser(MTP_inputUserSelf()), rpcDone(&MainWidget::startWithSelf));
return;
}
if (!Auth().validateSelf(*self)) {
} else if (!Auth().validateSelf(*self)) {
constexpr auto kRequestUserAgainTimeout = TimeMs(10000);
App::CallDelayed(kRequestUserAgainTimeout, this, [=] {
MTP::send(MTPusers_GetFullUser(MTP_inputUserSelf()), rpcDone(&MainWidget::startWithSelf));
});
return;
}
@@ -5092,11 +5095,9 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
auto &d = update.c_updateChannel();
if (const auto channel = App::channelLoaded(d.vchannel_id.v)) {
channel->inviter = UserId(0);
if (!channel->amIn()) {
if (const auto history = App::historyLoaded(channel->id)) {
history->updateChatListExistence();
}
} else if (!channel->amCreator() && App::history(channel->id)) {
if (channel->amIn()
&& !channel->amCreator()
&& App::history(channel->id)) {
_updatedChannels.insert(channel, true);
Auth().api().requestSelfParticipant(channel);
}

View File

@@ -149,6 +149,7 @@ mediaviewSaveMsgStyle: TextStyle(defaultTextStyle) {
}
mediaviewTextPalette: TextPalette(defaultTextPalette) {
linkFg: mediaviewTextLinkFg;
monoFg: mediaviewCaptionFg;
}
mediaviewCaptionStyle: defaultTextStyle;

View File

@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_file_parser.h"
#include "lang/lang_translator.h"
#include "lang/lang_cloud_manager.h"
#include "lang/lang_hardcoded.h"
#include "observer_peer.h"
#include "storage/file_upload.h"
#include "mainwidget.h"
@@ -44,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/qthelp_url.h"
#include "boxes/connection_box.h"
#include "boxes/confirm_phone_box.h"
#include "boxes/confirm_box.h"
#include "boxes/share_box.h"
namespace {
@@ -278,13 +280,50 @@ bool Messenger::eventFilter(QObject *object, QEvent *e) {
return QObject::eventFilter(object, e);
}
void Messenger::setCurrentProxy(
const ProxyData &proxy,
bool enabled) {
const auto key = [&](const ProxyData &proxy) {
if (proxy.type == ProxyData::Type::Mtproto) {
return std::make_pair(proxy.host, proxy.port);
}
return std::make_pair(QString(), uint32(0));
};
const auto previousKey = key(Global::UseProxy()
? Global::SelectedProxy()
: ProxyData());
Global::SetSelectedProxy(proxy);
Global::SetUseProxy(enabled);
Sandbox::refreshGlobalProxy();
if (_mtproto) {
_mtproto->restart();
if (previousKey != key(proxy)) {
_mtproto->reInitConnection(_mtproto->mainDcId());
}
}
if (_mtprotoForKeysDestroy) {
_mtprotoForKeysDestroy->restart();
}
Global::RefConnectionTypeChanged().notify();
}
void Messenger::badMtprotoConfigurationError() {
if (Global::UseProxy() && !_badProxyDisableBox) {
_badProxyDisableBox = Ui::show(Box<InformBox>(
Lang::Hard::ProxyConfigError(),
[=] { setCurrentProxy(Global::SelectedProxy(), false); }));
}
}
void Messenger::setMtpMainDcId(MTP::DcId mainDcId) {
Expects(!_mtproto);
_private->mtpConfig.mainDcId = mainDcId;
}
void Messenger::setMtpKey(MTP::DcId dcId, const MTP::AuthKey::Data &keyData) {
Expects(!_mtproto);
_private->mtpConfig.keys.push_back(std::make_shared<MTP::AuthKey>(MTP::AuthKey::Type::ReadFromFile, dcId, keyData));
}
@@ -856,11 +895,11 @@ bool Messenger::openLocalUrl(const QString &url) {
}
} else if (auto socksMatch = regex_match(qsl("^socks/?\\?(.+)(#|$)"), command, matchOptions)) {
auto params = url_parse_params(socksMatch->captured(1), UrlParamNameTransform::ToLower);
ConnectionBox::ShowApplyProxyConfirmation(ProxyData::Type::Socks5, params);
ProxiesBoxController::ShowApplyConfirmation(ProxyData::Type::Socks5, params);
return true;
} else if (auto proxyMatch = regex_match(qsl("^proxy/?\\?(.+)(#|$)"), command, matchOptions)) {
auto params = url_parse_params(proxyMatch->captured(1), UrlParamNameTransform::ToLower);
ConnectionBox::ShowApplyProxyConfirmation(ProxyData::Type::Mtproto, params);
ProxiesBoxController::ShowApplyConfirmation(ProxyData::Type::Mtproto, params);
return true;
}
return false;

View File

@@ -17,6 +17,7 @@ class MainWidget;
class FileUploader;
class Translator;
class MediaView;
class BoxContent;
namespace Core {
class Launcher;
@@ -98,6 +99,8 @@ public:
MTP::DcOptions *dcOptions() {
return _dcOptions.get();
}
void setCurrentProxy(const ProxyData &proxy, bool enabled);
void badMtprotoConfigurationError();
// Set from legacy storage.
void setMtpMainDcId(MTP::DcId mainDcId);
@@ -239,6 +242,7 @@ private:
std::unique_ptr<AuthSession> _authSession;
base::Observable<void> _authSessionChanged;
base::Observable<void> _passcodedChanged;
QPointer<BoxContent> _badProxyDisableBox;
std::unique_ptr<Media::Audio::Instance> _audio;
QImage _logo;

View File

@@ -39,6 +39,7 @@ constexpr auto kMarkConnectionOldTimeout = TimeMs(192000);
constexpr auto kPingDelayDisconnect = 60;
constexpr auto kPingSendAfter = TimeMs(30000);
constexpr auto kPingSendAfterForce = TimeMs(45000);
constexpr auto kTestModeDcIdShift = 10000;
// If we can't connect for this time we will ask _instance to update config.
constexpr auto kRequestConfigTimeout = TimeMs(8000);
@@ -55,35 +56,6 @@ QString LogIdsVector(const QVector<MTPlong> &ids) {
return idsStr + "]";
}
bytes::vector ProtocolSecretFromPassword(const QString &password) {
const auto size = password.size();
if (size % 2) {
return {};
}
const auto length = size / 2;
const auto fromHex = [](QChar ch) -> int {
const auto code = int(ch.unicode());
if (code >= '0' && code <= '9') {
return (code - '0');
} else if (code >= 'A' && code <= 'F') {
return 10 + (code - 'A');
} else if (ch >= 'a' && ch <= 'f') {
return 10 + (code - 'a');
}
return -1;
};
auto result = bytes::vector(length);
for (auto i = 0; i != length; ++i) {
const auto high = fromHex(password[2 * i]);
const auto low = fromHex(password[2 * i + 1]);
if (high < 0 || low < 0) {
return {};
}
result[i] = static_cast<gsl::byte>(high * 16 + low);
}
return result;
}
bool IsGoodModExpFirst(const openssl::BigNum &modexp, const openssl::BigNum &prime) {
auto diff = prime - modexp;
if (modexp.failed() || prime.failed() || diff.failed()) {
@@ -365,7 +337,11 @@ void ConnectionPrivate::appendTestConnection(
+ (protocol == DcOptions::Variants::Tcp ? 1 : 0)
+ (protocolSecret.empty() ? 0 : 1);
_testConnections.push_back({
AbstractConnection::create(protocol, thread()),
AbstractConnection::Create(
_instance,
protocol,
thread(),
_connectionOptions->proxy),
priority
});
auto weak = _testConnections.back().data.get();
@@ -388,16 +364,22 @@ void ConnectionPrivate::appendTestConnection(
onDisconnected(weak);
});
InvokeQueued(_testConnections.back().data, [=] {
weak->connectToServer(ip, port, protocolSecret, getProtocolDcId());
});
}
int16 ConnectionPrivate::getProtocolDcId() const {
const auto dcId = MTP::bareDcId(_shiftedDcId);
const auto simpleDcId = MTP::isTemporaryDcId(dcId)
? MTP::getRealIdFromTemporaryDcId(dcId)
: dcId;
const auto protocolDcId = (_dcType == DcType::MediaDownload)
? -simpleDcId
const auto testedDcId = cTestMode()
? (kTestModeDcIdShift + simpleDcId)
: simpleDcId;
InvokeQueued(_testConnections.back().data, [=] {
weak->connectToServer(ip, port, protocolSecret, protocolDcId);
});
return (_dcType == DcType::MediaDownload)
? -testedDcId
: testedDcId;
}
void ConnectionPrivate::destroyAllConnections() {
@@ -732,7 +714,7 @@ void ConnectionPrivate::tryToSend() {
return;
}
bool needsLayer = !sessionData->layerWasInited();
bool needsLayer = !_connectionOptions->inited;
int32 state = getState();
bool prependOnly = (state != ConnectedState);
mtpRequest pingRequest;
@@ -835,12 +817,34 @@ void ConnectionPrivate::tryToSend() {
int32 initSize = 0, initSizeInInts = 0;
if (needsLayer) {
Assert(_connectionOptions != nullptr);
auto systemLangCode = _connectionOptions->systemLangCode;
auto cloudLangCode = _connectionOptions->cloudLangCode;
auto langPack = "tdesktop";
auto deviceModel = (_dcType == DcType::Cdn) ? "n/a" : cApiDeviceModel();
auto systemVersion = (_dcType == DcType::Cdn) ? "n/a" : cApiSystemVersion();
initWrapper = MTPInitConnection<mtpRequest>(MTP_int(ApiId), MTP_string(deviceModel), MTP_string(systemVersion), MTP_string(str_const_toString(AppVersionStr)), MTP_string(systemLangCode), MTP_string(langPack), MTP_string(cloudLangCode), mtpRequest());
const auto systemLangCode = _connectionOptions->systemLangCode;
const auto cloudLangCode = _connectionOptions->cloudLangCode;
const auto langPack = "tdesktop";
const auto deviceModel = (_dcType == DcType::Cdn)
? "n/a"
: cApiDeviceModel();
const auto systemVersion = (_dcType == DcType::Cdn)
? "n/a"
: cApiSystemVersion();
const auto proxyType = _connectionOptions->proxy.type;
const auto mtprotoProxy = (proxyType == ProxyData::Type::Mtproto);
const auto clientProxyFields = mtprotoProxy
? MTP_inputClientProxy(
MTP_string(_connectionOptions->proxy.host),
MTP_int(_connectionOptions->proxy.port))
: MTPInputClientProxy();
using Flag = MTPInitConnection<mtpRequest>::Flag;
initWrapper = MTPInitConnection<mtpRequest>(
MTP_flags(mtprotoProxy ? Flag::f_proxy : Flag(0)),
MTP_int(ApiId),
MTP_string(deviceModel),
MTP_string(systemVersion),
MTP_string(str_const_toString(AppVersionStr)),
MTP_string(systemLangCode),
MTP_string(langPack),
MTP_string(cloudLangCode),
clientProxyFields,
mtpRequest());
initSizeInInts = (initWrapper.innerLength() >> 2) + 2;
initSize = initSizeInInts * sizeof(mtpPrime);
}
@@ -1083,11 +1087,8 @@ void ConnectionPrivate::connectToServer(bool afterConfig) {
destroyAllConnections();
if (_connectionOptions->proxy.type == ProxyData::Type::Mtproto) {
appendTestConnection(
DcOptions::Variants::Tcp,
_connectionOptions->proxy.host,
_connectionOptions->proxy.port,
ProtocolSecretFromPassword(_connectionOptions->proxy.password));
// host, port, secret for mtproto proxy are taken from proxy.
appendTestConnection(DcOptions::Variants::Tcp, {}, 0, {});
} else {
using Variants = DcOptions::Variants;
const auto special = (_dcType == DcType::Temporary);
@@ -1230,11 +1231,11 @@ void ConnectionPrivate::onReceivedSome() {
_oldConnectionTimer.callOnce(kMarkConnectionOldTimeout);
_waitForReceivedTimer.cancel();
if (firstSentAt > 0) {
int32 ms = getms(true) - firstSentAt;
const auto ms = getms(true) - firstSentAt;
DEBUG_LOG(("MTP Info: response in %1ms, _waitForReceived: %2ms").arg(ms).arg(_waitForReceived));
if (ms > 0 && ms * 2 < int32(_waitForReceived)) {
_waitForReceived = qMax(ms * 2, int32(kMinReceiveTimeout));
if (ms > 0 && ms * 2 < _waitForReceived) {
_waitForReceived = qMax(ms * 2, kMinReceiveTimeout);
}
firstSentAt = -1;
}
@@ -1285,20 +1286,24 @@ void ConnectionPrivate::waitReceivedFailed() {
}
DEBUG_LOG(("MTP Info: immediate restart!"));
InvokeQueued(this, [this] { connectToServer(); });
InvokeQueued(this, [=] { connectToServer(); });
}
void ConnectionPrivate::waitConnectedFailed() {
DEBUG_LOG(("MTP Info: can't connect in %1ms").arg(_waitForConnected));
if (_waitForConnected < kMaxConnectedTimeout) {
_waitForConnected *= 2;
auto maxTimeout = kMaxConnectedTimeout;
for (const auto &connection : _testConnections) {
accumulate_max(maxTimeout, connection.data->fullConnectTimeout());
}
if (_waitForConnected < maxTimeout) {
_waitForConnected = std::min(maxTimeout, 2 * _waitForConnected);
}
doDisconnect();
restarted = true;
DEBUG_LOG(("MTP Info: immediate restart!"));
InvokeQueued(this, [this] { connectToServer(); });
InvokeQueued(this, [=] { connectToServer(); });
}
void ConnectionPrivate::waitBetterFailed() {
@@ -1329,8 +1334,15 @@ void ConnectionPrivate::finishAndDestroy() {
}
void ConnectionPrivate::requestCDNConfig() {
connect(_instance, SIGNAL(cdnConfigLoaded()), this, SLOT(onCDNConfigLoaded()), Qt::UniqueConnection);
InvokeQueued(_instance, [instance = _instance] { instance->requestCDNConfig(); });
connect(
_instance,
SIGNAL(cdnConfigLoaded()),
this,
SLOT(onCDNConfigLoaded()),
Qt::UniqueConnection);
InvokeQueued(_instance, [instance = _instance] {
instance->requestCDNConfig();
});
}
void ConnectionPrivate::handleReceived() {
@@ -2016,9 +2028,9 @@ ConnectionPrivate::HandleResult ConnectionPrivate::handleOneReceived(const mtpPr
// An error could be some RPC_CALL_FAIL or other error inside
// the initConnection, so we're not sure yet that it was inited.
// Wait till a good response is received.
if (!sessionData->layerWasInited()) {
sessionData->setLayerWasInited(true);
sessionData->owner()->notifyLayerInited(true);
if (!_connectionOptions->inited) {
_connectionOptions->inited = true;
sessionData->notifyConnectionInited(*_connectionOptions);
}
}
@@ -2414,10 +2426,8 @@ void ConnectionPrivate::onDisconnected(
removeTestConnection(connection);
if (_testConnections.empty()) {
if (!_connection || _connection == connection.get()) {
destroyAllConnections();
restart();
}
destroyAllConnections();
restart();
} else {
confirmBestConnection();
}
@@ -2562,7 +2572,14 @@ void ConnectionPrivate::pqAnswered() {
return restart();
}
auto p_q_inner = MTP_p_q_inner_data(res_pq_data.vpq, MTP_bytes(std::move(p)), MTP_bytes(std::move(q)), _authKeyData->nonce, _authKeyData->server_nonce, _authKeyData->new_nonce);
auto p_q_inner = MTP_p_q_inner_data_dc(
res_pq_data.vpq,
MTP_bytes(std::move(p)),
MTP_bytes(std::move(q)),
_authKeyData->nonce,
_authKeyData->server_nonce,
_authKeyData->new_nonce,
MTP_int(getProtocolDcId()));
auto dhEncString = encryptPQInnerRSA(p_q_inner, rsaKey);
if (dhEncString.empty()) {
return restart();
@@ -2578,8 +2595,8 @@ void ConnectionPrivate::pqAnswered() {
req_DH_params.vnonce = _authKeyData->nonce;
req_DH_params.vserver_nonce = _authKeyData->server_nonce;
req_DH_params.vpublic_key_fingerprint = MTP_long(rsaKey.getFingerPrint());
req_DH_params.vp = p_q_inner.c_p_q_inner_data().vp;
req_DH_params.vq = p_q_inner.c_p_q_inner_data().vq;
req_DH_params.vp = p_q_inner.c_p_q_inner_data_dc().vp;
req_DH_params.vq = p_q_inner.c_p_q_inner_data_dc().vq;
req_DH_params.vencrypted_data = MTP_bytes(dhEncString);
sendRequestNotSecure(req_DH_params);
}
@@ -2964,19 +2981,18 @@ void ConnectionPrivate::clearAuthKeyData() {
void ConnectionPrivate::onError(
not_null<AbstractConnection*> connection,
qint32 errorCode) {
if (_connection) {
return;
}
if (errorCode == -429) {
LOG(("Protocol Error: -429 flood code returned!"));
} else if (errorCode == -444) {
LOG(("Protocol Error: -444 bad dc_id code returned!"));
InvokeQueued(_instance, [instance = _instance] {
instance->badConfigurationError();
});
}
removeTestConnection(connection);
if (_testConnections.empty()) {
if (!_connection || _connection == connection.get()) {
handleError(errorCode);
}
handleError(errorCode);
} else {
confirmBestConnection();
}
@@ -3218,8 +3234,4 @@ std::vector<gsl::byte> CreateAuthKey(base::const_byte_span firstBytes, base::con
return internal::CreateAuthKey(firstBytes, randomBytes, primeBytes);
}
bytes::vector ProtocolSecretFromPassword(const QString &password) {
return internal::ProtocolSecretFromPassword(password);
}
} // namespace MTP

View File

@@ -26,8 +26,6 @@ struct ModExpFirst {
ModExpFirst CreateModExp(int g, base::const_byte_span primeBytes, base::const_byte_span randomSeed);
std::vector<gsl::byte> CreateAuthKey(base::const_byte_span firstBytes, base::const_byte_span randomBytes, base::const_byte_span primeBytes);
bytes::vector ProtocolSecretFromPassword(const QString &password);
namespace internal {
class AbstractConnection;
@@ -169,6 +167,7 @@ private:
void destroyAllConnections();
void confirmBestConnection();
void removeTestConnection(not_null<AbstractConnection*> connection);
int16 getProtocolDcId() const;
mtpMsgId placeToContainer(mtpRequest &toSendRequest, mtpMsgId &bigMsgId, mtpMsgId *&haveSentArr, mtpRequest &req);
mtpMsgId prepareToSend(mtpRequest &request, mtpMsgId currentLastId);
@@ -214,7 +213,7 @@ private:
template <typename TResponse>
bool readResponseNotSecure(TResponse &response);
Instance *_instance = nullptr;
not_null<Instance*> _instance;
DcType _dcType = DcType::Regular;
mutable QReadWriteLock stateConnMutex;
@@ -239,8 +238,8 @@ private:
base::Timer _waitForConnectedTimer;
base::Timer _waitForReceivedTimer;
base::Timer _waitForBetterTimer;
uint32 _waitForReceived = 0;
uint32 _waitForConnected = 0;
TimeMs _waitForReceived = 0;
TimeMs _waitForConnected = 0;
TimeMs firstSentAt = -1;
QVector<MTPlong> ackRequestData, resendRequestData;

View File

@@ -9,10 +9,43 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/connection_tcp.h"
#include "mtproto/connection_http.h"
#include "mtproto/connection_resolving.h"
#include "mtproto/session.h"
namespace MTP {
namespace internal {
namespace {
bytes::vector ProtocolSecretFromPassword(const QString &password) {
const auto size = password.size();
if (size % 2) {
return {};
}
const auto length = size / 2;
const auto fromHex = [](QChar ch) -> int {
const auto code = int(ch.unicode());
if (code >= '0' && code <= '9') {
return (code - '0');
} else if (code >= 'A' && code <= 'F') {
return 10 + (code - 'A');
} else if (ch >= 'a' && ch <= 'f') {
return 10 + (code - 'a');
}
return -1;
};
auto result = bytes::vector(length);
for (auto i = 0; i != length; ++i) {
const auto high = fromHex(password[2 * i]);
const auto low = fromHex(password[2 * i + 1]);
if (high < 0 || low < 0) {
return {};
}
result[i] = static_cast<gsl::byte>(high * 16 + low);
}
return result;
}
} // namespace
ConnectionPointer::ConnectionPointer() = default;
@@ -122,19 +155,39 @@ MTPResPQ AbstractConnection::readPQFakeReply(const mtpBuffer &buffer) {
return response;
}
AbstractConnection::AbstractConnection(QThread *thread) {
AbstractConnection::AbstractConnection(
QThread *thread,
const ProxyData &proxy)
: _proxy(proxy) {
moveToThread(thread);
}
ConnectionPointer AbstractConnection::create(
ConnectionPointer AbstractConnection::Create(
not_null<Instance*> instance,
DcOptions::Variants::Protocol protocol,
QThread *thread) {
if (protocol == DcOptions::Variants::Tcp) {
return ConnectionPointer(new TCPConnection(thread));
} else {
return ConnectionPointer(new HTTPConnection(thread));
QThread *thread,
const ProxyData &proxy) {
auto result = [&] {
if (protocol == DcOptions::Variants::Tcp) {
return ConnectionPointer::New<TcpConnection>(thread, proxy);
} else {
return ConnectionPointer::New<HttpConnection>(thread, proxy);
}
}();
if (proxy.tryCustomResolve()) {
return ConnectionPointer::New<ResolvingConnection>(
instance,
thread,
proxy,
std::move(result));
}
return result;
}
} // namespace internal
bytes::vector ProtocolSecretFromPassword(const QString &password) {
return internal::ProtocolSecretFromPassword(password);
}
} // namespace MTP

View File

@@ -11,6 +11,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/bytes.h"
namespace MTP {
bytes::vector ProtocolSecretFromPassword(const QString &password);
namespace internal {
struct ConnectionOptions;
@@ -21,10 +24,16 @@ class ConnectionPointer {
public:
ConnectionPointer();
ConnectionPointer(std::nullptr_t);
explicit ConnectionPointer(AbstractConnection *value);
ConnectionPointer(ConnectionPointer &&other);
ConnectionPointer &operator=(ConnectionPointer &&other);
template <typename ConnectionType, typename ...Args>
static ConnectionPointer New(Args &&...args) {
return ConnectionPointer(new ConnectionType(
std::forward<Args>(args)...
));
}
AbstractConnection *get() const;
void reset(AbstractConnection *value = nullptr);
operator AbstractConnection*() const;
@@ -35,6 +44,8 @@ public:
~ConnectionPointer();
private:
explicit ConnectionPointer(AbstractConnection *value);
AbstractConnection *_value = nullptr;
};
@@ -43,22 +54,24 @@ class AbstractConnection : public QObject {
Q_OBJECT
public:
AbstractConnection(QThread *thread);
AbstractConnection(
QThread *thread,
const ProxyData &proxy);
AbstractConnection(const AbstractConnection &other) = delete;
AbstractConnection &operator=(const AbstractConnection &other) = delete;
virtual ~AbstractConnection() = 0;
// virtual constructor
static ConnectionPointer create(
static ConnectionPointer Create(
not_null<Instance*> instance,
DcOptions::Variants::Protocol protocol,
QThread *thread);
QThread *thread,
const ProxyData &proxy);
void setSentEncrypted() {
_sentEncrypted = true;
}
virtual ConnectionPointer clone(const ProxyData &proxy) = 0;
virtual void setProxyOverride(const ProxyData &proxy) = 0;
virtual TimeMs pingTime() const = 0;
virtual TimeMs fullConnectTimeout() const = 0;
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(
@@ -79,6 +92,10 @@ public:
virtual QString transport() const = 0;
virtual QString tag() const = 0;
void setSentEncrypted() {
_sentEncrypted = true;
}
using BuffersQueue = std::deque<mtpBuffer>;
BuffersQueue &received() {
return _receivedQueue;
@@ -100,6 +117,7 @@ protected:
BuffersQueue _receivedQueue; // list of received packets, not processed yet
bool _sentEncrypted = false;
int _pingTime = 0;
ProxyData _proxy;
// first we always send fake MTPReq_pq to see if connection works at all
// we send them simultaneously through TCP/HTTP/IPv4/IPv6 to choose the working one

View File

@@ -14,10 +14,80 @@ namespace internal {
namespace {
constexpr auto kForceHttpPort = 80;
constexpr auto kFullConnectionTimeout = TimeMs(8000);
} // namespace
mtpBuffer HTTPConnection::handleResponse(QNetworkReply *reply) {
HttpConnection::HttpConnection(QThread *thread, const ProxyData &proxy)
: AbstractConnection(thread, proxy)
, _checkNonce(rand_value<MTPint128>()) {
_manager.moveToThread(thread);
_manager.setProxy(ToNetworkProxy(proxy));
}
ConnectionPointer HttpConnection::clone(const ProxyData &proxy) {
return ConnectionPointer::New<HttpConnection>(thread(), proxy);
}
void HttpConnection::sendData(mtpBuffer &buffer) {
if (_status == Status::Finished) return;
if (buffer.size() < 3) {
LOG(("TCP Error: writing bad packet, len = %1").arg(buffer.size() * sizeof(mtpPrime)));
TCP_LOG(("TCP Error: bad packet %1").arg(Logs::mb(&buffer[0], buffer.size() * sizeof(mtpPrime)).str()));
emit error(kErrorCodeOther);
return;
}
int32 requestSize = (buffer.size() - 3) * sizeof(mtpPrime);
QNetworkRequest request(url());
request.setHeader(QNetworkRequest::ContentLengthHeader, QVariant(requestSize));
request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(qsl("application/x-www-form-urlencoded")));
TCP_LOG(("HTTP Info: sending %1 len request").arg(requestSize));
_requests.insert(_manager.post(request, QByteArray((const char*)(&buffer[2]), requestSize)));
}
void HttpConnection::disconnectFromServer() {
if (_status == Status::Finished) return;
_status = Status::Finished;
for (const auto request : base::take(_requests)) {
request->abort();
request->deleteLater();
}
disconnect(
&_manager,
&QNetworkAccessManager::finished,
this,
&HttpConnection::requestFinished);
}
void HttpConnection::connectToServer(
const QString &address,
int port,
const bytes::vector &protocolSecret,
int16 protocolDcId) {
_address = address;
TCP_LOG(("HTTP Info: address is %1").arg(url().toDisplayString()));
connect(
&_manager,
&QNetworkAccessManager::finished,
this,
&HttpConnection::requestFinished);
mtpBuffer buffer(preparePQFake(_checkNonce));
DEBUG_LOG(("Connection Info: "
"sending fake req_pq through HTTP transport to '%1'").arg(address));
_pingTime = getms();
sendData(buffer);
}
mtpBuffer HttpConnection::handleResponse(QNetworkReply *reply) {
QByteArray response = reply->readAll();
TCP_LOG(("HTTP Info: read %1 bytes").arg(response.size()));
@@ -34,7 +104,7 @@ mtpBuffer HTTPConnection::handleResponse(QNetworkReply *reply) {
return data;
}
qint32 HTTPConnection::handleError(QNetworkReply *reply) { // returnes "maybe bad key"
qint32 HttpConnection::handleError(QNetworkReply *reply) { // returnes "maybe bad key"
auto result = qint32(kErrorCodeOther);
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
@@ -46,9 +116,9 @@ qint32 HTTPConnection::handleError(QNetworkReply *reply) { // returnes "maybe ba
switch (reply->error()) {
case QNetworkReply::ConnectionRefusedError: LOG(("HTTP Error: connection refused - %1").arg(reply->errorString())); break;
case QNetworkReply::RemoteHostClosedError: LOG(("HTTP Error: remote host closed - %1").arg(reply->errorString())); break;
case QNetworkReply::HostNotFoundError: LOG(("HTTP Error: host not found - %2").arg(reply->error()).arg(reply->errorString())); break;
case QNetworkReply::TimeoutError: LOG(("HTTP Error: timeout - %2").arg(reply->error()).arg(reply->errorString())); break;
case QNetworkReply::OperationCanceledError: LOG(("HTTP Error: cancelled - %2").arg(reply->error()).arg(reply->errorString())); break;
case QNetworkReply::HostNotFoundError: LOG(("HTTP Error: host not found - %1").arg(reply->errorString())); break;
case QNetworkReply::TimeoutError: LOG(("HTTP Error: timeout - %1").arg(reply->errorString())); break;
case QNetworkReply::OperationCanceledError: LOG(("HTTP Error: cancelled - %1").arg(reply->errorString())); break;
case QNetworkReply::SslHandshakeFailedError:
case QNetworkReply::TemporaryNetworkFailureError:
case QNetworkReply::NetworkSessionFailedError:
@@ -81,92 +151,31 @@ qint32 HTTPConnection::handleError(QNetworkReply *reply) { // returnes "maybe ba
return result;
}
HTTPConnection::HTTPConnection(QThread *thread) : AbstractConnection(thread)
, status(WaitingHttp)
, httpNonce(rand_value<MTPint128>()) {
manager.moveToThread(thread);
bool HttpConnection::isConnected() const {
return (_status == Status::Ready);
}
void HTTPConnection::setProxyOverride(const ProxyData &proxy) {
manager.setProxy(ToNetworkProxy(proxy));
}
void HTTPConnection::sendData(mtpBuffer &buffer) {
if (status == FinishedWork) return;
if (buffer.size() < 3) {
LOG(("TCP Error: writing bad packet, len = %1").arg(buffer.size() * sizeof(mtpPrime)));
TCP_LOG(("TCP Error: bad packet %1").arg(Logs::mb(&buffer[0], buffer.size() * sizeof(mtpPrime)).str()));
emit error(kErrorCodeOther);
return;
}
int32 requestSize = (buffer.size() - 3) * sizeof(mtpPrime);
QNetworkRequest request(url());
request.setHeader(QNetworkRequest::ContentLengthHeader, QVariant(requestSize));
request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(qsl("application/x-www-form-urlencoded")));
TCP_LOG(("HTTP Info: sending %1 len request %2").arg(requestSize).arg(Logs::mb(&buffer[2], requestSize).str()));
requests.insert(manager.post(request, QByteArray((const char*)(&buffer[2]), requestSize)));
}
void HTTPConnection::disconnectFromServer() {
if (status == FinishedWork) return;
status = FinishedWork;
Requests copy = requests;
requests.clear();
for (Requests::const_iterator i = copy.cbegin(), e = copy.cend(); i != e; ++i) {
(*i)->abort();
(*i)->deleteLater();
}
disconnect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*)));
}
void HTTPConnection::connectToServer(
const QString &ip,
int port,
const bytes::vector &protocolSecret,
int16 protocolDcId) {
_address = ip;
TCP_LOG(("HTTP Info: address is %1").arg(url().toDisplayString()));
connect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*)));
mtpBuffer buffer(preparePQFake(httpNonce));
DEBUG_LOG(("Connection Info: sending fake req_pq through HTTP transport to %1").arg(ip));
_pingTime = getms();
sendData(buffer);
}
bool HTTPConnection::isConnected() const {
return (status == UsingHttp);
}
void HTTPConnection::requestFinished(QNetworkReply *reply) {
if (status == FinishedWork) return;
void HttpConnection::requestFinished(QNetworkReply *reply) {
if (_status == Status::Finished) return;
reply->deleteLater();
if (reply->error() == QNetworkReply::NoError) {
requests.remove(reply);
_requests.remove(reply);
mtpBuffer data = handleResponse(reply);
if (data.size() == 1) {
emit error(data[0]);
} else if (!data.isEmpty()) {
if (status == UsingHttp) {
if (_status == Status::Ready) {
_receivedQueue.push_back(data);
emit receivedData();
} else {
try {
auto res_pq = readPQFakeReply(data);
const auto &res_pq_data(res_pq.c_resPQ());
if (res_pq_data.vnonce == httpNonce) {
if (res_pq_data.vnonce == _checkNonce) {
DEBUG_LOG(("Connection Info: HTTP-transport to %1 connected by pq-response").arg(_address));
status = UsingHttp;
_status = Status::Ready;
_pingTime = getms() - _pingTime;
emit connected();
}
@@ -177,7 +186,7 @@ void HTTPConnection::requestFinished(QNetworkReply *reply) {
}
}
} else {
if (!requests.remove(reply)) {
if (!_requests.remove(reply)) {
return;
}
@@ -185,23 +194,27 @@ void HTTPConnection::requestFinished(QNetworkReply *reply) {
}
}
TimeMs HTTPConnection::pingTime() const {
TimeMs HttpConnection::pingTime() const {
return isConnected() ? _pingTime : TimeMs(0);
}
bool HTTPConnection::usingHttpWait() {
TimeMs HttpConnection::fullConnectTimeout() const {
return kFullConnectionTimeout;
}
bool HttpConnection::usingHttpWait() {
return true;
}
bool HTTPConnection::needHttpWait() {
return requests.isEmpty();
bool HttpConnection::needHttpWait() {
return _requests.isEmpty();
}
int32 HTTPConnection::debugState() const {
int32 HttpConnection::debugState() const {
return -1;
}
QString HTTPConnection::transport() const {
QString HttpConnection::transport() const {
if (!isConnected()) {
return QString();
}
@@ -212,7 +225,7 @@ QString HTTPConnection::transport() const {
return result;
}
QString HTTPConnection::tag() const {
QString HttpConnection::tag() const {
auto result = qsl("HTTP");
if (qthelp::is_ipv6(_address)) {
result += qsl("/IPv6");
@@ -222,7 +235,7 @@ QString HTTPConnection::tag() const {
return result;
}
QUrl HTTPConnection::url() const {
QUrl HttpConnection::url() const {
const auto pattern = qthelp::is_ipv6(_address)
? qsl("http://[%1]:%2/api")
: qsl("http://%1:%2/api");

View File

@@ -12,18 +12,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace MTP {
namespace internal {
class HTTPConnection : public AbstractConnection {
Q_OBJECT
class HttpConnection : public AbstractConnection {
public:
HTTPConnection(QThread *thread);
HttpConnection(QThread *thread, const ProxyData &proxy);
ConnectionPointer clone(const ProxyData &proxy) override;
void setProxyOverride(const ProxyData &proxy) override;
TimeMs pingTime() const override;
TimeMs fullConnectTimeout() const override;
void sendData(mtpBuffer &buffer) override;
void disconnectFromServer() override;
void connectToServer(
const QString &ip,
const QString &address,
int port,
const bytes::vector &protocolSecret,
int16 protocolDcId) override;
@@ -39,25 +39,23 @@ public:
static mtpBuffer handleResponse(QNetworkReply *reply);
static qint32 handleError(QNetworkReply *reply); // returnes error code
public slots:
void requestFinished(QNetworkReply *reply);
private:
QUrl url() const;
enum Status {
WaitingHttp = 0,
UsingHttp,
FinishedWork
};
Status status;
MTPint128 httpNonce;
void requestFinished(QNetworkReply *reply);
QNetworkAccessManager manager;
enum class Status {
Waiting = 0,
Ready,
Finished,
};
Status _status = Status::Waiting;
MTPint128 _checkNonce;
QNetworkAccessManager _manager;
QString _address;
typedef QSet<QNetworkReply*> Requests;
Requests requests;
QSet<QNetworkReply*> _requests;
TimeMs _pingTime = 0;

View File

@@ -0,0 +1,240 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "mtproto/connection_resolving.h"
namespace MTP {
namespace internal {
namespace {
constexpr auto kOneConnectionTimeout = 4000;
} // namespace
ResolvingConnection::ResolvingConnection(
not_null<Instance*> instance,
QThread *thread,
const ProxyData &proxy,
ConnectionPointer &&child)
: AbstractConnection(thread, proxy)
, _instance(instance)
, _timeoutTimer([=] { handleError(kErrorCodeOther); }) {
setChild(std::move(child));
if (proxy.resolvedExpireAt < getms(true)) {
const auto host = proxy.host;
connect(
instance,
&Instance::proxyDomainResolved,
this,
&ResolvingConnection::domainResolved,
Qt::QueuedConnection);
InvokeQueued(instance, [=] {
instance->resolveProxyDomain(host);
});
}
if (!proxy.resolvedIPs.empty()) {
refreshChild();
}
}
ConnectionPointer ResolvingConnection::clone(const ProxyData &proxy) {
Unexpected("ResolvingConnection::clone call.");
}
void ResolvingConnection::setChild(ConnectionPointer &&child) {
_child = std::move(child);
connect(
_child,
&AbstractConnection::receivedData,
this,
&ResolvingConnection::handleReceivedData);
connect(
_child,
&AbstractConnection::receivedSome,
this,
&ResolvingConnection::receivedSome);
connect(
_child,
&AbstractConnection::error,
this,
&ResolvingConnection::handleError);
connect(_child,
&AbstractConnection::connected,
this,
&ResolvingConnection::handleConnected);
connect(_child,
&AbstractConnection::disconnected,
this,
&ResolvingConnection::handleDisconnected);
if (_protocolDcId) {
_child->connectToServer(
_address,
_port,
_protocolSecret,
_protocolDcId);
}
}
void ResolvingConnection::domainResolved(
const QString &host,
const QStringList &ips,
qint64 expireAt) {
if (host != _proxy.host || !_child) {
return;
}
_proxy.resolvedExpireAt = expireAt;
auto index = 0;
for (const auto &ip : ips) {
if (index >= _proxy.resolvedIPs.size()) {
_proxy.resolvedIPs.push_back(ip);
} else if (_proxy.resolvedIPs[index] != ip) {
_proxy.resolvedIPs[index] = ip;
if (_ipIndex >= index) {
_ipIndex = index - 1;
refreshChild();
}
}
++index;
}
if (index < _proxy.resolvedIPs.size()) {
_proxy.resolvedIPs.resize(index);
if (_ipIndex >= index) {
emitError(kErrorCodeOther);
}
}
if (_ipIndex < 0) {
refreshChild();
}
}
bool ResolvingConnection::refreshChild() {
if (!_child) {
return true;
} else if (++_ipIndex >= _proxy.resolvedIPs.size()) {
return false;
}
setChild(_child->clone(ToDirectIpProxy(_proxy, _ipIndex)));
_timeoutTimer.callOnce(kOneConnectionTimeout);
return true;
}
void ResolvingConnection::emitError(int errorCode) {
_ipIndex = -1;
_child = nullptr;
emit error(errorCode);
}
void ResolvingConnection::handleError(int errorCode) {
if (_connected) {
emitError(errorCode);
} else if (!_proxy.resolvedIPs.empty()) {
if (!refreshChild()) {
emitError(errorCode);
}
} else {
// Wait for the domain to be resolved.
}
}
void ResolvingConnection::handleDisconnected() {
if (_connected) {
emit disconnected();
} else {
handleError(kErrorCodeOther);
}
}
void ResolvingConnection::handleReceivedData() {
auto &my = received();
auto &his = _child->received();
for (auto &item : his) {
my.push_back(std::move(item));
}
his.clear();
emit receivedData();
}
void ResolvingConnection::handleConnected() {
_connected = true;
_timeoutTimer.cancel();
if (_ipIndex >= 0) {
const auto host = _proxy.host;
const auto good = _proxy.resolvedIPs[_ipIndex];
const auto instance = _instance;
InvokeQueued(_instance, [=] {
instance->setGoodProxyDomain(host, good);
});
}
emit connected();
}
TimeMs ResolvingConnection::pingTime() const {
Expects(_child != nullptr);
return _child->pingTime();
}
TimeMs ResolvingConnection::fullConnectTimeout() const {
return kOneConnectionTimeout * qMax(int(_proxy.resolvedIPs.size()), 1);
}
void ResolvingConnection::sendData(mtpBuffer &buffer) {
Expects(_child != nullptr);
_child->sendData(buffer);
}
void ResolvingConnection::disconnectFromServer() {
_address = QString();
_port = 0;
_protocolSecret = bytes::vector();
_protocolDcId = 0;
if (!_child) {
return;
}
_child->disconnectFromServer();
}
void ResolvingConnection::connectToServer(
const QString &address,
int port,
const bytes::vector &protocolSecret,
int16 protocolDcId) {
if (!_child) {
InvokeQueued(this, [=] { emitError(kErrorCodeOther); });
return;
}
_address = address;
_port = port;
_protocolSecret = protocolSecret;
_protocolDcId = protocolDcId;
return _child->connectToServer(
address,
port,
protocolSecret,
protocolDcId);
}
bool ResolvingConnection::isConnected() const {
return _child ? _child->isConnected() : false;
}
int32 ResolvingConnection::debugState() const {
return _child ? _child->debugState() : -1;
}
QString ResolvingConnection::transport() const {
return _child ? _child->transport() : QString();
}
QString ResolvingConnection::tag() const {
return _child ? _child->tag() : QString();
}
} // namespace internal
} // namespace MTP

View File

@@ -0,0 +1,70 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "mtproto/auth_key.h"
#include "mtproto/connection_abstract.h"
#include "base/timer.h"
namespace MTP {
namespace internal {
class ResolvingConnection : public AbstractConnection {
public:
ResolvingConnection(
not_null<Instance*> instance,
QThread *thread,
const ProxyData &proxy,
ConnectionPointer &&child);
ConnectionPointer clone(const ProxyData &proxy) override;
TimeMs pingTime() const override;
TimeMs fullConnectTimeout() const override;
void sendData(mtpBuffer &buffer) override;
void disconnectFromServer() override;
void connectToServer(
const QString &address,
int port,
const bytes::vector &protocolSecret,
int16 protocolDcId) override;
bool isConnected() const override;
int32 debugState() const override;
QString transport() const override;
QString tag() const override;
private:
void setChild(ConnectionPointer &&child);
bool refreshChild();
void emitError(int errorCode);
void domainResolved(
const QString &host,
const QStringList &ips,
qint64 expireAt);
void handleError(int errorCode);
void handleConnected();
void handleDisconnected();
void handleReceivedData();
not_null<Instance*> _instance;
ConnectionPointer _child;
bool _connected = false;
int _ipIndex = -1;
QString _address;
int _port = 0;
bytes::vector _protocolSecret;
int16 _protocolDcId = 0;
base::Timer _timeoutTimer;
};
} // namespace internal
} // namespace MTP

View File

@@ -18,8 +18,9 @@ namespace {
constexpr auto kMinReceiveTimeout = TimeMs(2000);
constexpr auto kMaxReceiveTimeout = TimeMs(8000);
constexpr auto kPacketSizeMax = 64 * 1024 * 1024U;
uint32 tcpPacketSize(const char *packet) { // must have at least 4 bytes readable
uint32 CountTcpPacketSize(const char *packet) { // must have at least 4 bytes readable
uint32 result = (packet[0] > 0) ? packet[0] : 0;
if (result == 0x7f) {
const uchar *bytes = reinterpret_cast<const uchar*>(packet);
@@ -29,92 +30,114 @@ uint32 tcpPacketSize(const char *packet) { // must have at least 4 bytes readabl
return (result << 2) + 1;
}
using ErrorSignal = void(QTcpSocket::*)(QAbstractSocket::SocketError);
const auto QTcpSocket_error = ErrorSignal(&QAbstractSocket::error);
} // namespace
AbstractTCPConnection::AbstractTCPConnection(
QThread *thread)
: AbstractConnection(thread)
, currentPos((char*)shortBuffer) {
TcpConnection::TcpConnection(QThread *thread, const ProxyData &proxy)
: AbstractConnection(thread, proxy)
, _currentPosition(reinterpret_cast<char*>(_shortBuffer))
, _checkNonce(rand_value<MTPint128>())
, _timeout(kMinReceiveTimeout)
, _timeoutTimer(thread, [=] { handleTimeout(); }) {
_socket.moveToThread(thread);
_socket.setProxy(ToNetworkProxy(proxy));
connect(&_socket, QTcpSocket_error, this, &TcpConnection::socketError);
connect(
&_socket,
&QTcpSocket::connected,
this,
&TcpConnection::socketConnected);
connect(
&_socket,
&QTcpSocket::disconnected,
this,
&TcpConnection::socketDisconnected);
}
void AbstractTCPConnection::setProxyOverride(const ProxyData &proxy) {
sock.setProxy(ToNetworkProxy(proxy));
ConnectionPointer TcpConnection::clone(const ProxyData &proxy) {
return ConnectionPointer::New<TcpConnection>(thread(), proxy);
}
AbstractTCPConnection::~AbstractTCPConnection() = default;
void AbstractTCPConnection::socketRead() {
if (sock.state() != QAbstractSocket::ConnectedState) {
LOG(("MTP error: socket not connected in socketRead(), state: %1").arg(sock.state()));
void TcpConnection::socketRead() {
if (_socket.state() != QAbstractSocket::ConnectedState) {
LOG(("MTP error: "
"socket not connected in socketRead(), state: %1"
).arg(_socket.state()));
emit error(kErrorCodeOther);
return;
}
do {
uint32 toRead = packetLeft ? packetLeft : (readingToShort ? (MTPShortBufferSize * sizeof(mtpPrime) - packetRead) : 4);
if (readingToShort) {
if (currentPos + toRead > ((char*)shortBuffer) + MTPShortBufferSize * sizeof(mtpPrime)) {
longBuffer.resize(((packetRead + toRead) >> 2) + 1);
memcpy(&longBuffer[0], shortBuffer, packetRead);
currentPos = ((char*)&longBuffer[0]) + packetRead;
readingToShort = false;
uint32 toRead = _packetLeft
? _packetLeft
: (_readingToShort
? (kShortBufferSize * sizeof(mtpPrime) - _packetRead)
: 4);
if (_readingToShort) {
if (_currentPosition + toRead > ((char*)_shortBuffer) + kShortBufferSize * sizeof(mtpPrime)) {
_longBuffer.resize(((_packetRead + toRead) >> 2) + 1);
memcpy(&_longBuffer[0], _shortBuffer, _packetRead);
_currentPosition = ((char*)&_longBuffer[0]) + _packetRead;
_readingToShort = false;
}
} else {
if (longBuffer.size() * sizeof(mtpPrime) < packetRead + toRead) {
longBuffer.resize(((packetRead + toRead) >> 2) + 1);
currentPos = ((char*)&longBuffer[0]) + packetRead;
if (_longBuffer.size() * sizeof(mtpPrime) < _packetRead + toRead) {
_longBuffer.resize(((_packetRead + toRead) >> 2) + 1);
_currentPosition = ((char*)&_longBuffer[0]) + _packetRead;
}
}
int32 bytes = (int32)sock.read(currentPos, toRead);
int32 bytes = (int32)_socket.read(_currentPosition, toRead);
if (bytes > 0) {
aesCtrEncrypt(currentPos, bytes, _receiveKey, &_receiveState);
aesCtrEncrypt(_currentPosition, bytes, _receiveKey, &_receiveState);
TCP_LOG(("TCP Info: read %1 bytes").arg(bytes));
packetRead += bytes;
currentPos += bytes;
if (packetLeft) {
packetLeft -= bytes;
if (!packetLeft) {
socketPacket(currentPos - packetRead, packetRead);
currentPos = (char*)shortBuffer;
packetRead = packetLeft = 0;
readingToShort = true;
longBuffer.clear();
_packetRead += bytes;
_currentPosition += bytes;
if (_packetLeft) {
_packetLeft -= bytes;
if (!_packetLeft) {
socketPacket(_currentPosition - _packetRead, _packetRead);
_currentPosition = (char*)_shortBuffer;
_packetRead = _packetLeft = 0;
_readingToShort = true;
_longBuffer.clear();
} else {
TCP_LOG(("TCP Info: not enough %1 for packet! read %2").arg(packetLeft).arg(packetRead));
TCP_LOG(("TCP Info: not enough %1 for packet! read %2").arg(_packetLeft).arg(_packetRead));
emit receivedSome();
}
} else {
bool move = false;
while (packetRead >= 4) {
uint32 packetSize = tcpPacketSize(currentPos - packetRead);
if (packetSize < 5 || packetSize > MTPPacketSizeMax) {
while (_packetRead >= 4) {
uint32 packetSize = CountTcpPacketSize(_currentPosition - _packetRead);
if (packetSize < 5 || packetSize > kPacketSizeMax) {
LOG(("TCP Error: packet size = %1").arg(packetSize));
emit error(kErrorCodeOther);
return;
}
if (packetRead >= packetSize) {
socketPacket(currentPos - packetRead, packetSize);
packetRead -= packetSize;
packetLeft = 0;
if (_packetRead >= packetSize) {
socketPacket(_currentPosition - _packetRead, packetSize);
_packetRead -= packetSize;
_packetLeft = 0;
move = true;
} else {
packetLeft = packetSize - packetRead;
TCP_LOG(("TCP Info: not enough %1 for packet! size %2 read %3").arg(packetLeft).arg(packetSize).arg(packetRead));
_packetLeft = packetSize - _packetRead;
TCP_LOG(("TCP Info: not enough %1 for packet! size %2 read %3").arg(_packetLeft).arg(packetSize).arg(_packetRead));
emit receivedSome();
break;
}
}
if (move) {
if (!packetRead) {
currentPos = (char*)shortBuffer;
readingToShort = true;
longBuffer.clear();
} else if (!readingToShort && packetRead < MTPShortBufferSize * sizeof(mtpPrime)) {
memcpy(shortBuffer, currentPos - packetRead, packetRead);
currentPos = (char*)shortBuffer + packetRead;
readingToShort = true;
longBuffer.clear();
if (!_packetRead) {
_currentPosition = (char*)_shortBuffer;
_readingToShort = true;
_longBuffer.clear();
} else if (!_readingToShort && _packetRead < kShortBufferSize * sizeof(mtpPrime)) {
memcpy(_shortBuffer, _currentPosition - _packetRead, _packetRead);
_currentPosition = (char*)_shortBuffer + _packetRead;
_readingToShort = true;
_longBuffer.clear();
}
}
}
@@ -126,11 +149,11 @@ void AbstractTCPConnection::socketRead() {
TCP_LOG(("TCP Info: no bytes read, but bytes available was true..."));
break;
}
} while (sock.state() == QAbstractSocket::ConnectedState && sock.bytesAvailable());
} while (_socket.state() == QAbstractSocket::ConnectedState && _socket.bytesAvailable());
}
mtpBuffer AbstractTCPConnection::handleResponse(const char *packet, uint32 length) {
if (length < 5 || length > MTPPacketSizeMax) {
mtpBuffer TcpConnection::handleResponse(const char *packet, uint32 length) {
if (length < 5 || length > kPacketSizeMax) {
LOG(("TCP Error: bad packet size %1").arg(length));
return mtpBuffer(1, -500);
}
@@ -148,7 +171,14 @@ mtpBuffer AbstractTCPConnection::handleResponse(const char *packet, uint32 lengt
const mtpPrime *packetdata = reinterpret_cast<const mtpPrime*>(packet + (length - len));
TCP_LOG(("TCP Info: packet received, size = %1").arg(size * sizeof(mtpPrime)));
if (size == 1) {
LOG(("TCP Error: error packet received, code = %1").arg(*packetdata));
LOG(("TCP Error: "
"error packet received, endpoint: '%1:%2', "
"protocolDcId: %3, secret_len: %4, code = %5"
).arg(_address.isEmpty() ? ("proxy_" + _proxy.host) : _address
).arg(_address.isEmpty() ? _proxy.port : _port
).arg(_protocolDcId
).arg(_protocolSecret.size()
).arg(*packetdata));
return mtpBuffer(1, *packetdata);
}
@@ -158,26 +188,26 @@ mtpBuffer AbstractTCPConnection::handleResponse(const char *packet, uint32 lengt
return data;
}
void AbstractTCPConnection::handleError(QAbstractSocket::SocketError e, QTcpSocket &sock) {
void TcpConnection::handleError(QAbstractSocket::SocketError e, QTcpSocket &socket) {
switch (e) {
case QAbstractSocket::ConnectionRefusedError:
LOG(("TCP Error: socket connection refused - %1").arg(sock.errorString()));
LOG(("TCP Error: socket connection refused - %1").arg(socket.errorString()));
break;
case QAbstractSocket::RemoteHostClosedError:
TCP_LOG(("TCP Info: remote host closed socket connection - %1").arg(sock.errorString()));
TCP_LOG(("TCP Info: remote host closed socket connection - %1").arg(socket.errorString()));
break;
case QAbstractSocket::HostNotFoundError:
LOG(("TCP Error: host not found - %1").arg(sock.errorString()));
LOG(("TCP Error: host not found - %1").arg(socket.errorString()));
break;
case QAbstractSocket::SocketTimeoutError:
LOG(("TCP Error: socket timeout - %1").arg(sock.errorString()));
LOG(("TCP Error: socket timeout - %1").arg(socket.errorString()));
break;
case QAbstractSocket::NetworkError:
LOG(("TCP Error: network - %1").arg(sock.errorString()));
LOG(("TCP Error: network - %1").arg(socket.errorString()));
break;
case QAbstractSocket::ProxyAuthenticationRequiredError:
@@ -186,77 +216,62 @@ void AbstractTCPConnection::handleError(QAbstractSocket::SocketError e, QTcpSock
case QAbstractSocket::ProxyConnectionTimeoutError:
case QAbstractSocket::ProxyNotFoundError:
case QAbstractSocket::ProxyProtocolError:
LOG(("TCP Error: proxy (%1) - %2").arg(e).arg(sock.errorString()));
LOG(("TCP Error: proxy (%1) - %2").arg(e).arg(socket.errorString()));
break;
default:
LOG(("TCP Error: other (%1) - %2").arg(e).arg(sock.errorString()));
LOG(("TCP Error: other (%1) - %2").arg(e).arg(socket.errorString()));
break;
}
TCP_LOG(("TCP Error %1, restarting! - %2").arg(e).arg(sock.errorString()));
TCP_LOG(("TCP Error %1, restarting! - %2").arg(e).arg(socket.errorString()));
}
TCPConnection::TCPConnection(QThread *thread)
: AbstractTCPConnection(thread)
, status(WaitingTcp)
, tcpNonce(rand_value<MTPint128>())
, _tcpTimeout(kMinReceiveTimeout) {
tcpTimeoutTimer.moveToThread(thread);
tcpTimeoutTimer.setSingleShot(true);
connect(&tcpTimeoutTimer, SIGNAL(timeout()), this, SLOT(onTcpTimeoutTimer()));
sock.moveToThread(thread);
connect(&sock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)));
connect(&sock, SIGNAL(connected()), this, SLOT(onSocketConnected()));
connect(&sock, SIGNAL(disconnected()), this, SLOT(onSocketDisconnected()));
}
void TCPConnection::onSocketConnected() {
if (status == WaitingTcp) {
mtpBuffer buffer(preparePQFake(tcpNonce));
void TcpConnection::socketConnected() {
if (_status == Status::Waiting) {
mtpBuffer buffer(preparePQFake(_checkNonce));
DEBUG_LOG(("Connection Info: sending fake req_pq through TCP transport to %1").arg(_address));
if (_tcpTimeout < 0) _tcpTimeout = -_tcpTimeout;
tcpTimeoutTimer.start(_tcpTimeout);
if (_timeout < 0) _timeout = -_timeout;
_timeoutTimer.callOnce(_timeout);
_pingTime = getms();
sendData(buffer);
}
}
void TCPConnection::onTcpTimeoutTimer() {
if (status == WaitingTcp) {
if (_tcpTimeout < kMaxReceiveTimeout) {
_tcpTimeout *= 2;
void TcpConnection::handleTimeout() {
if (_status == Status::Waiting) {
if (_timeout < kMaxReceiveTimeout) {
_timeout *= 2;
}
_tcpTimeout = -_tcpTimeout;
_timeout = -_timeout;
QAbstractSocket::SocketState state = sock.state();
QAbstractSocket::SocketState state = _socket.state();
if (state == QAbstractSocket::ConnectedState || state == QAbstractSocket::ConnectingState || state == QAbstractSocket::HostLookupState) {
sock.disconnectFromHost();
_socket.disconnectFromHost();
} else if (state != QAbstractSocket::ClosingState) {
sock.connectToHost(QHostAddress(_address), _port);
_socket.connectToHost(_address, _port);
}
}
}
void TCPConnection::onSocketDisconnected() {
if (_tcpTimeout < 0) {
_tcpTimeout = -_tcpTimeout;
if (status == WaitingTcp) {
sock.connectToHost(QHostAddress(_address), _port);
void TcpConnection::socketDisconnected() {
if (_timeout < 0) {
_timeout = -_timeout;
if (_status == Status::Waiting) {
_socket.connectToHost(_address, _port);
return;
}
}
if (status == WaitingTcp || status == UsingTcp) {
if (_status == Status::Waiting || _status == Status::Ready) {
emit disconnected();
}
}
void TCPConnection::sendData(mtpBuffer &buffer) {
if (status == FinishedWork) return;
void TcpConnection::sendData(mtpBuffer &buffer) {
if (_status == Status::Finished) return;
if (buffer.size() < 3) {
LOG(("TCP Error: writing bad packet, len = %1").arg(buffer.size() * sizeof(mtpPrime)));
@@ -265,10 +280,10 @@ void TCPConnection::sendData(mtpBuffer &buffer) {
return;
}
tcpSend(buffer);
sendBuffer(buffer);
}
void AbstractTCPConnection::writeConnectionStart() {
void TcpConnection::writeConnectionStart() {
// prepare random part
auto nonceBytes = bytes::vector(64);
const auto nonce = bytes::make_span(nonceBytes);
@@ -280,8 +295,7 @@ void AbstractTCPConnection::writeConnectionStart() {
const auto reserved11 = 0x44414548U;
const auto reserved12 = 0x54534F50U;
const auto reserved13 = 0x20544547U;
const auto reserved14 = 0x20544547U;
const auto reserved15 = 0xEEEEEEEEU;
const auto reserved14 = 0xEEEEEEEEU;
const auto reserved21 = 0x00000000U;
do {
bytes::set_random(nonce);
@@ -290,7 +304,6 @@ void AbstractTCPConnection::writeConnectionStart() {
|| *first == reserved12
|| *first == reserved13
|| *first == reserved14
|| *first == reserved15
|| *second == reserved21);
const auto prepareKey = [&](bytes::span key, bytes::const_span from) {
@@ -330,80 +343,89 @@ void AbstractTCPConnection::writeConnectionStart() {
const auto dcId = reinterpret_cast<int16*>(nonce.data() + 60);
*dcId = _protocolDcId;
sock.write(reinterpret_cast<const char*>(nonce.data()), 56);
_socket.write(reinterpret_cast<const char*>(nonce.data()), 56);
aesCtrEncrypt(nonce.data(), 64, _sendKey, &_sendState);
sock.write(reinterpret_cast<const char*>(nonce.subspan(56).data()), 8);
_socket.write(reinterpret_cast<const char*>(nonce.subspan(56).data()), 8);
}
void AbstractTCPConnection::tcpSend(mtpBuffer &buffer) {
if (!packetNum) {
void TcpConnection::sendBuffer(mtpBuffer &buffer) {
if (!_packetIndex++) {
writeConnectionStart();
}
++packetNum;
uint32 size = buffer.size() - 3, len = size * 4;
char *data = reinterpret_cast<char*>(&buffer[0]);
if (size < 0x7f) {
data[7] = char(size);
TCP_LOG(("TCP Info: write %1 packet %2").arg(packetNum).arg(len + 1));
TCP_LOG(("TCP Info: write %1 packet %2").arg(_packetIndex).arg(len + 1));
aesCtrEncrypt(data + 7, len + 1, _sendKey, &_sendState);
sock.write(data + 7, len + 1);
_socket.write(data + 7, len + 1);
} else {
data[4] = 0x7f;
reinterpret_cast<uchar*>(data)[5] = uchar(size & 0xFF);
reinterpret_cast<uchar*>(data)[6] = uchar((size >> 8) & 0xFF);
reinterpret_cast<uchar*>(data)[7] = uchar((size >> 16) & 0xFF);
TCP_LOG(("TCP Info: write %1 packet %2").arg(packetNum).arg(len + 4));
TCP_LOG(("TCP Info: write %1 packet %2").arg(_packetIndex).arg(len + 4));
aesCtrEncrypt(data + 4, len + 4, _sendKey, &_sendState);
sock.write(data + 4, len + 4);
_socket.write(data + 4, len + 4);
}
}
void TCPConnection::disconnectFromServer() {
if (status == FinishedWork) return;
status = FinishedWork;
void TcpConnection::disconnectFromServer() {
if (_status == Status::Finished) return;
_status = Status::Finished;
disconnect(&sock, SIGNAL(readyRead()), 0, 0);
sock.close();
disconnect(&_socket, &QTcpSocket::readyRead, nullptr, nullptr);
_socket.close();
}
void TCPConnection::connectToServer(
const QString &ip,
void TcpConnection::connectToServer(
const QString &address,
int port,
const bytes::vector &protocolSecret,
int16 protocolDcId) {
_address = ip;
_port = port;
_protocolSecret = protocolSecret;
if (_proxy.type == ProxyData::Type::Mtproto) {
_address = _proxy.host;
_port = _proxy.port;
_protocolSecret = ProtocolSecretFromPassword(_proxy.password);
} else {
_address = address;
_port = port;
_protocolSecret = protocolSecret;
}
_protocolDcId = protocolDcId;
connect(&sock, SIGNAL(readyRead()), this, SLOT(socketRead()));
sock.connectToHost(QHostAddress(_address), _port);
connect(&_socket, &QTcpSocket::readyRead, this, [=] { socketRead(); });
_socket.connectToHost(_address, _port);
}
TimeMs TCPConnection::pingTime() const {
TimeMs TcpConnection::pingTime() const {
return isConnected() ? _pingTime : TimeMs(0);
}
void TCPConnection::socketPacket(const char *packet, uint32 length) {
if (status == FinishedWork) return;
TimeMs TcpConnection::fullConnectTimeout() const {
return kMaxReceiveTimeout;
}
void TcpConnection::socketPacket(const char *packet, uint32 length) {
if (_status == Status::Finished) return;
mtpBuffer data = handleResponse(packet, length);
if (data.size() == 1) {
emit error(data[0]);
} else if (status == UsingTcp) {
} else if (_status == Status::Ready) {
_receivedQueue.push_back(data);
emit receivedData();
} else if (status == WaitingTcp) {
tcpTimeoutTimer.stop();
} else if (_status == Status::Waiting) {
_timeoutTimer.cancel();
try {
auto res_pq = readPQFakeReply(data);
const auto &res_pq_data(res_pq.c_resPQ());
if (res_pq_data.vnonce == tcpNonce) {
if (res_pq_data.vnonce == _checkNonce) {
DEBUG_LOG(("Connection Info: TCP-transport to %1 chosen by pq-response").arg(_address));
status = UsingTcp;
_status = Status::Ready;
_pingTime = (getms() - _pingTime);
emit connected();
}
@@ -414,15 +436,15 @@ void TCPConnection::socketPacket(const char *packet, uint32 length) {
}
}
bool TCPConnection::isConnected() const {
return (status == UsingTcp);
bool TcpConnection::isConnected() const {
return (_status == Status::Ready);
}
int32 TCPConnection::debugState() const {
return sock.state();
int32 TcpConnection::debugState() const {
return _socket.state();
}
QString TCPConnection::transport() const {
QString TcpConnection::transport() const {
if (!isConnected()) {
return QString();
}
@@ -433,7 +455,7 @@ QString TCPConnection::transport() const {
return result;
}
QString TCPConnection::tag() const {
QString TcpConnection::tag() const {
auto result = qsl("TCP");
if (qthelp::is_ipv6(_address)) {
result += qsl("/IPv6");
@@ -443,10 +465,10 @@ QString TCPConnection::tag() const {
return result;
}
void TCPConnection::socketError(QAbstractSocket::SocketError e) {
if (status == FinishedWork) return;
void TcpConnection::socketError(QAbstractSocket::SocketError e) {
if (_status == Status::Finished) return;
handleError(e, sock);
handleError(e, _socket);
emit error(kErrorCodeOther);
}

View File

@@ -9,64 +9,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/auth_key.h"
#include "mtproto/connection_abstract.h"
#include "base/timer.h"
namespace MTP {
namespace internal {
class AbstractTCPConnection : public AbstractConnection {
Q_OBJECT
class TcpConnection : public AbstractConnection {
public:
AbstractTCPConnection(QThread *thread);
TcpConnection(
QThread *thread,
const ProxyData &proxy);
void setProxyOverride(const ProxyData &proxy) override;
virtual ~AbstractTCPConnection() = 0;
public slots:
void socketRead();
protected:
void writeConnectionStart();
QTcpSocket sock;
uint32 packetNum = 0; // sent packet number
uint32 packetRead = 0;
uint32 packetLeft = 0; // reading from socket
bool readingToShort = true;
char *currentPos;
mtpBuffer longBuffer;
mtpPrime shortBuffer[MTPShortBufferSize];
virtual void socketPacket(const char *packet, uint32 length) = 0;
static mtpBuffer handleResponse(const char *packet, uint32 length);
static void handleError(QAbstractSocket::SocketError e, QTcpSocket &sock);
static uint32 fourCharsToUInt(char ch1, char ch2, char ch3, char ch4) {
char ch[4] = { ch1, ch2, ch3, ch4 };
return *reinterpret_cast<uint32*>(ch);
}
void tcpSend(mtpBuffer &buffer);
uchar _sendKey[CTRState::KeySize];
CTRState _sendState;
uchar _receiveKey[CTRState::KeySize];
CTRState _receiveState;
int16 _protocolDcId = 0;
bytes::vector _protocolSecret;
};
class TCPConnection : public AbstractTCPConnection {
Q_OBJECT
public:
TCPConnection(QThread *thread);
ConnectionPointer clone(const ProxyData &proxy) override;
TimeMs pingTime() const override;
TimeMs fullConnectTimeout() const override;
void sendData(mtpBuffer &buffer) override;
void disconnectFromServer() override;
void connectToServer(
const QString &ip,
const QString &address,
int port,
const bytes::vector &protocolSecret,
int16 protocolDcId) override;
@@ -77,29 +38,57 @@ public:
QString transport() const override;
QString tag() const override;
public slots:
void socketError(QAbstractSocket::SocketError e);
void onSocketConnected();
void onSocketDisconnected();
void onTcpTimeoutTimer();
protected:
void socketPacket(const char *packet, uint32 length) override;
private:
enum Status {
WaitingTcp = 0,
UsingTcp,
FinishedWork
enum class Status {
Waiting = 0,
Ready,
Finished,
};
Status status;
MTPint128 tcpNonce;
static constexpr auto kShortBufferSize = 65535; // Of ints, 256 kb.
void socketRead();
void writeConnectionStart();
void socketPacket(const char *packet, uint32 length);
void socketConnected();
void socketDisconnected();
void socketError(QAbstractSocket::SocketError e);
void handleTimeout();
mtpBuffer handleResponse(const char *packet, uint32 length);
static void handleError(QAbstractSocket::SocketError e, QTcpSocket &sock);
static uint32 fourCharsToUInt(char ch1, char ch2, char ch3, char ch4) {
char ch[4] = { ch1, ch2, ch3, ch4 };
return *reinterpret_cast<uint32*>(ch);
}
void sendBuffer(mtpBuffer &buffer);
QTcpSocket _socket;
uint32 _packetIndex = 0; // sent packet number
uint32 _packetRead = 0;
uint32 _packetLeft = 0; // reading from socket
bool _readingToShort = true;
mtpBuffer _longBuffer;
mtpPrime _shortBuffer[kShortBufferSize];
char *_currentPosition = nullptr;
uchar _sendKey[CTRState::KeySize];
CTRState _sendState;
uchar _receiveKey[CTRState::KeySize];
CTRState _receiveState;
int16 _protocolDcId = 0;
bytes::vector _protocolSecret;
Status _status = Status::Waiting;
MTPint128 _checkNonce;
QString _address;
int32 _port, _tcpTimeout;
QTimer tcpTimeoutTimer;
int32 _port = 0;
int32 _timeout = 0;
base::Timer _timeoutTimer;
TimeMs _pingTime = 0;
};

View File

@@ -28,8 +28,7 @@ public:
bool connectionInited() const {
QMutexLocker lock(&initLock);
bool res = _connectionInited;
return res;
return _connectionInited;
}
void setConnectionInited(bool connectionInited = true) {
QMutexLocker lock(&initLock);
@@ -38,7 +37,7 @@ public:
signals:
void authKeyCreated();
void layerWasInited(bool was);
void connectionWasInited();
private slots:
void authKeyWrite();

View File

@@ -11,11 +11,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/dc_options.h"
#include "mtproto/dcenter.h"
#include "mtproto/config_loader.h"
#include "mtproto/special_config_request.h"
#include "mtproto/connection.h"
#include "mtproto/sender.h"
#include "mtproto/rsa_public_key.h"
#include "storage/localstorage.h"
#include "auth_session.h"
#include "application.h"
#include "apiwrap.h"
#include "messenger.h"
#include "lang/lang_instance.h"
@@ -36,6 +38,8 @@ public:
void start(Config &&config);
void resolveProxyDomain(const QString &host);
void setGoodProxyDomain(const QString &host, const QString &ip);
void suggestMainDcId(DcId mainDcId);
void setMainDcId(DcId mainDcId);
DcId mainDcId() const;
@@ -50,6 +54,7 @@ public:
void requestConfigIfOld();
void requestCDNConfig();
void setUserPhone(const QString &phone);
void badConfigurationError();
void restart();
void restart(ShiftedDcId shiftedDcId);
@@ -124,6 +129,11 @@ private:
bool exportFail(const RPCError &error, mtpRequestId requestId);
bool onErrorDefault(mtpRequestId requestId, const RPCError &error);
void applyDomainIps(
const QString &host,
const QStringList &ips,
TimeMs expireAt);
void logoutGuestDcs();
bool logoutGuestDone(mtpRequestId requestId);
@@ -159,6 +169,7 @@ private:
base::set_of_unique_ptr<internal::Connection> _quittingConnections;
std::unique_ptr<internal::ConfigLoader> _configLoader;
std::unique_ptr<DomainResolver> _domainResolver;
QString _userPhone;
mtpRequestId _cdnConfigLoadRequestId = 0;
TimeMs _lastConfigLoadedTime = 0;
@@ -201,7 +212,11 @@ private:
};
Instance::Private::Private(not_null<Instance*> instance, not_null<DcOptions*> options, Instance::Mode mode) : Sender()
Instance::Private::Private(
not_null<Instance*> instance,
not_null<DcOptions*> options,
Instance::Mode mode)
: Sender()
, _instance(instance)
, _dcOptions(options)
, _mode(mode) {
@@ -259,6 +274,85 @@ void Instance::Private::start(Config &&config) {
requestConfig();
}
void Instance::Private::resolveProxyDomain(const QString &host) {
if (!_domainResolver) {
_domainResolver = std::make_unique<DomainResolver>([=](
const QString &host,
const QStringList &ips,
TimeMs expireAt) {
applyDomainIps(host, ips, expireAt);
});
}
_domainResolver->resolve(host);
}
void Instance::Private::applyDomainIps(
const QString &host,
const QStringList &ips,
TimeMs expireAt) {
const auto applyToProxy = [&](ProxyData &proxy) {
if (!proxy.tryCustomResolve() || proxy.host != host) {
return false;
}
proxy.resolvedExpireAt = expireAt;
auto copy = ips;
auto &current = proxy.resolvedIPs;
const auto i = ranges::remove_if(current, [&](const QString &ip) {
const auto index = copy.indexOf(ip);
if (index < 0) {
return true;
}
copy.removeAt(index);
return false;
});
if (i == end(current) && copy.isEmpty()) {
// Even if the proxy was changed already, we still want
// to refreshOptions in all sessions across all instances.
return true;
}
current.erase(i, end(current));
for (const auto &ip : copy) {
proxy.resolvedIPs.push_back(ip);
}
return true;
};
for (auto &proxy : Global::RefProxiesList()) {
applyToProxy(proxy);
}
if (applyToProxy(Global::RefSelectedProxy()) && Global::UseProxy()) {
for (auto &session : _sessions) {
session.second->refreshOptions();
}
}
emit _instance->proxyDomainResolved(host, ips, expireAt);
}
void Instance::Private::setGoodProxyDomain(
const QString &host,
const QString &ip) {
const auto applyToProxy = [&](ProxyData &proxy) {
if (!proxy.tryCustomResolve() || proxy.host != host) {
return false;
}
auto &current = proxy.resolvedIPs;
auto i = ranges::find(current, ip);
if (i == end(current) || i == begin(current)) {
return false;
}
while (i != begin(current)) {
const auto j = i--;
std::swap(*i, *j);
}
return true;
};
for (auto &proxy : Global::RefProxiesList()) {
applyToProxy(proxy);
}
if (applyToProxy(Global::RefSelectedProxy()) && Global::UseProxy()) {
Sandbox::refreshGlobalProxy();
}
}
void Instance::Private::suggestMainDcId(DcId mainDcId) {
if (_mainDcIdForced) return;
setMainDcId(mainDcId);
@@ -305,6 +399,12 @@ void Instance::Private::setUserPhone(const QString &phone) {
}
}
void Instance::Private::badConfigurationError() {
if (_mode == Mode::Normal) {
Messenger::Instance().badMtprotoConfigurationError();
}
}
void Instance::Private::requestConfigIfOld() {
const auto timeout = Global::BlockedMode()
? kConfigBecomesOldForBlockedIn
@@ -479,8 +579,11 @@ void Instance::Private::stopSession(ShiftedDcId shiftedDcId) {
}
void Instance::Private::reInitConnection(DcId dcId) {
killSession(dcId);
getSession(dcId)->notifyLayerInited(false);
for (auto &session : _sessions) {
if (bareDcId(session.second->getDcWithShift()) == dcId) {
session.second->reInitConnection();
}
}
}
void Instance::Private::logout(
@@ -942,10 +1045,13 @@ void Instance::Private::importDone(const MTPauth_Authorization &result, mtpReque
const auto shiftedDcId = queryRequestByDc(requestId);
if (!shiftedDcId) {
LOG(("MTP Error: auth import request not found in requestsByDC, requestId: %1").arg(requestId));
RPCError error(internal::rpcClientError("AUTH_IMPORT_FAIL", QString("did not find import request in requestsByDC, request %1").arg(requestId)));
if (_globalHandler.onFail && hasAuthorization()) {
(*_globalHandler.onFail)(requestId, error); // auth failed in main dc
}
//
// Don't log out on export/import problems, perhaps this is a server side error.
//
//RPCError error(internal::rpcClientError("AUTH_IMPORT_FAIL", QString("did not find import request in requestsByDC, request %1").arg(requestId)));
//if (_globalHandler.onFail && hasAuthorization()) {
// (*_globalHandler.onFail)(requestId, error); // auth failed in main dc
//}
return;
}
auto newdc = bareDcId(*shiftedDcId);
@@ -980,9 +1086,12 @@ void Instance::Private::importDone(const MTPauth_Authorization &result, mtpReque
bool Instance::Private::importFail(const RPCError &error, mtpRequestId requestId) {
if (isDefaultHandledError(error)) return false;
if (_globalHandler.onFail && hasAuthorization()) {
(*_globalHandler.onFail)(requestId, error); // auth import failed
}
//
// Don't log out on export/import problems, perhaps this is a server side error.
//
//if (_globalHandler.onFail && hasAuthorization()) {
// (*_globalHandler.onFail)(requestId, error); // auth import failed
//}
return true;
}
@@ -990,10 +1099,13 @@ void Instance::Private::exportDone(const MTPauth_ExportedAuthorization &result,
auto it = _authExportRequests.find(requestId);
if (it == _authExportRequests.cend()) {
LOG(("MTP Error: auth export request target dcWithShift not found, requestId: %1").arg(requestId));
RPCError error(internal::rpcClientError("AUTH_IMPORT_FAIL", QString("did not find target dcWithShift, request %1").arg(requestId)));
if (_globalHandler.onFail && hasAuthorization()) {
(*_globalHandler.onFail)(requestId, error); // auth failed in main dc
}
//
// Don't log out on export/import problems, perhaps this is a server side error.
//
//RPCError error(internal::rpcClientError("AUTH_IMPORT_FAIL", QString("did not find target dcWithShift, request %1").arg(requestId)));
//if (_globalHandler.onFail && hasAuthorization()) {
// (*_globalHandler.onFail)(requestId, error); // auth failed in main dc
//}
return;
}
@@ -1013,9 +1125,12 @@ bool Instance::Private::exportFail(const RPCError &error, mtpRequestId requestId
if (it != _authExportRequests.cend()) {
_authWaiters[bareDcId(it->second)].clear();
}
if (_globalHandler.onFail && hasAuthorization()) {
(*_globalHandler.onFail)(requestId, error); // auth failed in main dc
}
//
// Don't log out on export/import problems, perhaps this is a server side error.
//
//if (_globalHandler.onFail && hasAuthorization()) {
// (*_globalHandler.onFail)(requestId, error); // auth failed in main dc
//}
return true;
}
@@ -1250,13 +1365,17 @@ internal::Session *Instance::Private::getSession(ShiftedDcId shiftedDcId) {
void Instance::Private::scheduleKeyDestroy(ShiftedDcId shiftedDcId) {
Expects(isKeysDestroyer());
_instance->send(MTPauth_LogOut(), rpcDone([this, shiftedDcId](const MTPBool &result) {
if (dcOptions()->dcType(shiftedDcId) == DcType::Cdn) {
performKeyDestroy(shiftedDcId);
}), rpcFail([this, shiftedDcId](const RPCError &error) {
if (isDefaultHandledError(error)) return false;
performKeyDestroy(shiftedDcId);
return true;
}), shiftedDcId);
} else {
_instance->send(MTPauth_LogOut(), rpcDone([=](const MTPBool &) {
performKeyDestroy(shiftedDcId);
}), rpcFail([=](const RPCError &error) {
if (isDefaultHandledError(error)) return false;
performKeyDestroy(shiftedDcId);
return true;
}), shiftedDcId);
}
}
void Instance::Private::performKeyDestroy(ShiftedDcId shiftedDcId) {
@@ -1330,11 +1449,20 @@ void Instance::Private::prepareToDestroy() {
MustNotCreateSessions = true;
}
Instance::Instance(not_null<DcOptions*> options, Mode mode, Config &&config) : QObject()
Instance::Instance(not_null<DcOptions*> options, Mode mode, Config &&config)
: QObject()
, _private(std::make_unique<Private>(this, options, mode)) {
_private->start(std::move(config));
}
void Instance::resolveProxyDomain(const QString &host) {
_private->resolveProxyDomain(host);
}
void Instance::setGoodProxyDomain(const QString &host, const QString &ip) {
_private->setGoodProxyDomain(host, ip);
}
void Instance::suggestMainDcId(DcId mainDcId) {
_private->suggestMainDcId(mainDcId);
}
@@ -1363,6 +1491,10 @@ void Instance::setUserPhone(const QString &phone) {
_private->setUserPhone(phone);
}
void Instance::badConfigurationError() {
_private->badConfigurationError();
}
void Instance::requestConfigIfOld() {
_private->requestConfigIfOld();
}

View File

@@ -46,6 +46,8 @@ public:
Instance(const Instance &other) = delete;
Instance &operator=(const Instance &other) = delete;
void resolveProxyDomain(const QString &host);
void setGoodProxyDomain(const QString &host, const QString &ip);
void suggestMainDcId(DcId mainDcId);
void setMainDcId(DcId mainDcId);
DcId mainDcId() const;
@@ -138,6 +140,7 @@ public:
void requestConfigIfOld();
void requestCDNConfig();
void setUserPhone(const QString &phone);
void badConfigurationError();
~Instance();
@@ -149,6 +152,10 @@ signals:
void cdnConfigLoaded();
void keyDestroyed(qint32 shiftedDcId);
void allKeysDestroyed();
void proxyDomainResolved(
QString host,
QStringList ips,
qint64 expireAt);
private slots:
void onKeyDestroyed(qint32 shiftedDcId);

View File

@@ -59,6 +59,19 @@ void SessionData::setKey(const AuthKeyPtr &key) {
}
}
void SessionData::notifyConnectionInited(const ConnectionOptions &options) {
QWriteLocker locker(&_lock);
if (options.cloudLangCode == _options.cloudLangCode
&& options.systemLangCode == _options.systemLangCode
&& options.proxy == _options.proxy
&& !_options.inited) {
_options.inited = true;
locker.unlock();
owner()->notifyDcConnectionInited();
}
}
void SessionData::clear(Instance *instance) {
auto clearCallbacks = std::vector<RPCCallbackClear>();
{
@@ -110,7 +123,7 @@ Session::Session(not_null<Instance*> instance, ShiftedDcId shiftedDcId) : QObjec
connect(&timeouter, SIGNAL(timeout()), this, SLOT(checkRequestsByTimer()));
timeouter.start(1000);
refreshDataFields();
refreshOptions();
connect(&sender, SIGNAL(timeout()), this, SLOT(needToResumeAndSend()));
}
@@ -133,11 +146,11 @@ void Session::createDcData() {
if (auto lock = ReadLockerAttempt(keyMutex())) {
data.setKey(dc->getKey());
if (dc->connectionInited()) {
data.setLayerWasInited(true);
data.setConnectionInited();
}
}
connect(dc.get(), SIGNAL(authKeyCreated()), this, SLOT(authKeyCreatedForDC()), Qt::QueuedConnection);
connect(dc.get(), SIGNAL(layerWasInited(bool)), this, SLOT(layerWasInitedForDC(bool)), Qt::QueuedConnection);
connect(dc.get(), SIGNAL(connectionWasInited()), this, SLOT(connectionWasInitedForDC()), Qt::QueuedConnection);
}
void Session::registerRequest(mtpRequestId requestId, ShiftedDcId dcWithShift) {
@@ -163,11 +176,11 @@ void Session::restart() {
DEBUG_LOG(("Session Error: can't restart a killed session"));
return;
}
refreshDataFields();
refreshOptions();
emit needToRestart();
}
void Session::refreshDataFields() {
void Session::refreshOptions() {
const auto &proxy = Global::SelectedProxy();
const auto proxyType = Global::UseProxy()
? proxy.type
@@ -176,7 +189,7 @@ void Session::refreshDataFields() {
const auto useHttp = (proxyType != ProxyData::Type::Mtproto);
const auto useIPv4 = true;
const auto useIPv6 = Global::TryIPv6();
data.setConnectionOptions(ConnectionOptions(
data.applyConnectionOptions(ConnectionOptions(
_instance->systemLangCode(),
_instance->cloudLangCode(),
Global::UseProxy() ? proxy : ProxyData(),
@@ -186,6 +199,12 @@ void Session::refreshDataFields() {
useTcp));
}
void Session::reInitConnection() {
dc->setConnectionInited(false);
data.setConnectionInited(false);
restart();
}
void Session::stop() {
if (_killed) {
DEBUG_LOG(("Session Error: can't kill a killed session"));
@@ -548,15 +567,15 @@ void Session::notifyKeyCreated(AuthKeyPtr &&key) {
dc->setKey(std::move(key));
}
void Session::layerWasInitedForDC(bool wasInited) {
DEBUG_LOG(("MTP Info: Session::layerWasInitedForDC slot, dcWithShift %1").arg(dcWithShift));
data.setLayerWasInited(wasInited);
void Session::connectionWasInitedForDC() {
DEBUG_LOG(("MTP Info: Session::connectionWasInitedForDC slot, dcWithShift %1").arg(dcWithShift));
data.setConnectionInited();
}
void Session::notifyLayerInited(bool wasInited) {
DEBUG_LOG(("MTP Info: emitting MTProtoDC::layerWasInited(%1), dcWithShift %2").arg(Logs::b(wasInited)).arg(dcWithShift));
dc->setConnectionInited(wasInited);
emit dc->layerWasInited(wasInited);
void Session::notifyDcConnectionInited() {
DEBUG_LOG(("MTP Info: emitting MTProtoDC::connectionWasInited(), dcWithShift %1").arg(dcWithShift));
dc->setConnectionInited();
emit dc->connectionWasInited();
}
void Session::destroyKey() {

View File

@@ -105,6 +105,7 @@ struct ConnectionOptions {
bool useIPv6 = true;
bool useHttp = true;
bool useTcp = true;
bool inited = false;
};
@@ -127,18 +128,16 @@ public:
QReadLocker locker(&_lock);
return _session;
}
bool layerWasInited() const {
QReadLocker locker(&_lock);
return _layerInited;
}
void setLayerWasInited(bool was) {
void setConnectionInited(bool inited = true) {
QWriteLocker locker(&_lock);
_layerInited = was;
_options.inited = inited;
}
void setConnectionOptions(ConnectionOptions options) {
void notifyConnectionInited(const ConnectionOptions &options);
void applyConnectionOptions(ConnectionOptions options) {
QWriteLocker locker(&_lock);
const auto inited = _options.inited;
_options = options;
_options.inited = inited;
}
ConnectionOptions connectionOptions() const {
QReadLocker locker(&_lock);
@@ -300,6 +299,8 @@ public:
void start();
void restart();
void refreshOptions();
void reInitConnection();
void stop();
void kill();
@@ -310,7 +311,7 @@ public:
QReadWriteLock *keyMutex() const;
void notifyKeyCreated(AuthKeyPtr &&key);
void destroyKey();
void notifyLayerInited(bool wasInited);
void notifyDcConnectionInited();
void ping();
void cancel(mtpRequestId requestId, mtpMsgId msgId);
@@ -348,7 +349,7 @@ public slots:
void resendAll(); // after connection restart
void authKeyCreatedForDC();
void layerWasInitedForDC(bool wasInited);
void connectionWasInitedForDC();
void tryToReceive();
void checkRequestsByTimer();
@@ -361,7 +362,6 @@ public slots:
private:
void createDcData();
void refreshDataFields();
void registerRequest(mtpRequestId requestId, ShiftedDcId dcWithShift);
mtpRequestId storeRequest(

View File

@@ -16,7 +16,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace MTP {
namespace {
struct DnsEntry {
QString data;
int64 TTL = 0;
};
constexpr auto kSendNextTimeout = TimeMs(1000);
constexpr auto kMinTimeToLive = 10 * TimeMs(1000);
constexpr auto kMaxTimeToLive = 300 * TimeMs(1000);
constexpr auto kPublicKey = str_const("\
-----BEGIN RSA PUBLIC KEY-----\n\
@@ -32,6 +39,16 @@ Y1hZCxdv6cs5UnW9+PWvS+WIbkh+GaWYxwIDAQAB\n\
constexpr auto kUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36";
const auto &DnsDomains() {
static auto result = std::vector<QString>{
qsl("google.com"),
qsl("www.google.com"),
qsl("google.ru"),
qsl("www.google.ru"),
};
return result;
}
bool CheckPhoneByPrefixesRules(const QString &phone, const QString &rules) {
const auto check = QString(phone).replace(
QRegularExpression("[^0-9]"),
@@ -49,15 +66,15 @@ bool CheckPhoneByPrefixesRules(const QString &phone, const QString &rules) {
return result;
}
QByteArray ParseDnsResponse(const QByteArray &response) {
// Read and store to "entries" map all the data bytes from the response:
std::vector<DnsEntry> ParseDnsResponse(const QByteArray &response) {
// Read and store to "result" all the data bytes from the response:
// { ..,
// "Answer": [
// { .., "data": "bytes1", .. },
// { .., "data": "bytes2", .. }
// { .., "data": "bytes1", "TTL": int, .. },
// { .., "data": "bytes2", "TTL": int, .. }
// ],
// .. }
auto entries = QMap<int, QString>();
auto result = std::vector<DnsEntry>();
auto error = QJsonParseError{ 0, QJsonParseError::NoError };
auto document = QJsonDocument::fromJson(response, &error);
if (error.error != QJsonParseError::NoError) {
@@ -82,6 +99,10 @@ QByteArray ParseDnsResponse(const QByteArray &response) {
} else {
auto object = elem.toObject();
auto dataIt = object.find(qsl("data"));
auto ttlIt = object.find(qsl("TTL"));
auto ttl = (ttlIt != object.constEnd())
? int64(std::round((*ttlIt).toDouble()))
: int64(0);
if (dataIt == object.constEnd()) {
LOG(("Config Error: Could not find data "
"in Answer array entry in dns response JSON."));
@@ -89,27 +110,34 @@ QByteArray ParseDnsResponse(const QByteArray &response) {
LOG(("Config Error: Not a string data found "
"in Answer array entry in dns response JSON."));
} else {
auto data = (*dataIt).toString();
entries.insertMulti(INT_MAX - data.size(), data);
result.push_back({ (*dataIt).toString(), ttl });
}
}
}
}
}
return result;
}
QByteArray ConcatenateDnsTxtFields(const std::vector<DnsEntry> &response) {
auto entries = QMap<int, QString>();
for (const auto &entry : response) {
entries.insertMulti(INT_MAX - entry.data.size(), entry.data);
}
return QStringList(entries.values()).join(QString()).toLatin1();
}
} // namespace
SpecialConfigRequest::Request::Request(not_null<QNetworkReply*> reply)
ServiceWebRequest::ServiceWebRequest(not_null<QNetworkReply*> reply)
: reply(reply.get()) {
}
SpecialConfigRequest::Request::Request(Request &&other)
ServiceWebRequest::ServiceWebRequest(ServiceWebRequest &&other)
: reply(base::take(other.reply)) {
}
auto SpecialConfigRequest::Request::operator=(Request &&other) -> Request& {
ServiceWebRequest &ServiceWebRequest::operator=(ServiceWebRequest &&other) {
if (reply != other.reply) {
destroy();
reply = base::take(other.reply);
@@ -117,14 +145,14 @@ auto SpecialConfigRequest::Request::operator=(Request &&other) -> Request& {
return *this;
}
void SpecialConfigRequest::Request::destroy() {
void ServiceWebRequest::destroy() {
if (const auto value = base::take(reply)) {
value->deleteLater();
value->abort();
}
}
SpecialConfigRequest::Request::~Request() {
ServiceWebRequest::~ServiceWebRequest() {
destroy();
}
@@ -137,13 +165,13 @@ SpecialConfigRequest::SpecialConfigRequest(
const QString &phone)
: _callback(std::move(callback))
, _phone(phone) {
_manager.setProxy(QNetworkProxy::NoProxy);
_attempts = {
{ Type::App, qsl("software-download.microsoft.com") },
{ Type::Dns, qsl("google.com") },
{ Type::Dns, qsl("www.google.com") },
{ Type::Dns, qsl("google.ru") },
{ Type::Dns, qsl("www.google.ru") },
};
for (const auto &domain : DnsDomains()) {
_attempts.push_back({ Type::Dns, domain });
}
std::random_device rd;
ranges::shuffle(_attempts, std::mt19937(rd()));
sendNextRequest();
@@ -171,7 +199,7 @@ void SpecialConfigRequest::performRequest(const Attempt &attempt) {
switch (type) {
case Type::App: {
url.setPath(cTestMode()
? qsl("/test/config.txt")
? qsl("/testv2/config.txt")
: qsl("/prodv2/config.txt"));
request.setRawHeader("Host", "tcdnb.azureedge.net");
} break;
@@ -179,7 +207,7 @@ void SpecialConfigRequest::performRequest(const Attempt &attempt) {
url.setPath(qsl("/resolve"));
url.setQuery(
qsl("name=%1.stel.com&type=16").arg(
cTestMode() ? qsl("tap") : qsl("apv2")));
cTestMode() ? qsl("testapv2") : qsl("apv2")));
request.setRawHeader("Host", "dns.google.com");
} break;
default: Unexpected("Type in SpecialConfigRequest::performRequest.");
@@ -200,7 +228,8 @@ void SpecialConfigRequest::requestFinished(
const auto result = finalizeRequest(reply);
switch (type) {
case Type::App: handleResponse(result); break;
case Type::Dns: handleResponse(ParseDnsResponse(result)); break;
case Type::Dns: handleResponse(
ConcatenateDnsTxtFields(ParseDnsResponse(result))); break;
default: Unexpected("Type in SpecialConfigRequest::requestFinished.");
}
}
@@ -217,7 +246,7 @@ QByteArray SpecialConfigRequest::finalizeRequest(
const auto from = ranges::remove(
_requests,
reply,
[](const Request &request) { return request.reply; });
[](const ServiceWebRequest &request) { return request.reply; });
_requests.erase(from, end(_requests));
return result;
}
@@ -347,4 +376,144 @@ void SpecialConfigRequest::handleResponse(const QByteArray &bytes) {
}
}
DomainResolver::DomainResolver(base::lambda<void(
const QString &host,
const QStringList &ips,
TimeMs expireAt)> callback)
: _callback(std::move(callback)) {
_manager.setProxy(QNetworkProxy::NoProxy);
}
void DomainResolver::resolve(const QString &domain) {
resolve({ domain, false });
resolve({ domain, true });
}
void DomainResolver::resolve(const AttemptKey &key) {
if (_attempts.find(key) != end(_attempts)) {
return;
} else if (_requests.find(key) != end(_requests)) {
return;
}
const auto i = _cache.find(key);
_lastTimestamp = getms(true);
if (i != end(_cache) && i->second.expireAt > _lastTimestamp) {
checkExpireAndPushResult(key.domain);
return;
}
auto hosts = DnsDomains();
std::random_device rd;
ranges::shuffle(hosts, std::mt19937(rd()));
_attempts.emplace(key, std::move(hosts));
sendNextRequest(key);
}
void DomainResolver::checkExpireAndPushResult(const QString &domain) {
const auto ipv4 = _cache.find({ domain, false });
if (ipv4 == end(_cache) || ipv4->second.expireAt <= _lastTimestamp) {
return;
}
auto result = ipv4->second;
const auto ipv6 = _cache.find({ domain, true });
if (ipv6 != end(_cache) && ipv6->second.expireAt > _lastTimestamp) {
result.ips.append(ipv6->second.ips);
accumulate_min(result.expireAt, ipv6->second.expireAt);
}
InvokeQueued(this, [=] {
_callback(domain, result.ips, result.expireAt);
});
}
void DomainResolver::sendNextRequest(const AttemptKey &key) {
auto i = _attempts.find(key);
if (i == end(_attempts)) {
return;
}
auto &hosts = i->second;
const auto host = hosts.back();
hosts.pop_back();
if (!hosts.empty()) {
App::CallDelayed(kSendNextTimeout, this, [=] {
sendNextRequest(key);
});
}
performRequest(key, host);
}
void DomainResolver::performRequest(
const AttemptKey &key,
const QString &host) {
auto url = QUrl();
url.setScheme(qsl("https"));
url.setHost(host);
url.setPath(qsl("/resolve"));
url.setQuery(
qsl("name=%1&type=%2").arg(key.domain).arg(key.ipv6 ? 28 : 1));
auto request = QNetworkRequest();
request.setRawHeader("Host", "dns.google.com");
request.setUrl(url);
request.setRawHeader("User-Agent", kUserAgent);
const auto i = _requests.emplace(
key,
std::vector<ServiceWebRequest>()).first;
const auto reply = i->second.emplace_back(
_manager.get(request)
).reply;
connect(reply, &QNetworkReply::finished, this, [=] {
requestFinished(key, reply);
});
}
void DomainResolver::requestFinished(
const AttemptKey &key,
not_null<QNetworkReply*> reply) {
const auto result = finalizeRequest(key, reply);
const auto response = ParseDnsResponse(result);
if (response.empty()) {
return;
}
_requests.erase(key);
_attempts.erase(key);
auto entry = CacheEntry();
auto ttl = kMaxTimeToLive;
for (const auto &item : response) {
entry.ips.push_back(item.data);
accumulate_min(ttl, std::max(
item.TTL * TimeMs(1000),
kMinTimeToLive));
}
_lastTimestamp = getms(true);
entry.expireAt = _lastTimestamp + ttl;
_cache[key] = std::move(entry);
checkExpireAndPushResult(key.domain);
}
QByteArray DomainResolver::finalizeRequest(
const AttemptKey &key,
not_null<QNetworkReply*> reply) {
if (reply->error() != QNetworkReply::NoError) {
LOG(("Resolve Error: Failed to get response from %1, error: %2 (%3)"
).arg(reply->request().url().toDisplayString()
).arg(reply->errorString()
).arg(reply->error()));
}
const auto result = reply->readAll();
const auto i = _requests.find(key);
if (i != end(_requests)) {
auto &requests = i->second;
const auto from = ranges::remove(
requests,
reply,
[](const ServiceWebRequest &request) { return request.reply; });
requests.erase(from, end(requests));
if (requests.empty()) {
_requests.erase(i);
}
}
return result;
}
} // namespace MTP

View File

@@ -11,6 +11,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace MTP {
struct ServiceWebRequest {
ServiceWebRequest(not_null<QNetworkReply*> reply);
ServiceWebRequest(ServiceWebRequest &&other);
ServiceWebRequest &operator=(ServiceWebRequest &&other);
~ServiceWebRequest();
void destroy();
QPointer<QNetworkReply> reply;
};
class SpecialConfigRequest : public QObject {
public:
SpecialConfigRequest(
@@ -30,17 +42,6 @@ private:
Type type;
QString domain;
};
struct Request {
Request(not_null<QNetworkReply*> reply);
Request(Request &&other);
Request &operator=(Request &&other);
~Request();
void destroy();
QPointer<QNetworkReply> reply;
};
void sendNextRequest();
void performRequest(const Attempt &attempt);
@@ -59,7 +60,60 @@ private:
QNetworkAccessManager _manager;
std::vector<Attempt> _attempts;
std::vector<Request> _requests;
std::vector<ServiceWebRequest> _requests;
};
class DomainResolver : public QObject {
public:
DomainResolver(base::lambda<void(
const QString &domain,
const QStringList &ips,
TimeMs expireAt)> callback);
void resolve(const QString &domain);
private:
struct AttemptKey {
QString domain;
bool ipv6 = false;
inline bool operator<(const AttemptKey &other) const {
return (domain < other.domain)
|| (domain == other.domain && !ipv6 && other.ipv6);
}
inline bool operator==(const AttemptKey &other) const {
return (domain == other.domain) && (ipv6 == other.ipv6);
}
};
struct CacheEntry {
QStringList ips;
TimeMs expireAt = 0;
};
void resolve(const AttemptKey &key);
void sendNextRequest(const AttemptKey &key);
void performRequest(const AttemptKey &key, const QString &host);
void checkExpireAndPushResult(const QString &domain);
void requestFinished(
const AttemptKey &key,
not_null<QNetworkReply*> reply);
QByteArray finalizeRequest(
const AttemptKey &key,
not_null<QNetworkReply*> reply);
base::lambda<void(
const QString &domain,
const QStringList &ips,
TimeMs expireAt)> _callback;
QNetworkAccessManager _manager;
std::map<AttemptKey, std::vector<QString>> _attempts;
std::map<AttemptKey, std::vector<ServiceWebRequest>> _requests;
std::map<AttemptKey, CacheEntry> _cache;
TimeMs _lastTimestamp = 0;
};

View File

@@ -64,6 +64,7 @@ struct PeerUpdate {
ChannelRightsChanged = (1 << 17),
ChannelStickersChanged = (1 << 18),
ChannelPinnedChanged = (1 << 19),
ChannelPromotedChanged = (1 << 20),
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }

View File

@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Platform {
class MainWindow : public Window::MainWindow {
// The Q_OBJECT meta info is used for qobject_cast to MainWindow!
Q_OBJECT
public:

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