Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f7df6987c | ||
|
|
3d75d21a3e | ||
|
|
250718e766 | ||
|
|
a5cbade8ec | ||
|
|
3ac50cf77f | ||
|
|
f35bf41d26 | ||
|
|
f1816815a9 | ||
|
|
37bf9ffcff | ||
|
|
5e7642b42a | ||
|
|
6f6ec217e3 | ||
|
|
017ec87d60 | ||
|
|
30dd8fe070 | ||
|
|
8e442563f2 | ||
|
|
c43dcf0567 | ||
|
|
f2a5a29d12 | ||
|
|
678b9a8eb5 | ||
|
|
96f7c0c02e | ||
|
|
66b7b6da2a | ||
|
|
38daffdbfe | ||
|
|
0238c03956 | ||
|
|
425e56b3ea | ||
|
|
4478c0a143 | ||
|
|
a053384618 | ||
|
|
694e8cd19f | ||
|
|
4bcd1e3c59 | ||
|
|
5f063c0151 | ||
|
|
5a1d4d55c6 | ||
|
|
d3f85b4c4e | ||
|
|
df9ec4b466 | ||
|
|
4f9507ed97 | ||
|
|
f761b6aa9e | ||
|
|
168a7ce2e5 | ||
|
|
4b763a76df | ||
|
|
8764da787b | ||
|
|
7d8ba15252 | ||
|
|
96c0c30f7c | ||
|
|
bb6ab5314c | ||
|
|
e3c6abfc3d | ||
|
|
5c5bccae0b | ||
|
|
296e009808 | ||
|
|
4d84781a65 | ||
|
|
710b9bf454 |
@@ -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 *
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -781,3 +781,4 @@ proxyDropdownDownPosition: point(-2px, 35px);
|
||||
proxyDropdownUpPosition: point(-2px, 20px);
|
||||
|
||||
proxyAboutPadding: margins(22px, 7px, 22px, 14px);
|
||||
proxyAboutSponsorPadding: margins(22px, 7px, 22px, 0px);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
@@ -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 }, ">(" },
|
||||
|
||||
@@ -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\
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
|
||||
@@ -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 ®ion, 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 ®ion, 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 ®ion, 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 ®ion, 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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -149,6 +149,7 @@ mediaviewSaveMsgStyle: TextStyle(defaultTextStyle) {
|
||||
}
|
||||
mediaviewTextPalette: TextPalette(defaultTextPalette) {
|
||||
linkFg: mediaviewTextLinkFg;
|
||||
monoFg: mediaviewCaptionFg;
|
||||
}
|
||||
|
||||
mediaviewCaptionStyle: defaultTextStyle;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
240
Telegram/SourceFiles/mtproto/connection_resolving.cpp
Normal file
240
Telegram/SourceFiles/mtproto/connection_resolving.cpp
Normal 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
|
||||
70
Telegram/SourceFiles/mtproto/connection_resolving.h
Normal file
70
Telegram/SourceFiles/mtproto/connection_resolving.h
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 ¤t = 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 ¤t = 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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user