From cc52478a93e1b75f7fd3f34f0faf0bc2115ef542 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Fri, 3 Feb 2023 14:46:00 +0100 Subject: LanguageClient: Export LanguageClientOutlineItem To enable more customizations by specialized clients. Change-Id: I0ad92e248e931389c3fa239df424df8883e1d86e Reviewed-by: Qt CI Bot Reviewed-by: Reviewed-by: David Schulz --- src/libs/languageserverprotocol/lsptypes.h | 1 - 1 file changed, 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/languageserverprotocol/lsptypes.h b/src/libs/languageserverprotocol/lsptypes.h index 239d40f662..4adc35aa10 100644 --- a/src/libs/languageserverprotocol/lsptypes.h +++ b/src/libs/languageserverprotocol/lsptypes.h @@ -556,7 +556,6 @@ enum class SymbolKind { TypeParameter = 26, LastSymbolKind = TypeParameter, }; -using SymbolStringifier = std::function; namespace CompletionItemKind { enum Kind { -- cgit v1.2.3 From 207f2b216c5937c737f46bf2aacebaef93c3ffb3 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Wed, 8 Feb 2023 17:18:13 +0100 Subject: CPlusPlus: Add lexer support for new C++20 keywords Change-Id: I2b83deb0502ebf2cdca2af774fbb2ce26e947c11 Reviewed-by: Christian Stenger --- src/libs/3rdparty/cplusplus/Keywords.cpp | 104 +++++++++++++++++++++++++++-- src/libs/3rdparty/cplusplus/Keywords.kwgen | 10 +++ src/libs/3rdparty/cplusplus/Token.cpp | 8 +++ src/libs/3rdparty/cplusplus/Token.h | 8 +++ src/libs/cplusplus/cplusplus.qbs | 1 + 5 files changed, 127 insertions(+), 4 deletions(-) (limited to 'src/libs') diff --git a/src/libs/3rdparty/cplusplus/Keywords.cpp b/src/libs/3rdparty/cplusplus/Keywords.cpp index 1637333b42..a60ec24aec 100644 --- a/src/libs/3rdparty/cplusplus/Keywords.cpp +++ b/src/libs/3rdparty/cplusplus/Keywords.cpp @@ -603,6 +603,34 @@ static inline int classify7(const char *s, LanguageFeatures features) } } } + else if (features.cxx20Enabled && s[0] == 'c') { + if (s[1] == 'h') { + if (s[2] == 'a') { + if (s[3] == 'r') { + if (s[4] == '8') { + if (s[5] == '_') { + if (s[6] == 't') { + return T_CHAR8_T; + } + } + } + } + } + } + else if (s[1] == 'o') { + if (s[2] == 'n') { + if (s[3] == 'c') { + if (s[4] == 'e') { + if (s[5] == 'p') { + if (s[6] == 't') { + return T_CONCEPT; + } + } + } + } + } + } + } else if (s[0] == 'd') { if (s[1] == 'e') { if (s[2] == 'f') { @@ -847,7 +875,31 @@ static inline int classify8(const char *s, LanguageFeatures features) } } else if (s[1] == 'o') { - if (s[2] == 'n') { + if (features.cxx20Enabled && s[2] == '_') { + if (s[3] == 'a') { + if (s[4] == 'w') { + if (s[5] == 'a') { + if (s[6] == 'i') { + if (s[7] == 't') { + return T_CO_AWAIT; + } + } + } + } + } + else if (s[3] == 'y') { + if (s[4] == 'i') { + if (s[5] == 'e') { + if (s[6] == 'l') { + if (s[7] == 'd') { + return T_CO_YIELD; + } + } + } + } + } + } + else if (s[2] == 'n') { if (s[3] == 't') { if (s[4] == 'i') { if (s[5] == 'n') { @@ -945,6 +997,19 @@ static inline int classify8(const char *s, LanguageFeatures features) } } } + else if (features.cxx20Enabled && s[2] == 'q') { + if (s[3] == 'u') { + if (s[4] == 'i') { + if (s[5] == 'r') { + if (s[6] == 'e') { + if (s[7] == 's') { + return T_REQUIRES; + } + } + } + } + } + } } } else if (features.cxxEnabled && s[0] == 't') { @@ -1097,13 +1162,35 @@ static inline int classify9(const char *s, LanguageFeatures features) } } } - else if (features.cxx11Enabled && s[0] == 'c') { + else if (s[0] == 'c') { if (s[1] == 'o') { - if (s[2] == 'n') { + if (features.cxx20Enabled && s[2] == '_') { + if (s[3] == 'r') { + if (s[4] == 'e') { + if (s[5] == 't') { + if (s[6] == 'u') { + if (s[7] == 'r') { + if (s[8] == 'n') { + return T_CO_RETURN; + } + } + } + } + } + } + } + else if (s[2] == 'n') { if (s[3] == 's') { if (s[4] == 't') { if (s[5] == 'e') { - if (s[6] == 'x') { + if (features.cxx20Enabled && s[6] == 'v') { + if (s[7] == 'a') { + if (s[8] == 'l') { + return T_CONSTEVAL; + } + } + } + else if (features.cxx11Enabled && s[6] == 'x') { if (s[7] == 'p') { if (s[8] == 'r') { return T_CONSTEXPR; @@ -1111,6 +1198,15 @@ static inline int classify9(const char *s, LanguageFeatures features) } } } + else if (features.cxx20Enabled && s[5] == 'i') { + if (s[6] == 'n') { + if (s[7] == 'i') { + if (s[8] == 't') { + return T_CONSTINIT; + } + } + } + } } } } diff --git a/src/libs/3rdparty/cplusplus/Keywords.kwgen b/src/libs/3rdparty/cplusplus/Keywords.kwgen index 36300a8776..70deeb648d 100644 --- a/src/libs/3rdparty/cplusplus/Keywords.kwgen +++ b/src/libs/3rdparty/cplusplus/Keywords.kwgen @@ -128,6 +128,16 @@ nullptr static_assert thread_local +%pre-check=features.cxx20Enabled +char8_t +concept +consteval +constinit +co_await +co_return +co_yield +requires + %pre-check=features.qtKeywordsEnabled emit foreach diff --git a/src/libs/3rdparty/cplusplus/Token.cpp b/src/libs/3rdparty/cplusplus/Token.cpp index 31fdb2036c..2e8a0ce7f2 100644 --- a/src/libs/3rdparty/cplusplus/Token.cpp +++ b/src/libs/3rdparty/cplusplus/Token.cpp @@ -120,9 +120,15 @@ const char *token_names[] = { ("case"), ("catch"), ("class"), + ("co_await"), + ("co_return"), + ("co_yield"), + ("concept"), ("const"), ("const_cast"), + ("consteval"), ("constexpr"), + ("constinit"), ("continue"), ("decltype"), ("default"), @@ -151,6 +157,7 @@ const char *token_names[] = { ("public"), ("register"), ("reinterpret_cast"), + ("requires"), ("return"), ("sizeof"), ("static"), @@ -210,6 +217,7 @@ const char *token_names[] = { // Primitive types ("bool"), ("char"), + ("char8_t"), ("char16_t"), ("char32_t"), ("double"), diff --git a/src/libs/3rdparty/cplusplus/Token.h b/src/libs/3rdparty/cplusplus/Token.h index 3a04a44a86..204096893a 100644 --- a/src/libs/3rdparty/cplusplus/Token.h +++ b/src/libs/3rdparty/cplusplus/Token.h @@ -130,9 +130,15 @@ enum Kind { T_CASE, T_CATCH, T_CLASS, + T_CO_AWAIT, + T_CO_RETURN, + T_CO_YIELD, + T_CONCEPT, T_CONST, T_CONST_CAST, + T_CONSTEVAL, T_CONSTEXPR, + T_CONSTINIT, T_CONTINUE, T_DECLTYPE, T_DEFAULT, @@ -161,6 +167,7 @@ enum Kind { T_PUBLIC, T_REGISTER, T_REINTERPRET_CAST, + T_REQUIRES, T_RETURN, T_SIZEOF, T_STATIC, @@ -223,6 +230,7 @@ enum Kind { T_FIRST_PRIMITIVE, T_BOOL = T_FIRST_PRIMITIVE, T_CHAR, + T_CHAR8_T, T_CHAR16_T, T_CHAR32_T, T_DOUBLE, diff --git a/src/libs/cplusplus/cplusplus.qbs b/src/libs/cplusplus/cplusplus.qbs index 80c6174ba2..0aed0ab438 100644 --- a/src/libs/cplusplus/cplusplus.qbs +++ b/src/libs/cplusplus/cplusplus.qbs @@ -41,6 +41,7 @@ Project { "FullySpecifiedType.cpp", "FullySpecifiedType.h", "Keywords.cpp", + "Keywords.kwgen", "Lexer.cpp", "Lexer.h", "LiteralTable.h", -- cgit v1.2.3 From db720d271da982470f444ec93beb7ca4d938f490 Mon Sep 17 00:00:00 2001 From: Artem Sokolovskii Date: Fri, 3 Feb 2023 13:18:59 +0100 Subject: MultiTextCursor: Optimize multitextcursor - Use map for merging intervals instead 2 times list iteration. - Time complexity O(nlogn) instead O(n^2) Change-Id: If65391999e1ff191752447935602fcc9847243fe Reviewed-by: David Schulz --- src/libs/utils/multitextcursor.cpp | 271 ++++++++++++++++++++++--------------- src/libs/utils/multitextcursor.h | 28 ++-- 2 files changed, 180 insertions(+), 119 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/multitextcursor.cpp b/src/libs/utils/multitextcursor.cpp index 2f598066ee..934ca17ed3 100644 --- a/src/libs/utils/multitextcursor.cpp +++ b/src/libs/utils/multitextcursor.cpp @@ -16,33 +16,130 @@ namespace Utils { MultiTextCursor::MultiTextCursor() {} MultiTextCursor::MultiTextCursor(const QList &cursors) - : m_cursors(cursors) { - mergeCursors(); + setCursors(cursors); +} + +void MultiTextCursor::fillMapWithList() +{ + m_cursorMap.clear(); + for (auto it = m_cursorList.begin(); it != m_cursorList.end(); ++it) + m_cursorMap[it->selectionStart()] = it; +} + +MultiTextCursor& MultiTextCursor::operator=(const MultiTextCursor &multiCursor) +{ + m_cursorList = multiCursor.m_cursorList; + fillMapWithList(); + return *this; +} + +MultiTextCursor::MultiTextCursor(const MultiTextCursor &multiCursor) +{ + *this = multiCursor; +} + +MultiTextCursor& MultiTextCursor::operator=(const MultiTextCursor &&multiCursor) +{ + m_cursorList = std::move(multiCursor.m_cursorList); + fillMapWithList(); + return *this; +} + +MultiTextCursor::MultiTextCursor(const MultiTextCursor &&multiCursor) +{ + *this = std::move(multiCursor); +} + +MultiTextCursor::~MultiTextCursor() = default; + +static bool cursorsOverlap(const QTextCursor &c1, const QTextCursor &c2) +{ + if (c1.hasSelection()) { + if (c2.hasSelection()) { + return c2.selectionEnd() > c1.selectionStart() + && c2.selectionStart() < c1.selectionEnd(); + } + const int c2Pos = c2.position(); + return c2Pos > c1.selectionStart() && c2Pos < c1.selectionEnd(); + } + if (c2.hasSelection()) { + const int c1Pos = c1.position(); + return c1Pos > c2.selectionStart() && c1Pos < c2.selectionEnd(); + } + return c1 == c2; +}; + +static void mergeCursors(QTextCursor &c1, const QTextCursor &c2) +{ + if (c1.position() == c2.position() && c1.anchor() == c2.anchor()) + return; + if (c1.hasSelection()) { + if (!c2.hasSelection()) + return; + int pos = c1.position(); + int anchor = c1.anchor(); + if (c1.selectionStart() > c2.selectionStart()) { + if (pos < anchor) + pos = c2.selectionStart(); + else + anchor = c2.selectionStart(); + } + if (c1.selectionEnd() < c2.selectionEnd()) { + if (pos < anchor) + anchor = c2.selectionEnd(); + else + pos = c2.selectionEnd(); + } + c1.setPosition(anchor); + c1.setPosition(pos, QTextCursor::KeepAnchor); + } else { + c1 = c2; + } } void MultiTextCursor::addCursor(const QTextCursor &cursor) { QTC_ASSERT(!cursor.isNull(), return); - m_cursors.append(cursor); - mergeCursors(); + + QTextCursor c1 = cursor; + const int pos = c1.selectionStart(); + + auto found = m_cursorMap.lower_bound(pos); + if (found != m_cursorMap.begin()) + --found; + + for (; !m_cursorMap.empty() && found != m_cursorMap.end() + && found->second->selectionStart() <= cursor.selectionEnd();) { + const QTextCursor &c2 = *found->second; + if (cursorsOverlap(c1, c2)) { + Utils::mergeCursors(c1, c2); + m_cursorList.erase(found->second); + found = m_cursorMap.erase(found); + continue; + } + ++found; + } + + m_cursorMap[pos] = m_cursorList.insert(m_cursorList.end(), c1); } void MultiTextCursor::addCursors(const QList &cursors) { - m_cursors.append(cursors); - mergeCursors(); + for (const QTextCursor &c : cursors) + addCursor(c); } void MultiTextCursor::setCursors(const QList &cursors) { - m_cursors = cursors; - mergeCursors(); + m_cursorList.clear(); + m_cursorMap.clear(); + addCursors(cursors); } const QList MultiTextCursor::cursors() const { - return m_cursors; + return QList(m_cursorList.begin(), m_cursorList.end()); } void MultiTextCursor::replaceMainCursor(const QTextCursor &cursor) @@ -54,64 +151,72 @@ void MultiTextCursor::replaceMainCursor(const QTextCursor &cursor) QTextCursor MultiTextCursor::mainCursor() const { - if (m_cursors.isEmpty()) + if (m_cursorList.empty()) return {}; - return m_cursors.last(); + return m_cursorList.back(); } QTextCursor MultiTextCursor::takeMainCursor() { - if (m_cursors.isEmpty()) + if (m_cursorList.empty()) return {}; - return m_cursors.takeLast(); + + QTextCursor cursor = m_cursorList.back(); + auto it = m_cursorList.end(); + --it; + m_cursorMap.erase(it->selectionStart()); + m_cursorList.erase(it); + + return cursor; } void MultiTextCursor::beginEditBlock() { - QTC_ASSERT(!m_cursors.empty(), return); - m_cursors.last().beginEditBlock(); + QTC_ASSERT(!m_cursorList.empty(), return); + m_cursorList.back().beginEditBlock(); } void MultiTextCursor::endEditBlock() { - QTC_ASSERT(!m_cursors.empty(), return); - m_cursors.last().endEditBlock(); + QTC_ASSERT(!m_cursorList.empty(), return); + m_cursorList.back().endEditBlock(); } bool MultiTextCursor::isNull() const { - return m_cursors.isEmpty(); + return m_cursorList.empty(); } bool MultiTextCursor::hasMultipleCursors() const { - return m_cursors.size() > 1; + return m_cursorList.size() > 1; } int MultiTextCursor::cursorCount() const { - return m_cursors.size(); + return m_cursorList.size(); } void MultiTextCursor::movePosition(QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode, int n) { - for (QTextCursor &cursor : m_cursors) + for (auto &cursor : m_cursorList) cursor.movePosition(operation, mode, n); + mergeCursors(); } bool MultiTextCursor::hasSelection() const { - return Utils::anyOf(m_cursors, &QTextCursor::hasSelection); + return Utils::anyOf(m_cursorList, &QTextCursor::hasSelection); } QString MultiTextCursor::selectedText() const { QString text; - const QList cursors = Utils::sorted(m_cursors); - for (const QTextCursor &cursor : cursors) { + for (const auto &element : std::as_const(m_cursorMap)) { + const QTextCursor &cursor = *element.second; const QString &cursorText = cursor.selectedText(); if (cursorText.isEmpty()) continue; @@ -128,8 +233,8 @@ QString MultiTextCursor::selectedText() const void MultiTextCursor::removeSelectedText() { beginEditBlock(); - for (QTextCursor &c : m_cursors) - c.removeSelectedText(); + for (auto cursor = m_cursorList.begin(); cursor != m_cursorList.end(); ++cursor) + cursor->removeSelectedText(); endEditBlock(); mergeCursors(); } @@ -149,25 +254,27 @@ static void insertAndSelect(QTextCursor &cursor, const QString &text, bool selec void MultiTextCursor::insertText(const QString &text, bool selectNewText) { - if (m_cursors.isEmpty()) + if (m_cursorList.empty()) return; - m_cursors.last().beginEditBlock(); + + m_cursorList.back().beginEditBlock(); if (hasMultipleCursors()) { QStringList lines = text.split('\n'); if (!lines.isEmpty() && lines.last().isEmpty()) lines.pop_back(); int index = 0; - if (lines.count() == m_cursors.count()) { - QList cursors = Utils::sorted(m_cursors); - for (QTextCursor &cursor : cursors) + if (static_cast(lines.count()) == m_cursorList.size()) { + for (const auto &element : std::as_const(m_cursorMap)) { + QTextCursor &cursor = *element.second; insertAndSelect(cursor, lines.at(index++), selectNewText); - m_cursors.last().endEditBlock(); + } + m_cursorList.back().endEditBlock(); return; } } - for (QTextCursor &cursor : m_cursors) - insertAndSelect(cursor, text, selectNewText); - m_cursors.last().endEditBlock(); + for (auto cursor = m_cursorList.begin(); cursor != m_cursorList.end(); ++cursor) + insertAndSelect(*cursor, text, selectNewText); + m_cursorList.back().endEditBlock(); } bool equalCursors(const QTextCursor &lhs, const QTextCursor &rhs) @@ -177,17 +284,21 @@ bool equalCursors(const QTextCursor &lhs, const QTextCursor &rhs) bool MultiTextCursor::operator==(const MultiTextCursor &other) const { - if (m_cursors.size() != other.m_cursors.size()) + if (m_cursorList.size() != other.m_cursorList.size()) return false; - if (m_cursors.isEmpty()) + if (m_cursorList.empty()) return true; - QList thisCursors = m_cursors; - QList otherCursors = other.m_cursors; - if (!equalCursors(thisCursors.takeLast(), otherCursors.takeLast())) + + if (!equalCursors(m_cursorList.back(), other.m_cursorList.back())) return false; - for (const QTextCursor &oc : otherCursors) { - auto compare = [oc](const QTextCursor &c) { return equalCursors(oc, c); }; - if (!Utils::contains(thisCursors, compare)) + + auto it = m_cursorMap.begin(); + auto otherIt = other.m_cursorMap.begin(); + for (;it != m_cursorMap.end() && otherIt != other.m_cursorMap.end(); ++it, ++otherIt) { + const QTextCursor &cursor = *it->second; + const QTextCursor &otherCursor = *otherIt->second; + if (it->first != otherIt->first || cursor != otherCursor + || cursor.anchor() != otherCursor.anchor()) return false; } return true; @@ -198,70 +309,10 @@ bool MultiTextCursor::operator!=(const MultiTextCursor &other) const return !operator==(other); } -static bool cursorsOverlap(const QTextCursor &c1, const QTextCursor &c2) -{ - if (c1.hasSelection()) { - if (c2.hasSelection()) { - return c2.selectionEnd() > c1.selectionStart() - && c2.selectionStart() < c1.selectionEnd(); - } - const int c2Pos = c2.position(); - return c2Pos > c1.selectionStart() && c2Pos < c1.selectionEnd(); - } - if (c2.hasSelection()) { - const int c1Pos = c1.position(); - return c1Pos > c2.selectionStart() && c1Pos < c2.selectionEnd(); - } - return c1 == c2; -}; - -static void mergeCursors(QTextCursor &c1, const QTextCursor &c2) -{ - if (c1.position() == c2.position() && c1.anchor() == c2.anchor()) - return; - if (c1.hasSelection()) { - if (!c2.hasSelection()) - return; - int pos = c1.position(); - int anchor = c1.anchor(); - if (c1.selectionStart() > c2.selectionStart()) { - if (pos < anchor) - pos = c2.selectionStart(); - else - anchor = c2.selectionStart(); - } - if (c1.selectionEnd() < c2.selectionEnd()) { - if (pos < anchor) - anchor = c2.selectionEnd(); - else - pos = c2.selectionEnd(); - } - c1.setPosition(anchor); - c1.setPosition(pos, QTextCursor::KeepAnchor); - } else { - c1 = c2; - } -} - void MultiTextCursor::mergeCursors() { - std::list cursors(m_cursors.begin(), m_cursors.end()); - cursors = Utils::filtered(cursors, [](const QTextCursor &c){ - return !c.isNull(); - }); - for (auto it = cursors.begin(); it != cursors.end(); ++it) { - QTextCursor &c1 = *it; - for (auto other = std::next(it); other != cursors.end();) { - const QTextCursor &c2 = *other; - if (cursorsOverlap(c1, c2)) { - Utils::mergeCursors(c1, c2); - other = cursors.erase(other); - continue; - } - ++other; - } - } - m_cursors = QList(cursors.begin(), cursors.end()); + QList cursors(m_cursorList.begin(), m_cursorList.end()); + setCursors(cursors); } // could go into QTextCursor... @@ -321,7 +372,7 @@ bool MultiTextCursor::handleMoveKeyEvent(QKeyEvent *e, return false; } - const QList cursors = m_cursors; + const std::list cursors = m_cursorList; for (QTextCursor cursor : cursors) { if (camelCaseNavigationEnabled && op == QTextCursor::WordRight) CamelCaseCursor::right(&cursor, edit, QTextCursor::MoveAnchor); @@ -329,14 +380,14 @@ bool MultiTextCursor::handleMoveKeyEvent(QKeyEvent *e, CamelCaseCursor::left(&cursor, edit, QTextCursor::MoveAnchor); else cursor.movePosition(op, QTextCursor::MoveAnchor); - m_cursors << cursor; - } - mergeCursors(); + addCursor(cursor); + } return true; } - for (QTextCursor &cursor : m_cursors) { + for (auto it = m_cursorList.begin(); it != m_cursorList.end(); ++it) { + QTextCursor &cursor = *it; QTextCursor::MoveMode mode = QTextCursor::MoveAnchor; QTextCursor::MoveOperation op = QTextCursor::NoMove; diff --git a/src/libs/utils/multitextcursor.h b/src/libs/utils/multitextcursor.h index 390082e63a..edb4f5f1fd 100644 --- a/src/libs/utils/multitextcursor.h +++ b/src/libs/utils/multitextcursor.h @@ -21,6 +21,13 @@ public: MultiTextCursor(); explicit MultiTextCursor(const QList &cursors); + MultiTextCursor(const MultiTextCursor &multiCursor); + MultiTextCursor &operator=(const MultiTextCursor &multiCursor); + MultiTextCursor(const MultiTextCursor &&multiCursor); + MultiTextCursor &operator=(const MultiTextCursor &&multiCursor); + + ~MultiTextCursor(); + /// replace all cursors with \param cursors and the last one will be the new main cursors void setCursors(const QList &cursors); const QList cursors() const; @@ -69,20 +76,23 @@ public: bool operator==(const MultiTextCursor &other) const; bool operator!=(const MultiTextCursor &other) const; - using iterator = QList::iterator; - using const_iterator = QList::const_iterator; + using iterator = std::list::iterator; + using const_iterator = std::list::const_iterator; - iterator begin() { return m_cursors.begin(); } - iterator end() { return m_cursors.end(); } - const_iterator begin() const { return m_cursors.begin(); } - const_iterator end() const { return m_cursors.end(); } - const_iterator constBegin() const { return m_cursors.constBegin(); } - const_iterator constEnd() const { return m_cursors.constEnd(); } + iterator begin() { return m_cursorList.begin(); } + iterator end() { return m_cursorList.end(); } + const_iterator begin() const { return m_cursorList.begin(); } + const_iterator end() const { return m_cursorList.end(); } + const_iterator constBegin() const { return m_cursorList.cbegin(); } + const_iterator constEnd() const { return m_cursorList.cend(); } static bool multiCursorAddEvent(QKeyEvent *e, QKeySequence::StandardKey matchKey); private: - QList m_cursors; + std::list m_cursorList; + std::map::iterator> m_cursorMap; + + void fillMapWithList(); }; } // namespace Utils -- cgit v1.2.3 From 06dda40ccc1a44675706aa93dbf83ea88a8763a6 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Mon, 6 Feb 2023 20:27:59 +0100 Subject: TaskTree: Add synchronous invocation (Tasking::Sync) Make it possible to mix synchronous and asynchronous calls inside task tree. Basically, it's a shortcut for: bool syncMethod(); Group { OnGroupSetup([syncMethod] { return syncMethod() ? TaskAction::StopWithDone : TaskAction::StopWithError; }) } Please note: similarly to Group, Sync isn't counted as a task inside taskCount() and doesn't emit TaskTree::progressValueChanged() when finished. It's being considered as a simple handler that doesn't last long and shouldn't block the GUI thread. Otherwise, use AsyncTask instead. Change-Id: If71c5da2f9b202a69c41a555cc93d314476952f4 Reviewed-by: Eike Ziller --- src/libs/utils/tasktree.h | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/tasktree.h b/src/libs/utils/tasktree.h index 54a0cfda53..b90e10c7c1 100644 --- a/src/libs/utils/tasktree.h +++ b/src/libs/utils/tasktree.h @@ -244,6 +244,16 @@ public: OnGroupError(const GroupEndHandler &handler) : TaskItem({{}, {}, handler}) {} }; +// Synchronous invocation. Similarly to Group - isn't counted as a task inside taskCount() +class QTCREATOR_UTILS_EXPORT Sync : public Group +{ +public: + using SynchronousMethod = std::function; + Sync(const SynchronousMethod &sync) + : Group({OnGroupSetup([sync] { return sync() ? TaskAction::StopWithDone + : TaskAction::StopWithError; })}) {} +}; + QTCREATOR_UTILS_EXPORT extern ParallelLimit sequential; QTCREATOR_UTILS_EXPORT extern ParallelLimit parallel; QTCREATOR_UTILS_EXPORT extern Workflow stopOnError; -- cgit v1.2.3 From fc73324c506b1950ee413d9ab3b0844c5a4b1549 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Thu, 9 Feb 2023 13:05:49 +0100 Subject: Utils: Remove old Qt 5.4 workaround see QTBUG-40449. Change-Id: I0c347e81f8791f042c28200844d9102d84151b3c Reviewed-by: hjk Reviewed-by: Eike Ziller Reviewed-by: Qt CI Bot --- src/libs/utils/dropsupport.cpp | 4 ---- src/libs/utils/fileutils_mac.h | 1 - src/libs/utils/fileutils_mac.mm | 11 ----------- 3 files changed, 16 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/dropsupport.cpp b/src/libs/utils/dropsupport.cpp index 9d914e0f08..6def3fd1af 100644 --- a/src/libs/utils/dropsupport.cpp +++ b/src/libs/utils/dropsupport.cpp @@ -40,10 +40,6 @@ static bool isFileDropMime(const QMimeData *d, QList *fil const QList::const_iterator cend = urls.constEnd(); for (QList::const_iterator it = urls.constBegin(); it != cend; ++it) { QUrl url = *it; -#ifdef Q_OS_OSX - // for file drops from Finder, working around QTBUG-40449 - url = Internal::filePathUrl(url); -#endif const QString fileName = url.toLocalFile(); if (!fileName.isEmpty()) { hasFiles = true; diff --git a/src/libs/utils/fileutils_mac.h b/src/libs/utils/fileutils_mac.h index 4c9e7557e1..c7ddd0d2cd 100644 --- a/src/libs/utils/fileutils_mac.h +++ b/src/libs/utils/fileutils_mac.h @@ -8,7 +8,6 @@ namespace Utils { namespace Internal { -QUrl filePathUrl(const QUrl &url); QString normalizePathName(const QString &filePath); } // Internal diff --git a/src/libs/utils/fileutils_mac.mm b/src/libs/utils/fileutils_mac.mm index e7ff062f66..c3d494991d 100644 --- a/src/libs/utils/fileutils_mac.mm +++ b/src/libs/utils/fileutils_mac.mm @@ -14,17 +14,6 @@ namespace Utils { namespace Internal { -QUrl filePathUrl(const QUrl &url) -{ - QUrl ret = url; - @autoreleasepool { - NSURL *nsurl = url.toNSURL(); - if ([nsurl isFileReferenceURL]) - ret = QUrl::fromNSURL([nsurl filePathURL]); - } - return ret; -} - QString normalizePathName(const QString &filePath) { QString result; -- cgit v1.2.3 From 7fe93633952671277c2204beb3298ee19becf237 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Wed, 15 Feb 2023 12:11:56 +0100 Subject: CPlusPlus: Use categorized logging in lexer Not suprisingly, the #ifdef-based debugging produced uncompilable code when enabled. Change-Id: I4a6646bfa98a8500491be4892d464ec88512bec7 Reviewed-by: Reviewed-by: Christian Stenger --- src/libs/cplusplus/pp-engine.cpp | 63 +++++++++++++++------------------------- src/libs/cplusplus/pp-engine.h | 1 + 2 files changed, 25 insertions(+), 39 deletions(-) (limited to 'src/libs') diff --git a/src/libs/cplusplus/pp-engine.cpp b/src/libs/cplusplus/pp-engine.cpp index d8221b151f..ef27d9eace 100644 --- a/src/libs/cplusplus/pp-engine.cpp +++ b/src/libs/cplusplus/pp-engine.cpp @@ -37,20 +37,17 @@ #include #include #include +#include #include #include #include +#include #include #include -#define NO_DEBUG - -#ifndef NO_DEBUG -# include -#endif // NO_DEBUG - -#include +// FIXME: This is used for errors that should appear in the editor. +static Q_LOGGING_CATEGORY(lexerLog, "qtc.cpp.lexer", QtWarningMsg) using namespace Utils; @@ -119,13 +116,6 @@ static bool isQtReservedWord(const char *name, int size) return false; } -static void nestingTooDeep() -{ -#ifndef NO_DEBUG - std::cerr << "*** WARNING #if / #ifdef nesting exceeded the max level " << MAX_LEVEL << std::endl; -#endif -} - } // anonymous namespace namespace CPlusPlus { @@ -1680,10 +1670,7 @@ void Preprocessor::handleIncludeDirective(PPToken *tk, bool includeNext) GuardLocker depthLocker(m_includeDepthGuard); if (m_includeDepthGuard.lockCount() > MAX_INCLUDE_DEPTH) { - // FIXME: Categorized logging! -#ifndef NO_DEBUG - std::cerr << "Maximum include depth exceeded" << m_state.m_currentFileName << std::endl; -#endif + qCWarning(lexerLog) << "Maximum include depth exceeded" << m_state.m_currentFileName; return; } @@ -1929,10 +1916,8 @@ void Preprocessor::handleIfDirective(PPToken *tk) Value result; const PPToken lastExpressionToken = evalExpression(tk, result); - if (m_state.m_ifLevel >= MAX_LEVEL - 1) { - nestingTooDeep(); + if (!checkConditionalNesting()) return; - } const bool value = !result.is_zero(); @@ -1953,7 +1938,7 @@ void Preprocessor::handleIfDirective(PPToken *tk) void Preprocessor::handleElifDirective(PPToken *tk, const PPToken £Token) { if (m_state.m_ifLevel == 0) { -// std::cerr << "*** WARNING #elif without #if" << std::endl; + qCWarning(lexerLog) << "#elif without #if"; handleIfDirective(tk); } else { lex(tk); // consume "elif" token @@ -2000,22 +1985,18 @@ void Preprocessor::handleElseDirective(PPToken *tk, const PPToken £Token) else if (m_client && !wasSkipping && startSkipping) startSkippingBlocks(poundToken); } -#ifndef NO_DEBUG } else { - std::cerr << "*** WARNING #else without #if" << std::endl; -#endif // NO_DEBUG + qCWarning(lexerLog) << "#else without #if"; } } void Preprocessor::handleEndIfDirective(PPToken *tk, const PPToken £Token) { if (m_state.m_ifLevel == 0) { -#ifndef NO_DEBUG - std::cerr << "*** WARNING #endif without #if"; + qCWarning(lexerLog) << "#endif without #if"; if (!tk->generated()) - std::cerr << " on line " << tk->lineno << " of file " << m_state.m_currentFileName.toUtf8().constData(); - std::cerr << std::endl; -#endif // NO_DEBUG + qCWarning(lexerLog) << "on line" << tk->lineno << "of file" + << m_state.m_currentFileName.toUtf8().constData(); } else { bool wasSkipping = m_state.m_skipping[m_state.m_ifLevel]; m_state.m_skipping[m_state.m_ifLevel] = false; @@ -2061,22 +2042,18 @@ void Preprocessor::handleIfDefDirective(bool checkUndefined, PPToken *tk) const bool wasSkipping = m_state.m_skipping[m_state.m_ifLevel]; - if (m_state.m_ifLevel < MAX_LEVEL - 1) { + if (checkConditionalNesting()) { ++m_state.m_ifLevel; m_state.m_trueTest[m_state.m_ifLevel] = value; m_state.m_skipping[m_state.m_ifLevel] = wasSkipping ? wasSkipping : !value; if (m_client && !wasSkipping && !value) startSkippingBlocks(*tk); - } else { - nestingTooDeep(); } lex(tk); // consume the identifier -#ifndef NO_DEBUG } else { - std::cerr << "*** WARNING #ifdef without identifier" << std::endl; -#endif // NO_DEBUG + qCWarning(lexerLog) << "#ifdef without identifier"; } } @@ -2103,10 +2080,8 @@ void Preprocessor::handleUndefDirective(PPToken *tk) m_client->macroAdded(*macro); } lex(tk); // consume macro name -#ifndef NO_DEBUG } else { - std::cerr << "*** WARNING #undef without identifier" << std::endl; -#endif // NO_DEBUG + qCWarning(lexerLog) << "#undef without identifier"; } } @@ -2203,4 +2178,14 @@ void Preprocessor::maybeStartOutputLine() buffer.append('\n'); } +bool Preprocessor::checkConditionalNesting() const +{ + if (m_state.m_ifLevel >= MAX_LEVEL - 1) { + qCWarning(lexerLog) << "#if/#ifdef nesting exceeding maximum level" << MAX_LEVEL; + return false; + } + return true; +} + + } // namespace CPlusPlus diff --git a/src/libs/cplusplus/pp-engine.h b/src/libs/cplusplus/pp-engine.h index c888e8775d..2163380dea 100644 --- a/src/libs/cplusplus/pp-engine.h +++ b/src/libs/cplusplus/pp-engine.h @@ -237,6 +237,7 @@ private: PPToken generateConcatenated(const PPToken &leftTk, const PPToken &rightTk); void startSkippingBlocks(const PPToken &tk) const; + bool checkConditionalNesting() const; private: Client *m_client; -- cgit v1.2.3 From efc4a0f1af0fb644cab0055e40ccd0b7d51c43f2 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Sat, 11 Feb 2023 18:43:19 +0100 Subject: AsyncTask: Make it possible to run with promise Using QtConcurrent API. Adapt asynctask tests according to new API. The old API in going to be removed, soon. Change-Id: I3cf163e9492526f0b49bd162c27e6c55a98ace7a Reviewed-by: Eike Ziller --- src/libs/utils/asynctask.cpp | 20 ++++++++++++++++++++ src/libs/utils/asynctask.h | 44 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/asynctask.cpp b/src/libs/utils/asynctask.cpp index 3576ad804c..73217318a9 100644 --- a/src/libs/utils/asynctask.cpp +++ b/src/libs/utils/asynctask.cpp @@ -3,6 +3,26 @@ #include "asynctask.h" +#include + namespace Utils { +static int s_maxThreadCount = INT_MAX; + +class AsyncThreadPool : public QThreadPool +{ +public: + AsyncThreadPool() { + setMaxThreadCount(s_maxThreadCount); + moveToThread(qApp->thread()); + } +}; + +Q_GLOBAL_STATIC(AsyncThreadPool, s_asyncThreadPool); + +QThreadPool *asyncThreadPool() +{ + return s_asyncThreadPool; +} + } // namespace Utils diff --git a/src/libs/utils/asynctask.h b/src/libs/utils/asynctask.h index 1a6ae7d1b6..84cf1ee838 100644 --- a/src/libs/utils/asynctask.h +++ b/src/libs/utils/asynctask.h @@ -11,9 +11,12 @@ #include "tasktree.h" #include +#include namespace Utils { +QTCREATOR_UTILS_EXPORT QThreadPool *asyncThreadPool(); + class QTCREATOR_UTILS_EXPORT AsyncTaskBase : public QObject { Q_OBJECT @@ -38,7 +41,11 @@ public: m_watcher.waitForFinished(); } - using StartHandler = std::function()>; + template + void setConcurrentCallData(Function &&function, Args &&...args) + { + return wrapConcurrent(std::forward(function), std::forward(args)...); + } template void setAsyncCallData(const Function &function, const Args &...args) @@ -69,6 +76,41 @@ public: bool isResultAvailable() const { return future().resultCount(); } private: + template + void wrapConcurrent(Function &&function, Args &&...args) + { + m_startHandler = [=] { + return callConcurrent(function, args...); + }; + } + + template + void wrapConcurrent(std::reference_wrapper &&wrapper, Args &&...args) + { + m_startHandler = [=] { + return callConcurrent(std::forward(wrapper.get()), args...); + }; + } + + template + auto callConcurrent(Function &&function, Args &&...args) + { + // Notice: we can't just call: + // + // return QtConcurrent::run(function, args...); + // + // since there is no way of passing m_priority there. + // There is an overload with thread pool, however, there is no overload with priority. + // + // Below implementation copied from QtConcurrent::run(): + QThreadPool *threadPool = m_threadPool ? m_threadPool : asyncThreadPool(); + QtConcurrent::DecayedTuple + tuple{std::forward(function), std::forward(args)...}; + return QtConcurrent::TaskResolver, std::decay_t...> + ::run(std::move(tuple), QtConcurrent::TaskStartParameters{threadPool, m_priority}); + } + + using StartHandler = std::function()>; StartHandler m_startHandler; FutureSynchronizer *m_synchronizer = nullptr; QThreadPool *m_threadPool = nullptr; -- cgit v1.2.3 From 69ec9c43618f021a0146a1f23b13f18b827893d1 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Mon, 20 Feb 2023 20:58:38 +0100 Subject: TaskTree: Add TreeStorage::operator*() Reuse it in some places. Change-Id: I335f38fa0384ea17bd8e981d743f835c3f05b731 Reviewed-by: Reviewed-by: hjk --- src/libs/utils/tasktree.h | 1 + 1 file changed, 1 insertion(+) (limited to 'src/libs') diff --git a/src/libs/utils/tasktree.h b/src/libs/utils/tasktree.h index b90e10c7c1..db1798affe 100644 --- a/src/libs/utils/tasktree.h +++ b/src/libs/utils/tasktree.h @@ -74,6 +74,7 @@ class TreeStorage : public TreeStorageBase { public: TreeStorage() : TreeStorageBase(TreeStorage::ctor(), TreeStorage::dtor()) {} + StorageStruct &operator*() const noexcept { return *activeStorage(); } StorageStruct *operator->() const noexcept { return activeStorage(); } StorageStruct *activeStorage() const { return static_cast(activeStorageVoid()); -- cgit v1.2.3 From 31fa792b5bc66f295e301c46825fe7d4d4fc0709 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Mon, 20 Feb 2023 20:33:59 +0100 Subject: TaskTree: Update inline comments Change-Id: I8a34eb0757fc6d6bf7589ced7a714bb6e564fd09 Reviewed-by: hjk --- src/libs/utils/tasktree.h | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/tasktree.h b/src/libs/utils/tasktree.h index db1798affe..8a44b8a5b0 100644 --- a/src/libs/utils/tasktree.h +++ b/src/libs/utils/tasktree.h @@ -87,20 +87,21 @@ private: } }; -// 4 policies: +// WorkflowPolicy: // 1. When all children finished with done -> report done, otherwise: // a) Report error on first error and stop executing other children (including their subtree) -// b) On first error - wait for all children to be finished and report error afterwards +// b) On first error - continue executing all children and report error afterwards // 2. When all children finished with error -> report error, otherwise: // a) Report done on first done and stop executing other children (including their subtree) -// b) On first done - wait for all children to be finished and report done afterwards +// b) On first done - continue executing all children and report done afterwards +// 3. Always run all children, ignore their result and report done afterwards enum class WorkflowPolicy { - StopOnError, // 1a - Will report error on any child error, otherwise done (if all children were done) - ContinueOnError, // 1b - the same. When no children it reports done. - StopOnDone, // 2a - Will report done on any child done, otherwise error (if all children were error) - ContinueOnDone, // 2b - the same. When no children it reports done. (?) - Optional // Returns always done after all children finished + StopOnError, // 1a - Reports error on first child error, otherwise done (if all children were done) + ContinueOnError, // 1b - The same, but children execution continues. When no children it reports done. + StopOnDone, // 2a - Reports done on first child done, otherwise error (if all children were error) + ContinueOnDone, // 2b - The same, but children execution continues. When no children it reports done. (?) + Optional // 3 - Always reports done after all children finished }; enum class TaskAction -- cgit v1.2.3 From 37ec527e6d74d4bc816e1868919a97ac8a398cae Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Wed, 22 Feb 2023 11:58:17 +0100 Subject: Fix tilde expansion in FilePath::fromUserInput It was not working for just "~/" without additional path component. Change-Id: I559a7771fc09d2e604f567548585a668e8809729 Reviewed-by: Marcus Tillmanns Reviewed-by: --- src/libs/utils/filepath.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index db8008182f..0e46b61c7d 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -1038,10 +1038,10 @@ FilePath FilePath::fromStringWithExtension(const QString &filepath, const QStrin */ FilePath FilePath::fromUserInput(const QString &filePath) { - QString clean = doCleanPath(filePath); - if (clean.startsWith(QLatin1String("~/"))) - return FileUtils::homePath().pathAppended(clean.mid(2)); - return FilePath::fromString(clean); + const QString expandedPath = filePath.startsWith("~/") + ? (QDir::homePath() + "/" + filePath.mid(2)) + : filePath; + return FilePath::fromString(doCleanPath(expandedPath)); } /*! -- cgit v1.2.3 From 66d4e12a586509c7ff903bae633f4fa2ebdcc0ce Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Mon, 23 Jan 2023 17:11:34 +0100 Subject: Build: Remove FindQt5.cmake No longer needed, since we generally only support building with Qt 6 nowadays, and the parts that still do support building with Qt 5 handle that manually. Change-Id: I72381589ca3ab7bf1af88d9f185cad7f0cdf149c Reviewed-by: Qt CI Bot Reviewed-by: Reviewed-by: Cristian Adam --- src/libs/extensionsystem/CMakeLists.txt | 2 +- src/libs/utils/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/extensionsystem/CMakeLists.txt b/src/libs/extensionsystem/CMakeLists.txt index ea7cb60beb..0e4e6607a5 100644 --- a/src/libs/extensionsystem/CMakeLists.txt +++ b/src/libs/extensionsystem/CMakeLists.txt @@ -19,7 +19,7 @@ add_qtc_library(ExtensionSystem SKIP_AUTOMOC pluginmanager.cpp ) -find_package(Qt5 COMPONENTS Test QUIET) +find_package(Qt6 COMPONENTS Test QUIET) extend_qtc_library(ExtensionSystem CONDITION TARGET Qt::Test diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index 76f4e8a314..34b24ff78c 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -2,7 +2,7 @@ add_qtc_library(Utils DEPENDS Qt::Qml Qt::Xml PUBLIC_DEPENDS Qt::Concurrent Qt::Core Qt::Network Qt::Gui Qt::Widgets - Qt6Core5Compat + Qt::Core5Compat SOURCES ../3rdparty/span/span.hpp ../3rdparty/tl_expected/include/tl/expected.hpp -- cgit v1.2.3 From 00fd4c8feadbfe354b8c7f93038f60bec86b5554 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Wed, 22 Feb 2023 15:58:18 +0100 Subject: Utils: Fix diriterator for scheme folders Previously the "fileName" of every device inside a scheme subfolder would be empty. Therefore QDirIterator would skip them. Change-Id: Ifa46859aadbd8a81364a1fe0a72b9a50a7a396ca Reviewed-by: Eike Ziller Reviewed-by: Qt CI Bot --- src/libs/utils/filepath.cpp | 6 ++++++ src/libs/utils/fsengine/diriterator.h | 3 +++ 2 files changed, 9 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 0e46b61c7d..8c0832df58 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -233,6 +233,9 @@ QString FilePath::toString() const if (!needsDevice()) return path(); + if (pathView().isEmpty()) + return scheme() + "://" + encodedHost(); + if (isRelativePath()) return scheme() + "://" + encodedHost() + "/./" + pathView(); return scheme() + "://" + encodedHost() + pathView(); @@ -255,6 +258,9 @@ QString FilePath::toFSPathString() const if (scheme().isEmpty()) return path(); + if (pathView().isEmpty()) + return specialRootPath() + '/' + scheme() + '/' + encodedHost(); + if (isRelativePath()) return specialRootPath() + '/' + scheme() + '/' + encodedHost() + "/./" + pathView(); return specialRootPath() + '/' + scheme() + '/' + encodedHost() + pathView(); diff --git a/src/libs/utils/fsengine/diriterator.h b/src/libs/utils/fsengine/diriterator.h index 9daebbebaa..54e9d5d2dd 100644 --- a/src/libs/utils/fsengine/diriterator.h +++ b/src/libs/utils/fsengine/diriterator.h @@ -39,6 +39,9 @@ public: QString currentFileName() const override { const QString result = it->fileName(); + if (result.isEmpty() && !it->host().isEmpty()) { + return it->host().toString(); + } return chopIfEndsWith(result, '/'); } -- cgit v1.2.3 From 82194d7e9cd8dbc18ef2fae33d87c3d0ce144059 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Thu, 23 Feb 2023 12:45:15 +0100 Subject: Terminal: Add required 3rdparty libraries Change-Id: Ic477e305f78632f5c454cd639dfc7e41fb643fe1 Reviewed-by: Leena Miettinen Reviewed-by: hjk --- src/libs/3rdparty/CMakeLists.txt | 6 + src/libs/3rdparty/libptyqt/CMakeLists.txt | 28 + src/libs/3rdparty/libptyqt/LICENSE | 21 + src/libs/3rdparty/libptyqt/conptyprocess.cpp | 346 +++ src/libs/3rdparty/libptyqt/conptyprocess.h | 167 ++ src/libs/3rdparty/libptyqt/iptyprocess.h | 56 + src/libs/3rdparty/libptyqt/ptyqt.cpp | 45 + src/libs/3rdparty/libptyqt/ptyqt.h | 12 + src/libs/3rdparty/libptyqt/ptyqt.qbs | 45 + src/libs/3rdparty/libptyqt/unixptyprocess.cpp | 376 ++++ src/libs/3rdparty/libptyqt/unixptyprocess.h | 66 + src/libs/3rdparty/libptyqt/winptyprocess.cpp | 275 +++ src/libs/3rdparty/libptyqt/winptyprocess.h | 42 + src/libs/3rdparty/libvterm/CMakeLists.txt | 25 + src/libs/3rdparty/libvterm/CONTRIBUTING | 22 + src/libs/3rdparty/libvterm/LICENSE | 23 + src/libs/3rdparty/libvterm/include/vterm.h | 635 ++++++ .../3rdparty/libvterm/include/vterm_keycodes.h | 61 + src/libs/3rdparty/libvterm/src/encoding.c | 230 ++ .../3rdparty/libvterm/src/encoding/DECdrawing.inc | 36 + src/libs/3rdparty/libvterm/src/encoding/uk.inc | 6 + src/libs/3rdparty/libvterm/src/fullwidth.inc | 111 + src/libs/3rdparty/libvterm/src/keyboard.c | 226 ++ src/libs/3rdparty/libvterm/src/mouse.c | 99 + src/libs/3rdparty/libvterm/src/parser.c | 402 ++++ src/libs/3rdparty/libvterm/src/pen.c | 607 +++++ src/libs/3rdparty/libvterm/src/rect.h | 56 + src/libs/3rdparty/libvterm/src/screen.c | 1183 ++++++++++ src/libs/3rdparty/libvterm/src/state.c | 2315 ++++++++++++++++++++ src/libs/3rdparty/libvterm/src/unicode.c | 313 +++ src/libs/3rdparty/libvterm/src/utf8.h | 39 + src/libs/3rdparty/libvterm/src/vterm.c | 429 ++++ src/libs/3rdparty/libvterm/src/vterm_internal.h | 296 +++ src/libs/3rdparty/libvterm/vterm.pc.in | 8 + src/libs/3rdparty/libvterm/vterm.qbs | 33 + src/libs/3rdparty/winpty/.gitattributes | 19 + src/libs/3rdparty/winpty/.gitignore | 16 + src/libs/3rdparty/winpty/CMakeLists.txt | 1 + src/libs/3rdparty/winpty/LICENSE | 21 + src/libs/3rdparty/winpty/README.md | 151 ++ src/libs/3rdparty/winpty/RELEASES.md | 280 +++ src/libs/3rdparty/winpty/VERSION.txt | 1 + src/libs/3rdparty/winpty/appveyor.yml | 16 + src/libs/3rdparty/winpty/configure | 167 ++ src/libs/3rdparty/winpty/misc/.gitignore | 2 + src/libs/3rdparty/winpty/misc/BufferResizeTests.cc | 90 + .../3rdparty/winpty/misc/ChangeScreenBuffer.cc | 53 + src/libs/3rdparty/winpty/misc/ClearConsole.cc | 72 + src/libs/3rdparty/winpty/misc/ConinMode.cc | 117 + src/libs/3rdparty/winpty/misc/ConinMode.ps1 | 116 + src/libs/3rdparty/winpty/misc/ConoutMode.cc | 113 + src/libs/3rdparty/winpty/misc/DebugClient.py | 42 + src/libs/3rdparty/winpty/misc/DebugServer.py | 63 + src/libs/3rdparty/winpty/misc/DumpLines.py | 5 + .../3rdparty/winpty/misc/EnableExtendedFlags.txt | 46 + .../misc/Font-Report-June2016/CP437-Consolas.txt | 528 +++++ .../misc/Font-Report-June2016/CP437-Lucida.txt | 633 ++++++ .../winpty/misc/Font-Report-June2016/CP932.txt | 630 ++++++ .../winpty/misc/Font-Report-June2016/CP936.txt | 630 ++++++ .../winpty/misc/Font-Report-June2016/CP949.txt | 630 ++++++ .../winpty/misc/Font-Report-June2016/CP950.txt | 630 ++++++ .../Font-Report-June2016/MinimumWindowWidths.txt | 16 + .../winpty/misc/Font-Report-June2016/Results.txt | 4 + .../Windows10SetFontBugginess.txt | 144 ++ src/libs/3rdparty/winpty/misc/FontSurvey.cc | 100 + src/libs/3rdparty/winpty/misc/FormatChar.h | 21 + src/libs/3rdparty/winpty/misc/FreezePerfTest.cc | 62 + src/libs/3rdparty/winpty/misc/GetCh.cc | 20 + src/libs/3rdparty/winpty/misc/GetConsolePos.cc | 41 + src/libs/3rdparty/winpty/misc/GetFont.cc | 261 +++ .../3rdparty/winpty/misc/IdentifyConsoleWindow.ps1 | 51 + src/libs/3rdparty/winpty/misc/IsNewConsole.cc | 87 + src/libs/3rdparty/winpty/misc/MouseInputNotes.txt | 90 + src/libs/3rdparty/winpty/misc/MoveConsoleWindow.cc | 34 + src/libs/3rdparty/winpty/misc/Notes.txt | 219 ++ src/libs/3rdparty/winpty/misc/OSVersion.cc | 27 + .../winpty/misc/ScreenBufferFreezeInactive.cc | 101 + src/libs/3rdparty/winpty/misc/ScreenBufferTest.cc | 671 ++++++ src/libs/3rdparty/winpty/misc/ScreenBufferTest2.cc | 151 ++ src/libs/3rdparty/winpty/misc/SelectAllTest.cc | 45 + src/libs/3rdparty/winpty/misc/SetBufInfo.cc | 90 + src/libs/3rdparty/winpty/misc/SetBufferSize.cc | 32 + src/libs/3rdparty/winpty/misc/SetCursorPos.cc | 10 + src/libs/3rdparty/winpty/misc/SetFont.cc | 145 ++ src/libs/3rdparty/winpty/misc/SetWindowRect.cc | 36 + src/libs/3rdparty/winpty/misc/ShowArgv.cc | 12 + src/libs/3rdparty/winpty/misc/ShowConsoleInput.cc | 40 + src/libs/3rdparty/winpty/misc/Spew.py | 5 + src/libs/3rdparty/winpty/misc/TestUtil.cc | 172 ++ .../3rdparty/winpty/misc/UnicodeDoubleWidthTest.cc | 102 + src/libs/3rdparty/winpty/misc/UnicodeWideTest1.cc | 246 +++ src/libs/3rdparty/winpty/misc/UnicodeWideTest2.cc | 130 ++ src/libs/3rdparty/winpty/misc/UnixEcho.cc | 89 + src/libs/3rdparty/winpty/misc/Utf16Echo.cc | 46 + src/libs/3rdparty/winpty/misc/VeryLargeRead.cc | 122 ++ src/libs/3rdparty/winpty/misc/VkEscapeTest.cc | 56 + .../3rdparty/winpty/misc/Win10ResizeWhileFrozen.cc | 52 + src/libs/3rdparty/winpty/misc/Win10WrapTest1.cc | 57 + src/libs/3rdparty/winpty/misc/Win10WrapTest2.cc | 30 + src/libs/3rdparty/winpty/misc/Win32Echo1.cc | 26 + src/libs/3rdparty/winpty/misc/Win32Echo2.cc | 19 + src/libs/3rdparty/winpty/misc/Win32Test1.cc | 46 + src/libs/3rdparty/winpty/misc/Win32Test2.cc | 70 + src/libs/3rdparty/winpty/misc/Win32Test3.cc | 78 + src/libs/3rdparty/winpty/misc/Win32Write1.cc | 44 + .../3rdparty/winpty/misc/WindowsBugCrashReader.cc | 27 + src/libs/3rdparty/winpty/misc/WriteConsole.cc | 106 + src/libs/3rdparty/winpty/misc/build32.sh | 9 + src/libs/3rdparty/winpty/misc/build64.sh | 9 + src/libs/3rdparty/winpty/misc/color-test.sh | 212 ++ src/libs/3rdparty/winpty/misc/font-notes.txt | 300 +++ src/libs/3rdparty/winpty/misc/winbug-15048.cc | 201 ++ .../3rdparty/winpty/ship/build-pty4j-libpty.bat | 36 + src/libs/3rdparty/winpty/ship/common_ship.py | 89 + src/libs/3rdparty/winpty/ship/make_msvc_package.py | 163 ++ src/libs/3rdparty/winpty/ship/ship.py | 104 + src/libs/3rdparty/winpty/src/CMakeLists.txt | 112 + src/libs/3rdparty/winpty/src/agent/Agent.cc | 612 ++++++ src/libs/3rdparty/winpty/src/agent/Agent.h | 103 + .../winpty/src/agent/AgentCreateDesktop.cc | 84 + .../3rdparty/winpty/src/agent/AgentCreateDesktop.h | 28 + src/libs/3rdparty/winpty/src/agent/ConsoleFont.cc | 698 ++++++ src/libs/3rdparty/winpty/src/agent/ConsoleFont.h | 28 + src/libs/3rdparty/winpty/src/agent/ConsoleInput.cc | 852 +++++++ src/libs/3rdparty/winpty/src/agent/ConsoleInput.h | 109 + .../winpty/src/agent/ConsoleInputReencoding.cc | 121 + .../winpty/src/agent/ConsoleInputReencoding.h | 36 + src/libs/3rdparty/winpty/src/agent/ConsoleLine.cc | 152 ++ src/libs/3rdparty/winpty/src/agent/ConsoleLine.h | 41 + src/libs/3rdparty/winpty/src/agent/Coord.h | 87 + .../3rdparty/winpty/src/agent/DebugShowInput.cc | 239 ++ .../3rdparty/winpty/src/agent/DebugShowInput.h | 32 + .../3rdparty/winpty/src/agent/DefaultInputMap.cc | 422 ++++ .../3rdparty/winpty/src/agent/DefaultInputMap.h | 28 + src/libs/3rdparty/winpty/src/agent/DsrSender.h | 30 + src/libs/3rdparty/winpty/src/agent/EventLoop.cc | 99 + src/libs/3rdparty/winpty/src/agent/EventLoop.h | 47 + src/libs/3rdparty/winpty/src/agent/InputMap.cc | 246 +++ src/libs/3rdparty/winpty/src/agent/InputMap.h | 114 + .../3rdparty/winpty/src/agent/LargeConsoleRead.cc | 71 + .../3rdparty/winpty/src/agent/LargeConsoleRead.h | 68 + src/libs/3rdparty/winpty/src/agent/NamedPipe.cc | 378 ++++ src/libs/3rdparty/winpty/src/agent/NamedPipe.h | 125 ++ src/libs/3rdparty/winpty/src/agent/Scraper.cc | 699 ++++++ src/libs/3rdparty/winpty/src/agent/Scraper.h | 103 + src/libs/3rdparty/winpty/src/agent/SimplePool.h | 75 + src/libs/3rdparty/winpty/src/agent/SmallRect.h | 143 ++ src/libs/3rdparty/winpty/src/agent/Terminal.cc | 535 +++++ src/libs/3rdparty/winpty/src/agent/Terminal.h | 69 + .../3rdparty/winpty/src/agent/UnicodeEncoding.h | 157 ++ .../winpty/src/agent/UnicodeEncodingTest.cc | 189 ++ src/libs/3rdparty/winpty/src/agent/Win32Console.cc | 107 + src/libs/3rdparty/winpty/src/agent/Win32Console.h | 67 + .../winpty/src/agent/Win32ConsoleBuffer.cc | 193 ++ .../3rdparty/winpty/src/agent/Win32ConsoleBuffer.h | 99 + src/libs/3rdparty/winpty/src/agent/main.cc | 120 + src/libs/3rdparty/winpty/src/agent/subdir.mk | 61 + src/libs/3rdparty/winpty/src/configurations.gypi | 60 + .../3rdparty/winpty/src/debugserver/DebugServer.cc | 117 + src/libs/3rdparty/winpty/src/debugserver/subdir.mk | 41 + src/libs/3rdparty/winpty/src/include/winpty.h | 242 ++ .../3rdparty/winpty/src/include/winpty_constants.h | 131 ++ .../3rdparty/winpty/src/libwinpty/AgentLocation.cc | 75 + .../3rdparty/winpty/src/libwinpty/AgentLocation.h | 28 + .../winpty/src/libwinpty/LibWinptyException.h | 54 + .../3rdparty/winpty/src/libwinpty/WinptyInternal.h | 72 + src/libs/3rdparty/winpty/src/libwinpty/subdir.mk | 46 + src/libs/3rdparty/winpty/src/libwinpty/winpty.cc | 970 ++++++++ src/libs/3rdparty/winpty/src/shared/AgentMsg.h | 38 + .../winpty/src/shared/BackgroundDesktop.cc | 122 ++ .../3rdparty/winpty/src/shared/BackgroundDesktop.h | 73 + src/libs/3rdparty/winpty/src/shared/Buffer.cc | 103 + src/libs/3rdparty/winpty/src/shared/Buffer.h | 102 + src/libs/3rdparty/winpty/src/shared/DebugClient.cc | 187 ++ src/libs/3rdparty/winpty/src/shared/DebugClient.h | 38 + src/libs/3rdparty/winpty/src/shared/GenRandom.cc | 138 ++ src/libs/3rdparty/winpty/src/shared/GenRandom.h | 55 + .../3rdparty/winpty/src/shared/GetCommitHash.bat | 13 + src/libs/3rdparty/winpty/src/shared/Mutex.h | 54 + src/libs/3rdparty/winpty/src/shared/OsModule.h | 63 + src/libs/3rdparty/winpty/src/shared/OwnedHandle.cc | 36 + src/libs/3rdparty/winpty/src/shared/OwnedHandle.h | 45 + .../3rdparty/winpty/src/shared/PrecompiledHeader.h | 43 + .../3rdparty/winpty/src/shared/StringBuilder.h | 227 ++ .../winpty/src/shared/StringBuilderTest.cc | 114 + src/libs/3rdparty/winpty/src/shared/StringUtil.cc | 55 + src/libs/3rdparty/winpty/src/shared/StringUtil.h | 80 + .../3rdparty/winpty/src/shared/TimeMeasurement.h | 63 + .../3rdparty/winpty/src/shared/UnixCtrlChars.h | 45 + .../winpty/src/shared/UpdateGenVersion.bat | 20 + .../3rdparty/winpty/src/shared/WindowsSecurity.cc | 460 ++++ .../3rdparty/winpty/src/shared/WindowsSecurity.h | 104 + .../3rdparty/winpty/src/shared/WindowsVersion.cc | 252 +++ .../3rdparty/winpty/src/shared/WindowsVersion.h | 29 + .../3rdparty/winpty/src/shared/WinptyAssert.cc | 55 + src/libs/3rdparty/winpty/src/shared/WinptyAssert.h | 64 + .../3rdparty/winpty/src/shared/WinptyException.cc | 57 + .../3rdparty/winpty/src/shared/WinptyException.h | 43 + .../3rdparty/winpty/src/shared/WinptyVersion.cc | 42 + .../3rdparty/winpty/src/shared/WinptyVersion.h | 27 + .../3rdparty/winpty/src/shared/winpty_snprintf.h | 99 + src/libs/3rdparty/winpty/src/subdir.mk | 5 + src/libs/3rdparty/winpty/src/tests/subdir.mk | 28 + src/libs/3rdparty/winpty/src/tests/trivial_test.cc | 158 ++ .../winpty/src/unix-adapter/InputHandler.cc | 114 + .../winpty/src/unix-adapter/InputHandler.h | 56 + .../winpty/src/unix-adapter/OutputHandler.cc | 80 + .../winpty/src/unix-adapter/OutputHandler.h | 53 + src/libs/3rdparty/winpty/src/unix-adapter/Util.cc | 86 + src/libs/3rdparty/winpty/src/unix-adapter/Util.h | 31 + .../3rdparty/winpty/src/unix-adapter/WakeupFd.cc | 70 + .../3rdparty/winpty/src/unix-adapter/WakeupFd.h | 42 + src/libs/3rdparty/winpty/src/unix-adapter/main.cc | 729 ++++++ .../3rdparty/winpty/src/unix-adapter/subdir.mk | 41 + src/libs/3rdparty/winpty/src/winpty.gyp | 206 ++ src/libs/3rdparty/winpty/vcbuild.bat | 83 + src/libs/3rdparty/winpty/winpty.qbs | 207 ++ src/libs/libs.qbs | 3 + src/libs/qlitehtml | 2 +- 219 files changed, 33222 insertions(+), 1 deletion(-) create mode 100644 src/libs/3rdparty/libptyqt/CMakeLists.txt create mode 100644 src/libs/3rdparty/libptyqt/LICENSE create mode 100644 src/libs/3rdparty/libptyqt/conptyprocess.cpp create mode 100644 src/libs/3rdparty/libptyqt/conptyprocess.h create mode 100644 src/libs/3rdparty/libptyqt/iptyprocess.h create mode 100644 src/libs/3rdparty/libptyqt/ptyqt.cpp create mode 100644 src/libs/3rdparty/libptyqt/ptyqt.h create mode 100644 src/libs/3rdparty/libptyqt/ptyqt.qbs create mode 100644 src/libs/3rdparty/libptyqt/unixptyprocess.cpp create mode 100644 src/libs/3rdparty/libptyqt/unixptyprocess.h create mode 100644 src/libs/3rdparty/libptyqt/winptyprocess.cpp create mode 100644 src/libs/3rdparty/libptyqt/winptyprocess.h create mode 100644 src/libs/3rdparty/libvterm/CMakeLists.txt create mode 100644 src/libs/3rdparty/libvterm/CONTRIBUTING create mode 100644 src/libs/3rdparty/libvterm/LICENSE create mode 100644 src/libs/3rdparty/libvterm/include/vterm.h create mode 100644 src/libs/3rdparty/libvterm/include/vterm_keycodes.h create mode 100644 src/libs/3rdparty/libvterm/src/encoding.c create mode 100644 src/libs/3rdparty/libvterm/src/encoding/DECdrawing.inc create mode 100644 src/libs/3rdparty/libvterm/src/encoding/uk.inc create mode 100644 src/libs/3rdparty/libvterm/src/fullwidth.inc create mode 100644 src/libs/3rdparty/libvterm/src/keyboard.c create mode 100644 src/libs/3rdparty/libvterm/src/mouse.c create mode 100644 src/libs/3rdparty/libvterm/src/parser.c create mode 100644 src/libs/3rdparty/libvterm/src/pen.c create mode 100644 src/libs/3rdparty/libvterm/src/rect.h create mode 100644 src/libs/3rdparty/libvterm/src/screen.c create mode 100644 src/libs/3rdparty/libvterm/src/state.c create mode 100644 src/libs/3rdparty/libvterm/src/unicode.c create mode 100644 src/libs/3rdparty/libvterm/src/utf8.h create mode 100644 src/libs/3rdparty/libvterm/src/vterm.c create mode 100644 src/libs/3rdparty/libvterm/src/vterm_internal.h create mode 100644 src/libs/3rdparty/libvterm/vterm.pc.in create mode 100644 src/libs/3rdparty/libvterm/vterm.qbs create mode 100644 src/libs/3rdparty/winpty/.gitattributes create mode 100644 src/libs/3rdparty/winpty/.gitignore create mode 100644 src/libs/3rdparty/winpty/CMakeLists.txt create mode 100644 src/libs/3rdparty/winpty/LICENSE create mode 100644 src/libs/3rdparty/winpty/README.md create mode 100644 src/libs/3rdparty/winpty/RELEASES.md create mode 100644 src/libs/3rdparty/winpty/VERSION.txt create mode 100644 src/libs/3rdparty/winpty/appveyor.yml create mode 100644 src/libs/3rdparty/winpty/configure create mode 100644 src/libs/3rdparty/winpty/misc/.gitignore create mode 100644 src/libs/3rdparty/winpty/misc/BufferResizeTests.cc create mode 100644 src/libs/3rdparty/winpty/misc/ChangeScreenBuffer.cc create mode 100644 src/libs/3rdparty/winpty/misc/ClearConsole.cc create mode 100644 src/libs/3rdparty/winpty/misc/ConinMode.cc create mode 100644 src/libs/3rdparty/winpty/misc/ConinMode.ps1 create mode 100644 src/libs/3rdparty/winpty/misc/ConoutMode.cc create mode 100644 src/libs/3rdparty/winpty/misc/DebugClient.py create mode 100644 src/libs/3rdparty/winpty/misc/DebugServer.py create mode 100644 src/libs/3rdparty/winpty/misc/DumpLines.py create mode 100644 src/libs/3rdparty/winpty/misc/EnableExtendedFlags.txt create mode 100644 src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP437-Consolas.txt create mode 100644 src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP437-Lucida.txt create mode 100644 src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP932.txt create mode 100644 src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP936.txt create mode 100644 src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP949.txt create mode 100644 src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP950.txt create mode 100644 src/libs/3rdparty/winpty/misc/Font-Report-June2016/MinimumWindowWidths.txt create mode 100644 src/libs/3rdparty/winpty/misc/Font-Report-June2016/Results.txt create mode 100644 src/libs/3rdparty/winpty/misc/Font-Report-June2016/Windows10SetFontBugginess.txt create mode 100644 src/libs/3rdparty/winpty/misc/FontSurvey.cc create mode 100644 src/libs/3rdparty/winpty/misc/FormatChar.h create mode 100644 src/libs/3rdparty/winpty/misc/FreezePerfTest.cc create mode 100644 src/libs/3rdparty/winpty/misc/GetCh.cc create mode 100644 src/libs/3rdparty/winpty/misc/GetConsolePos.cc create mode 100644 src/libs/3rdparty/winpty/misc/GetFont.cc create mode 100644 src/libs/3rdparty/winpty/misc/IdentifyConsoleWindow.ps1 create mode 100644 src/libs/3rdparty/winpty/misc/IsNewConsole.cc create mode 100644 src/libs/3rdparty/winpty/misc/MouseInputNotes.txt create mode 100644 src/libs/3rdparty/winpty/misc/MoveConsoleWindow.cc create mode 100644 src/libs/3rdparty/winpty/misc/Notes.txt create mode 100644 src/libs/3rdparty/winpty/misc/OSVersion.cc create mode 100644 src/libs/3rdparty/winpty/misc/ScreenBufferFreezeInactive.cc create mode 100644 src/libs/3rdparty/winpty/misc/ScreenBufferTest.cc create mode 100644 src/libs/3rdparty/winpty/misc/ScreenBufferTest2.cc create mode 100644 src/libs/3rdparty/winpty/misc/SelectAllTest.cc create mode 100644 src/libs/3rdparty/winpty/misc/SetBufInfo.cc create mode 100644 src/libs/3rdparty/winpty/misc/SetBufferSize.cc create mode 100644 src/libs/3rdparty/winpty/misc/SetCursorPos.cc create mode 100644 src/libs/3rdparty/winpty/misc/SetFont.cc create mode 100644 src/libs/3rdparty/winpty/misc/SetWindowRect.cc create mode 100644 src/libs/3rdparty/winpty/misc/ShowArgv.cc create mode 100644 src/libs/3rdparty/winpty/misc/ShowConsoleInput.cc create mode 100644 src/libs/3rdparty/winpty/misc/Spew.py create mode 100644 src/libs/3rdparty/winpty/misc/TestUtil.cc create mode 100644 src/libs/3rdparty/winpty/misc/UnicodeDoubleWidthTest.cc create mode 100644 src/libs/3rdparty/winpty/misc/UnicodeWideTest1.cc create mode 100644 src/libs/3rdparty/winpty/misc/UnicodeWideTest2.cc create mode 100644 src/libs/3rdparty/winpty/misc/UnixEcho.cc create mode 100644 src/libs/3rdparty/winpty/misc/Utf16Echo.cc create mode 100644 src/libs/3rdparty/winpty/misc/VeryLargeRead.cc create mode 100644 src/libs/3rdparty/winpty/misc/VkEscapeTest.cc create mode 100644 src/libs/3rdparty/winpty/misc/Win10ResizeWhileFrozen.cc create mode 100644 src/libs/3rdparty/winpty/misc/Win10WrapTest1.cc create mode 100644 src/libs/3rdparty/winpty/misc/Win10WrapTest2.cc create mode 100644 src/libs/3rdparty/winpty/misc/Win32Echo1.cc create mode 100644 src/libs/3rdparty/winpty/misc/Win32Echo2.cc create mode 100644 src/libs/3rdparty/winpty/misc/Win32Test1.cc create mode 100644 src/libs/3rdparty/winpty/misc/Win32Test2.cc create mode 100644 src/libs/3rdparty/winpty/misc/Win32Test3.cc create mode 100644 src/libs/3rdparty/winpty/misc/Win32Write1.cc create mode 100644 src/libs/3rdparty/winpty/misc/WindowsBugCrashReader.cc create mode 100644 src/libs/3rdparty/winpty/misc/WriteConsole.cc create mode 100644 src/libs/3rdparty/winpty/misc/build32.sh create mode 100644 src/libs/3rdparty/winpty/misc/build64.sh create mode 100644 src/libs/3rdparty/winpty/misc/color-test.sh create mode 100644 src/libs/3rdparty/winpty/misc/font-notes.txt create mode 100644 src/libs/3rdparty/winpty/misc/winbug-15048.cc create mode 100644 src/libs/3rdparty/winpty/ship/build-pty4j-libpty.bat create mode 100644 src/libs/3rdparty/winpty/ship/common_ship.py create mode 100644 src/libs/3rdparty/winpty/ship/make_msvc_package.py create mode 100644 src/libs/3rdparty/winpty/ship/ship.py create mode 100644 src/libs/3rdparty/winpty/src/CMakeLists.txt create mode 100644 src/libs/3rdparty/winpty/src/agent/Agent.cc create mode 100644 src/libs/3rdparty/winpty/src/agent/Agent.h create mode 100644 src/libs/3rdparty/winpty/src/agent/AgentCreateDesktop.cc create mode 100644 src/libs/3rdparty/winpty/src/agent/AgentCreateDesktop.h create mode 100644 src/libs/3rdparty/winpty/src/agent/ConsoleFont.cc create mode 100644 src/libs/3rdparty/winpty/src/agent/ConsoleFont.h create mode 100644 src/libs/3rdparty/winpty/src/agent/ConsoleInput.cc create mode 100644 src/libs/3rdparty/winpty/src/agent/ConsoleInput.h create mode 100644 src/libs/3rdparty/winpty/src/agent/ConsoleInputReencoding.cc create mode 100644 src/libs/3rdparty/winpty/src/agent/ConsoleInputReencoding.h create mode 100644 src/libs/3rdparty/winpty/src/agent/ConsoleLine.cc create mode 100644 src/libs/3rdparty/winpty/src/agent/ConsoleLine.h create mode 100644 src/libs/3rdparty/winpty/src/agent/Coord.h create mode 100644 src/libs/3rdparty/winpty/src/agent/DebugShowInput.cc create mode 100644 src/libs/3rdparty/winpty/src/agent/DebugShowInput.h create mode 100644 src/libs/3rdparty/winpty/src/agent/DefaultInputMap.cc create mode 100644 src/libs/3rdparty/winpty/src/agent/DefaultInputMap.h create mode 100644 src/libs/3rdparty/winpty/src/agent/DsrSender.h create mode 100644 src/libs/3rdparty/winpty/src/agent/EventLoop.cc create mode 100644 src/libs/3rdparty/winpty/src/agent/EventLoop.h create mode 100644 src/libs/3rdparty/winpty/src/agent/InputMap.cc create mode 100644 src/libs/3rdparty/winpty/src/agent/InputMap.h create mode 100644 src/libs/3rdparty/winpty/src/agent/LargeConsoleRead.cc create mode 100644 src/libs/3rdparty/winpty/src/agent/LargeConsoleRead.h create mode 100644 src/libs/3rdparty/winpty/src/agent/NamedPipe.cc create mode 100644 src/libs/3rdparty/winpty/src/agent/NamedPipe.h create mode 100644 src/libs/3rdparty/winpty/src/agent/Scraper.cc create mode 100644 src/libs/3rdparty/winpty/src/agent/Scraper.h create mode 100644 src/libs/3rdparty/winpty/src/agent/SimplePool.h create mode 100644 src/libs/3rdparty/winpty/src/agent/SmallRect.h create mode 100644 src/libs/3rdparty/winpty/src/agent/Terminal.cc create mode 100644 src/libs/3rdparty/winpty/src/agent/Terminal.h create mode 100644 src/libs/3rdparty/winpty/src/agent/UnicodeEncoding.h create mode 100644 src/libs/3rdparty/winpty/src/agent/UnicodeEncodingTest.cc create mode 100644 src/libs/3rdparty/winpty/src/agent/Win32Console.cc create mode 100644 src/libs/3rdparty/winpty/src/agent/Win32Console.h create mode 100644 src/libs/3rdparty/winpty/src/agent/Win32ConsoleBuffer.cc create mode 100644 src/libs/3rdparty/winpty/src/agent/Win32ConsoleBuffer.h create mode 100644 src/libs/3rdparty/winpty/src/agent/main.cc create mode 100644 src/libs/3rdparty/winpty/src/agent/subdir.mk create mode 100644 src/libs/3rdparty/winpty/src/configurations.gypi create mode 100644 src/libs/3rdparty/winpty/src/debugserver/DebugServer.cc create mode 100644 src/libs/3rdparty/winpty/src/debugserver/subdir.mk create mode 100644 src/libs/3rdparty/winpty/src/include/winpty.h create mode 100644 src/libs/3rdparty/winpty/src/include/winpty_constants.h create mode 100644 src/libs/3rdparty/winpty/src/libwinpty/AgentLocation.cc create mode 100644 src/libs/3rdparty/winpty/src/libwinpty/AgentLocation.h create mode 100644 src/libs/3rdparty/winpty/src/libwinpty/LibWinptyException.h create mode 100644 src/libs/3rdparty/winpty/src/libwinpty/WinptyInternal.h create mode 100644 src/libs/3rdparty/winpty/src/libwinpty/subdir.mk create mode 100644 src/libs/3rdparty/winpty/src/libwinpty/winpty.cc create mode 100644 src/libs/3rdparty/winpty/src/shared/AgentMsg.h create mode 100644 src/libs/3rdparty/winpty/src/shared/BackgroundDesktop.cc create mode 100644 src/libs/3rdparty/winpty/src/shared/BackgroundDesktop.h create mode 100644 src/libs/3rdparty/winpty/src/shared/Buffer.cc create mode 100644 src/libs/3rdparty/winpty/src/shared/Buffer.h create mode 100644 src/libs/3rdparty/winpty/src/shared/DebugClient.cc create mode 100644 src/libs/3rdparty/winpty/src/shared/DebugClient.h create mode 100644 src/libs/3rdparty/winpty/src/shared/GenRandom.cc create mode 100644 src/libs/3rdparty/winpty/src/shared/GenRandom.h create mode 100644 src/libs/3rdparty/winpty/src/shared/GetCommitHash.bat create mode 100644 src/libs/3rdparty/winpty/src/shared/Mutex.h create mode 100644 src/libs/3rdparty/winpty/src/shared/OsModule.h create mode 100644 src/libs/3rdparty/winpty/src/shared/OwnedHandle.cc create mode 100644 src/libs/3rdparty/winpty/src/shared/OwnedHandle.h create mode 100644 src/libs/3rdparty/winpty/src/shared/PrecompiledHeader.h create mode 100644 src/libs/3rdparty/winpty/src/shared/StringBuilder.h create mode 100644 src/libs/3rdparty/winpty/src/shared/StringBuilderTest.cc create mode 100644 src/libs/3rdparty/winpty/src/shared/StringUtil.cc create mode 100644 src/libs/3rdparty/winpty/src/shared/StringUtil.h create mode 100644 src/libs/3rdparty/winpty/src/shared/TimeMeasurement.h create mode 100644 src/libs/3rdparty/winpty/src/shared/UnixCtrlChars.h create mode 100644 src/libs/3rdparty/winpty/src/shared/UpdateGenVersion.bat create mode 100644 src/libs/3rdparty/winpty/src/shared/WindowsSecurity.cc create mode 100644 src/libs/3rdparty/winpty/src/shared/WindowsSecurity.h create mode 100644 src/libs/3rdparty/winpty/src/shared/WindowsVersion.cc create mode 100644 src/libs/3rdparty/winpty/src/shared/WindowsVersion.h create mode 100644 src/libs/3rdparty/winpty/src/shared/WinptyAssert.cc create mode 100644 src/libs/3rdparty/winpty/src/shared/WinptyAssert.h create mode 100644 src/libs/3rdparty/winpty/src/shared/WinptyException.cc create mode 100644 src/libs/3rdparty/winpty/src/shared/WinptyException.h create mode 100644 src/libs/3rdparty/winpty/src/shared/WinptyVersion.cc create mode 100644 src/libs/3rdparty/winpty/src/shared/WinptyVersion.h create mode 100644 src/libs/3rdparty/winpty/src/shared/winpty_snprintf.h create mode 100644 src/libs/3rdparty/winpty/src/subdir.mk create mode 100644 src/libs/3rdparty/winpty/src/tests/subdir.mk create mode 100644 src/libs/3rdparty/winpty/src/tests/trivial_test.cc create mode 100644 src/libs/3rdparty/winpty/src/unix-adapter/InputHandler.cc create mode 100644 src/libs/3rdparty/winpty/src/unix-adapter/InputHandler.h create mode 100644 src/libs/3rdparty/winpty/src/unix-adapter/OutputHandler.cc create mode 100644 src/libs/3rdparty/winpty/src/unix-adapter/OutputHandler.h create mode 100644 src/libs/3rdparty/winpty/src/unix-adapter/Util.cc create mode 100644 src/libs/3rdparty/winpty/src/unix-adapter/Util.h create mode 100644 src/libs/3rdparty/winpty/src/unix-adapter/WakeupFd.cc create mode 100644 src/libs/3rdparty/winpty/src/unix-adapter/WakeupFd.h create mode 100644 src/libs/3rdparty/winpty/src/unix-adapter/main.cc create mode 100644 src/libs/3rdparty/winpty/src/unix-adapter/subdir.mk create mode 100644 src/libs/3rdparty/winpty/src/winpty.gyp create mode 100644 src/libs/3rdparty/winpty/vcbuild.bat create mode 100644 src/libs/3rdparty/winpty/winpty.qbs (limited to 'src/libs') diff --git a/src/libs/3rdparty/CMakeLists.txt b/src/libs/3rdparty/CMakeLists.txt index 7cf97ab87f..0cf9818ed5 100644 --- a/src/libs/3rdparty/CMakeLists.txt +++ b/src/libs/3rdparty/CMakeLists.txt @@ -1,2 +1,8 @@ add_subdirectory(cplusplus) add_subdirectory(syntax-highlighting) +add_subdirectory(libvterm) +add_subdirectory(libptyqt) + +if(WIN32) + add_subdirectory(winpty) +endif() diff --git a/src/libs/3rdparty/libptyqt/CMakeLists.txt b/src/libs/3rdparty/libptyqt/CMakeLists.txt new file mode 100644 index 0000000000..c6e8b74573 --- /dev/null +++ b/src/libs/3rdparty/libptyqt/CMakeLists.txt @@ -0,0 +1,28 @@ +set(SOURCES + iptyprocess.h + ptyqt.cpp ptyqt.h +) + +if (WIN32) + list(APPEND SOURCES + winptyprocess.cpp winptyprocess.h + conptyprocess.cpp conptyprocess.h + ) +else() + list(APPEND SOURCES unixptyprocess.cpp unixptyprocess.h) +endif() + +add_library(ptyqt STATIC ${SOURCES}) +target_link_libraries(ptyqt PUBLIC Qt::Core) + +if (WIN32) + target_link_libraries(ptyqt PRIVATE winpty Qt::Network) + #target_compile_definitions(ptyqt PRIVATE PTYQT_DEBUG) +endif() + +set_target_properties(ptyqt + PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR} + QT_COMPILE_OPTIONS_DISABLE_WARNINGS ON + POSITION_INDEPENDENT_CODE ON +) diff --git a/src/libs/3rdparty/libptyqt/LICENSE b/src/libs/3rdparty/libptyqt/LICENSE new file mode 100644 index 0000000000..73996c7c90 --- /dev/null +++ b/src/libs/3rdparty/libptyqt/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Vitaly Petrov, v31337@gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/src/libs/3rdparty/libptyqt/conptyprocess.cpp b/src/libs/3rdparty/libptyqt/conptyprocess.cpp new file mode 100644 index 0000000000..b50f319ebf --- /dev/null +++ b/src/libs/3rdparty/libptyqt/conptyprocess.cpp @@ -0,0 +1,346 @@ +#include "conptyprocess.h" +#include +#include +#include +#include +#include +#include +#include + +#include + +#define READ_INTERVAL_MSEC 500 + +HRESULT ConPtyProcess::createPseudoConsoleAndPipes(HPCON* phPC, HANDLE* phPipeIn, HANDLE* phPipeOut, qint16 cols, qint16 rows) +{ + HRESULT hr{ E_UNEXPECTED }; + HANDLE hPipePTYIn{ INVALID_HANDLE_VALUE }; + HANDLE hPipePTYOut{ INVALID_HANDLE_VALUE }; + + // Create the pipes to which the ConPTY will connect + if (CreatePipe(&hPipePTYIn, phPipeOut, NULL, 0) && + CreatePipe(phPipeIn, &hPipePTYOut, NULL, 0)) + { + // Create the Pseudo Console of the required size, attached to the PTY-end of the pipes + hr = m_winContext.createPseudoConsole({cols, rows}, hPipePTYIn, hPipePTYOut, 0, phPC); + + // Note: We can close the handles to the PTY-end of the pipes here + // because the handles are dup'ed into the ConHost and will be released + // when the ConPTY is destroyed. + if (INVALID_HANDLE_VALUE != hPipePTYOut) CloseHandle(hPipePTYOut); + if (INVALID_HANDLE_VALUE != hPipePTYIn) CloseHandle(hPipePTYIn); + } + + return hr; +} + +// Initializes the specified startup info struct with the required properties and +// updates its thread attribute list with the specified ConPTY handle +HRESULT ConPtyProcess::initializeStartupInfoAttachedToPseudoConsole(STARTUPINFOEX* pStartupInfo, HPCON hPC) +{ + HRESULT hr{ E_UNEXPECTED }; + + if (pStartupInfo) + { + SIZE_T attrListSize{}; + + pStartupInfo->StartupInfo.hStdInput = m_hPipeIn; + pStartupInfo->StartupInfo.hStdError = m_hPipeOut; + pStartupInfo->StartupInfo.hStdOutput = m_hPipeOut; + pStartupInfo->StartupInfo.dwFlags |= STARTF_USESTDHANDLES; + + pStartupInfo->StartupInfo.cb = sizeof(STARTUPINFOEX); + + // Get the size of the thread attribute list. + InitializeProcThreadAttributeList(NULL, 1, 0, &attrListSize); + + // Allocate a thread attribute list of the correct size + pStartupInfo->lpAttributeList = reinterpret_cast( + HeapAlloc(GetProcessHeap(), 0, attrListSize)); + + // Initialize thread attribute list + if (pStartupInfo->lpAttributeList + && InitializeProcThreadAttributeList(pStartupInfo->lpAttributeList, 1, 0, &attrListSize)) + { + // Set Pseudo Console attribute + hr = UpdateProcThreadAttribute( + pStartupInfo->lpAttributeList, + 0, + PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, + hPC, + sizeof(HPCON), + NULL, + NULL) + ? S_OK + : HRESULT_FROM_WIN32(GetLastError()); + } + else + { + hr = HRESULT_FROM_WIN32(GetLastError()); + } + } + return hr; +} + +ConPtyProcess::ConPtyProcess() + : IPtyProcess() + , m_ptyHandler { INVALID_HANDLE_VALUE } + , m_hPipeIn { INVALID_HANDLE_VALUE } + , m_hPipeOut { INVALID_HANDLE_VALUE } + , m_readThread(nullptr) +{ + +} + +ConPtyProcess::~ConPtyProcess() +{ + kill(); +} + +bool ConPtyProcess::startProcess(const QString &executable, + const QStringList &arguments, + const QString &workingDir, + QStringList environment, + qint16 cols, + qint16 rows) +{ + if (!isAvailable()) + { + m_lastError = m_winContext.lastError(); + return false; + } + + //already running + if (m_ptyHandler != INVALID_HANDLE_VALUE) + return false; + + QFileInfo fi(executable); + if (fi.isRelative() || !QFile::exists(executable)) { + //todo add auto-find executable in PATH env var + m_lastError = QString("ConPty Error: shell file path '%1' must be absolute").arg(executable); + return false; + } + + m_shellPath = executable; + m_size = QPair(cols, rows); + + //env + std::wstringstream envBlock; + for (const QString &line: std::as_const(environment)) + { + envBlock << line.toStdWString() << L'\0'; + } + envBlock << L'\0'; + std::wstring env = envBlock.str(); + LPWSTR envArg = env.empty() ? nullptr : env.data(); + + QStringList exeAndArgs = arguments; + exeAndArgs.prepend(m_shellPath); + + std::wstring cmdArg = exeAndArgs.join(" ").toStdWString(); + + HRESULT hr{ E_UNEXPECTED }; + + // Create the Pseudo Console and pipes to it + hr = createPseudoConsoleAndPipes(&m_ptyHandler, &m_hPipeIn, &m_hPipeOut, cols, rows); + + if (S_OK != hr) + { + m_lastError = QString("ConPty Error: CreatePseudoConsoleAndPipes fail"); + return false; + } + + // Initialize the necessary startup info struct + if (S_OK != initializeStartupInfoAttachedToPseudoConsole(&m_shellStartupInfo, m_ptyHandler)) + { + m_lastError = QString("ConPty Error: InitializeStartupInfoAttachedToPseudoConsole fail"); + return false; + } + + hr = CreateProcess(NULL, // No module name - use Command Line + cmdArg.data(), // Command Line + NULL, // Process handle not inheritable + NULL, // Thread handle not inheritable + FALSE, // Inherit handles + EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT, // Creation flags + envArg, // Environment block + workingDir.toStdWString().c_str(), // Use parent's starting directory + &m_shellStartupInfo.StartupInfo, // Pointer to STARTUPINFO + &m_shellProcessInformation) // Pointer to PROCESS_INFORMATION + ? S_OK + : GetLastError(); + + if (S_OK != hr) + { + m_lastError = QString("ConPty Error: Cannot create process -> %1").arg(hr); + return false; + } + + m_pid = m_shellProcessInformation.dwProcessId; + + // Notify when the shell process has been terminated + RegisterWaitForSingleObject( + &m_shellCloseWaitHandle, + m_shellProcessInformation.hProcess, + [](PVOID data, BOOLEAN) { + auto self = static_cast(data); + DWORD exitCode = 0; + GetExitCodeProcess(self->m_shellProcessInformation.hProcess, &exitCode); + self->m_exitCode = exitCode; + // Do not respawn if the object is about to be destructed + if (!self->m_aboutToDestruct) + emit self->notifier()->aboutToClose(); + }, + this, + INFINITE, + WT_EXECUTEONLYONCE); + + //this code runned in separate thread + m_readThread = QThread::create([this]() + { + //buffers + const DWORD BUFF_SIZE{ 1024 }; + char szBuffer[BUFF_SIZE]{}; + + forever + { + DWORD dwBytesRead{}; + + // Read from the pipe + BOOL result = ReadFile(m_hPipeIn, szBuffer, BUFF_SIZE, &dwBytesRead, NULL); + + const bool needMoreData = !result && GetLastError() == ERROR_MORE_DATA; + if (result || needMoreData) { + QMutexLocker locker(&m_bufferMutex); + m_buffer.m_readBuffer.append(szBuffer, dwBytesRead); + m_buffer.emitReadyRead(); + } + + const bool brokenPipe = !result && GetLastError() == ERROR_BROKEN_PIPE; + if (QThread::currentThread()->isInterruptionRequested() || brokenPipe) + break; + } + }); + + //start read thread + m_readThread->start(); + + return true; +} + +bool ConPtyProcess::resize(qint16 cols, qint16 rows) +{ + if (m_ptyHandler == nullptr) + { + return false; + } + + bool res = SUCCEEDED(m_winContext.resizePseudoConsole(m_ptyHandler, {cols, rows})); + + if (res) + { + m_size = QPair(cols, rows); + } + + return res; + + return true; +} + +bool ConPtyProcess::kill() +{ + bool exitCode = false; + + if (m_ptyHandler != INVALID_HANDLE_VALUE) { + m_aboutToDestruct = true; + + // Close ConPTY - this will terminate client process if running + m_winContext.closePseudoConsole(m_ptyHandler); + + // Clean-up the pipes + if (INVALID_HANDLE_VALUE != m_hPipeOut) + CloseHandle(m_hPipeOut); + if (INVALID_HANDLE_VALUE != m_hPipeIn) + CloseHandle(m_hPipeIn); + + m_readThread->requestInterruption(); + if (!m_readThread->wait(1000)) + m_readThread->terminate(); + m_readThread->deleteLater(); + m_readThread = nullptr; + + m_pid = 0; + m_ptyHandler = INVALID_HANDLE_VALUE; + m_hPipeIn = INVALID_HANDLE_VALUE; + m_hPipeOut = INVALID_HANDLE_VALUE; + + CloseHandle(m_shellProcessInformation.hThread); + CloseHandle(m_shellProcessInformation.hProcess); + UnregisterWait(m_shellCloseWaitHandle); + + // Cleanup attribute list + if (m_shellStartupInfo.lpAttributeList) { + DeleteProcThreadAttributeList(m_shellStartupInfo.lpAttributeList); + HeapFree(GetProcessHeap(), 0, m_shellStartupInfo.lpAttributeList); + } + + exitCode = true; + } + + return exitCode; +} + +IPtyProcess::PtyType ConPtyProcess::type() +{ + return PtyType::ConPty; +} + +QString ConPtyProcess::dumpDebugInfo() +{ +#ifdef PTYQT_DEBUG + return QString("PID: %1, Type: %2, Cols: %3, Rows: %4") + .arg(m_pid).arg(type()) + .arg(m_size.first).arg(m_size.second); +#else + return QString("Nothing..."); +#endif +} + +QIODevice *ConPtyProcess::notifier() +{ + return &m_buffer; +} + +QByteArray ConPtyProcess::readAll() +{ + QByteArray result; + { + QMutexLocker locker(&m_bufferMutex); + result.swap(m_buffer.m_readBuffer); + } + return result; +} + +qint64 ConPtyProcess::write(const QByteArray &byteArray) +{ + DWORD dwBytesWritten{}; + WriteFile(m_hPipeOut, byteArray.data(), byteArray.size(), &dwBytesWritten, NULL); + return dwBytesWritten; +} + +bool ConPtyProcess::isAvailable() +{ +#ifdef TOO_OLD_WINSDK + return false; //very importnant! ConPty can be built, but it doesn't work if built with old sdk and Win10 < 1903 +#endif + + qint32 buildNumber = QSysInfo::kernelVersion().split(".").last().toInt(); + if (buildNumber < CONPTY_MINIMAL_WINDOWS_VERSION) + return false; + return m_winContext.init(); +} + +void ConPtyProcess::moveToThread(QThread *targetThread) +{ + //nothing for now... +} diff --git a/src/libs/3rdparty/libptyqt/conptyprocess.h b/src/libs/3rdparty/libptyqt/conptyprocess.h new file mode 100644 index 0000000000..b3f77664c2 --- /dev/null +++ b/src/libs/3rdparty/libptyqt/conptyprocess.h @@ -0,0 +1,167 @@ +#ifndef CONPTYPROCESS_H +#define CONPTYPROCESS_H + +#include "iptyprocess.h" +#include +#include +#include + +#include +#include +#include +#include +#include + +//Taken from the RS5 Windows SDK, but redefined here in case we're targeting <= 17733 +//Just for compile, ConPty doesn't work with Windows SDK < 17733 +#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE +#define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE \ + ProcThreadAttributeValue(22, FALSE, TRUE, FALSE) + +typedef VOID* HPCON; + +#define TOO_OLD_WINSDK +#endif + +template +std::vector vectorFromString(const std::basic_string &str) +{ + return std::vector(str.begin(), str.end()); +} + +//ConPTY available only on Windows 10 releazed after 1903 (19H1) Windows release +class WindowsContext +{ +public: + typedef HRESULT (*CreatePseudoConsolePtr)( + COORD size, // ConPty Dimensions + HANDLE hInput, // ConPty Input + HANDLE hOutput, // ConPty Output + DWORD dwFlags, // ConPty Flags + HPCON* phPC); // ConPty Reference + + typedef HRESULT (*ResizePseudoConsolePtr)(HPCON hPC, COORD size); + + typedef VOID (*ClosePseudoConsolePtr)(HPCON hPC); + + WindowsContext() + : createPseudoConsole(nullptr) + , resizePseudoConsole(nullptr) + , closePseudoConsole(nullptr) + { + + } + + bool init() + { + //already initialized + if (createPseudoConsole) + return true; + + //try to load symbols from library + //if it fails -> we can't use ConPty API + HANDLE kernel32Handle = LoadLibraryExW(L"kernel32.dll", 0, 0); + + if (kernel32Handle != nullptr) + { + createPseudoConsole = (CreatePseudoConsolePtr)GetProcAddress((HMODULE)kernel32Handle, "CreatePseudoConsole"); + resizePseudoConsole = (ResizePseudoConsolePtr)GetProcAddress((HMODULE)kernel32Handle, "ResizePseudoConsole"); + closePseudoConsole = (ClosePseudoConsolePtr)GetProcAddress((HMODULE)kernel32Handle, "ClosePseudoConsole"); + if (createPseudoConsole == NULL || resizePseudoConsole == NULL || closePseudoConsole == NULL) + { + m_lastError = QString("WindowsContext/ConPty error: %1").arg("Invalid on load API functions"); + return false; + } + } + else + { + m_lastError = QString("WindowsContext/ConPty error: %1").arg("Unable to load kernel32"); + return false; + } + + return true; + } + + QString lastError() + { + return m_lastError; + } + +public: + //vars + CreatePseudoConsolePtr createPseudoConsole; + ResizePseudoConsolePtr resizePseudoConsole; + ClosePseudoConsolePtr closePseudoConsole; + +private: + QString m_lastError; +}; + +class PtyBuffer : public QIODevice +{ + friend class ConPtyProcess; + Q_OBJECT +public: + + //just empty realization, we need only 'readyRead' signal of this class + qint64 readData(char *data, qint64 maxlen) { return 0; } + qint64 writeData(const char *data, qint64 len) { return 0; } + + bool isSequential() const { return true; } + qint64 bytesAvailable() const { return m_readBuffer.size(); } + qint64 size() const { return m_readBuffer.size(); } + + void emitReadyRead() + { + //for emit signal from PtyBuffer own thread + QTimer::singleShot(1, this, [this]() + { + emit readyRead(); + }); + } + +private: + QByteArray m_readBuffer; +}; + +class ConPtyProcess : public IPtyProcess +{ +public: + ConPtyProcess(); + ~ConPtyProcess(); + + bool startProcess(const QString &executable, + const QStringList &arguments, + const QString &workingDir, + QStringList environment, + qint16 cols, + qint16 rows); + bool resize(qint16 cols, qint16 rows); + bool kill(); + PtyType type(); + QString dumpDebugInfo(); + virtual QIODevice *notifier(); + virtual QByteArray readAll(); + virtual qint64 write(const QByteArray &byteArray); + bool isAvailable(); + void moveToThread(QThread *targetThread); + +private: + HRESULT createPseudoConsoleAndPipes(HPCON* phPC, HANDLE* phPipeIn, HANDLE* phPipeOut, qint16 cols, qint16 rows); + HRESULT initializeStartupInfoAttachedToPseudoConsole(STARTUPINFOEX* pStartupInfo, HPCON hPC); + +private: + WindowsContext m_winContext; + HPCON m_ptyHandler; + HANDLE m_hPipeIn, m_hPipeOut; + + QThread *m_readThread; + QMutex m_bufferMutex; + PtyBuffer m_buffer; + bool m_aboutToDestruct{false}; + PROCESS_INFORMATION m_shellProcessInformation{}; + HANDLE m_shellCloseWaitHandle{INVALID_HANDLE_VALUE}; + STARTUPINFOEX m_shellStartupInfo{}; +}; + +#endif // CONPTYPROCESS_H diff --git a/src/libs/3rdparty/libptyqt/iptyprocess.h b/src/libs/3rdparty/libptyqt/iptyprocess.h new file mode 100644 index 0000000000..3d974908c8 --- /dev/null +++ b/src/libs/3rdparty/libptyqt/iptyprocess.h @@ -0,0 +1,56 @@ +#ifndef IPTYPROCESS_H +#define IPTYPROCESS_H + +#include +#include +#include + +#define CONPTY_MINIMAL_WINDOWS_VERSION 18309 + +class IPtyProcess +{ +public: + enum PtyType { UnixPty = 0, WinPty = 1, ConPty = 2, AutoPty = 3 }; + + IPtyProcess() = default; + IPtyProcess(const IPtyProcess &) = delete; + IPtyProcess &operator=(const IPtyProcess &) = delete; + + virtual ~IPtyProcess() {} + + virtual bool startProcess(const QString &executable, + const QStringList &arguments, + const QString &workingDir, + QStringList environment, + qint16 cols, + qint16 rows) + = 0; + virtual bool resize(qint16 cols, qint16 rows) = 0; + virtual bool kill() = 0; + virtual PtyType type() = 0; + virtual QString dumpDebugInfo() = 0; + virtual QIODevice *notifier() = 0; + virtual QByteArray readAll() = 0; + virtual qint64 write(const QByteArray &byteArray) = 0; + virtual bool isAvailable() = 0; + virtual void moveToThread(QThread *targetThread) = 0; + qint64 pid() { return m_pid; } + QPair size() { return m_size; } + const QString lastError() { return m_lastError; } + int exitCode() { return m_exitCode; } + bool toggleTrace() + { + m_trace = !m_trace; + return m_trace; + } + +protected: + QString m_shellPath; + QString m_lastError; + qint64 m_pid{0}; + int m_exitCode{0}; + QPair m_size; //cols / rows + bool m_trace{false}; +}; + +#endif // IPTYPROCESS_H diff --git a/src/libs/3rdparty/libptyqt/ptyqt.cpp b/src/libs/3rdparty/libptyqt/ptyqt.cpp new file mode 100644 index 0000000000..3883395e22 --- /dev/null +++ b/src/libs/3rdparty/libptyqt/ptyqt.cpp @@ -0,0 +1,45 @@ +#include "ptyqt.h" +#include + +#ifdef Q_OS_WIN +#include "winptyprocess.h" +#include "conptyprocess.h" +#endif + +#ifdef Q_OS_UNIX +#include "unixptyprocess.h" +#endif + + +IPtyProcess *PtyQt::createPtyProcess(IPtyProcess::PtyType ptyType) +{ + switch (ptyType) + { +#ifdef Q_OS_WIN + case IPtyProcess::PtyType::WinPty: + return new WinPtyProcess(); + break; + case IPtyProcess::PtyType::ConPty: + return new ConPtyProcess(); + break; +#endif +#ifdef Q_OS_UNIX + case IPtyProcess::PtyType::UnixPty: + return new UnixPtyProcess(); + break; +#endif + case IPtyProcess::PtyType::AutoPty: + default: + break; + } + +#ifdef Q_OS_WIN + if (ConPtyProcess().isAvailable()) + return new ConPtyProcess(); + else + return new WinPtyProcess(); +#endif +#ifdef Q_OS_UNIX + return new UnixPtyProcess(); +#endif +} diff --git a/src/libs/3rdparty/libptyqt/ptyqt.h b/src/libs/3rdparty/libptyqt/ptyqt.h new file mode 100644 index 0000000000..23b80d346b --- /dev/null +++ b/src/libs/3rdparty/libptyqt/ptyqt.h @@ -0,0 +1,12 @@ +#ifndef PTYQT_H +#define PTYQT_H + +#include "iptyprocess.h" + +class PtyQt +{ +public: + static IPtyProcess *createPtyProcess(IPtyProcess::PtyType ptyType); +}; + +#endif // PTYQT_H diff --git a/src/libs/3rdparty/libptyqt/ptyqt.qbs b/src/libs/3rdparty/libptyqt/ptyqt.qbs new file mode 100644 index 0000000000..7ff4da9f56 --- /dev/null +++ b/src/libs/3rdparty/libptyqt/ptyqt.qbs @@ -0,0 +1,45 @@ +import qbs + +Project { + name: "ptyqt" + + QtcLibrary { + Depends { name: "Qt.core" } + Depends { name: "Qt.network"; condition: qbs.targetOS.contains("windows") } + Depends { name: "winpty"; condition: qbs.targetOS.contains("windows") } + + type: "staticlibrary" + + files: [ + "iptyprocess.h", + "ptyqt.cpp", + "ptyqt.h", + ] + + Group { + name: "ptyqt UNIX files" + condition: qbs.targetOS.contains("unix") + files: [ + "unixptyprocess.cpp", + "unixptyprocess.h", + ] + } + + Group { + name: "ptyqt Windows files" + condition: qbs.targetOS.contains("windows") + files: [ + "conptyprocess.cpp", + "conptyprocess.h", + "winptyprocess.cpp", + "winptyprocess.h", + ] + } + + Export { + Depends { name: "cpp" } + Depends { name: "winpty"; condition: qbs.targetOS.contains("windows") } + cpp.includePaths: base.concat(exportingProduct.sourceDirectory) + } + } +} diff --git a/src/libs/3rdparty/libptyqt/unixptyprocess.cpp b/src/libs/3rdparty/libptyqt/unixptyprocess.cpp new file mode 100644 index 0000000000..a9167e7696 --- /dev/null +++ b/src/libs/3rdparty/libptyqt/unixptyprocess.cpp @@ -0,0 +1,376 @@ +#include "unixptyprocess.h" +#include + +#include +#include +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_FREEBSD) +#include +#endif +#include +#include +#include +#include +#include +#include + +UnixPtyProcess::UnixPtyProcess() + : IPtyProcess() + , m_readMasterNotify(0) +{ + m_shellProcess.setWorkingDirectory( + QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); +} + +UnixPtyProcess::~UnixPtyProcess() +{ + kill(); +} + +bool UnixPtyProcess::startProcess(const QString &shellPath, + const QStringList &arguments, + const QString &workingDir, + QStringList environment, + qint16 cols, + qint16 rows) +{ + if (!isAvailable()) { + m_lastError = QString("UnixPty Error: unavailable"); + return false; + } + + if (m_shellProcess.state() == QProcess::Running) + return false; + + QFileInfo fi(shellPath); + if (fi.isRelative() || !QFile::exists(shellPath)) { + //todo add auto-find executable in PATH env var + m_lastError = QString("UnixPty Error: shell file path must be absolute"); + return false; + } + + m_shellPath = shellPath; + m_size = QPair(cols, rows); + + int rc = 0; + + m_shellProcess.m_handleMaster = ::posix_openpt(O_RDWR | O_NOCTTY); + if (m_shellProcess.m_handleMaster <= 0) { + m_lastError = QString("UnixPty Error: unable to open master -> %1").arg(QLatin1String(strerror(errno))); + kill(); + return false; + } + + m_shellProcess.m_handleSlaveName = QLatin1String(ptsname(m_shellProcess.m_handleMaster)); + if (m_shellProcess.m_handleSlaveName.isEmpty()) { + m_lastError = QString("UnixPty Error: unable to get slave name -> %1").arg(QLatin1String(strerror(errno))); + kill(); + return false; + } + + rc = grantpt(m_shellProcess.m_handleMaster); + if (rc != 0) { + m_lastError + = QString("UnixPty Error: unable to change perms for slave -> %1").arg(QLatin1String(strerror(errno))); + kill(); + return false; + } + + rc = unlockpt(m_shellProcess.m_handleMaster); + if (rc != 0) { + m_lastError = QString("UnixPty Error: unable to unlock slave -> %1").arg(QLatin1String(strerror(errno))); + kill(); + return false; + } + + m_shellProcess.m_handleSlave = ::open(m_shellProcess.m_handleSlaveName.toLatin1().data(), + O_RDWR | O_NOCTTY); + if (m_shellProcess.m_handleSlave < 0) { + m_lastError = QString("UnixPty Error: unable to open slave -> %1").arg(QLatin1String(strerror(errno))); + kill(); + return false; + } + + rc = fcntl(m_shellProcess.m_handleMaster, F_SETFD, FD_CLOEXEC); + if (rc == -1) { + m_lastError + = QString("UnixPty Error: unable to set flags for master -> %1").arg(QLatin1String(strerror(errno))); + kill(); + return false; + } + + rc = fcntl(m_shellProcess.m_handleSlave, F_SETFD, FD_CLOEXEC); + if (rc == -1) { + m_lastError + = QString("UnixPty Error: unable to set flags for slave -> %1").arg(QLatin1String(strerror(errno))); + kill(); + return false; + } + + struct ::termios ttmode; + rc = tcgetattr(m_shellProcess.m_handleMaster, &ttmode); + if (rc != 0) { + m_lastError = QString("UnixPty Error: termios fail -> %1").arg(QLatin1String(strerror(errno))); + kill(); + return false; + } + + ttmode.c_iflag = ICRNL | IXON | IXANY | IMAXBEL | BRKINT; +#if defined(IUTF8) + ttmode.c_iflag |= IUTF8; +#endif + + ttmode.c_oflag = OPOST | ONLCR; + ttmode.c_cflag = CREAD | CS8 | HUPCL; + ttmode.c_lflag = ICANON | ISIG | IEXTEN | ECHO | ECHOE | ECHOK | ECHOKE | ECHOCTL; + + ttmode.c_cc[VEOF] = 4; + ttmode.c_cc[VEOL] = -1; + ttmode.c_cc[VEOL2] = -1; + ttmode.c_cc[VERASE] = 0x7f; + ttmode.c_cc[VWERASE] = 23; + ttmode.c_cc[VKILL] = 21; + ttmode.c_cc[VREPRINT] = 18; + ttmode.c_cc[VINTR] = 3; + ttmode.c_cc[VQUIT] = 0x1c; + ttmode.c_cc[VSUSP] = 26; + ttmode.c_cc[VSTART] = 17; + ttmode.c_cc[VSTOP] = 19; + ttmode.c_cc[VLNEXT] = 22; + ttmode.c_cc[VDISCARD] = 15; + ttmode.c_cc[VMIN] = 1; + ttmode.c_cc[VTIME] = 0; + +#if (__APPLE__) + ttmode.c_cc[VDSUSP] = 25; + ttmode.c_cc[VSTATUS] = 20; +#endif + + cfsetispeed(&ttmode, B38400); + cfsetospeed(&ttmode, B38400); + + rc = tcsetattr(m_shellProcess.m_handleMaster, TCSANOW, &ttmode); + if (rc != 0) { + m_lastError + = QString("UnixPty Error: unabble to set associated params -> %1").arg(QLatin1String(strerror(errno))); + kill(); + return false; + } + + m_readMasterNotify = new QSocketNotifier(m_shellProcess.m_handleMaster, + QSocketNotifier::Read, + &m_shellProcess); + m_readMasterNotify->setEnabled(true); + m_readMasterNotify->moveToThread(m_shellProcess.thread()); + QObject::connect(m_readMasterNotify, &QSocketNotifier::activated, [this](int socket) { + Q_UNUSED(socket) + + QByteArray buffer; + int size = 1025; + int readSize = 1024; + QByteArray data; + do { + char nativeBuffer[size]; + int len = ::read(m_shellProcess.m_handleMaster, nativeBuffer, readSize); + data = QByteArray(nativeBuffer, len); + buffer.append(data); + } while (data.size() == readSize); //last data block always < readSize + + m_shellReadBuffer.append(buffer); + m_shellProcess.emitReadyRead(); + }); + + QObject::connect(&m_shellProcess, &QProcess::finished, &m_shellProcess, [this](int exitCode) { + m_exitCode = exitCode; + emit m_shellProcess.aboutToClose(); + }); + + QStringList defaultVars; + + defaultVars.append("TERM=xterm-256color"); + defaultVars.append("ITERM_PROFILE=Default"); + defaultVars.append("XPC_FLAGS=0x0"); + defaultVars.append("XPC_SERVICE_NAME=0"); + defaultVars.append("LANG=en_US.UTF-8"); + defaultVars.append("LC_ALL=en_US.UTF-8"); + defaultVars.append("LC_CTYPE=UTF-8"); + defaultVars.append("INIT_CWD=" + QCoreApplication::applicationDirPath()); + defaultVars.append("COMMAND_MODE=unix2003"); + defaultVars.append("COLORTERM=truecolor"); + + QStringList varNames; + foreach (QString line, environment) { + varNames.append(line.split("=").first()); + } + + //append default env vars only if they don't exists in current env + foreach (QString defVar, defaultVars) { + if (!varNames.contains(defVar.split("=").first())) + environment.append(defVar); + } + + QProcessEnvironment envFormat; + foreach (QString line, environment) { + envFormat.insert(line.split("=").first(), line.split("=").last()); + } + m_shellProcess.setWorkingDirectory(workingDir); + m_shellProcess.setProcessEnvironment(envFormat); + m_shellProcess.setReadChannel(QProcess::StandardOutput); + m_shellProcess.start(m_shellPath, arguments); + m_shellProcess.waitForStarted(); + + m_pid = m_shellProcess.processId(); + + resize(cols, rows); + + return true; +} + +bool UnixPtyProcess::resize(qint16 cols, qint16 rows) +{ + struct winsize winp; + winp.ws_col = cols; + winp.ws_row = rows; + winp.ws_xpixel = 0; + winp.ws_ypixel = 0; + + bool res = ((ioctl(m_shellProcess.m_handleMaster, TIOCSWINSZ, &winp) != -1) + && (ioctl(m_shellProcess.m_handleSlave, TIOCSWINSZ, &winp) != -1)); + + if (res) { + m_size = QPair(cols, rows); + } + + return res; +} + +bool UnixPtyProcess::kill() +{ + m_shellProcess.m_handleSlaveName = QString(); + if (m_shellProcess.m_handleSlave >= 0) { + ::close(m_shellProcess.m_handleSlave); + m_shellProcess.m_handleSlave = -1; + } + if (m_shellProcess.m_handleMaster >= 0) { + ::close(m_shellProcess.m_handleMaster); + m_shellProcess.m_handleMaster = -1; + } + + if (m_shellProcess.state() == QProcess::Running) { + m_readMasterNotify->disconnect(); + m_readMasterNotify->deleteLater(); + + m_shellProcess.terminate(); + m_shellProcess.waitForFinished(1000); + + if (m_shellProcess.state() == QProcess::Running) { + QProcess::startDetached(QString("kill -9 %1").arg(pid())); + m_shellProcess.kill(); + m_shellProcess.waitForFinished(1000); + } + + return (m_shellProcess.state() == QProcess::NotRunning); + } + return false; +} + +IPtyProcess::PtyType UnixPtyProcess::type() +{ + return IPtyProcess::UnixPty; +} + +QString UnixPtyProcess::dumpDebugInfo() +{ +#ifdef PTYQT_DEBUG + return QString("PID: %1, In: %2, Out: %3, Type: %4, Cols: %5, Rows: %6, IsRunning: %7, Shell: " + "%8, SlaveName: %9") + .arg(m_pid) + .arg(m_shellProcess.m_handleMaster) + .arg(m_shellProcess.m_handleSlave) + .arg(type()) + .arg(m_size.first) + .arg(m_size.second) + .arg(m_shellProcess.state() == QProcess::Running) + .arg(m_shellPath) + .arg(m_shellProcess.m_handleSlaveName); +#else + return QString("Nothing..."); +#endif +} + +QIODevice *UnixPtyProcess::notifier() +{ + return &m_shellProcess; +} + +QByteArray UnixPtyProcess::readAll() +{ + QByteArray tmpBuffer = m_shellReadBuffer; + m_shellReadBuffer.clear(); + return tmpBuffer; +} + +qint64 UnixPtyProcess::write(const QByteArray &byteArray) +{ + int result = ::write(m_shellProcess.m_handleMaster, byteArray.constData(), byteArray.size()); + Q_UNUSED(result) + + return byteArray.size(); +} + +bool UnixPtyProcess::isAvailable() +{ + //todo check something more if required + return true; +} + +void UnixPtyProcess::moveToThread(QThread *targetThread) +{ + m_shellProcess.moveToThread(targetThread); +} + +void ShellProcess::configChildProcess() +{ + dup2(m_handleSlave, STDIN_FILENO); + dup2(m_handleSlave, STDOUT_FILENO); + dup2(m_handleSlave, STDERR_FILENO); + + pid_t sid = setsid(); + ioctl(m_handleSlave, TIOCSCTTY, 0); + tcsetpgrp(m_handleSlave, sid); + +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_FREEBSD) + // on Android imposible to put record to the 'utmp' file + struct utmpx utmpxInfo; + memset(&utmpxInfo, 0, sizeof(utmpxInfo)); + + strncpy(utmpxInfo.ut_user, qgetenv("USER"), sizeof(utmpxInfo.ut_user)); + + QString device(m_handleSlaveName); + if (device.startsWith("/dev/")) + device = device.mid(5); + + const char *d = device.toLatin1().constData(); + + strncpy(utmpxInfo.ut_line, d, sizeof(utmpxInfo.ut_line)); + + strncpy(utmpxInfo.ut_id, d + strlen(d) - sizeof(utmpxInfo.ut_id), sizeof(utmpxInfo.ut_id)); + + struct timeval tv; + gettimeofday(&tv, 0); + utmpxInfo.ut_tv.tv_sec = tv.tv_sec; + utmpxInfo.ut_tv.tv_usec = tv.tv_usec; + + utmpxInfo.ut_type = USER_PROCESS; + utmpxInfo.ut_pid = getpid(); + + utmpxname(_PATH_UTMPX); + setutxent(); + pututxline(&utmpxInfo); + endutxent(); + +#if !defined(Q_OS_UNIX) + updwtmpx(_PATH_UTMPX, &loginInfo); +#endif + +#endif +} diff --git a/src/libs/3rdparty/libptyqt/unixptyprocess.h b/src/libs/3rdparty/libptyqt/unixptyprocess.h new file mode 100644 index 0000000000..e4df0d2f74 --- /dev/null +++ b/src/libs/3rdparty/libptyqt/unixptyprocess.h @@ -0,0 +1,66 @@ +#ifndef UNIXPTYPROCESS_H +#define UNIXPTYPROCESS_H + +#include "iptyprocess.h" +#include +#include + +// support for build with MUSL on Alpine Linux +#ifndef _PATH_UTMPX +#include +#define _PATH_UTMPX "/var/log/utmp" +#endif + +class ShellProcess : public QProcess +{ + friend class UnixPtyProcess; + Q_OBJECT +public: + ShellProcess() + : QProcess() + , m_handleMaster(-1) + , m_handleSlave(-1) + { + setProcessChannelMode(QProcess::SeparateChannels); + setChildProcessModifier([this]() { configChildProcess(); }); + } + + void emitReadyRead() { emit readyRead(); } + +protected: + void configChildProcess(); + +private: + int m_handleMaster, m_handleSlave; + QString m_handleSlaveName; +}; + +class UnixPtyProcess : public IPtyProcess +{ +public: + UnixPtyProcess(); + virtual ~UnixPtyProcess(); + + virtual bool startProcess(const QString &executable, + const QStringList &arguments, + const QString &workingDir, + QStringList environment, + qint16 cols, + qint16 rows); + virtual bool resize(qint16 cols, qint16 rows); + virtual bool kill(); + virtual PtyType type(); + virtual QString dumpDebugInfo(); + virtual QIODevice *notifier(); + virtual QByteArray readAll(); + virtual qint64 write(const QByteArray &byteArray); + virtual bool isAvailable(); + void moveToThread(QThread *targetThread); + +private: + ShellProcess m_shellProcess; + QSocketNotifier *m_readMasterNotify; + QByteArray m_shellReadBuffer; +}; + +#endif // UNIXPTYPROCESS_H diff --git a/src/libs/3rdparty/libptyqt/winptyprocess.cpp b/src/libs/3rdparty/libptyqt/winptyprocess.cpp new file mode 100644 index 0000000000..90ac139b9c --- /dev/null +++ b/src/libs/3rdparty/libptyqt/winptyprocess.cpp @@ -0,0 +1,275 @@ +#include "winptyprocess.h" +#include +#include +#include +#include + +#define DEBUG_VAR_LEGACY "WINPTYDBG" +#define DEBUG_VAR_ACTUAL "WINPTY_DEBUG" +#define SHOW_CONSOLE_VAR "WINPTY_SHOW_CONSOLE" +#define WINPTY_AGENT_NAME "winpty-agent.exe" +#define WINPTY_DLL_NAME "winpty.dll" + +QString castErrorToString(winpty_error_ptr_t error_ptr) +{ + return QString::fromStdWString(winpty_error_msg(error_ptr)); +} + +WinPtyProcess::WinPtyProcess() + : IPtyProcess() + , m_ptyHandler(nullptr) + , m_innerHandle(nullptr) + , m_inSocket(nullptr) + , m_outSocket(nullptr) +{ +#ifdef PTYQT_DEBUG + m_trace = true; +#endif +} + +WinPtyProcess::~WinPtyProcess() +{ + kill(); +} + +bool WinPtyProcess::startProcess(const QString &executable, + const QStringList &arguments, + const QString &workingDir, + QStringList environment, + qint16 cols, + qint16 rows) +{ + if (!isAvailable()) + { + m_lastError = QString("WinPty Error: winpty-agent.exe or winpty.dll not found!"); + return false; + } + + //already running + if (m_ptyHandler != nullptr) + return false; + + QFileInfo fi(executable); + if (fi.isRelative() || !QFile::exists(executable)) + { + //todo add auto-find executable in PATH env var + m_lastError = QString("WinPty Error: shell file path must be absolute"); + return false; + } + + m_shellPath = executable; + m_size = QPair(cols, rows); + +#ifdef PTYQT_DEBUG + if (m_trace) + { + environment.append(QString("%1=1").arg(DEBUG_VAR_LEGACY)); + environment.append(QString("%1=trace").arg(DEBUG_VAR_ACTUAL)); + environment.append(QString("%1=1").arg(SHOW_CONSOLE_VAR)); + SetEnvironmentVariableA(DEBUG_VAR_LEGACY, "1"); + SetEnvironmentVariableA(DEBUG_VAR_ACTUAL, "trace"); + SetEnvironmentVariableA(SHOW_CONSOLE_VAR, "1"); + } +#endif + + //env + std::wstringstream envBlock; + foreach (QString line, environment) + { + envBlock << line.toStdWString() << L'\0'; + } + std::wstring env = envBlock.str(); + + //create start config + winpty_error_ptr_t errorPtr = nullptr; + winpty_config_t* startConfig = winpty_config_new(0, &errorPtr); + if (startConfig == nullptr) + { + m_lastError = QString("WinPty Error: create start config -> %1").arg(castErrorToString(errorPtr)); + return false; + } + winpty_error_free(errorPtr); + + //set params + winpty_config_set_initial_size(startConfig, cols, rows); + winpty_config_set_mouse_mode(startConfig, WINPTY_MOUSE_MODE_AUTO); + //winpty_config_set_agent_timeout(); + + //start agent + m_ptyHandler = winpty_open(startConfig, &errorPtr); + winpty_config_free(startConfig); //start config is local var, free it after use + + if (m_ptyHandler == nullptr) + { + m_lastError = QString("WinPty Error: start agent -> %1").arg(castErrorToString(errorPtr)); + return false; + } + winpty_error_free(errorPtr); + + //create spawn config + winpty_spawn_config_t* spawnConfig = winpty_spawn_config_new(WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, m_shellPath.toStdWString().c_str(), + //commandLine.toStdWString().c_str(), cwd.toStdWString().c_str(), + arguments.join(" ").toStdWString().c_str(), workingDir.toStdWString().c_str(), + env.c_str(), + &errorPtr); + + if (spawnConfig == nullptr) + { + m_lastError = QString("WinPty Error: create spawn config -> %1").arg(castErrorToString(errorPtr)); + return false; + } + winpty_error_free(errorPtr); + + //spawn the new process + BOOL spawnSuccess = winpty_spawn(m_ptyHandler, spawnConfig, &m_innerHandle, nullptr, nullptr, &errorPtr); + winpty_spawn_config_free(spawnConfig); //spawn config is local var, free it after use + if (!spawnSuccess) + { + m_lastError = QString("WinPty Error: start terminal process -> %1").arg(castErrorToString(errorPtr)); + return false; + } + winpty_error_free(errorPtr); + + m_pid = (int)GetProcessId(m_innerHandle); + + // Notify when the shell process has been terminated + RegisterWaitForSingleObject( + &m_shellCloseWaitHandle, + m_innerHandle, + [](PVOID data, BOOLEAN) { + auto self = static_cast(data); + // Do not respawn if the object is about to be destructed + DWORD exitCode = 0; + GetExitCodeProcess(self->m_innerHandle, &exitCode); + self->m_exitCode = exitCode; + if (!self->m_aboutToDestruct) + emit self->notifier()->aboutToClose(); + }, + this, + INFINITE, + WT_EXECUTEONLYONCE); + + //get pipe names + LPCWSTR conInPipeName = winpty_conin_name(m_ptyHandler); + m_conInName = QString::fromStdWString(std::wstring(conInPipeName)); + m_inSocket = new QLocalSocket(); + m_inSocket->connectToServer(m_conInName, QIODevice::WriteOnly); + m_inSocket->waitForConnected(); + + LPCWSTR conOutPipeName = winpty_conout_name(m_ptyHandler); + m_conOutName = QString::fromStdWString(std::wstring(conOutPipeName)); + m_outSocket = new QLocalSocket(); + m_outSocket->connectToServer(m_conOutName, QIODevice::ReadOnly); + m_outSocket->waitForConnected(); + + if (m_inSocket->state() != QLocalSocket::ConnectedState && m_outSocket->state() != QLocalSocket::ConnectedState) + { + m_lastError = QString("WinPty Error: Unable to connect local sockets -> %1 / %2").arg(m_inSocket->errorString()).arg(m_outSocket->errorString()); + m_inSocket->deleteLater(); + m_outSocket->deleteLater(); + m_inSocket = nullptr; + m_outSocket = nullptr; + return false; + } + + return true; +} + +bool WinPtyProcess::resize(qint16 cols, qint16 rows) +{ + if (m_ptyHandler == nullptr) + { + return false; + } + + bool res = winpty_set_size(m_ptyHandler, cols, rows, nullptr); + + if (res) + { + m_size = QPair(cols, rows); + } + + return res; +} + +bool WinPtyProcess::kill() +{ + bool exitCode = false; + if (m_innerHandle != nullptr && m_ptyHandler != nullptr) { + m_aboutToDestruct = true; + //disconnect all signals (readyRead, ...) + m_inSocket->disconnect(); + m_outSocket->disconnect(); + + //disconnect for server + m_inSocket->disconnectFromServer(); + m_outSocket->disconnectFromServer(); + + m_inSocket->deleteLater(); + m_outSocket->deleteLater(); + + m_inSocket = nullptr; + m_outSocket = nullptr; + + winpty_free(m_ptyHandler); + exitCode = CloseHandle(m_innerHandle); + + UnregisterWait(m_shellCloseWaitHandle); + + m_ptyHandler = nullptr; + m_innerHandle = nullptr; + m_conInName = QString(); + m_conOutName = QString(); + m_pid = 0; + } + return exitCode; +} + +IPtyProcess::PtyType WinPtyProcess::type() +{ + return PtyType::WinPty; +} + +QString WinPtyProcess::dumpDebugInfo() +{ +#ifdef PTYQT_DEBUG + return QString("PID: %1, ConIn: %2, ConOut: %3, Type: %4, Cols: %5, Rows: %6, IsRunning: %7, Shell: %8") + .arg(m_pid).arg(m_conInName).arg(m_conOutName).arg(type()) + .arg(m_size.first).arg(m_size.second).arg(m_ptyHandler != nullptr) + .arg(m_shellPath); +#else + return QString("Nothing..."); +#endif +} + +QIODevice *WinPtyProcess::notifier() +{ + return m_outSocket; +} + +QByteArray WinPtyProcess::readAll() +{ + return m_outSocket->readAll(); +} + +qint64 WinPtyProcess::write(const QByteArray &byteArray) +{ + return m_inSocket->write(byteArray); +} + +bool WinPtyProcess::isAvailable() +{ +#ifdef PTYQT_BUILD_STATIC + return QFile::exists(QCoreApplication::applicationDirPath() + "/" + WINPTY_AGENT_NAME); +#elif PTYQT_BUILD_DYNAMIC + return QFile::exists(QCoreApplication::applicationDirPath() + "/" + WINPTY_AGENT_NAME) + && QFile::exists(QCoreApplication::applicationDirPath() + "/" + WINPTY_DLL_NAME); +#endif + return true; +} + +void WinPtyProcess::moveToThread(QThread *targetThread) +{ + m_inSocket->moveToThread(targetThread); + m_outSocket->moveToThread(targetThread); +} diff --git a/src/libs/3rdparty/libptyqt/winptyprocess.h b/src/libs/3rdparty/libptyqt/winptyprocess.h new file mode 100644 index 0000000000..547bcf7c97 --- /dev/null +++ b/src/libs/3rdparty/libptyqt/winptyprocess.h @@ -0,0 +1,42 @@ +#ifndef WINPTYPROCESS_H +#define WINPTYPROCESS_H + +#include "iptyprocess.h" +#include "winpty.h" + +#include + +class WinPtyProcess : public IPtyProcess +{ +public: + WinPtyProcess(); + ~WinPtyProcess(); + + bool startProcess(const QString &executable, + const QStringList &arguments, + const QString &workingDir, + QStringList environment, + qint16 cols, + qint16 rows); + bool resize(qint16 cols, qint16 rows); + bool kill(); + PtyType type(); + QString dumpDebugInfo(); + QIODevice *notifier(); + QByteArray readAll(); + qint64 write(const QByteArray &byteArray); + bool isAvailable(); + void moveToThread(QThread *targetThread); + +private: + winpty_t *m_ptyHandler; + HANDLE m_innerHandle; + QString m_conInName; + QString m_conOutName; + QLocalSocket *m_inSocket; + QLocalSocket *m_outSocket; + bool m_aboutToDestruct{false}; + HANDLE m_shellCloseWaitHandle{INVALID_HANDLE_VALUE}; +}; + +#endif // WINPTYPROCESS_H diff --git a/src/libs/3rdparty/libvterm/CMakeLists.txt b/src/libs/3rdparty/libvterm/CMakeLists.txt new file mode 100644 index 0000000000..4a0ede6f0f --- /dev/null +++ b/src/libs/3rdparty/libvterm/CMakeLists.txt @@ -0,0 +1,25 @@ +add_library(libvterm STATIC + src/encoding.c + src/fullwidth.inc + src/keyboard.c + src/mouse.c + src/parser.c + src/pen.c + src/rect.h + src/screen.c + src/state.c + src/unicode.c + src/utf8.h + src/vterm.c + src/vterm_internal.h +) + +target_include_directories(libvterm PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +set_target_properties(libvterm + PROPERTIES + QT_COMPILE_OPTIONS_DISABLE_WARNINGS ON + POSITION_INDEPENDENT_CODE ON  +) diff --git a/src/libs/3rdparty/libvterm/CONTRIBUTING b/src/libs/3rdparty/libvterm/CONTRIBUTING new file mode 100644 index 0000000000..e9a8f0c331 --- /dev/null +++ b/src/libs/3rdparty/libvterm/CONTRIBUTING @@ -0,0 +1,22 @@ +How to Contribute +----------------- + +The main resources for this library are: + + Launchpad + https://launchpad.net/libvterm + + IRC: + ##tty or #tickit on irc.libera.chat + + Email: + Paul "LeoNerd" Evans + + +Bug reports and feature requests can be sent to any of the above resources. + +New features, bug patches, etc.. should in the first instance be discussed via +any of the resources listed above, before starting work on the actual code. +There may be future plans or development already in-progress that could be +affected so it is better to discuss the ideas first before starting work +actually writing any code. diff --git a/src/libs/3rdparty/libvterm/LICENSE b/src/libs/3rdparty/libvterm/LICENSE new file mode 100644 index 0000000000..0d051634b2 --- /dev/null +++ b/src/libs/3rdparty/libvterm/LICENSE @@ -0,0 +1,23 @@ + + +The MIT License + +Copyright (c) 2008 Paul Evans + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/libs/3rdparty/libvterm/include/vterm.h b/src/libs/3rdparty/libvterm/include/vterm.h new file mode 100644 index 0000000000..c0f008776b --- /dev/null +++ b/src/libs/3rdparty/libvterm/include/vterm.h @@ -0,0 +1,635 @@ +#ifndef __VTERM_H__ +#define __VTERM_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include "vterm_keycodes.h" + +#define VTERM_VERSION_MAJOR 0 +#define VTERM_VERSION_MINOR 3 + +#define VTERM_CHECK_VERSION \ + vterm_check_version(VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR) + +/* Any cell can contain at most one basic printing character and 5 combining + * characters. This number could be changed but will be ABI-incompatible if + * you do */ +#define VTERM_MAX_CHARS_PER_CELL 6 + +typedef struct VTerm VTerm; +typedef struct VTermState VTermState; +typedef struct VTermScreen VTermScreen; + +typedef struct { + int row; + int col; +} VTermPos; + +/* some small utility functions; we can just keep these static here */ + +/* order points by on-screen flow order */ +static inline int vterm_pos_cmp(VTermPos a, VTermPos b) +{ + return (a.row == b.row) ? a.col - b.col : a.row - b.row; +} + +typedef struct { + int start_row; + int end_row; + int start_col; + int end_col; +} VTermRect; + +/* true if the rect contains the point */ +static inline int vterm_rect_contains(VTermRect r, VTermPos p) +{ + return p.row >= r.start_row && p.row < r.end_row && + p.col >= r.start_col && p.col < r.end_col; +} + +/* move a rect */ +static inline void vterm_rect_move(VTermRect *rect, int row_delta, int col_delta) +{ + rect->start_row += row_delta; rect->end_row += row_delta; + rect->start_col += col_delta; rect->end_col += col_delta; +} + +/** + * Bit-field describing the content of the tagged union `VTermColor`. + */ +typedef enum { + /** + * If the lower bit of `type` is not set, the colour is 24-bit RGB. + */ + VTERM_COLOR_RGB = 0x00, + + /** + * The colour is an index into a palette of 256 colours. + */ + VTERM_COLOR_INDEXED = 0x01, + + /** + * Mask that can be used to extract the RGB/Indexed bit. + */ + VTERM_COLOR_TYPE_MASK = 0x01, + + /** + * If set, indicates that this colour should be the default foreground + * color, i.e. there was no SGR request for another colour. When + * rendering this colour it is possible to ignore "idx" and just use a + * colour that is not in the palette. + */ + VTERM_COLOR_DEFAULT_FG = 0x02, + + /** + * If set, indicates that this colour should be the default background + * color, i.e. there was no SGR request for another colour. A common + * option when rendering this colour is to not render a background at + * all, for example by rendering the window transparently at this spot. + */ + VTERM_COLOR_DEFAULT_BG = 0x04, + + /** + * Mask that can be used to extract the default foreground/background bit. + */ + VTERM_COLOR_DEFAULT_MASK = 0x06 +} VTermColorType; + +/** + * Returns true if the VTERM_COLOR_RGB `type` flag is set, indicating that the + * given VTermColor instance is an indexed colour. + */ +#define VTERM_COLOR_IS_INDEXED(col) \ + (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_INDEXED) + +/** + * Returns true if the VTERM_COLOR_INDEXED `type` flag is set, indicating that + * the given VTermColor instance is an rgb colour. + */ +#define VTERM_COLOR_IS_RGB(col) \ + (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_RGB) + +/** + * Returns true if the VTERM_COLOR_DEFAULT_FG `type` flag is set, indicating + * that the given VTermColor instance corresponds to the default foreground + * color. + */ +#define VTERM_COLOR_IS_DEFAULT_FG(col) \ + (!!((col)->type & VTERM_COLOR_DEFAULT_FG)) + +/** + * Returns true if the VTERM_COLOR_DEFAULT_BG `type` flag is set, indicating + * that the given VTermColor instance corresponds to the default background + * color. + */ +#define VTERM_COLOR_IS_DEFAULT_BG(col) \ + (!!((col)->type & VTERM_COLOR_DEFAULT_BG)) + +/** + * Tagged union storing either an RGB color or an index into a colour palette. + * In order to convert indexed colours to RGB, you may use the + * vterm_state_convert_color_to_rgb() or vterm_screen_convert_color_to_rgb() + * functions which lookup the RGB colour from the palette maintained by a + * VTermState or VTermScreen instance. + */ +typedef union { + /** + * Tag indicating which union member is actually valid. This variable + * coincides with the `type` member of the `rgb` and the `indexed` struct + * in memory. Please use the `VTERM_COLOR_IS_*` test macros to check whether + * a particular type flag is set. + */ + uint8_t type; + + /** + * Valid if `VTERM_COLOR_IS_RGB(type)` is true. Holds the RGB colour values. + */ + struct { + /** + * Same as the top-level `type` member stored in VTermColor. + */ + uint8_t type; + + /** + * The actual 8-bit red, green, blue colour values. + */ + uint8_t red, green, blue; + } rgb; + + /** + * If `VTERM_COLOR_IS_INDEXED(type)` is true, this member holds the index into + * the colour palette. + */ + struct { + /** + * Same as the top-level `type` member stored in VTermColor. + */ + uint8_t type; + + /** + * Index into the colour map. + */ + uint8_t idx; + } indexed; +} VTermColor; + +/** + * Constructs a new VTermColor instance representing the given RGB values. + */ +static inline void vterm_color_rgb(VTermColor *col, uint8_t red, uint8_t green, + uint8_t blue) +{ + col->type = VTERM_COLOR_RGB; + col->rgb.red = red; + col->rgb.green = green; + col->rgb.blue = blue; +} + +/** + * Construct a new VTermColor instance representing an indexed color with the + * given index. + */ +static inline void vterm_color_indexed(VTermColor *col, uint8_t idx) +{ + col->type = VTERM_COLOR_INDEXED; + col->indexed.idx = idx; +} + +/** + * Compares two colours. Returns true if the colors are equal, false otherwise. + */ +int vterm_color_is_equal(const VTermColor *a, const VTermColor *b); + +typedef enum { + /* VTERM_VALUETYPE_NONE = 0 */ + VTERM_VALUETYPE_BOOL = 1, + VTERM_VALUETYPE_INT, + VTERM_VALUETYPE_STRING, + VTERM_VALUETYPE_COLOR, + + VTERM_N_VALUETYPES +} VTermValueType; + +typedef struct { + const char *str; + size_t len : 30; + bool initial : 1; + bool final : 1; +} VTermStringFragment; + +typedef union { + int boolean; + int number; + VTermStringFragment string; + VTermColor color; +} VTermValue; + +typedef enum { + /* VTERM_ATTR_NONE = 0 */ + VTERM_ATTR_BOLD = 1, // bool: 1, 22 + VTERM_ATTR_UNDERLINE, // number: 4, 21, 24 + VTERM_ATTR_ITALIC, // bool: 3, 23 + VTERM_ATTR_BLINK, // bool: 5, 25 + VTERM_ATTR_REVERSE, // bool: 7, 27 + VTERM_ATTR_CONCEAL, // bool: 8, 28 + VTERM_ATTR_STRIKE, // bool: 9, 29 + VTERM_ATTR_FONT, // number: 10-19 + VTERM_ATTR_FOREGROUND, // color: 30-39 90-97 + VTERM_ATTR_BACKGROUND, // color: 40-49 100-107 + VTERM_ATTR_SMALL, // bool: 73, 74, 75 + VTERM_ATTR_BASELINE, // number: 73, 74, 75 + + VTERM_N_ATTRS +} VTermAttr; + +typedef enum { + /* VTERM_PROP_NONE = 0 */ + VTERM_PROP_CURSORVISIBLE = 1, // bool + VTERM_PROP_CURSORBLINK, // bool + VTERM_PROP_ALTSCREEN, // bool + VTERM_PROP_TITLE, // string + VTERM_PROP_ICONNAME, // string + VTERM_PROP_REVERSE, // bool + VTERM_PROP_CURSORSHAPE, // number + VTERM_PROP_MOUSE, // number + + VTERM_N_PROPS +} VTermProp; + +enum { + VTERM_PROP_CURSORSHAPE_BLOCK = 1, + VTERM_PROP_CURSORSHAPE_UNDERLINE, + VTERM_PROP_CURSORSHAPE_BAR_LEFT, + + VTERM_N_PROP_CURSORSHAPES +}; + +enum { + VTERM_PROP_MOUSE_NONE = 0, + VTERM_PROP_MOUSE_CLICK, + VTERM_PROP_MOUSE_DRAG, + VTERM_PROP_MOUSE_MOVE, + + VTERM_N_PROP_MOUSES +}; + +typedef enum { + VTERM_SELECTION_CLIPBOARD = (1<<0), + VTERM_SELECTION_PRIMARY = (1<<1), + VTERM_SELECTION_SECONDARY = (1<<2), + VTERM_SELECTION_SELECT = (1<<3), + VTERM_SELECTION_CUT0 = (1<<4), /* also CUT1 .. CUT7 by bitshifting */ +} VTermSelectionMask; + +typedef struct { + const uint32_t *chars; + int width; + unsigned int protected_cell:1; /* DECSCA-protected against DECSEL/DECSED */ + unsigned int dwl:1; /* DECDWL or DECDHL double-width line */ + unsigned int dhl:2; /* DECDHL double-height line (1=top 2=bottom) */ +} VTermGlyphInfo; + +typedef struct { + unsigned int doublewidth:1; /* DECDWL or DECDHL line */ + unsigned int doubleheight:2; /* DECDHL line (1=top 2=bottom) */ + unsigned int continuation:1; /* Line is a flow continuation of the previous */ +} VTermLineInfo; + +/* Copies of VTermState fields that the 'resize' callback might have reason to + * edit. 'resize' callback gets total control of these fields and may + * free-and-reallocate them if required. They will be copied back from the + * struct after the callback has returned. + */ +typedef struct { + VTermPos pos; /* current cursor position */ + VTermLineInfo *lineinfos[2]; /* [1] may be NULL */ +} VTermStateFields; + +typedef struct { + /* libvterm relies on this memory to be zeroed out before it is returned + * by the allocator. */ + void *(*malloc)(size_t size, void *allocdata); + void (*free)(void *ptr, void *allocdata); +} VTermAllocatorFunctions; + +void vterm_check_version(int major, int minor); + +struct VTermBuilder { + int ver; /* currently unused but reserved for some sort of ABI version flag */ + + int rows, cols; + + const VTermAllocatorFunctions *allocator; + void *allocdata; + + /* Override default sizes for various structures */ + size_t outbuffer_len; /* default: 4096 */ + size_t tmpbuffer_len; /* default: 4096 */ +}; + +VTerm *vterm_build(const struct VTermBuilder *builder); + +/* A convenient shortcut for default cases */ +VTerm *vterm_new(int rows, int cols); +/* This shortcuts are generally discouraged in favour of just using vterm_build() */ +VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata); + +void vterm_free(VTerm* vt); + +void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp); +void vterm_set_size(VTerm *vt, int rows, int cols); + +int vterm_get_utf8(const VTerm *vt); +void vterm_set_utf8(VTerm *vt, int is_utf8); + +size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len); + +/* Setting output callback will override the buffer logic */ +typedef void VTermOutputCallback(const char *s, size_t len, void *user); +void vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user); + +/* These buffer functions only work if output callback is NOT set + * These are deprecated and will be removed in a later version */ +size_t vterm_output_get_buffer_size(const VTerm *vt); +size_t vterm_output_get_buffer_current(const VTerm *vt); +size_t vterm_output_get_buffer_remaining(const VTerm *vt); + +/* This too */ +size_t vterm_output_read(VTerm *vt, char *buffer, size_t len); + +void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod); +void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod); + +void vterm_keyboard_start_paste(VTerm *vt); +void vterm_keyboard_end_paste(VTerm *vt); + +void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod); +void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod); + +// ------------ +// Parser layer +// ------------ + +/* Flag to indicate non-final subparameters in a single CSI parameter. + * Consider + * CSI 1;2:3:4;5a + * 1 4 and 5 are final. + * 2 and 3 are non-final and will have this bit set + * + * Don't confuse this with the final byte of the CSI escape; 'a' in this case. + */ +#define CSI_ARG_FLAG_MORE (1U<<31) +#define CSI_ARG_MASK (~(1U<<31)) + +#define CSI_ARG_HAS_MORE(a) ((a) & CSI_ARG_FLAG_MORE) +#define CSI_ARG(a) ((a) & CSI_ARG_MASK) + +/* Can't use -1 to indicate a missing argument; use this instead */ +#define CSI_ARG_MISSING ((1UL<<31)-1) + +#define CSI_ARG_IS_MISSING(a) (CSI_ARG(a) == CSI_ARG_MISSING) +#define CSI_ARG_OR(a,def) (CSI_ARG(a) == CSI_ARG_MISSING ? (def) : CSI_ARG(a)) +#define CSI_ARG_COUNT(a) (CSI_ARG(a) == CSI_ARG_MISSING || CSI_ARG(a) == 0 ? 1 : CSI_ARG(a)) + +typedef struct { + int (*text)(const char *bytes, size_t len, void *user); + int (*control)(unsigned char control, void *user); + int (*escape)(const char *bytes, size_t len, void *user); + int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user); + int (*osc)(int command, VTermStringFragment frag, void *user); + int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user); + int (*apc)(VTermStringFragment frag, void *user); + int (*pm)(VTermStringFragment frag, void *user); + int (*sos)(VTermStringFragment frag, void *user); + int (*resize)(int rows, int cols, void *user); +} VTermParserCallbacks; + +void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user); +void *vterm_parser_get_cbdata(VTerm *vt); + +/* Normally NUL, CAN, SUB and DEL are ignored. Setting this true causes them + * to be emitted by the 'control' callback + */ +void vterm_parser_set_emit_nul(VTerm *vt, bool emit); + +// ----------- +// State layer +// ----------- + +typedef struct { + int (*putglyph)(VTermGlyphInfo *info, VTermPos pos, void *user); + int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user); + int (*scrollrect)(VTermRect rect, int downward, int rightward, void *user); + int (*moverect)(VTermRect dest, VTermRect src, void *user); + int (*erase)(VTermRect rect, int selective, void *user); + int (*initpen)(void *user); + int (*setpenattr)(VTermAttr attr, VTermValue *val, void *user); + int (*settermprop)(VTermProp prop, VTermValue *val, void *user); + int (*bell)(void *user); + int (*resize)(int rows, int cols, VTermStateFields *fields, void *user); + int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user); + int (*sb_clear)(void *user); +} VTermStateCallbacks; + +typedef struct { + int (*control)(unsigned char control, void *user); + int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user); + int (*osc)(int command, VTermStringFragment frag, void *user); + int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user); + int (*apc)(VTermStringFragment frag, void *user); + int (*pm)(VTermStringFragment frag, void *user); + int (*sos)(VTermStringFragment frag, void *user); +} VTermStateFallbacks; + +typedef struct { + int (*set)(VTermSelectionMask mask, VTermStringFragment frag, void *user); + int (*query)(VTermSelectionMask mask, void *user); +} VTermSelectionCallbacks; + +VTermState *vterm_obtain_state(VTerm *vt); + +void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user); +void *vterm_state_get_cbdata(VTermState *state); + +void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermStateFallbacks *fallbacks, void *user); +void *vterm_state_get_unrecognised_fbdata(VTermState *state); + +void vterm_state_reset(VTermState *state, int hard); +void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos); +void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg); +void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col); +void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg); +void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col); +void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright); +int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val); +int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val); +void vterm_state_focus_in(VTermState *state); +void vterm_state_focus_out(VTermState *state); +const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row); + +/** + * Makes sure that the given color `col` is indeed an RGB colour. After this + * function returns, VTERM_COLOR_IS_RGB(col) will return true, while all other + * flags stored in `col->type` will have been reset. + * + * @param state is the VTermState instance from which the colour palette should + * be extracted. + * @param col is a pointer at the VTermColor instance that should be converted + * to an RGB colour. + */ +void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col); + +void vterm_state_set_selection_callbacks(VTermState *state, const VTermSelectionCallbacks *callbacks, void *user, + char *buffer, size_t buflen); + +void vterm_state_send_selection(VTermState *state, VTermSelectionMask mask, VTermStringFragment frag); + +// ------------ +// Screen layer +// ------------ + +typedef struct { + unsigned int bold : 1; + unsigned int underline : 2; + unsigned int italic : 1; + unsigned int blink : 1; + unsigned int reverse : 1; + unsigned int conceal : 1; + unsigned int strike : 1; + unsigned int font : 4; /* 0 to 9 */ + unsigned int dwl : 1; /* On a DECDWL or DECDHL line */ + unsigned int dhl : 2; /* On a DECDHL line (1=top 2=bottom) */ + unsigned int small : 1; + unsigned int baseline : 2; +} VTermScreenCellAttrs; + +enum { + VTERM_UNDERLINE_OFF, + VTERM_UNDERLINE_SINGLE, + VTERM_UNDERLINE_DOUBLE, + VTERM_UNDERLINE_CURLY, +}; + +enum { + VTERM_BASELINE_NORMAL, + VTERM_BASELINE_RAISE, + VTERM_BASELINE_LOWER, +}; + +typedef struct { + uint32_t chars[VTERM_MAX_CHARS_PER_CELL]; + char width; + VTermScreenCellAttrs attrs; + VTermColor fg, bg; +} VTermScreenCell; + +typedef struct { + int (*damage)(VTermRect rect, void *user); + int (*moverect)(VTermRect dest, VTermRect src, void *user); + int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user); + int (*settermprop)(VTermProp prop, VTermValue *val, void *user); + int (*bell)(void *user); + int (*resize)(int rows, int cols, void *user); + int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user); + int (*sb_popline)(int cols, VTermScreenCell *cells, void *user); + int (*sb_clear)(void* user); +} VTermScreenCallbacks; + +VTermScreen *vterm_obtain_screen(VTerm *vt); + +void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user); +void *vterm_screen_get_cbdata(VTermScreen *screen); + +void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermStateFallbacks *fallbacks, void *user); +void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen); + +void vterm_screen_enable_reflow(VTermScreen *screen, bool reflow); + +// Back-compat alias for the brief time it was in 0.3-RC1 +#define vterm_screen_set_reflow vterm_screen_enable_reflow + +void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen); + +typedef enum { + VTERM_DAMAGE_CELL, /* every cell */ + VTERM_DAMAGE_ROW, /* entire rows */ + VTERM_DAMAGE_SCREEN, /* entire screen */ + VTERM_DAMAGE_SCROLL, /* entire screen + scrollrect */ + + VTERM_N_DAMAGES +} VTermDamageSize; + +void vterm_screen_flush_damage(VTermScreen *screen); +void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size); + +void vterm_screen_reset(VTermScreen *screen, int hard); + +/* Neither of these functions NUL-terminate the buffer */ +size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect); +size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect); + +typedef enum { + VTERM_ATTR_BOLD_MASK = 1 << 0, + VTERM_ATTR_UNDERLINE_MASK = 1 << 1, + VTERM_ATTR_ITALIC_MASK = 1 << 2, + VTERM_ATTR_BLINK_MASK = 1 << 3, + VTERM_ATTR_REVERSE_MASK = 1 << 4, + VTERM_ATTR_STRIKE_MASK = 1 << 5, + VTERM_ATTR_FONT_MASK = 1 << 6, + VTERM_ATTR_FOREGROUND_MASK = 1 << 7, + VTERM_ATTR_BACKGROUND_MASK = 1 << 8, + VTERM_ATTR_CONCEAL_MASK = 1 << 9, + VTERM_ATTR_SMALL_MASK = 1 << 10, + VTERM_ATTR_BASELINE_MASK = 1 << 11, + + VTERM_ALL_ATTRS_MASK = (1 << 12) - 1 +} VTermAttrMask; + +int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs); + +int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell); + +int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos); + +/** + * Same as vterm_state_convert_color_to_rgb(), but takes a `screen` instead of a `state` + * instance. + */ +void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col); + +/** + * Similar to vterm_state_set_default_colors(), but also resets colours in the + * screen buffer(s) + */ +void vterm_screen_set_default_colors(VTermScreen *screen, const VTermColor *default_fg, const VTermColor *default_bg); + +// --------- +// Utilities +// --------- + +VTermValueType vterm_get_attr_type(VTermAttr attr); +VTermValueType vterm_get_prop_type(VTermProp prop); + +void vterm_scroll_rect(VTermRect rect, + int downward, + int rightward, + int (*moverect)(VTermRect src, VTermRect dest, void *user), + int (*eraserect)(VTermRect rect, int selective, void *user), + void *user); + +void vterm_copy_cells(VTermRect dest, + VTermRect src, + void (*copycell)(VTermPos dest, VTermPos src, void *user), + void *user); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/libs/3rdparty/libvterm/include/vterm_keycodes.h b/src/libs/3rdparty/libvterm/include/vterm_keycodes.h new file mode 100644 index 0000000000..661759febd --- /dev/null +++ b/src/libs/3rdparty/libvterm/include/vterm_keycodes.h @@ -0,0 +1,61 @@ +#ifndef __VTERM_INPUT_H__ +#define __VTERM_INPUT_H__ + +typedef enum { + VTERM_MOD_NONE = 0x00, + VTERM_MOD_SHIFT = 0x01, + VTERM_MOD_ALT = 0x02, + VTERM_MOD_CTRL = 0x04, + + VTERM_ALL_MODS_MASK = 0x07 +} VTermModifier; + +typedef enum { + VTERM_KEY_NONE, + + VTERM_KEY_ENTER, + VTERM_KEY_TAB, + VTERM_KEY_BACKSPACE, + VTERM_KEY_ESCAPE, + + VTERM_KEY_UP, + VTERM_KEY_DOWN, + VTERM_KEY_LEFT, + VTERM_KEY_RIGHT, + + VTERM_KEY_INS, + VTERM_KEY_DEL, + VTERM_KEY_HOME, + VTERM_KEY_END, + VTERM_KEY_PAGEUP, + VTERM_KEY_PAGEDOWN, + + VTERM_KEY_FUNCTION_0 = 256, + VTERM_KEY_FUNCTION_MAX = VTERM_KEY_FUNCTION_0 + 255, + + VTERM_KEY_KP_0, + VTERM_KEY_KP_1, + VTERM_KEY_KP_2, + VTERM_KEY_KP_3, + VTERM_KEY_KP_4, + VTERM_KEY_KP_5, + VTERM_KEY_KP_6, + VTERM_KEY_KP_7, + VTERM_KEY_KP_8, + VTERM_KEY_KP_9, + VTERM_KEY_KP_MULT, + VTERM_KEY_KP_PLUS, + VTERM_KEY_KP_COMMA, + VTERM_KEY_KP_MINUS, + VTERM_KEY_KP_PERIOD, + VTERM_KEY_KP_DIVIDE, + VTERM_KEY_KP_ENTER, + VTERM_KEY_KP_EQUAL, + + VTERM_KEY_MAX, // Must be last + VTERM_N_KEYS = VTERM_KEY_MAX +} VTermKey; + +#define VTERM_KEY_FUNCTION(n) (VTERM_KEY_FUNCTION_0+(n)) + +#endif diff --git a/src/libs/3rdparty/libvterm/src/encoding.c b/src/libs/3rdparty/libvterm/src/encoding.c new file mode 100644 index 0000000000..434ac3f99b --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/encoding.c @@ -0,0 +1,230 @@ +#include "vterm_internal.h" + +#define UNICODE_INVALID 0xFFFD + +#if defined(DEBUG) && DEBUG > 1 +# define DEBUG_PRINT_UTF8 +#endif + +struct UTF8DecoderData { + // number of bytes remaining in this codepoint + int bytes_remaining; + + // number of bytes total in this codepoint once it's finished + // (for detecting overlongs) + int bytes_total; + + int this_cp; +}; + +static void init_utf8(VTermEncoding *enc, void *data_) +{ + struct UTF8DecoderData *data = data_; + + data->bytes_remaining = 0; + data->bytes_total = 0; +} + +static void decode_utf8(VTermEncoding *enc, void *data_, + uint32_t cp[], int *cpi, int cplen, + const char bytes[], size_t *pos, size_t bytelen) +{ + struct UTF8DecoderData *data = data_; + +#ifdef DEBUG_PRINT_UTF8 + printf("BEGIN UTF-8\n"); +#endif + + for(; *pos < bytelen && *cpi < cplen; (*pos)++) { + unsigned char c = bytes[*pos]; + +#ifdef DEBUG_PRINT_UTF8 + printf(" pos=%zd c=%02x rem=%d\n", *pos, c, data->bytes_remaining); +#endif + + if(c < 0x20) // C0 + return; + + else if(c >= 0x20 && c < 0x7f) { + if(data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + cp[(*cpi)++] = c; +#ifdef DEBUG_PRINT_UTF8 + printf(" UTF-8 char: U+%04x\n", c); +#endif + data->bytes_remaining = 0; + } + + else if(c == 0x7f) // DEL + return; + + else if(c >= 0x80 && c < 0xc0) { + if(!data->bytes_remaining) { + cp[(*cpi)++] = UNICODE_INVALID; + continue; + } + + data->this_cp <<= 6; + data->this_cp |= c & 0x3f; + data->bytes_remaining--; + + if(!data->bytes_remaining) { +#ifdef DEBUG_PRINT_UTF8 + printf(" UTF-8 raw char U+%04x bytelen=%d ", data->this_cp, data->bytes_total); +#endif + // Check for overlong sequences + switch(data->bytes_total) { + case 2: + if(data->this_cp < 0x0080) data->this_cp = UNICODE_INVALID; + break; + case 3: + if(data->this_cp < 0x0800) data->this_cp = UNICODE_INVALID; + break; + case 4: + if(data->this_cp < 0x10000) data->this_cp = UNICODE_INVALID; + break; + case 5: + if(data->this_cp < 0x200000) data->this_cp = UNICODE_INVALID; + break; + case 6: + if(data->this_cp < 0x4000000) data->this_cp = UNICODE_INVALID; + break; + } + // Now look for plain invalid ones + if((data->this_cp >= 0xD800 && data->this_cp <= 0xDFFF) || + data->this_cp == 0xFFFE || + data->this_cp == 0xFFFF) + data->this_cp = UNICODE_INVALID; +#ifdef DEBUG_PRINT_UTF8 + printf(" char: U+%04x\n", data->this_cp); +#endif + cp[(*cpi)++] = data->this_cp; + } + } + + else if(c >= 0xc0 && c < 0xe0) { + if(data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + data->this_cp = c & 0x1f; + data->bytes_total = 2; + data->bytes_remaining = 1; + } + + else if(c >= 0xe0 && c < 0xf0) { + if(data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + data->this_cp = c & 0x0f; + data->bytes_total = 3; + data->bytes_remaining = 2; + } + + else if(c >= 0xf0 && c < 0xf8) { + if(data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + data->this_cp = c & 0x07; + data->bytes_total = 4; + data->bytes_remaining = 3; + } + + else if(c >= 0xf8 && c < 0xfc) { + if(data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + data->this_cp = c & 0x03; + data->bytes_total = 5; + data->bytes_remaining = 4; + } + + else if(c >= 0xfc && c < 0xfe) { + if(data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + data->this_cp = c & 0x01; + data->bytes_total = 6; + data->bytes_remaining = 5; + } + + else { + cp[(*cpi)++] = UNICODE_INVALID; + } + } +} + +static VTermEncoding encoding_utf8 = { + .init = &init_utf8, + .decode = &decode_utf8, +}; + +static void decode_usascii(VTermEncoding *enc, void *data, + uint32_t cp[], int *cpi, int cplen, + const char bytes[], size_t *pos, size_t bytelen) +{ + int is_gr = bytes[*pos] & 0x80; + + for(; *pos < bytelen && *cpi < cplen; (*pos)++) { + unsigned char c = bytes[*pos] ^ is_gr; + + if(c < 0x20 || c == 0x7f || c >= 0x80) + return; + + cp[(*cpi)++] = c; + } +} + +static VTermEncoding encoding_usascii = { + .decode = &decode_usascii, +}; + +struct StaticTableEncoding { + const VTermEncoding enc; + const uint32_t chars[128]; +}; + +static void decode_table(VTermEncoding *enc, void *data, + uint32_t cp[], int *cpi, int cplen, + const char bytes[], size_t *pos, size_t bytelen) +{ + struct StaticTableEncoding *table = (struct StaticTableEncoding *)enc; + int is_gr = bytes[*pos] & 0x80; + + for(; *pos < bytelen && *cpi < cplen; (*pos)++) { + unsigned char c = bytes[*pos] ^ is_gr; + + if(c < 0x20 || c == 0x7f || c >= 0x80) + return; + + if(table->chars[c]) + cp[(*cpi)++] = table->chars[c]; + else + cp[(*cpi)++] = c; + } +} + +#include "encoding/DECdrawing.inc" +#include "encoding/uk.inc" + +static struct { + VTermEncodingType type; + char designation; + VTermEncoding *enc; +} +encodings[] = { + { ENC_UTF8, 'u', &encoding_utf8 }, + { ENC_SINGLE_94, '0', (VTermEncoding*)&encoding_DECdrawing }, + { ENC_SINGLE_94, 'A', (VTermEncoding*)&encoding_uk }, + { ENC_SINGLE_94, 'B', &encoding_usascii }, + { 0 }, +}; + +/* This ought to be INTERNAL but isn't because it's used by unit testing */ +VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation) +{ + for(int i = 0; encodings[i].designation; i++) + if(encodings[i].type == type && encodings[i].designation == designation) + return encodings[i].enc; + return NULL; +} diff --git a/src/libs/3rdparty/libvterm/src/encoding/DECdrawing.inc b/src/libs/3rdparty/libvterm/src/encoding/DECdrawing.inc new file mode 100644 index 0000000000..47093ed0a8 --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/encoding/DECdrawing.inc @@ -0,0 +1,36 @@ +static const struct StaticTableEncoding encoding_DECdrawing = { + { .decode = &decode_table }, + { + [0x60] = 0x25C6, + [0x61] = 0x2592, + [0x62] = 0x2409, + [0x63] = 0x240C, + [0x64] = 0x240D, + [0x65] = 0x240A, + [0x66] = 0x00B0, + [0x67] = 0x00B1, + [0x68] = 0x2424, + [0x69] = 0x240B, + [0x6a] = 0x2518, + [0x6b] = 0x2510, + [0x6c] = 0x250C, + [0x6d] = 0x2514, + [0x6e] = 0x253C, + [0x6f] = 0x23BA, + [0x70] = 0x23BB, + [0x71] = 0x2500, + [0x72] = 0x23BC, + [0x73] = 0x23BD, + [0x74] = 0x251C, + [0x75] = 0x2524, + [0x76] = 0x2534, + [0x77] = 0x252C, + [0x78] = 0x2502, + [0x79] = 0x2A7D, + [0x7a] = 0x2A7E, + [0x7b] = 0x03C0, + [0x7c] = 0x2260, + [0x7d] = 0x00A3, + [0x7e] = 0x00B7, + } +}; diff --git a/src/libs/3rdparty/libvterm/src/encoding/uk.inc b/src/libs/3rdparty/libvterm/src/encoding/uk.inc new file mode 100644 index 0000000000..da1445deca --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/encoding/uk.inc @@ -0,0 +1,6 @@ +static const struct StaticTableEncoding encoding_uk = { + { .decode = &decode_table }, + { + [0x23] = 0x00a3, + } +}; diff --git a/src/libs/3rdparty/libvterm/src/fullwidth.inc b/src/libs/3rdparty/libvterm/src/fullwidth.inc new file mode 100644 index 0000000000..a703529a76 --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/fullwidth.inc @@ -0,0 +1,111 @@ + { 0x1100, 0x115f }, + { 0x231a, 0x231b }, + { 0x2329, 0x232a }, + { 0x23e9, 0x23ec }, + { 0x23f0, 0x23f0 }, + { 0x23f3, 0x23f3 }, + { 0x25fd, 0x25fe }, + { 0x2614, 0x2615 }, + { 0x2648, 0x2653 }, + { 0x267f, 0x267f }, + { 0x2693, 0x2693 }, + { 0x26a1, 0x26a1 }, + { 0x26aa, 0x26ab }, + { 0x26bd, 0x26be }, + { 0x26c4, 0x26c5 }, + { 0x26ce, 0x26ce }, + { 0x26d4, 0x26d4 }, + { 0x26ea, 0x26ea }, + { 0x26f2, 0x26f3 }, + { 0x26f5, 0x26f5 }, + { 0x26fa, 0x26fa }, + { 0x26fd, 0x26fd }, + { 0x2705, 0x2705 }, + { 0x270a, 0x270b }, + { 0x2728, 0x2728 }, + { 0x274c, 0x274c }, + { 0x274e, 0x274e }, + { 0x2753, 0x2755 }, + { 0x2757, 0x2757 }, + { 0x2795, 0x2797 }, + { 0x27b0, 0x27b0 }, + { 0x27bf, 0x27bf }, + { 0x2b1b, 0x2b1c }, + { 0x2b50, 0x2b50 }, + { 0x2b55, 0x2b55 }, + { 0x2e80, 0x2e99 }, + { 0x2e9b, 0x2ef3 }, + { 0x2f00, 0x2fd5 }, + { 0x2ff0, 0x2ffb }, + { 0x3000, 0x303e }, + { 0x3041, 0x3096 }, + { 0x3099, 0x30ff }, + { 0x3105, 0x312f }, + { 0x3131, 0x318e }, + { 0x3190, 0x31ba }, + { 0x31c0, 0x31e3 }, + { 0x31f0, 0x321e }, + { 0x3220, 0x3247 }, + { 0x3250, 0x4dbf }, + { 0x4e00, 0xa48c }, + { 0xa490, 0xa4c6 }, + { 0xa960, 0xa97c }, + { 0xac00, 0xd7a3 }, + { 0xf900, 0xfaff }, + { 0xfe10, 0xfe19 }, + { 0xfe30, 0xfe52 }, + { 0xfe54, 0xfe66 }, + { 0xfe68, 0xfe6b }, + { 0xff01, 0xff60 }, + { 0xffe0, 0xffe6 }, + { 0x16fe0, 0x16fe3 }, + { 0x17000, 0x187f7 }, + { 0x18800, 0x18af2 }, + { 0x1b000, 0x1b11e }, + { 0x1b150, 0x1b152 }, + { 0x1b164, 0x1b167 }, + { 0x1b170, 0x1b2fb }, + { 0x1f004, 0x1f004 }, + { 0x1f0cf, 0x1f0cf }, + { 0x1f18e, 0x1f18e }, + { 0x1f191, 0x1f19a }, + { 0x1f200, 0x1f202 }, + { 0x1f210, 0x1f23b }, + { 0x1f240, 0x1f248 }, + { 0x1f250, 0x1f251 }, + { 0x1f260, 0x1f265 }, + { 0x1f300, 0x1f320 }, + { 0x1f32d, 0x1f335 }, + { 0x1f337, 0x1f37c }, + { 0x1f37e, 0x1f393 }, + { 0x1f3a0, 0x1f3ca }, + { 0x1f3cf, 0x1f3d3 }, + { 0x1f3e0, 0x1f3f0 }, + { 0x1f3f4, 0x1f3f4 }, + { 0x1f3f8, 0x1f43e }, + { 0x1f440, 0x1f440 }, + { 0x1f442, 0x1f4fc }, + { 0x1f4ff, 0x1f53d }, + { 0x1f54b, 0x1f54e }, + { 0x1f550, 0x1f567 }, + { 0x1f57a, 0x1f57a }, + { 0x1f595, 0x1f596 }, + { 0x1f5a4, 0x1f5a4 }, + { 0x1f5fb, 0x1f64f }, + { 0x1f680, 0x1f6c5 }, + { 0x1f6cc, 0x1f6cc }, + { 0x1f6d0, 0x1f6d2 }, + { 0x1f6d5, 0x1f6d5 }, + { 0x1f6eb, 0x1f6ec }, + { 0x1f6f4, 0x1f6fa }, + { 0x1f7e0, 0x1f7eb }, + { 0x1f90d, 0x1f971 }, + { 0x1f973, 0x1f976 }, + { 0x1f97a, 0x1f9a2 }, + { 0x1f9a5, 0x1f9aa }, + { 0x1f9ae, 0x1f9ca }, + { 0x1f9cd, 0x1f9ff }, + { 0x1fa70, 0x1fa73 }, + { 0x1fa78, 0x1fa7a }, + { 0x1fa80, 0x1fa82 }, + { 0x1fa90, 0x1fa95 }, diff --git a/src/libs/3rdparty/libvterm/src/keyboard.c b/src/libs/3rdparty/libvterm/src/keyboard.c new file mode 100644 index 0000000000..d31c8be12d --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/keyboard.c @@ -0,0 +1,226 @@ +#include "vterm_internal.h" + +#include + +#include "utf8.h" + +void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod) +{ + /* The shift modifier is never important for Unicode characters + * apart from Space + */ + if(c != ' ') + mod &= ~VTERM_MOD_SHIFT; + + if(mod == 0) { + // Normal text - ignore just shift + char str[6]; + int seqlen = fill_utf8(c, str); + vterm_push_output_bytes(vt, str, seqlen); + return; + } + + int needs_CSIu; + switch(c) { + /* Special Ctrl- letters that can't be represented elsewise */ + case 'i': case 'j': case 'm': case '[': + needs_CSIu = 1; + break; + /* Ctrl-\ ] ^ _ don't need CSUu */ + case '\\': case ']': case '^': case '_': + needs_CSIu = 0; + break; + /* Shift-space needs CSIu */ + case ' ': + needs_CSIu = !!(mod & VTERM_MOD_SHIFT); + break; + /* All other characters needs CSIu except for letters a-z */ + default: + needs_CSIu = (c < 'a' || c > 'z'); + } + + /* ALT we can just prefix with ESC; anything else requires CSI u */ + if(needs_CSIu && (mod & ~VTERM_MOD_ALT)) { + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", c, mod+1); + return; + } + + if(mod & VTERM_MOD_CTRL) + c &= 0x1f; + + vterm_push_output_sprintf(vt, "%s%c", mod & VTERM_MOD_ALT ? ESC_S : "", c); +} + +typedef struct { + enum { + KEYCODE_NONE, + KEYCODE_LITERAL, + KEYCODE_TAB, + KEYCODE_ENTER, + KEYCODE_SS3, + KEYCODE_CSI, + KEYCODE_CSI_CURSOR, + KEYCODE_CSINUM, + KEYCODE_KEYPAD, + } type; + char literal; + int csinum; +} keycodes_s; + +static keycodes_s keycodes[] = { + { KEYCODE_NONE }, // NONE + + { KEYCODE_ENTER, '\r' }, // ENTER + { KEYCODE_TAB, '\t' }, // TAB + { KEYCODE_LITERAL, '\x7f' }, // BACKSPACE == ASCII DEL + { KEYCODE_LITERAL, '\x1b' }, // ESCAPE + + { KEYCODE_CSI_CURSOR, 'A' }, // UP + { KEYCODE_CSI_CURSOR, 'B' }, // DOWN + { KEYCODE_CSI_CURSOR, 'D' }, // LEFT + { KEYCODE_CSI_CURSOR, 'C' }, // RIGHT + + { KEYCODE_CSINUM, '~', 2 }, // INS + { KEYCODE_CSINUM, '~', 3 }, // DEL + { KEYCODE_CSI_CURSOR, 'H' }, // HOME + { KEYCODE_CSI_CURSOR, 'F' }, // END + { KEYCODE_CSINUM, '~', 5 }, // PAGEUP + { KEYCODE_CSINUM, '~', 6 }, // PAGEDOWN +}; + +static keycodes_s keycodes_fn[] = { + { KEYCODE_NONE }, // F0 - shouldn't happen + { KEYCODE_SS3, 'P' }, // F1 + { KEYCODE_SS3, 'Q' }, // F2 + { KEYCODE_SS3, 'R' }, // F3 + { KEYCODE_SS3, 'S' }, // F4 + { KEYCODE_CSINUM, '~', 15 }, // F5 + { KEYCODE_CSINUM, '~', 17 }, // F6 + { KEYCODE_CSINUM, '~', 18 }, // F7 + { KEYCODE_CSINUM, '~', 19 }, // F8 + { KEYCODE_CSINUM, '~', 20 }, // F9 + { KEYCODE_CSINUM, '~', 21 }, // F10 + { KEYCODE_CSINUM, '~', 23 }, // F11 + { KEYCODE_CSINUM, '~', 24 }, // F12 +}; + +static keycodes_s keycodes_kp[] = { + { KEYCODE_KEYPAD, '0', 'p' }, // KP_0 + { KEYCODE_KEYPAD, '1', 'q' }, // KP_1 + { KEYCODE_KEYPAD, '2', 'r' }, // KP_2 + { KEYCODE_KEYPAD, '3', 's' }, // KP_3 + { KEYCODE_KEYPAD, '4', 't' }, // KP_4 + { KEYCODE_KEYPAD, '5', 'u' }, // KP_5 + { KEYCODE_KEYPAD, '6', 'v' }, // KP_6 + { KEYCODE_KEYPAD, '7', 'w' }, // KP_7 + { KEYCODE_KEYPAD, '8', 'x' }, // KP_8 + { KEYCODE_KEYPAD, '9', 'y' }, // KP_9 + { KEYCODE_KEYPAD, '*', 'j' }, // KP_MULT + { KEYCODE_KEYPAD, '+', 'k' }, // KP_PLUS + { KEYCODE_KEYPAD, ',', 'l' }, // KP_COMMA + { KEYCODE_KEYPAD, '-', 'm' }, // KP_MINUS + { KEYCODE_KEYPAD, '.', 'n' }, // KP_PERIOD + { KEYCODE_KEYPAD, '/', 'o' }, // KP_DIVIDE + { KEYCODE_KEYPAD, '\n', 'M' }, // KP_ENTER + { KEYCODE_KEYPAD, '=', 'X' }, // KP_EQUAL +}; + +void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod) +{ + if(key == VTERM_KEY_NONE) + return; + + keycodes_s k; + if(key < VTERM_KEY_FUNCTION_0) { + if(key >= sizeof(keycodes)/sizeof(keycodes[0])) + return; + k = keycodes[key]; + } + else if(key >= VTERM_KEY_FUNCTION_0 && key <= VTERM_KEY_FUNCTION_MAX) { + if((key - VTERM_KEY_FUNCTION_0) >= sizeof(keycodes_fn)/sizeof(keycodes_fn[0])) + return; + k = keycodes_fn[key - VTERM_KEY_FUNCTION_0]; + } + else if(key >= VTERM_KEY_KP_0) { + if((key - VTERM_KEY_KP_0) >= sizeof(keycodes_kp)/sizeof(keycodes_kp[0])) + return; + k = keycodes_kp[key - VTERM_KEY_KP_0]; + } + + switch(k.type) { + case KEYCODE_NONE: + break; + + case KEYCODE_TAB: + /* Shift-Tab is CSI Z but plain Tab is 0x09 */ + if(mod == VTERM_MOD_SHIFT) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "Z"); + else if(mod & VTERM_MOD_SHIFT) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%dZ", mod+1); + else + goto case_LITERAL; + break; + + case KEYCODE_ENTER: + /* Enter is CRLF in newline mode, but just LF in linefeed */ + if(vt->state->mode.newline) + vterm_push_output_sprintf(vt, "\r\n"); + else + goto case_LITERAL; + break; + + case KEYCODE_LITERAL: case_LITERAL: + if(mod & (VTERM_MOD_SHIFT|VTERM_MOD_CTRL)) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", k.literal, mod+1); + else + vterm_push_output_sprintf(vt, mod & VTERM_MOD_ALT ? ESC_S "%c" : "%c", k.literal); + break; + + case KEYCODE_SS3: case_SS3: + if(mod == 0) + vterm_push_output_sprintf_ctrl(vt, C1_SS3, "%c", k.literal); + else + goto case_CSI; + break; + + case KEYCODE_CSI: case_CSI: + if(mod == 0) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%c", k.literal); + else + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%d%c", mod + 1, k.literal); + break; + + case KEYCODE_CSINUM: + if(mod == 0) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d%c", k.csinum, k.literal); + else + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%d%c", k.csinum, mod + 1, k.literal); + break; + + case KEYCODE_CSI_CURSOR: + if(vt->state->mode.cursor) + goto case_SS3; + else + goto case_CSI; + + case KEYCODE_KEYPAD: + if(vt->state->mode.keypad) { + k.literal = k.csinum; + goto case_SS3; + } + else + goto case_LITERAL; + } +} + +void vterm_keyboard_start_paste(VTerm *vt) +{ + if(vt->state->mode.bracketpaste) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "200~"); +} + +void vterm_keyboard_end_paste(VTerm *vt) +{ + if(vt->state->mode.bracketpaste) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "201~"); +} diff --git a/src/libs/3rdparty/libvterm/src/mouse.c b/src/libs/3rdparty/libvterm/src/mouse.c new file mode 100644 index 0000000000..bd713f8106 --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/mouse.c @@ -0,0 +1,99 @@ +#include "vterm_internal.h" + +#include "utf8.h" + +static void output_mouse(VTermState *state, int code, int pressed, int modifiers, int col, int row) +{ + modifiers <<= 2; + + switch(state->mouse_protocol) { + case MOUSE_X10: + if(col + 0x21 > 0xff) + col = 0xff - 0x21; + if(row + 0x21 > 0xff) + row = 0xff - 0x21; + + if(!pressed) + code = 3; + + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%c%c%c", + (code | modifiers) + 0x20, col + 0x21, row + 0x21); + break; + + case MOUSE_UTF8: + { + char utf8[18]; size_t len = 0; + + if(!pressed) + code = 3; + + len += fill_utf8((code | modifiers) + 0x20, utf8 + len); + len += fill_utf8(col + 0x21, utf8 + len); + len += fill_utf8(row + 0x21, utf8 + len); + utf8[len] = 0; + + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%s", utf8); + } + break; + + case MOUSE_SGR: + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "<%d;%d;%d%c", + code | modifiers, col + 1, row + 1, pressed ? 'M' : 'm'); + break; + + case MOUSE_RXVT: + if(!pressed) + code = 3; + + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%d;%d;%dM", + code | modifiers, col + 1, row + 1); + break; + } +} + +void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod) +{ + VTermState *state = vt->state; + + if(col == state->mouse_col && row == state->mouse_row) + return; + + state->mouse_col = col; + state->mouse_row = row; + + if((state->mouse_flags & MOUSE_WANT_DRAG && state->mouse_buttons) || + (state->mouse_flags & MOUSE_WANT_MOVE)) { + int button = state->mouse_buttons & 0x01 ? 1 : + state->mouse_buttons & 0x02 ? 2 : + state->mouse_buttons & 0x04 ? 3 : 4; + output_mouse(state, button-1 + 0x20, 1, mod, col, row); + } +} + +void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod) +{ + VTermState *state = vt->state; + + int old_buttons = state->mouse_buttons; + + if(button > 0 && button <= 3) { + if(pressed) + state->mouse_buttons |= (1 << (button-1)); + else + state->mouse_buttons &= ~(1 << (button-1)); + } + + /* Most of the time we don't get button releases from 4/5 */ + if(state->mouse_buttons == old_buttons && button < 4) + return; + + if(!state->mouse_flags) + return; + + if(button < 4) { + output_mouse(state, button-1, pressed, mod, state->mouse_col, state->mouse_row); + } + else if(button < 6) { + output_mouse(state, button-4 + 0x40, pressed, mod, state->mouse_col, state->mouse_row); + } +} diff --git a/src/libs/3rdparty/libvterm/src/parser.c b/src/libs/3rdparty/libvterm/src/parser.c new file mode 100644 index 0000000000..b43a549cef --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/parser.c @@ -0,0 +1,402 @@ +#include "vterm_internal.h" + +#include +#include + +#undef DEBUG_PARSER + +static bool is_intermed(unsigned char c) +{ + return c >= 0x20 && c <= 0x2f; +} + +static void do_control(VTerm *vt, unsigned char control) +{ + if(vt->parser.callbacks && vt->parser.callbacks->control) + if((*vt->parser.callbacks->control)(control, vt->parser.cbdata)) + return; + + DEBUG_LOG("libvterm: Unhandled control 0x%02x\n", control); +} + +static void do_csi(VTerm *vt, char command) +{ +#ifdef DEBUG_PARSER + printf("Parsed CSI args as:\n", arglen, args); + printf(" leader: %s\n", vt->parser.v.csi.leader); + for(int argi = 0; argi < vt->parser.v.csi.argi; argi++) { + printf(" %lu", CSI_ARG(vt->parser.v.csi.args[argi])); + if(!CSI_ARG_HAS_MORE(vt->parser.v.csi.args[argi])) + printf("\n"); + printf(" intermed: %s\n", vt->parser.intermed); + } +#endif + + if(vt->parser.callbacks && vt->parser.callbacks->csi) + if((*vt->parser.callbacks->csi)( + vt->parser.v.csi.leaderlen ? vt->parser.v.csi.leader : NULL, + vt->parser.v.csi.args, + vt->parser.v.csi.argi, + vt->parser.intermedlen ? vt->parser.intermed : NULL, + command, + vt->parser.cbdata)) + return; + + DEBUG_LOG("libvterm: Unhandled CSI %c\n", command); +} + +static void do_escape(VTerm *vt, char command) +{ + char seq[INTERMED_MAX+1]; + + size_t len = vt->parser.intermedlen; + strncpy(seq, vt->parser.intermed, len); + seq[len++] = command; + seq[len] = 0; + + if(vt->parser.callbacks && vt->parser.callbacks->escape) + if((*vt->parser.callbacks->escape)(seq, len, vt->parser.cbdata)) + return; + + DEBUG_LOG("libvterm: Unhandled escape ESC 0x%02x\n", command); +} + +static void string_fragment(VTerm *vt, const char *str, size_t len, bool final) +{ + VTermStringFragment frag = { + .str = str, + .len = len, + .initial = vt->parser.string_initial, + .final = final, + }; + + switch(vt->parser.state) { + case OSC: + if(vt->parser.callbacks && vt->parser.callbacks->osc) + (*vt->parser.callbacks->osc)(vt->parser.v.osc.command, frag, vt->parser.cbdata); + break; + + case DCS: + if(vt->parser.callbacks && vt->parser.callbacks->dcs) + (*vt->parser.callbacks->dcs)(vt->parser.v.dcs.command, vt->parser.v.dcs.commandlen, frag, vt->parser.cbdata); + break; + + case APC: + if(vt->parser.callbacks && vt->parser.callbacks->apc) + (*vt->parser.callbacks->apc)(frag, vt->parser.cbdata); + break; + + case PM: + if(vt->parser.callbacks && vt->parser.callbacks->pm) + (*vt->parser.callbacks->pm)(frag, vt->parser.cbdata); + break; + + case SOS: + if(vt->parser.callbacks && vt->parser.callbacks->sos) + (*vt->parser.callbacks->sos)(frag, vt->parser.cbdata); + break; + + case NORMAL: + case CSI_LEADER: + case CSI_ARGS: + case CSI_INTERMED: + case OSC_COMMAND: + case DCS_COMMAND: + break; + } + + vt->parser.string_initial = false; +} + +size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len) +{ + size_t pos = 0; + const char *string_start; + + switch(vt->parser.state) { + case NORMAL: + case CSI_LEADER: + case CSI_ARGS: + case CSI_INTERMED: + case OSC_COMMAND: + case DCS_COMMAND: + string_start = NULL; + break; + case OSC: + case DCS: + case APC: + case PM: + case SOS: + string_start = bytes; + break; + } + +#define ENTER_STATE(st) do { vt->parser.state = st; string_start = NULL; } while(0) +#define ENTER_NORMAL_STATE() ENTER_STATE(NORMAL) + +#define IS_STRING_STATE() (vt->parser.state >= OSC_COMMAND) + + for( ; pos < len; pos++) { + unsigned char c = bytes[pos]; + bool c1_allowed = !vt->mode.utf8; + + if(c == 0x00 || c == 0x7f) { // NUL, DEL + if(IS_STRING_STATE()) { + string_fragment(vt, string_start, bytes + pos - string_start, false); + string_start = bytes + pos + 1; + } + if(vt->parser.emit_nul) + do_control(vt, c); + continue; + } + if(c == 0x18 || c == 0x1a) { // CAN, SUB + vt->parser.in_esc = false; + ENTER_NORMAL_STATE(); + if(vt->parser.emit_nul) + do_control(vt, c); + continue; + } + else if(c == 0x1b) { // ESC + vt->parser.intermedlen = 0; + if(!IS_STRING_STATE()) + vt->parser.state = NORMAL; + vt->parser.in_esc = true; + continue; + } + else if(c == 0x07 && // BEL, can stand for ST in OSC or DCS state + IS_STRING_STATE()) { + // fallthrough + } + else if(c < 0x20) { // other C0 + if(vt->parser.state == SOS) + continue; // All other C0s permitted in SOS + + if(IS_STRING_STATE()) + string_fragment(vt, string_start, bytes + pos - string_start, false); + do_control(vt, c); + if(IS_STRING_STATE()) + string_start = bytes + pos + 1; + continue; + } + // else fallthrough + + size_t string_len = bytes + pos - string_start; + + if(vt->parser.in_esc) { + // Hoist an ESC letter into a C1 if we're not in a string mode + // Always accept ESC \ == ST even in string mode + if(!vt->parser.intermedlen && + c >= 0x40 && c < 0x60 && + ((!IS_STRING_STATE() || c == 0x5c))) { + c += 0x40; + c1_allowed = true; + if(string_len) + string_len -= 1; + vt->parser.in_esc = false; + } + else { + string_start = NULL; + vt->parser.state = NORMAL; + } + } + + switch(vt->parser.state) { + case CSI_LEADER: + /* Extract leader bytes 0x3c to 0x3f */ + if(c >= 0x3c && c <= 0x3f) { + if(vt->parser.v.csi.leaderlen < CSI_LEADER_MAX-1) + vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen++] = c; + break; + } + + /* else fallthrough */ + vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen] = 0; + + vt->parser.v.csi.argi = 0; + vt->parser.v.csi.args[0] = CSI_ARG_MISSING; + vt->parser.state = CSI_ARGS; + + /* fallthrough */ + case CSI_ARGS: + /* Numerical value of argument */ + if(c >= '0' && c <= '9') { + if(vt->parser.v.csi.args[vt->parser.v.csi.argi] == CSI_ARG_MISSING) + vt->parser.v.csi.args[vt->parser.v.csi.argi] = 0; + vt->parser.v.csi.args[vt->parser.v.csi.argi] *= 10; + vt->parser.v.csi.args[vt->parser.v.csi.argi] += c - '0'; + break; + } + if(c == ':') { + vt->parser.v.csi.args[vt->parser.v.csi.argi] |= CSI_ARG_FLAG_MORE; + c = ';'; + } + if(c == ';') { + vt->parser.v.csi.argi++; + vt->parser.v.csi.args[vt->parser.v.csi.argi] = CSI_ARG_MISSING; + break; + } + + /* else fallthrough */ + vt->parser.v.csi.argi++; + vt->parser.intermedlen = 0; + vt->parser.state = CSI_INTERMED; + case CSI_INTERMED: + if(is_intermed(c)) { + if(vt->parser.intermedlen < INTERMED_MAX-1) + vt->parser.intermed[vt->parser.intermedlen++] = c; + break; + } + else if(c == 0x1b) { + /* ESC in CSI cancels */ + } + else if(c >= 0x40 && c <= 0x7e) { + vt->parser.intermed[vt->parser.intermedlen] = 0; + do_csi(vt, c); + } + /* else was invalid CSI */ + + ENTER_NORMAL_STATE(); + break; + + case OSC_COMMAND: + /* Numerical value of command */ + if(c >= '0' && c <= '9') { + if(vt->parser.v.osc.command == -1) + vt->parser.v.osc.command = 0; + else + vt->parser.v.osc.command *= 10; + vt->parser.v.osc.command += c - '0'; + break; + } + if(c == ';') { + vt->parser.state = OSC; + string_start = bytes + pos + 1; + break; + } + + /* else fallthrough */ + string_start = bytes + pos; + string_len = 0; + vt->parser.state = OSC; + goto string_state; + + case DCS_COMMAND: + if(vt->parser.v.dcs.commandlen < CSI_LEADER_MAX) + vt->parser.v.dcs.command[vt->parser.v.dcs.commandlen++] = c; + + if(c >= 0x40 && c<= 0x7e) { + string_start = bytes + pos + 1; + vt->parser.state = DCS; + } + break; + +string_state: + case OSC: + case DCS: + case APC: + case PM: + case SOS: + if(c == 0x07 || (c1_allowed && c == 0x9c)) { + string_fragment(vt, string_start, string_len, true); + ENTER_NORMAL_STATE(); + } + break; + + case NORMAL: + if(vt->parser.in_esc) { + if(is_intermed(c)) { + if(vt->parser.intermedlen < INTERMED_MAX-1) + vt->parser.intermed[vt->parser.intermedlen++] = c; + } + else if(c >= 0x30 && c < 0x7f) { + do_escape(vt, c); + vt->parser.in_esc = 0; + ENTER_NORMAL_STATE(); + } + else { + DEBUG_LOG("TODO: Unhandled byte %02x in Escape\n", c); + } + break; + } + if(c1_allowed && c >= 0x80 && c < 0xa0) { + switch(c) { + case 0x90: // DCS + vt->parser.string_initial = true; + vt->parser.v.dcs.commandlen = 0; + ENTER_STATE(DCS_COMMAND); + break; + case 0x98: // SOS + vt->parser.string_initial = true; + ENTER_STATE(SOS); + string_start = bytes + pos + 1; + string_len = 0; + break; + case 0x9b: // CSI + vt->parser.v.csi.leaderlen = 0; + ENTER_STATE(CSI_LEADER); + break; + case 0x9d: // OSC + vt->parser.v.osc.command = -1; + vt->parser.string_initial = true; + string_start = bytes + pos + 1; + ENTER_STATE(OSC_COMMAND); + break; + case 0x9e: // PM + vt->parser.string_initial = true; + ENTER_STATE(PM); + string_start = bytes + pos + 1; + string_len = 0; + break; + case 0x9f: // APC + vt->parser.string_initial = true; + ENTER_STATE(APC); + string_start = bytes + pos + 1; + string_len = 0; + break; + default: + do_control(vt, c); + break; + } + } + else { + size_t eaten = 0; + if(vt->parser.callbacks && vt->parser.callbacks->text) + eaten = (*vt->parser.callbacks->text)(bytes + pos, len - pos, vt->parser.cbdata); + + if(!eaten) { + DEBUG_LOG("libvterm: Text callback did not consume any input\n"); + /* force it to make progress */ + eaten = 1; + } + + pos += (eaten - 1); // we'll ++ it again in a moment + } + break; + } + } + + if(string_start) { + size_t string_len = bytes + pos - string_start; + if(vt->parser.in_esc) + string_len -= 1; + string_fragment(vt, string_start, string_len, false); + } + + return len; +} + +void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user) +{ + vt->parser.callbacks = callbacks; + vt->parser.cbdata = user; +} + +void *vterm_parser_get_cbdata(VTerm *vt) +{ + return vt->parser.cbdata; +} + +void vterm_parser_set_emit_nul(VTerm *vt, bool emit) +{ + vt->parser.emit_nul = emit; +} diff --git a/src/libs/3rdparty/libvterm/src/pen.c b/src/libs/3rdparty/libvterm/src/pen.c new file mode 100644 index 0000000000..2227a6fcd3 --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/pen.c @@ -0,0 +1,607 @@ +#include "vterm_internal.h" + +#include + +/** + * Structure used to store RGB triples without the additional metadata stored in + * VTermColor. + */ +typedef struct { + uint8_t red, green, blue; +} VTermRGB; + +static const VTermRGB ansi_colors[] = { + /* R G B */ + { 0, 0, 0 }, // black + { 224, 0, 0 }, // red + { 0, 224, 0 }, // green + { 224, 224, 0 }, // yellow + { 0, 0, 224 }, // blue + { 224, 0, 224 }, // magenta + { 0, 224, 224 }, // cyan + { 224, 224, 224 }, // white == light grey + + // high intensity + { 128, 128, 128 }, // black + { 255, 64, 64 }, // red + { 64, 255, 64 }, // green + { 255, 255, 64 }, // yellow + { 64, 64, 255 }, // blue + { 255, 64, 255 }, // magenta + { 64, 255, 255 }, // cyan + { 255, 255, 255 }, // white for real +}; + +static int ramp6[] = { + 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF, +}; + +static int ramp24[] = { + 0x00, 0x0B, 0x16, 0x21, 0x2C, 0x37, 0x42, 0x4D, 0x58, 0x63, 0x6E, 0x79, + 0x85, 0x90, 0x9B, 0xA6, 0xB1, 0xBC, 0xC7, 0xD2, 0xDD, 0xE8, 0xF3, 0xFF, +}; + +static void lookup_default_colour_ansi(long idx, VTermColor *col) +{ + if (idx >= 0 && idx < 16) { + vterm_color_rgb( + col, + ansi_colors[idx].red, ansi_colors[idx].green, ansi_colors[idx].blue); + } +} + +static bool lookup_colour_ansi(const VTermState *state, long index, VTermColor *col) +{ + if(index >= 0 && index < 16) { + *col = state->colors[index]; + return true; + } + + return false; +} + +static bool lookup_colour_palette(const VTermState *state, long index, VTermColor *col) +{ + if(index >= 0 && index < 16) { + // Normal 8 colours or high intensity - parse as palette 0 + return lookup_colour_ansi(state, index, col); + } + else if(index >= 16 && index < 232) { + // 216-colour cube + index -= 16; + + vterm_color_rgb(col, ramp6[index/6/6 % 6], + ramp6[index/6 % 6], + ramp6[index % 6]); + + return true; + } + else if(index >= 232 && index < 256) { + // 24 greyscales + index -= 232; + + vterm_color_rgb(col, ramp24[index], ramp24[index], ramp24[index]); + + return true; + } + + return false; +} + +static int lookup_colour(const VTermState *state, int palette, const long args[], int argcount, VTermColor *col) +{ + switch(palette) { + case 2: // RGB mode - 3 args contain colour values directly + if(argcount < 3) + return argcount; + + vterm_color_rgb(col, CSI_ARG(args[0]), CSI_ARG(args[1]), CSI_ARG(args[2])); + + return 3; + + case 5: // XTerm 256-colour mode + if (!argcount || CSI_ARG_IS_MISSING(args[0])) { + return argcount ? 1 : 0; + } + + vterm_color_indexed(col, args[0]); + + return argcount ? 1 : 0; + + default: + DEBUG_LOG("Unrecognised colour palette %d\n", palette); + return 0; + } +} + +// Some conveniences + +static void setpenattr(VTermState *state, VTermAttr attr, VTermValueType type, VTermValue *val) +{ +#ifdef DEBUG + if(type != vterm_get_attr_type(attr)) { + DEBUG_LOG("Cannot set attr %d as it has type %d, not type %d\n", + attr, vterm_get_attr_type(attr), type); + return; + } +#endif + if(state->callbacks && state->callbacks->setpenattr) + (*state->callbacks->setpenattr)(attr, val, state->cbdata); +} + +static void setpenattr_bool(VTermState *state, VTermAttr attr, int boolean) +{ + VTermValue val = { .boolean = boolean }; + setpenattr(state, attr, VTERM_VALUETYPE_BOOL, &val); +} + +static void setpenattr_int(VTermState *state, VTermAttr attr, int number) +{ + VTermValue val = { .number = number }; + setpenattr(state, attr, VTERM_VALUETYPE_INT, &val); +} + +static void setpenattr_col(VTermState *state, VTermAttr attr, VTermColor color) +{ + VTermValue val = { .color = color }; + setpenattr(state, attr, VTERM_VALUETYPE_COLOR, &val); +} + +static void set_pen_col_ansi(VTermState *state, VTermAttr attr, long col) +{ + VTermColor *colp = (attr == VTERM_ATTR_BACKGROUND) ? &state->pen.bg : &state->pen.fg; + + vterm_color_indexed(colp, col); + + setpenattr_col(state, attr, *colp); +} + +INTERNAL void vterm_state_newpen(VTermState *state) +{ + // 90% grey so that pure white is brighter + vterm_color_rgb(&state->default_fg, 240, 240, 240); + vterm_color_rgb(&state->default_bg, 0, 0, 0); + vterm_state_set_default_colors(state, &state->default_fg, &state->default_bg); + + for(int col = 0; col < 16; col++) + lookup_default_colour_ansi(col, &state->colors[col]); +} + +INTERNAL void vterm_state_resetpen(VTermState *state) +{ + state->pen.bold = 0; setpenattr_bool(state, VTERM_ATTR_BOLD, 0); + state->pen.underline = 0; setpenattr_int (state, VTERM_ATTR_UNDERLINE, 0); + state->pen.italic = 0; setpenattr_bool(state, VTERM_ATTR_ITALIC, 0); + state->pen.blink = 0; setpenattr_bool(state, VTERM_ATTR_BLINK, 0); + state->pen.reverse = 0; setpenattr_bool(state, VTERM_ATTR_REVERSE, 0); + state->pen.conceal = 0; setpenattr_bool(state, VTERM_ATTR_CONCEAL, 0); + state->pen.strike = 0; setpenattr_bool(state, VTERM_ATTR_STRIKE, 0); + state->pen.font = 0; setpenattr_int (state, VTERM_ATTR_FONT, 0); + state->pen.small = 0; setpenattr_bool(state, VTERM_ATTR_SMALL, 0); + state->pen.baseline = 0; setpenattr_int (state, VTERM_ATTR_BASELINE, 0); + + state->pen.fg = state->default_fg; setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->default_fg); + state->pen.bg = state->default_bg; setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->default_bg); +} + +INTERNAL void vterm_state_savepen(VTermState *state, int save) +{ + if(save) { + state->saved.pen = state->pen; + } + else { + state->pen = state->saved.pen; + + setpenattr_bool(state, VTERM_ATTR_BOLD, state->pen.bold); + setpenattr_int (state, VTERM_ATTR_UNDERLINE, state->pen.underline); + setpenattr_bool(state, VTERM_ATTR_ITALIC, state->pen.italic); + setpenattr_bool(state, VTERM_ATTR_BLINK, state->pen.blink); + setpenattr_bool(state, VTERM_ATTR_REVERSE, state->pen.reverse); + setpenattr_bool(state, VTERM_ATTR_CONCEAL, state->pen.conceal); + setpenattr_bool(state, VTERM_ATTR_STRIKE, state->pen.strike); + setpenattr_int (state, VTERM_ATTR_FONT, state->pen.font); + setpenattr_bool(state, VTERM_ATTR_SMALL, state->pen.small); + setpenattr_int (state, VTERM_ATTR_BASELINE, state->pen.baseline); + + setpenattr_col( state, VTERM_ATTR_FOREGROUND, state->pen.fg); + setpenattr_col( state, VTERM_ATTR_BACKGROUND, state->pen.bg); + } +} + +int vterm_color_is_equal(const VTermColor *a, const VTermColor *b) +{ + /* First make sure that the two colours are of the same type (RGB/Indexed) */ + if (a->type != b->type) { + return false; + } + + /* Depending on the type inspect the corresponding members */ + if (VTERM_COLOR_IS_INDEXED(a)) { + return a->indexed.idx == b->indexed.idx; + } + else if (VTERM_COLOR_IS_RGB(a)) { + return (a->rgb.red == b->rgb.red) + && (a->rgb.green == b->rgb.green) + && (a->rgb.blue == b->rgb.blue); + } + + return 0; +} + +void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg) +{ + *default_fg = state->default_fg; + *default_bg = state->default_bg; +} + +void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col) +{ + lookup_colour_palette(state, index, col); +} + +void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg) +{ + if(default_fg) { + state->default_fg = *default_fg; + state->default_fg.type = (state->default_fg.type & ~VTERM_COLOR_DEFAULT_MASK) + | VTERM_COLOR_DEFAULT_FG; + } + + if(default_bg) { + state->default_bg = *default_bg; + state->default_bg.type = (state->default_bg.type & ~VTERM_COLOR_DEFAULT_MASK) + | VTERM_COLOR_DEFAULT_BG; + } +} + +void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col) +{ + if(index >= 0 && index < 16) + state->colors[index] = *col; +} + +void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col) +{ + if (VTERM_COLOR_IS_INDEXED(col)) { /* Convert indexed colors to RGB */ + lookup_colour_palette(state, col->indexed.idx, col); + } + col->type &= VTERM_COLOR_TYPE_MASK; /* Reset any metadata but the type */ +} + +void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright) +{ + state->bold_is_highbright = bold_is_highbright; +} + +INTERNAL void vterm_state_setpen(VTermState *state, const long args[], int argcount) +{ + // SGR - ECMA-48 8.3.117 + + int argi = 0; + int value; + + while(argi < argcount) { + // This logic is easier to do 'done' backwards; set it true, and make it + // false again in the 'default' case + int done = 1; + + long arg; + switch(arg = CSI_ARG(args[argi])) { + case CSI_ARG_MISSING: + case 0: // Reset + vterm_state_resetpen(state); + break; + + case 1: { // Bold on + const VTermColor *fg = &state->pen.fg; + state->pen.bold = 1; + setpenattr_bool(state, VTERM_ATTR_BOLD, 1); + if(!VTERM_COLOR_IS_DEFAULT_FG(fg) && VTERM_COLOR_IS_INDEXED(fg) && fg->indexed.idx < 8 && state->bold_is_highbright) + set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, fg->indexed.idx + (state->pen.bold ? 8 : 0)); + break; + } + + case 3: // Italic on + state->pen.italic = 1; + setpenattr_bool(state, VTERM_ATTR_ITALIC, 1); + break; + + case 4: // Underline + state->pen.underline = VTERM_UNDERLINE_SINGLE; + if(CSI_ARG_HAS_MORE(args[argi])) { + argi++; + switch(CSI_ARG(args[argi])) { + case 0: + state->pen.underline = 0; + break; + case 1: + state->pen.underline = VTERM_UNDERLINE_SINGLE; + break; + case 2: + state->pen.underline = VTERM_UNDERLINE_DOUBLE; + break; + case 3: + state->pen.underline = VTERM_UNDERLINE_CURLY; + break; + } + } + setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); + break; + + case 5: // Blink + state->pen.blink = 1; + setpenattr_bool(state, VTERM_ATTR_BLINK, 1); + break; + + case 7: // Reverse on + state->pen.reverse = 1; + setpenattr_bool(state, VTERM_ATTR_REVERSE, 1); + break; + + case 8: // Conceal on + state->pen.conceal = 1; + setpenattr_bool(state, VTERM_ATTR_CONCEAL, 1); + break; + + case 9: // Strikethrough on + state->pen.strike = 1; + setpenattr_bool(state, VTERM_ATTR_STRIKE, 1); + break; + + case 10: case 11: case 12: case 13: case 14: + case 15: case 16: case 17: case 18: case 19: // Select font + state->pen.font = CSI_ARG(args[argi]) - 10; + setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font); + break; + + case 21: // Underline double + state->pen.underline = VTERM_UNDERLINE_DOUBLE; + setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); + break; + + case 22: // Bold off + state->pen.bold = 0; + setpenattr_bool(state, VTERM_ATTR_BOLD, 0); + break; + + case 23: // Italic and Gothic (currently unsupported) off + state->pen.italic = 0; + setpenattr_bool(state, VTERM_ATTR_ITALIC, 0); + break; + + case 24: // Underline off + state->pen.underline = 0; + setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0); + break; + + case 25: // Blink off + state->pen.blink = 0; + setpenattr_bool(state, VTERM_ATTR_BLINK, 0); + break; + + case 27: // Reverse off + state->pen.reverse = 0; + setpenattr_bool(state, VTERM_ATTR_REVERSE, 0); + break; + + case 28: // Conceal off (Reveal) + state->pen.conceal = 0; + setpenattr_bool(state, VTERM_ATTR_CONCEAL, 0); + break; + + case 29: // Strikethrough off + state->pen.strike = 0; + setpenattr_bool(state, VTERM_ATTR_STRIKE, 0); + break; + + case 30: case 31: case 32: case 33: + case 34: case 35: case 36: case 37: // Foreground colour palette + value = CSI_ARG(args[argi]) - 30; + if(state->pen.bold && state->bold_is_highbright) + value += 8; + set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value); + break; + + case 38: // Foreground colour alternative palette + if(argcount - argi < 1) + return; + argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.fg); + setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); + break; + + case 39: // Foreground colour default + state->pen.fg = state->default_fg; + setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); + break; + + case 40: case 41: case 42: case 43: + case 44: case 45: case 46: case 47: // Background colour palette + value = CSI_ARG(args[argi]) - 40; + set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value); + break; + + case 48: // Background colour alternative palette + if(argcount - argi < 1) + return; + argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.bg); + setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); + break; + + case 49: // Default background + state->pen.bg = state->default_bg; + setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); + break; + + case 73: // Superscript + case 74: // Subscript + case 75: // Superscript/subscript off + state->pen.small = (arg != 75); + state->pen.baseline = + (arg == 73) ? VTERM_BASELINE_RAISE : + (arg == 74) ? VTERM_BASELINE_LOWER : + VTERM_BASELINE_NORMAL; + setpenattr_bool(state, VTERM_ATTR_SMALL, state->pen.small); + setpenattr_int (state, VTERM_ATTR_BASELINE, state->pen.baseline); + break; + + case 90: case 91: case 92: case 93: + case 94: case 95: case 96: case 97: // Foreground colour high-intensity palette + value = CSI_ARG(args[argi]) - 90 + 8; + set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value); + break; + + case 100: case 101: case 102: case 103: + case 104: case 105: case 106: case 107: // Background colour high-intensity palette + value = CSI_ARG(args[argi]) - 100 + 8; + set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value); + break; + + default: + done = 0; + break; + } + + if(!done) + DEBUG_LOG("libvterm: Unhandled CSI SGR %ld\n", arg); + + while(CSI_ARG_HAS_MORE(args[argi++])); + } +} + +static int vterm_state_getpen_color(const VTermColor *col, int argi, long args[], int fg) +{ + /* Do nothing if the given color is the default color */ + if (( fg && VTERM_COLOR_IS_DEFAULT_FG(col)) || + (!fg && VTERM_COLOR_IS_DEFAULT_BG(col))) { + return argi; + } + + /* Decide whether to send an indexed color or an RGB color */ + if (VTERM_COLOR_IS_INDEXED(col)) { + const uint8_t idx = col->indexed.idx; + if (idx < 8) { + args[argi++] = (idx + (fg ? 30 : 40)); + } + else if (idx < 16) { + args[argi++] = (idx - 8 + (fg ? 90 : 100)); + } + else { + args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48); + args[argi++] = CSI_ARG_FLAG_MORE | 5; + args[argi++] = idx; + } + } + else if (VTERM_COLOR_IS_RGB(col)) { + args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48); + args[argi++] = CSI_ARG_FLAG_MORE | 2; + args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.red; + args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.green; + args[argi++] = col->rgb.blue; + } + return argi; +} + +INTERNAL int vterm_state_getpen(VTermState *state, long args[], int argcount) +{ + int argi = 0; + + if(state->pen.bold) + args[argi++] = 1; + + if(state->pen.italic) + args[argi++] = 3; + + if(state->pen.underline == VTERM_UNDERLINE_SINGLE) + args[argi++] = 4; + if(state->pen.underline == VTERM_UNDERLINE_CURLY) + args[argi++] = 4 | CSI_ARG_FLAG_MORE, args[argi++] = 3; + + if(state->pen.blink) + args[argi++] = 5; + + if(state->pen.reverse) + args[argi++] = 7; + + if(state->pen.conceal) + args[argi++] = 8; + + if(state->pen.strike) + args[argi++] = 9; + + if(state->pen.font) + args[argi++] = 10 + state->pen.font; + + if(state->pen.underline == VTERM_UNDERLINE_DOUBLE) + args[argi++] = 21; + + argi = vterm_state_getpen_color(&state->pen.fg, argi, args, true); + + argi = vterm_state_getpen_color(&state->pen.bg, argi, args, false); + + if(state->pen.small) { + if(state->pen.baseline == VTERM_BASELINE_RAISE) + args[argi++] = 73; + else if(state->pen.baseline == VTERM_BASELINE_LOWER) + args[argi++] = 74; + } + + return argi; +} + +int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val) +{ + switch(attr) { + case VTERM_ATTR_BOLD: + val->boolean = state->pen.bold; + return 1; + + case VTERM_ATTR_UNDERLINE: + val->number = state->pen.underline; + return 1; + + case VTERM_ATTR_ITALIC: + val->boolean = state->pen.italic; + return 1; + + case VTERM_ATTR_BLINK: + val->boolean = state->pen.blink; + return 1; + + case VTERM_ATTR_REVERSE: + val->boolean = state->pen.reverse; + return 1; + + case VTERM_ATTR_CONCEAL: + val->boolean = state->pen.conceal; + return 1; + + case VTERM_ATTR_STRIKE: + val->boolean = state->pen.strike; + return 1; + + case VTERM_ATTR_FONT: + val->number = state->pen.font; + return 1; + + case VTERM_ATTR_FOREGROUND: + val->color = state->pen.fg; + return 1; + + case VTERM_ATTR_BACKGROUND: + val->color = state->pen.bg; + return 1; + + case VTERM_ATTR_SMALL: + val->boolean = state->pen.small; + return 1; + + case VTERM_ATTR_BASELINE: + val->number = state->pen.baseline; + return 1; + + case VTERM_N_ATTRS: + return 0; + } + + return 0; +} diff --git a/src/libs/3rdparty/libvterm/src/rect.h b/src/libs/3rdparty/libvterm/src/rect.h new file mode 100644 index 0000000000..2114f24c1b --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/rect.h @@ -0,0 +1,56 @@ +/* + * Some utility functions on VTermRect structures + */ + +#define STRFrect "(%d,%d-%d,%d)" +#define ARGSrect(r) (r).start_row, (r).start_col, (r).end_row, (r).end_col + +/* Expand dst to contain src as well */ +static void rect_expand(VTermRect *dst, VTermRect *src) +{ + if(dst->start_row > src->start_row) dst->start_row = src->start_row; + if(dst->start_col > src->start_col) dst->start_col = src->start_col; + if(dst->end_row < src->end_row) dst->end_row = src->end_row; + if(dst->end_col < src->end_col) dst->end_col = src->end_col; +} + +/* Clip the dst to ensure it does not step outside of bounds */ +static void rect_clip(VTermRect *dst, VTermRect *bounds) +{ + if(dst->start_row < bounds->start_row) dst->start_row = bounds->start_row; + if(dst->start_col < bounds->start_col) dst->start_col = bounds->start_col; + if(dst->end_row > bounds->end_row) dst->end_row = bounds->end_row; + if(dst->end_col > bounds->end_col) dst->end_col = bounds->end_col; + /* Ensure it doesn't end up negatively-sized */ + if(dst->end_row < dst->start_row) dst->end_row = dst->start_row; + if(dst->end_col < dst->start_col) dst->end_col = dst->start_col; +} + +/* True if the two rectangles are equal */ +static int rect_equal(VTermRect *a, VTermRect *b) +{ + return (a->start_row == b->start_row) && + (a->start_col == b->start_col) && + (a->end_row == b->end_row) && + (a->end_col == b->end_col); +} + +/* True if small is contained entirely within big */ +static int rect_contains(VTermRect *big, VTermRect *small) +{ + if(small->start_row < big->start_row) return 0; + if(small->start_col < big->start_col) return 0; + if(small->end_row > big->end_row) return 0; + if(small->end_col > big->end_col) return 0; + return 1; +} + +/* True if the rectangles overlap at all */ +static int rect_intersects(VTermRect *a, VTermRect *b) +{ + if(a->start_row > b->end_row || b->start_row > a->end_row) + return 0; + if(a->start_col > b->end_col || b->start_col > a->end_col) + return 0; + return 1; +} diff --git a/src/libs/3rdparty/libvterm/src/screen.c b/src/libs/3rdparty/libvterm/src/screen.c new file mode 100644 index 0000000000..51c7f99e74 --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/screen.c @@ -0,0 +1,1183 @@ +#include "vterm_internal.h" + +#include +#include + +#include "rect.h" +#include "utf8.h" + +#define UNICODE_SPACE 0x20 +#define UNICODE_LINEFEED 0x0a + +#undef DEBUG_REFLOW + +/* State of the pen at some moment in time, also used in a cell */ +typedef struct +{ + /* After the bitfield */ + VTermColor fg, bg; + + unsigned int bold : 1; + unsigned int underline : 2; + unsigned int italic : 1; + unsigned int blink : 1; + unsigned int reverse : 1; + unsigned int conceal : 1; + unsigned int strike : 1; + unsigned int font : 4; /* 0 to 9 */ + unsigned int small : 1; + unsigned int baseline : 2; + + /* Extra state storage that isn't strictly pen-related */ + unsigned int protected_cell : 1; + unsigned int dwl : 1; /* on a DECDWL or DECDHL line */ + unsigned int dhl : 2; /* on a DECDHL line (1=top 2=bottom) */ +} ScreenPen; + +/* Internal representation of a screen cell */ +typedef struct +{ + uint32_t chars[VTERM_MAX_CHARS_PER_CELL]; + ScreenPen pen; +} ScreenCell; + +struct VTermScreen +{ + VTerm *vt; + VTermState *state; + + const VTermScreenCallbacks *callbacks; + void *cbdata; + + VTermDamageSize damage_merge; + /* start_row == -1 => no damage */ + VTermRect damaged; + VTermRect pending_scrollrect; + int pending_scroll_downward, pending_scroll_rightward; + + int rows; + int cols; + + unsigned int global_reverse : 1; + unsigned int reflow : 1; + + /* Primary and Altscreen. buffers[1] is lazily allocated as needed */ + ScreenCell *buffers[2]; + + /* buffer will == buffers[0] or buffers[1], depending on altscreen */ + ScreenCell *buffer; + + /* buffer for a single screen row used in scrollback storage callbacks */ + VTermScreenCell *sb_buffer; + + ScreenPen pen; +}; + +static inline void clearcell(const VTermScreen *screen, ScreenCell *cell) +{ + cell->chars[0] = 0; + cell->pen = screen->pen; +} + +static inline ScreenCell *getcell(const VTermScreen *screen, int row, int col) +{ + if(row < 0 || row >= screen->rows) + return NULL; + if(col < 0 || col >= screen->cols) + return NULL; + return screen->buffer + (screen->cols * row) + col; +} + +static ScreenCell *alloc_buffer(VTermScreen *screen, int rows, int cols) +{ + ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * rows * cols); + + for(int row = 0; row < rows; row++) { + for(int col = 0; col < cols; col++) { + clearcell(screen, &new_buffer[row * cols + col]); + } + } + + return new_buffer; +} + +static void damagerect(VTermScreen *screen, VTermRect rect) +{ + VTermRect emit; + + switch(screen->damage_merge) { + case VTERM_DAMAGE_CELL: + /* Always emit damage event */ + emit = rect; + break; + + case VTERM_DAMAGE_ROW: + /* Emit damage longer than one row. Try to merge with existing damage in + * the same row */ + if(rect.end_row > rect.start_row + 1) { + // Bigger than 1 line - flush existing, emit this + vterm_screen_flush_damage(screen); + emit = rect; + } + else if(screen->damaged.start_row == -1) { + // None stored yet + screen->damaged = rect; + return; + } + else if(rect.start_row == screen->damaged.start_row) { + // Merge with the stored line + if(screen->damaged.start_col > rect.start_col) + screen->damaged.start_col = rect.start_col; + if(screen->damaged.end_col < rect.end_col) + screen->damaged.end_col = rect.end_col; + return; + } + else { + // Emit the currently stored line, store a new one + emit = screen->damaged; + screen->damaged = rect; + } + break; + + case VTERM_DAMAGE_SCREEN: + case VTERM_DAMAGE_SCROLL: + /* Never emit damage event */ + if(screen->damaged.start_row == -1) + screen->damaged = rect; + else { + rect_expand(&screen->damaged, &rect); + } + return; + + default: + DEBUG_LOG("TODO: Maybe merge damage for level %d\n", screen->damage_merge); + return; + } + + if(screen->callbacks && screen->callbacks->damage) + (*screen->callbacks->damage)(emit, screen->cbdata); +} + +static void damagescreen(VTermScreen *screen) +{ + VTermRect rect = { + .start_row = 0, + .end_row = screen->rows, + .start_col = 0, + .end_col = screen->cols, + }; + + damagerect(screen, rect); +} + +static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user) +{ + VTermScreen *screen = user; + ScreenCell *cell = getcell(screen, pos.row, pos.col); + + if(!cell) + return 0; + + int i; + for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++) { + cell->chars[i] = info->chars[i]; + cell->pen = screen->pen; + } + if(i < VTERM_MAX_CHARS_PER_CELL) + cell->chars[i] = 0; + + for(int col = 1; col < info->width; col++) + getcell(screen, pos.row, pos.col + col)->chars[0] = (uint32_t)-1; + + VTermRect rect = { + .start_row = pos.row, + .end_row = pos.row+1, + .start_col = pos.col, + .end_col = pos.col+info->width, + }; + + cell->pen.protected_cell = info->protected_cell; + cell->pen.dwl = info->dwl; + cell->pen.dhl = info->dhl; + + damagerect(screen, rect); + + return 1; +} + +static void sb_pushline_from_row(VTermScreen *screen, int row) +{ + VTermPos pos = { .row = row }; + for(pos.col = 0; pos.col < screen->cols; pos.col++) + vterm_screen_get_cell(screen, pos, screen->sb_buffer + pos.col); + + (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata); +} + +static int moverect_internal(VTermRect dest, VTermRect src, void *user) +{ + VTermScreen *screen = user; + + if(screen->callbacks && screen->callbacks->sb_pushline && + dest.start_row == 0 && dest.start_col == 0 && // starts top-left corner + dest.end_col == screen->cols && // full width + screen->buffer == screen->buffers[BUFIDX_PRIMARY]) { // not altscreen + for(int row = 0; row < src.start_row; row++) + sb_pushline_from_row(screen, row); + } + + int cols = src.end_col - src.start_col; + int downward = src.start_row - dest.start_row; + + int init_row, test_row, inc_row; + if(downward < 0) { + init_row = dest.end_row - 1; + test_row = dest.start_row - 1; + inc_row = -1; + } + else { + init_row = dest.start_row; + test_row = dest.end_row; + inc_row = +1; + } + + for(int row = init_row; row != test_row; row += inc_row) + memmove(getcell(screen, row, dest.start_col), + getcell(screen, row + downward, src.start_col), + cols * sizeof(ScreenCell)); + + return 1; +} + +static int moverect_user(VTermRect dest, VTermRect src, void *user) +{ + VTermScreen *screen = user; + + if(screen->callbacks && screen->callbacks->moverect) { + if(screen->damage_merge != VTERM_DAMAGE_SCROLL) + // Avoid an infinite loop + vterm_screen_flush_damage(screen); + + if((*screen->callbacks->moverect)(dest, src, screen->cbdata)) + return 1; + } + + damagerect(screen, dest); + + return 1; +} + +static int erase_internal(VTermRect rect, int selective, void *user) +{ + VTermScreen *screen = user; + + for(int row = rect.start_row; row < screen->state->rows && row < rect.end_row; row++) { + const VTermLineInfo *info = vterm_state_get_lineinfo(screen->state, row); + + for(int col = rect.start_col; col < rect.end_col; col++) { + ScreenCell *cell = getcell(screen, row, col); + + if(selective && cell->pen.protected_cell) + continue; + + cell->chars[0] = 0; + cell->pen = (ScreenPen){ + /* Only copy .fg and .bg; leave things like rv in reset state */ + .fg = screen->pen.fg, + .bg = screen->pen.bg, + }; + cell->pen.dwl = info->doublewidth; + cell->pen.dhl = info->doubleheight; + } + } + + return 1; +} + +static int erase_user(VTermRect rect, int selective, void *user) +{ + VTermScreen *screen = user; + + damagerect(screen, rect); + + return 1; +} + +static int erase(VTermRect rect, int selective, void *user) +{ + erase_internal(rect, selective, user); + return erase_user(rect, 0, user); +} + +static int scrollrect(VTermRect rect, int downward, int rightward, void *user) +{ + VTermScreen *screen = user; + + if(screen->damage_merge != VTERM_DAMAGE_SCROLL) { + vterm_scroll_rect(rect, downward, rightward, + moverect_internal, erase_internal, screen); + + vterm_screen_flush_damage(screen); + + vterm_scroll_rect(rect, downward, rightward, + moverect_user, erase_user, screen); + + return 1; + } + + if(screen->damaged.start_row != -1 && + !rect_intersects(&rect, &screen->damaged)) { + vterm_screen_flush_damage(screen); + } + + if(screen->pending_scrollrect.start_row == -1) { + screen->pending_scrollrect = rect; + screen->pending_scroll_downward = downward; + screen->pending_scroll_rightward = rightward; + } + else if(rect_equal(&screen->pending_scrollrect, &rect) && + ((screen->pending_scroll_downward == 0 && downward == 0) || + (screen->pending_scroll_rightward == 0 && rightward == 0))) { + screen->pending_scroll_downward += downward; + screen->pending_scroll_rightward += rightward; + } + else { + vterm_screen_flush_damage(screen); + + screen->pending_scrollrect = rect; + screen->pending_scroll_downward = downward; + screen->pending_scroll_rightward = rightward; + } + + vterm_scroll_rect(rect, downward, rightward, + moverect_internal, erase_internal, screen); + + if(screen->damaged.start_row == -1) + return 1; + + if(rect_contains(&rect, &screen->damaged)) { + /* Scroll region entirely contains the damage; just move it */ + vterm_rect_move(&screen->damaged, -downward, -rightward); + rect_clip(&screen->damaged, &rect); + } + /* There are a number of possible cases here, but lets restrict this to only + * the common case where we might actually gain some performance by + * optimising it. Namely, a vertical scroll that neatly cuts the damage + * region in half. + */ + else if(rect.start_col <= screen->damaged.start_col && + rect.end_col >= screen->damaged.end_col && + rightward == 0) { + if(screen->damaged.start_row >= rect.start_row && + screen->damaged.start_row < rect.end_row) { + screen->damaged.start_row -= downward; + if(screen->damaged.start_row < rect.start_row) + screen->damaged.start_row = rect.start_row; + if(screen->damaged.start_row > rect.end_row) + screen->damaged.start_row = rect.end_row; + } + if(screen->damaged.end_row >= rect.start_row && + screen->damaged.end_row < rect.end_row) { + screen->damaged.end_row -= downward; + if(screen->damaged.end_row < rect.start_row) + screen->damaged.end_row = rect.start_row; + if(screen->damaged.end_row > rect.end_row) + screen->damaged.end_row = rect.end_row; + } + } + else { + DEBUG_LOG("TODO: Just flush and redo damaged=" STRFrect " rect=" STRFrect "\n", + ARGSrect(screen->damaged), ARGSrect(rect)); + } + + return 1; +} + +static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user) +{ + VTermScreen *screen = user; + + if(screen->callbacks && screen->callbacks->movecursor) + return (*screen->callbacks->movecursor)(pos, oldpos, visible, screen->cbdata); + + return 0; +} + +static int setpenattr(VTermAttr attr, VTermValue *val, void *user) +{ + VTermScreen *screen = user; + + switch(attr) { + case VTERM_ATTR_BOLD: + screen->pen.bold = val->boolean; + return 1; + case VTERM_ATTR_UNDERLINE: + screen->pen.underline = val->number; + return 1; + case VTERM_ATTR_ITALIC: + screen->pen.italic = val->boolean; + return 1; + case VTERM_ATTR_BLINK: + screen->pen.blink = val->boolean; + return 1; + case VTERM_ATTR_REVERSE: + screen->pen.reverse = val->boolean; + return 1; + case VTERM_ATTR_CONCEAL: + screen->pen.conceal = val->boolean; + return 1; + case VTERM_ATTR_STRIKE: + screen->pen.strike = val->boolean; + return 1; + case VTERM_ATTR_FONT: + screen->pen.font = val->number; + return 1; + case VTERM_ATTR_FOREGROUND: + screen->pen.fg = val->color; + return 1; + case VTERM_ATTR_BACKGROUND: + screen->pen.bg = val->color; + return 1; + case VTERM_ATTR_SMALL: + screen->pen.small = val->boolean; + return 1; + case VTERM_ATTR_BASELINE: + screen->pen.baseline = val->number; + return 1; + + case VTERM_N_ATTRS: + return 0; + } + + return 0; +} + +static int settermprop(VTermProp prop, VTermValue *val, void *user) +{ + VTermScreen *screen = user; + + switch(prop) { + case VTERM_PROP_ALTSCREEN: + if(val->boolean && !screen->buffers[BUFIDX_ALTSCREEN]) + return 0; + + screen->buffer = val->boolean ? screen->buffers[BUFIDX_ALTSCREEN] : screen->buffers[BUFIDX_PRIMARY]; + /* only send a damage event on disable; because during enable there's an + * erase that sends a damage anyway + */ + if(!val->boolean) + damagescreen(screen); + break; + case VTERM_PROP_REVERSE: + screen->global_reverse = val->boolean; + damagescreen(screen); + break; + default: + ; /* ignore */ + } + + if(screen->callbacks && screen->callbacks->settermprop) + return (*screen->callbacks->settermprop)(prop, val, screen->cbdata); + + return 1; +} + +static int bell(void *user) +{ + VTermScreen *screen = user; + + if(screen->callbacks && screen->callbacks->bell) + return (*screen->callbacks->bell)(screen->cbdata); + + return 0; +} + +/* How many cells are non-blank + * Returns the position of the first blank cell in the trailing blank end */ +static int line_popcount(ScreenCell *buffer, int row, int rows, int cols) +{ + int col = cols - 1; + while(col >= 0 && buffer[row * cols + col].chars[0] == 0) + col--; + return col + 1; +} + +#define REFLOW (screen->reflow) + +static void resize_buffer(VTermScreen *screen, int bufidx, int new_rows, int new_cols, bool active, VTermStateFields *statefields) +{ + int old_rows = screen->rows; + int old_cols = screen->cols; + + ScreenCell *old_buffer = screen->buffers[bufidx]; + VTermLineInfo *old_lineinfo = statefields->lineinfos[bufidx]; + + ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * new_rows * new_cols); + VTermLineInfo *new_lineinfo = vterm_allocator_malloc(screen->vt, sizeof(new_lineinfo[0]) * new_rows); + + int old_row = old_rows - 1; + int new_row = new_rows - 1; + + VTermPos old_cursor = statefields->pos; + VTermPos new_cursor = { -1, -1 }; + +#ifdef DEBUG_REFLOW + fprintf(stderr, "Resizing from %dx%d to %dx%d; cursor was at (%d,%d)\n", + old_cols, old_rows, new_cols, new_rows, old_cursor.col, old_cursor.row); +#endif + + /* Keep track of the final row that is knonw to be blank, so we know what + * spare space we have for scrolling into + */ + int final_blank_row = new_rows; + + while(old_row >= 0) { + int old_row_end = old_row; + /* TODO: Stop if dwl or dhl */ + while(REFLOW && old_lineinfo && old_row >= 0 && old_lineinfo[old_row].continuation) + old_row--; + int old_row_start = old_row; + + int width = 0; + for(int row = old_row_start; row <= old_row_end; row++) { + if(REFLOW && row < (old_rows - 1) && old_lineinfo[row + 1].continuation) + width += old_cols; + else + width += line_popcount(old_buffer, row, old_rows, old_cols); + } + + if(final_blank_row == (new_row + 1) && width == 0) + final_blank_row = new_row; + + int new_height = REFLOW + ? width ? (width + new_cols - 1) / new_cols : 1 + : 1; + + int new_row_end = new_row; + int new_row_start = new_row - new_height + 1; + + old_row = old_row_start; + int old_col = 0; + + int spare_rows = new_rows - final_blank_row; + + if(new_row_start < 0 && /* we'd fall off the top */ + spare_rows >= 0 && /* we actually have spare rows */ + (!active || new_cursor.row == -1 || (new_cursor.row - new_row_start) < new_rows)) + { + /* Attempt to scroll content down into the blank rows at the bottom to + * make it fit + */ + int downwards = -new_row_start; + if(downwards > spare_rows) + downwards = spare_rows; + int rowcount = new_rows - downwards; + +#ifdef DEBUG_REFLOW + fprintf(stderr, " scroll %d rows +%d downwards\n", rowcount, downwards); +#endif + + memmove(&new_buffer[downwards * new_cols], &new_buffer[0], rowcount * new_cols * sizeof(ScreenCell)); + memmove(&new_lineinfo[downwards], &new_lineinfo[0], rowcount * sizeof(new_lineinfo[0])); + + new_row += downwards; + new_row_start += downwards; + new_row_end += downwards; + + if(new_cursor.row >= 0) + new_cursor.row += downwards; + + final_blank_row += downwards; + } + +#ifdef DEBUG_REFLOW + fprintf(stderr, " rows [%d..%d] <- [%d..%d] width=%d\n", + new_row_start, new_row_end, old_row_start, old_row_end, width); +#endif + + if(new_row_start < 0) + break; + + for(new_row = new_row_start, old_row = old_row_start; new_row <= new_row_end; new_row++) { + int count = width >= new_cols ? new_cols : width; + width -= count; + + int new_col = 0; + + while(count) { + /* TODO: This could surely be done a lot faster by memcpy()'ing the entire range */ + new_buffer[new_row * new_cols + new_col] = old_buffer[old_row * old_cols + old_col]; + + if(old_cursor.row == old_row && old_cursor.col == old_col) + new_cursor.row = new_row, new_cursor.col = new_col; + + old_col++; + if(old_col == old_cols) { + old_row++; + + if(!REFLOW) { + new_col++; + break; + } + old_col = 0; + } + + new_col++; + count--; + } + + if(old_cursor.row == old_row && old_cursor.col >= old_col) { + new_cursor.row = new_row, new_cursor.col = (old_cursor.col - old_col + new_col); + if(new_cursor.col >= new_cols) + new_cursor.col = new_cols-1; + } + + while(new_col < new_cols) { + clearcell(screen, &new_buffer[new_row * new_cols + new_col]); + new_col++; + } + + new_lineinfo[new_row].continuation = (new_row > new_row_start); + } + + old_row = old_row_start - 1; + new_row = new_row_start - 1; + } + + if(old_cursor.row <= old_row) { + /* cursor would have moved entirely off the top of the screen; lets just + * bring it within range */ + new_cursor.row = 0, new_cursor.col = old_cursor.col; + if(new_cursor.col >= new_cols) + new_cursor.col = new_cols-1; + } + + /* We really expect the cursor position to be set by now */ + if(active && (new_cursor.row == -1 || new_cursor.col == -1)) { + fprintf(stderr, "screen_resize failed to update cursor position\n"); + abort(); + } + + if(old_row >= 0 && bufidx == BUFIDX_PRIMARY) { + /* Push spare lines to scrollback buffer */ + for(int row = 0; row <= old_row; row++) + sb_pushline_from_row(screen, row); + if(active) + statefields->pos.row -= (old_row + 1); + } + if(new_row >= 0 && bufidx == BUFIDX_PRIMARY && + screen->callbacks && screen->callbacks->sb_popline) { + /* Try to backfill rows by popping scrollback buffer */ + while(new_row >= 0) { + if(!(screen->callbacks->sb_popline(old_cols, screen->sb_buffer, screen->cbdata))) + break; + + VTermPos pos = { .row = new_row }; + for(pos.col = 0; pos.col < old_cols && pos.col < new_cols; pos.col += screen->sb_buffer[pos.col].width) { + VTermScreenCell *src = &screen->sb_buffer[pos.col]; + ScreenCell *dst = &new_buffer[pos.row * new_cols + pos.col]; + + for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL; i++) { + dst->chars[i] = src->chars[i]; + if(!src->chars[i]) + break; + } + + dst->pen.bold = src->attrs.bold; + dst->pen.underline = src->attrs.underline; + dst->pen.italic = src->attrs.italic; + dst->pen.blink = src->attrs.blink; + dst->pen.reverse = src->attrs.reverse ^ screen->global_reverse; + dst->pen.conceal = src->attrs.conceal; + dst->pen.strike = src->attrs.strike; + dst->pen.font = src->attrs.font; + dst->pen.small = src->attrs.small; + dst->pen.baseline = src->attrs.baseline; + + dst->pen.fg = src->fg; + dst->pen.bg = src->bg; + + if(src->width == 2 && pos.col < (new_cols-1)) + (dst + 1)->chars[0] = (uint32_t) -1; + } + for( ; pos.col < new_cols; pos.col++) + clearcell(screen, &new_buffer[pos.row * new_cols + pos.col]); + new_row--; + + if(active) + statefields->pos.row++; + } + } + if(new_row >= 0) { + /* Scroll new rows back up to the top and fill in blanks at the bottom */ + int moverows = new_rows - new_row - 1; + memmove(&new_buffer[0], &new_buffer[(new_row + 1) * new_cols], moverows * new_cols * sizeof(ScreenCell)); + memmove(&new_lineinfo[0], &new_lineinfo[new_row + 1], moverows * sizeof(new_lineinfo[0])); + + new_cursor.row -= (new_row + 1); + + for(new_row = moverows; new_row < new_rows; new_row++) { + for(int col = 0; col < new_cols; col++) + clearcell(screen, &new_buffer[new_row * new_cols + col]); + new_lineinfo[new_row] = (VTermLineInfo){ 0 }; + } + } + + vterm_allocator_free(screen->vt, old_buffer); + screen->buffers[bufidx] = new_buffer; + + vterm_allocator_free(screen->vt, old_lineinfo); + statefields->lineinfos[bufidx] = new_lineinfo; + + if(active) + statefields->pos = new_cursor; + + return; +} + +static int resize(int new_rows, int new_cols, VTermStateFields *fields, void *user) +{ + VTermScreen *screen = user; + + int altscreen_active = (screen->buffers[BUFIDX_ALTSCREEN] && screen->buffer == screen->buffers[BUFIDX_ALTSCREEN]); + + int old_rows = screen->rows; + int old_cols = screen->cols; + + if(new_cols > old_cols) { + /* Ensure that ->sb_buffer is large enough for a new or and old row */ + if(screen->sb_buffer) + vterm_allocator_free(screen->vt, screen->sb_buffer); + + screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols); + } + + resize_buffer(screen, 0, new_rows, new_cols, !altscreen_active, fields); + if(screen->buffers[BUFIDX_ALTSCREEN]) + resize_buffer(screen, 1, new_rows, new_cols, altscreen_active, fields); + else if(new_rows != old_rows) { + /* We don't need a full resize of the altscreen because it isn't enabled + * but we should at least keep the lineinfo the right size */ + vterm_allocator_free(screen->vt, fields->lineinfos[BUFIDX_ALTSCREEN]); + + VTermLineInfo *new_lineinfo = vterm_allocator_malloc(screen->vt, sizeof(new_lineinfo[0]) * new_rows); + for(int row = 0; row < new_rows; row++) + new_lineinfo[row] = (VTermLineInfo){ 0 }; + + fields->lineinfos[BUFIDX_ALTSCREEN] = new_lineinfo; + } + + screen->buffer = altscreen_active ? screen->buffers[BUFIDX_ALTSCREEN] : screen->buffers[BUFIDX_PRIMARY]; + + screen->rows = new_rows; + screen->cols = new_cols; + + if(new_cols <= old_cols) { + if(screen->sb_buffer) + vterm_allocator_free(screen->vt, screen->sb_buffer); + + screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols); + } + + /* TODO: Maaaaybe we can optimise this if there's no reflow happening */ + damagescreen(screen); + + if(screen->callbacks && screen->callbacks->resize) + return (*screen->callbacks->resize)(new_rows, new_cols, screen->cbdata); + + return 1; +} + +static int setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user) +{ + VTermScreen *screen = user; + + if(newinfo->doublewidth != oldinfo->doublewidth || + newinfo->doubleheight != oldinfo->doubleheight) { + for(int col = 0; col < screen->cols; col++) { + ScreenCell *cell = getcell(screen, row, col); + cell->pen.dwl = newinfo->doublewidth; + cell->pen.dhl = newinfo->doubleheight; + } + + VTermRect rect = { + .start_row = row, + .end_row = row + 1, + .start_col = 0, + .end_col = newinfo->doublewidth ? screen->cols / 2 : screen->cols, + }; + damagerect(screen, rect); + + if(newinfo->doublewidth) { + rect.start_col = screen->cols / 2; + rect.end_col = screen->cols; + + erase_internal(rect, 0, user); + } + } + + return 1; +} + +static int sb_clear(void *user) { + VTermScreen *screen = user; + + if(screen->callbacks && screen->callbacks->sb_clear) + if((*screen->callbacks->sb_clear)(screen->cbdata)) + return 1; + + return 0; +} + +static VTermStateCallbacks state_cbs = { + .putglyph = &putglyph, + .movecursor = &movecursor, + .scrollrect = &scrollrect, + .erase = &erase, + .setpenattr = &setpenattr, + .settermprop = &settermprop, + .bell = &bell, + .resize = &resize, + .setlineinfo = &setlineinfo, + .sb_clear = &sb_clear, +}; + +static VTermScreen *screen_new(VTerm *vt) +{ + VTermState *state = vterm_obtain_state(vt); + if(!state) + return NULL; + + VTermScreen *screen = vterm_allocator_malloc(vt, sizeof(VTermScreen)); + int rows, cols; + + vterm_get_size(vt, &rows, &cols); + + screen->vt = vt; + screen->state = state; + + screen->damage_merge = VTERM_DAMAGE_CELL; + screen->damaged.start_row = -1; + screen->pending_scrollrect.start_row = -1; + + screen->rows = rows; + screen->cols = cols; + + screen->global_reverse = false; + screen->reflow = false; + + screen->callbacks = NULL; + screen->cbdata = NULL; + + screen->buffers[BUFIDX_PRIMARY] = alloc_buffer(screen, rows, cols); + + screen->buffer = screen->buffers[BUFIDX_PRIMARY]; + + screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * cols); + + vterm_state_set_callbacks(screen->state, &state_cbs, screen); + + return screen; +} + +INTERNAL void vterm_screen_free(VTermScreen *screen) +{ + vterm_allocator_free(screen->vt, screen->buffers[BUFIDX_PRIMARY]); + if(screen->buffers[BUFIDX_ALTSCREEN]) + vterm_allocator_free(screen->vt, screen->buffers[BUFIDX_ALTSCREEN]); + + vterm_allocator_free(screen->vt, screen->sb_buffer); + + vterm_allocator_free(screen->vt, screen); +} + +void vterm_screen_reset(VTermScreen *screen, int hard) +{ + screen->damaged.start_row = -1; + screen->pending_scrollrect.start_row = -1; + vterm_state_reset(screen->state, hard); + vterm_screen_flush_damage(screen); +} + +static size_t _get_chars(const VTermScreen *screen, const int utf8, void *buffer, size_t len, const VTermRect rect) +{ + size_t outpos = 0; + int padding = 0; + +#define PUT(c) \ + if(utf8) { \ + size_t thislen = utf8_seqlen(c); \ + if(buffer && outpos + thislen <= len) \ + outpos += fill_utf8((c), (char *)buffer + outpos); \ + else \ + outpos += thislen; \ + } \ + else { \ + if(buffer && outpos + 1 <= len) \ + ((uint32_t*)buffer)[outpos++] = (c); \ + else \ + outpos++; \ + } + + for(int row = rect.start_row; row < rect.end_row; row++) { + for(int col = rect.start_col; col < rect.end_col; col++) { + ScreenCell *cell = getcell(screen, row, col); + + if(cell->chars[0] == 0) + // Erased cell, might need a space + padding++; + else if(cell->chars[0] == (uint32_t)-1) + // Gap behind a double-width char, do nothing + ; + else { + while(padding) { + PUT(UNICODE_SPACE); + padding--; + } + for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) { + PUT(cell->chars[i]); + } + } + } + + if(row < rect.end_row - 1) { + PUT(UNICODE_LINEFEED); + padding = 0; + } + } + + return outpos; +} + +size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect) +{ + return _get_chars(screen, 0, chars, len, rect); +} + +size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect) +{ + return _get_chars(screen, 1, str, len, rect); +} + +/* Copy internal to external representation of a screen cell */ +int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell) +{ + ScreenCell *intcell = getcell(screen, pos.row, pos.col); + if(!intcell) + return 0; + + for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL; i++) { + cell->chars[i] = intcell->chars[i]; + if(!intcell->chars[i]) + break; + } + + cell->attrs.bold = intcell->pen.bold; + cell->attrs.underline = intcell->pen.underline; + cell->attrs.italic = intcell->pen.italic; + cell->attrs.blink = intcell->pen.blink; + cell->attrs.reverse = intcell->pen.reverse ^ screen->global_reverse; + cell->attrs.conceal = intcell->pen.conceal; + cell->attrs.strike = intcell->pen.strike; + cell->attrs.font = intcell->pen.font; + cell->attrs.small = intcell->pen.small; + cell->attrs.baseline = intcell->pen.baseline; + + cell->attrs.dwl = intcell->pen.dwl; + cell->attrs.dhl = intcell->pen.dhl; + + cell->fg = intcell->pen.fg; + cell->bg = intcell->pen.bg; + + if(pos.col < (screen->cols - 1) && + getcell(screen, pos.row, pos.col + 1)->chars[0] == (uint32_t)-1) + cell->width = 2; + else + cell->width = 1; + + return 1; +} + +int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos) +{ + /* This cell is EOL if this and every cell to the right is black */ + for(; pos.col < screen->cols; pos.col++) { + ScreenCell *cell = getcell(screen, pos.row, pos.col); + if(cell->chars[0] != 0) + return 0; + } + + return 1; +} + +VTermScreen *vterm_obtain_screen(VTerm *vt) +{ + if(vt->screen) + return vt->screen; + + VTermScreen *screen = screen_new(vt); + vt->screen = screen; + + return screen; +} + +void vterm_screen_enable_reflow(VTermScreen *screen, bool reflow) +{ + screen->reflow = reflow; +} + +#undef vterm_screen_set_reflow +void vterm_screen_set_reflow(VTermScreen *screen, bool reflow) +{ + vterm_screen_enable_reflow(screen, reflow); +} + +void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen) +{ + if(!screen->buffers[BUFIDX_ALTSCREEN] && altscreen) { + int rows, cols; + vterm_get_size(screen->vt, &rows, &cols); + + screen->buffers[BUFIDX_ALTSCREEN] = alloc_buffer(screen, rows, cols); + } +} + +void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user) +{ + screen->callbacks = callbacks; + screen->cbdata = user; +} + +void *vterm_screen_get_cbdata(VTermScreen *screen) +{ + return screen->cbdata; +} + +void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermStateFallbacks *fallbacks, void *user) +{ + vterm_state_set_unrecognised_fallbacks(screen->state, fallbacks, user); +} + +void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen) +{ + return vterm_state_get_unrecognised_fbdata(screen->state); +} + +void vterm_screen_flush_damage(VTermScreen *screen) +{ + if(screen->pending_scrollrect.start_row != -1) { + vterm_scroll_rect(screen->pending_scrollrect, screen->pending_scroll_downward, screen->pending_scroll_rightward, + moverect_user, erase_user, screen); + + screen->pending_scrollrect.start_row = -1; + } + + if(screen->damaged.start_row != -1) { + if(screen->callbacks && screen->callbacks->damage) + (*screen->callbacks->damage)(screen->damaged, screen->cbdata); + + screen->damaged.start_row = -1; + } +} + +void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size) +{ + vterm_screen_flush_damage(screen); + screen->damage_merge = size; +} + +static int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b) +{ + if((attrs & VTERM_ATTR_BOLD_MASK) && (a->pen.bold != b->pen.bold)) + return 1; + if((attrs & VTERM_ATTR_UNDERLINE_MASK) && (a->pen.underline != b->pen.underline)) + return 1; + if((attrs & VTERM_ATTR_ITALIC_MASK) && (a->pen.italic != b->pen.italic)) + return 1; + if((attrs & VTERM_ATTR_BLINK_MASK) && (a->pen.blink != b->pen.blink)) + return 1; + if((attrs & VTERM_ATTR_REVERSE_MASK) && (a->pen.reverse != b->pen.reverse)) + return 1; + if((attrs & VTERM_ATTR_CONCEAL_MASK) && (a->pen.conceal != b->pen.conceal)) + return 1; + if((attrs & VTERM_ATTR_STRIKE_MASK) && (a->pen.strike != b->pen.strike)) + return 1; + if((attrs & VTERM_ATTR_FONT_MASK) && (a->pen.font != b->pen.font)) + return 1; + if((attrs & VTERM_ATTR_FOREGROUND_MASK) && !vterm_color_is_equal(&a->pen.fg, &b->pen.fg)) + return 1; + if((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_is_equal(&a->pen.bg, &b->pen.bg)) + return 1; + if((attrs & VTERM_ATTR_SMALL_MASK) && (a->pen.small != b->pen.small)) + return 1; + if((attrs & VTERM_ATTR_BASELINE_MASK) && (a->pen.baseline != b->pen.baseline)) + return 1; + + return 0; +} + +int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs) +{ + ScreenCell *target = getcell(screen, pos.row, pos.col); + + // TODO: bounds check + extent->start_row = pos.row; + extent->end_row = pos.row + 1; + + if(extent->start_col < 0) + extent->start_col = 0; + if(extent->end_col < 0) + extent->end_col = screen->cols; + + int col; + + for(col = pos.col - 1; col >= extent->start_col; col--) + if(attrs_differ(attrs, target, getcell(screen, pos.row, col))) + break; + extent->start_col = col + 1; + + for(col = pos.col + 1; col < extent->end_col; col++) + if(attrs_differ(attrs, target, getcell(screen, pos.row, col))) + break; + extent->end_col = col - 1; + + return 1; +} + +void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col) +{ + vterm_state_convert_color_to_rgb(screen->state, col); +} + +static void reset_default_colours(VTermScreen *screen, ScreenCell *buffer) +{ + for(int row = 0; row <= screen->rows - 1; row++) + for(int col = 0; col <= screen->cols - 1; col++) { + ScreenCell *cell = &buffer[row * screen->cols + col]; + if(VTERM_COLOR_IS_DEFAULT_FG(&cell->pen.fg)) + cell->pen.fg = screen->pen.fg; + if(VTERM_COLOR_IS_DEFAULT_BG(&cell->pen.bg)) + cell->pen.bg = screen->pen.bg; + } +} + +void vterm_screen_set_default_colors(VTermScreen *screen, const VTermColor *default_fg, const VTermColor *default_bg) +{ + vterm_state_set_default_colors(screen->state, default_fg, default_bg); + + if(default_fg && VTERM_COLOR_IS_DEFAULT_FG(&screen->pen.fg)) { + screen->pen.fg = *default_fg; + screen->pen.fg.type = (screen->pen.fg.type & ~VTERM_COLOR_DEFAULT_MASK) + | VTERM_COLOR_DEFAULT_FG; + } + + if(default_bg && VTERM_COLOR_IS_DEFAULT_BG(&screen->pen.bg)) { + screen->pen.bg = *default_bg; + screen->pen.bg.type = (screen->pen.bg.type & ~VTERM_COLOR_DEFAULT_MASK) + | VTERM_COLOR_DEFAULT_BG; + } + + reset_default_colours(screen, screen->buffers[0]); + if(screen->buffers[1]) + reset_default_colours(screen, screen->buffers[1]); +} diff --git a/src/libs/3rdparty/libvterm/src/state.c b/src/libs/3rdparty/libvterm/src/state.c new file mode 100644 index 0000000000..b22fba706b --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/state.c @@ -0,0 +1,2315 @@ +#include "vterm_internal.h" + +#include +#include + +#define strneq(a,b,n) (strncmp(a,b,n)==0) + +#if defined(DEBUG) && DEBUG > 1 +# define DEBUG_GLYPH_COMBINE +#endif + +/* Some convenient wrappers to make callback functions easier */ + +static void putglyph(VTermState *state, const uint32_t chars[], int width, VTermPos pos) +{ + VTermGlyphInfo info = { + .chars = chars, + .width = width, + .protected_cell = state->protected_cell, + .dwl = state->lineinfo[pos.row].doublewidth, + .dhl = state->lineinfo[pos.row].doubleheight, + }; + + if(state->callbacks && state->callbacks->putglyph) + if((*state->callbacks->putglyph)(&info, pos, state->cbdata)) + return; + + DEBUG_LOG("libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row); +} + +static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom) +{ + if(state->pos.col == oldpos->col && state->pos.row == oldpos->row) + return; + + if(cancel_phantom) + state->at_phantom = 0; + + if(state->callbacks && state->callbacks->movecursor) + if((*state->callbacks->movecursor)(state->pos, *oldpos, state->mode.cursor_visible, state->cbdata)) + return; +} + +static void erase(VTermState *state, VTermRect rect, int selective) +{ + if(rect.end_col == state->cols) { + /* If we're erasing the final cells of any lines, cancel the continuation + * marker on the subsequent line + */ + for(int row = rect.start_row + 1; row < rect.end_row + 1 && row < state->rows; row++) + state->lineinfo[row].continuation = 0; + } + + if(state->callbacks && state->callbacks->erase) + if((*state->callbacks->erase)(rect, selective, state->cbdata)) + return; +} + +static VTermState *vterm_state_new(VTerm *vt) +{ + VTermState *state = vterm_allocator_malloc(vt, sizeof(VTermState)); + + state->vt = vt; + + state->rows = vt->rows; + state->cols = vt->cols; + + state->mouse_col = 0; + state->mouse_row = 0; + state->mouse_buttons = 0; + + state->mouse_protocol = MOUSE_X10; + + state->callbacks = NULL; + state->cbdata = NULL; + + state->selection.callbacks = NULL; + state->selection.user = NULL; + state->selection.buffer = NULL; + + vterm_state_newpen(state); + + state->bold_is_highbright = 0; + + state->combine_chars_size = 16; + state->combine_chars = vterm_allocator_malloc(state->vt, state->combine_chars_size * sizeof(state->combine_chars[0])); + + state->tabstops = vterm_allocator_malloc(state->vt, (state->cols + 7) / 8); + + state->lineinfos[BUFIDX_PRIMARY] = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo)); + /* TODO: Make an 'enable' function */ + state->lineinfos[BUFIDX_ALTSCREEN] = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo)); + state->lineinfo = state->lineinfos[BUFIDX_PRIMARY]; + + state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u'); + if(*state->encoding_utf8.enc->init) + (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data); + + return state; +} + +INTERNAL void vterm_state_free(VTermState *state) +{ + vterm_allocator_free(state->vt, state->tabstops); + vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_PRIMARY]); + if(state->lineinfos[BUFIDX_ALTSCREEN]) + vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_ALTSCREEN]); + vterm_allocator_free(state->vt, state->combine_chars); + vterm_allocator_free(state->vt, state); +} + +static void scroll(VTermState *state, VTermRect rect, int downward, int rightward) +{ + if(!downward && !rightward) + return; + + int rows = rect.end_row - rect.start_row; + if(downward > rows) + downward = rows; + else if(downward < -rows) + downward = -rows; + + int cols = rect.end_col - rect.start_col; + if(rightward > cols) + rightward = cols; + else if(rightward < -cols) + rightward = -cols; + + // Update lineinfo if full line + if(rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) { + int height = rect.end_row - rect.start_row - abs(downward); + + if(downward > 0) { + memmove(state->lineinfo + rect.start_row, + state->lineinfo + rect.start_row + downward, + height * sizeof(state->lineinfo[0])); + for(int row = rect.end_row - downward; row < rect.end_row; row++) + state->lineinfo[row] = (VTermLineInfo){ 0 }; + } + else { + memmove(state->lineinfo + rect.start_row - downward, + state->lineinfo + rect.start_row, + height * sizeof(state->lineinfo[0])); + for(int row = rect.start_row; row < rect.start_row - downward; row++) + state->lineinfo[row] = (VTermLineInfo){ 0 }; + } + } + + if(state->callbacks && state->callbacks->scrollrect) + if((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata)) + return; + + if(state->callbacks) + vterm_scroll_rect(rect, downward, rightward, + state->callbacks->moverect, state->callbacks->erase, state->cbdata); +} + +static void linefeed(VTermState *state) +{ + if(state->pos.row == SCROLLREGION_BOTTOM(state) - 1) { + VTermRect rect = { + .start_row = state->scrollregion_top, + .end_row = SCROLLREGION_BOTTOM(state), + .start_col = SCROLLREGION_LEFT(state), + .end_col = SCROLLREGION_RIGHT(state), + }; + + scroll(state, rect, 1, 0); + } + else if(state->pos.row < state->rows-1) + state->pos.row++; +} + +static void grow_combine_buffer(VTermState *state) +{ + size_t new_size = state->combine_chars_size * 2; + uint32_t *new_chars = vterm_allocator_malloc(state->vt, new_size * sizeof(new_chars[0])); + + memcpy(new_chars, state->combine_chars, state->combine_chars_size * sizeof(new_chars[0])); + + vterm_allocator_free(state->vt, state->combine_chars); + + state->combine_chars = new_chars; + state->combine_chars_size = new_size; +} + +static void set_col_tabstop(VTermState *state, int col) +{ + unsigned char mask = 1 << (col & 7); + state->tabstops[col >> 3] |= mask; +} + +static void clear_col_tabstop(VTermState *state, int col) +{ + unsigned char mask = 1 << (col & 7); + state->tabstops[col >> 3] &= ~mask; +} + +static int is_col_tabstop(VTermState *state, int col) +{ + unsigned char mask = 1 << (col & 7); + return state->tabstops[col >> 3] & mask; +} + +static int is_cursor_in_scrollregion(const VTermState *state) +{ + if(state->pos.row < state->scrollregion_top || + state->pos.row >= SCROLLREGION_BOTTOM(state)) + return 0; + if(state->pos.col < SCROLLREGION_LEFT(state) || + state->pos.col >= SCROLLREGION_RIGHT(state)) + return 0; + + return 1; +} + +static void tab(VTermState *state, int count, int direction) +{ + while(count > 0) { + if(direction > 0) { + if(state->pos.col >= THISROWWIDTH(state)-1) + return; + + state->pos.col++; + } + else if(direction < 0) { + if(state->pos.col < 1) + return; + + state->pos.col--; + } + + if(is_col_tabstop(state, state->pos.col)) + count--; + } +} + +#define NO_FORCE 0 +#define FORCE 1 + +#define DWL_OFF 0 +#define DWL_ON 1 + +#define DHL_OFF 0 +#define DHL_TOP 1 +#define DHL_BOTTOM 2 + +static void set_lineinfo(VTermState *state, int row, int force, int dwl, int dhl) +{ + VTermLineInfo info = state->lineinfo[row]; + + if(dwl == DWL_OFF) + info.doublewidth = DWL_OFF; + else if(dwl == DWL_ON) + info.doublewidth = DWL_ON; + // else -1 to ignore + + if(dhl == DHL_OFF) + info.doubleheight = DHL_OFF; + else if(dhl == DHL_TOP) + info.doubleheight = DHL_TOP; + else if(dhl == DHL_BOTTOM) + info.doubleheight = DHL_BOTTOM; + + if((state->callbacks && + state->callbacks->setlineinfo && + (*state->callbacks->setlineinfo)(row, &info, state->lineinfo + row, state->cbdata)) + || force) + state->lineinfo[row] = info; +} + +static int on_text(const char bytes[], size_t len, void *user) +{ + VTermState *state = user; + + VTermPos oldpos = state->pos; + + uint32_t *codepoints = (uint32_t *)(state->vt->tmpbuffer); + size_t maxpoints = (state->vt->tmpbuffer_len) / sizeof(uint32_t); + + int npoints = 0; + size_t eaten = 0; + + VTermEncodingInstance *encoding = + state->gsingle_set ? &state->encoding[state->gsingle_set] : + !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set] : + state->vt->mode.utf8 ? &state->encoding_utf8 : + &state->encoding[state->gr_set]; + + (*encoding->enc->decode)(encoding->enc, encoding->data, + codepoints, &npoints, state->gsingle_set ? 1 : maxpoints, + bytes, &eaten, len); + + /* There's a chance an encoding (e.g. UTF-8) hasn't found enough bytes yet + * for even a single codepoint + */ + if(!npoints) + return eaten; + + if(state->gsingle_set && npoints) + state->gsingle_set = 0; + + int i = 0; + + /* This is a combining char. that needs to be merged with the previous + * glyph output */ + if(vterm_unicode_is_combining(codepoints[i])) { + /* See if the cursor has moved since */ + if(state->pos.row == state->combine_pos.row && state->pos.col == state->combine_pos.col + state->combine_width) { +#ifdef DEBUG_GLYPH_COMBINE + int printpos; + printf("DEBUG: COMBINING SPLIT GLYPH of chars {"); + for(printpos = 0; state->combine_chars[printpos]; printpos++) + printf("U+%04x ", state->combine_chars[printpos]); + printf("} + {"); +#endif + + /* Find where we need to append these combining chars */ + int saved_i = 0; + while(state->combine_chars[saved_i]) + saved_i++; + + /* Add extra ones */ + while(i < npoints && vterm_unicode_is_combining(codepoints[i])) { + if(saved_i >= state->combine_chars_size) + grow_combine_buffer(state); + state->combine_chars[saved_i++] = codepoints[i++]; + } + if(saved_i >= state->combine_chars_size) + grow_combine_buffer(state); + state->combine_chars[saved_i] = 0; + +#ifdef DEBUG_GLYPH_COMBINE + for(; state->combine_chars[printpos]; printpos++) + printf("U+%04x ", state->combine_chars[printpos]); + printf("}\n"); +#endif + + /* Now render it */ + putglyph(state, state->combine_chars, state->combine_width, state->combine_pos); + } + else { + DEBUG_LOG("libvterm: TODO: Skip over split char+combining\n"); + } + } + + for(; i < npoints; i++) { + // Try to find combining characters following this + int glyph_starts = i; + int glyph_ends; + for(glyph_ends = i + 1; + (glyph_ends < npoints) && (glyph_ends < glyph_starts + VTERM_MAX_CHARS_PER_CELL); + glyph_ends++) + if(!vterm_unicode_is_combining(codepoints[glyph_ends])) + break; + + int width = 0; + + uint32_t chars[VTERM_MAX_CHARS_PER_CELL + 1]; + + for( ; i < glyph_ends; i++) { + chars[i - glyph_starts] = codepoints[i]; + int this_width = vterm_unicode_width(codepoints[i]); +#ifdef DEBUG + if(this_width < 0) { + fprintf(stderr, "Text with negative-width codepoint U+%04x\n", codepoints[i]); + abort(); + } +#endif + width += this_width; + } + + while(i < npoints && vterm_unicode_is_combining(codepoints[i])) + i++; + + chars[glyph_ends - glyph_starts] = 0; + i--; + +#ifdef DEBUG_GLYPH_COMBINE + int printpos; + printf("DEBUG: COMBINED GLYPH of %d chars {", glyph_ends - glyph_starts); + for(printpos = 0; printpos < glyph_ends - glyph_starts; printpos++) + printf("U+%04x ", chars[printpos]); + printf("}, onscreen width %d\n", width); +#endif + + if(state->at_phantom || state->pos.col + width > THISROWWIDTH(state)) { + linefeed(state); + state->pos.col = 0; + state->at_phantom = 0; + state->lineinfo[state->pos.row].continuation = 1; + } + + if(state->mode.insert) { + /* TODO: This will be a little inefficient for large bodies of text, as + * it'll have to 'ICH' effectively before every glyph. We should scan + * ahead and ICH as many times as required + */ + VTermRect rect = { + .start_row = state->pos.row, + .end_row = state->pos.row + 1, + .start_col = state->pos.col, + .end_col = THISROWWIDTH(state), + }; + scroll(state, rect, 0, -1); + } + + putglyph(state, chars, width, state->pos); + + if(i == npoints - 1) { + /* End of the buffer. Save the chars in case we have to combine with + * more on the next call */ + int save_i; + for(save_i = 0; chars[save_i]; save_i++) { + if(save_i >= state->combine_chars_size) + grow_combine_buffer(state); + state->combine_chars[save_i] = chars[save_i]; + } + if(save_i >= state->combine_chars_size) + grow_combine_buffer(state); + state->combine_chars[save_i] = 0; + state->combine_width = width; + state->combine_pos = state->pos; + } + + if(state->pos.col + width >= THISROWWIDTH(state)) { + if(state->mode.autowrap) + state->at_phantom = 1; + } + else { + state->pos.col += width; + } + } + + updatecursor(state, &oldpos, 0); + +#ifdef DEBUG + if(state->pos.row < 0 || state->pos.row >= state->rows || + state->pos.col < 0 || state->pos.col >= state->cols) { + fprintf(stderr, "Position out of bounds after text: (%d,%d)\n", + state->pos.row, state->pos.col); + abort(); + } +#endif + + return eaten; +} + +static int on_control(unsigned char control, void *user) +{ + VTermState *state = user; + + VTermPos oldpos = state->pos; + + switch(control) { + case 0x07: // BEL - ECMA-48 8.3.3 + if(state->callbacks && state->callbacks->bell) + (*state->callbacks->bell)(state->cbdata); + break; + + case 0x08: // BS - ECMA-48 8.3.5 + if(state->pos.col > 0) + state->pos.col--; + break; + + case 0x09: // HT - ECMA-48 8.3.60 + tab(state, 1, +1); + break; + + case 0x0a: // LF - ECMA-48 8.3.74 + case 0x0b: // VT + case 0x0c: // FF + linefeed(state); + if(state->mode.newline) + state->pos.col = 0; + break; + + case 0x0d: // CR - ECMA-48 8.3.15 + state->pos.col = 0; + break; + + case 0x0e: // LS1 - ECMA-48 8.3.76 + state->gl_set = 1; + break; + + case 0x0f: // LS0 - ECMA-48 8.3.75 + state->gl_set = 0; + break; + + case 0x84: // IND - DEPRECATED but implemented for completeness + linefeed(state); + break; + + case 0x85: // NEL - ECMA-48 8.3.86 + linefeed(state); + state->pos.col = 0; + break; + + case 0x88: // HTS - ECMA-48 8.3.62 + set_col_tabstop(state, state->pos.col); + break; + + case 0x8d: // RI - ECMA-48 8.3.104 + if(state->pos.row == state->scrollregion_top) { + VTermRect rect = { + .start_row = state->scrollregion_top, + .end_row = SCROLLREGION_BOTTOM(state), + .start_col = SCROLLREGION_LEFT(state), + .end_col = SCROLLREGION_RIGHT(state), + }; + + scroll(state, rect, -1, 0); + } + else if(state->pos.row > 0) + state->pos.row--; + break; + + case 0x8e: // SS2 - ECMA-48 8.3.141 + state->gsingle_set = 2; + break; + + case 0x8f: // SS3 - ECMA-48 8.3.142 + state->gsingle_set = 3; + break; + + default: + if(state->fallbacks && state->fallbacks->control) + if((*state->fallbacks->control)(control, state->fbdata)) + return 1; + + return 0; + } + + updatecursor(state, &oldpos, 1); + +#ifdef DEBUG + if(state->pos.row < 0 || state->pos.row >= state->rows || + state->pos.col < 0 || state->pos.col >= state->cols) { + fprintf(stderr, "Position out of bounds after Ctrl %02x: (%d,%d)\n", + control, state->pos.row, state->pos.col); + abort(); + } +#endif + + return 1; +} + +static int settermprop_bool(VTermState *state, VTermProp prop, int v) +{ + VTermValue val = { .boolean = v }; + return vterm_state_set_termprop(state, prop, &val); +} + +static int settermprop_int(VTermState *state, VTermProp prop, int v) +{ + VTermValue val = { .number = v }; + return vterm_state_set_termprop(state, prop, &val); +} + +static int settermprop_string(VTermState *state, VTermProp prop, VTermStringFragment frag) +{ + VTermValue val = { .string = frag }; + return vterm_state_set_termprop(state, prop, &val); +} + +static void savecursor(VTermState *state, int save) +{ + if(save) { + state->saved.pos = state->pos; + state->saved.mode.cursor_visible = state->mode.cursor_visible; + state->saved.mode.cursor_blink = state->mode.cursor_blink; + state->saved.mode.cursor_shape = state->mode.cursor_shape; + + vterm_state_savepen(state, 1); + } + else { + VTermPos oldpos = state->pos; + + state->pos = state->saved.pos; + + settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->saved.mode.cursor_visible); + settermprop_bool(state, VTERM_PROP_CURSORBLINK, state->saved.mode.cursor_blink); + settermprop_int (state, VTERM_PROP_CURSORSHAPE, state->saved.mode.cursor_shape); + + vterm_state_savepen(state, 0); + + updatecursor(state, &oldpos, 1); + } +} + +static int on_escape(const char *bytes, size_t len, void *user) +{ + VTermState *state = user; + + /* Easier to decode this from the first byte, even though the final + * byte terminates it + */ + switch(bytes[0]) { + case ' ': + if(len != 2) + return 0; + + switch(bytes[1]) { + case 'F': // S7C1T + state->vt->mode.ctrl8bit = 0; + break; + + case 'G': // S8C1T + state->vt->mode.ctrl8bit = 1; + break; + + default: + return 0; + } + return 2; + + case '#': + if(len != 2) + return 0; + + switch(bytes[1]) { + case '3': // DECDHL top + if(state->mode.leftrightmargin) + break; + set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_TOP); + break; + + case '4': // DECDHL bottom + if(state->mode.leftrightmargin) + break; + set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_BOTTOM); + break; + + case '5': // DECSWL + if(state->mode.leftrightmargin) + break; + set_lineinfo(state, state->pos.row, NO_FORCE, DWL_OFF, DHL_OFF); + break; + + case '6': // DECDWL + if(state->mode.leftrightmargin) + break; + set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_OFF); + break; + + case '8': // DECALN + { + VTermPos pos; + uint32_t E[] = { 'E', 0 }; + for(pos.row = 0; pos.row < state->rows; pos.row++) + for(pos.col = 0; pos.col < ROWWIDTH(state, pos.row); pos.col++) + putglyph(state, E, 1, pos); + break; + } + + default: + return 0; + } + return 2; + + case '(': case ')': case '*': case '+': // SCS + if(len != 2) + return 0; + + { + int setnum = bytes[0] - 0x28; + VTermEncoding *newenc = vterm_lookup_encoding(ENC_SINGLE_94, bytes[1]); + + if(newenc) { + state->encoding[setnum].enc = newenc; + + if(newenc->init) + (*newenc->init)(newenc, state->encoding[setnum].data); + } + } + + return 2; + + case '7': // DECSC + savecursor(state, 1); + return 1; + + case '8': // DECRC + savecursor(state, 0); + return 1; + + case '<': // Ignored by VT100. Used in VT52 mode to switch up to VT100 + return 1; + + case '=': // DECKPAM + state->mode.keypad = 1; + return 1; + + case '>': // DECKPNM + state->mode.keypad = 0; + return 1; + + case 'c': // RIS - ECMA-48 8.3.105 + { + VTermPos oldpos = state->pos; + vterm_state_reset(state, 1); + if(state->callbacks && state->callbacks->movecursor) + (*state->callbacks->movecursor)(state->pos, oldpos, state->mode.cursor_visible, state->cbdata); + return 1; + } + + case 'n': // LS2 - ECMA-48 8.3.78 + state->gl_set = 2; + return 1; + + case 'o': // LS3 - ECMA-48 8.3.80 + state->gl_set = 3; + return 1; + + case '~': // LS1R - ECMA-48 8.3.77 + state->gr_set = 1; + return 1; + + case '}': // LS2R - ECMA-48 8.3.79 + state->gr_set = 2; + return 1; + + case '|': // LS3R - ECMA-48 8.3.81 + state->gr_set = 3; + return 1; + + default: + return 0; + } +} + +static void set_mode(VTermState *state, int num, int val) +{ + switch(num) { + case 4: // IRM - ECMA-48 7.2.10 + state->mode.insert = val; + break; + + case 20: // LNM - ANSI X3.4-1977 + state->mode.newline = val; + break; + + default: + DEBUG_LOG("libvterm: Unknown mode %d\n", num); + return; + } +} + +static void set_dec_mode(VTermState *state, int num, int val) +{ + switch(num) { + case 1: + state->mode.cursor = val; + break; + + case 5: // DECSCNM - screen mode + settermprop_bool(state, VTERM_PROP_REVERSE, val); + break; + + case 6: // DECOM - origin mode + { + VTermPos oldpos = state->pos; + state->mode.origin = val; + state->pos.row = state->mode.origin ? state->scrollregion_top : 0; + state->pos.col = state->mode.origin ? SCROLLREGION_LEFT(state) : 0; + updatecursor(state, &oldpos, 1); + } + break; + + case 7: + state->mode.autowrap = val; + break; + + case 12: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, val); + break; + + case 25: + settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, val); + break; + + case 69: // DECVSSM - vertical split screen mode + // DECLRMM - left/right margin mode + state->mode.leftrightmargin = val; + if(val) { + // Setting DECVSSM must clear doublewidth/doubleheight state of every line + for(int row = 0; row < state->rows; row++) + set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); + } + + break; + + case 1000: + case 1002: + case 1003: + settermprop_int(state, VTERM_PROP_MOUSE, + !val ? VTERM_PROP_MOUSE_NONE : + (num == 1000) ? VTERM_PROP_MOUSE_CLICK : + (num == 1002) ? VTERM_PROP_MOUSE_DRAG : + VTERM_PROP_MOUSE_MOVE); + break; + + case 1004: + state->mode.report_focus = val; + break; + + case 1005: + state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10; + break; + + case 1006: + state->mouse_protocol = val ? MOUSE_SGR : MOUSE_X10; + break; + + case 1015: + state->mouse_protocol = val ? MOUSE_RXVT : MOUSE_X10; + break; + + case 1047: + settermprop_bool(state, VTERM_PROP_ALTSCREEN, val); + break; + + case 1048: + savecursor(state, val); + break; + + case 1049: + settermprop_bool(state, VTERM_PROP_ALTSCREEN, val); + savecursor(state, val); + break; + + case 2004: + state->mode.bracketpaste = val; + break; + + default: + DEBUG_LOG("libvterm: Unknown DEC mode %d\n", num); + return; + } +} + +static void request_dec_mode(VTermState *state, int num) +{ + int reply; + + switch(num) { + case 1: + reply = state->mode.cursor; + break; + + case 5: + reply = state->mode.screen; + break; + + case 6: + reply = state->mode.origin; + break; + + case 7: + reply = state->mode.autowrap; + break; + + case 12: + reply = state->mode.cursor_blink; + break; + + case 25: + reply = state->mode.cursor_visible; + break; + + case 69: + reply = state->mode.leftrightmargin; + break; + + case 1000: + reply = state->mouse_flags == MOUSE_WANT_CLICK; + break; + + case 1002: + reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_DRAG); + break; + + case 1003: + reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_MOVE); + break; + + case 1004: + reply = state->mode.report_focus; + break; + + case 1005: + reply = state->mouse_protocol == MOUSE_UTF8; + break; + + case 1006: + reply = state->mouse_protocol == MOUSE_SGR; + break; + + case 1015: + reply = state->mouse_protocol == MOUSE_RXVT; + break; + + case 1047: + reply = state->mode.alt_screen; + break; + + case 2004: + reply = state->mode.bracketpaste; + break; + + default: + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, 0); + return; + } + + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, reply ? 1 : 2); +} + +static void request_version_string(VTermState *state) +{ + vterm_push_output_sprintf_str(state->vt, C1_DCS, true, ">|libvterm(%d.%d)", + VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR); +} + +static int on_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user) +{ + VTermState *state = user; + int leader_byte = 0; + int intermed_byte = 0; + int cancel_phantom = 1; + + if(leader && leader[0]) { + if(leader[1]) // longer than 1 char + return 0; + + switch(leader[0]) { + case '?': + case '>': + leader_byte = leader[0]; + break; + default: + return 0; + } + } + + if(intermed && intermed[0]) { + if(intermed[1]) // longer than 1 char + return 0; + + switch(intermed[0]) { + case ' ': + case '"': + case '$': + case '\'': + intermed_byte = intermed[0]; + break; + default: + return 0; + } + } + + VTermPos oldpos = state->pos; + + // Some temporaries for later code + int count, val; + int row, col; + VTermRect rect; + int selective; + +#define LBOUND(v,min) if((v) < (min)) (v) = (min) +#define UBOUND(v,max) if((v) > (max)) (v) = (max) + +#define LEADER(l,b) ((l << 8) | b) +#define INTERMED(i,b) ((i << 16) | b) + + switch(intermed_byte << 16 | leader_byte << 8 | command) { + case 0x40: // ICH - ECMA-48 8.3.64 + count = CSI_ARG_COUNT(args[0]); + + if(!is_cursor_in_scrollregion(state)) + break; + + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; + if(state->mode.leftrightmargin) + rect.end_col = SCROLLREGION_RIGHT(state); + else + rect.end_col = THISROWWIDTH(state); + + scroll(state, rect, 0, -count); + + break; + + case 0x41: // CUU - ECMA-48 8.3.22 + count = CSI_ARG_COUNT(args[0]); + state->pos.row -= count; + state->at_phantom = 0; + break; + + case 0x42: // CUD - ECMA-48 8.3.19 + count = CSI_ARG_COUNT(args[0]); + state->pos.row += count; + state->at_phantom = 0; + break; + + case 0x43: // CUF - ECMA-48 8.3.20 + count = CSI_ARG_COUNT(args[0]); + state->pos.col += count; + state->at_phantom = 0; + break; + + case 0x44: // CUB - ECMA-48 8.3.18 + count = CSI_ARG_COUNT(args[0]); + state->pos.col -= count; + state->at_phantom = 0; + break; + + case 0x45: // CNL - ECMA-48 8.3.12 + count = CSI_ARG_COUNT(args[0]); + state->pos.col = 0; + state->pos.row += count; + state->at_phantom = 0; + break; + + case 0x46: // CPL - ECMA-48 8.3.13 + count = CSI_ARG_COUNT(args[0]); + state->pos.col = 0; + state->pos.row -= count; + state->at_phantom = 0; + break; + + case 0x47: // CHA - ECMA-48 8.3.9 + val = CSI_ARG_OR(args[0], 1); + state->pos.col = val-1; + state->at_phantom = 0; + break; + + case 0x48: // CUP - ECMA-48 8.3.21 + row = CSI_ARG_OR(args[0], 1); + col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); + // zero-based + state->pos.row = row-1; + state->pos.col = col-1; + if(state->mode.origin) { + state->pos.row += state->scrollregion_top; + state->pos.col += SCROLLREGION_LEFT(state); + } + state->at_phantom = 0; + break; + + case 0x49: // CHT - ECMA-48 8.3.10 + count = CSI_ARG_COUNT(args[0]); + tab(state, count, +1); + break; + + case 0x4a: // ED - ECMA-48 8.3.39 + case LEADER('?', 0x4a): // DECSED - Selective Erase in Display + selective = (leader_byte == '?'); + switch(CSI_ARG(args[0])) { + case CSI_ARG_MISSING: + case 0: + rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; rect.end_col = state->cols; + if(rect.end_col > rect.start_col) + erase(state, rect, selective); + + rect.start_row = state->pos.row + 1; rect.end_row = state->rows; + rect.start_col = 0; + for(int row = rect.start_row; row < rect.end_row; row++) + set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); + if(rect.end_row > rect.start_row) + erase(state, rect, selective); + break; + + case 1: + rect.start_row = 0; rect.end_row = state->pos.row; + rect.start_col = 0; rect.end_col = state->cols; + for(int row = rect.start_row; row < rect.end_row; row++) + set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); + if(rect.end_col > rect.start_col) + erase(state, rect, selective); + + rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; + rect.end_col = state->pos.col + 1; + if(rect.end_row > rect.start_row) + erase(state, rect, selective); + break; + + case 2: + rect.start_row = 0; rect.end_row = state->rows; + rect.start_col = 0; rect.end_col = state->cols; + for(int row = rect.start_row; row < rect.end_row; row++) + set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); + erase(state, rect, selective); + break; + + case 3: + if(state->callbacks && state->callbacks->sb_clear) + if((*state->callbacks->sb_clear)(state->cbdata)) + return 1; + break; + } + break; + + case 0x4b: // EL - ECMA-48 8.3.41 + case LEADER('?', 0x4b): // DECSEL - Selective Erase in Line + selective = (leader_byte == '?'); + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + + switch(CSI_ARG(args[0])) { + case CSI_ARG_MISSING: + case 0: + rect.start_col = state->pos.col; rect.end_col = THISROWWIDTH(state); break; + case 1: + rect.start_col = 0; rect.end_col = state->pos.col + 1; break; + case 2: + rect.start_col = 0; rect.end_col = THISROWWIDTH(state); break; + default: + return 0; + } + + if(rect.end_col > rect.start_col) + erase(state, rect, selective); + + break; + + case 0x4c: // IL - ECMA-48 8.3.67 + count = CSI_ARG_COUNT(args[0]); + + if(!is_cursor_in_scrollregion(state)) + break; + + rect.start_row = state->pos.row; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = SCROLLREGION_LEFT(state); + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, -count, 0); + + break; + + case 0x4d: // DL - ECMA-48 8.3.32 + count = CSI_ARG_COUNT(args[0]); + + if(!is_cursor_in_scrollregion(state)) + break; + + rect.start_row = state->pos.row; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = SCROLLREGION_LEFT(state); + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, count, 0); + + break; + + case 0x50: // DCH - ECMA-48 8.3.26 + count = CSI_ARG_COUNT(args[0]); + + if(!is_cursor_in_scrollregion(state)) + break; + + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; + if(state->mode.leftrightmargin) + rect.end_col = SCROLLREGION_RIGHT(state); + else + rect.end_col = THISROWWIDTH(state); + + scroll(state, rect, 0, count); + + break; + + case 0x53: // SU - ECMA-48 8.3.147 + count = CSI_ARG_COUNT(args[0]); + + rect.start_row = state->scrollregion_top; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = SCROLLREGION_LEFT(state); + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, count, 0); + + break; + + case 0x54: // SD - ECMA-48 8.3.113 + count = CSI_ARG_COUNT(args[0]); + + rect.start_row = state->scrollregion_top; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = SCROLLREGION_LEFT(state); + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, -count, 0); + + break; + + case 0x58: // ECH - ECMA-48 8.3.38 + count = CSI_ARG_COUNT(args[0]); + + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; + rect.end_col = state->pos.col + count; + UBOUND(rect.end_col, THISROWWIDTH(state)); + + erase(state, rect, 0); + break; + + case 0x5a: // CBT - ECMA-48 8.3.7 + count = CSI_ARG_COUNT(args[0]); + tab(state, count, -1); + break; + + case 0x60: // HPA - ECMA-48 8.3.57 + col = CSI_ARG_OR(args[0], 1); + state->pos.col = col-1; + state->at_phantom = 0; + break; + + case 0x61: // HPR - ECMA-48 8.3.59 + count = CSI_ARG_COUNT(args[0]); + state->pos.col += count; + state->at_phantom = 0; + break; + + case 0x62: { // REP - ECMA-48 8.3.103 + const int row_width = THISROWWIDTH(state); + count = CSI_ARG_COUNT(args[0]); + col = state->pos.col + count; + UBOUND(col, row_width); + while (state->pos.col < col) { + putglyph(state, state->combine_chars, state->combine_width, state->pos); + state->pos.col += state->combine_width; + } + if (state->pos.col + state->combine_width >= row_width) { + if (state->mode.autowrap) { + state->at_phantom = 1; + cancel_phantom = 0; + } + } + break; + } + + case 0x63: // DA - ECMA-48 8.3.24 + val = CSI_ARG_OR(args[0], 0); + if(val == 0) + // DEC VT100 response + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?1;2c"); + break; + + case LEADER('>', 0x63): // DEC secondary Device Attributes + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, ">%d;%d;%dc", 0, 100, 0); + break; + + case 0x64: // VPA - ECMA-48 8.3.158 + row = CSI_ARG_OR(args[0], 1); + state->pos.row = row-1; + if(state->mode.origin) + state->pos.row += state->scrollregion_top; + state->at_phantom = 0; + break; + + case 0x65: // VPR - ECMA-48 8.3.160 + count = CSI_ARG_COUNT(args[0]); + state->pos.row += count; + state->at_phantom = 0; + break; + + case 0x66: // HVP - ECMA-48 8.3.63 + row = CSI_ARG_OR(args[0], 1); + col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); + // zero-based + state->pos.row = row-1; + state->pos.col = col-1; + if(state->mode.origin) { + state->pos.row += state->scrollregion_top; + state->pos.col += SCROLLREGION_LEFT(state); + } + state->at_phantom = 0; + break; + + case 0x67: // TBC - ECMA-48 8.3.154 + val = CSI_ARG_OR(args[0], 0); + + switch(val) { + case 0: + clear_col_tabstop(state, state->pos.col); + break; + case 3: + case 5: + for(col = 0; col < state->cols; col++) + clear_col_tabstop(state, col); + break; + case 1: + case 2: + case 4: + break; + /* TODO: 1, 2 and 4 aren't meaningful yet without line tab stops */ + default: + return 0; + } + break; + + case 0x68: // SM - ECMA-48 8.3.125 + if(!CSI_ARG_IS_MISSING(args[0])) + set_mode(state, CSI_ARG(args[0]), 1); + break; + + case LEADER('?', 0x68): // DEC private mode set + if(!CSI_ARG_IS_MISSING(args[0])) + set_dec_mode(state, CSI_ARG(args[0]), 1); + break; + + case 0x6a: // HPB - ECMA-48 8.3.58 + count = CSI_ARG_COUNT(args[0]); + state->pos.col -= count; + state->at_phantom = 0; + break; + + case 0x6b: // VPB - ECMA-48 8.3.159 + count = CSI_ARG_COUNT(args[0]); + state->pos.row -= count; + state->at_phantom = 0; + break; + + case 0x6c: // RM - ECMA-48 8.3.106 + if(!CSI_ARG_IS_MISSING(args[0])) + set_mode(state, CSI_ARG(args[0]), 0); + break; + + case LEADER('?', 0x6c): // DEC private mode reset + if(!CSI_ARG_IS_MISSING(args[0])) + set_dec_mode(state, CSI_ARG(args[0]), 0); + break; + + case 0x6d: // SGR - ECMA-48 8.3.117 + vterm_state_setpen(state, args, argcount); + break; + + case LEADER('?', 0x6d): // DECSGR + /* No actual DEC terminal recognised these, but some printers did. These + * are alternative ways to request subscript/superscript/off + */ + for(int argi = 0; argi < argcount; argi++) { + long arg; + switch(arg = CSI_ARG(args[argi])) { + case 4: // Superscript on + arg = 73; + vterm_state_setpen(state, &arg, 1); + break; + case 5: // Subscript on + arg = 74; + vterm_state_setpen(state, &arg, 1); + break; + case 24: // Super+subscript off + arg = 75; + vterm_state_setpen(state, &arg, 1); + break; + } + } + break; + + case 0x6e: // DSR - ECMA-48 8.3.35 + case LEADER('?', 0x6e): // DECDSR + val = CSI_ARG_OR(args[0], 0); + + { + char *qmark = (leader_byte == '?') ? "?" : ""; + + switch(val) { + case 0: case 1: case 2: case 3: case 4: + // ignore - these are replies + break; + case 5: + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s0n", qmark); + break; + case 6: // CPR - cursor position report + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s%d;%dR", qmark, state->pos.row + 1, state->pos.col + 1); + break; + } + } + break; + + + case LEADER('!', 0x70): // DECSTR - DEC soft terminal reset + vterm_state_reset(state, 0); + break; + + case LEADER('?', INTERMED('$', 0x70)): + request_dec_mode(state, CSI_ARG(args[0])); + break; + + case LEADER('>', 0x71): // XTVERSION - xterm query version string + request_version_string(state); + break; + + case INTERMED(' ', 0x71): // DECSCUSR - DEC set cursor shape + val = CSI_ARG_OR(args[0], 1); + + switch(val) { + case 0: case 1: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); + settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); + break; + case 2: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); + settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); + break; + case 3: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); + settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE); + break; + case 4: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); + settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE); + break; + case 5: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); + settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT); + break; + case 6: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); + settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT); + break; + } + + break; + + case INTERMED('"', 0x71): // DECSCA - DEC select character protection attribute + val = CSI_ARG_OR(args[0], 0); + + switch(val) { + case 0: case 2: + state->protected_cell = 0; + break; + case 1: + state->protected_cell = 1; + break; + } + + break; + + case 0x72: // DECSTBM - DEC custom + state->scrollregion_top = CSI_ARG_OR(args[0], 1) - 1; + state->scrollregion_bottom = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); + LBOUND(state->scrollregion_top, 0); + UBOUND(state->scrollregion_top, state->rows); + LBOUND(state->scrollregion_bottom, -1); + if(state->scrollregion_top == 0 && state->scrollregion_bottom == state->rows) + state->scrollregion_bottom = -1; + else + UBOUND(state->scrollregion_bottom, state->rows); + + if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) { + // Invalid + state->scrollregion_top = 0; + state->scrollregion_bottom = -1; + } + + // Setting the scrolling region restores the cursor to the home position + state->pos.row = 0; + state->pos.col = 0; + if(state->mode.origin) { + state->pos.row += state->scrollregion_top; + state->pos.col += SCROLLREGION_LEFT(state); + } + + break; + + case 0x73: // DECSLRM - DEC custom + // Always allow setting these margins, just they won't take effect without DECVSSM + state->scrollregion_left = CSI_ARG_OR(args[0], 1) - 1; + state->scrollregion_right = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); + LBOUND(state->scrollregion_left, 0); + UBOUND(state->scrollregion_left, state->cols); + LBOUND(state->scrollregion_right, -1); + if(state->scrollregion_left == 0 && state->scrollregion_right == state->cols) + state->scrollregion_right = -1; + else + UBOUND(state->scrollregion_right, state->cols); + + if(state->scrollregion_right > -1 && + state->scrollregion_right <= state->scrollregion_left) { + // Invalid + state->scrollregion_left = 0; + state->scrollregion_right = -1; + } + + // Setting the scrolling region restores the cursor to the home position + state->pos.row = 0; + state->pos.col = 0; + if(state->mode.origin) { + state->pos.row += state->scrollregion_top; + state->pos.col += SCROLLREGION_LEFT(state); + } + + break; + + case INTERMED('\'', 0x7D): // DECIC + count = CSI_ARG_COUNT(args[0]); + + if(!is_cursor_in_scrollregion(state)) + break; + + rect.start_row = state->scrollregion_top; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = state->pos.col; + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, 0, -count); + + break; + + case INTERMED('\'', 0x7E): // DECDC + count = CSI_ARG_COUNT(args[0]); + + if(!is_cursor_in_scrollregion(state)) + break; + + rect.start_row = state->scrollregion_top; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = state->pos.col; + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, 0, count); + + break; + + default: + if(state->fallbacks && state->fallbacks->csi) + if((*state->fallbacks->csi)(leader, args, argcount, intermed, command, state->fbdata)) + return 1; + + return 0; + } + + if(state->mode.origin) { + LBOUND(state->pos.row, state->scrollregion_top); + UBOUND(state->pos.row, SCROLLREGION_BOTTOM(state)-1); + LBOUND(state->pos.col, SCROLLREGION_LEFT(state)); + UBOUND(state->pos.col, SCROLLREGION_RIGHT(state)-1); + } + else { + LBOUND(state->pos.row, 0); + UBOUND(state->pos.row, state->rows-1); + LBOUND(state->pos.col, 0); + UBOUND(state->pos.col, THISROWWIDTH(state)-1); + } + + updatecursor(state, &oldpos, cancel_phantom); + +#ifdef DEBUG + if(state->pos.row < 0 || state->pos.row >= state->rows || + state->pos.col < 0 || state->pos.col >= state->cols) { + fprintf(stderr, "Position out of bounds after CSI %c: (%d,%d)\n", + command, state->pos.row, state->pos.col); + abort(); + } + + if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) { + fprintf(stderr, "Scroll region height out of bounds after CSI %c: %d <= %d\n", + command, SCROLLREGION_BOTTOM(state), state->scrollregion_top); + abort(); + } + + if(SCROLLREGION_RIGHT(state) <= SCROLLREGION_LEFT(state)) { + fprintf(stderr, "Scroll region width out of bounds after CSI %c: %d <= %d\n", + command, SCROLLREGION_RIGHT(state), SCROLLREGION_LEFT(state)); + abort(); + } +#endif + + return 1; +} + +static char base64_one(uint8_t b) +{ + if(b < 26) + return 'A' + b; + else if(b < 52) + return 'a' + b - 26; + else if(b < 62) + return '0' + b - 52; + else if(b == 62) + return '+'; + else if(b == 63) + return '/'; + return 0; +} + +static uint8_t unbase64one(char c) +{ + if(c >= 'A' && c <= 'Z') + return c - 'A'; + else if(c >= 'a' && c <= 'z') + return c - 'a' + 26; + else if(c >= '0' && c <= '9') + return c - '0' + 52; + else if(c == '+') + return 62; + else if(c == '/') + return 63; + + return 0xFF; +} + +static void osc_selection(VTermState *state, VTermStringFragment frag) +{ + if(frag.initial) { + state->tmp.selection.mask = 0; + state->tmp.selection.state = SELECTION_INITIAL; + } + + while(!state->tmp.selection.state && frag.len) { + /* Parse selection parameter */ + switch(frag.str[0]) { + case 'c': + state->tmp.selection.mask |= VTERM_SELECTION_CLIPBOARD; + break; + case 'p': + state->tmp.selection.mask |= VTERM_SELECTION_PRIMARY; + break; + case 'q': + state->tmp.selection.mask |= VTERM_SELECTION_SECONDARY; + break; + case 's': + state->tmp.selection.mask |= VTERM_SELECTION_SELECT; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + state->tmp.selection.mask |= (VTERM_SELECTION_CUT0 << (frag.str[0] - '0')); + break; + + case ';': + state->tmp.selection.state = SELECTION_SELECTED; + if(!state->tmp.selection.mask) + state->tmp.selection.mask = VTERM_SELECTION_SELECT|VTERM_SELECTION_CUT0; + break; + } + + frag.str++; + frag.len--; + } + + if(!frag.len) + return; + + if(state->tmp.selection.state == SELECTION_SELECTED) { + if(frag.str[0] == '?') { + state->tmp.selection.state = SELECTION_QUERY; + } + else { + state->tmp.selection.state = SELECTION_SET_INITIAL; + state->tmp.selection.recvpartial = 0; + } + } + + if(state->tmp.selection.state == SELECTION_QUERY) { + if(state->selection.callbacks->query) + (*state->selection.callbacks->query)(state->tmp.selection.mask, state->selection.user); + return; + } + + if(state->selection.callbacks->set) { + size_t bufcur = 0; + char *buffer = state->selection.buffer; + + uint32_t x = 0; /* Current decoding value */ + int n = 0; /* Number of sextets consumed */ + + if(state->tmp.selection.recvpartial) { + n = state->tmp.selection.recvpartial >> 24; + x = state->tmp.selection.recvpartial & 0x03FFFF; /* could be up to 18 bits of state in here */ + + state->tmp.selection.recvpartial = 0; + } + + while((state->selection.buflen - bufcur) >= 3 && frag.len) { + if(frag.str[0] == '=') { + if(n == 2) { + buffer[0] = (x >> 4) & 0xFF; + buffer += 1, bufcur += 1; + } + if(n == 3) { + buffer[0] = (x >> 10) & 0xFF; + buffer[1] = (x >> 2) & 0xFF; + buffer += 2, bufcur += 2; + } + + while(frag.len && frag.str[0] == '=') + frag.str++, frag.len--; + + n = 0; + } + else { + uint8_t b = unbase64one(frag.str[0]); + if(b == 0xFF) { + DEBUG_LOG("base64decode bad input %02X\n", (uint8_t)frag.str[0]); + } + else { + x = (x << 6) | b; + n++; + } + frag.str++, frag.len--; + + if(n == 4) { + buffer[0] = (x >> 16) & 0xFF; + buffer[1] = (x >> 8) & 0xFF; + buffer[2] = (x >> 0) & 0xFF; + + buffer += 3, bufcur += 3; + x = 0; + n = 0; + } + } + + if(!frag.len || (state->selection.buflen - bufcur) < 3) { + if(bufcur) { + (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){ + .str = state->selection.buffer, + .len = bufcur, + .initial = state->tmp.selection.state == SELECTION_SET_INITIAL, + .final = frag.final, + }, state->selection.user); + state->tmp.selection.state = SELECTION_SET; + } + + buffer = state->selection.buffer; + bufcur = 0; + } + } + + if(n) + state->tmp.selection.recvpartial = (n << 24) | x; + } +} + +static int on_osc(int command, VTermStringFragment frag, void *user) +{ + VTermState *state = user; + + switch(command) { + case 0: + settermprop_string(state, VTERM_PROP_ICONNAME, frag); + settermprop_string(state, VTERM_PROP_TITLE, frag); + return 1; + + case 1: + settermprop_string(state, VTERM_PROP_ICONNAME, frag); + return 1; + + case 2: + settermprop_string(state, VTERM_PROP_TITLE, frag); + return 1; + + case 52: + if(state->selection.callbacks) + osc_selection(state, frag); + + return 1; + + default: + if(state->fallbacks && state->fallbacks->osc) + if((*state->fallbacks->osc)(command, frag, state->fbdata)) + return 1; + } + + return 0; +} + +static void request_status_string(VTermState *state, VTermStringFragment frag) +{ + VTerm *vt = state->vt; + + char *tmp = state->tmp.decrqss; + + if(frag.initial) + tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0; + + int i = 0; + while(i < sizeof(state->tmp.decrqss)-1 && tmp[i]) + i++; + while(i < sizeof(state->tmp.decrqss)-1 && frag.len--) + tmp[i++] = (frag.str++)[0]; + tmp[i] = 0; + + if(!frag.final) + return; + + switch(tmp[0] | tmp[1]<<8 | tmp[2]<<16) { + case 'm': { + // Query SGR + long args[20]; + int argc = vterm_state_getpen(state, args, sizeof(args)/sizeof(args[0])); + size_t cur = 0; + + cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + vt->mode.ctrl8bit ? "\x90" "1$r" : ESC_S "P" "1$r"); // DCS 1$r ... + if(cur >= vt->tmpbuffer_len) + return; + + for(int argi = 0; argi < argc; argi++) { + cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + argi == argc - 1 ? "%ld" : + CSI_ARG_HAS_MORE(args[argi]) ? "%ld:" : + "%ld;", + CSI_ARG(args[argi])); + if(cur >= vt->tmpbuffer_len) + return; + } + + cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + vt->mode.ctrl8bit ? "m" "\x9C" : "m" ESC_S "\\"); // ... m ST + if(cur >= vt->tmpbuffer_len) + return; + + vterm_push_output_bytes(vt, vt->tmpbuffer, cur); + return; + } + + case 'r': + // Query DECSTBM + vterm_push_output_sprintf_str(vt, C1_DCS, true, + "1$r%d;%dr", state->scrollregion_top+1, SCROLLREGION_BOTTOM(state)); + return; + + case 's': + // Query DECSLRM + vterm_push_output_sprintf_str(vt, C1_DCS, true, + "1$r%d;%ds", SCROLLREGION_LEFT(state)+1, SCROLLREGION_RIGHT(state)); + return; + + case ' '|('q'<<8): { + // Query DECSCUSR + int reply; + switch(state->mode.cursor_shape) { + case VTERM_PROP_CURSORSHAPE_BLOCK: reply = 2; break; + case VTERM_PROP_CURSORSHAPE_UNDERLINE: reply = 4; break; + case VTERM_PROP_CURSORSHAPE_BAR_LEFT: reply = 6; break; + } + if(state->mode.cursor_blink) + reply--; + vterm_push_output_sprintf_str(vt, C1_DCS, true, + "1$r%d q", reply); + return; + } + + case '\"'|('q'<<8): + // Query DECSCA + vterm_push_output_sprintf_str(vt, C1_DCS, true, + "1$r%d\"q", state->protected_cell ? 1 : 2); + return; + } + + vterm_push_output_sprintf_str(state->vt, C1_DCS, true, "0$r"); +} + +static int on_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user) +{ + VTermState *state = user; + + if(commandlen == 2 && strneq(command, "$q", 2)) { + request_status_string(state, frag); + return 1; + } + else if(state->fallbacks && state->fallbacks->dcs) + if((*state->fallbacks->dcs)(command, commandlen, frag, state->fbdata)) + return 1; + + DEBUG_LOG("libvterm: Unhandled DCS %.*s\n", (int)commandlen, command); + return 0; +} + +static int on_apc(VTermStringFragment frag, void *user) +{ + VTermState *state = user; + + if(state->fallbacks && state->fallbacks->apc) + if((*state->fallbacks->apc)(frag, state->fbdata)) + return 1; + + /* No DEBUG_LOG because all APCs are unhandled */ + return 0; +} + +static int on_pm(VTermStringFragment frag, void *user) +{ + VTermState *state = user; + + if(state->fallbacks && state->fallbacks->pm) + if((*state->fallbacks->pm)(frag, state->fbdata)) + return 1; + + /* No DEBUG_LOG because all PMs are unhandled */ + return 0; +} + +static int on_sos(VTermStringFragment frag, void *user) +{ + VTermState *state = user; + + if(state->fallbacks && state->fallbacks->sos) + if((*state->fallbacks->sos)(frag, state->fbdata)) + return 1; + + /* No DEBUG_LOG because all SOSs are unhandled */ + return 0; +} + +static int on_resize(int rows, int cols, void *user) +{ + VTermState *state = user; + VTermPos oldpos = state->pos; + + if(cols != state->cols) { + unsigned char *newtabstops = vterm_allocator_malloc(state->vt, (cols + 7) / 8); + + /* TODO: This can all be done much more efficiently bytewise */ + int col; + for(col = 0; col < state->cols && col < cols; col++) { + unsigned char mask = 1 << (col & 7); + if(state->tabstops[col >> 3] & mask) + newtabstops[col >> 3] |= mask; + else + newtabstops[col >> 3] &= ~mask; + } + + for( ; col < cols; col++) { + unsigned char mask = 1 << (col & 7); + if(col % 8 == 0) + newtabstops[col >> 3] |= mask; + else + newtabstops[col >> 3] &= ~mask; + } + + vterm_allocator_free(state->vt, state->tabstops); + state->tabstops = newtabstops; + } + + state->rows = rows; + state->cols = cols; + + if(state->scrollregion_bottom > -1) + UBOUND(state->scrollregion_bottom, state->rows); + if(state->scrollregion_right > -1) + UBOUND(state->scrollregion_right, state->cols); + + VTermStateFields fields = { + .pos = state->pos, + .lineinfos = { [0] = state->lineinfos[0], [1] = state->lineinfos[1] }, + }; + + if(state->callbacks && state->callbacks->resize) { + (*state->callbacks->resize)(rows, cols, &fields, state->cbdata); + state->pos = fields.pos; + + state->lineinfos[0] = fields.lineinfos[0]; + state->lineinfos[1] = fields.lineinfos[1]; + } + else { + if(rows != state->rows) { + for(int bufidx = BUFIDX_PRIMARY; bufidx <= BUFIDX_ALTSCREEN; bufidx++) { + VTermLineInfo *oldlineinfo = state->lineinfos[bufidx]; + if(!oldlineinfo) + continue; + + VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt, rows * sizeof(VTermLineInfo)); + + int row; + for(row = 0; row < state->rows && row < rows; row++) { + newlineinfo[row] = oldlineinfo[row]; + } + + for( ; row < rows; row++) { + newlineinfo[row] = (VTermLineInfo){ + .doublewidth = 0, + }; + } + + vterm_allocator_free(state->vt, state->lineinfos[bufidx]); + state->lineinfos[bufidx] = newlineinfo; + } + } + } + + state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY]; + + if(state->at_phantom && state->pos.col < cols-1) { + state->at_phantom = 0; + state->pos.col++; + } + + if(state->pos.row < 0) + state->pos.row = 0; + if(state->pos.row >= rows) + state->pos.row = rows - 1; + if(state->pos.col < 0) + state->pos.col = 0; + if(state->pos.col >= cols) + state->pos.col = cols - 1; + + updatecursor(state, &oldpos, 1); + + return 1; +} + +static const VTermParserCallbacks parser_callbacks = { + .text = on_text, + .control = on_control, + .escape = on_escape, + .csi = on_csi, + .osc = on_osc, + .dcs = on_dcs, + .apc = on_apc, + .pm = on_pm, + .sos = on_sos, + .resize = on_resize, +}; + +VTermState *vterm_obtain_state(VTerm *vt) +{ + if(vt->state) + return vt->state; + + VTermState *state = vterm_state_new(vt); + vt->state = state; + + vterm_parser_set_callbacks(vt, &parser_callbacks, state); + + return state; +} + +void vterm_state_reset(VTermState *state, int hard) +{ + state->scrollregion_top = 0; + state->scrollregion_bottom = -1; + state->scrollregion_left = 0; + state->scrollregion_right = -1; + + state->mode.keypad = 0; + state->mode.cursor = 0; + state->mode.autowrap = 1; + state->mode.insert = 0; + state->mode.newline = 0; + state->mode.alt_screen = 0; + state->mode.origin = 0; + state->mode.leftrightmargin = 0; + state->mode.bracketpaste = 0; + state->mode.report_focus = 0; + + state->mouse_flags = 0; + + state->vt->mode.ctrl8bit = 0; + + for(int col = 0; col < state->cols; col++) + if(col % 8 == 0) + set_col_tabstop(state, col); + else + clear_col_tabstop(state, col); + + for(int row = 0; row < state->rows; row++) + set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); + + if(state->callbacks && state->callbacks->initpen) + (*state->callbacks->initpen)(state->cbdata); + + vterm_state_resetpen(state); + + VTermEncoding *default_enc = state->vt->mode.utf8 ? + vterm_lookup_encoding(ENC_UTF8, 'u') : + vterm_lookup_encoding(ENC_SINGLE_94, 'B'); + + for(int i = 0; i < 4; i++) { + state->encoding[i].enc = default_enc; + if(default_enc->init) + (*default_enc->init)(default_enc, state->encoding[i].data); + } + + state->gl_set = 0; + state->gr_set = 1; + state->gsingle_set = 0; + + state->protected_cell = 0; + + // Initialise the props + settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, 1); + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); + settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); + + if(hard) { + state->pos.row = 0; + state->pos.col = 0; + state->at_phantom = 0; + + VTermRect rect = { 0, state->rows, 0, state->cols }; + erase(state, rect, 0); + } +} + +void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos) +{ + *cursorpos = state->pos; +} + +void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user) +{ + if(callbacks) { + state->callbacks = callbacks; + state->cbdata = user; + + if(state->callbacks && state->callbacks->initpen) + (*state->callbacks->initpen)(state->cbdata); + } + else { + state->callbacks = NULL; + state->cbdata = NULL; + } +} + +void *vterm_state_get_cbdata(VTermState *state) +{ + return state->cbdata; +} + +void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermStateFallbacks *fallbacks, void *user) +{ + if(fallbacks) { + state->fallbacks = fallbacks; + state->fbdata = user; + } + else { + state->fallbacks = NULL; + state->fbdata = NULL; + } +} + +void *vterm_state_get_unrecognised_fbdata(VTermState *state) +{ + return state->fbdata; +} + +int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val) +{ + /* Only store the new value of the property if usercode said it was happy. + * This is especially important for altscreen switching */ + if(state->callbacks && state->callbacks->settermprop) + if(!(*state->callbacks->settermprop)(prop, val, state->cbdata)) + return 0; + + switch(prop) { + case VTERM_PROP_TITLE: + case VTERM_PROP_ICONNAME: + // we don't store these, just transparently pass through + return 1; + case VTERM_PROP_CURSORVISIBLE: + state->mode.cursor_visible = val->boolean; + return 1; + case VTERM_PROP_CURSORBLINK: + state->mode.cursor_blink = val->boolean; + return 1; + case VTERM_PROP_CURSORSHAPE: + state->mode.cursor_shape = val->number; + return 1; + case VTERM_PROP_REVERSE: + state->mode.screen = val->boolean; + return 1; + case VTERM_PROP_ALTSCREEN: + state->mode.alt_screen = val->boolean; + state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY]; + if(state->mode.alt_screen) { + VTermRect rect = { + .start_row = 0, + .start_col = 0, + .end_row = state->rows, + .end_col = state->cols, + }; + erase(state, rect, 0); + } + return 1; + case VTERM_PROP_MOUSE: + state->mouse_flags = 0; + if(val->number) + state->mouse_flags |= MOUSE_WANT_CLICK; + if(val->number == VTERM_PROP_MOUSE_DRAG) + state->mouse_flags |= MOUSE_WANT_DRAG; + if(val->number == VTERM_PROP_MOUSE_MOVE) + state->mouse_flags |= MOUSE_WANT_MOVE; + return 1; + + case VTERM_N_PROPS: + return 0; + } + + return 0; +} + +void vterm_state_focus_in(VTermState *state) +{ + if(state->mode.report_focus) + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "I"); +} + +void vterm_state_focus_out(VTermState *state) +{ + if(state->mode.report_focus) + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "O"); +} + +const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row) +{ + return state->lineinfo + row; +} + +void vterm_state_set_selection_callbacks(VTermState *state, const VTermSelectionCallbacks *callbacks, void *user, + char *buffer, size_t buflen) +{ + if(buflen && !buffer) + buffer = vterm_allocator_malloc(state->vt, buflen); + + state->selection.callbacks = callbacks; + state->selection.user = user; + state->selection.buffer = buffer; + state->selection.buflen = buflen; +} + +void vterm_state_send_selection(VTermState *state, VTermSelectionMask mask, VTermStringFragment frag) +{ + VTerm *vt = state->vt; + + if(frag.initial) { + /* TODO: support sending more than one mask bit */ + const static char selection_chars[] = "cpqs"; + int idx; + for(idx = 0; idx < 4; idx++) + if(mask & (1 << idx)) + break; + + vterm_push_output_sprintf_str(vt, C1_OSC, false, "52;%c;", selection_chars[idx]); + + state->tmp.selection.sendpartial = 0; + } + + if(frag.len) { + size_t bufcur = 0; + char *buffer = state->selection.buffer; + + uint32_t x = 0; + int n = 0; + + if(state->tmp.selection.sendpartial) { + n = state->tmp.selection.sendpartial >> 24; + x = state->tmp.selection.sendpartial & 0xFFFFFF; + + state->tmp.selection.sendpartial = 0; + } + + while((state->selection.buflen - bufcur) >= 4 && frag.len) { + x = (x << 8) | frag.str[0]; + n++; + frag.str++, frag.len--; + + if(n == 3) { + buffer[0] = base64_one((x >> 18) & 0x3F); + buffer[1] = base64_one((x >> 12) & 0x3F); + buffer[2] = base64_one((x >> 6) & 0x3F); + buffer[3] = base64_one((x >> 0) & 0x3F); + + buffer += 4, bufcur += 4; + x = 0; + n = 0; + } + + if(!frag.len || (state->selection.buflen - bufcur) < 4) { + if(bufcur) + vterm_push_output_bytes(vt, state->selection.buffer, bufcur); + + buffer = state->selection.buffer; + bufcur = 0; + } + } + + if(n) + state->tmp.selection.sendpartial = (n << 24) | x; + } + + if(frag.final) { + if(state->tmp.selection.sendpartial) { + int n = state->tmp.selection.sendpartial >> 24; + uint32_t x = state->tmp.selection.sendpartial & 0xFFFFFF; + char *buffer = state->selection.buffer; + + /* n is either 1 or 2 now */ + x <<= (n == 1) ? 16 : 8; + + buffer[0] = base64_one((x >> 18) & 0x3F); + buffer[1] = base64_one((x >> 12) & 0x3F); + buffer[2] = (n == 1) ? '=' : base64_one((x >> 6) & 0x3F); + buffer[3] = '='; + + vterm_push_output_sprintf_str(vt, 0, true, "%.*s", 4, buffer); + } + else + vterm_push_output_sprintf_str(vt, 0, true, ""); + } +} diff --git a/src/libs/3rdparty/libvterm/src/unicode.c b/src/libs/3rdparty/libvterm/src/unicode.c new file mode 100644 index 0000000000..269244ff6b --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/unicode.c @@ -0,0 +1,313 @@ +#include "vterm_internal.h" + +// ### The following from http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c +// With modifications: +// made functions static +// moved 'combining' table to file scope, so other functions can see it +// ################################################################### + +/* + * This is an implementation of wcwidth() and wcswidth() (defined in + * IEEE Std 1002.1-2001) for Unicode. + * + * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html + * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html + * + * In fixed-width output devices, Latin characters all occupy a single + * "cell" position of equal width, whereas ideographic CJK characters + * occupy two such cells. Interoperability between terminal-line + * applications and (teletype-style) character terminals using the + * UTF-8 encoding requires agreement on which character should advance + * the cursor by how many cell positions. No established formal + * standards exist at present on which Unicode character shall occupy + * how many cell positions on character terminals. These routines are + * a first attempt of defining such behavior based on simple rules + * applied to data provided by the Unicode Consortium. + * + * For some graphical characters, the Unicode standard explicitly + * defines a character-cell width via the definition of the East Asian + * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes. + * In all these cases, there is no ambiguity about which width a + * terminal shall use. For characters in the East Asian Ambiguous (A) + * class, the width choice depends purely on a preference of backward + * compatibility with either historic CJK or Western practice. + * Choosing single-width for these characters is easy to justify as + * the appropriate long-term solution, as the CJK practice of + * displaying these characters as double-width comes from historic + * implementation simplicity (8-bit encoded characters were displayed + * single-width and 16-bit ones double-width, even for Greek, + * Cyrillic, etc.) and not any typographic considerations. + * + * Much less clear is the choice of width for the Not East Asian + * (Neutral) class. Existing practice does not dictate a width for any + * of these characters. It would nevertheless make sense + * typographically to allocate two character cells to characters such + * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be + * represented adequately with a single-width glyph. The following + * routines at present merely assign a single-cell width to all + * neutral characters, in the interest of simplicity. This is not + * entirely satisfactory and should be reconsidered before + * establishing a formal standard in this area. At the moment, the + * decision which Not East Asian (Neutral) characters should be + * represented by double-width glyphs cannot yet be answered by + * applying a simple rule from the Unicode database content. Setting + * up a proper standard for the behavior of UTF-8 character terminals + * will require a careful analysis not only of each Unicode character, + * but also of each presentation form, something the author of these + * routines has avoided to do so far. + * + * http://www.unicode.org/unicode/reports/tr11/ + * + * Markus Kuhn -- 2007-05-26 (Unicode 5.0) + * + * Permission to use, copy, modify, and distribute this software + * for any purpose and without fee is hereby granted. The author + * disclaims all warranties with regard to this software. + * + * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c + */ + +struct interval { + int first; + int last; +}; + +/* sorted list of non-overlapping intervals of non-spacing characters */ +/* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ +static const struct interval combining[] = { + { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, + { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, + { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, + { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 }, + { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, + { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, + { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 }, + { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, + { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, + { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, + { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, + { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, + { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, + { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, + { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, + { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D }, + { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, + { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, + { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC }, + { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, + { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, + { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, + { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, + { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, + { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, + { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, + { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, + { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, + { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, + { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F }, + { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, + { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, + { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, + { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, + { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, + { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, + { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, + { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF }, + { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 }, + { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F }, + { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, + { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, + { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, + { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F }, + { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 }, + { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, + { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, + { 0xE0100, 0xE01EF } +}; + + +/* auxiliary function for binary search in interval table */ +static int bisearch(uint32_t ucs, const struct interval *table, int max) { + int min = 0; + int mid; + + if (ucs < table[0].first || ucs > table[max].last) + return 0; + while (max >= min) { + mid = (min + max) / 2; + if (ucs > table[mid].last) + min = mid + 1; + else if (ucs < table[mid].first) + max = mid - 1; + else + return 1; + } + + return 0; +} + + +/* The following two functions define the column width of an ISO 10646 + * character as follows: + * + * - The null character (U+0000) has a column width of 0. + * + * - Other C0/C1 control characters and DEL will lead to a return + * value of -1. + * + * - Non-spacing and enclosing combining characters (general + * category code Mn or Me in the Unicode database) have a + * column width of 0. + * + * - SOFT HYPHEN (U+00AD) has a column width of 1. + * + * - Other format characters (general category code Cf in the Unicode + * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. + * + * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) + * have a column width of 0. + * + * - Spacing characters in the East Asian Wide (W) or East Asian + * Full-width (F) category as defined in Unicode Technical + * Report #11 have a column width of 2. + * + * - All remaining characters (including all printable + * ISO 8859-1 and WGL4 characters, Unicode control characters, + * etc.) have a column width of 1. + * + * This implementation assumes that uint32_t characters are encoded + * in ISO 10646. + */ + + +static int mk_wcwidth(uint32_t ucs) +{ + /* test for 8-bit control characters */ + if (ucs == 0) + return 0; + if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) + return -1; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, combining, + sizeof(combining) / sizeof(struct interval) - 1)) + return 0; + + /* if we arrive here, ucs is not a combining or C0/C1 control character */ + + return 1 + + (ucs >= 0x1100 && + (ucs <= 0x115f || /* Hangul Jamo init. consonants */ + ucs == 0x2329 || ucs == 0x232a || + (ucs >= 0x2e80 && ucs <= 0xa4cf && + ucs != 0x303f) || /* CJK ... Yi */ + (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ + (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ + (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */ + (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ + (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */ + (ucs >= 0xffe0 && ucs <= 0xffe6) || + (ucs >= 0x20000 && ucs <= 0x2fffd) || + (ucs >= 0x30000 && ucs <= 0x3fffd))); +} + + +#ifdef USE_MK_WCWIDTH_CJK + +/* + * The following functions are the same as mk_wcwidth() and + * mk_wcswidth(), except that spacing characters in the East Asian + * Ambiguous (A) category as defined in Unicode Technical Report #11 + * have a column width of 2. This variant might be useful for users of + * CJK legacy encodings who want to migrate to UCS without changing + * the traditional terminal character-width behaviour. It is not + * otherwise recommended for general use. + */ +static int mk_wcwidth_cjk(uint32_t ucs) +{ + /* sorted list of non-overlapping intervals of East Asian Ambiguous + * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */ + static const struct interval ambiguous[] = { + { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 }, + { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 }, + { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 }, + { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 }, + { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED }, + { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA }, + { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 }, + { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B }, + { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 }, + { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 }, + { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 }, + { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE }, + { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 }, + { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA }, + { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 }, + { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB }, + { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB }, + { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 }, + { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 }, + { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 }, + { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 }, + { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 }, + { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 }, + { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 }, + { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC }, + { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 }, + { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 }, + { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 }, + { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 }, + { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 }, + { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 }, + { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B }, + { 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 }, + { 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 }, + { 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E }, + { 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 }, + { 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 }, + { 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F }, + { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 }, + { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF }, + { 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x254B }, + { 0x2550, 0x2573 }, { 0x2580, 0x258F }, { 0x2592, 0x2595 }, + { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 }, + { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 }, + { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 }, + { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF }, { 0x2605, 0x2606 }, + { 0x2609, 0x2609 }, { 0x260E, 0x260F }, { 0x2614, 0x2615 }, + { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 }, + { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 }, + { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F }, + { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xE000, 0xF8FF }, + { 0xFFFD, 0xFFFD }, { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD } + }; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, ambiguous, + sizeof(ambiguous) / sizeof(struct interval) - 1)) + return 2; + + return mk_wcwidth(ucs); +} + +#endif + +// ################################ +// ### The rest added by Paul Evans + +static const struct interval fullwidth[] = { +#include "fullwidth.inc" +}; + +INTERNAL int vterm_unicode_width(uint32_t codepoint) +{ + if(bisearch(codepoint, fullwidth, sizeof(fullwidth) / sizeof(fullwidth[0]) - 1)) + return 2; + + return mk_wcwidth(codepoint); +} + +INTERNAL int vterm_unicode_is_combining(uint32_t codepoint) +{ + return bisearch(codepoint, combining, sizeof(combining) / sizeof(struct interval) - 1); +} diff --git a/src/libs/3rdparty/libvterm/src/utf8.h b/src/libs/3rdparty/libvterm/src/utf8.h new file mode 100644 index 0000000000..9a336d357f --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/utf8.h @@ -0,0 +1,39 @@ +/* The following functions copied and adapted from libtermkey + * + * http://www.leonerd.org.uk/code/libtermkey/ + */ +static inline unsigned int utf8_seqlen(long codepoint) +{ + if(codepoint < 0x0000080) return 1; + if(codepoint < 0x0000800) return 2; + if(codepoint < 0x0010000) return 3; + if(codepoint < 0x0200000) return 4; + if(codepoint < 0x4000000) return 5; + return 6; +} + +/* Does NOT NUL-terminate the buffer */ +static int fill_utf8(long codepoint, char *str) +{ + int nbytes = utf8_seqlen(codepoint); + + // This is easier done backwards + int b = nbytes; + while(b > 1) { + b--; + str[b] = 0x80 | (codepoint & 0x3f); + codepoint >>= 6; + } + + switch(nbytes) { + case 1: str[0] = (codepoint & 0x7f); break; + case 2: str[0] = 0xc0 | (codepoint & 0x1f); break; + case 3: str[0] = 0xe0 | (codepoint & 0x0f); break; + case 4: str[0] = 0xf0 | (codepoint & 0x07); break; + case 5: str[0] = 0xf8 | (codepoint & 0x03); break; + case 6: str[0] = 0xfc | (codepoint & 0x01); break; + } + + return nbytes; +} +/* end copy */ diff --git a/src/libs/3rdparty/libvterm/src/vterm.c b/src/libs/3rdparty/libvterm/src/vterm.c new file mode 100644 index 0000000000..0997887f5f --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/vterm.c @@ -0,0 +1,429 @@ +#include "vterm_internal.h" + +#include +#include +#include +#include + +/***************** + * API functions * + *****************/ + +static void *default_malloc(size_t size, void *allocdata) +{ + void *ptr = malloc(size); + if(ptr) + memset(ptr, 0, size); + return ptr; +} + +static void default_free(void *ptr, void *allocdata) +{ + free(ptr); +} + +static VTermAllocatorFunctions default_allocator = { + .malloc = &default_malloc, + .free = &default_free, +}; + +VTerm *vterm_new(int rows, int cols) +{ + return vterm_build(&(const struct VTermBuilder){ + .rows = rows, + .cols = cols, + }); +} + +VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata) +{ + return vterm_build(&(const struct VTermBuilder){ + .rows = rows, + .cols = cols, + .allocator = funcs, + .allocdata = allocdata, + }); +} + +/* A handy macro for defaulting values out of builder fields */ +#define DEFAULT(v, def) ((v) ? (v) : (def)) + +VTerm *vterm_build(const struct VTermBuilder *builder) +{ + const VTermAllocatorFunctions *allocator = DEFAULT(builder->allocator, &default_allocator); + + /* Need to bootstrap using the allocator function directly */ + VTerm *vt = (*allocator->malloc)(sizeof(VTerm), builder->allocdata); + + vt->allocator = allocator; + vt->allocdata = builder->allocdata; + + vt->rows = builder->rows; + vt->cols = builder->cols; + + vt->parser.state = NORMAL; + + vt->parser.callbacks = NULL; + vt->parser.cbdata = NULL; + + vt->parser.emit_nul = false; + + vt->outfunc = NULL; + vt->outdata = NULL; + + vt->outbuffer_len = DEFAULT(builder->outbuffer_len, 4096); + vt->outbuffer_cur = 0; + vt->outbuffer = vterm_allocator_malloc(vt, vt->outbuffer_len); + + vt->tmpbuffer_len = DEFAULT(builder->tmpbuffer_len, 4096); + vt->tmpbuffer = vterm_allocator_malloc(vt, vt->tmpbuffer_len); + + return vt; +} + +void vterm_free(VTerm *vt) +{ + if(vt->screen) + vterm_screen_free(vt->screen); + + if(vt->state) + vterm_state_free(vt->state); + + vterm_allocator_free(vt, vt->outbuffer); + vterm_allocator_free(vt, vt->tmpbuffer); + + vterm_allocator_free(vt, vt); +} + +INTERNAL void *vterm_allocator_malloc(VTerm *vt, size_t size) +{ + return (*vt->allocator->malloc)(size, vt->allocdata); +} + +INTERNAL void vterm_allocator_free(VTerm *vt, void *ptr) +{ + (*vt->allocator->free)(ptr, vt->allocdata); +} + +void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp) +{ + if(rowsp) + *rowsp = vt->rows; + if(colsp) + *colsp = vt->cols; +} + +void vterm_set_size(VTerm *vt, int rows, int cols) +{ + if(rows < 1 || cols < 1) + return; + + vt->rows = rows; + vt->cols = cols; + + if(vt->parser.callbacks && vt->parser.callbacks->resize) + (*vt->parser.callbacks->resize)(rows, cols, vt->parser.cbdata); +} + +int vterm_get_utf8(const VTerm *vt) +{ + return vt->mode.utf8; +} + +void vterm_set_utf8(VTerm *vt, int is_utf8) +{ + vt->mode.utf8 = is_utf8; +} + +void vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user) +{ + vt->outfunc = func; + vt->outdata = user; +} + +INTERNAL void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len) +{ + if(vt->outfunc) { + (vt->outfunc)(bytes, len, vt->outdata); + return; + } + + if(len > vt->outbuffer_len - vt->outbuffer_cur) + return; + + memcpy(vt->outbuffer + vt->outbuffer_cur, bytes, len); + vt->outbuffer_cur += len; +} + +INTERNAL void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args) +{ + size_t len = vsnprintf(vt->tmpbuffer, vt->tmpbuffer_len, + format, args); + + vterm_push_output_bytes(vt, vt->tmpbuffer, len); +} + +INTERNAL void vterm_push_output_sprintf(VTerm *vt, const char *format, ...) +{ + va_list args; + va_start(args, format); + vterm_push_output_vsprintf(vt, format, args); + va_end(args); +} + +INTERNAL void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...) +{ + size_t cur; + + if(ctrl >= 0x80 && !vt->mode.ctrl8bit) + cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len, + ESC_S "%c", ctrl - 0x40); + else + cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len, + "%c", ctrl); + + if(cur >= vt->tmpbuffer_len) + return; + + va_list args; + va_start(args, fmt); + cur += vsnprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + fmt, args); + va_end(args); + + if(cur >= vt->tmpbuffer_len) + return; + + vterm_push_output_bytes(vt, vt->tmpbuffer, cur); +} + +INTERNAL void vterm_push_output_sprintf_str(VTerm *vt, unsigned char ctrl, bool term, const char *fmt, ...) +{ + size_t cur = 0; + + if(ctrl) { + if(ctrl >= 0x80 && !vt->mode.ctrl8bit) + cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len, + ESC_S "%c", ctrl - 0x40); + else + cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len, + "%c", ctrl); + + if(cur >= vt->tmpbuffer_len) + return; + } + + va_list args; + va_start(args, fmt); + cur += vsnprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + fmt, args); + va_end(args); + + if(cur >= vt->tmpbuffer_len) + return; + + if(term) { + cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + vt->mode.ctrl8bit ? "\x9C" : ESC_S "\\"); // ST + + if(cur >= vt->tmpbuffer_len) + return; + } + + vterm_push_output_bytes(vt, vt->tmpbuffer, cur); +} + +size_t vterm_output_get_buffer_size(const VTerm *vt) +{ + return vt->outbuffer_len; +} + +size_t vterm_output_get_buffer_current(const VTerm *vt) +{ + return vt->outbuffer_cur; +} + +size_t vterm_output_get_buffer_remaining(const VTerm *vt) +{ + return vt->outbuffer_len - vt->outbuffer_cur; +} + +size_t vterm_output_read(VTerm *vt, char *buffer, size_t len) +{ + if(len > vt->outbuffer_cur) + len = vt->outbuffer_cur; + + memcpy(buffer, vt->outbuffer, len); + + if(len < vt->outbuffer_cur) + memmove(vt->outbuffer, vt->outbuffer + len, vt->outbuffer_cur - len); + + vt->outbuffer_cur -= len; + + return len; +} + +VTermValueType vterm_get_attr_type(VTermAttr attr) +{ + switch(attr) { + case VTERM_ATTR_BOLD: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_UNDERLINE: return VTERM_VALUETYPE_INT; + case VTERM_ATTR_ITALIC: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_BLINK: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_REVERSE: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_CONCEAL: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_STRIKE: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_FONT: return VTERM_VALUETYPE_INT; + case VTERM_ATTR_FOREGROUND: return VTERM_VALUETYPE_COLOR; + case VTERM_ATTR_BACKGROUND: return VTERM_VALUETYPE_COLOR; + case VTERM_ATTR_SMALL: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_BASELINE: return VTERM_VALUETYPE_INT; + + case VTERM_N_ATTRS: return 0; + } + return 0; /* UNREACHABLE */ +} + +VTermValueType vterm_get_prop_type(VTermProp prop) +{ + switch(prop) { + case VTERM_PROP_CURSORVISIBLE: return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_CURSORBLINK: return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_ALTSCREEN: return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_TITLE: return VTERM_VALUETYPE_STRING; + case VTERM_PROP_ICONNAME: return VTERM_VALUETYPE_STRING; + case VTERM_PROP_REVERSE: return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_CURSORSHAPE: return VTERM_VALUETYPE_INT; + case VTERM_PROP_MOUSE: return VTERM_VALUETYPE_INT; + + case VTERM_N_PROPS: return 0; + } + return 0; /* UNREACHABLE */ +} + +void vterm_scroll_rect(VTermRect rect, + int downward, + int rightward, + int (*moverect)(VTermRect src, VTermRect dest, void *user), + int (*eraserect)(VTermRect rect, int selective, void *user), + void *user) +{ + VTermRect src; + VTermRect dest; + + if(abs(downward) >= rect.end_row - rect.start_row || + abs(rightward) >= rect.end_col - rect.start_col) { + /* Scroll more than area; just erase the lot */ + (*eraserect)(rect, 0, user); + return; + } + + if(rightward >= 0) { + /* rect: [XXX................] + * src: [----------------] + * dest: [----------------] + */ + dest.start_col = rect.start_col; + dest.end_col = rect.end_col - rightward; + src.start_col = rect.start_col + rightward; + src.end_col = rect.end_col; + } + else { + /* rect: [................XXX] + * src: [----------------] + * dest: [----------------] + */ + int leftward = -rightward; + dest.start_col = rect.start_col + leftward; + dest.end_col = rect.end_col; + src.start_col = rect.start_col; + src.end_col = rect.end_col - leftward; + } + + if(downward >= 0) { + dest.start_row = rect.start_row; + dest.end_row = rect.end_row - downward; + src.start_row = rect.start_row + downward; + src.end_row = rect.end_row; + } + else { + int upward = -downward; + dest.start_row = rect.start_row + upward; + dest.end_row = rect.end_row; + src.start_row = rect.start_row; + src.end_row = rect.end_row - upward; + } + + if(moverect) + (*moverect)(dest, src, user); + + if(downward > 0) + rect.start_row = rect.end_row - downward; + else if(downward < 0) + rect.end_row = rect.start_row - downward; + + if(rightward > 0) + rect.start_col = rect.end_col - rightward; + else if(rightward < 0) + rect.end_col = rect.start_col - rightward; + + (*eraserect)(rect, 0, user); +} + +void vterm_copy_cells(VTermRect dest, + VTermRect src, + void (*copycell)(VTermPos dest, VTermPos src, void *user), + void *user) +{ + int downward = src.start_row - dest.start_row; + int rightward = src.start_col - dest.start_col; + + int init_row, test_row, init_col, test_col; + int inc_row, inc_col; + + if(downward < 0) { + init_row = dest.end_row - 1; + test_row = dest.start_row - 1; + inc_row = -1; + } + else /* downward >= 0 */ { + init_row = dest.start_row; + test_row = dest.end_row; + inc_row = +1; + } + + if(rightward < 0) { + init_col = dest.end_col - 1; + test_col = dest.start_col - 1; + inc_col = -1; + } + else /* rightward >= 0 */ { + init_col = dest.start_col; + test_col = dest.end_col; + inc_col = +1; + } + + VTermPos pos; + for(pos.row = init_row; pos.row != test_row; pos.row += inc_row) + for(pos.col = init_col; pos.col != test_col; pos.col += inc_col) { + VTermPos srcpos = { pos.row + downward, pos.col + rightward }; + (*copycell)(pos, srcpos, user); + } +} + +void vterm_check_version(int major, int minor) +{ + if(major != VTERM_VERSION_MAJOR) { + fprintf(stderr, "libvterm major version mismatch; %d (wants) != %d (library)\n", + major, VTERM_VERSION_MAJOR); + exit(1); + } + + if(minor > VTERM_VERSION_MINOR) { + fprintf(stderr, "libvterm minor version mismatch; %d (wants) > %d (library)\n", + minor, VTERM_VERSION_MINOR); + exit(1); + } + + // Happy +} diff --git a/src/libs/3rdparty/libvterm/src/vterm_internal.h b/src/libs/3rdparty/libvterm/src/vterm_internal.h new file mode 100644 index 0000000000..df9495c678 --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/vterm_internal.h @@ -0,0 +1,296 @@ +#ifndef __VTERM_INTERNAL_H__ +#define __VTERM_INTERNAL_H__ + +#include "vterm.h" + +#include + +#if defined(__GNUC__) +# define INTERNAL __attribute__((visibility("internal"))) +#else +# define INTERNAL +#endif + +#ifdef DEBUG +# define DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__) +#else +# define DEBUG_LOG(...) +#endif + +#define ESC_S "\x1b" + +#define INTERMED_MAX 16 + +#define CSI_ARGS_MAX 16 +#define CSI_LEADER_MAX 16 + +#define BUFIDX_PRIMARY 0 +#define BUFIDX_ALTSCREEN 1 + +typedef struct VTermEncoding VTermEncoding; + +typedef struct { + VTermEncoding *enc; + + // This size should be increased if required by other stateful encodings + char data[4*sizeof(uint32_t)]; +} VTermEncodingInstance; + +struct VTermPen +{ + VTermColor fg; + VTermColor bg; + unsigned int bold:1; + unsigned int underline:2; + unsigned int italic:1; + unsigned int blink:1; + unsigned int reverse:1; + unsigned int conceal:1; + unsigned int strike:1; + unsigned int font:4; /* To store 0-9 */ + unsigned int small:1; + unsigned int baseline:2; +}; + +struct VTermState +{ + VTerm *vt; + + const VTermStateCallbacks *callbacks; + void *cbdata; + + const VTermStateFallbacks *fallbacks; + void *fbdata; + + int rows; + int cols; + + /* Current cursor position */ + VTermPos pos; + + int at_phantom; /* True if we're on the "81st" phantom column to defer a wraparound */ + + int scrollregion_top; + int scrollregion_bottom; /* -1 means unbounded */ +#define SCROLLREGION_BOTTOM(state) ((state)->scrollregion_bottom > -1 ? (state)->scrollregion_bottom : (state)->rows) + int scrollregion_left; +#define SCROLLREGION_LEFT(state) ((state)->mode.leftrightmargin ? (state)->scrollregion_left : 0) + int scrollregion_right; /* -1 means unbounded */ +#define SCROLLREGION_RIGHT(state) ((state)->mode.leftrightmargin && (state)->scrollregion_right > -1 ? (state)->scrollregion_right : (state)->cols) + + /* Bitvector of tab stops */ + unsigned char *tabstops; + + /* Primary and Altscreen; lineinfos[1] is lazily allocated as needed */ + VTermLineInfo *lineinfos[2]; + + /* lineinfo will == lineinfos[0] or lineinfos[1], depending on altscreen */ + VTermLineInfo *lineinfo; +#define ROWWIDTH(state,row) ((state)->lineinfo[(row)].doublewidth ? ((state)->cols / 2) : (state)->cols) +#define THISROWWIDTH(state) ROWWIDTH(state, (state)->pos.row) + + /* Mouse state */ + int mouse_col, mouse_row; + int mouse_buttons; + int mouse_flags; +#define MOUSE_WANT_CLICK 0x01 +#define MOUSE_WANT_DRAG 0x02 +#define MOUSE_WANT_MOVE 0x04 + + enum { MOUSE_X10, MOUSE_UTF8, MOUSE_SGR, MOUSE_RXVT } mouse_protocol; + + /* Last glyph output, for Unicode recombining purposes */ + uint32_t *combine_chars; + size_t combine_chars_size; // Number of ELEMENTS in the above + int combine_width; // The width of the glyph above + VTermPos combine_pos; // Position before movement + + struct { + unsigned int keypad:1; + unsigned int cursor:1; + unsigned int autowrap:1; + unsigned int insert:1; + unsigned int newline:1; + unsigned int cursor_visible:1; + unsigned int cursor_blink:1; + unsigned int cursor_shape:2; + unsigned int alt_screen:1; + unsigned int origin:1; + unsigned int screen:1; + unsigned int leftrightmargin:1; + unsigned int bracketpaste:1; + unsigned int report_focus:1; + } mode; + + VTermEncodingInstance encoding[4], encoding_utf8; + int gl_set, gr_set, gsingle_set; + + struct VTermPen pen; + + VTermColor default_fg; + VTermColor default_bg; + VTermColor colors[16]; // Store the 8 ANSI and the 8 ANSI high-brights only + + int bold_is_highbright; + + unsigned int protected_cell : 1; + + /* Saved state under DEC mode 1048/1049 */ + struct { + VTermPos pos; + struct VTermPen pen; + + struct { + unsigned int cursor_visible:1; + unsigned int cursor_blink:1; + unsigned int cursor_shape:2; + } mode; + } saved; + + /* Temporary state for DECRQSS parsing */ + union { + char decrqss[4]; + struct { + uint16_t mask; + enum { + SELECTION_INITIAL, + SELECTION_SELECTED, + SELECTION_QUERY, + SELECTION_SET_INITIAL, + SELECTION_SET, + } state : 8; + uint32_t recvpartial; + uint32_t sendpartial; + } selection; + } tmp; + + struct { + const VTermSelectionCallbacks *callbacks; + void *user; + char *buffer; + size_t buflen; + } selection; +}; + +struct VTerm +{ + const VTermAllocatorFunctions *allocator; + void *allocdata; + + int rows; + int cols; + + struct { + unsigned int utf8:1; + unsigned int ctrl8bit:1; + } mode; + + struct { + enum VTermParserState { + NORMAL, + CSI_LEADER, + CSI_ARGS, + CSI_INTERMED, + DCS_COMMAND, + /* below here are the "string states" */ + OSC_COMMAND, + OSC, + DCS, + APC, + PM, + SOS, + } state; + + bool in_esc : 1; + + int intermedlen; + char intermed[INTERMED_MAX]; + + union { + struct { + int leaderlen; + char leader[CSI_LEADER_MAX]; + + int argi; + long args[CSI_ARGS_MAX]; + } csi; + struct { + int command; + } osc; + struct { + int commandlen; + char command[CSI_LEADER_MAX]; + } dcs; + } v; + + const VTermParserCallbacks *callbacks; + void *cbdata; + + bool string_initial; + + bool emit_nul; + } parser; + + /* len == malloc()ed size; cur == number of valid bytes */ + + VTermOutputCallback *outfunc; + void *outdata; + + char *outbuffer; + size_t outbuffer_len; + size_t outbuffer_cur; + + char *tmpbuffer; + size_t tmpbuffer_len; + + VTermState *state; + VTermScreen *screen; +}; + +struct VTermEncoding { + void (*init) (VTermEncoding *enc, void *data); + void (*decode)(VTermEncoding *enc, void *data, + uint32_t cp[], int *cpi, int cplen, + const char bytes[], size_t *pos, size_t len); +}; + +typedef enum { + ENC_UTF8, + ENC_SINGLE_94 +} VTermEncodingType; + +void *vterm_allocator_malloc(VTerm *vt, size_t size); +void vterm_allocator_free(VTerm *vt, void *ptr); + +void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len); +void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args); +void vterm_push_output_sprintf(VTerm *vt, const char *format, ...); +void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...); +void vterm_push_output_sprintf_str(VTerm *vt, unsigned char ctrl, bool term, const char *fmt, ...); + +void vterm_state_free(VTermState *state); + +void vterm_state_newpen(VTermState *state); +void vterm_state_resetpen(VTermState *state); +void vterm_state_setpen(VTermState *state, const long args[], int argcount); +int vterm_state_getpen(VTermState *state, long args[], int argcount); +void vterm_state_savepen(VTermState *state, int save); + +enum { + C1_SS3 = 0x8f, + C1_DCS = 0x90, + C1_CSI = 0x9b, + C1_ST = 0x9c, + C1_OSC = 0x9d, +}; + +void vterm_state_push_output_sprintf_CSI(VTermState *vts, const char *format, ...); + +void vterm_screen_free(VTermScreen *screen); + +VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation); + +int vterm_unicode_width(uint32_t codepoint); +int vterm_unicode_is_combining(uint32_t codepoint); + +#endif diff --git a/src/libs/3rdparty/libvterm/vterm.pc.in b/src/libs/3rdparty/libvterm/vterm.pc.in new file mode 100644 index 0000000000..681a270d51 --- /dev/null +++ b/src/libs/3rdparty/libvterm/vterm.pc.in @@ -0,0 +1,8 @@ +libdir=@LIBDIR@ +includedir=@INCDIR@ + +Name: vterm +Description: Abstract VT220/Xterm/ECMA-48 emulation library +Version: 0.3.1 +Libs: -L${libdir} -lvterm +Cflags: -I${includedir} diff --git a/src/libs/3rdparty/libvterm/vterm.qbs b/src/libs/3rdparty/libvterm/vterm.qbs new file mode 100644 index 0000000000..274e3a46d6 --- /dev/null +++ b/src/libs/3rdparty/libvterm/vterm.qbs @@ -0,0 +1,33 @@ +Project { + QtcLibrary { + name: "vterm" + type: "staticlibrary" + + Depends { name: "cpp" } + cpp.includePaths: base.concat("include") + + Group { + prefix: "src/" + files: [ + "encoding.c", + "fullwidth.inc", + "keyboard.c", + "mouse.c", + "parser.c", + "pen.c", + "rect.h", + "screen.c", + "state.c", + "unicode.c", + "utf8.h", + "vterm.c", + "vterm_internal.h", + ] + } + + Export { + Depends { name: "cpp" } + cpp.includePaths: base.concat("include") + } + } +} diff --git a/src/libs/3rdparty/winpty/.gitattributes b/src/libs/3rdparty/winpty/.gitattributes new file mode 100644 index 0000000000..36d4c60f1a --- /dev/null +++ b/src/libs/3rdparty/winpty/.gitattributes @@ -0,0 +1,19 @@ +* text=auto +*.bat text eol=crlf +*.c text +*.cc text +*.gyp text +*.gypi text +*.h text +*.ps1 text eol=crlf +*.rst text +*.sh text +*.txt text +.gitignore text +.gitattributes text +Makefile text +configure text + +*.sh eol=lf +configure eol=lf +VERSION.txt eol=lf diff --git a/src/libs/3rdparty/winpty/.gitignore b/src/libs/3rdparty/winpty/.gitignore new file mode 100644 index 0000000000..68c6b47fb3 --- /dev/null +++ b/src/libs/3rdparty/winpty/.gitignore @@ -0,0 +1,16 @@ +*.sln +*.suo +*.vcxproj +*.vcxproj.filters +*.pyc +winpty.sdf +winpty.opensdf +/config.mk +/build +/build-gyp +/build-libpty +/ship/packages +/ship/tmp +/src/Default +/src/Release +/src/gen diff --git a/src/libs/3rdparty/winpty/CMakeLists.txt b/src/libs/3rdparty/winpty/CMakeLists.txt new file mode 100644 index 0000000000..febd4f0ab6 --- /dev/null +++ b/src/libs/3rdparty/winpty/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(src) diff --git a/src/libs/3rdparty/winpty/LICENSE b/src/libs/3rdparty/winpty/LICENSE new file mode 100644 index 0000000000..246fbe0113 --- /dev/null +++ b/src/libs/3rdparty/winpty/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2011-2016 Ryan Prichard + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/src/libs/3rdparty/winpty/README.md b/src/libs/3rdparty/winpty/README.md new file mode 100644 index 0000000000..bc8e7d6e5f --- /dev/null +++ b/src/libs/3rdparty/winpty/README.md @@ -0,0 +1,151 @@ +# winpty + +[![Build Status](https://ci.appveyor.com/api/projects/status/69tb9gylsph1ee1x/branch/master?svg=true)](https://ci.appveyor.com/project/rprichard/winpty/branch/master) + +winpty is a Windows software package providing an interface similar to a Unix +pty-master for communicating with Windows console programs. The package +consists of a library (libwinpty) and a tool for Cygwin and MSYS for running +Windows console programs in a Cygwin/MSYS pty. + +The software works by starting the `winpty-agent.exe` process with a new, +hidden console window, which bridges between the console API and terminal +input/output escape codes. It polls the hidden console's screen buffer for +changes and generates a corresponding stream of output. + +The Unix adapter allows running Windows console programs (e.g. CMD, PowerShell, +IronPython, etc.) under `mintty` or Cygwin's `sshd` with +properly-functioning input (e.g. arrow and function keys) and output (e.g. line +buffering). The library could be also useful for writing a non-Cygwin SSH +server. + +## Supported Windows versions + +winpty runs on Windows XP through Windows 10, including server versions. It +can be compiled into either 32-bit or 64-bit binaries. + +## Cygwin/MSYS adapter (`winpty.exe`) + +### Prerequisites + +You need the following to build winpty: + +* A Cygwin or MSYS installation +* GNU make +* A MinGW g++ toolchain capable of compiling C++11 code to build `winpty.dll` + and `winpty-agent.exe` +* A g++ toolchain targeting Cygwin or MSYS to build `winpty.exe` + +Winpty requires two g++ toolchains as it is split into two parts. The +`winpty.dll` and `winpty-agent.exe` binaries interface with the native +Windows command prompt window so they are compiled with the native MinGW +toolchain. The `winpty.exe` binary interfaces with the MSYS/Cygwin terminal so +it is compiled with the MSYS/Cygwin toolchain. + +MinGW appears to be split into two distributions -- MinGW (creates 32-bit +binaries) and MinGW-w64 (creates both 32-bit and 64-bit binaries). Either +one is generally acceptable. + +#### Cygwin packages + +The default g++ compiler for Cygwin targets Cygwin itself, but Cygwin also +packages MinGW-w64 compilers. As of this writing, the necessary packages are: + +* Either `mingw64-i686-gcc-g++` or `mingw64-x86_64-gcc-g++`. Select the + appropriate compiler for your CPU architecture. +* `gcc-g++` +* `make` + +As of this writing (2016-01-23), only the MinGW-w64 compiler is acceptable. +The MinGW compiler (e.g. from the `mingw-gcc-g++` package) is no longer +maintained and is too buggy. + +#### MSYS packages + +For the original MSYS, use the `mingw-get` tool (MinGW Installation Manager), +and select at least these components: + +* `mingw-developer-toolkit` +* `mingw32-base` +* `mingw32-gcc-g++` +* `msys-base` +* `msys-system-builder` + +When running `./configure`, make sure that `mingw32-g++` is in your +`PATH`. It will be in the `C:\MinGW\bin` directory. + +#### MSYS2 packages + +For MSYS2, use `pacman` and install at least these packages: + +* `msys/gcc` +* `mingw32/mingw-w64-i686-gcc` or `mingw64/mingw-w64-x86_64-gcc`. Select + the appropriate compiler for your CPU architecture. +* `make` + +MSYS2 provides three start menu shortcuts for starting MSYS2: + +* MinGW-w64 Win32 Shell +* MinGW-w64 Win64 Shell +* MSYS2 Shell + +To build winpty, use the MinGW-w64 {Win32,Win64} shortcut of the architecture +matching MSYS2. These shortcuts will put the g++ compiler from the +`{mingw32,mingw64}/mingw-w64-{i686,x86_64}-gcc` packages into the `PATH`. + +Alternatively, instead of installing `mingw32/mingw-w64-i686-gcc` or +`mingw64/mingw-w64-x86_64-gcc`, install the `mingw-w64-cross-gcc` and +`mingw-w64-cross-crt-git` packages. These packages install cross-compilers +into `/opt/bin`, and then any of the three shortcuts will work. + +### Building the Unix adapter + +In the project directory, run `./configure`, then `make`, then `make install`. +By default, winpty is installed into `/usr/local`. Pass `PREFIX=` to +`make install` to override this default. + +### Using the Unix adapter + +To run a Windows console program in `mintty` or Cygwin `sshd`, prepend +`winpty` to the command-line: + + $ winpty powershell + Windows PowerShell + Copyright (C) 2009 Microsoft Corporation. All rights reserved. + + PS C:\rprichard\proj\winpty> 10 + 20 + 30 + PS C:\rprichard\proj\winpty> exit + +## Embedding winpty / MSVC compilation + +See `src/include/winpty.h` for the prototypes of functions exported by +`winpty.dll`. + +Only the `winpty.exe` binary uses Cygwin; all the other binaries work without +it and can be compiled with either MinGW or MSVC. To compile using MSVC, +download gyp and run `gyp -I configurations.gypi` in the `src` subdirectory. +This will generate a `winpty.sln` and associated project files. See the +`src/winpty.gyp` and `src/configurations.gypi` files for notes on dealing with +MSVC versions and different architectures. + +Compiling winpty with MSVC currently requires MSVC 2013 or newer. + +## Debugging winpty + +winpty comes with a tool for collecting timestamped debugging output. To use +it: + +1. Run `winpty-debugserver.exe` on the same computer as winpty. +2. Set the `WINPTY_DEBUG` environment variable to `trace` for the + `winpty.exe` process and/or the process using `libwinpty.dll`. + +winpty also recognizes a `WINPTY_SHOW_CONSOLE` environment variable. Set it +to 1 to prevent winpty from hiding the console window. + +## Copyright + +This project is distributed under the MIT license (see the `LICENSE` file in +the project root). + +By submitting a pull request for this project, you agree to license your +contribution under the MIT license to this project. diff --git a/src/libs/3rdparty/winpty/RELEASES.md b/src/libs/3rdparty/winpty/RELEASES.md new file mode 100644 index 0000000000..768cdf90e3 --- /dev/null +++ b/src/libs/3rdparty/winpty/RELEASES.md @@ -0,0 +1,280 @@ +# Next Version + +Input handling changes: + + * Improve Ctrl-C handling with programs that use unprocessed input. (e.g. + Ctrl-C now cancels input with PowerShell on Windows 10.) + [#116](https://github.com/rprichard/winpty/issues/116) + * Fix a theoretical issue with input event ordering. + [#117](https://github.com/rprichard/winpty/issues/117) + * Ctrl/Shift+{Arrow,Home,End} keys now work with IntelliJ. + [#118](https://github.com/rprichard/winpty/issues/118) + +# Version 0.4.3 (2017-05-17) + +Input handling changes: + + * winpty sets `ENHANCED_KEY` for arrow and navigation keys. This fixes an + issue with the Ruby REPL. + [#99](https://github.com/rprichard/winpty/issues/99) + * AltGr keys are handled better now. + [#109](https://github.com/rprichard/winpty/issues/109) + * In `ENABLE_VIRTUAL_TERMINAL_INPUT` mode, when typing Home/End with a + modifier (e.g. Ctrl), winpty now generates an H/F escape sequence like + `^[[1;5F` rather than a 1/4 escape like `^[[4;5~`. + [#114](https://github.com/rprichard/winpty/issues/114) + +Resizing and scraping fixes: + + * winpty now synthesizes a `WINDOW_BUFFER_SIZE_EVENT` event after resizing + the console to better propagate window size changes to console programs. + In particular, this affects WSL and Cygwin. + [#110](https://github.com/rprichard/winpty/issues/110) + * Better handling of resizing for certain full-screen programs, like + WSL less. + [#112](https://github.com/rprichard/winpty/issues/112) + * Hide the cursor if it's currently outside the console window. This change + fixes an issue with Far Manager. + [#113](https://github.com/rprichard/winpty/issues/113) + * winpty now avoids using console fonts smaller than 5px high to improve + half-vs-full-width character handling. See + https://github.com/Microsoft/vscode/issues/19665. + [b4db322010](https://github.com/rprichard/winpty/commit/b4db322010d2d897e6c496fefc4f0ecc9b84c2f3) + +Cygwin/MSYS adapter fix: + + * The way the `winpty` Cygwin/MSYS2 adapter searches for the program to + launch changed. It now resolves symlinks and searches the PATH explicitly. + [#81](https://github.com/rprichard/winpty/issues/81) + [#98](https://github.com/rprichard/winpty/issues/98) + +This release does not include binaries for the old MSYS1 project anymore. +MSYS2 will continue to be supported. See +https://github.com/rprichard/winpty/issues/97. + +# Version 0.4.2 (2017-01-18) + +This release improves WSL support (i.e. Bash-on-Windows): + + * winpty generates more correct input escape sequences for WSL programs that + enable an alternate input mode using DECCKM. This bug affected arrow keys + and Home/End in WSL programs such as `vim`, `mc`, and `less`. + [#90](https://github.com/rprichard/winpty/issues/90) + * winpty now recognizes the `COMMON_LVB_REVERSE_VIDEO` and + `COMMON_LVB_UNDERSCORE` text attributes. The Windows console uses these + attributes to implement the SGR.4(Underline) and SGR.7(Negative) modes in + its VT handling. This change affects WSL pager status bars, man pages, etc. + +The build system no longer has a "version suffix" mechanism, so passing +`VERSION_SUFFIX=` to make or `-D VERSION_SUFFIX=` to gyp now +has no effect. AFAIK, the mechanism was never used publicly. +[67a34b6c03](https://github.com/rprichard/winpty/commit/67a34b6c03557a5c2e0a2bdd502c2210921d8f3e) + +# Version 0.4.1 (2017-01-03) + +Bug fixes: + + * This version fixes a bug where the `winpty-agent.exe` process could read + past the end of a buffer. + [#94](https://github.com/rprichard/winpty/issues/94) + +# Version 0.4.0 (2016-06-28) + +The winpty library has a new API that should be easier for embedding. +[880c00c69e](https://github.com/rprichard/winpty/commit/880c00c69eeca73643ddb576f02c5badbec81f56) + +User-visible changes: + + * winpty now automatically puts the terminal into mouse mode when it detects + that the console has left QuickEdit mode. The `--mouse` option still forces + the terminal into mouse mode. In principle, an option could be added to + suppress terminal mode, but hopefully it won't be necessary. There is a + script in the `misc` subdirectory, `misc/ConinMode.ps1`, that can change + the QuickEdit mode from the command-line. + * winpty now passes keyboard escapes to `bash.exe` in the Windows Subsystem + for Linux. + [#82](https://github.com/rprichard/winpty/issues/82) + +Bug fixes: + + * By default, `winpty.dll` avoids calling `SetProcessWindowStation` within + the calling process. + [#58](https://github.com/rprichard/winpty/issues/58) + * Fixed an uninitialized memory bug that could have crashed winpty. + [#80](https://github.com/rprichard/winpty/issues/80) + * winpty now works better with very large and very small terminal windows. + It resizes the console font according to the number of columns. + [#61](https://github.com/rprichard/winpty/issues/61) + * winpty no longer uses Mark to freeze the console on Windows 10. The Mark + command could interfere with the cursor position, corrupting the data in + the screen buffer. + [#79](https://github.com/rprichard/winpty/issues/79) + +# Version 0.3.0 (2016-05-20) + +User-visible changes: + + * The UNIX adapter is renamed from `console.exe` to `winpty.exe` to be + consistent with MSYS2. The name `winpty.exe` is less likely to conflict + with another program and is easier to search for online (e.g. for someone + unfamiliar with winpty). + * The UNIX adapter now clears the `TERM` variable. + [#43](https://github.com/rprichard/winpty/issues/43) + * An escape character appearing in a console screen buffer cell is converted + to a '?'. + [#47](https://github.com/rprichard/winpty/issues/47) + +Bug fixes: + + * A major bug affecting XP users was fixed. + [#67](https://github.com/rprichard/winpty/issues/67) + * Fixed an incompatibility with ConEmu where winpty hung if ConEmu's + "Process 'start'" feature was enabled. + [#70](https://github.com/rprichard/winpty/issues/70) + * Fixed a bug where `cmd.exe` sometimes printed the message, + `Not enough storage is available to process this command.`. + [#74](https://github.com/rprichard/winpty/issues/74) + +Many changes internally: + + * The codebase is switched from C++03 to C++11 and uses exceptions internally. + No exceptions are thrown across the C APIs defined in `winpty.h`. + * This version drops support for the original MinGW compiler packaged with + Cygwin (`i686-pc-mingw32-g++`). The MinGW-w64 compiler is still supported, + as is the MinGW distributed at mingw.org. Compiling with MSVC now requires + MSVC 2013 or newer. Windows XP is still supported. + [ec3eae8df5](https://github.com/rprichard/winpty/commit/ec3eae8df5bbbb36d7628d168b0815638d122f37) + * Pipe security is improved. winpty works harder to produce unique pipe names + and includes a random component in the name. winpty secures pipes with a + DACL that prevents arbitrary users from connecting to its pipes. winpty now + passes `PIPE_REJECT_REMOTE_CLIENTS` on Vista and up, and it verifies that + the pipe client PID is correct, again on Vista and up. When connecting to a + named pipe, winpty uses the `SECURITY_IDENTIFICATION` flag to restrict + impersonation. Previous versions *should* still be secure. + * `winpty-debugserver.exe` now has an `--everyone` flag that allows capturing + debug output from other users. + * The code now compiles cleanly with MSVC's "Security Development Lifecycle" + (`/SDL`) checks enabled. + +# Version 0.2.2 (2016-02-25) + +Minor bug fixes and enhancements: + + * Fix a bug that generated spurious mouse input records when an incomplete + mouse escape sequence was seen. + * Fix a buffer overflow bug in `winpty-debugserver.exe` affecting messages of + exactly 4096 bytes. + * For MSVC builds, add a `src/configurations.gypi` file that can be included + on the gyp command-line to enable 32-bit and 64-bit builds. + * `winpty-agent --show-input` mode: Flush stdout after each line. + * Makefile builds: generate a `build/winpty.lib` import library to accompany + `build/winpty.dll`. + +# Version 0.2.1 (2015-12-19) + + * The main project source was moved into a `src` directory for better code + organization and to fix + [#51](https://github.com/rprichard/winpty/issues/51). + * winpty recognizes many more escape sequences, including: + * putty/rxvt's F1-F4 keys + [#40](https://github.com/rprichard/winpty/issues/40) + * the Linux virtual console's F1-F5 keys + * the "application numpad" keys (e.g. enabled with DECPAM) + * Fixed handling of Shift-Alt-O and Alt-[. + * Added support for mouse input. The UNIX adapter has a `--mouse` argument + that puts the terminal into mouse mode, but the agent recognizes mouse + input even without the argument. The agent recognizes double-clicks using + Windows' double-click interval setting (i.e. GetDoubleClickTime). + [#57](https://github.com/rprichard/winpty/issues/57) + +Changes to debugging interfaces: + + * The `WINPTY_DEBUG` variable is now a comma-separated list. The old + behavior (i.e. tracing) is enabled with `WINPTY_DEBUG=trace`. + * The UNIX adapter program now has a `--showkey` argument that dumps input + bytes. + * The `winpty-agent.exe` program has a `--show-input` argument that dumps + `INPUT_RECORD` records. (It omits mouse events unless `--with-mouse` is + also specified.) The agent also responds to `WINPTY_DEBUG=trace,input`, + which logs input bytes and synthesized console events, and it responds to + `WINPTY_DEBUG=trace,dump_input_map`, which dumps the internal table of + escape sequences. + +# Version 0.2.0 (2015-11-13) + +No changes to the API, but many small changes to the implementation. The big +changes include: + + * Support for 64-bit Cygwin and MSYS2 + * Support for Windows 10 + * Better Unicode support (especially East Asian languages) + +Details: + + * The `configure` script recognizes 64-bit Cygwin and MSYS2 environments and + selects the appropriate compiler. + * winpty works much better with the upgraded console in Windows 10. The + `conhost.exe` hang can still occur, but only with certain programs, and + is much less likely to occur. With the new console, use Mark instead of + SelectAll, for better performance. + [#31](https://github.com/rprichard/winpty/issues/31) + [#30](https://github.com/rprichard/winpty/issues/30) + [#53](https://github.com/rprichard/winpty/issues/53) + * The UNIX adapter now calls `setlocale(LC_ALL, "")` to set the locale. + * Improved Unicode support. When a console is started with an East Asian code + page, winpty now chooses an East Asian font rather than Consolas / Lucida + Console. Selecting the right font helps synchronize character widths + between the console and terminal. (It's not perfect, though.) + [#41](https://github.com/rprichard/winpty/issues/41) + * winpty now more-or-less works with programs that change the screen buffer + or resize the original screen buffer. If the screen buffer height changes, + winpty switches to a "direct mode", where it makes no effort to track + scrolling. In direct mode, it merely syncs snapshots of the console to the + terminal. Caveats: + * Changing the screen buffer (i.e. `SetConsoleActiveScreenBuffer`) + breaks winpty on Windows 7. This problem can eventually be mitigated, + but never completely fixed, due to Windows 7 bugginess. + * Resizing the original screen buffer can hang `conhost.exe` on Windows 10. + Enabling the legacy console is a workaround. + * If a program changes the screen buffer and then exits, relying on the OS + to restore the original screen buffer, that restoration probably will not + happen with winpty. winpty's behavior can probably be improved here. + * Improved color handling: + * DkGray-on-Black text was previously hiddenly completely. Now it is + output as DkGray, with a fallback to LtGray on terminals that don't + recognize the intense colors. + [#39](https://github.com/rprichard/winpty/issues/39). + * The console is always initialized to LtGray-on-Black, regardless of the + user setting, which matches the console color heuristic, which translates + LtGray-on-Black to "reset SGR parameters." + * Shift-Tab is recognized correctly now. + [#19](https://github.com/rprichard/winpty/issues/19) + * Add a `--version` argument to `winpty-agent.exe` and the UNIX adapter. The + argument reports the nominal version (i.e. the `VERSION.txt`) file, with a + "VERSION_SUFFIX" appended (defaulted to `-dev`), and a git commit hash, if + the `git` command successfully reports a hash during the build. The `git` + command is invoked by either `make` or `gyp`. + * The agent now combines `ReadConsoleOutputW` calls when it polls the console + buffer for changes, which may slightly reduce its CPU overhead. + [#44](https://github.com/rprichard/winpty/issues/44). + * A `gyp` file is added to help compile with MSVC. + * The code can now be compiled as C++11 code, though it isn't by default. + [bde8922e08](https://github.com/rprichard/winpty/commit/bde8922e08c3638e01ecc7b581b676c314163e3c) + * If winpty can't create a new window station, it charges ahead rather than + aborting. This situation might happen if winpty were started from an SSH + session. + * Debugging improvements: + * `WINPTYDBG` is renamed to `WINPTY_DEBUG`, and a new `WINPTY_SHOW_CONSOLE` + variable keeps the underlying console visible. + * A `winpty-debugserver.exe` program is built and shipped by default. It + collects the trace output enabled with `WINPTY_DEBUG`. + * The `Makefile` build of winpty now compiles `winpty-agent.exe` and + `winpty.dll` with -O2. + +# Version 0.1.1 (2012-07-28) + +Minor bugfix release. + +# Version 0.1 (2012-04-17) + +Initial release. diff --git a/src/libs/3rdparty/winpty/VERSION.txt b/src/libs/3rdparty/winpty/VERSION.txt new file mode 100644 index 0000000000..5d47ff8c45 --- /dev/null +++ b/src/libs/3rdparty/winpty/VERSION.txt @@ -0,0 +1 @@ +0.4.4-dev diff --git a/src/libs/3rdparty/winpty/appveyor.yml b/src/libs/3rdparty/winpty/appveyor.yml new file mode 100644 index 0000000000..a9e8726fc1 --- /dev/null +++ b/src/libs/3rdparty/winpty/appveyor.yml @@ -0,0 +1,16 @@ +image: Visual Studio 2015 + +init: + - C:\msys64\usr\bin\bash --login -c "pacman -S --needed --noconfirm --noprogressbar msys/make msys/tar msys/gcc mingw-w64-cross-toolchain" + - C:\cygwin\setup-x86 -q -P mingw64-i686-gcc-g++,mingw64-x86_64-gcc-g++,make + - C:\cygwin64\setup-x86_64 -q -P mingw64-i686-gcc-g++,mingw64-x86_64-gcc-g++,make + +build_script: + - C:\Python27-x64\python.exe ship\ship.py --kind msys2 --arch x64 --syspath C:\msys64 + - C:\Python27-x64\python.exe ship\ship.py --kind cygwin --arch ia32 --syspath C:\cygwin + - C:\Python27-x64\python.exe ship\ship.py --kind cygwin --arch x64 --syspath C:\cygwin64 + - C:\Python27-x64\python.exe ship\make_msvc_package.py + +artifacts: + - path: ship\packages\*.tar.gz + - path: ship\packages\*.zip diff --git a/src/libs/3rdparty/winpty/configure b/src/libs/3rdparty/winpty/configure new file mode 100644 index 0000000000..6d37d65b09 --- /dev/null +++ b/src/libs/3rdparty/winpty/configure @@ -0,0 +1,167 @@ +#!/bin/bash +# +# Copyright (c) 2011-2015 Ryan Prichard +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +# +# findTool(desc, commandList) +# +# Searches commandLine for the first command in the PATH and returns it. +# Prints an error and aborts the script if no match is found. +# +FINDTOOL_OUT="" +function findTool { + DESC=$1 + OPTIONS=$2 + for CMD in ${OPTIONS}; do + if (which $CMD &>/dev/null) then + echo "Found $DESC: $CMD" + FINDTOOL_OUT="$CMD" + return + fi + done + echo "Error: could not find $DESC. One of these should be in your PATH:" + for CMD in ${OPTIONS}; do + echo " * $CMD" + done + exit 1 +} + +IS_CYGWIN=0 +IS_MSYS1=0 +IS_MSYS2=0 + +# Link parts of the Cygwin binary statically to aid in redistribution? The +# binary still links dynamically against the main DLL. The MinGW binaries are +# also statically linked and therefore depend only on Windows DLLs. I started +# linking the Cygwin/MSYS binary statically, because G++ 4.7 changed the +# Windows C++ ABI. +UNIX_LDFLAGS_STATIC='-static -static-libgcc -static-libstdc++' + +# Detect the environment -- Cygwin or MSYS. +case $(uname -s) in + CYGWIN*) + echo 'uname -s identifies a Cygwin environment.' + IS_CYGWIN=1 + case $(uname -m) in + i686) + echo 'uname -m identifies an i686 environment.' + UNIX_CXX=i686-pc-cygwin-g++ + MINGW_CXX=i686-w64-mingw32-g++ + ;; + x86_64) + echo 'uname -m identifies an x86_64 environment.' + UNIX_CXX=x86_64-pc-cygwin-g++ + MINGW_CXX=x86_64-w64-mingw32-g++ + ;; + *) + echo 'Error: uname -m did not match either i686 or x86_64.' + exit 1 + ;; + esac + ;; + MSYS*|MINGW*) + # MSYS2 notes: + # - MSYS2 offers two shortcuts to open an environment: + # - MinGW-w64 Win32 Shell. This env reports a `uname -s` of + # MINGW32_NT-6.1 on 32-bit Win7. The MinGW-w64 compiler + # (i686-w64-mingw32-g++.exe) is in the PATH. + # - MSYS2 Shell. `uname -s` instead reports MSYS_NT-6.1. + # The i686-w64-mingw32-g++ compiler is not in the PATH. + # - MSYS2 appears to use MinGW-w64, not the older mingw.org. + # MSYS notes: + # - `uname -s` is always MINGW32_NT-6.1 on Win7. + echo 'uname -s identifies an MSYS/MSYS2 environment.' + case $(uname -m) in + i686) + echo 'uname -m identifies an i686 environment.' + UNIX_CXX=i686-pc-msys-g++ + if echo "$(uname -r)" | grep '^1[.]' > /dev/null; then + # The MSYS-targeting compiler for the original 32-bit-only + # MSYS does not recognize the -static-libstdc++ flag, and + # it does not work with -static, because it tries to link + # statically with the core MSYS library and fails. + # + # Distinguish between the two using the major version + # number of `uname -r`: + # + # MSYS uname -r: 1.0.18(0.48/3/2) + # MSYS2 uname -r: 2.0.0(0.284/5/3) + # + # This is suboptimal because MSYS2 is not actually the + # second version of MSYS--it's a brand-new fork of Cygwin. + # + IS_MSYS1=1 + UNIX_LDFLAGS_STATIC= + MINGW_CXX=mingw32-g++ + else + IS_MSYS2=1 + MINGW_CXX=i686-w64-mingw32-g++.exe + fi + ;; + x86_64) + echo 'uname -m identifies an x86_64 environment.' + IS_MSYS2=1 + UNIX_CXX=x86_64-pc-msys-g++ + MINGW_CXX=x86_64-w64-mingw32-g++ + ;; + *) + echo 'Error: uname -m did not match either i686 or x86_64.' + exit 1 + ;; + esac + ;; + *) + echo 'Error: uname -s did not match either CYGWIN* or MINGW*.' + exit 1 + ;; +esac + +# Search the PATH and pick the first match. +findTool "Cygwin/MSYS G++ compiler" "$UNIX_CXX" +UNIX_CXX=$FINDTOOL_OUT +findTool "MinGW G++ compiler" "$MINGW_CXX" +MINGW_CXX=$FINDTOOL_OUT + +# Write config files. +echo Writing config.mk +echo UNIX_CXX=$UNIX_CXX > config.mk +echo UNIX_LDFLAGS_STATIC=$UNIX_LDFLAGS_STATIC >> config.mk +echo MINGW_CXX=$MINGW_CXX >> config.mk + +if test $IS_MSYS1 = 1; then + echo UNIX_CXXFLAGS += -DWINPTY_TARGET_MSYS1 >> config.mk + # The MSYS1 MinGW compiler has a bug that prevents inclusion of algorithm + # and math.h in normal C++11 mode. The workaround is to enable the gnu++11 + # mode instead. The bug was fixed on 2015-07-31, but as of 2016-02-26, the + # fix apparently hasn't been released. See + # http://ehc.ac/p/mingw/bugs/2250/. + echo MINGW_ENABLE_CXX11_FLAG := -std=gnu++11 >> config.mk +fi + +if test -d .git -a -f .git/HEAD -a -f .git/index && git rev-parse HEAD >&/dev/null; then + echo "Commit info: git" + echo 'COMMIT_HASH = $(shell git rev-parse HEAD)' >> config.mk + echo 'COMMIT_HASH_DEP := config.mk .git/HEAD .git/index' >> config.mk +else + echo "Commit info: none" + echo 'COMMIT_HASH := none' >> config.mk + echo 'COMMIT_HASH_DEP := config.mk' >> config.mk +fi diff --git a/src/libs/3rdparty/winpty/misc/.gitignore b/src/libs/3rdparty/winpty/misc/.gitignore new file mode 100644 index 0000000000..23751645fa --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/.gitignore @@ -0,0 +1,2 @@ +*.exe +UnixEcho \ No newline at end of file diff --git a/src/libs/3rdparty/winpty/misc/BufferResizeTests.cc b/src/libs/3rdparty/winpty/misc/BufferResizeTests.cc new file mode 100644 index 0000000000..a5bb074826 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/BufferResizeTests.cc @@ -0,0 +1,90 @@ +#include +#include + +#include "TestUtil.cc" + +void dumpInfoToTrace() { + CONSOLE_SCREEN_BUFFER_INFO info; + assert(GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info)); + trace("win=(%d,%d,%d,%d)", + (int)info.srWindow.Left, + (int)info.srWindow.Top, + (int)info.srWindow.Right, + (int)info.srWindow.Bottom); + trace("buf=(%d,%d)", + (int)info.dwSize.X, + (int)info.dwSize.Y); + trace("cur=(%d,%d)", + (int)info.dwCursorPosition.X, + (int)info.dwCursorPosition.Y); +} + +int main(int argc, char *argv[]) { + if (argc == 1) { + startChildProcess(L"CHILD"); + return 0; + } + + setWindowPos(0, 0, 1, 1); + + if (false) { + // Reducing the buffer height can move the window up. + setBufferSize(80, 25); + setWindowPos(0, 20, 80, 5); + Sleep(2000); + setBufferSize(80, 10); + } + + if (false) { + // Reducing the buffer height moves the window up and the buffer + // contents up too. + setBufferSize(80, 25); + setWindowPos(0, 20, 80, 5); + setCursorPos(0, 20); + printf("TEST1\nTEST2\nTEST3\nTEST4\n"); + fflush(stdout); + Sleep(2000); + setBufferSize(80, 10); + } + + if (false) { + // Reducing the buffer width can move the window left. + setBufferSize(80, 25); + setWindowPos(40, 0, 40, 25); + Sleep(2000); + setBufferSize(60, 25); + } + + if (false) { + // Sometimes the buffer contents are shifted up; sometimes they're + // shifted down. It seems to depend on the cursor position? + + // setBufferSize(80, 25); + // setWindowPos(0, 20, 80, 5); + // setCursorPos(0, 20); + // printf("TESTa\nTESTb\nTESTc\nTESTd\nTESTe"); + // fflush(stdout); + // setCursorPos(0, 0); + // printf("TEST1\nTEST2\nTEST3\nTEST4\nTEST5"); + // fflush(stdout); + // setCursorPos(0, 24); + // Sleep(5000); + // setBufferSize(80, 24); + + setBufferSize(80, 20); + setWindowPos(0, 10, 80, 10); + setCursorPos(0, 18); + + printf("TEST1\nTEST2"); + fflush(stdout); + setCursorPos(0, 18); + + Sleep(2000); + setBufferSize(80, 18); + } + + dumpInfoToTrace(); + Sleep(30000); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/ChangeScreenBuffer.cc b/src/libs/3rdparty/winpty/misc/ChangeScreenBuffer.cc new file mode 100644 index 0000000000..701a2cb4a3 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/ChangeScreenBuffer.cc @@ -0,0 +1,53 @@ +// A test program for CreateConsoleScreenBuffer / SetConsoleActiveScreenBuffer +// + +#include +#include +#include +#include +#include + +#include "TestUtil.cc" + +int main() +{ + HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE); + HANDLE childBuffer = CreateConsoleScreenBuffer( + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, CONSOLE_TEXTMODE_BUFFER, NULL); + + SetConsoleActiveScreenBuffer(childBuffer); + + while (true) { + char buf[1024]; + CONSOLE_SCREEN_BUFFER_INFO info; + + assert(GetConsoleScreenBufferInfo(origBuffer, &info)); + trace("child.size=(%d,%d)", (int)info.dwSize.X, (int)info.dwSize.Y); + trace("child.cursor=(%d,%d)", (int)info.dwCursorPosition.X, (int)info.dwCursorPosition.Y); + trace("child.window=(%d,%d,%d,%d)", + (int)info.srWindow.Left, (int)info.srWindow.Top, + (int)info.srWindow.Right, (int)info.srWindow.Bottom); + trace("child.maxSize=(%d,%d)", (int)info.dwMaximumWindowSize.X, (int)info.dwMaximumWindowSize.Y); + + int ch = getch(); + sprintf(buf, "%02x\n", ch); + DWORD actual = 0; + WriteFile(childBuffer, buf, strlen(buf), &actual, NULL); + if (ch == 0x1b/*ESC*/ || ch == 0x03/*CTRL-C*/) + break; + + if (ch == 'b') { + setBufferSize(origBuffer, 40, 25); + } else if (ch == 'w') { + setWindowPos(origBuffer, 1, 1, 38, 23); + } else if (ch == 'c') { + setCursorPos(origBuffer, 10, 10); + } + } + + SetConsoleActiveScreenBuffer(origBuffer); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/ClearConsole.cc b/src/libs/3rdparty/winpty/misc/ClearConsole.cc new file mode 100644 index 0000000000..f95f8c84ca --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/ClearConsole.cc @@ -0,0 +1,72 @@ +/* + * Demonstrates that console clearing sets each cell's character to SP, not + * NUL, and it sets the attribute of each cell to the current text attribute. + * + * This confirms the MSDN instruction in the "Clearing the Screen" article. + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms682022(v=vs.85).aspx + * It advises using GetConsoleScreenBufferInfo to get the current text + * attribute, then FillConsoleOutputCharacter and FillConsoleOutputAttribute to + * write to the console buffer. + */ + +#include + +#include +#include +#include + +#include "TestUtil.cc" + +int main(int argc, char *argv[]) { + if (argc == 1) { + startChildProcess(L"CHILD"); + return 0; + } + + const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE); + + SetConsoleTextAttribute(conout, 0x24); + system("cls"); + + setWindowPos(0, 0, 1, 1); + setBufferSize(80, 25); + setWindowPos(0, 0, 80, 25); + + CHAR_INFO buf; + COORD bufSize = { 1, 1 }; + COORD bufCoord = { 0, 0 }; + SMALL_RECT rect = { 5, 5, 5, 5 }; + BOOL ret; + DWORD actual; + COORD writeCoord = { 5, 5 }; + + // After cls, each cell's character is a space, and its attributes are the + // default text attributes. + ret = ReadConsoleOutputW(conout, &buf, bufSize, bufCoord, &rect); + assert(ret && buf.Char.UnicodeChar == L' ' && buf.Attributes == 0x24); + + // Nevertheless, it is possible to change a cell to NUL. + ret = FillConsoleOutputCharacterW(conout, L'\0', 1, writeCoord, &actual); + assert(ret && actual == 1); + ret = ReadConsoleOutputW(conout, &buf, bufSize, bufCoord, &rect); + assert(ret && buf.Char.UnicodeChar == L'\0' && buf.Attributes == 0x24); + + // As well as a 0 attribute. (As one would expect, the cell is + // black-on-black.) + ret = FillConsoleOutputAttribute(conout, 0, 1, writeCoord, &actual); + assert(ret && actual == 1); + ret = ReadConsoleOutputW(conout, &buf, bufSize, bufCoord, &rect); + assert(ret && buf.Char.UnicodeChar == L'\0' && buf.Attributes == 0); + ret = FillConsoleOutputCharacterW(conout, L'X', 1, writeCoord, &actual); + assert(ret && actual == 1); + ret = ReadConsoleOutputW(conout, &buf, bufSize, bufCoord, &rect); + assert(ret && buf.Char.UnicodeChar == L'X' && buf.Attributes == 0); + + // The 'X' is invisible. + countDown(3); + + ret = FillConsoleOutputAttribute(conout, 0x42, 1, writeCoord, &actual); + assert(ret && actual == 1); + + countDown(5); +} diff --git a/src/libs/3rdparty/winpty/misc/ConinMode.cc b/src/libs/3rdparty/winpty/misc/ConinMode.cc new file mode 100644 index 0000000000..1e1428d8b0 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/ConinMode.cc @@ -0,0 +1,117 @@ +#include + +#include +#include +#include + +#include +#include + +static HANDLE getConin() { + HANDLE conin = GetStdHandle(STD_INPUT_HANDLE); + if (conin == INVALID_HANDLE_VALUE) { + fprintf(stderr, "error: cannot get stdin\n"); + exit(1); + } + return conin; +} + +static DWORD getConsoleMode() { + DWORD mode = 0; + if (!GetConsoleMode(getConin(), &mode)) { + fprintf(stderr, "error: GetConsoleMode failed (is stdin a console?)\n"); + exit(1); + } + return mode; +} + +static void setConsoleMode(DWORD mode) { + if (!SetConsoleMode(getConin(), mode)) { + fprintf(stderr, "error: SetConsoleMode failed (is stdin a console?)\n"); + exit(1); + } +} + +static long parseInt(const std::string &s) { + errno = 0; + char *endptr = nullptr; + long result = strtol(s.c_str(), &endptr, 0); + if (errno != 0 || !endptr || *endptr != '\0') { + fprintf(stderr, "error: could not parse integral argument '%s'\n", s.c_str()); + exit(1); + } + return result; +} + +static void usage() { + printf("Usage: ConinMode [verb] [options]\n"); + printf("Verbs:\n"); + printf(" [info] Dumps info about mode flags.\n"); + printf(" get Prints the mode DWORD.\n"); + printf(" set VALUE Sets the mode to VALUE, which can be decimal, hex, or octal.\n"); + printf(" set VALUE MASK\n"); + printf(" Same as `set VALUE`, but only alters the bits in MASK.\n"); + exit(1); +} + +struct { + const char *name; + DWORD value; +} kInputFlags[] = { + "ENABLE_PROCESSED_INPUT", ENABLE_PROCESSED_INPUT, // 0x0001 + "ENABLE_LINE_INPUT", ENABLE_LINE_INPUT, // 0x0002 + "ENABLE_ECHO_INPUT", ENABLE_ECHO_INPUT, // 0x0004 + "ENABLE_WINDOW_INPUT", ENABLE_WINDOW_INPUT, // 0x0008 + "ENABLE_MOUSE_INPUT", ENABLE_MOUSE_INPUT, // 0x0010 + "ENABLE_INSERT_MODE", ENABLE_INSERT_MODE, // 0x0020 + "ENABLE_QUICK_EDIT_MODE", ENABLE_QUICK_EDIT_MODE, // 0x0040 + "ENABLE_EXTENDED_FLAGS", ENABLE_EXTENDED_FLAGS, // 0x0080 + "ENABLE_VIRTUAL_TERMINAL_INPUT", 0x0200/*ENABLE_VIRTUAL_TERMINAL_INPUT*/, // 0x0200 +}; + +int main(int argc, char *argv[]) { + std::vector args; + for (size_t i = 1; i < argc; ++i) { + args.push_back(argv[i]); + } + + if (args.empty() || args.size() == 1 && args[0] == "info") { + DWORD mode = getConsoleMode(); + printf("mode: 0x%lx\n", mode); + for (const auto &flag : kInputFlags) { + printf("%-29s 0x%04lx %s\n", flag.name, flag.value, flag.value & mode ? "ON" : "off"); + mode &= ~flag.value; + } + for (int i = 0; i < 32; ++i) { + if (mode & (1u << i)) { + printf("Unrecognized flag: %04x\n", (1u << i)); + } + } + return 0; + } + + const auto verb = args[0]; + + if (verb == "set") { + if (args.size() == 2) { + const DWORD newMode = parseInt(args[1]); + setConsoleMode(newMode); + } else if (args.size() == 3) { + const DWORD mode = parseInt(args[1]); + const DWORD mask = parseInt(args[2]); + const int newMode = (getConsoleMode() & ~mask) | (mode & mask); + setConsoleMode(newMode); + } else { + usage(); + } + } else if (verb == "get") { + if (args.size() != 1) { + usage(); + } + printf("0x%lx\n", getConsoleMode()); + } else { + usage(); + } + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/ConinMode.ps1 b/src/libs/3rdparty/winpty/misc/ConinMode.ps1 new file mode 100644 index 0000000000..ecfe8f039e --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/ConinMode.ps1 @@ -0,0 +1,116 @@ +# +# PowerShell script for controlling the console QuickEdit and InsertMode flags. +# +# Turn QuickEdit off to interact with mouse-driven console programs. +# +# Usage: +# +# powershell .\ConinMode.ps1 [Options] +# +# Options: +# -QuickEdit [on/off] +# -InsertMode [on/off] +# -Mode [integer] +# + +param ( + [ValidateSet("on", "off")][string] $QuickEdit, + [ValidateSet("on", "off")][string] $InsertMode, + [int] $Mode +) + +$signature = @' +[DllImport("kernel32.dll", SetLastError = true)] +public static extern IntPtr GetStdHandle(int nStdHandle); + +[DllImport("kernel32.dll", SetLastError = true)] +public static extern uint GetConsoleMode( + IntPtr hConsoleHandle, + out uint lpMode); + +[DllImport("kernel32.dll", SetLastError = true)] +public static extern uint SetConsoleMode( + IntPtr hConsoleHandle, + uint dwMode); + +public const int STD_INPUT_HANDLE = -10; +public const int ENABLE_INSERT_MODE = 0x0020; +public const int ENABLE_QUICK_EDIT_MODE = 0x0040; +public const int ENABLE_EXTENDED_FLAGS = 0x0080; +'@ + +$WinAPI = Add-Type -MemberDefinition $signature ` + -Name WinAPI -Namespace ConinModeScript ` + -PassThru + +function GetConIn { + $ret = $WinAPI::GetStdHandle($WinAPI::STD_INPUT_HANDLE) + if ($ret -eq -1) { + throw "error: cannot get stdin" + } + return $ret +} + +function GetConsoleMode { + $conin = GetConIn + $mode = 0 + $ret = $WinAPI::GetConsoleMode($conin, [ref]$mode) + if ($ret -eq 0) { + throw "GetConsoleMode failed (is stdin a console?)" + } + return $mode +} + +function SetConsoleMode($mode) { + $conin = GetConIn + $ret = $WinAPI::SetConsoleMode($conin, $mode) + if ($ret -eq 0) { + throw "SetConsoleMode failed (is stdin a console?)" + } +} + +$oldMode = GetConsoleMode +$newMode = $oldMode +$doingSomething = $false + +if ($PSBoundParameters.ContainsKey("Mode")) { + $newMode = $Mode + $doingSomething = $true +} + +if ($QuickEdit + $InsertMode -ne "") { + if (!($newMode -band $WinAPI::ENABLE_EXTENDED_FLAGS)) { + # We can't enable an extended flag without overwriting the existing + # QuickEdit/InsertMode flags. AFAICT, there is no way to query their + # existing values, so at least we can choose sensible defaults. + $newMode = $newMode -bor $WinAPI::ENABLE_EXTENDED_FLAGS + $newMode = $newMode -bor $WinAPI::ENABLE_QUICK_EDIT_MODE + $newMode = $newMode -bor $WinAPI::ENABLE_INSERT_MODE + $doingSomething = $true + } +} + +if ($QuickEdit -eq "on") { + $newMode = $newMode -bor $WinAPI::ENABLE_QUICK_EDIT_MODE + $doingSomething = $true +} elseif ($QuickEdit -eq "off") { + $newMode = $newMode -band (-bnot $WinAPI::ENABLE_QUICK_EDIT_MODE) + $doingSomething = $true +} + +if ($InsertMode -eq "on") { + $newMode = $newMode -bor $WinAPI::ENABLE_INSERT_MODE + $doingSomething = $true +} elseif ($InsertMode -eq "off") { + $newMode = $newMode -band (-bnot $WinAPI::ENABLE_INSERT_MODE) + $doingSomething = $true +} + +if ($doingSomething) { + echo "old mode: $oldMode" + SetConsoleMode $newMode + $newMode = GetConsoleMode + echo "new mode: $newMode" +} else { + echo "mode: $oldMode" +} diff --git a/src/libs/3rdparty/winpty/misc/ConoutMode.cc b/src/libs/3rdparty/winpty/misc/ConoutMode.cc new file mode 100644 index 0000000000..100e0c7bea --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/ConoutMode.cc @@ -0,0 +1,113 @@ +#include + +#include +#include +#include + +#include +#include + +static HANDLE getConout() { + HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE); + if (conout == INVALID_HANDLE_VALUE) { + fprintf(stderr, "error: cannot get stdout\n"); + exit(1); + } + return conout; +} + +static DWORD getConsoleMode() { + DWORD mode = 0; + if (!GetConsoleMode(getConout(), &mode)) { + fprintf(stderr, "error: GetConsoleMode failed (is stdout a console?)\n"); + exit(1); + } + return mode; +} + +static void setConsoleMode(DWORD mode) { + if (!SetConsoleMode(getConout(), mode)) { + fprintf(stderr, "error: SetConsoleMode failed (is stdout a console?)\n"); + exit(1); + } +} + +static long parseInt(const std::string &s) { + errno = 0; + char *endptr = nullptr; + long result = strtol(s.c_str(), &endptr, 0); + if (errno != 0 || !endptr || *endptr != '\0') { + fprintf(stderr, "error: could not parse integral argument '%s'\n", s.c_str()); + exit(1); + } + return result; +} + +static void usage() { + printf("Usage: ConoutMode [verb] [options]\n"); + printf("Verbs:\n"); + printf(" [info] Dumps info about mode flags.\n"); + printf(" get Prints the mode DWORD.\n"); + printf(" set VALUE Sets the mode to VALUE, which can be decimal, hex, or octal.\n"); + printf(" set VALUE MASK\n"); + printf(" Same as `set VALUE`, but only alters the bits in MASK.\n"); + exit(1); +} + +struct { + const char *name; + DWORD value; +} kOutputFlags[] = { + "ENABLE_PROCESSED_OUTPUT", ENABLE_PROCESSED_OUTPUT, // 0x0001 + "ENABLE_WRAP_AT_EOL_OUTPUT", ENABLE_WRAP_AT_EOL_OUTPUT, // 0x0002 + "ENABLE_VIRTUAL_TERMINAL_PROCESSING", 0x0004/*ENABLE_VIRTUAL_TERMINAL_PROCESSING*/, // 0x0004 + "DISABLE_NEWLINE_AUTO_RETURN", 0x0008/*DISABLE_NEWLINE_AUTO_RETURN*/, // 0x0008 + "ENABLE_LVB_GRID_WORLDWIDE", 0x0010/*ENABLE_LVB_GRID_WORLDWIDE*/, //0x0010 +}; + +int main(int argc, char *argv[]) { + std::vector args; + for (size_t i = 1; i < argc; ++i) { + args.push_back(argv[i]); + } + + if (args.empty() || args.size() == 1 && args[0] == "info") { + DWORD mode = getConsoleMode(); + printf("mode: 0x%lx\n", mode); + for (const auto &flag : kOutputFlags) { + printf("%-34s 0x%04lx %s\n", flag.name, flag.value, flag.value & mode ? "ON" : "off"); + mode &= ~flag.value; + } + for (int i = 0; i < 32; ++i) { + if (mode & (1u << i)) { + printf("Unrecognized flag: %04x\n", (1u << i)); + } + } + return 0; + } + + const auto verb = args[0]; + + if (verb == "set") { + if (args.size() == 2) { + const DWORD newMode = parseInt(args[1]); + setConsoleMode(newMode); + } else if (args.size() == 3) { + const DWORD mode = parseInt(args[1]); + const DWORD mask = parseInt(args[2]); + const int newMode = (getConsoleMode() & ~mask) | (mode & mask); + setConsoleMode(newMode); + } else { + usage(); + } + } else if (verb == "get") { + if (args.size() != 1) { + usage(); + } + printf("0x%lx\n", getConsoleMode()); + } else { + usage(); + } + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/DebugClient.py b/src/libs/3rdparty/winpty/misc/DebugClient.py new file mode 100644 index 0000000000..cd12df8924 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/DebugClient.py @@ -0,0 +1,42 @@ +#!python +# Run with native CPython. Needs pywin32 extensions. + +# Copyright (c) 2011-2012 Ryan Prichard +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +import winerror +import win32pipe +import win32file +import win32api +import sys +import pywintypes +import time + +if len(sys.argv) != 2: + print("Usage: %s message" % sys.argv[0]) + sys.exit(1) + +message = "[%05.3f %s]: %s" % (time.time() % 100000, sys.argv[0], sys.argv[1]) + +win32pipe.CallNamedPipe( + "\\\\.\\pipe\\DebugServer", + message.encode(), + 16, + win32pipe.NMPWAIT_WAIT_FOREVER) diff --git a/src/libs/3rdparty/winpty/misc/DebugServer.py b/src/libs/3rdparty/winpty/misc/DebugServer.py new file mode 100644 index 0000000000..3fc068bae7 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/DebugServer.py @@ -0,0 +1,63 @@ +#!python +# +# Run with native CPython. Needs pywin32 extensions. + +# Copyright (c) 2011-2012 Ryan Prichard +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +import win32pipe +import win32api +import win32file +import time +import threading +import sys + +# A message may not be larger than this size. +MSG_SIZE=4096 + +serverPipe = win32pipe.CreateNamedPipe( + "\\\\.\\pipe\\DebugServer", + win32pipe.PIPE_ACCESS_DUPLEX, + win32pipe.PIPE_TYPE_MESSAGE | win32pipe.PIPE_READMODE_MESSAGE, + win32pipe.PIPE_UNLIMITED_INSTANCES, + MSG_SIZE, + MSG_SIZE, + 10 * 1000, + None) +while True: + win32pipe.ConnectNamedPipe(serverPipe, None) + (ret, data) = win32file.ReadFile(serverPipe, MSG_SIZE) + print(data.decode()) + sys.stdout.flush() + + # The client uses CallNamedPipe to send its message. CallNamedPipe waits + # for a reply message. If I send a reply, however, using WriteFile, then + # sometimes WriteFile fails with: + # pywintypes.error: (232, 'WriteFile', 'The pipe is being closed.') + # I can't figure out how to write a strictly correct pipe server, but if + # I comment out the WriteFile line, then everything seems to work. I + # think the DisconnectNamedPipe call aborts the client's CallNamedPipe + # call normally. + + try: + win32file.WriteFile(serverPipe, b'OK') + except: + pass + win32pipe.DisconnectNamedPipe(serverPipe) diff --git a/src/libs/3rdparty/winpty/misc/DumpLines.py b/src/libs/3rdparty/winpty/misc/DumpLines.py new file mode 100644 index 0000000000..40049961b5 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/DumpLines.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python +import sys + +for i in range(1, int(sys.argv[1]) + 1): + print i, "X" * 78 diff --git a/src/libs/3rdparty/winpty/misc/EnableExtendedFlags.txt b/src/libs/3rdparty/winpty/misc/EnableExtendedFlags.txt new file mode 100644 index 0000000000..37914dac26 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/EnableExtendedFlags.txt @@ -0,0 +1,46 @@ +Note regarding ENABLE_EXTENDED_FLAGS (2016-05-30) + +There is a complicated interaction between the ENABLE_EXTENDED_FLAGS flag +and the ENABLE_QUICK_EDIT_MODE and ENABLE_INSERT_MODE flags (presumably for +backwards compatibility?). I studied the behavior on Windows 7 and Windows +10, with both the old and new consoles, and I didn't see any differences +between versions. Here's what I seemed to observe: + + - The console has three flags internally: + - QuickEdit + - InsertMode + - ExtendedFlags + + - SetConsoleMode psuedocode: + void SetConsoleMode(..., DWORD mode) { + ExtendedFlags = (mode & (ENABLE_EXTENDED_FLAGS + | ENABLE_QUICK_EDIT_MODE + | ENABLE_INSERT_MODE )) != 0; + if (ExtendedFlags) { + QuickEdit = (mode & ENABLE_QUICK_EDIT_MODE) != 0; + InsertMode = (mode & ENABLE_INSERT_MODE) != 0; + } + } + + - Setting QuickEdit or InsertMode from the properties dialog GUI does not + affect the ExtendedFlags setting -- it simply toggles the one flag. + + - GetConsoleMode psuedocode: + GetConsoleMode(..., DWORD *result) { + if (ExtendedFlags) { + *result |= ENABLE_EXTENDED_FLAGS; + if (QuickEdit) { *result |= ENABLE_QUICK_EDIT_MODE; } + if (InsertMode) { *result |= ENABLE_INSERT_MODE; } + } + } + +Effectively, the ExtendedFlags flags controls whether the other two flags +are visible/controlled by the user application. If they aren't visible, +though, there is no way for the user application to make them visible, +except by overwriting their values! Calling SetConsoleMode with just +ENABLE_EXTENDED_FLAGS would clear the extended flags we want to read. + +Consequently, if a program temporarily alters the QuickEdit flag (e.g. to +enable mouse input), it cannot restore the original values of the QuickEdit +and InsertMode flags, UNLESS every other console program cooperates by +keeping the ExtendedFlags flag set. diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP437-Consolas.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP437-Consolas.txt new file mode 100644 index 0000000000..067bd3824a --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP437-Consolas.txt @@ -0,0 +1,528 @@ +================================== +Code Page 437, Consolas font +================================== + +Options: -face "Consolas" -family 0x36 +Chars: A2 A3 2014 3044 30FC 4000 + +FontSurvey "-face \"Consolas\" -family 0x36" + +Windows 7 +--------- + +Size 1: 1,3 BAD (HHHHHH) +Size 2: 1,2 BAD (HHHHHH) +Size 3: 1,3 BAD (HHHHHH) +Size 4: 2,4 BAD (HHHHHH) +Size 5: 2,5 BAD (HHHHHH) +Size 6: 3,6 BAD (HHHHHH) +Size 7: 3,6 BAD (HHHHHH) +Size 8: 4,8 BAD (HHHHHH) +Size 9: 4,9 BAD (HHHHHH) +Size 10: 5,10 BAD (HHHHHH) +Size 11: 5,11 BAD (HHHHHH) +Size 12: 6,12 BAD (HHHHHH) +Size 13: 6,13 BAD (HHHHHH) +Size 14: 7,14 BAD (HHHHHH) +Size 15: 7,15 BAD (HHHHHH) +Size 16: 8,16 BAD (HHHHHH) +Size 17: 8,17 BAD (HHHHHH) +Size 18: 8,18 BAD (HHHHHH) +Size 19: 9,19 BAD (HHHHHH) +Size 20: 9,20 BAD (HHHHHH) +Size 21: 10,22 BAD (HHHHHH) +Size 22: 10,22 BAD (HHHHHH) +Size 23: 11,23 BAD (HHHHHH) +Size 24: 11,24 BAD (HHHHHH) +Size 25: 12,25 BAD (HHHHHH) +Size 26: 12,26 BAD (HHHHHH) +Size 27: 13,27 BAD (HHHHHH) +Size 28: 13,28 BAD (HHHHHH) +Size 29: 14,29 BAD (HHHHHH) +Size 30: 14,30 BAD (HHHHHH) +Size 31: 15,31 BAD (HHHHHH) +Size 32: 15,32 BAD (HHHHHH) +Size 33: 15,33 BAD (HHHHHH) +Size 34: 16,34 BAD (HHHHHH) +Size 35: 16,36 BAD (HHHHHH) +Size 36: 17,36 BAD (HHHHHH) +Size 37: 17,37 BAD (HHHHHH) +Size 38: 18,38 BAD (HHHHHH) +Size 39: 18,39 BAD (HHHHHH) +Size 40: 19,40 BAD (HHHHHH) +Size 41: 19,41 BAD (HHHHHH) +Size 42: 20,42 BAD (HHHHHH) +Size 43: 20,43 BAD (HHHHHH) +Size 44: 21,44 BAD (HHHHHH) +Size 45: 21,45 BAD (HHHHHH) +Size 46: 22,46 BAD (HHHHHH) +Size 47: 22,47 BAD (HHHHHH) +Size 48: 23,48 BAD (HHHHHH) +Size 49: 23,49 BAD (HHHHHH) +Size 50: 23,50 BAD (HHHHHH) +Size 51: 24,51 BAD (HHHHHH) +Size 52: 24,52 BAD (HHHHHH) +Size 53: 25,53 BAD (HHHHHH) +Size 54: 25,54 BAD (HHHHHH) +Size 55: 26,55 BAD (HHHHHH) +Size 56: 26,56 BAD (HHHHHH) +Size 57: 27,57 BAD (HHHHHH) +Size 58: 27,58 BAD (HHHHHH) +Size 59: 28,59 BAD (HHHHHH) +Size 60: 28,60 BAD (HHHHHH) +Size 61: 29,61 BAD (HHHHHH) +Size 62: 29,62 BAD (HHHHHH) +Size 63: 30,64 BAD (HHHHHH) +Size 64: 30,64 BAD (HHHHHH) +Size 65: 31,65 BAD (HHHHHH) +Size 66: 31,66 BAD (HHHHHH) +Size 67: 31,67 BAD (HHHHHH) +Size 68: 32,68 BAD (HHHHHH) +Size 69: 32,69 BAD (HHHHHH) +Size 70: 33,70 BAD (HHHHHH) +Size 71: 33,71 BAD (HHHHHH) +Size 72: 34,72 BAD (HHHHHH) +Size 73: 34,73 BAD (HHHHHH) +Size 74: 35,74 BAD (HHHHHH) +Size 75: 35,75 BAD (HHHHHH) +Size 76: 36,76 BAD (HHHHHH) +Size 77: 36,77 BAD (HHHHHH) +Size 78: 37,78 BAD (HHHHHH) +Size 79: 37,79 BAD (HHHHHH) +Size 80: 38,80 BAD (HHHHHH) +Size 81: 38,81 BAD (HHHHHH) +Size 82: 39,82 BAD (HHHHHH) +Size 83: 39,83 BAD (HHHHHH) +Size 84: 39,84 BAD (HHHHHH) +Size 85: 40,85 BAD (HHHHHH) +Size 86: 40,86 BAD (HHHHHH) +Size 87: 41,87 BAD (HHHHHH) +Size 88: 41,88 BAD (HHHHHH) +Size 89: 42,89 BAD (HHHHHH) +Size 90: 42,90 BAD (HHHHHH) +Size 91: 43,91 BAD (HHHHHH) +Size 92: 43,92 BAD (HHHHHH) +Size 93: 44,93 BAD (HHHHHH) +Size 94: 44,94 BAD (HHHHHH) +Size 95: 45,95 BAD (HHHHHH) +Size 96: 45,96 BAD (HHHHHH) +Size 97: 46,97 BAD (HHHHHH) +Size 98: 46,98 BAD (HHHHHH) +Size 99: 46,99 BAD (HHHHHH) +Size 100: 47,100 BAD (HHHHHH) + +Windows 8 +--------- + +Size 1: 1,3 BAD (HHHHHH) +Size 2: 1,2 BAD (HHHHHH) +Size 3: 1,3 BAD (HHHHHH) +Size 4: 2,4 BAD (HHHHHH) +Size 5: 2,5 BAD (HHHHHH) +Size 6: 3,6 BAD (HHHHHH) +Size 7: 3,6 BAD (HHHHHH) +Size 8: 4,8 BAD (HHHHHH) +Size 9: 4,9 BAD (HHHHHH) +Size 10: 5,10 BAD (HHHHHH) +Size 11: 5,11 BAD (HHHHHH) +Size 12: 6,12 BAD (HHHHHH) +Size 13: 6,13 BAD (HHHHHH) +Size 14: 7,14 BAD (HHHHHH) +Size 15: 7,15 BAD (HHHHHH) +Size 16: 8,16 BAD (HHHHHH) +Size 17: 8,17 BAD (HHHHHH) +Size 18: 8,18 BAD (HHHHHH) +Size 19: 9,19 BAD (HHHHHH) +Size 20: 9,20 BAD (HHHHHH) +Size 21: 10,22 BAD (HHHHHH) +Size 22: 10,22 BAD (HHHHHH) +Size 23: 11,23 BAD (HHHHHH) +Size 24: 11,24 BAD (HHHHHH) +Size 25: 12,25 BAD (HHHHHH) +Size 26: 12,26 BAD (HHHHHH) +Size 27: 13,27 BAD (HHHHHH) +Size 28: 13,28 BAD (HHHHHH) +Size 29: 14,29 BAD (HHHHHH) +Size 30: 14,30 BAD (HHHHHH) +Size 31: 15,31 BAD (HHHHHH) +Size 32: 15,32 BAD (HHHHHH) +Size 33: 15,33 BAD (HHHHHH) +Size 34: 16,34 BAD (HHHHHH) +Size 35: 16,36 BAD (HHHHHH) +Size 36: 17,36 BAD (HHHHHH) +Size 37: 17,37 BAD (HHHHHH) +Size 38: 18,38 BAD (HHHHHH) +Size 39: 18,39 BAD (HHHHHH) +Size 40: 19,40 BAD (HHHHHH) +Size 41: 19,41 BAD (HHHHHH) +Size 42: 20,42 BAD (HHHHHH) +Size 43: 20,43 BAD (HHHHHH) +Size 44: 21,44 BAD (HHHHHH) +Size 45: 21,45 BAD (HHHHHH) +Size 46: 22,46 BAD (HHHHHH) +Size 47: 22,47 BAD (HHHHHH) +Size 48: 23,48 BAD (HHHHHH) +Size 49: 23,49 BAD (HHHHHH) +Size 50: 23,50 BAD (HHHHHH) +Size 51: 24,51 BAD (HHHHHH) +Size 52: 24,52 BAD (HHHHHH) +Size 53: 25,53 BAD (HHHHHH) +Size 54: 25,54 BAD (HHHHHH) +Size 55: 26,55 BAD (HHHHHH) +Size 56: 26,56 BAD (HHHHHH) +Size 57: 27,57 BAD (HHHHHH) +Size 58: 27,58 BAD (HHHHHH) +Size 59: 28,59 BAD (HHHHHH) +Size 60: 28,60 BAD (HHHHHH) +Size 61: 29,61 BAD (HHHHHH) +Size 62: 29,62 BAD (HHHHHH) +Size 63: 30,64 BAD (HHHHHH) +Size 64: 30,64 BAD (HHHHHH) +Size 65: 31,65 BAD (HHHHHH) +Size 66: 31,66 BAD (HHHHHH) +Size 67: 31,67 BAD (HHHHHH) +Size 68: 32,68 BAD (HHHHHH) +Size 69: 32,69 BAD (HHHHHH) +Size 70: 33,70 BAD (HHHHHH) +Size 71: 33,71 BAD (HHHHHH) +Size 72: 34,72 BAD (HHHHHH) +Size 73: 34,73 BAD (HHHHHH) +Size 74: 35,74 BAD (HHHHHH) +Size 75: 35,75 BAD (HHHHHH) +Size 76: 36,76 BAD (HHHHHH) +Size 77: 36,77 BAD (HHHHHH) +Size 78: 37,78 BAD (HHHHHH) +Size 79: 37,79 BAD (HHHHHH) +Size 80: 38,80 BAD (HHHHHH) +Size 81: 38,81 BAD (HHHHHH) +Size 82: 39,82 BAD (HHHHHH) +Size 83: 39,83 BAD (HHHHHH) +Size 84: 39,84 BAD (HHHHHH) +Size 85: 40,85 BAD (HHHHHH) +Size 86: 40,86 BAD (HHHHHH) +Size 87: 41,87 BAD (HHHHHH) +Size 88: 41,88 BAD (HHHHHH) +Size 89: 42,89 BAD (HHHHHH) +Size 90: 42,90 BAD (HHHHHH) +Size 91: 43,91 BAD (HHHHHH) +Size 92: 43,92 BAD (HHHHHH) +Size 93: 44,93 BAD (HHHHHH) +Size 94: 44,94 BAD (HHHHHH) +Size 95: 45,95 BAD (HHHHHH) +Size 96: 45,96 BAD (HHHHHH) +Size 97: 46,97 BAD (HHHHHH) +Size 98: 46,98 BAD (HHHHHH) +Size 99: 46,99 BAD (HHHHHH) +Size 100: 47,100 BAD (HHHHHH) + +Windows 8.1 +----------- + +Size 1: 1,3 BAD (HHHHHH) +Size 2: 1,2 BAD (HHHHHH) +Size 3: 1,3 BAD (HHHHHH) +Size 4: 2,4 BAD (HHHHHH) +Size 5: 2,5 BAD (HHHHHH) +Size 6: 3,6 BAD (HHHHHH) +Size 7: 3,6 BAD (HHHHHH) +Size 8: 4,8 BAD (HHHHHH) +Size 9: 4,9 BAD (HHHHHH) +Size 10: 5,10 BAD (HHHHHH) +Size 11: 5,11 BAD (HHHHHH) +Size 12: 6,12 BAD (HHHHHH) +Size 13: 6,13 BAD (HHHHHH) +Size 14: 7,14 BAD (HHHHHH) +Size 15: 7,15 BAD (HHHHHH) +Size 16: 8,16 BAD (HHHHHH) +Size 17: 8,17 BAD (HHHHHH) +Size 18: 8,18 BAD (HHHHHH) +Size 19: 9,19 BAD (HHHHHH) +Size 20: 9,20 BAD (HHHHHH) +Size 21: 10,22 BAD (HHHHHH) +Size 22: 10,22 BAD (HHHHHH) +Size 23: 11,23 BAD (HHHHHH) +Size 24: 11,24 BAD (HHHHHH) +Size 25: 12,25 BAD (HHHHHH) +Size 26: 12,26 BAD (HHHHHH) +Size 27: 13,27 BAD (HHHHHH) +Size 28: 13,28 BAD (HHHHHH) +Size 29: 14,29 BAD (HHHHHH) +Size 30: 14,30 BAD (HHHHHH) +Size 31: 15,31 BAD (HHHHHH) +Size 32: 15,32 BAD (HHHHHH) +Size 33: 15,33 BAD (HHHHHH) +Size 34: 16,34 BAD (HHHHHH) +Size 35: 16,36 BAD (HHHHHH) +Size 36: 17,36 BAD (HHHHHH) +Size 37: 17,37 BAD (HHHHHH) +Size 38: 18,38 BAD (HHHHHH) +Size 39: 18,39 BAD (HHHHHH) +Size 40: 19,40 BAD (HHHHHH) +Size 41: 19,41 BAD (HHHHHH) +Size 42: 20,42 BAD (HHHHHH) +Size 43: 20,43 BAD (HHHHHH) +Size 44: 21,44 BAD (HHHHHH) +Size 45: 21,45 BAD (HHHHHH) +Size 46: 22,46 BAD (HHHHHH) +Size 47: 22,47 BAD (HHHHHH) +Size 48: 23,48 BAD (HHHHHH) +Size 49: 23,49 BAD (HHHHHH) +Size 50: 23,50 BAD (HHHHHH) +Size 51: 24,51 BAD (HHHHHH) +Size 52: 24,52 BAD (HHHHHH) +Size 53: 25,53 BAD (HHHHHH) +Size 54: 25,54 BAD (HHHHHH) +Size 55: 26,55 BAD (HHHHHH) +Size 56: 26,56 BAD (HHHHHH) +Size 57: 27,57 BAD (HHHHHH) +Size 58: 27,58 BAD (HHHHHH) +Size 59: 28,59 BAD (HHHHHH) +Size 60: 28,60 BAD (HHHHHH) +Size 61: 29,61 BAD (HHHHHH) +Size 62: 29,62 BAD (HHHHHH) +Size 63: 30,64 BAD (HHHHHH) +Size 64: 30,64 BAD (HHHHHH) +Size 65: 31,65 BAD (HHHHHH) +Size 66: 31,66 BAD (HHHHHH) +Size 67: 31,67 BAD (HHHHHH) +Size 68: 32,68 BAD (HHHHHH) +Size 69: 32,69 BAD (HHHHHH) +Size 70: 33,70 BAD (HHHHHH) +Size 71: 33,71 BAD (HHHHHH) +Size 72: 34,72 BAD (HHHHHH) +Size 73: 34,73 BAD (HHHHHH) +Size 74: 35,74 BAD (HHHHHH) +Size 75: 35,75 BAD (HHHHHH) +Size 76: 36,76 BAD (HHHHHH) +Size 77: 36,77 BAD (HHHHHH) +Size 78: 37,78 BAD (HHHHHH) +Size 79: 37,79 BAD (HHHHHH) +Size 80: 38,80 BAD (HHHHHH) +Size 81: 38,81 BAD (HHHHHH) +Size 82: 39,82 BAD (HHHHHH) +Size 83: 39,83 BAD (HHHHHH) +Size 84: 39,84 BAD (HHHHHH) +Size 85: 40,85 BAD (HHHHHH) +Size 86: 40,86 BAD (HHHHHH) +Size 87: 41,87 BAD (HHHHHH) +Size 88: 41,88 BAD (HHHHHH) +Size 89: 42,89 BAD (HHHHHH) +Size 90: 42,90 BAD (HHHHHH) +Size 91: 43,91 BAD (HHHHHH) +Size 92: 43,92 BAD (HHHHHH) +Size 93: 44,93 BAD (HHHHHH) +Size 94: 44,94 BAD (HHHHHH) +Size 95: 45,95 BAD (HHHHHH) +Size 96: 45,96 BAD (HHHHHH) +Size 97: 46,97 BAD (HHHHHH) +Size 98: 46,98 BAD (HHHHHH) +Size 99: 46,99 BAD (HHHHHH) +Size 100: 47,100 BAD (HHHHHH) + +Windows 10 14342 Old Console +---------------------------- + +Size 1: 1,3 BAD (HHHHHH) +Size 2: 1,2 BAD (HHHHHH) +Size 3: 1,3 BAD (HHHHHH) +Size 4: 2,4 BAD (HHHHHH) +Size 5: 2,5 BAD (HHHHHH) +Size 6: 3,6 BAD (HHHHHH) +Size 7: 3,6 BAD (HHHHHH) +Size 8: 4,8 BAD (HHHHHH) +Size 9: 4,9 BAD (HHHHHH) +Size 10: 5,10 BAD (HHHHHH) +Size 11: 5,11 BAD (HHHHHH) +Size 12: 6,12 BAD (HHHHHH) +Size 13: 6,13 BAD (HHHHHH) +Size 14: 7,14 BAD (HHHHHH) +Size 15: 7,15 BAD (HHHHHH) +Size 16: 8,16 BAD (HHHHHH) +Size 17: 8,17 BAD (HHHHHH) +Size 18: 8,18 BAD (HHHHHH) +Size 19: 9,19 BAD (HHHHHH) +Size 20: 9,20 BAD (HHHHHH) +Size 21: 10,22 BAD (HHHHHH) +Size 22: 10,22 BAD (HHHHHH) +Size 23: 11,23 BAD (HHHHHH) +Size 24: 11,24 BAD (HHHHHH) +Size 25: 12,25 BAD (HHHHHH) +Size 26: 12,26 BAD (HHHHHH) +Size 27: 13,27 BAD (HHHHHH) +Size 28: 13,28 BAD (HHHHHH) +Size 29: 14,29 BAD (HHHHHH) +Size 30: 14,30 BAD (HHHHHH) +Size 31: 15,31 BAD (HHHHHH) +Size 32: 15,32 BAD (HHHHHH) +Size 33: 15,33 BAD (HHHHHH) +Size 34: 16,34 BAD (HHHHHH) +Size 35: 16,36 BAD (HHHHHH) +Size 36: 17,36 BAD (HHHHHH) +Size 37: 17,37 BAD (HHHHHH) +Size 38: 18,38 BAD (HHHHHH) +Size 39: 18,39 BAD (HHHHHH) +Size 40: 19,40 BAD (HHHHHH) +Size 41: 19,41 BAD (HHHHHH) +Size 42: 20,42 BAD (HHHHHH) +Size 43: 20,43 BAD (HHHHHH) +Size 44: 21,44 BAD (HHHHHH) +Size 45: 21,45 BAD (HHHHHH) +Size 46: 22,46 BAD (HHHHHH) +Size 47: 22,47 BAD (HHHHHH) +Size 48: 23,48 BAD (HHHHHH) +Size 49: 23,49 BAD (HHHHHH) +Size 50: 23,50 BAD (HHHHHH) +Size 51: 24,51 BAD (HHHHHH) +Size 52: 24,52 BAD (HHHHHH) +Size 53: 25,53 BAD (HHHHHH) +Size 54: 25,54 BAD (HHHHHH) +Size 55: 26,55 BAD (HHHHHH) +Size 56: 26,56 BAD (HHHHHH) +Size 57: 27,57 BAD (HHHHHH) +Size 58: 27,58 BAD (HHHHHH) +Size 59: 28,59 BAD (HHHHHH) +Size 60: 28,60 BAD (HHHHHH) +Size 61: 29,61 BAD (HHHHHH) +Size 62: 29,62 BAD (HHHHHH) +Size 63: 30,64 BAD (HHHHHH) +Size 64: 30,64 BAD (HHHHHH) +Size 65: 31,65 BAD (HHHHHH) +Size 66: 31,66 BAD (HHHHHH) +Size 67: 31,67 BAD (HHHHHH) +Size 68: 32,68 BAD (HHHHHH) +Size 69: 32,69 BAD (HHHHHH) +Size 70: 33,70 BAD (HHHHHH) +Size 71: 33,71 BAD (HHHHHH) +Size 72: 34,72 BAD (HHHHHH) +Size 73: 34,73 BAD (HHHHHH) +Size 74: 35,74 BAD (HHHHHH) +Size 75: 35,75 BAD (HHHHHH) +Size 76: 36,76 BAD (HHHHHH) +Size 77: 36,77 BAD (HHHHHH) +Size 78: 37,78 BAD (HHHHHH) +Size 79: 37,79 BAD (HHHHHH) +Size 80: 38,80 BAD (HHHHHH) +Size 81: 38,81 BAD (HHHHHH) +Size 82: 39,82 BAD (HHHHHH) +Size 83: 39,83 BAD (HHHHHH) +Size 84: 39,84 BAD (HHHHHH) +Size 85: 40,85 BAD (HHHHHH) +Size 86: 40,86 BAD (HHHHHH) +Size 87: 41,87 BAD (HHHHHH) +Size 88: 41,88 BAD (HHHHHH) +Size 89: 42,89 BAD (HHHHHH) +Size 90: 42,90 BAD (HHHHHH) +Size 91: 43,91 BAD (HHHHHH) +Size 92: 43,92 BAD (HHHHHH) +Size 93: 44,93 BAD (HHHHHH) +Size 94: 44,94 BAD (HHHHHH) +Size 95: 45,95 BAD (HHHHHH) +Size 96: 45,96 BAD (HHHHHH) +Size 97: 46,97 BAD (HHHHHH) +Size 98: 46,98 BAD (HHHHHH) +Size 99: 46,99 BAD (HHHHHH) +Size 100: 47,100 BAD (HHHHHH) + +Windows 10 14342 New Console +---------------------------- + +Size 1: 1,1 BAD (HHHHHH) +Size 2: 1,2 BAD (HHHHHH) +Size 3: 1,3 BAD (HHHHHH) +Size 4: 2,4 BAD (HHHHHH) +Size 5: 2,5 BAD (HHHHHH) +Size 6: 3,6 BAD (HHHHHH) +Size 7: 3,7 BAD (HHHHHH) +Size 8: 4,8 BAD (HHHHHH) +Size 9: 4,9 BAD (HHHHHH) +Size 10: 5,10 BAD (HHHHHH) +Size 11: 5,11 BAD (HHHHHH) +Size 12: 6,12 BAD (HHHHHH) +Size 13: 6,13 BAD (HHHHHH) +Size 14: 7,14 BAD (HHHHHH) +Size 15: 7,15 BAD (HHHHHH) +Size 16: 8,16 BAD (HHHHHH) +Size 17: 8,17 BAD (HHHHHH) +Size 18: 8,18 BAD (HHHHHH) +Size 19: 9,19 BAD (HHHHHH) +Size 20: 9,20 BAD (HHHHHH) +Size 21: 10,21 BAD (HHHHHH) +Size 22: 10,22 BAD (HHHHHH) +Size 23: 11,23 BAD (HHHHHH) +Size 24: 11,24 BAD (HHHHHH) +Size 25: 12,25 BAD (HHHHHH) +Size 26: 12,26 BAD (HHHHHH) +Size 27: 13,27 BAD (HHHHHH) +Size 28: 13,28 BAD (HHHHHH) +Size 29: 14,29 BAD (HHHHHH) +Size 30: 14,30 BAD (HHHHHH) +Size 31: 15,31 BAD (HHHHHH) +Size 32: 15,32 BAD (HHHHHH) +Size 33: 15,33 BAD (HHHHHH) +Size 34: 16,34 BAD (HHHHHH) +Size 35: 16,35 BAD (HHHHHH) +Size 36: 17,36 BAD (HHHHHH) +Size 37: 17,37 BAD (HHHHHH) +Size 38: 18,38 BAD (HHHHHH) +Size 39: 18,39 BAD (HHHHHH) +Size 40: 19,40 BAD (HHHHHH) +Size 41: 19,41 BAD (HHHHHH) +Size 42: 20,42 BAD (HHHHHH) +Size 43: 20,43 BAD (HHHHHH) +Size 44: 21,44 BAD (HHHHHH) +Size 45: 21,45 BAD (HHHHHH) +Size 46: 22,46 BAD (HHHHHH) +Size 47: 22,47 BAD (HHHHHH) +Size 48: 23,48 BAD (HHHHHH) +Size 49: 23,49 BAD (HHHHHH) +Size 50: 23,50 BAD (HHHHHH) +Size 51: 24,51 BAD (HHHHHH) +Size 52: 24,52 BAD (HHHHHH) +Size 53: 25,53 BAD (HHHHHH) +Size 54: 25,54 BAD (HHHHHH) +Size 55: 26,55 BAD (HHHHHH) +Size 56: 26,56 BAD (HHHHHH) +Size 57: 27,57 BAD (HHHHHH) +Size 58: 27,58 BAD (HHHHHH) +Size 59: 28,59 BAD (HHHHHH) +Size 60: 28,60 BAD (HHHHHH) +Size 61: 29,61 BAD (HHHHHH) +Size 62: 29,62 BAD (HHHHHH) +Size 63: 30,63 BAD (HHHHHH) +Size 64: 30,64 BAD (HHHHHH) +Size 65: 31,65 BAD (HHHHHH) +Size 66: 31,66 BAD (HHHHHH) +Size 67: 31,67 BAD (HHHHHH) +Size 68: 32,68 BAD (HHHHHH) +Size 69: 32,69 BAD (HHHHHH) +Size 70: 33,70 BAD (HHHHHH) +Size 71: 33,71 BAD (HHHHHH) +Size 72: 34,72 BAD (HHHHHH) +Size 73: 34,73 BAD (HHHHHH) +Size 74: 35,74 BAD (HHHHHH) +Size 75: 35,75 BAD (HHHHHH) +Size 76: 36,76 BAD (HHHHHH) +Size 77: 36,77 BAD (HHHHHH) +Size 78: 37,78 BAD (HHHHHH) +Size 79: 37,79 BAD (HHHHHH) +Size 80: 38,80 BAD (HHHHHH) +Size 81: 38,81 BAD (HHHHHH) +Size 82: 39,82 BAD (HHHHHH) +Size 83: 39,83 BAD (HHHHHH) +Size 84: 39,84 BAD (HHHHHH) +Size 85: 40,85 BAD (HHHHHH) +Size 86: 40,86 BAD (HHHHHH) +Size 87: 41,87 BAD (HHHHHH) +Size 88: 41,88 BAD (HHHHHH) +Size 89: 42,89 BAD (HHHHHH) +Size 90: 42,90 BAD (HHHHHH) +Size 91: 43,91 BAD (HHHHHH) +Size 92: 43,92 BAD (HHHHHH) +Size 93: 44,93 BAD (HHHHHH) +Size 94: 44,94 BAD (HHHHHH) +Size 95: 45,95 BAD (HHHHHH) +Size 96: 45,96 BAD (HHHHHH) +Size 97: 46,97 BAD (HHHHHH) +Size 98: 46,98 BAD (HHHHHH) +Size 99: 46,99 BAD (HHHHHH) +Size 100: 47,100 BAD (HHHHHH) diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP437-Lucida.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP437-Lucida.txt new file mode 100644 index 0000000000..0eed93ad98 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP437-Lucida.txt @@ -0,0 +1,633 @@ +================================== +Code Page 437, Lucida Console font +================================== + +Options: -face "Lucida Console" -family 0x36 +Chars: A2 A3 2014 3044 30FC 4000 + +FontSurvey "-face \"Lucida Console\" -family 0x36" + +Vista +----- + +Size 1: 1,2 BAD (HHHHHH) +Size 2: 1,2 BAD (HHHHHH) +Size 3: 2,3 BAD (HHHHHH) +Size 4: 2,4 BAD (HHHHHH) +Size 5: 3,5 BAD (HHHHHH) +Size 6: 4,6 BAD (HHHHHH) +Size 7: 4,7 BAD (HHHHHH) +Size 8: 5,8 BAD (HHHHHH) +Size 9: 5,9 BAD (HHHHHH) +Size 10: 6,10 BAD (HHHHHH) +Size 11: 7,11 BAD (HHHHHH) +Size 12: 7,12 BAD (HHHHHH) +Size 13: 8,13 BAD (HHHHHH) +Size 14: 8,14 BAD (HHHHHH) +Size 15: 9,15 BAD (HHHHHH) +Size 16: 10,16 BAD (HHHHHH) +Size 17: 10,17 BAD (HHHHHH) +Size 18: 11,18 BAD (HHHHHH) +Size 19: 11,19 BAD (HHHHHH) +Size 20: 12,20 BAD (HHHHHH) +Size 21: 13,21 BAD (HHHHHH) +Size 22: 13,22 BAD (HHHHHH) +Size 23: 14,23 BAD (HHHHHH) +Size 24: 14,24 BAD (HHHHHH) +Size 25: 15,25 BAD (HHHHHH) +Size 26: 16,26 BAD (HHHHHH) +Size 27: 16,27 BAD (HHHHHH) +Size 28: 17,28 BAD (HHHHHH) +Size 29: 17,29 BAD (HHHHHH) +Size 30: 18,30 BAD (HHHHHH) +Size 31: 19,31 BAD (HHHHHH) +Size 32: 19,32 BAD (HHHHHH) +Size 33: 20,33 BAD (HHHHHH) +Size 34: 20,34 BAD (HHHHHH) +Size 35: 21,35 BAD (HHHHHH) +Size 36: 22,36 BAD (HHHHHH) +Size 37: 22,37 BAD (HHHHHH) +Size 38: 23,38 BAD (HHHHHH) +Size 39: 23,39 BAD (HHHHHH) +Size 40: 24,40 BAD (HHHHHH) +Size 41: 25,41 BAD (HHHHHH) +Size 42: 25,42 BAD (HHHHHH) +Size 43: 26,43 BAD (HHHHHH) +Size 44: 27,44 BAD (HHHHHH) +Size 45: 27,45 BAD (HHHHHH) +Size 46: 28,46 BAD (HHHHHH) +Size 47: 28,47 BAD (HHHHHH) +Size 48: 29,48 BAD (HHHHHH) +Size 49: 30,49 BAD (HHHHHH) +Size 50: 30,50 BAD (HHHHHH) +Size 51: 31,51 BAD (HHHHHH) +Size 52: 31,52 BAD (HHHHHH) +Size 53: 32,53 BAD (HHHHHH) +Size 54: 33,54 BAD (HHHHHH) +Size 55: 33,55 BAD (HHHHHH) +Size 56: 34,56 BAD (HHHHHH) +Size 57: 34,57 BAD (HHHHHH) +Size 58: 35,58 BAD (HHHHHH) +Size 59: 36,59 BAD (HHHHHH) +Size 60: 36,60 BAD (HHHHHH) +Size 61: 37,61 BAD (HHHHHH) +Size 62: 37,62 BAD (HHHHHH) +Size 63: 38,63 BAD (HHHHHH) +Size 64: 39,65 BAD (HHHHHH) +Size 65: 39,65 BAD (HHHHHH) +Size 66: 40,66 BAD (HHHHHH) +Size 67: 40,67 BAD (HHHHHH) +Size 68: 41,68 BAD (HHHHHH) +Size 69: 42,69 BAD (HHHHHH) +Size 70: 42,70 BAD (HHHHHH) +Size 71: 43,71 BAD (HHHHHH) +Size 72: 43,72 BAD (HHHHHH) +Size 73: 44,73 BAD (HHHHHH) +Size 74: 45,74 BAD (HHHHHH) +Size 75: 45,75 BAD (HHHHHH) +Size 76: 46,76 BAD (HHHHHH) +Size 77: 46,77 BAD (HHHHHH) +Size 78: 47,78 BAD (HHHHHH) +Size 79: 48,79 BAD (HHHHHH) +Size 80: 48,80 BAD (HHHHHH) +Size 81: 49,81 BAD (HHHHHH) +Size 82: 49,82 BAD (HHHHHH) +Size 83: 50,83 BAD (HHHHHH) +Size 84: 51,84 BAD (HHHHHH) +Size 85: 51,85 BAD (HHHHHH) +Size 86: 52,86 BAD (HHHHHH) +Size 87: 52,87 BAD (HHHHHH) +Size 88: 53,88 BAD (HHHHHH) +Size 89: 54,89 BAD (HHHHHH) +Size 90: 54,90 BAD (HHHHHH) +Size 91: 55,91 BAD (HHHHHH) +Size 92: 55,92 BAD (HHHHHH) +Size 93: 56,93 BAD (HHHHHH) +Size 94: 57,94 BAD (HHHHHH) +Size 95: 57,95 BAD (HHHHHH) +Size 96: 58,96 BAD (HHHHHH) +Size 97: 58,97 BAD (HHHHHH) +Size 98: 59,98 BAD (HHHHHH) +Size 99: 60,99 BAD (HHHHHH) +Size 100: 60,100 BAD (HHHHHH) + + +Windows 7 +--------- + +Size 1: 1,2 BAD (HHHHHH) +Size 2: 1,2 BAD (HHHHHH) +Size 3: 2,3 BAD (HHHHHH) +Size 4: 2,4 BAD (HHHHHH) +Size 5: 3,5 BAD (HHHHHH) +Size 6: 4,6 BAD (HHHHHH) +Size 7: 4,7 BAD (HHHHHH) +Size 8: 5,8 BAD (HHHHHH) +Size 9: 5,9 BAD (HHHHHH) +Size 10: 6,10 BAD (HHHHHH) +Size 11: 7,11 BAD (HHHHHH) +Size 12: 7,12 BAD (HHHHHH) +Size 13: 8,13 BAD (HHHHHH) +Size 14: 8,14 BAD (HHHHHH) +Size 15: 9,15 BAD (HHHHHH) +Size 16: 10,16 BAD (HHHHHH) +Size 17: 10,17 BAD (HHHHHH) +Size 18: 11,18 BAD (HHHHHH) +Size 19: 11,19 BAD (HHHHHH) +Size 20: 12,20 BAD (HHHHHH) +Size 21: 13,21 BAD (HHHHHH) +Size 22: 13,22 BAD (HHHHHH) +Size 23: 14,23 BAD (HHHHHH) +Size 24: 14,24 BAD (HHHHHH) +Size 25: 15,25 BAD (HHHHHH) +Size 26: 16,26 BAD (HHHHHH) +Size 27: 16,27 BAD (HHHHHH) +Size 28: 17,28 BAD (HHHHHH) +Size 29: 17,29 BAD (HHHHHH) +Size 30: 18,30 BAD (HHHHHH) +Size 31: 19,31 BAD (HHHHHH) +Size 32: 19,32 BAD (HHHHHH) +Size 33: 20,33 BAD (HHHHHH) +Size 34: 20,34 BAD (HHHHHH) +Size 35: 21,35 BAD (HHHHHH) +Size 36: 22,36 BAD (HHHHHH) +Size 37: 22,37 BAD (HHHHHH) +Size 38: 23,38 BAD (HHHHHH) +Size 39: 23,39 BAD (HHHHHH) +Size 40: 24,40 BAD (HHHHHH) +Size 41: 25,41 BAD (HHHHHH) +Size 42: 25,42 BAD (HHHHHH) +Size 43: 26,43 BAD (HHHHHH) +Size 44: 27,44 BAD (HHHHHH) +Size 45: 27,45 BAD (HHHHHH) +Size 46: 28,46 BAD (HHHHHH) +Size 47: 28,47 BAD (HHHHHH) +Size 48: 29,48 BAD (HHHHHH) +Size 49: 30,49 BAD (HHHHHH) +Size 50: 30,50 BAD (HHHHHH) +Size 51: 31,51 BAD (HHHHHH) +Size 52: 31,52 BAD (HHHHHH) +Size 53: 32,53 BAD (HHHHHH) +Size 54: 33,54 BAD (HHHHHH) +Size 55: 33,55 BAD (HHHHHH) +Size 56: 34,56 BAD (HHHHHH) +Size 57: 34,57 BAD (HHHHHH) +Size 58: 35,58 BAD (HHHHHH) +Size 59: 36,59 BAD (HHHHHH) +Size 60: 36,60 BAD (HHHHHH) +Size 61: 37,61 BAD (HHHHHH) +Size 62: 37,62 BAD (HHHHHH) +Size 63: 38,63 BAD (HHHHHH) +Size 64: 39,65 BAD (HHHHHH) +Size 65: 39,65 BAD (HHHHHH) +Size 66: 40,66 BAD (HHHHHH) +Size 67: 40,67 BAD (HHHHHH) +Size 68: 41,68 BAD (HHHHHH) +Size 69: 42,69 BAD (HHHHHH) +Size 70: 42,70 BAD (HHHHHH) +Size 71: 43,71 BAD (HHHHHH) +Size 72: 43,72 BAD (HHHHHH) +Size 73: 44,73 BAD (HHHHHH) +Size 74: 45,74 BAD (HHHHHH) +Size 75: 45,75 BAD (HHHHHH) +Size 76: 46,76 BAD (HHHHHH) +Size 77: 46,77 BAD (HHHHHH) +Size 78: 47,78 BAD (HHHHHH) +Size 79: 48,79 BAD (HHHHHH) +Size 80: 48,80 BAD (HHHHHH) +Size 81: 49,81 BAD (HHHHHH) +Size 82: 49,82 BAD (HHHHHH) +Size 83: 50,83 BAD (HHHHHH) +Size 84: 51,84 BAD (HHHHHH) +Size 85: 51,85 BAD (HHHHHH) +Size 86: 52,86 BAD (HHHHHH) +Size 87: 52,87 BAD (HHHHHH) +Size 88: 53,88 BAD (HHHHHH) +Size 89: 54,89 BAD (HHHHHH) +Size 90: 54,90 BAD (HHHHHH) +Size 91: 55,91 BAD (HHHHHH) +Size 92: 55,92 BAD (HHHHHH) +Size 93: 56,93 BAD (HHHHHH) +Size 94: 57,94 BAD (HHHHHH) +Size 95: 57,95 BAD (HHHHHH) +Size 96: 58,96 BAD (HHHHHH) +Size 97: 58,97 BAD (HHHHHH) +Size 98: 59,98 BAD (HHHHHH) +Size 99: 60,99 BAD (HHHHHH) +Size 100: 60,100 BAD (HHHHHH) + +Windows 8 +--------- + +Size 1: 1,2 BAD (HHHHHH) +Size 2: 1,2 BAD (HHHHHH) +Size 3: 2,3 BAD (HHHHHH) +Size 4: 2,4 BAD (HHHHHH) +Size 5: 3,5 BAD (HHHHHH) +Size 6: 4,6 BAD (HHHHHH) +Size 7: 4,7 BAD (HHHHHH) +Size 8: 5,8 BAD (HHHHHH) +Size 9: 5,9 BAD (HHHHHH) +Size 10: 6,10 BAD (HHHHHH) +Size 11: 7,11 BAD (HHHHHH) +Size 12: 7,12 BAD (HHHHHH) +Size 13: 8,13 BAD (HHHHHH) +Size 14: 8,14 BAD (HHHHHH) +Size 15: 9,15 BAD (HHHHHH) +Size 16: 10,16 BAD (HHHHHH) +Size 17: 10,17 BAD (HHHHHH) +Size 18: 11,18 BAD (HHHHHH) +Size 19: 11,19 BAD (HHHHHH) +Size 20: 12,20 BAD (HHHHHH) +Size 21: 13,21 BAD (HHHHHH) +Size 22: 13,22 BAD (HHHHHH) +Size 23: 14,23 BAD (HHHHHH) +Size 24: 14,24 BAD (HHHHHH) +Size 25: 15,25 BAD (HHHHHH) +Size 26: 16,26 BAD (HHHHHH) +Size 27: 16,27 BAD (HHHHHH) +Size 28: 17,28 BAD (HHHHHH) +Size 29: 17,29 BAD (HHHHHH) +Size 30: 18,30 BAD (HHHHHH) +Size 31: 19,31 BAD (HHHHHH) +Size 32: 19,32 BAD (HHHHHH) +Size 33: 20,33 BAD (HHHHHH) +Size 34: 20,34 BAD (HHHHHH) +Size 35: 21,35 BAD (HHHHHH) +Size 36: 22,36 BAD (HHHHHH) +Size 37: 22,37 BAD (HHHHHH) +Size 38: 23,38 BAD (HHHHHH) +Size 39: 23,39 BAD (HHHHHH) +Size 40: 24,40 BAD (HHHHHH) +Size 41: 25,41 BAD (HHHHHH) +Size 42: 25,42 BAD (HHHHHH) +Size 43: 26,43 BAD (HHHHHH) +Size 44: 27,44 BAD (HHHHHH) +Size 45: 27,45 BAD (HHHHHH) +Size 46: 28,46 BAD (HHHHHH) +Size 47: 28,47 BAD (HHHHHH) +Size 48: 29,48 BAD (HHHHHH) +Size 49: 30,49 BAD (HHHHHH) +Size 50: 30,50 BAD (HHHHHH) +Size 51: 31,51 BAD (HHHHHH) +Size 52: 31,52 BAD (HHHHHH) +Size 53: 32,53 BAD (HHHHHH) +Size 54: 33,54 BAD (HHHHHH) +Size 55: 33,55 BAD (HHHHHH) +Size 56: 34,56 BAD (HHHHHH) +Size 57: 34,57 BAD (HHHHHH) +Size 58: 35,58 BAD (HHHHHH) +Size 59: 36,59 BAD (HHHHHH) +Size 60: 36,60 BAD (HHHHHH) +Size 61: 37,61 BAD (HHHHHH) +Size 62: 37,62 BAD (HHHHHH) +Size 63: 38,63 BAD (HHHHHH) +Size 64: 39,65 BAD (HHHHHH) +Size 65: 39,65 BAD (HHHHHH) +Size 66: 40,66 BAD (HHHHHH) +Size 67: 40,67 BAD (HHHHHH) +Size 68: 41,68 BAD (HHHHHH) +Size 69: 42,69 BAD (HHHHHH) +Size 70: 42,70 BAD (HHHHHH) +Size 71: 43,71 BAD (HHHHHH) +Size 72: 43,72 BAD (HHHHHH) +Size 73: 44,73 BAD (HHHHHH) +Size 74: 45,74 BAD (HHHHHH) +Size 75: 45,75 BAD (HHHHHH) +Size 76: 46,76 BAD (HHHHHH) +Size 77: 46,77 BAD (HHHHHH) +Size 78: 47,78 BAD (HHHHHH) +Size 79: 48,79 BAD (HHHHHH) +Size 80: 48,80 BAD (HHHHHH) +Size 81: 49,81 BAD (HHHHHH) +Size 82: 49,82 BAD (HHHHHH) +Size 83: 50,83 BAD (HHHHHH) +Size 84: 51,84 BAD (HHHHHH) +Size 85: 51,85 BAD (HHHHHH) +Size 86: 52,86 BAD (HHHHHH) +Size 87: 52,87 BAD (HHHHHH) +Size 88: 53,88 BAD (HHHHHH) +Size 89: 54,89 BAD (HHHHHH) +Size 90: 54,90 BAD (HHHHHH) +Size 91: 55,91 BAD (HHHHHH) +Size 92: 55,92 BAD (HHHHHH) +Size 93: 56,93 BAD (HHHHHH) +Size 94: 57,94 BAD (HHHHHH) +Size 95: 57,95 BAD (HHHHHH) +Size 96: 58,96 BAD (HHHHHH) +Size 97: 58,97 BAD (HHHHHH) +Size 98: 59,98 BAD (HHHHHH) +Size 99: 60,99 BAD (HHHHHH) +Size 100: 60,100 BAD (HHHHHH) + +Windows 8.1 +----------- + +Size 1: 1,2 BAD (HHHHHH) +Size 2: 1,2 BAD (HHHHHH) +Size 3: 2,3 BAD (HHHHHH) +Size 4: 2,4 BAD (HHHHHH) +Size 5: 3,5 BAD (HHHHHH) +Size 6: 4,6 BAD (HHHHHH) +Size 7: 4,7 BAD (HHHHHH) +Size 8: 5,8 BAD (HHHHHH) +Size 9: 5,9 BAD (HHHHHH) +Size 10: 6,10 BAD (HHHHHH) +Size 11: 7,11 BAD (HHHHHH) +Size 12: 7,12 BAD (HHHHHH) +Size 13: 8,13 BAD (HHHHHH) +Size 14: 8,14 BAD (HHHHHH) +Size 15: 9,15 BAD (HHHHHH) +Size 16: 10,16 BAD (HHHHHH) +Size 17: 10,17 BAD (HHHHHH) +Size 18: 11,18 BAD (HHHHHH) +Size 19: 11,19 BAD (HHHHHH) +Size 20: 12,20 BAD (HHHHHH) +Size 21: 13,21 BAD (HHHHHH) +Size 22: 13,22 BAD (HHHHHH) +Size 23: 14,23 BAD (HHHHHH) +Size 24: 14,24 BAD (HHHHHH) +Size 25: 15,25 BAD (HHHHHH) +Size 26: 16,26 BAD (HHHHHH) +Size 27: 16,27 BAD (HHHHHH) +Size 28: 17,28 BAD (HHHHHH) +Size 29: 17,29 BAD (HHHHHH) +Size 30: 18,30 BAD (HHHHHH) +Size 31: 19,31 BAD (HHHHHH) +Size 32: 19,32 BAD (HHHHHH) +Size 33: 20,33 BAD (HHHHHH) +Size 34: 20,34 BAD (HHHHHH) +Size 35: 21,35 BAD (HHHHHH) +Size 36: 22,36 BAD (HHHHHH) +Size 37: 22,37 BAD (HHHHHH) +Size 38: 23,38 BAD (HHHHHH) +Size 39: 23,39 BAD (HHHHHH) +Size 40: 24,40 BAD (HHHHHH) +Size 41: 25,41 BAD (HHHHHH) +Size 42: 25,42 BAD (HHHHHH) +Size 43: 26,43 BAD (HHHHHH) +Size 44: 27,44 BAD (HHHHHH) +Size 45: 27,45 BAD (HHHHHH) +Size 46: 28,46 BAD (HHHHHH) +Size 47: 28,47 BAD (HHHHHH) +Size 48: 29,48 BAD (HHHHHH) +Size 49: 30,49 BAD (HHHHHH) +Size 50: 30,50 BAD (HHHHHH) +Size 51: 31,51 BAD (HHHHHH) +Size 52: 31,52 BAD (HHHHHH) +Size 53: 32,53 BAD (HHHHHH) +Size 54: 33,54 BAD (HHHHHH) +Size 55: 33,55 BAD (HHHHHH) +Size 56: 34,56 BAD (HHHHHH) +Size 57: 34,57 BAD (HHHHHH) +Size 58: 35,58 BAD (HHHHHH) +Size 59: 36,59 BAD (HHHHHH) +Size 60: 36,60 BAD (HHHHHH) +Size 61: 37,61 BAD (HHHHHH) +Size 62: 37,62 BAD (HHHHHH) +Size 63: 38,63 BAD (HHHHHH) +Size 64: 39,65 BAD (HHHHHH) +Size 65: 39,65 BAD (HHHHHH) +Size 66: 40,66 BAD (HHHHHH) +Size 67: 40,67 BAD (HHHHHH) +Size 68: 41,68 BAD (HHHHHH) +Size 69: 42,69 BAD (HHHHHH) +Size 70: 42,70 BAD (HHHHHH) +Size 71: 43,71 BAD (HHHHHH) +Size 72: 43,72 BAD (HHHHHH) +Size 73: 44,73 BAD (HHHHHH) +Size 74: 45,74 BAD (HHHHHH) +Size 75: 45,75 BAD (HHHHHH) +Size 76: 46,76 BAD (HHHHHH) +Size 77: 46,77 BAD (HHHHHH) +Size 78: 47,78 BAD (HHHHHH) +Size 79: 48,79 BAD (HHHHHH) +Size 80: 48,80 BAD (HHHHHH) +Size 81: 49,81 BAD (HHHHHH) +Size 82: 49,82 BAD (HHHHHH) +Size 83: 50,83 BAD (HHHHHH) +Size 84: 51,84 BAD (HHHHHH) +Size 85: 51,85 BAD (HHHHHH) +Size 86: 52,86 BAD (HHHHHH) +Size 87: 52,87 BAD (HHHHHH) +Size 88: 53,88 BAD (HHHHHH) +Size 89: 54,89 BAD (HHHHHH) +Size 90: 54,90 BAD (HHHHHH) +Size 91: 55,91 BAD (HHHHHH) +Size 92: 55,92 BAD (HHHHHH) +Size 93: 56,93 BAD (HHHHHH) +Size 94: 57,94 BAD (HHHHHH) +Size 95: 57,95 BAD (HHHHHH) +Size 96: 58,96 BAD (HHHHHH) +Size 97: 58,97 BAD (HHHHHH) +Size 98: 59,98 BAD (HHHHHH) +Size 99: 60,99 BAD (HHHHHH) +Size 100: 60,100 BAD (HHHHHH) + +Windows 10 14342 Old Console +---------------------------- + +Size 1: 1,2 BAD (HHHHHH) +Size 2: 1,2 BAD (HHHHHH) +Size 3: 2,3 BAD (HHHHHH) +Size 4: 2,4 BAD (HHHHHH) +Size 5: 3,5 BAD (HHHHHH) +Size 6: 4,6 BAD (HHHHHH) +Size 7: 4,7 BAD (HHHHHH) +Size 8: 5,8 BAD (HHHHHH) +Size 9: 5,9 BAD (HHHHHH) +Size 10: 6,10 BAD (HHHHHH) +Size 11: 7,11 BAD (HHHHHH) +Size 12: 7,12 BAD (HHHHHH) +Size 13: 8,13 BAD (HHHHHH) +Size 14: 8,14 BAD (HHHHHH) +Size 15: 9,15 BAD (HHHHHH) +Size 16: 10,16 BAD (HHHHHH) +Size 17: 10,17 BAD (HHHHHH) +Size 18: 11,18 BAD (HHHHHH) +Size 19: 11,19 BAD (HHHHHH) +Size 20: 12,20 BAD (HHHHHH) +Size 21: 13,21 BAD (HHHHHH) +Size 22: 13,22 BAD (HHHHHH) +Size 23: 14,23 BAD (HHHHHH) +Size 24: 14,24 BAD (HHHHHH) +Size 25: 15,25 BAD (HHHHHH) +Size 26: 16,26 BAD (HHHHHH) +Size 27: 16,27 BAD (HHHHHH) +Size 28: 17,28 BAD (HHHHHH) +Size 29: 17,29 BAD (HHHHHH) +Size 30: 18,30 BAD (HHHHHH) +Size 31: 19,31 BAD (HHHHHH) +Size 32: 19,32 BAD (HHHHHH) +Size 33: 20,33 BAD (HHHHHH) +Size 34: 20,34 BAD (HHHHHH) +Size 35: 21,35 BAD (HHHHHH) +Size 36: 22,36 BAD (HHHHHH) +Size 37: 22,37 BAD (HHHHHH) +Size 38: 23,38 BAD (HHHHHH) +Size 39: 23,39 BAD (HHHHHH) +Size 40: 24,40 BAD (HHHHHH) +Size 41: 25,41 BAD (HHHHHH) +Size 42: 25,42 BAD (HHHHHH) +Size 43: 26,43 BAD (HHHHHH) +Size 44: 27,44 BAD (HHHHHH) +Size 45: 27,45 BAD (HHHHHH) +Size 46: 28,46 BAD (HHHHHH) +Size 47: 28,47 BAD (HHHHHH) +Size 48: 29,48 BAD (HHHHHH) +Size 49: 30,49 BAD (HHHHHH) +Size 50: 30,50 BAD (HHHHHH) +Size 51: 31,51 BAD (HHHHHH) +Size 52: 31,52 BAD (HHHHHH) +Size 53: 32,53 BAD (HHHHHH) +Size 54: 33,54 BAD (HHHHHH) +Size 55: 33,55 BAD (HHHHHH) +Size 56: 34,56 BAD (HHHHHH) +Size 57: 34,57 BAD (HHHHHH) +Size 58: 35,58 BAD (HHHHHH) +Size 59: 36,59 BAD (HHHHHH) +Size 60: 36,60 BAD (HHHHHH) +Size 61: 37,61 BAD (HHHHHH) +Size 62: 37,62 BAD (HHHHHH) +Size 63: 38,63 BAD (HHHHHH) +Size 64: 39,65 BAD (HHHHHH) +Size 65: 39,65 BAD (HHHHHH) +Size 66: 40,66 BAD (HHHHHH) +Size 67: 40,67 BAD (HHHHHH) +Size 68: 41,68 BAD (HHHHHH) +Size 69: 42,69 BAD (HHHHHH) +Size 70: 42,70 BAD (HHHHHH) +Size 71: 43,71 BAD (HHHHHH) +Size 72: 43,72 BAD (HHHHHH) +Size 73: 44,73 BAD (HHHHHH) +Size 74: 45,74 BAD (HHHHHH) +Size 75: 45,75 BAD (HHHHHH) +Size 76: 46,76 BAD (HHHHHH) +Size 77: 46,77 BAD (HHHHHH) +Size 78: 47,78 BAD (HHHHHH) +Size 79: 48,79 BAD (HHHHHH) +Size 80: 48,80 BAD (HHHHHH) +Size 81: 49,81 BAD (HHHHHH) +Size 82: 49,82 BAD (HHHHHH) +Size 83: 50,83 BAD (HHHHHH) +Size 84: 51,84 BAD (HHHHHH) +Size 85: 51,85 BAD (HHHHHH) +Size 86: 52,86 BAD (HHHHHH) +Size 87: 52,87 BAD (HHHHHH) +Size 88: 53,88 BAD (HHHHHH) +Size 89: 54,89 BAD (HHHHHH) +Size 90: 54,90 BAD (HHHHHH) +Size 91: 55,91 BAD (HHHHHH) +Size 92: 55,92 BAD (HHHHHH) +Size 93: 56,93 BAD (HHHHHH) +Size 94: 57,94 BAD (HHHHHH) +Size 95: 57,95 BAD (HHHHHH) +Size 96: 58,96 BAD (HHHHHH) +Size 97: 58,97 BAD (HHHHHH) +Size 98: 59,98 BAD (HHHHHH) +Size 99: 60,99 BAD (HHHHHH) +Size 100: 60,100 BAD (HHHHHH) + +Windows 10 14342 New Console +---------------------------- + +Size 1: 1,1 BAD (HHHHHH) +Size 2: 1,2 BAD (HHHHHH) +Size 3: 2,3 BAD (HHHHHH) +Size 4: 2,4 BAD (HHHHHH) +Size 5: 3,5 BAD (HHHHHH) +Size 6: 4,6 BAD (HHHHHH) +Size 7: 4,7 BAD (HHHHHH) +Size 8: 5,8 BAD (HHHHHH) +Size 9: 5,9 BAD (HHHHHH) +Size 10: 6,10 BAD (HHHHHH) +Size 11: 7,11 BAD (HHHHHH) +Size 12: 7,12 BAD (HHHHHH) +Size 13: 8,13 BAD (HHHHHH) +Size 14: 8,14 BAD (HHHHHH) +Size 15: 9,15 BAD (HHHHHH) +Size 16: 10,16 BAD (HHHHHH) +Size 17: 10,17 BAD (HHHHHH) +Size 18: 11,18 BAD (HHHHHH) +Size 19: 11,19 BAD (HHHHHH) +Size 20: 12,20 BAD (HHHHHH) +Size 21: 13,21 BAD (HHHHHH) +Size 22: 13,22 BAD (HHHHHH) +Size 23: 14,23 BAD (HHHHHH) +Size 24: 14,24 BAD (HHHHHH) +Size 25: 15,25 BAD (HHHHHH) +Size 26: 16,26 BAD (HHHHHH) +Size 27: 16,27 BAD (HHHHHH) +Size 28: 17,28 BAD (HHHHHH) +Size 29: 17,29 BAD (HHHHHH) +Size 30: 18,30 BAD (HHHHHH) +Size 31: 19,31 BAD (HHHHHH) +Size 32: 19,32 BAD (HHHHHH) +Size 33: 20,33 BAD (HHHHHH) +Size 34: 20,34 BAD (HHHHHH) +Size 35: 21,35 BAD (HHHHHH) +Size 36: 22,36 BAD (HHHHHH) +Size 37: 22,37 BAD (HHHHHH) +Size 38: 23,38 BAD (HHHHHH) +Size 39: 23,39 BAD (HHHHHH) +Size 40: 24,40 BAD (HHHHHH) +Size 41: 25,41 BAD (HHHHHH) +Size 42: 25,42 BAD (HHHHHH) +Size 43: 26,43 BAD (HHHHHH) +Size 44: 27,44 BAD (HHHHHH) +Size 45: 27,45 BAD (HHHHHH) +Size 46: 28,46 BAD (HHHHHH) +Size 47: 28,47 BAD (HHHHHH) +Size 48: 29,48 BAD (HHHHHH) +Size 49: 30,49 BAD (HHHHHH) +Size 50: 30,50 BAD (HHHHHH) +Size 51: 31,51 BAD (HHHHHH) +Size 52: 31,52 BAD (HHHHHH) +Size 53: 32,53 BAD (HHHHHH) +Size 54: 33,54 BAD (HHHHHH) +Size 55: 33,55 BAD (HHHHHH) +Size 56: 34,56 BAD (HHHHHH) +Size 57: 34,57 BAD (HHHHHH) +Size 58: 35,58 BAD (HHHHHH) +Size 59: 36,59 BAD (HHHHHH) +Size 60: 36,60 BAD (HHHHHH) +Size 61: 37,61 BAD (HHHHHH) +Size 62: 37,62 BAD (HHHHHH) +Size 63: 38,63 BAD (HHHHHH) +Size 64: 39,64 BAD (HHHHHH) +Size 65: 39,65 BAD (HHHHHH) +Size 66: 40,66 BAD (HHHHHH) +Size 67: 40,67 BAD (HHHHHH) +Size 68: 41,68 BAD (HHHHHH) +Size 69: 42,69 BAD (HHHHHH) +Size 70: 42,70 BAD (HHHHHH) +Size 71: 43,71 BAD (HHHHHH) +Size 72: 43,72 BAD (HHHHHH) +Size 73: 44,73 BAD (HHHHHH) +Size 74: 45,74 BAD (HHHHHH) +Size 75: 45,75 BAD (HHHHHH) +Size 76: 46,76 BAD (HHHHHH) +Size 77: 46,77 BAD (HHHHHH) +Size 78: 47,78 BAD (HHHHHH) +Size 79: 48,79 BAD (HHHHHH) +Size 80: 48,80 BAD (HHHHHH) +Size 81: 49,81 BAD (HHHHHH) +Size 82: 49,82 BAD (HHHHHH) +Size 83: 50,83 BAD (HHHHHH) +Size 84: 51,84 BAD (HHHHHH) +Size 85: 51,85 BAD (HHHHHH) +Size 86: 52,86 BAD (HHHHHH) +Size 87: 52,87 BAD (HHHHHH) +Size 88: 53,88 BAD (HHHHHH) +Size 89: 54,89 BAD (HHHHHH) +Size 90: 54,90 BAD (HHHHHH) +Size 91: 55,91 BAD (HHHHHH) +Size 92: 55,92 BAD (HHHHHH) +Size 93: 56,93 BAD (HHHHHH) +Size 94: 57,94 BAD (HHHHHH) +Size 95: 57,95 BAD (HHHHHH) +Size 96: 58,96 BAD (HHHHHH) +Size 97: 58,97 BAD (HHHHHH) +Size 98: 59,98 BAD (HHHHHH) +Size 99: 60,99 BAD (HHHHHH) +Size 100: 60,100 BAD (HHHHHH) diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP932.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP932.txt new file mode 100644 index 0000000000..ed3637eac1 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP932.txt @@ -0,0 +1,630 @@ +======================================= +Code Page 932, Japanese, MS Gothic font +======================================= + +Options: -face-gothic -family 0x36 +Chars: A2 A3 2014 3044 30FC 4000 + +Vista +----- + +Size 1: 1,2 OK (HHHFFF) +Size 2: 1,2 OK (HHHFFF) +Size 3: 2,3 BAD (FFFFHH) +Size 4: 2,4 OK (HHHFFF) +Size 5: 3,5 OK (HHHFFF) +Size 6: 3,6 OK (HHHFFF) +Size 7: 4,7 OK (HHHFFF) +Size 8: 4,8 OK (HHHFFF) +Size 9: 5,9 OK (HHHFFF) +Size 10: 5,10 OK (HHHFFF) +Size 11: 6,11 OK (HHHFFF) +Size 12: 6,12 OK (HHHFFF) +Size 13: 7,13 OK (HHHFFF) +Size 14: 7,14 BAD (HHHFHH) +Size 15: 8,15 OK (HHHFFF) +Size 16: 8,16 BAD (HHHFHH) +Size 17: 9,17 OK (HHHFFF) +Size 18: 9,18 BAD (HHHFHH) +Size 19: 10,19 OK (HHHFFF) +Size 20: 10,20 BAD (HHHFHH) +Size 21: 11,21 OK (HHHFFF) +Size 22: 11,22 BAD (HHHFHH) +Size 23: 12,23 BAD (HHHFHH) +Size 24: 12,24 BAD (HHHFHH) +Size 25: 13,25 BAD (HHHFHH) +Size 26: 13,26 BAD (HHHFHH) +Size 27: 14,27 BAD (HHHFHH) +Size 28: 14,28 BAD (HHHFHH) +Size 29: 15,29 BAD (HHHFHH) +Size 30: 15,30 BAD (HHHFHH) +Size 31: 16,31 BAD (HHHFHH) +Size 32: 16,33 BAD (HHHFHH) +Size 33: 17,33 BAD (HHHFHH) +Size 34: 17,34 BAD (HHHFHH) +Size 35: 18,35 BAD (HHHFHH) +Size 36: 18,36 BAD (HHHFHH) +Size 37: 19,37 BAD (HHHFHH) +Size 38: 19,38 BAD (HHHFHH) +Size 39: 20,39 BAD (HHHFHH) +Size 40: 20,40 BAD (HHHFHH) +Size 41: 21,41 BAD (HHHFHH) +Size 42: 21,42 BAD (HHHFHH) +Size 43: 22,43 BAD (HHHFHH) +Size 44: 22,44 BAD (HHHFHH) +Size 45: 23,45 BAD (HHHFHH) +Size 46: 23,46 BAD (HHHFHH) +Size 47: 24,47 BAD (HHHFHH) +Size 48: 24,48 BAD (HHHFHH) +Size 49: 25,49 BAD (HHHFHH) +Size 50: 25,50 BAD (HHHFHH) +Size 51: 26,51 BAD (HHHFHH) +Size 52: 26,52 BAD (HHHFHH) +Size 53: 27,53 BAD (HHHFHH) +Size 54: 27,54 BAD (HHHFHH) +Size 55: 28,55 BAD (HHHFHH) +Size 56: 28,56 BAD (HHHFHH) +Size 57: 29,57 BAD (HHHFHH) +Size 58: 29,58 BAD (HHHFHH) +Size 59: 30,59 BAD (HHHFHH) +Size 60: 30,60 BAD (HHHFHH) +Size 61: 31,61 BAD (HHHFHH) +Size 62: 31,62 BAD (HHHFHH) +Size 63: 32,63 BAD (HHHFHH) +Size 64: 32,64 BAD (HHHFHH) +Size 65: 33,65 BAD (HHHFHH) +Size 66: 33,66 BAD (HHHFHH) +Size 67: 34,67 BAD (HHHFHH) +Size 68: 34,68 BAD (HHHFHH) +Size 69: 35,69 BAD (HHHFHH) +Size 70: 35,70 BAD (HHHFHH) +Size 71: 36,71 BAD (HHHFHH) +Size 72: 36,72 BAD (HHHFHH) +Size 73: 37,73 BAD (HHHFHH) +Size 74: 37,74 BAD (HHHFHH) +Size 75: 38,75 BAD (HHHFHH) +Size 76: 38,76 BAD (HHHFHH) +Size 77: 39,77 BAD (HHHFHH) +Size 78: 39,78 BAD (HHHFHH) +Size 79: 40,79 BAD (HHHFHH) +Size 80: 40,80 BAD (HHHFHH) +Size 81: 41,81 BAD (HHHFHH) +Size 82: 41,82 BAD (HHHFHH) +Size 83: 42,83 BAD (HHHFHH) +Size 84: 42,84 BAD (HHHFHH) +Size 85: 43,85 BAD (HHHFHH) +Size 86: 43,86 BAD (HHHFHH) +Size 87: 44,87 BAD (HHHFHH) +Size 88: 44,88 BAD (HHHFHH) +Size 89: 45,89 BAD (HHHFHH) +Size 90: 45,90 BAD (HHHFHH) +Size 91: 46,91 BAD (HHHFHH) +Size 92: 46,92 BAD (HHHFHH) +Size 93: 47,93 BAD (HHHFHH) +Size 94: 47,94 BAD (HHHFHH) +Size 95: 48,95 BAD (HHHFHH) +Size 96: 48,97 BAD (HHHFHH) +Size 97: 49,97 BAD (HHHFHH) +Size 98: 49,98 BAD (HHHFHH) +Size 99: 50,99 BAD (HHHFHH) +Size 100: 50,100 BAD (HHHFHH) + +Windows 7 +--------- + +Size 1: 1,2 OK (HHHFFF) +Size 2: 1,2 OK (HHHFFF) +Size 3: 2,3 BAD (FFFFHH) +Size 4: 2,4 OK (HHHFFF) +Size 5: 3,5 OK (HHHFFF) +Size 6: 3,6 OK (HHHFFF) +Size 7: 4,7 OK (HHHFFF) +Size 8: 4,8 OK (HHHFFF) +Size 9: 5,9 OK (HHHFFF) +Size 10: 5,10 OK (HHHFFF) +Size 11: 6,11 OK (HHHFFF) +Size 12: 6,12 OK (HHHFFF) +Size 13: 7,13 OK (HHHFFF) +Size 14: 7,14 BAD (FFFFFF) +Size 15: 8,15 OK (HHHFFF) +Size 16: 8,16 BAD (FFFFFF) +Size 17: 9,17 OK (HHHFFF) +Size 18: 9,18 BAD (FFFFFF) +Size 19: 10,19 OK (HHHFFF) +Size 20: 10,20 BAD (FFFFFF) +Size 21: 11,21 OK (HHHFFF) +Size 22: 11,22 BAD (FFFFFF) +Size 23: 12,23 BAD (FFFFFF) +Size 24: 12,24 BAD (FFFFFF) +Size 25: 13,25 BAD (FFFFFF) +Size 26: 13,26 BAD (FFFFFF) +Size 27: 14,27 BAD (FFFFFF) +Size 28: 14,28 BAD (FFFFFF) +Size 29: 15,29 BAD (FFFFFF) +Size 30: 15,30 BAD (FFFFFF) +Size 31: 16,31 BAD (FFFFFF) +Size 32: 16,33 BAD (FFFFFF) +Size 33: 17,33 BAD (FFFFFF) +Size 34: 17,34 BAD (FFFFFF) +Size 35: 18,35 BAD (FFFFFF) +Size 36: 18,36 BAD (FFFFFF) +Size 37: 19,37 BAD (FFFFFF) +Size 38: 19,38 BAD (FFFFFF) +Size 39: 20,39 BAD (FFFFFF) +Size 40: 20,40 BAD (FFFFFF) +Size 41: 21,41 BAD (FFFFFF) +Size 42: 21,42 BAD (FFFFFF) +Size 43: 22,43 BAD (FFFFFF) +Size 44: 22,44 BAD (FFFFFF) +Size 45: 23,45 BAD (FFFFFF) +Size 46: 23,46 BAD (FFFFFF) +Size 47: 24,47 BAD (FFFFFF) +Size 48: 24,48 BAD (FFFFFF) +Size 49: 25,49 BAD (FFFFFF) +Size 50: 25,50 BAD (FFFFFF) +Size 51: 26,51 BAD (FFFFFF) +Size 52: 26,52 BAD (FFFFFF) +Size 53: 27,53 BAD (FFFFFF) +Size 54: 27,54 BAD (FFFFFF) +Size 55: 28,55 BAD (FFFFFF) +Size 56: 28,56 BAD (FFFFFF) +Size 57: 29,57 BAD (FFFFFF) +Size 58: 29,58 BAD (FFFFFF) +Size 59: 30,59 BAD (FFFFFF) +Size 60: 30,60 BAD (FFFFFF) +Size 61: 31,61 BAD (FFFFFF) +Size 62: 31,62 BAD (FFFFFF) +Size 63: 32,63 BAD (FFFFFF) +Size 64: 32,64 BAD (FFFFFF) +Size 65: 33,65 BAD (FFFFFF) +Size 66: 33,66 BAD (FFFFFF) +Size 67: 34,67 BAD (FFFFFF) +Size 68: 34,68 BAD (FFFFFF) +Size 69: 35,69 BAD (FFFFFF) +Size 70: 35,70 BAD (FFFFFF) +Size 71: 36,71 BAD (FFFFFF) +Size 72: 36,72 BAD (FFFFFF) +Size 73: 37,73 BAD (FFFFFF) +Size 74: 37,74 BAD (FFFFFF) +Size 75: 38,75 BAD (FFFFFF) +Size 76: 38,76 BAD (FFFFFF) +Size 77: 39,77 BAD (FFFFFF) +Size 78: 39,78 BAD (FFFFFF) +Size 79: 40,79 BAD (FFFFFF) +Size 80: 40,80 BAD (FFFFFF) +Size 81: 41,81 BAD (FFFFFF) +Size 82: 41,82 BAD (FFFFFF) +Size 83: 42,83 BAD (FFFFFF) +Size 84: 42,84 BAD (FFFFFF) +Size 85: 43,85 BAD (FFFFFF) +Size 86: 43,86 BAD (FFFFFF) +Size 87: 44,87 BAD (FFFFFF) +Size 88: 44,88 BAD (FFFFFF) +Size 89: 45,89 BAD (FFFFFF) +Size 90: 45,90 BAD (FFFFFF) +Size 91: 46,91 BAD (FFFFFF) +Size 92: 46,92 BAD (FFFFFF) +Size 93: 47,93 BAD (FFFFFF) +Size 94: 47,94 BAD (FFFFFF) +Size 95: 48,95 BAD (FFFFFF) +Size 96: 48,97 BAD (FFFFFF) +Size 97: 49,97 BAD (FFFFFF) +Size 98: 49,98 BAD (FFFFFF) +Size 99: 50,99 BAD (FFFFFF) +Size 100: 50,100 BAD (FFFFFF) + +Windows 8 +--------- + +Size 1: 1,2 BAD (FFFFHH) +Size 2: 1,2 BAD (FFFFHH) +Size 3: 2,3 BAD (FFFFFF) +Size 4: 2,4 BAD (FFFFHH) +Size 5: 3,5 BAD (FFFFFF) +Size 6: 3,6 BAD (FFFFHH) +Size 7: 4,7 BAD (FFFFFF) +Size 8: 4,8 BAD (FFFFHH) +Size 9: 5,9 BAD (FFFFFF) +Size 10: 5,10 BAD (FFFFHH) +Size 11: 6,11 BAD (FFFFFF) +Size 12: 6,12 BAD (FFFFHH) +Size 13: 7,13 BAD (FFFFFF) +Size 14: 7,14 BAD (FFFFHH) +Size 15: 8,15 BAD (FFFFFF) +Size 16: 8,16 BAD (FFFFHH) +Size 17: 9,17 BAD (FFFFFF) +Size 18: 9,18 BAD (FFFFHH) +Size 19: 10,19 BAD (FFFFFF) +Size 20: 10,20 BAD (FFFFFF) +Size 21: 11,21 BAD (FFFFFF) +Size 22: 11,22 BAD (FFFFFF) +Size 23: 12,23 BAD (FFFFFF) +Size 24: 12,24 BAD (FFFFFF) +Size 25: 13,25 BAD (FFFFFF) +Size 26: 13,26 BAD (FFFFFF) +Size 27: 14,27 BAD (FFFFFF) +Size 28: 14,28 BAD (FFFFFF) +Size 29: 15,29 BAD (FFFFFF) +Size 30: 15,30 BAD (FFFFFF) +Size 31: 16,31 BAD (FFFFFF) +Size 32: 16,33 BAD (FFFFFF) +Size 33: 17,33 BAD (FFFFFF) +Size 34: 17,34 BAD (FFFFFF) +Size 35: 18,35 BAD (FFFFFF) +Size 36: 18,36 BAD (FFFFFF) +Size 37: 19,37 BAD (FFFFFF) +Size 38: 19,38 BAD (FFFFFF) +Size 39: 20,39 BAD (FFFFFF) +Size 40: 20,40 BAD (FFFFFF) +Size 41: 21,41 BAD (FFFFFF) +Size 42: 21,42 BAD (FFFFFF) +Size 43: 22,43 BAD (FFFFFF) +Size 44: 22,44 BAD (FFFFFF) +Size 45: 23,45 BAD (FFFFFF) +Size 46: 23,46 BAD (FFFFFF) +Size 47: 24,47 BAD (FFFFFF) +Size 48: 24,48 BAD (FFFFFF) +Size 49: 25,49 BAD (FFFFFF) +Size 50: 25,50 BAD (FFFFFF) +Size 51: 26,51 BAD (FFFFFF) +Size 52: 26,52 BAD (FFFFFF) +Size 53: 27,53 BAD (FFFFFF) +Size 54: 27,54 BAD (FFFFFF) +Size 55: 28,55 BAD (FFFFFF) +Size 56: 28,56 BAD (FFFFFF) +Size 57: 29,57 BAD (FFFFFF) +Size 58: 29,58 BAD (FFFFFF) +Size 59: 30,59 BAD (FFFFFF) +Size 60: 30,60 BAD (FFFFFF) +Size 61: 31,61 BAD (FFFFFF) +Size 62: 31,62 BAD (FFFFFF) +Size 63: 32,63 BAD (FFFFFF) +Size 64: 32,64 BAD (FFFFFF) +Size 65: 33,65 BAD (FFFFFF) +Size 66: 33,66 BAD (FFFFFF) +Size 67: 34,67 BAD (FFFFFF) +Size 68: 34,68 BAD (FFFFFF) +Size 69: 35,69 BAD (FFFFFF) +Size 70: 35,70 BAD (FFFFFF) +Size 71: 36,71 BAD (FFFFFF) +Size 72: 36,72 BAD (FFFFFF) +Size 73: 37,73 BAD (FFFFFF) +Size 74: 37,74 BAD (FFFFFF) +Size 75: 38,75 BAD (FFFFFF) +Size 76: 38,76 BAD (FFFFFF) +Size 77: 39,77 BAD (FFFFFF) +Size 78: 39,78 BAD (FFFFFF) +Size 79: 40,79 BAD (FFFFFF) +Size 80: 40,80 BAD (FFFFFF) +Size 81: 41,81 BAD (FFFFFF) +Size 82: 41,82 BAD (FFFFFF) +Size 83: 42,83 BAD (FFFFFF) +Size 84: 42,84 BAD (FFFFFF) +Size 85: 43,85 BAD (FFFFFF) +Size 86: 43,86 BAD (FFFFFF) +Size 87: 44,87 BAD (FFFFFF) +Size 88: 44,88 BAD (FFFFFF) +Size 89: 45,89 BAD (FFFFFF) +Size 90: 45,90 BAD (FFFFFF) +Size 91: 46,91 BAD (FFFFFF) +Size 92: 46,92 BAD (FFFFFF) +Size 93: 47,93 BAD (FFFFFF) +Size 94: 47,94 BAD (FFFFFF) +Size 95: 48,95 BAD (FFFFFF) +Size 96: 48,97 BAD (FFFFFF) +Size 97: 49,97 BAD (FFFFFF) +Size 98: 49,98 BAD (FFFFFF) +Size 99: 50,99 BAD (FFFFFF) +Size 100: 50,100 BAD (FFFFFF) + +Windows 8.1 +----------- + +Size 1: 1,2 BAD (FFFFHH) +Size 2: 1,2 BAD (FFFFHH) +Size 3: 2,3 BAD (FFFFFF) +Size 4: 2,4 BAD (FFFFHH) +Size 5: 3,5 BAD (FFFFFF) +Size 6: 3,6 BAD (FFFFHH) +Size 7: 4,7 BAD (FFFFFF) +Size 8: 4,8 BAD (FFFFHH) +Size 9: 5,9 BAD (FFFFFF) +Size 10: 5,10 BAD (FFFFHH) +Size 11: 6,11 BAD (FFFFFF) +Size 12: 6,12 BAD (FFFFHH) +Size 13: 7,13 BAD (FFFFFF) +Size 14: 7,14 BAD (FFFFHH) +Size 15: 8,15 BAD (FFFFFF) +Size 16: 8,16 BAD (FFFFHH) +Size 17: 9,17 BAD (FFFFFF) +Size 18: 9,18 BAD (FFFFHH) +Size 19: 10,19 BAD (FFFFFF) +Size 20: 10,20 BAD (FFFFFF) +Size 21: 11,21 BAD (FFFFFF) +Size 22: 11,22 BAD (FFFFFF) +Size 23: 12,23 BAD (FFFFFF) +Size 24: 12,24 BAD (FFFFFF) +Size 25: 13,25 BAD (FFFFFF) +Size 26: 13,26 BAD (FFFFFF) +Size 27: 14,27 BAD (FFFFFF) +Size 28: 14,28 BAD (FFFFFF) +Size 29: 15,29 BAD (FFFFFF) +Size 30: 15,30 BAD (FFFFFF) +Size 31: 16,31 BAD (FFFFFF) +Size 32: 16,33 BAD (FFFFFF) +Size 33: 17,33 BAD (FFFFFF) +Size 34: 17,34 BAD (FFFFFF) +Size 35: 18,35 BAD (FFFFFF) +Size 36: 18,36 BAD (FFFFFF) +Size 37: 19,37 BAD (FFFFFF) +Size 38: 19,38 BAD (FFFFFF) +Size 39: 20,39 BAD (FFFFFF) +Size 40: 20,40 BAD (FFFFFF) +Size 41: 21,41 BAD (FFFFFF) +Size 42: 21,42 BAD (FFFFFF) +Size 43: 22,43 BAD (FFFFFF) +Size 44: 22,44 BAD (FFFFFF) +Size 45: 23,45 BAD (FFFFFF) +Size 46: 23,46 BAD (FFFFFF) +Size 47: 24,47 BAD (FFFFFF) +Size 48: 24,48 BAD (FFFFFF) +Size 49: 25,49 BAD (FFFFFF) +Size 50: 25,50 BAD (FFFFFF) +Size 51: 26,51 BAD (FFFFFF) +Size 52: 26,52 BAD (FFFFFF) +Size 53: 27,53 BAD (FFFFFF) +Size 54: 27,54 BAD (FFFFFF) +Size 55: 28,55 BAD (FFFFFF) +Size 56: 28,56 BAD (FFFFFF) +Size 57: 29,57 BAD (FFFFFF) +Size 58: 29,58 BAD (FFFFFF) +Size 59: 30,59 BAD (FFFFFF) +Size 60: 30,60 BAD (FFFFFF) +Size 61: 31,61 BAD (FFFFFF) +Size 62: 31,62 BAD (FFFFFF) +Size 63: 32,63 BAD (FFFFFF) +Size 64: 32,64 BAD (FFFFFF) +Size 65: 33,65 BAD (FFFFFF) +Size 66: 33,66 BAD (FFFFFF) +Size 67: 34,67 BAD (FFFFFF) +Size 68: 34,68 BAD (FFFFFF) +Size 69: 35,69 BAD (FFFFFF) +Size 70: 35,70 BAD (FFFFFF) +Size 71: 36,71 BAD (FFFFFF) +Size 72: 36,72 BAD (FFFFFF) +Size 73: 37,73 BAD (FFFFFF) +Size 74: 37,74 BAD (FFFFFF) +Size 75: 38,75 BAD (FFFFFF) +Size 76: 38,76 BAD (FFFFFF) +Size 77: 39,77 BAD (FFFFFF) +Size 78: 39,78 BAD (FFFFFF) +Size 79: 40,79 BAD (FFFFFF) +Size 80: 40,80 BAD (FFFFFF) +Size 81: 41,81 BAD (FFFFFF) +Size 82: 41,82 BAD (FFFFFF) +Size 83: 42,83 BAD (FFFFFF) +Size 84: 42,84 BAD (FFFFFF) +Size 85: 43,85 BAD (FFFFFF) +Size 86: 43,86 BAD (FFFFFF) +Size 87: 44,87 BAD (FFFFFF) +Size 88: 44,88 BAD (FFFFFF) +Size 89: 45,89 BAD (FFFFFF) +Size 90: 45,90 BAD (FFFFFF) +Size 91: 46,91 BAD (FFFFFF) +Size 92: 46,92 BAD (FFFFFF) +Size 93: 47,93 BAD (FFFFFF) +Size 94: 47,94 BAD (FFFFFF) +Size 95: 48,95 BAD (FFFFFF) +Size 96: 48,97 BAD (FFFFFF) +Size 97: 49,97 BAD (FFFFFF) +Size 98: 49,98 BAD (FFFFFF) +Size 99: 50,99 BAD (FFFFFF) +Size 100: 50,100 BAD (FFFFFF) + +Windows 10 14342 Old Console +---------------------------- + +Size 1: 1,2 BAD (FFFFHH) +Size 2: 1,2 BAD (FFFFHH) +Size 3: 2,3 BAD (FFFFFF) +Size 4: 2,4 BAD (FFFFHH) +Size 5: 3,5 BAD (FFFFFF) +Size 6: 3,6 BAD (FFFFHH) +Size 7: 4,7 BAD (FFFFFF) +Size 8: 4,8 BAD (FFFFHH) +Size 9: 5,9 BAD (FFFFFF) +Size 10: 5,10 BAD (FFFFHH) +Size 11: 6,11 BAD (FFFFFF) +Size 12: 6,12 BAD (FFFFHH) +Size 13: 7,13 BAD (FFFFFF) +Size 14: 7,14 BAD (FFFFHH) +Size 15: 8,15 BAD (FFFFFF) +Size 16: 8,16 BAD (FFFFHH) +Size 17: 9,17 BAD (FFFFFF) +Size 18: 9,18 BAD (FFFFHH) +Size 19: 10,19 BAD (FFFFFF) +Size 20: 10,20 BAD (FFFFFF) +Size 21: 11,21 BAD (FFFFFF) +Size 22: 11,22 BAD (FFFFFF) +Size 23: 12,23 BAD (FFFFFF) +Size 24: 12,24 BAD (FFFFFF) +Size 25: 13,25 BAD (FFFFFF) +Size 26: 13,26 BAD (FFFFFF) +Size 27: 14,27 BAD (FFFFFF) +Size 28: 14,28 BAD (FFFFFF) +Size 29: 15,29 BAD (FFFFFF) +Size 30: 15,30 BAD (FFFFFF) +Size 31: 16,31 BAD (FFFFFF) +Size 32: 16,33 BAD (FFFFFF) +Size 33: 17,33 BAD (FFFFFF) +Size 34: 17,34 BAD (FFFFFF) +Size 35: 18,35 BAD (FFFFFF) +Size 36: 18,36 BAD (FFFFFF) +Size 37: 19,37 BAD (FFFFFF) +Size 38: 19,38 BAD (FFFFFF) +Size 39: 20,39 BAD (FFFFFF) +Size 40: 20,40 BAD (FFFFFF) +Size 41: 21,41 BAD (FFFFFF) +Size 42: 21,42 BAD (FFFFFF) +Size 43: 22,43 BAD (FFFFFF) +Size 44: 22,44 BAD (FFFFFF) +Size 45: 23,45 BAD (FFFFFF) +Size 46: 23,46 BAD (FFFFFF) +Size 47: 24,47 BAD (FFFFFF) +Size 48: 24,48 BAD (FFFFFF) +Size 49: 25,49 BAD (FFFFFF) +Size 50: 25,50 BAD (FFFFFF) +Size 51: 26,51 BAD (FFFFFF) +Size 52: 26,52 BAD (FFFFFF) +Size 53: 27,53 BAD (FFFFFF) +Size 54: 27,54 BAD (FFFFFF) +Size 55: 28,55 BAD (FFFFFF) +Size 56: 28,56 BAD (FFFFFF) +Size 57: 29,57 BAD (FFFFFF) +Size 58: 29,58 BAD (FFFFFF) +Size 59: 30,59 BAD (FFFFFF) +Size 60: 30,60 BAD (FFFFFF) +Size 61: 31,61 BAD (FFFFFF) +Size 62: 31,62 BAD (FFFFFF) +Size 63: 32,63 BAD (FFFFFF) +Size 64: 32,64 BAD (FFFFFF) +Size 65: 33,65 BAD (FFFFFF) +Size 66: 33,66 BAD (FFFFFF) +Size 67: 34,67 BAD (FFFFFF) +Size 68: 34,68 BAD (FFFFFF) +Size 69: 35,69 BAD (FFFFFF) +Size 70: 35,70 BAD (FFFFFF) +Size 71: 36,71 BAD (FFFFFF) +Size 72: 36,72 BAD (FFFFFF) +Size 73: 37,73 BAD (FFFFFF) +Size 74: 37,74 BAD (FFFFFF) +Size 75: 38,75 BAD (FFFFFF) +Size 76: 38,76 BAD (FFFFFF) +Size 77: 39,77 BAD (FFFFFF) +Size 78: 39,78 BAD (FFFFFF) +Size 79: 40,79 BAD (FFFFFF) +Size 80: 40,80 BAD (FFFFFF) +Size 81: 41,81 BAD (FFFFFF) +Size 82: 41,82 BAD (FFFFFF) +Size 83: 42,83 BAD (FFFFFF) +Size 84: 42,84 BAD (FFFFFF) +Size 85: 43,85 BAD (FFFFFF) +Size 86: 43,86 BAD (FFFFFF) +Size 87: 44,87 BAD (FFFFFF) +Size 88: 44,88 BAD (FFFFFF) +Size 89: 45,89 BAD (FFFFFF) +Size 90: 45,90 BAD (FFFFFF) +Size 91: 46,91 BAD (FFFFFF) +Size 92: 46,92 BAD (FFFFFF) +Size 93: 47,93 BAD (FFFFFF) +Size 94: 47,94 BAD (FFFFFF) +Size 95: 48,95 BAD (FFFFFF) +Size 96: 48,97 BAD (FFFFFF) +Size 97: 49,97 BAD (FFFFFF) +Size 98: 49,98 BAD (FFFFFF) +Size 99: 50,99 BAD (FFFFFF) +Size 100: 50,100 BAD (FFFFFF) + +Windows 10 14342 New Console +---------------------------- + +Size 1: 1,1 OK (HHHFFF) +Size 2: 1,2 OK (HHHFFF) +Size 3: 2,3 OK (HHHFFF) +Size 4: 2,4 OK (HHHFFF) +Size 5: 3,5 OK (HHHFFF) +Size 6: 3,6 OK (HHHFFF) +Size 7: 4,7 OK (HHHFFF) +Size 8: 4,8 OK (HHHFFF) +Size 9: 5,9 OK (HHHFFF) +Size 10: 5,10 OK (HHHFFF) +Size 11: 6,11 OK (HHHFFF) +Size 12: 6,12 OK (HHHFFF) +Size 13: 7,13 OK (HHHFFF) +Size 14: 7,14 OK (HHHFFF) +Size 15: 8,15 OK (HHHFFF) +Size 16: 8,16 OK (HHHFFF) +Size 17: 9,17 OK (HHHFFF) +Size 18: 9,18 OK (HHHFFF) +Size 19: 10,19 OK (HHHFFF) +Size 20: 10,20 OK (HHHFFF) +Size 21: 11,21 OK (HHHFFF) +Size 22: 11,22 OK (HHHFFF) +Size 23: 12,23 OK (HHHFFF) +Size 24: 12,24 OK (HHHFFF) +Size 25: 13,25 OK (HHHFFF) +Size 26: 13,26 OK (HHHFFF) +Size 27: 14,27 OK (HHHFFF) +Size 28: 14,28 OK (HHHFFF) +Size 29: 15,29 OK (HHHFFF) +Size 30: 15,30 OK (HHHFFF) +Size 31: 16,31 OK (HHHFFF) +Size 32: 16,32 OK (HHHFFF) +Size 33: 17,33 OK (HHHFFF) +Size 34: 17,34 OK (HHHFFF) +Size 35: 18,35 OK (HHHFFF) +Size 36: 18,36 OK (HHHFFF) +Size 37: 19,37 OK (HHHFFF) +Size 38: 19,38 OK (HHHFFF) +Size 39: 20,39 OK (HHHFFF) +Size 40: 20,40 OK (HHHFFF) +Size 41: 21,41 OK (HHHFFF) +Size 42: 21,42 OK (HHHFFF) +Size 43: 22,43 OK (HHHFFF) +Size 44: 22,44 OK (HHHFFF) +Size 45: 23,45 OK (HHHFFF) +Size 46: 23,46 OK (HHHFFF) +Size 47: 24,47 OK (HHHFFF) +Size 48: 24,48 OK (HHHFFF) +Size 49: 25,49 OK (HHHFFF) +Size 50: 25,50 OK (HHHFFF) +Size 51: 26,51 OK (HHHFFF) +Size 52: 26,52 OK (HHHFFF) +Size 53: 27,53 OK (HHHFFF) +Size 54: 27,54 OK (HHHFFF) +Size 55: 28,55 OK (HHHFFF) +Size 56: 28,56 OK (HHHFFF) +Size 57: 29,57 OK (HHHFFF) +Size 58: 29,58 OK (HHHFFF) +Size 59: 30,59 OK (HHHFFF) +Size 60: 30,60 OK (HHHFFF) +Size 61: 31,61 OK (HHHFFF) +Size 62: 31,62 OK (HHHFFF) +Size 63: 32,63 OK (HHHFFF) +Size 64: 32,64 OK (HHHFFF) +Size 65: 33,65 OK (HHHFFF) +Size 66: 33,66 OK (HHHFFF) +Size 67: 34,67 OK (HHHFFF) +Size 68: 34,68 OK (HHHFFF) +Size 69: 35,69 OK (HHHFFF) +Size 70: 35,70 OK (HHHFFF) +Size 71: 36,71 OK (HHHFFF) +Size 72: 36,72 OK (HHHFFF) +Size 73: 37,73 OK (HHHFFF) +Size 74: 37,74 OK (HHHFFF) +Size 75: 38,75 OK (HHHFFF) +Size 76: 38,76 OK (HHHFFF) +Size 77: 39,77 OK (HHHFFF) +Size 78: 39,78 OK (HHHFFF) +Size 79: 40,79 OK (HHHFFF) +Size 80: 40,80 OK (HHHFFF) +Size 81: 41,81 OK (HHHFFF) +Size 82: 41,82 OK (HHHFFF) +Size 83: 42,83 OK (HHHFFF) +Size 84: 42,84 OK (HHHFFF) +Size 85: 43,85 OK (HHHFFF) +Size 86: 43,86 OK (HHHFFF) +Size 87: 44,87 OK (HHHFFF) +Size 88: 44,88 OK (HHHFFF) +Size 89: 45,89 OK (HHHFFF) +Size 90: 45,90 OK (HHHFFF) +Size 91: 46,91 OK (HHHFFF) +Size 92: 46,92 OK (HHHFFF) +Size 93: 47,93 OK (HHHFFF) +Size 94: 47,94 OK (HHHFFF) +Size 95: 48,95 OK (HHHFFF) +Size 96: 48,96 OK (HHHFFF) +Size 97: 49,97 OK (HHHFFF) +Size 98: 49,98 OK (HHHFFF) +Size 99: 50,99 OK (HHHFFF) +Size 100: 50,100 OK (HHHFFF) diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP936.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP936.txt new file mode 100644 index 0000000000..43210dac51 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP936.txt @@ -0,0 +1,630 @@ +========================================================== +Code Page 936, Chinese Simplified (China/PRC), SimSun font +========================================================== + +Options: -face-simsun -family 0x36 +Chars: A2 A3 2014 3044 30FC 4000 + +Vista +----- + +Size 1: 1,2 GOOD (HHFFFF) +Size 2: 1,2 GOOD (HHFFFF) +Size 3: 2,3 BAD (FFHFHH) +Size 4: 2,5 GOOD (HHFFFF) +Size 5: 3,6 BAD (HHHFHH) +Size 6: 3,7 GOOD (HHFFFF) +Size 7: 4,8 BAD (HHHFHH) +Size 8: 4,9 GOOD (HHFFFF) +Size 9: 5,10 BAD (HHHFHH) +Size 10: 5,11 GOOD (HHFFFF) +Size 11: 6,13 BAD (HHHFHH) +Size 12: 6,14 GOOD (HHFFFF) +Size 13: 7,15 BAD (HHHFHH) +Size 14: 7,16 GOOD (HHFFFF) +Size 15: 8,17 BAD (HHHFHH) +Size 16: 8,18 GOOD (HHFFFF) +Size 17: 9,19 BAD (HHHFHH) +Size 18: 9,21 GOOD (HHFFFF) +Size 19: 10,22 BAD (HHHFHH) +Size 20: 10,23 GOOD (HHFFFF) +Size 21: 11,24 BAD (HHHFHH) +Size 22: 11,25 GOOD (HHFFFF) +Size 23: 12,26 BAD (HHHFHH) +Size 24: 12,27 GOOD (HHFFFF) +Size 25: 13,29 BAD (HHHFHH) +Size 26: 13,30 GOOD (HHFFFF) +Size 27: 14,31 BAD (HHHFHH) +Size 28: 14,32 GOOD (HHFFFF) +Size 29: 15,33 BAD (HHHFHH) +Size 30: 15,34 GOOD (HHFFFF) +Size 31: 16,35 BAD (HHHFHH) +Size 32: 16,38 GOOD (HHFFFF) +Size 33: 17,38 BAD (HHHFHH) +Size 34: 17,39 GOOD (HHFFFF) +Size 35: 18,40 BAD (HHHFHH) +Size 36: 18,41 GOOD (HHFFFF) +Size 37: 19,42 BAD (HHHFHH) +Size 38: 19,43 GOOD (HHFFFF) +Size 39: 20,44 BAD (HHHFHH) +Size 40: 20,46 GOOD (HHFFFF) +Size 41: 21,47 BAD (HHHFHH) +Size 42: 21,48 GOOD (HHFFFF) +Size 43: 22,49 BAD (HHHFHH) +Size 44: 22,50 GOOD (HHFFFF) +Size 45: 23,51 BAD (HHHFHH) +Size 46: 23,52 GOOD (HHFFFF) +Size 47: 24,54 BAD (HHHFHH) +Size 48: 24,55 GOOD (HHFFFF) +Size 49: 25,56 BAD (HHHFHH) +Size 50: 25,57 GOOD (HHFFFF) +Size 51: 26,58 BAD (HHHFHH) +Size 52: 26,59 GOOD (HHFFFF) +Size 53: 27,60 BAD (HHHFHH) +Size 54: 27,62 GOOD (HHFFFF) +Size 55: 28,63 BAD (HHHFHH) +Size 56: 28,64 GOOD (HHFFFF) +Size 57: 29,65 BAD (HHHFHH) +Size 58: 29,66 GOOD (HHFFFF) +Size 59: 30,67 BAD (HHHFHH) +Size 60: 30,68 GOOD (HHFFFF) +Size 61: 31,70 BAD (HHHFHH) +Size 62: 31,71 GOOD (HHFFFF) +Size 63: 32,72 BAD (HHHFHH) +Size 64: 32,73 GOOD (HHFFFF) +Size 65: 33,74 GOOD (HHFFFF) +Size 66: 33,75 GOOD (HHFFFF) +Size 67: 34,76 GOOD (HHFFFF) +Size 68: 34,78 GOOD (HHFFFF) +Size 69: 35,79 GOOD (HHFFFF) +Size 70: 35,80 GOOD (HHFFFF) +Size 71: 36,81 GOOD (HHFFFF) +Size 72: 36,82 GOOD (HHFFFF) +Size 73: 37,83 GOOD (HHFFFF) +Size 74: 37,84 GOOD (HHFFFF) +Size 75: 38,86 GOOD (HHFFFF) +Size 76: 38,87 GOOD (HHFFFF) +Size 77: 39,88 GOOD (HHFFFF) +Size 78: 39,89 GOOD (HHFFFF) +Size 79: 40,90 GOOD (HHFFFF) +Size 80: 40,91 GOOD (HHFFFF) +Size 81: 41,92 GOOD (HHFFFF) +Size 82: 41,94 GOOD (HHFFFF) +Size 83: 42,95 GOOD (HHFFFF) +Size 84: 42,96 GOOD (HHFFFF) +Size 85: 43,97 GOOD (HHFFFF) +Size 86: 43,98 GOOD (HHFFFF) +Size 87: 44,99 GOOD (HHFFFF) +Size 88: 44,100 GOOD (HHFFFF) +Size 89: 45,102 GOOD (HHFFFF) +Size 90: 45,103 GOOD (HHFFFF) +Size 91: 46,104 GOOD (HHFFFF) +Size 92: 46,105 GOOD (HHFFFF) +Size 93: 47,106 GOOD (HHFFFF) +Size 94: 47,107 GOOD (HHFFFF) +Size 95: 48,108 GOOD (HHFFFF) +Size 96: 48,111 GOOD (HHFFFF) +Size 97: 49,111 GOOD (HHFFFF) +Size 98: 49,112 GOOD (HHFFFF) +Size 99: 50,113 GOOD (HHFFFF) +Size 100: 50,114 GOOD (HHFFFF) + +Windows 7 +--------- + +Size 1: 1,2 GOOD (HHFFFF) +Size 2: 1,2 GOOD (HHFFFF) +Size 3: 2,3 BAD (FFHFHH) +Size 4: 2,5 GOOD (HHFFFF) +Size 5: 3,6 BAD (FFHFHH) +Size 6: 3,7 GOOD (HHFFFF) +Size 7: 4,8 BAD (FFHFHH) +Size 8: 4,9 GOOD (HHFFFF) +Size 9: 5,10 BAD (FFHFHH) +Size 10: 5,11 GOOD (HHFFFF) +Size 11: 6,13 BAD (FFHFHH) +Size 12: 6,14 GOOD (HHFFFF) +Size 13: 7,15 BAD (FFHFHH) +Size 14: 7,16 GOOD (HHFFFF) +Size 15: 8,17 BAD (FFHFHH) +Size 16: 8,18 GOOD (HHFFFF) +Size 17: 9,19 BAD (FFHFHH) +Size 18: 9,21 GOOD (HHFFFF) +Size 19: 10,22 BAD (FFHFHH) +Size 20: 10,23 GOOD (HHFFFF) +Size 21: 11,24 BAD (FFHFHH) +Size 22: 11,25 GOOD (HHFFFF) +Size 23: 12,26 BAD (FFHFHH) +Size 24: 12,27 GOOD (HHFFFF) +Size 25: 13,29 BAD (FFHFHH) +Size 26: 13,30 GOOD (HHFFFF) +Size 27: 14,31 BAD (FFHFHH) +Size 28: 14,32 GOOD (HHFFFF) +Size 29: 15,33 BAD (FFHFHH) +Size 30: 15,34 GOOD (HHFFFF) +Size 31: 16,35 BAD (FFHFHH) +Size 32: 16,38 GOOD (HHFFFF) +Size 33: 17,38 BAD (FFHFHH) +Size 34: 17,39 GOOD (HHFFFF) +Size 35: 18,40 BAD (FFHFHH) +Size 36: 18,41 GOOD (HHFFFF) +Size 37: 19,42 BAD (FFHFHH) +Size 38: 19,43 GOOD (HHFFFF) +Size 39: 20,44 BAD (FFHFHH) +Size 40: 20,46 GOOD (HHFFFF) +Size 41: 21,47 BAD (FFHFHH) +Size 42: 21,48 GOOD (HHFFFF) +Size 43: 22,49 BAD (FFHFHH) +Size 44: 22,50 GOOD (HHFFFF) +Size 45: 23,51 BAD (FFHFHH) +Size 46: 23,52 GOOD (HHFFFF) +Size 47: 24,54 BAD (FFHFHH) +Size 48: 24,55 GOOD (HHFFFF) +Size 49: 25,56 BAD (FFHFHH) +Size 50: 25,57 GOOD (HHFFFF) +Size 51: 26,58 BAD (FFHFHH) +Size 52: 26,59 GOOD (HHFFFF) +Size 53: 27,60 BAD (FFHFHH) +Size 54: 27,62 GOOD (HHFFFF) +Size 55: 28,63 BAD (FFHFHH) +Size 56: 28,64 GOOD (HHFFFF) +Size 57: 29,65 BAD (FFHFHH) +Size 58: 29,66 GOOD (HHFFFF) +Size 59: 30,67 BAD (FFHFHH) +Size 60: 30,68 GOOD (HHFFFF) +Size 61: 31,70 BAD (FFHFHH) +Size 62: 31,71 GOOD (HHFFFF) +Size 63: 32,72 BAD (FFHFHH) +Size 64: 32,73 GOOD (HHFFFF) +Size 65: 33,74 GOOD (HHFFFF) +Size 66: 33,75 GOOD (HHFFFF) +Size 67: 34,76 GOOD (HHFFFF) +Size 68: 34,78 GOOD (HHFFFF) +Size 69: 35,79 GOOD (HHFFFF) +Size 70: 35,80 GOOD (HHFFFF) +Size 71: 36,81 GOOD (HHFFFF) +Size 72: 36,82 GOOD (HHFFFF) +Size 73: 37,83 GOOD (HHFFFF) +Size 74: 37,84 GOOD (HHFFFF) +Size 75: 38,86 GOOD (HHFFFF) +Size 76: 38,87 GOOD (HHFFFF) +Size 77: 39,88 GOOD (HHFFFF) +Size 78: 39,89 GOOD (HHFFFF) +Size 79: 40,90 GOOD (HHFFFF) +Size 80: 40,91 GOOD (HHFFFF) +Size 81: 41,92 GOOD (HHFFFF) +Size 82: 41,94 GOOD (HHFFFF) +Size 83: 42,95 GOOD (HHFFFF) +Size 84: 42,96 GOOD (HHFFFF) +Size 85: 43,97 GOOD (HHFFFF) +Size 86: 43,98 GOOD (HHFFFF) +Size 87: 44,99 GOOD (HHFFFF) +Size 88: 44,100 GOOD (HHFFFF) +Size 89: 45,102 GOOD (HHFFFF) +Size 90: 45,103 GOOD (HHFFFF) +Size 91: 46,104 GOOD (HHFFFF) +Size 92: 46,105 GOOD (HHFFFF) +Size 93: 47,106 GOOD (HHFFFF) +Size 94: 47,107 GOOD (HHFFFF) +Size 95: 48,108 GOOD (HHFFFF) +Size 96: 48,111 GOOD (HHFFFF) +Size 97: 49,111 GOOD (HHFFFF) +Size 98: 49,112 GOOD (HHFFFF) +Size 99: 50,113 GOOD (HHFFFF) +Size 100: 50,114 GOOD (HHFFFF) + +Windows 8 +--------- + +Size 1: 1,2 GOOD (HHFFFF) +Size 2: 1,2 GOOD (HHFFFF) +Size 3: 2,3 BAD (FFHFHH) +Size 4: 2,5 GOOD (HHFFFF) +Size 5: 3,6 BAD (FFHFHH) +Size 6: 3,7 GOOD (HHFFFF) +Size 7: 4,8 BAD (FFHFHH) +Size 8: 4,9 GOOD (HHFFFF) +Size 9: 5,10 BAD (FFHFHH) +Size 10: 5,11 GOOD (HHFFFF) +Size 11: 6,13 BAD (FFHFHH) +Size 12: 6,14 GOOD (HHFFFF) +Size 13: 7,15 BAD (FFHFHH) +Size 14: 7,16 GOOD (HHFFFF) +Size 15: 8,17 BAD (FFHFHH) +Size 16: 8,18 GOOD (HHFFFF) +Size 17: 9,19 BAD (FFHFHH) +Size 18: 9,21 GOOD (HHFFFF) +Size 19: 10,22 BAD (FFHFHH) +Size 20: 10,23 GOOD (HHFFFF) +Size 21: 11,24 BAD (FFHFHH) +Size 22: 11,25 GOOD (HHFFFF) +Size 23: 12,26 BAD (FFHFHH) +Size 24: 12,27 GOOD (HHFFFF) +Size 25: 13,29 BAD (FFHFHH) +Size 26: 13,30 GOOD (HHFFFF) +Size 27: 14,31 BAD (FFHFHH) +Size 28: 14,32 GOOD (HHFFFF) +Size 29: 15,33 BAD (FFHFHH) +Size 30: 15,34 GOOD (HHFFFF) +Size 31: 16,35 BAD (FFHFHH) +Size 32: 16,38 GOOD (HHFFFF) +Size 33: 17,38 BAD (FFHFHH) +Size 34: 17,39 GOOD (HHFFFF) +Size 35: 18,40 BAD (FFHFHH) +Size 36: 18,41 GOOD (HHFFFF) +Size 37: 19,42 BAD (FFHFHH) +Size 38: 19,43 GOOD (HHFFFF) +Size 39: 20,44 BAD (FFHFHH) +Size 40: 20,46 GOOD (HHFFFF) +Size 41: 21,47 BAD (FFHFHH) +Size 42: 21,48 GOOD (HHFFFF) +Size 43: 22,49 BAD (FFHFHH) +Size 44: 22,50 GOOD (HHFFFF) +Size 45: 23,51 BAD (FFHFHH) +Size 46: 23,52 GOOD (HHFFFF) +Size 47: 24,54 BAD (FFHFHH) +Size 48: 24,55 GOOD (HHFFFF) +Size 49: 25,56 BAD (FFHFHH) +Size 50: 25,57 GOOD (HHFFFF) +Size 51: 26,58 BAD (FFHFHH) +Size 52: 26,59 GOOD (HHFFFF) +Size 53: 27,60 BAD (FFHFHH) +Size 54: 27,62 GOOD (HHFFFF) +Size 55: 28,63 BAD (FFHFHH) +Size 56: 28,64 GOOD (HHFFFF) +Size 57: 29,65 BAD (FFHFHH) +Size 58: 29,66 GOOD (HHFFFF) +Size 59: 30,67 BAD (FFHFHH) +Size 60: 30,68 GOOD (HHFFFF) +Size 61: 31,70 BAD (FFHFHH) +Size 62: 31,71 GOOD (HHFFFF) +Size 63: 32,72 BAD (FFHFHH) +Size 64: 32,73 GOOD (HHFFFF) +Size 65: 33,74 GOOD (HHFFFF) +Size 66: 33,75 GOOD (HHFFFF) +Size 67: 34,76 GOOD (HHFFFF) +Size 68: 34,78 GOOD (HHFFFF) +Size 69: 35,79 GOOD (HHFFFF) +Size 70: 35,80 GOOD (HHFFFF) +Size 71: 36,81 GOOD (HHFFFF) +Size 72: 36,82 GOOD (HHFFFF) +Size 73: 37,83 GOOD (HHFFFF) +Size 74: 37,84 GOOD (HHFFFF) +Size 75: 38,86 GOOD (HHFFFF) +Size 76: 38,87 GOOD (HHFFFF) +Size 77: 39,88 GOOD (HHFFFF) +Size 78: 39,89 GOOD (HHFFFF) +Size 79: 40,90 GOOD (HHFFFF) +Size 80: 40,91 GOOD (HHFFFF) +Size 81: 41,92 GOOD (HHFFFF) +Size 82: 41,94 GOOD (HHFFFF) +Size 83: 42,95 GOOD (HHFFFF) +Size 84: 42,96 GOOD (HHFFFF) +Size 85: 43,97 GOOD (HHFFFF) +Size 86: 43,98 GOOD (HHFFFF) +Size 87: 44,99 GOOD (HHFFFF) +Size 88: 44,100 GOOD (HHFFFF) +Size 89: 45,102 GOOD (HHFFFF) +Size 90: 45,103 GOOD (HHFFFF) +Size 91: 46,104 GOOD (HHFFFF) +Size 92: 46,105 GOOD (HHFFFF) +Size 93: 47,106 GOOD (HHFFFF) +Size 94: 47,107 GOOD (HHFFFF) +Size 95: 48,108 GOOD (HHFFFF) +Size 96: 48,111 GOOD (HHFFFF) +Size 97: 49,111 GOOD (HHFFFF) +Size 98: 49,112 GOOD (HHFFFF) +Size 99: 50,113 GOOD (HHFFFF) +Size 100: 50,114 GOOD (HHFFFF) + +Windows 8.1 +----------- + +Size 1: 1,2 GOOD (HHFFFF) +Size 2: 1,2 GOOD (HHFFFF) +Size 3: 2,3 BAD (FFHFHH) +Size 4: 2,5 GOOD (HHFFFF) +Size 5: 3,6 BAD (FFHFHH) +Size 6: 3,7 GOOD (HHFFFF) +Size 7: 4,8 BAD (FFHFHH) +Size 8: 4,9 GOOD (HHFFFF) +Size 9: 5,10 BAD (FFHFHH) +Size 10: 5,11 GOOD (HHFFFF) +Size 11: 6,13 BAD (FFHFHH) +Size 12: 6,14 GOOD (HHFFFF) +Size 13: 7,15 BAD (FFHFHH) +Size 14: 7,16 GOOD (HHFFFF) +Size 15: 8,17 BAD (FFHFHH) +Size 16: 8,18 GOOD (HHFFFF) +Size 17: 9,19 BAD (FFHFHH) +Size 18: 9,21 GOOD (HHFFFF) +Size 19: 10,22 BAD (FFHFHH) +Size 20: 10,23 GOOD (HHFFFF) +Size 21: 11,24 BAD (FFHFHH) +Size 22: 11,25 GOOD (HHFFFF) +Size 23: 12,26 BAD (FFHFHH) +Size 24: 12,27 GOOD (HHFFFF) +Size 25: 13,29 BAD (FFHFHH) +Size 26: 13,30 GOOD (HHFFFF) +Size 27: 14,31 BAD (FFHFHH) +Size 28: 14,32 GOOD (HHFFFF) +Size 29: 15,33 BAD (FFHFHH) +Size 30: 15,34 GOOD (HHFFFF) +Size 31: 16,35 BAD (FFHFHH) +Size 32: 16,38 GOOD (HHFFFF) +Size 33: 17,38 BAD (FFHFHH) +Size 34: 17,39 GOOD (HHFFFF) +Size 35: 18,40 BAD (FFHFHH) +Size 36: 18,41 GOOD (HHFFFF) +Size 37: 19,42 BAD (FFHFHH) +Size 38: 19,43 GOOD (HHFFFF) +Size 39: 20,44 BAD (FFHFHH) +Size 40: 20,46 GOOD (HHFFFF) +Size 41: 21,47 BAD (FFHFHH) +Size 42: 21,48 GOOD (HHFFFF) +Size 43: 22,49 BAD (FFHFHH) +Size 44: 22,50 GOOD (HHFFFF) +Size 45: 23,51 BAD (FFHFHH) +Size 46: 23,52 GOOD (HHFFFF) +Size 47: 24,54 BAD (FFHFHH) +Size 48: 24,55 GOOD (HHFFFF) +Size 49: 25,56 BAD (FFHFHH) +Size 50: 25,57 GOOD (HHFFFF) +Size 51: 26,58 BAD (FFHFHH) +Size 52: 26,59 GOOD (HHFFFF) +Size 53: 27,60 BAD (FFHFHH) +Size 54: 27,62 GOOD (HHFFFF) +Size 55: 28,63 BAD (FFHFHH) +Size 56: 28,64 GOOD (HHFFFF) +Size 57: 29,65 BAD (FFHFHH) +Size 58: 29,66 GOOD (HHFFFF) +Size 59: 30,67 BAD (FFHFHH) +Size 60: 30,68 GOOD (HHFFFF) +Size 61: 31,70 BAD (FFHFHH) +Size 62: 31,71 GOOD (HHFFFF) +Size 63: 32,72 BAD (FFHFHH) +Size 64: 32,73 GOOD (HHFFFF) +Size 65: 33,74 GOOD (HHFFFF) +Size 66: 33,75 GOOD (HHFFFF) +Size 67: 34,76 GOOD (HHFFFF) +Size 68: 34,78 GOOD (HHFFFF) +Size 69: 35,79 GOOD (HHFFFF) +Size 70: 35,80 GOOD (HHFFFF) +Size 71: 36,81 GOOD (HHFFFF) +Size 72: 36,82 GOOD (HHFFFF) +Size 73: 37,83 GOOD (HHFFFF) +Size 74: 37,84 GOOD (HHFFFF) +Size 75: 38,86 GOOD (HHFFFF) +Size 76: 38,87 GOOD (HHFFFF) +Size 77: 39,88 GOOD (HHFFFF) +Size 78: 39,89 GOOD (HHFFFF) +Size 79: 40,90 GOOD (HHFFFF) +Size 80: 40,91 GOOD (HHFFFF) +Size 81: 41,92 GOOD (HHFFFF) +Size 82: 41,94 GOOD (HHFFFF) +Size 83: 42,95 GOOD (HHFFFF) +Size 84: 42,96 GOOD (HHFFFF) +Size 85: 43,97 GOOD (HHFFFF) +Size 86: 43,98 GOOD (HHFFFF) +Size 87: 44,99 GOOD (HHFFFF) +Size 88: 44,100 GOOD (HHFFFF) +Size 89: 45,102 GOOD (HHFFFF) +Size 90: 45,103 GOOD (HHFFFF) +Size 91: 46,104 GOOD (HHFFFF) +Size 92: 46,105 GOOD (HHFFFF) +Size 93: 47,106 GOOD (HHFFFF) +Size 94: 47,107 GOOD (HHFFFF) +Size 95: 48,108 GOOD (HHFFFF) +Size 96: 48,111 GOOD (HHFFFF) +Size 97: 49,111 GOOD (HHFFFF) +Size 98: 49,112 GOOD (HHFFFF) +Size 99: 50,113 GOOD (HHFFFF) +Size 100: 50,114 GOOD (HHFFFF) + +Windows 10 14342 Old Console +---------------------------- + +Size 1: 1,2 GOOD (HHFFFF) +Size 2: 1,2 GOOD (HHFFFF) +Size 3: 2,3 BAD (FFHFHH) +Size 4: 2,5 GOOD (HHFFFF) +Size 5: 3,6 BAD (FFHFHH) +Size 6: 3,7 GOOD (HHFFFF) +Size 7: 4,8 BAD (FFHFHH) +Size 8: 4,9 GOOD (HHFFFF) +Size 9: 5,10 BAD (FFHFHH) +Size 10: 5,11 GOOD (HHFFFF) +Size 11: 6,13 BAD (FFHFHH) +Size 12: 6,14 GOOD (HHFFFF) +Size 13: 7,15 BAD (FFHFHH) +Size 14: 7,16 GOOD (HHFFFF) +Size 15: 8,17 BAD (FFHFHH) +Size 16: 8,18 GOOD (HHFFFF) +Size 17: 9,19 BAD (FFHFHH) +Size 18: 9,21 GOOD (HHFFFF) +Size 19: 10,22 BAD (FFHFHH) +Size 20: 10,23 GOOD (HHFFFF) +Size 21: 11,24 BAD (FFHFHH) +Size 22: 11,25 GOOD (HHFFFF) +Size 23: 12,26 BAD (FFHFHH) +Size 24: 12,27 GOOD (HHFFFF) +Size 25: 13,29 BAD (FFHFHH) +Size 26: 13,30 GOOD (HHFFFF) +Size 27: 14,31 BAD (FFHFHH) +Size 28: 14,32 GOOD (HHFFFF) +Size 29: 15,33 BAD (FFHFHH) +Size 30: 15,34 GOOD (HHFFFF) +Size 31: 16,35 BAD (FFHFHH) +Size 32: 16,38 GOOD (HHFFFF) +Size 33: 17,38 BAD (FFHFHH) +Size 34: 17,39 GOOD (HHFFFF) +Size 35: 18,40 BAD (FFHFHH) +Size 36: 18,41 GOOD (HHFFFF) +Size 37: 19,42 BAD (FFHFHH) +Size 38: 19,43 GOOD (HHFFFF) +Size 39: 20,44 BAD (FFHFHH) +Size 40: 20,46 GOOD (HHFFFF) +Size 41: 21,47 BAD (FFHFHH) +Size 42: 21,48 GOOD (HHFFFF) +Size 43: 22,49 BAD (FFHFHH) +Size 44: 22,50 GOOD (HHFFFF) +Size 45: 23,51 BAD (FFHFHH) +Size 46: 23,52 GOOD (HHFFFF) +Size 47: 24,54 BAD (FFHFHH) +Size 48: 24,55 GOOD (HHFFFF) +Size 49: 25,56 BAD (FFHFHH) +Size 50: 25,57 GOOD (HHFFFF) +Size 51: 26,58 BAD (FFHFHH) +Size 52: 26,59 GOOD (HHFFFF) +Size 53: 27,60 BAD (FFHFHH) +Size 54: 27,62 GOOD (HHFFFF) +Size 55: 28,63 BAD (FFHFHH) +Size 56: 28,64 GOOD (HHFFFF) +Size 57: 29,65 BAD (FFHFHH) +Size 58: 29,66 GOOD (HHFFFF) +Size 59: 30,67 BAD (FFHFHH) +Size 60: 30,68 GOOD (HHFFFF) +Size 61: 31,70 BAD (FFHFHH) +Size 62: 31,71 GOOD (HHFFFF) +Size 63: 32,72 BAD (FFHFHH) +Size 64: 32,73 GOOD (HHFFFF) +Size 65: 33,74 GOOD (HHFFFF) +Size 66: 33,75 GOOD (HHFFFF) +Size 67: 34,76 GOOD (HHFFFF) +Size 68: 34,78 GOOD (HHFFFF) +Size 69: 35,79 GOOD (HHFFFF) +Size 70: 35,80 GOOD (HHFFFF) +Size 71: 36,81 GOOD (HHFFFF) +Size 72: 36,82 GOOD (HHFFFF) +Size 73: 37,83 GOOD (HHFFFF) +Size 74: 37,84 GOOD (HHFFFF) +Size 75: 38,86 GOOD (HHFFFF) +Size 76: 38,87 GOOD (HHFFFF) +Size 77: 39,88 GOOD (HHFFFF) +Size 78: 39,89 GOOD (HHFFFF) +Size 79: 40,90 GOOD (HHFFFF) +Size 80: 40,91 GOOD (HHFFFF) +Size 81: 41,92 GOOD (HHFFFF) +Size 82: 41,94 GOOD (HHFFFF) +Size 83: 42,95 GOOD (HHFFFF) +Size 84: 42,96 GOOD (HHFFFF) +Size 85: 43,97 GOOD (HHFFFF) +Size 86: 43,98 GOOD (HHFFFF) +Size 87: 44,99 GOOD (HHFFFF) +Size 88: 44,100 GOOD (HHFFFF) +Size 89: 45,102 GOOD (HHFFFF) +Size 90: 45,103 GOOD (HHFFFF) +Size 91: 46,104 GOOD (HHFFFF) +Size 92: 46,105 GOOD (HHFFFF) +Size 93: 47,106 GOOD (HHFFFF) +Size 94: 47,107 GOOD (HHFFFF) +Size 95: 48,108 GOOD (HHFFFF) +Size 96: 48,111 GOOD (HHFFFF) +Size 97: 49,111 GOOD (HHFFFF) +Size 98: 49,112 GOOD (HHFFFF) +Size 99: 50,113 GOOD (HHFFFF) +Size 100: 50,114 GOOD (HHFFFF) + +Windows 10 14342 New Console +---------------------------- + +Size 1: 1,1 GOOD (HHFFFF) +Size 2: 1,2 GOOD (HHFFFF) +Size 3: 2,3 GOOD (HHFFFF) +Size 4: 2,4 GOOD (HHFFFF) +Size 5: 3,5 GOOD (HHFFFF) +Size 6: 3,6 GOOD (HHFFFF) +Size 7: 4,7 GOOD (HHFFFF) +Size 8: 4,8 GOOD (HHFFFF) +Size 9: 5,9 GOOD (HHFFFF) +Size 10: 5,10 GOOD (HHFFFF) +Size 11: 6,11 GOOD (HHFFFF) +Size 12: 6,12 GOOD (HHFFFF) +Size 13: 7,13 GOOD (HHFFFF) +Size 14: 7,14 GOOD (HHFFFF) +Size 15: 8,15 GOOD (HHFFFF) +Size 16: 8,16 GOOD (HHFFFF) +Size 17: 9,17 GOOD (HHFFFF) +Size 18: 9,18 GOOD (HHFFFF) +Size 19: 10,19 GOOD (HHFFFF) +Size 20: 10,20 GOOD (HHFFFF) +Size 21: 11,21 GOOD (HHFFFF) +Size 22: 11,22 GOOD (HHFFFF) +Size 23: 12,23 GOOD (HHFFFF) +Size 24: 12,24 GOOD (HHFFFF) +Size 25: 13,25 GOOD (HHFFFF) +Size 26: 13,26 GOOD (HHFFFF) +Size 27: 14,27 GOOD (HHFFFF) +Size 28: 14,28 GOOD (HHFFFF) +Size 29: 15,29 GOOD (HHFFFF) +Size 30: 15,30 GOOD (HHFFFF) +Size 31: 16,31 GOOD (HHFFFF) +Size 32: 16,32 GOOD (HHFFFF) +Size 33: 17,33 GOOD (HHFFFF) +Size 34: 17,34 GOOD (HHFFFF) +Size 35: 18,35 GOOD (HHFFFF) +Size 36: 18,36 GOOD (HHFFFF) +Size 37: 19,37 GOOD (HHFFFF) +Size 38: 19,38 GOOD (HHFFFF) +Size 39: 20,39 GOOD (HHFFFF) +Size 40: 20,40 GOOD (HHFFFF) +Size 41: 21,41 GOOD (HHFFFF) +Size 42: 21,42 GOOD (HHFFFF) +Size 43: 22,43 GOOD (HHFFFF) +Size 44: 22,44 GOOD (HHFFFF) +Size 45: 23,45 GOOD (HHFFFF) +Size 46: 23,46 GOOD (HHFFFF) +Size 47: 24,47 GOOD (HHFFFF) +Size 48: 24,48 GOOD (HHFFFF) +Size 49: 25,49 GOOD (HHFFFF) +Size 50: 25,50 GOOD (HHFFFF) +Size 51: 26,51 GOOD (HHFFFF) +Size 52: 26,52 GOOD (HHFFFF) +Size 53: 27,53 GOOD (HHFFFF) +Size 54: 27,54 GOOD (HHFFFF) +Size 55: 28,55 GOOD (HHFFFF) +Size 56: 28,56 GOOD (HHFFFF) +Size 57: 29,57 GOOD (HHFFFF) +Size 58: 29,58 GOOD (HHFFFF) +Size 59: 30,59 GOOD (HHFFFF) +Size 60: 30,60 GOOD (HHFFFF) +Size 61: 31,61 GOOD (HHFFFF) +Size 62: 31,62 GOOD (HHFFFF) +Size 63: 32,63 GOOD (HHFFFF) +Size 64: 32,64 GOOD (HHFFFF) +Size 65: 33,65 GOOD (HHFFFF) +Size 66: 33,66 GOOD (HHFFFF) +Size 67: 34,67 GOOD (HHFFFF) +Size 68: 34,68 GOOD (HHFFFF) +Size 69: 35,69 GOOD (HHFFFF) +Size 70: 35,70 GOOD (HHFFFF) +Size 71: 36,71 GOOD (HHFFFF) +Size 72: 36,72 GOOD (HHFFFF) +Size 73: 37,73 GOOD (HHFFFF) +Size 74: 37,74 GOOD (HHFFFF) +Size 75: 38,75 GOOD (HHFFFF) +Size 76: 38,76 GOOD (HHFFFF) +Size 77: 39,77 GOOD (HHFFFF) +Size 78: 39,78 GOOD (HHFFFF) +Size 79: 40,79 GOOD (HHFFFF) +Size 80: 40,80 GOOD (HHFFFF) +Size 81: 41,81 GOOD (HHFFFF) +Size 82: 41,82 GOOD (HHFFFF) +Size 83: 42,83 GOOD (HHFFFF) +Size 84: 42,84 GOOD (HHFFFF) +Size 85: 43,85 GOOD (HHFFFF) +Size 86: 43,86 GOOD (HHFFFF) +Size 87: 44,87 GOOD (HHFFFF) +Size 88: 44,88 GOOD (HHFFFF) +Size 89: 45,89 GOOD (HHFFFF) +Size 90: 45,90 GOOD (HHFFFF) +Size 91: 46,91 GOOD (HHFFFF) +Size 92: 46,92 GOOD (HHFFFF) +Size 93: 47,93 GOOD (HHFFFF) +Size 94: 47,94 GOOD (HHFFFF) +Size 95: 48,95 GOOD (HHFFFF) +Size 96: 48,96 GOOD (HHFFFF) +Size 97: 49,97 GOOD (HHFFFF) +Size 98: 49,98 GOOD (HHFFFF) +Size 99: 50,99 GOOD (HHFFFF) +Size 100: 50,100 GOOD (HHFFFF) diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP949.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP949.txt new file mode 100644 index 0000000000..2f0ea1e7c2 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP949.txt @@ -0,0 +1,630 @@ +===================================== +Code Page 949, Korean, GulimChe font +===================================== + +Options: -face-gulimche -family 0x36 +Chars: A2 A3 2014 3044 30FC 4000 + +Vista +----- + +Size 1: 1,2 OK (HHHFFF) +Size 2: 1,2 OK (HHHFFF) +Size 3: 2,3 BAD (FFFFHH) +Size 4: 2,5 OK (HHHFFF) +Size 5: 3,6 BAD (HHHFHH) +Size 6: 3,7 OK (HHHFFF) +Size 7: 4,8 BAD (HHHFHH) +Size 8: 4,9 OK (HHHFFF) +Size 9: 5,10 BAD (HHHFHH) +Size 10: 5,11 OK (HHHFFF) +Size 11: 6,13 BAD (HHHFHH) +Size 12: 6,14 OK (HHHFFF) +Size 13: 7,15 BAD (HHHFHH) +Size 14: 7,16 OK (HHHFFF) +Size 15: 8,17 BAD (HHHFHH) +Size 16: 8,18 OK (HHHFFF) +Size 17: 9,20 BAD (HHHFHH) +Size 18: 9,21 OK (HHHFFF) +Size 19: 10,22 BAD (HHHFHH) +Size 20: 10,23 OK (HHHFFF) +Size 21: 11,24 BAD (HHHFHH) +Size 22: 11,25 OK (HHHFFF) +Size 23: 12,26 BAD (HHHFHH) +Size 24: 12,28 OK (HHHFFF) +Size 25: 13,29 BAD (HHHFHH) +Size 26: 13,30 OK (HHHFFF) +Size 27: 14,31 BAD (HHHFHH) +Size 28: 14,32 OK (HHHFFF) +Size 29: 15,33 BAD (HHHFHH) +Size 30: 15,34 OK (HHHFFF) +Size 31: 16,36 BAD (HHHFHH) +Size 32: 16,37 OK (HHHFFF) +Size 33: 17,38 BAD (HHHFHH) +Size 34: 17,39 OK (HHHFFF) +Size 35: 18,40 BAD (HHHFHH) +Size 36: 18,41 OK (HHHFFF) +Size 37: 19,42 BAD (HHHFHH) +Size 38: 19,44 OK (HHHFFF) +Size 39: 20,45 BAD (HHHFHH) +Size 40: 20,46 OK (HHHFFF) +Size 41: 21,47 BAD (HHHFHH) +Size 42: 21,48 OK (HHHFFF) +Size 43: 22,49 BAD (HHHFHH) +Size 44: 22,51 OK (HHHFFF) +Size 45: 23,52 BAD (HHHFHH) +Size 46: 23,53 OK (HHHFFF) +Size 47: 24,54 BAD (HHHFHH) +Size 48: 24,55 OK (HHHFFF) +Size 49: 25,56 BAD (HHHFHH) +Size 50: 25,57 OK (HHHFFF) +Size 51: 26,59 BAD (HHHFHH) +Size 52: 26,60 OK (HHHFFF) +Size 53: 27,61 BAD (HHHFHH) +Size 54: 27,62 OK (HHHFFF) +Size 55: 28,63 BAD (HHHFHH) +Size 56: 28,64 OK (HHHFFF) +Size 57: 29,65 BAD (HHHFHH) +Size 58: 29,67 OK (HHHFFF) +Size 59: 30,68 BAD (HHHFHH) +Size 60: 30,69 OK (HHHFFF) +Size 61: 31,70 BAD (HHHFHH) +Size 62: 31,71 OK (HHHFFF) +Size 63: 32,72 BAD (HHHFHH) +Size 64: 32,74 OK (HHHFFF) +Size 65: 33,75 BAD (HHHFHH) +Size 66: 33,76 OK (HHHFFF) +Size 67: 34,77 BAD (HHHFHH) +Size 68: 34,78 OK (HHHFFF) +Size 69: 35,79 BAD (HHHFHH) +Size 70: 35,80 OK (HHHFFF) +Size 71: 36,82 BAD (HHHFHH) +Size 72: 36,83 OK (HHHFFF) +Size 73: 37,84 BAD (HHHFHH) +Size 74: 37,85 OK (HHHFFF) +Size 75: 38,86 BAD (HHHFHH) +Size 76: 38,87 OK (HHHFFF) +Size 77: 39,88 BAD (HHHFHH) +Size 78: 39,90 OK (HHHFFF) +Size 79: 40,91 BAD (HHHFHH) +Size 80: 40,92 OK (HHHFFF) +Size 81: 41,93 BAD (HHHFHH) +Size 82: 41,94 OK (HHHFFF) +Size 83: 42,95 BAD (HHHFHH) +Size 84: 42,96 OK (HHHFFF) +Size 85: 43,98 BAD (HHHFHH) +Size 86: 43,99 OK (HHHFFF) +Size 87: 44,100 BAD (HHHFHH) +Size 88: 44,101 OK (HHHFFF) +Size 89: 45,102 BAD (HHHFHH) +Size 90: 45,103 OK (HHHFFF) +Size 91: 46,105 BAD (HHHFHH) +Size 92: 46,106 OK (HHHFFF) +Size 93: 47,107 BAD (HHHFHH) +Size 94: 47,108 OK (HHHFFF) +Size 95: 48,109 BAD (HHHFHH) +Size 96: 48,110 OK (HHHFFF) +Size 97: 49,111 BAD (HHHFHH) +Size 98: 49,113 OK (HHHFFF) +Size 99: 50,114 BAD (HHHFHH) +Size 100: 50,115 OK (HHHFFF) + +Windows 7 +--------- + +Size 1: 1,2 OK (HHHFFF) +Size 2: 1,2 OK (HHHFFF) +Size 3: 2,3 BAD (FFFFHH) +Size 4: 2,5 OK (HHHFFF) +Size 5: 3,6 BAD (FFFFHH) +Size 6: 3,7 OK (HHHFFF) +Size 7: 4,8 BAD (FFFFHH) +Size 8: 4,9 OK (HHHFFF) +Size 9: 5,10 BAD (FFFFHH) +Size 10: 5,11 OK (HHHFFF) +Size 11: 6,13 BAD (FFFFHH) +Size 12: 6,14 OK (HHHFFF) +Size 13: 7,15 BAD (FFFFHH) +Size 14: 7,16 OK (HHHFFF) +Size 15: 8,17 BAD (FFFFHH) +Size 16: 8,18 OK (HHHFFF) +Size 17: 9,20 BAD (FFFFHH) +Size 18: 9,21 OK (HHHFFF) +Size 19: 10,22 BAD (FFFFHH) +Size 20: 10,23 OK (HHHFFF) +Size 21: 11,24 BAD (FFFFHH) +Size 22: 11,25 OK (HHHFFF) +Size 23: 12,26 BAD (FFFFHH) +Size 24: 12,28 OK (HHHFFF) +Size 25: 13,29 BAD (FFFFHH) +Size 26: 13,30 OK (HHHFFF) +Size 27: 14,31 BAD (FFFFHH) +Size 28: 14,32 OK (HHHFFF) +Size 29: 15,33 BAD (FFFFHH) +Size 30: 15,34 OK (HHHFFF) +Size 31: 16,36 BAD (FFFFHH) +Size 32: 16,37 OK (HHHFFF) +Size 33: 17,38 BAD (FFFFHH) +Size 34: 17,39 OK (HHHFFF) +Size 35: 18,40 BAD (FFFFHH) +Size 36: 18,41 OK (HHHFFF) +Size 37: 19,42 BAD (FFFFHH) +Size 38: 19,44 OK (HHHFFF) +Size 39: 20,45 BAD (FFFFHH) +Size 40: 20,46 OK (HHHFFF) +Size 41: 21,47 BAD (FFFFHH) +Size 42: 21,48 OK (HHHFFF) +Size 43: 22,49 BAD (FFFFHH) +Size 44: 22,51 OK (HHHFFF) +Size 45: 23,52 BAD (FFFFHH) +Size 46: 23,53 OK (HHHFFF) +Size 47: 24,54 BAD (FFFFHH) +Size 48: 24,55 OK (HHHFFF) +Size 49: 25,56 BAD (FFFFHH) +Size 50: 25,57 OK (HHHFFF) +Size 51: 26,59 BAD (FFFFHH) +Size 52: 26,60 OK (HHHFFF) +Size 53: 27,61 BAD (FFFFHH) +Size 54: 27,62 OK (HHHFFF) +Size 55: 28,63 BAD (FFFFHH) +Size 56: 28,64 OK (HHHFFF) +Size 57: 29,65 BAD (FFFFHH) +Size 58: 29,67 OK (HHHFFF) +Size 59: 30,68 BAD (FFFFHH) +Size 60: 30,69 OK (HHHFFF) +Size 61: 31,70 BAD (FFFFHH) +Size 62: 31,71 OK (HHHFFF) +Size 63: 32,72 BAD (FFFFHH) +Size 64: 32,74 OK (HHHFFF) +Size 65: 33,75 BAD (FFFFHH) +Size 66: 33,76 OK (HHHFFF) +Size 67: 34,77 BAD (FFFFHH) +Size 68: 34,78 OK (HHHFFF) +Size 69: 35,79 BAD (FFFFHH) +Size 70: 35,80 OK (HHHFFF) +Size 71: 36,82 BAD (FFFFHH) +Size 72: 36,83 OK (HHHFFF) +Size 73: 37,84 BAD (FFFFHH) +Size 74: 37,85 OK (HHHFFF) +Size 75: 38,86 BAD (FFFFHH) +Size 76: 38,87 OK (HHHFFF) +Size 77: 39,88 BAD (FFFFHH) +Size 78: 39,90 OK (HHHFFF) +Size 79: 40,91 BAD (FFFFHH) +Size 80: 40,92 OK (HHHFFF) +Size 81: 41,93 BAD (FFFFHH) +Size 82: 41,94 OK (HHHFFF) +Size 83: 42,95 BAD (FFFFHH) +Size 84: 42,96 OK (HHHFFF) +Size 85: 43,98 BAD (FFFFHH) +Size 86: 43,99 OK (HHHFFF) +Size 87: 44,100 BAD (FFFFHH) +Size 88: 44,101 OK (HHHFFF) +Size 89: 45,102 BAD (FFFFHH) +Size 90: 45,103 OK (HHHFFF) +Size 91: 46,105 BAD (FFFFHH) +Size 92: 46,106 OK (HHHFFF) +Size 93: 47,107 BAD (FFFFHH) +Size 94: 47,108 OK (HHHFFF) +Size 95: 48,109 BAD (FFFFHH) +Size 96: 48,110 OK (HHHFFF) +Size 97: 49,111 BAD (FFFFHH) +Size 98: 49,113 OK (HHHFFF) +Size 99: 50,114 BAD (FFFFHH) +Size 100: 50,115 OK (HHHFFF) + +Windows 8 +--------- + +Size 1: 1,2 OK (HHHFFF) +Size 2: 1,2 OK (HHHFFF) +Size 3: 2,3 BAD (FFFFHH) +Size 4: 2,5 OK (HHHFFF) +Size 5: 3,6 BAD (FFFFHH) +Size 6: 3,7 OK (HHHFFF) +Size 7: 4,8 BAD (FFFFHH) +Size 8: 4,9 OK (HHHFFF) +Size 9: 5,10 BAD (FFFFHH) +Size 10: 5,11 OK (HHHFFF) +Size 11: 6,13 BAD (FFFFHH) +Size 12: 6,14 OK (HHHFFF) +Size 13: 7,15 BAD (FFFFHH) +Size 14: 7,16 OK (HHHFFF) +Size 15: 8,17 BAD (FFFFHH) +Size 16: 8,18 OK (HHHFFF) +Size 17: 9,20 BAD (FFFFHH) +Size 18: 9,21 OK (HHHFFF) +Size 19: 10,22 BAD (FFFFHH) +Size 20: 10,23 OK (HHHFFF) +Size 21: 11,24 BAD (FFFFHH) +Size 22: 11,25 OK (HHHFFF) +Size 23: 12,26 BAD (FFFFHH) +Size 24: 12,28 OK (HHHFFF) +Size 25: 13,29 BAD (FFFFHH) +Size 26: 13,30 OK (HHHFFF) +Size 27: 14,31 BAD (FFFFHH) +Size 28: 14,32 OK (HHHFFF) +Size 29: 15,33 BAD (FFFFHH) +Size 30: 15,34 OK (HHHFFF) +Size 31: 16,36 BAD (FFFFHH) +Size 32: 16,37 OK (HHHFFF) +Size 33: 17,38 BAD (FFFFHH) +Size 34: 17,39 OK (HHHFFF) +Size 35: 18,40 BAD (FFFFHH) +Size 36: 18,41 OK (HHHFFF) +Size 37: 19,42 BAD (FFFFHH) +Size 38: 19,44 OK (HHHFFF) +Size 39: 20,45 BAD (FFFFHH) +Size 40: 20,46 OK (HHHFFF) +Size 41: 21,47 BAD (FFFFHH) +Size 42: 21,48 OK (HHHFFF) +Size 43: 22,49 BAD (FFFFHH) +Size 44: 22,51 OK (HHHFFF) +Size 45: 23,52 BAD (FFFFHH) +Size 46: 23,53 OK (HHHFFF) +Size 47: 24,54 BAD (FFFFHH) +Size 48: 24,55 OK (HHHFFF) +Size 49: 25,56 BAD (FFFFHH) +Size 50: 25,57 OK (HHHFFF) +Size 51: 26,59 BAD (FFFFHH) +Size 52: 26,60 OK (HHHFFF) +Size 53: 27,61 BAD (FFFFHH) +Size 54: 27,62 OK (HHHFFF) +Size 55: 28,63 BAD (FFFFHH) +Size 56: 28,64 OK (HHHFFF) +Size 57: 29,65 BAD (FFFFHH) +Size 58: 29,67 OK (HHHFFF) +Size 59: 30,68 BAD (FFFFHH) +Size 60: 30,69 OK (HHHFFF) +Size 61: 31,70 BAD (FFFFHH) +Size 62: 31,71 OK (HHHFFF) +Size 63: 32,72 BAD (FFFFHH) +Size 64: 32,74 OK (HHHFFF) +Size 65: 33,75 BAD (FFFFHH) +Size 66: 33,76 OK (HHHFFF) +Size 67: 34,77 BAD (FFFFHH) +Size 68: 34,78 OK (HHHFFF) +Size 69: 35,79 BAD (FFFFHH) +Size 70: 35,80 OK (HHHFFF) +Size 71: 36,82 BAD (FFFFHH) +Size 72: 36,83 OK (HHHFFF) +Size 73: 37,84 BAD (FFFFHH) +Size 74: 37,85 OK (HHHFFF) +Size 75: 38,86 BAD (FFFFHH) +Size 76: 38,87 OK (HHHFFF) +Size 77: 39,88 BAD (FFFFHH) +Size 78: 39,90 OK (HHHFFF) +Size 79: 40,91 BAD (FFFFHH) +Size 80: 40,92 OK (HHHFFF) +Size 81: 41,93 BAD (FFFFHH) +Size 82: 41,94 OK (HHHFFF) +Size 83: 42,95 BAD (FFFFHH) +Size 84: 42,96 OK (HHHFFF) +Size 85: 43,98 BAD (FFFFHH) +Size 86: 43,99 OK (HHHFFF) +Size 87: 44,100 BAD (FFFFHH) +Size 88: 44,101 OK (HHHFFF) +Size 89: 45,102 BAD (FFFFHH) +Size 90: 45,103 OK (HHHFFF) +Size 91: 46,105 BAD (FFFFHH) +Size 92: 46,106 OK (HHHFFF) +Size 93: 47,107 BAD (FFFFHH) +Size 94: 47,108 OK (HHHFFF) +Size 95: 48,109 BAD (FFFFHH) +Size 96: 48,110 OK (HHHFFF) +Size 97: 49,111 BAD (FFFFHH) +Size 98: 49,113 OK (HHHFFF) +Size 99: 50,114 BAD (FFFFHH) +Size 100: 50,115 OK (HHHFFF) + +Windows 8.1 +----------- + +Size 1: 1,2 OK (HHHFFF) +Size 2: 1,2 OK (HHHFFF) +Size 3: 2,3 BAD (FFFFHH) +Size 4: 2,5 OK (HHHFFF) +Size 5: 3,6 BAD (FFFFHH) +Size 6: 3,7 OK (HHHFFF) +Size 7: 4,8 BAD (FFFFHH) +Size 8: 4,9 OK (HHHFFF) +Size 9: 5,10 BAD (FFFFHH) +Size 10: 5,11 OK (HHHFFF) +Size 11: 6,13 BAD (FFFFHH) +Size 12: 6,14 OK (HHHFFF) +Size 13: 7,15 BAD (FFFFHH) +Size 14: 7,16 OK (HHHFFF) +Size 15: 8,17 BAD (FFFFHH) +Size 16: 8,18 OK (HHHFFF) +Size 17: 9,20 BAD (FFFFHH) +Size 18: 9,21 OK (HHHFFF) +Size 19: 10,22 BAD (FFFFHH) +Size 20: 10,23 OK (HHHFFF) +Size 21: 11,24 BAD (FFFFHH) +Size 22: 11,25 OK (HHHFFF) +Size 23: 12,26 BAD (FFFFHH) +Size 24: 12,28 OK (HHHFFF) +Size 25: 13,29 BAD (FFFFHH) +Size 26: 13,30 OK (HHHFFF) +Size 27: 14,31 BAD (FFFFHH) +Size 28: 14,32 OK (HHHFFF) +Size 29: 15,33 BAD (FFFFHH) +Size 30: 15,34 OK (HHHFFF) +Size 31: 16,36 BAD (FFFFHH) +Size 32: 16,37 OK (HHHFFF) +Size 33: 17,38 BAD (FFFFHH) +Size 34: 17,39 OK (HHHFFF) +Size 35: 18,40 BAD (FFFFHH) +Size 36: 18,41 OK (HHHFFF) +Size 37: 19,42 BAD (FFFFHH) +Size 38: 19,44 OK (HHHFFF) +Size 39: 20,45 BAD (FFFFHH) +Size 40: 20,46 OK (HHHFFF) +Size 41: 21,47 BAD (FFFFHH) +Size 42: 21,48 OK (HHHFFF) +Size 43: 22,49 BAD (FFFFHH) +Size 44: 22,51 OK (HHHFFF) +Size 45: 23,52 BAD (FFFFHH) +Size 46: 23,53 OK (HHHFFF) +Size 47: 24,54 BAD (FFFFHH) +Size 48: 24,55 OK (HHHFFF) +Size 49: 25,56 BAD (FFFFHH) +Size 50: 25,57 OK (HHHFFF) +Size 51: 26,59 BAD (FFFFHH) +Size 52: 26,60 OK (HHHFFF) +Size 53: 27,61 BAD (FFFFHH) +Size 54: 27,62 OK (HHHFFF) +Size 55: 28,63 BAD (FFFFHH) +Size 56: 28,64 OK (HHHFFF) +Size 57: 29,65 BAD (FFFFHH) +Size 58: 29,67 OK (HHHFFF) +Size 59: 30,68 BAD (FFFFHH) +Size 60: 30,69 OK (HHHFFF) +Size 61: 31,70 BAD (FFFFHH) +Size 62: 31,71 OK (HHHFFF) +Size 63: 32,72 BAD (FFFFHH) +Size 64: 32,74 OK (HHHFFF) +Size 65: 33,75 BAD (FFFFHH) +Size 66: 33,76 OK (HHHFFF) +Size 67: 34,77 BAD (FFFFHH) +Size 68: 34,78 OK (HHHFFF) +Size 69: 35,79 BAD (FFFFHH) +Size 70: 35,80 OK (HHHFFF) +Size 71: 36,82 BAD (FFFFHH) +Size 72: 36,83 OK (HHHFFF) +Size 73: 37,84 BAD (FFFFHH) +Size 74: 37,85 OK (HHHFFF) +Size 75: 38,86 BAD (FFFFHH) +Size 76: 38,87 OK (HHHFFF) +Size 77: 39,88 BAD (FFFFHH) +Size 78: 39,90 OK (HHHFFF) +Size 79: 40,91 BAD (FFFFHH) +Size 80: 40,92 OK (HHHFFF) +Size 81: 41,93 BAD (FFFFHH) +Size 82: 41,94 OK (HHHFFF) +Size 83: 42,95 BAD (FFFFHH) +Size 84: 42,96 OK (HHHFFF) +Size 85: 43,98 BAD (FFFFHH) +Size 86: 43,99 OK (HHHFFF) +Size 87: 44,100 BAD (FFFFHH) +Size 88: 44,101 OK (HHHFFF) +Size 89: 45,102 BAD (FFFFHH) +Size 90: 45,103 OK (HHHFFF) +Size 91: 46,105 BAD (FFFFHH) +Size 92: 46,106 OK (HHHFFF) +Size 93: 47,107 BAD (FFFFHH) +Size 94: 47,108 OK (HHHFFF) +Size 95: 48,109 BAD (FFFFHH) +Size 96: 48,110 OK (HHHFFF) +Size 97: 49,111 BAD (FFFFHH) +Size 98: 49,113 OK (HHHFFF) +Size 99: 50,114 BAD (FFFFHH) +Size 100: 50,115 OK (HHHFFF) + +Windows 10 14342 Old Console +---------------------------- + +Size 1: 1,2 OK (HHHFFF) +Size 2: 1,2 OK (HHHFFF) +Size 3: 2,3 BAD (FFFFHH) +Size 4: 2,5 OK (HHHFFF) +Size 5: 3,6 BAD (FFFFHH) +Size 6: 3,7 OK (HHHFFF) +Size 7: 4,8 BAD (FFFFHH) +Size 8: 4,9 OK (HHHFFF) +Size 9: 5,10 BAD (FFFFHH) +Size 10: 5,11 OK (HHHFFF) +Size 11: 6,13 BAD (FFFFHH) +Size 12: 6,14 OK (HHHFFF) +Size 13: 7,15 BAD (FFFFHH) +Size 14: 7,16 OK (HHHFFF) +Size 15: 8,17 BAD (FFFFHH) +Size 16: 8,18 OK (HHHFFF) +Size 17: 9,20 BAD (FFFFHH) +Size 18: 9,21 OK (HHHFFF) +Size 19: 10,22 BAD (FFFFHH) +Size 20: 10,23 OK (HHHFFF) +Size 21: 11,24 BAD (FFFFHH) +Size 22: 11,25 OK (HHHFFF) +Size 23: 12,26 BAD (FFFFHH) +Size 24: 12,28 OK (HHHFFF) +Size 25: 13,29 BAD (FFFFHH) +Size 26: 13,30 OK (HHHFFF) +Size 27: 14,31 BAD (FFFFHH) +Size 28: 14,32 OK (HHHFFF) +Size 29: 15,33 BAD (FFFFHH) +Size 30: 15,34 OK (HHHFFF) +Size 31: 16,36 BAD (FFFFHH) +Size 32: 16,37 OK (HHHFFF) +Size 33: 17,38 BAD (FFFFHH) +Size 34: 17,39 OK (HHHFFF) +Size 35: 18,40 BAD (FFFFHH) +Size 36: 18,41 OK (HHHFFF) +Size 37: 19,42 BAD (FFFFHH) +Size 38: 19,44 OK (HHHFFF) +Size 39: 20,45 BAD (FFFFHH) +Size 40: 20,46 OK (HHHFFF) +Size 41: 21,47 BAD (FFFFHH) +Size 42: 21,48 OK (HHHFFF) +Size 43: 22,49 BAD (FFFFHH) +Size 44: 22,51 OK (HHHFFF) +Size 45: 23,52 BAD (FFFFHH) +Size 46: 23,53 OK (HHHFFF) +Size 47: 24,54 BAD (FFFFHH) +Size 48: 24,55 OK (HHHFFF) +Size 49: 25,56 BAD (FFFFHH) +Size 50: 25,57 OK (HHHFFF) +Size 51: 26,59 BAD (FFFFHH) +Size 52: 26,60 OK (HHHFFF) +Size 53: 27,61 BAD (FFFFHH) +Size 54: 27,62 OK (HHHFFF) +Size 55: 28,63 BAD (FFFFHH) +Size 56: 28,64 OK (HHHFFF) +Size 57: 29,65 BAD (FFFFHH) +Size 58: 29,67 OK (HHHFFF) +Size 59: 30,68 BAD (FFFFHH) +Size 60: 30,69 OK (HHHFFF) +Size 61: 31,70 BAD (FFFFHH) +Size 62: 31,71 OK (HHHFFF) +Size 63: 32,72 BAD (FFFFHH) +Size 64: 32,74 OK (HHHFFF) +Size 65: 33,75 BAD (FFFFHH) +Size 66: 33,76 OK (HHHFFF) +Size 67: 34,77 BAD (FFFFHH) +Size 68: 34,78 OK (HHHFFF) +Size 69: 35,79 BAD (FFFFHH) +Size 70: 35,80 OK (HHHFFF) +Size 71: 36,82 BAD (FFFFHH) +Size 72: 36,83 OK (HHHFFF) +Size 73: 37,84 BAD (FFFFHH) +Size 74: 37,85 OK (HHHFFF) +Size 75: 38,86 BAD (FFFFHH) +Size 76: 38,87 OK (HHHFFF) +Size 77: 39,88 BAD (FFFFHH) +Size 78: 39,90 OK (HHHFFF) +Size 79: 40,91 BAD (FFFFHH) +Size 80: 40,92 OK (HHHFFF) +Size 81: 41,93 BAD (FFFFHH) +Size 82: 41,94 OK (HHHFFF) +Size 83: 42,95 BAD (FFFFHH) +Size 84: 42,96 OK (HHHFFF) +Size 85: 43,98 BAD (FFFFHH) +Size 86: 43,99 OK (HHHFFF) +Size 87: 44,100 BAD (FFFFHH) +Size 88: 44,101 OK (HHHFFF) +Size 89: 45,102 BAD (FFFFHH) +Size 90: 45,103 OK (HHHFFF) +Size 91: 46,105 BAD (FFFFHH) +Size 92: 46,106 OK (HHHFFF) +Size 93: 47,107 BAD (FFFFHH) +Size 94: 47,108 OK (HHHFFF) +Size 95: 48,109 BAD (FFFFHH) +Size 96: 48,110 OK (HHHFFF) +Size 97: 49,111 BAD (FFFFHH) +Size 98: 49,113 OK (HHHFFF) +Size 99: 50,114 BAD (FFFFHH) +Size 100: 50,115 OK (HHHFFF) + +Windows 10 14342 New Console +---------------------------- + +Size 1: 1,1 OK (HHHFFF) +Size 2: 1,2 OK (HHHFFF) +Size 3: 2,3 OK (HHHFFF) +Size 4: 2,4 OK (HHHFFF) +Size 5: 3,5 OK (HHHFFF) +Size 6: 3,6 OK (HHHFFF) +Size 7: 4,7 OK (HHHFFF) +Size 8: 4,8 OK (HHHFFF) +Size 9: 5,9 OK (HHHFFF) +Size 10: 5,10 OK (HHHFFF) +Size 11: 6,11 OK (HHHFFF) +Size 12: 6,12 OK (HHHFFF) +Size 13: 7,13 OK (HHHFFF) +Size 14: 7,14 OK (HHHFFF) +Size 15: 8,15 OK (HHHFFF) +Size 16: 8,16 OK (HHHFFF) +Size 17: 9,17 OK (HHHFFF) +Size 18: 9,18 OK (HHHFFF) +Size 19: 10,19 OK (HHHFFF) +Size 20: 10,20 OK (HHHFFF) +Size 21: 11,21 OK (HHHFFF) +Size 22: 11,22 OK (HHHFFF) +Size 23: 12,23 OK (HHHFFF) +Size 24: 12,24 OK (HHHFFF) +Size 25: 13,25 OK (HHHFFF) +Size 26: 13,26 OK (HHHFFF) +Size 27: 14,27 OK (HHHFFF) +Size 28: 14,28 OK (HHHFFF) +Size 29: 15,29 OK (HHHFFF) +Size 30: 15,30 OK (HHHFFF) +Size 31: 16,31 OK (HHHFFF) +Size 32: 16,32 OK (HHHFFF) +Size 33: 17,33 OK (HHHFFF) +Size 34: 17,34 OK (HHHFFF) +Size 35: 18,35 OK (HHHFFF) +Size 36: 18,36 OK (HHHFFF) +Size 37: 19,37 OK (HHHFFF) +Size 38: 19,38 OK (HHHFFF) +Size 39: 20,39 OK (HHHFFF) +Size 40: 20,40 OK (HHHFFF) +Size 41: 21,41 OK (HHHFFF) +Size 42: 21,42 OK (HHHFFF) +Size 43: 22,43 OK (HHHFFF) +Size 44: 22,44 OK (HHHFFF) +Size 45: 23,45 OK (HHHFFF) +Size 46: 23,46 OK (HHHFFF) +Size 47: 24,47 OK (HHHFFF) +Size 48: 24,48 OK (HHHFFF) +Size 49: 25,49 OK (HHHFFF) +Size 50: 25,50 OK (HHHFFF) +Size 51: 26,51 OK (HHHFFF) +Size 52: 26,52 OK (HHHFFF) +Size 53: 27,53 OK (HHHFFF) +Size 54: 27,54 OK (HHHFFF) +Size 55: 28,55 OK (HHHFFF) +Size 56: 28,56 OK (HHHFFF) +Size 57: 29,57 OK (HHHFFF) +Size 58: 29,58 OK (HHHFFF) +Size 59: 30,59 OK (HHHFFF) +Size 60: 30,60 OK (HHHFFF) +Size 61: 31,61 OK (HHHFFF) +Size 62: 31,62 OK (HHHFFF) +Size 63: 32,63 OK (HHHFFF) +Size 64: 32,64 OK (HHHFFF) +Size 65: 33,65 OK (HHHFFF) +Size 66: 33,66 OK (HHHFFF) +Size 67: 34,67 OK (HHHFFF) +Size 68: 34,68 OK (HHHFFF) +Size 69: 35,69 OK (HHHFFF) +Size 70: 35,70 OK (HHHFFF) +Size 71: 36,71 OK (HHHFFF) +Size 72: 36,72 OK (HHHFFF) +Size 73: 37,73 OK (HHHFFF) +Size 74: 37,74 OK (HHHFFF) +Size 75: 38,75 OK (HHHFFF) +Size 76: 38,76 OK (HHHFFF) +Size 77: 39,77 OK (HHHFFF) +Size 78: 39,78 OK (HHHFFF) +Size 79: 40,79 OK (HHHFFF) +Size 80: 40,80 OK (HHHFFF) +Size 81: 41,81 OK (HHHFFF) +Size 82: 41,82 OK (HHHFFF) +Size 83: 42,83 OK (HHHFFF) +Size 84: 42,84 OK (HHHFFF) +Size 85: 43,85 OK (HHHFFF) +Size 86: 43,86 OK (HHHFFF) +Size 87: 44,87 OK (HHHFFF) +Size 88: 44,88 OK (HHHFFF) +Size 89: 45,89 OK (HHHFFF) +Size 90: 45,90 OK (HHHFFF) +Size 91: 46,91 OK (HHHFFF) +Size 92: 46,92 OK (HHHFFF) +Size 93: 47,93 OK (HHHFFF) +Size 94: 47,94 OK (HHHFFF) +Size 95: 48,95 OK (HHHFFF) +Size 96: 48,96 OK (HHHFFF) +Size 97: 49,97 OK (HHHFFF) +Size 98: 49,98 OK (HHHFFF) +Size 99: 50,99 OK (HHHFFF) +Size 100: 50,100 OK (HHHFFF) diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP950.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP950.txt new file mode 100644 index 0000000000..0dbade504d --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP950.txt @@ -0,0 +1,630 @@ +=========================================================== +Code Page 950, Chinese Traditional (Taiwan), MingLight font +=========================================================== + +Options: -face-minglight -family 0x36 +Chars: A2 A3 2014 3044 30FC 4000 + +Vista +----- + +Size 1: 1,2 GOOD (HHFFFF) +Size 2: 1,2 GOOD (HHFFFF) +Size 3: 2,4 BAD (FFHFHH) +Size 4: 2,5 GOOD (HHFFFF) +Size 5: 3,6 BAD (HHHFHH) +Size 6: 3,7 GOOD (HHFFFF) +Size 7: 4,8 BAD (HHHFHH) +Size 8: 4,10 GOOD (HHFFFF) +Size 9: 5,11 BAD (HHHFHH) +Size 10: 5,12 GOOD (HHFFFF) +Size 11: 6,13 BAD (HHHFHH) +Size 12: 6,14 GOOD (HHFFFF) +Size 13: 7,16 BAD (HHHFHH) +Size 14: 7,17 GOOD (HHFFFF) +Size 15: 8,18 BAD (HHHFHH) +Size 16: 8,19 GOOD (HHFFFF) +Size 17: 9,20 BAD (HHHFHH) +Size 18: 9,22 GOOD (HHFFFF) +Size 19: 10,23 BAD (HHHFHH) +Size 20: 10,24 GOOD (HHFFFF) +Size 21: 11,25 BAD (HHHFHH) +Size 22: 11,26 GOOD (HHFFFF) +Size 23: 12,28 BAD (HHHFHH) +Size 24: 12,29 GOOD (HHFFFF) +Size 25: 13,30 BAD (HHHFHH) +Size 26: 13,31 GOOD (HHFFFF) +Size 27: 14,32 BAD (HHHFHH) +Size 28: 14,34 GOOD (HHFFFF) +Size 29: 15,35 BAD (HHHFHH) +Size 30: 15,36 GOOD (HHFFFF) +Size 31: 16,37 BAD (HHHFHH) +Size 32: 16,38 GOOD (HHFFFF) +Size 33: 17,40 BAD (HHHFHH) +Size 34: 17,41 GOOD (HHFFFF) +Size 35: 18,42 BAD (HHHFHH) +Size 36: 18,43 GOOD (HHFFFF) +Size 37: 19,44 BAD (HHHFHH) +Size 38: 19,46 GOOD (HHFFFF) +Size 39: 20,47 BAD (HHHFHH) +Size 40: 20,48 GOOD (HHFFFF) +Size 41: 21,49 BAD (HHHFHH) +Size 42: 21,50 GOOD (HHFFFF) +Size 43: 22,52 BAD (HHHFHH) +Size 44: 22,53 GOOD (HHFFFF) +Size 45: 23,54 BAD (HHHFHH) +Size 46: 23,55 GOOD (HHFFFF) +Size 47: 24,56 BAD (HHHFHH) +Size 48: 24,58 GOOD (HHFFFF) +Size 49: 25,59 BAD (HHHFHH) +Size 50: 25,60 GOOD (HHFFFF) +Size 51: 26,61 BAD (HHHFHH) +Size 52: 26,62 GOOD (HHFFFF) +Size 53: 27,64 BAD (HHHFHH) +Size 54: 27,65 GOOD (HHFFFF) +Size 55: 28,66 BAD (HHHFHH) +Size 56: 28,67 GOOD (HHFFFF) +Size 57: 29,68 BAD (HHHFHH) +Size 58: 29,70 GOOD (HHFFFF) +Size 59: 30,71 BAD (HHHFHH) +Size 60: 30,72 GOOD (HHFFFF) +Size 61: 31,73 BAD (HHHFHH) +Size 62: 31,74 GOOD (HHFFFF) +Size 63: 32,76 BAD (HHHFHH) +Size 64: 32,77 GOOD (HHFFFF) +Size 65: 33,78 BAD (HHHFHH) +Size 66: 33,79 GOOD (HHFFFF) +Size 67: 34,80 BAD (HHHFHH) +Size 68: 34,82 GOOD (HHFFFF) +Size 69: 35,83 BAD (HHHFHH) +Size 70: 35,84 GOOD (HHFFFF) +Size 71: 36,85 BAD (HHHFHH) +Size 72: 36,86 GOOD (HHFFFF) +Size 73: 37,88 BAD (HHHFHH) +Size 74: 37,89 GOOD (HHFFFF) +Size 75: 38,90 BAD (HHHFHH) +Size 76: 38,91 GOOD (HHFFFF) +Size 77: 39,92 BAD (HHHFHH) +Size 78: 39,94 GOOD (HHFFFF) +Size 79: 40,95 BAD (HHHFHH) +Size 80: 40,96 GOOD (HHFFFF) +Size 81: 41,97 BAD (HHHFHH) +Size 82: 41,98 GOOD (HHFFFF) +Size 83: 42,100 BAD (HHHFHH) +Size 84: 42,101 GOOD (HHFFFF) +Size 85: 43,102 BAD (HHHFHH) +Size 86: 43,103 GOOD (HHFFFF) +Size 87: 44,104 BAD (HHHFHH) +Size 88: 44,106 GOOD (HHFFFF) +Size 89: 45,107 BAD (HHHFHH) +Size 90: 45,108 GOOD (HHFFFF) +Size 91: 46,109 BAD (HHHFHH) +Size 92: 46,110 GOOD (HHFFFF) +Size 93: 47,112 BAD (HHHFHH) +Size 94: 47,113 GOOD (HHFFFF) +Size 95: 48,114 BAD (HHHFHH) +Size 96: 48,115 GOOD (HHFFFF) +Size 97: 49,116 BAD (HHHFHH) +Size 98: 49,118 GOOD (HHFFFF) +Size 99: 50,119 BAD (HHHFHH) +Size 100: 50,120 GOOD (HHFFFF) + +Windows 7 +--------- + +Size 1: 1,2 GOOD (HHFFFF) +Size 2: 1,2 GOOD (HHFFFF) +Size 3: 2,4 BAD (FFHFHH) +Size 4: 2,5 GOOD (HHFFFF) +Size 5: 3,6 BAD (FFHFHH) +Size 6: 3,7 GOOD (HHFFFF) +Size 7: 4,8 BAD (FFHFHH) +Size 8: 4,10 GOOD (HHFFFF) +Size 9: 5,11 BAD (FFHFHH) +Size 10: 5,12 GOOD (HHFFFF) +Size 11: 6,13 BAD (FFHFHH) +Size 12: 6,14 GOOD (HHFFFF) +Size 13: 7,16 BAD (FFHFHH) +Size 14: 7,17 GOOD (HHFFFF) +Size 15: 8,18 BAD (FFHFHH) +Size 16: 8,19 GOOD (HHFFFF) +Size 17: 9,20 BAD (FFHFHH) +Size 18: 9,22 GOOD (HHFFFF) +Size 19: 10,23 BAD (FFHFHH) +Size 20: 10,24 GOOD (HHFFFF) +Size 21: 11,25 BAD (FFHFHH) +Size 22: 11,26 GOOD (HHFFFF) +Size 23: 12,28 BAD (FFHFHH) +Size 24: 12,29 GOOD (HHFFFF) +Size 25: 13,30 BAD (FFHFHH) +Size 26: 13,31 GOOD (HHFFFF) +Size 27: 14,32 BAD (FFHFHH) +Size 28: 14,34 GOOD (HHFFFF) +Size 29: 15,35 BAD (FFHFHH) +Size 30: 15,36 GOOD (HHFFFF) +Size 31: 16,37 BAD (FFHFHH) +Size 32: 16,38 GOOD (HHFFFF) +Size 33: 17,40 BAD (FFHFHH) +Size 34: 17,41 GOOD (HHFFFF) +Size 35: 18,42 BAD (FFHFHH) +Size 36: 18,43 GOOD (HHFFFF) +Size 37: 19,44 BAD (FFHFHH) +Size 38: 19,46 GOOD (HHFFFF) +Size 39: 20,47 BAD (FFHFHH) +Size 40: 20,48 GOOD (HHFFFF) +Size 41: 21,49 BAD (FFHFHH) +Size 42: 21,50 GOOD (HHFFFF) +Size 43: 22,52 BAD (FFHFHH) +Size 44: 22,53 GOOD (HHFFFF) +Size 45: 23,54 BAD (FFHFHH) +Size 46: 23,55 GOOD (HHFFFF) +Size 47: 24,56 BAD (FFHFHH) +Size 48: 24,58 GOOD (HHFFFF) +Size 49: 25,59 BAD (FFHFHH) +Size 50: 25,60 GOOD (HHFFFF) +Size 51: 26,61 BAD (FFHFHH) +Size 52: 26,62 GOOD (HHFFFF) +Size 53: 27,64 BAD (FFHFHH) +Size 54: 27,65 GOOD (HHFFFF) +Size 55: 28,66 BAD (FFHFHH) +Size 56: 28,67 GOOD (HHFFFF) +Size 57: 29,68 BAD (FFHFHH) +Size 58: 29,70 GOOD (HHFFFF) +Size 59: 30,71 BAD (FFHFHH) +Size 60: 30,72 GOOD (HHFFFF) +Size 61: 31,73 BAD (FFHFHH) +Size 62: 31,74 GOOD (HHFFFF) +Size 63: 32,76 BAD (FFHFHH) +Size 64: 32,77 GOOD (HHFFFF) +Size 65: 33,78 BAD (FFHFHH) +Size 66: 33,79 GOOD (HHFFFF) +Size 67: 34,80 BAD (FFHFHH) +Size 68: 34,82 GOOD (HHFFFF) +Size 69: 35,83 BAD (FFHFHH) +Size 70: 35,84 GOOD (HHFFFF) +Size 71: 36,85 BAD (FFHFHH) +Size 72: 36,86 GOOD (HHFFFF) +Size 73: 37,88 BAD (FFHFHH) +Size 74: 37,89 GOOD (HHFFFF) +Size 75: 38,90 BAD (FFHFHH) +Size 76: 38,91 GOOD (HHFFFF) +Size 77: 39,92 BAD (FFHFHH) +Size 78: 39,94 GOOD (HHFFFF) +Size 79: 40,95 BAD (FFHFHH) +Size 80: 40,96 GOOD (HHFFFF) +Size 81: 41,97 BAD (FFHFHH) +Size 82: 41,98 GOOD (HHFFFF) +Size 83: 42,100 BAD (FFHFHH) +Size 84: 42,101 GOOD (HHFFFF) +Size 85: 43,102 BAD (FFHFHH) +Size 86: 43,103 GOOD (HHFFFF) +Size 87: 44,104 BAD (FFHFHH) +Size 88: 44,106 GOOD (HHFFFF) +Size 89: 45,107 BAD (FFHFHH) +Size 90: 45,108 GOOD (HHFFFF) +Size 91: 46,109 BAD (FFHFHH) +Size 92: 46,110 GOOD (HHFFFF) +Size 93: 47,112 BAD (FFHFHH) +Size 94: 47,113 GOOD (HHFFFF) +Size 95: 48,114 BAD (FFHFHH) +Size 96: 48,115 GOOD (HHFFFF) +Size 97: 49,116 BAD (FFHFHH) +Size 98: 49,118 GOOD (HHFFFF) +Size 99: 50,119 BAD (FFHFHH) +Size 100: 50,120 GOOD (HHFFFF) + +Windows 8 +--------- + +Size 1: 1,2 GOOD (HHFFFF) +Size 2: 1,2 GOOD (HHFFFF) +Size 3: 2,4 BAD (FFHFHH) +Size 4: 2,5 GOOD (HHFFFF) +Size 5: 3,6 BAD (FFHFHH) +Size 6: 3,7 GOOD (HHFFFF) +Size 7: 4,8 BAD (FFHFHH) +Size 8: 4,10 GOOD (HHFFFF) +Size 9: 5,11 BAD (FFHFHH) +Size 10: 5,12 GOOD (HHFFFF) +Size 11: 6,13 BAD (FFHFHH) +Size 12: 6,14 GOOD (HHFFFF) +Size 13: 7,16 BAD (FFHFHH) +Size 14: 7,17 GOOD (HHFFFF) +Size 15: 8,18 BAD (FFHFHH) +Size 16: 8,19 GOOD (HHFFFF) +Size 17: 9,20 BAD (FFHFHH) +Size 18: 9,22 GOOD (HHFFFF) +Size 19: 10,23 BAD (FFHFHH) +Size 20: 10,24 GOOD (HHFFFF) +Size 21: 11,25 BAD (FFHFHH) +Size 22: 11,26 GOOD (HHFFFF) +Size 23: 12,28 BAD (FFHFHH) +Size 24: 12,29 GOOD (HHFFFF) +Size 25: 13,30 BAD (FFHFHH) +Size 26: 13,31 GOOD (HHFFFF) +Size 27: 14,32 BAD (FFHFHH) +Size 28: 14,34 GOOD (HHFFFF) +Size 29: 15,35 BAD (FFHFHH) +Size 30: 15,36 GOOD (HHFFFF) +Size 31: 16,37 BAD (FFHFHH) +Size 32: 16,38 GOOD (HHFFFF) +Size 33: 17,40 BAD (FFHFHH) +Size 34: 17,41 GOOD (HHFFFF) +Size 35: 18,42 BAD (FFHFHH) +Size 36: 18,43 GOOD (HHFFFF) +Size 37: 19,44 BAD (FFHFHH) +Size 38: 19,46 GOOD (HHFFFF) +Size 39: 20,47 BAD (FFHFHH) +Size 40: 20,48 GOOD (HHFFFF) +Size 41: 21,49 BAD (FFHFHH) +Size 42: 21,50 GOOD (HHFFFF) +Size 43: 22,52 BAD (FFHFHH) +Size 44: 22,53 GOOD (HHFFFF) +Size 45: 23,54 BAD (FFHFHH) +Size 46: 23,55 GOOD (HHFFFF) +Size 47: 24,56 BAD (FFHFHH) +Size 48: 24,58 GOOD (HHFFFF) +Size 49: 25,59 BAD (FFHFHH) +Size 50: 25,60 GOOD (HHFFFF) +Size 51: 26,61 BAD (FFHFHH) +Size 52: 26,62 GOOD (HHFFFF) +Size 53: 27,64 BAD (FFHFHH) +Size 54: 27,65 GOOD (HHFFFF) +Size 55: 28,66 BAD (FFHFHH) +Size 56: 28,67 GOOD (HHFFFF) +Size 57: 29,68 BAD (FFHFHH) +Size 58: 29,70 GOOD (HHFFFF) +Size 59: 30,71 BAD (FFHFHH) +Size 60: 30,72 GOOD (HHFFFF) +Size 61: 31,73 BAD (FFHFHH) +Size 62: 31,74 GOOD (HHFFFF) +Size 63: 32,76 BAD (FFHFHH) +Size 64: 32,77 GOOD (HHFFFF) +Size 65: 33,78 BAD (FFHFHH) +Size 66: 33,79 GOOD (HHFFFF) +Size 67: 34,80 BAD (FFHFHH) +Size 68: 34,82 GOOD (HHFFFF) +Size 69: 35,83 BAD (FFHFHH) +Size 70: 35,84 GOOD (HHFFFF) +Size 71: 36,85 BAD (FFHFHH) +Size 72: 36,86 GOOD (HHFFFF) +Size 73: 37,88 BAD (FFHFHH) +Size 74: 37,89 GOOD (HHFFFF) +Size 75: 38,90 BAD (FFHFHH) +Size 76: 38,91 GOOD (HHFFFF) +Size 77: 39,92 BAD (FFHFHH) +Size 78: 39,94 GOOD (HHFFFF) +Size 79: 40,95 BAD (FFHFHH) +Size 80: 40,96 GOOD (HHFFFF) +Size 81: 41,97 BAD (FFHFHH) +Size 82: 41,98 GOOD (HHFFFF) +Size 83: 42,100 BAD (FFHFHH) +Size 84: 42,101 GOOD (HHFFFF) +Size 85: 43,102 BAD (FFHFHH) +Size 86: 43,103 GOOD (HHFFFF) +Size 87: 44,104 BAD (FFHFHH) +Size 88: 44,106 GOOD (HHFFFF) +Size 89: 45,107 BAD (FFHFHH) +Size 90: 45,108 GOOD (HHFFFF) +Size 91: 46,109 BAD (FFHFHH) +Size 92: 46,110 GOOD (HHFFFF) +Size 93: 47,112 BAD (FFHFHH) +Size 94: 47,113 GOOD (HHFFFF) +Size 95: 48,114 BAD (FFHFHH) +Size 96: 48,115 GOOD (HHFFFF) +Size 97: 49,116 BAD (FFHFHH) +Size 98: 49,118 GOOD (HHFFFF) +Size 99: 50,119 BAD (FFHFHH) +Size 100: 50,120 GOOD (HHFFFF) + +Windows 8.1 +----------- + +Size 1: 1,2 GOOD (HHFFFF) +Size 2: 1,2 GOOD (HHFFFF) +Size 3: 2,4 BAD (FFHFHH) +Size 4: 2,5 GOOD (HHFFFF) +Size 5: 3,6 BAD (FFHFHH) +Size 6: 3,7 GOOD (HHFFFF) +Size 7: 4,8 BAD (FFHFHH) +Size 8: 4,10 GOOD (HHFFFF) +Size 9: 5,11 BAD (FFHFHH) +Size 10: 5,12 GOOD (HHFFFF) +Size 11: 6,13 BAD (FFHFHH) +Size 12: 6,14 GOOD (HHFFFF) +Size 13: 7,16 BAD (FFHFHH) +Size 14: 7,17 GOOD (HHFFFF) +Size 15: 8,18 BAD (FFHFHH) +Size 16: 8,19 GOOD (HHFFFF) +Size 17: 9,20 BAD (FFHFHH) +Size 18: 9,22 GOOD (HHFFFF) +Size 19: 10,23 BAD (FFHFHH) +Size 20: 10,24 GOOD (HHFFFF) +Size 21: 11,25 BAD (FFHFHH) +Size 22: 11,26 GOOD (HHFFFF) +Size 23: 12,28 BAD (FFHFHH) +Size 24: 12,29 GOOD (HHFFFF) +Size 25: 13,30 BAD (FFHFHH) +Size 26: 13,31 GOOD (HHFFFF) +Size 27: 14,32 BAD (FFHFHH) +Size 28: 14,34 GOOD (HHFFFF) +Size 29: 15,35 BAD (FFHFHH) +Size 30: 15,36 GOOD (HHFFFF) +Size 31: 16,37 BAD (FFHFHH) +Size 32: 16,38 GOOD (HHFFFF) +Size 33: 17,40 BAD (FFHFHH) +Size 34: 17,41 GOOD (HHFFFF) +Size 35: 18,42 BAD (FFHFHH) +Size 36: 18,43 GOOD (HHFFFF) +Size 37: 19,44 BAD (FFHFHH) +Size 38: 19,46 GOOD (HHFFFF) +Size 39: 20,47 BAD (FFHFHH) +Size 40: 20,48 GOOD (HHFFFF) +Size 41: 21,49 BAD (FFHFHH) +Size 42: 21,50 GOOD (HHFFFF) +Size 43: 22,52 BAD (FFHFHH) +Size 44: 22,53 GOOD (HHFFFF) +Size 45: 23,54 BAD (FFHFHH) +Size 46: 23,55 GOOD (HHFFFF) +Size 47: 24,56 BAD (FFHFHH) +Size 48: 24,58 GOOD (HHFFFF) +Size 49: 25,59 BAD (FFHFHH) +Size 50: 25,60 GOOD (HHFFFF) +Size 51: 26,61 BAD (FFHFHH) +Size 52: 26,62 GOOD (HHFFFF) +Size 53: 27,64 BAD (FFHFHH) +Size 54: 27,65 GOOD (HHFFFF) +Size 55: 28,66 BAD (FFHFHH) +Size 56: 28,67 GOOD (HHFFFF) +Size 57: 29,68 BAD (FFHFHH) +Size 58: 29,70 GOOD (HHFFFF) +Size 59: 30,71 BAD (FFHFHH) +Size 60: 30,72 GOOD (HHFFFF) +Size 61: 31,73 BAD (FFHFHH) +Size 62: 31,74 GOOD (HHFFFF) +Size 63: 32,76 BAD (FFHFHH) +Size 64: 32,77 GOOD (HHFFFF) +Size 65: 33,78 BAD (FFHFHH) +Size 66: 33,79 GOOD (HHFFFF) +Size 67: 34,80 BAD (FFHFHH) +Size 68: 34,82 GOOD (HHFFFF) +Size 69: 35,83 BAD (FFHFHH) +Size 70: 35,84 GOOD (HHFFFF) +Size 71: 36,85 BAD (FFHFHH) +Size 72: 36,86 GOOD (HHFFFF) +Size 73: 37,88 BAD (FFHFHH) +Size 74: 37,89 GOOD (HHFFFF) +Size 75: 38,90 BAD (FFHFHH) +Size 76: 38,91 GOOD (HHFFFF) +Size 77: 39,92 BAD (FFHFHH) +Size 78: 39,94 GOOD (HHFFFF) +Size 79: 40,95 BAD (FFHFHH) +Size 80: 40,96 GOOD (HHFFFF) +Size 81: 41,97 BAD (FFHFHH) +Size 82: 41,98 GOOD (HHFFFF) +Size 83: 42,100 BAD (FFHFHH) +Size 84: 42,101 GOOD (HHFFFF) +Size 85: 43,102 BAD (FFHFHH) +Size 86: 43,103 GOOD (HHFFFF) +Size 87: 44,104 BAD (FFHFHH) +Size 88: 44,106 GOOD (HHFFFF) +Size 89: 45,107 BAD (FFHFHH) +Size 90: 45,108 GOOD (HHFFFF) +Size 91: 46,109 BAD (FFHFHH) +Size 92: 46,110 GOOD (HHFFFF) +Size 93: 47,112 BAD (FFHFHH) +Size 94: 47,113 GOOD (HHFFFF) +Size 95: 48,114 BAD (FFHFHH) +Size 96: 48,115 GOOD (HHFFFF) +Size 97: 49,116 BAD (FFHFHH) +Size 98: 49,118 GOOD (HHFFFF) +Size 99: 50,119 BAD (FFHFHH) +Size 100: 50,120 GOOD (HHFFFF) + +Windows 10 14342 Old Console +---------------------------- + +Size 1: 1,2 GOOD (HHFFFF) +Size 2: 1,2 GOOD (HHFFFF) +Size 3: 2,4 BAD (FFHFHH) +Size 4: 2,5 GOOD (HHFFFF) +Size 5: 3,6 BAD (FFHFHH) +Size 6: 3,7 GOOD (HHFFFF) +Size 7: 4,8 BAD (FFHFHH) +Size 8: 4,10 GOOD (HHFFFF) +Size 9: 5,11 BAD (FFHFHH) +Size 10: 5,12 GOOD (HHFFFF) +Size 11: 6,13 BAD (FFHFHH) +Size 12: 6,14 GOOD (HHFFFF) +Size 13: 7,16 BAD (FFHFHH) +Size 14: 7,17 GOOD (HHFFFF) +Size 15: 8,18 BAD (FFHFHH) +Size 16: 8,19 GOOD (HHFFFF) +Size 17: 9,20 BAD (FFHFHH) +Size 18: 9,22 GOOD (HHFFFF) +Size 19: 10,23 BAD (FFHFHH) +Size 20: 10,24 GOOD (HHFFFF) +Size 21: 11,25 BAD (FFHFHH) +Size 22: 11,26 GOOD (HHFFFF) +Size 23: 12,28 BAD (FFHFHH) +Size 24: 12,29 GOOD (HHFFFF) +Size 25: 13,30 BAD (FFHFHH) +Size 26: 13,31 GOOD (HHFFFF) +Size 27: 14,32 BAD (FFHFHH) +Size 28: 14,34 GOOD (HHFFFF) +Size 29: 15,35 BAD (FFHFHH) +Size 30: 15,36 GOOD (HHFFFF) +Size 31: 16,37 BAD (FFHFHH) +Size 32: 16,38 GOOD (HHFFFF) +Size 33: 17,40 BAD (FFHFHH) +Size 34: 17,41 GOOD (HHFFFF) +Size 35: 18,42 BAD (FFHFHH) +Size 36: 18,43 GOOD (HHFFFF) +Size 37: 19,44 BAD (FFHFHH) +Size 38: 19,46 GOOD (HHFFFF) +Size 39: 20,47 BAD (FFHFHH) +Size 40: 20,48 GOOD (HHFFFF) +Size 41: 21,49 BAD (FFHFHH) +Size 42: 21,50 GOOD (HHFFFF) +Size 43: 22,52 BAD (FFHFHH) +Size 44: 22,53 GOOD (HHFFFF) +Size 45: 23,54 BAD (FFHFHH) +Size 46: 23,55 GOOD (HHFFFF) +Size 47: 24,56 BAD (FFHFHH) +Size 48: 24,58 GOOD (HHFFFF) +Size 49: 25,59 BAD (FFHFHH) +Size 50: 25,60 GOOD (HHFFFF) +Size 51: 26,61 BAD (FFHFHH) +Size 52: 26,62 GOOD (HHFFFF) +Size 53: 27,64 BAD (FFHFHH) +Size 54: 27,65 GOOD (HHFFFF) +Size 55: 28,66 BAD (FFHFHH) +Size 56: 28,67 GOOD (HHFFFF) +Size 57: 29,68 BAD (FFHFHH) +Size 58: 29,70 GOOD (HHFFFF) +Size 59: 30,71 BAD (FFHFHH) +Size 60: 30,72 GOOD (HHFFFF) +Size 61: 31,73 BAD (FFHFHH) +Size 62: 31,74 GOOD (HHFFFF) +Size 63: 32,76 BAD (FFHFHH) +Size 64: 32,77 GOOD (HHFFFF) +Size 65: 33,78 BAD (FFHFHH) +Size 66: 33,79 GOOD (HHFFFF) +Size 67: 34,80 BAD (FFHFHH) +Size 68: 34,82 GOOD (HHFFFF) +Size 69: 35,83 BAD (FFHFHH) +Size 70: 35,84 GOOD (HHFFFF) +Size 71: 36,85 BAD (FFHFHH) +Size 72: 36,86 GOOD (HHFFFF) +Size 73: 37,88 BAD (FFHFHH) +Size 74: 37,89 GOOD (HHFFFF) +Size 75: 38,90 BAD (FFHFHH) +Size 76: 38,91 GOOD (HHFFFF) +Size 77: 39,92 BAD (FFHFHH) +Size 78: 39,94 GOOD (HHFFFF) +Size 79: 40,95 BAD (FFHFHH) +Size 80: 40,96 GOOD (HHFFFF) +Size 81: 41,97 BAD (FFHFHH) +Size 82: 41,98 GOOD (HHFFFF) +Size 83: 42,100 BAD (FFHFHH) +Size 84: 42,101 GOOD (HHFFFF) +Size 85: 43,102 BAD (FFHFHH) +Size 86: 43,103 GOOD (HHFFFF) +Size 87: 44,104 BAD (FFHFHH) +Size 88: 44,106 GOOD (HHFFFF) +Size 89: 45,107 BAD (FFHFHH) +Size 90: 45,108 GOOD (HHFFFF) +Size 91: 46,109 BAD (FFHFHH) +Size 92: 46,110 GOOD (HHFFFF) +Size 93: 47,112 BAD (FFHFHH) +Size 94: 47,113 GOOD (HHFFFF) +Size 95: 48,114 BAD (FFHFHH) +Size 96: 48,115 GOOD (HHFFFF) +Size 97: 49,116 BAD (FFHFHH) +Size 98: 49,118 GOOD (HHFFFF) +Size 99: 50,119 BAD (FFHFHH) +Size 100: 50,120 GOOD (HHFFFF) + +Windows 10 14342 New Console +---------------------------- + +Size 1: 1,1 GOOD (HHFFFF) +Size 2: 1,2 GOOD (HHFFFF) +Size 3: 2,3 GOOD (HHFFFF) +Size 4: 2,4 GOOD (HHFFFF) +Size 5: 3,5 GOOD (HHFFFF) +Size 6: 3,6 GOOD (HHFFFF) +Size 7: 4,7 GOOD (HHFFFF) +Size 8: 4,8 GOOD (HHFFFF) +Size 9: 5,9 GOOD (HHFFFF) +Size 10: 5,10 GOOD (HHFFFF) +Size 11: 6,11 GOOD (HHFFFF) +Size 12: 6,12 GOOD (HHFFFF) +Size 13: 7,13 GOOD (HHFFFF) +Size 14: 7,14 GOOD (HHFFFF) +Size 15: 8,15 GOOD (HHFFFF) +Size 16: 8,16 GOOD (HHFFFF) +Size 17: 9,17 GOOD (HHFFFF) +Size 18: 9,18 GOOD (HHFFFF) +Size 19: 10,19 GOOD (HHFFFF) +Size 20: 10,20 GOOD (HHFFFF) +Size 21: 11,21 GOOD (HHFFFF) +Size 22: 11,22 GOOD (HHFFFF) +Size 23: 12,23 GOOD (HHFFFF) +Size 24: 12,24 GOOD (HHFFFF) +Size 25: 13,25 GOOD (HHFFFF) +Size 26: 13,26 GOOD (HHFFFF) +Size 27: 14,27 GOOD (HHFFFF) +Size 28: 14,28 GOOD (HHFFFF) +Size 29: 15,29 GOOD (HHFFFF) +Size 30: 15,30 GOOD (HHFFFF) +Size 31: 16,31 GOOD (HHFFFF) +Size 32: 16,32 GOOD (HHFFFF) +Size 33: 17,33 GOOD (HHFFFF) +Size 34: 17,34 GOOD (HHFFFF) +Size 35: 18,35 GOOD (HHFFFF) +Size 36: 18,36 GOOD (HHFFFF) +Size 37: 19,37 GOOD (HHFFFF) +Size 38: 19,38 GOOD (HHFFFF) +Size 39: 20,39 GOOD (HHFFFF) +Size 40: 20,40 GOOD (HHFFFF) +Size 41: 21,41 GOOD (HHFFFF) +Size 42: 21,42 GOOD (HHFFFF) +Size 43: 22,43 GOOD (HHFFFF) +Size 44: 22,44 GOOD (HHFFFF) +Size 45: 23,45 GOOD (HHFFFF) +Size 46: 23,46 GOOD (HHFFFF) +Size 47: 24,47 GOOD (HHFFFF) +Size 48: 24,48 GOOD (HHFFFF) +Size 49: 25,49 GOOD (HHFFFF) +Size 50: 25,50 GOOD (HHFFFF) +Size 51: 26,51 GOOD (HHFFFF) +Size 52: 26,52 GOOD (HHFFFF) +Size 53: 27,53 GOOD (HHFFFF) +Size 54: 27,54 GOOD (HHFFFF) +Size 55: 28,55 GOOD (HHFFFF) +Size 56: 28,56 GOOD (HHFFFF) +Size 57: 29,57 GOOD (HHFFFF) +Size 58: 29,58 GOOD (HHFFFF) +Size 59: 30,59 GOOD (HHFFFF) +Size 60: 30,60 GOOD (HHFFFF) +Size 61: 31,61 GOOD (HHFFFF) +Size 62: 31,62 GOOD (HHFFFF) +Size 63: 32,63 GOOD (HHFFFF) +Size 64: 32,64 GOOD (HHFFFF) +Size 65: 33,65 GOOD (HHFFFF) +Size 66: 33,66 GOOD (HHFFFF) +Size 67: 34,67 GOOD (HHFFFF) +Size 68: 34,68 GOOD (HHFFFF) +Size 69: 35,69 GOOD (HHFFFF) +Size 70: 35,70 GOOD (HHFFFF) +Size 71: 36,71 GOOD (HHFFFF) +Size 72: 36,72 GOOD (HHFFFF) +Size 73: 37,73 GOOD (HHFFFF) +Size 74: 37,74 GOOD (HHFFFF) +Size 75: 38,75 GOOD (HHFFFF) +Size 76: 38,76 GOOD (HHFFFF) +Size 77: 39,77 GOOD (HHFFFF) +Size 78: 39,78 GOOD (HHFFFF) +Size 79: 40,79 GOOD (HHFFFF) +Size 80: 40,80 GOOD (HHFFFF) +Size 81: 41,81 GOOD (HHFFFF) +Size 82: 41,82 GOOD (HHFFFF) +Size 83: 42,83 GOOD (HHFFFF) +Size 84: 42,84 GOOD (HHFFFF) +Size 85: 43,85 GOOD (HHFFFF) +Size 86: 43,86 GOOD (HHFFFF) +Size 87: 44,87 GOOD (HHFFFF) +Size 88: 44,88 GOOD (HHFFFF) +Size 89: 45,89 GOOD (HHFFFF) +Size 90: 45,90 GOOD (HHFFFF) +Size 91: 46,91 GOOD (HHFFFF) +Size 92: 46,92 GOOD (HHFFFF) +Size 93: 47,93 GOOD (HHFFFF) +Size 94: 47,94 GOOD (HHFFFF) +Size 95: 48,95 GOOD (HHFFFF) +Size 96: 48,96 GOOD (HHFFFF) +Size 97: 49,97 GOOD (HHFFFF) +Size 98: 49,98 GOOD (HHFFFF) +Size 99: 50,99 GOOD (HHFFFF) +Size 100: 50,100 GOOD (HHFFFF) diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/MinimumWindowWidths.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/MinimumWindowWidths.txt new file mode 100644 index 0000000000..d5261d8db3 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/MinimumWindowWidths.txt @@ -0,0 +1,16 @@ +The narrowest allowed console window, in pixels, on a conventional (~96dpi) +monitor: + +(mode con: cols=40 lines=40) && SetFont.exe -face "Lucida Console" -h 1 && (ping -n 4 127.0.0.1 > NUL) && cls && GetConsolePos.exe && SetFont.exe -face "Lucida Console" -h 12 + +(mode con: cols=40 lines=40) && SetFont.exe -face "Lucida Console" -h 16 && (ping -n 4 127.0.0.1 > NUL) && cls && GetConsolePos.exe && SetFont.exe -face "Lucida Console" -h 12 + + sz1:px sz1:col sz16:px sz16:col +Vista: 124 104 137 10 +Windows 7: 132 112 147 11 +Windows 8: 140 120 147 11 +Windows 8.1: 140 120 147 11 +Windows 10 OLD: 136 116 147 11 +Windows 10 NEW: 136 103 136 10 + +I used build 14342 to test Windows 10. diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/Results.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/Results.txt new file mode 100644 index 0000000000..15a825cb51 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/Results.txt @@ -0,0 +1,4 @@ +As before, avoid odd sizes in favor of even sizes. + +It's curious that the Japanese font is handled so poorly, especially with +Windows 8 and later. diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/Windows10SetFontBugginess.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/Windows10SetFontBugginess.txt new file mode 100644 index 0000000000..fef397a1e3 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/Windows10SetFontBugginess.txt @@ -0,0 +1,144 @@ +Issues: + + - Starting with the 14342 build, changing the font using + SetCurrentConsoleFontEx does not affect the window size. e.g. The content + itself will resize/redraw, but the window neither shrinks nor expands. + Presumably this is an oversight? It's almost a convenience; if a program + is going to resize the window anyway, then it's nice that the window size + contraints don't get in the way. Ordinarily, changing the font doesn't just + change the window size in pixels--it can also change the size as measured in + rows and columns. + + - (Aside: in the 14342 build, there is also a bug with wmic.exe. Open a console + with more than 300 lines of screen buffer, then fill those lines with, e.g., + dir /s. Then run wmic.exe. You won't be able to see the wmic.exe prompt. + If you query the screen buffer info somehow, you'll notice that the srWindow + is not contained within the dwSize. This breaks winpty's scraping, because + it's invalid.) + + - In build 14316, with the Japanese locale, with the 437 code page, attempting + to set the Consolas font instead sets the Terminal (raster) font. It seems + to pick an appropriate vertical size. + + - It seems necessary to specify "-family 0x36" for maximum reliability. + Setting the family to 0 almost always works, and specifying just -tt rarely + works. + +Win7 + English locale / 437 code page: + SetFont.exe -face Consolas -h 16 works + SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead + SetFont.exe -face Consolas -h 16 -family 0x36 works + Japanese locale / 932 code page: + SetFont.exe -face Consolas -h 16 works + SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead + SetFont.exe -face Consolas -h 16 -family 0x36 works + Japanese locale / 437 code page: + SetFont.exe -face Consolas -h 16 works + SetFont.exe -face Consolas -h 16 -tt unreliable + SetFont.exe -face Consolas -h 16 -family 0x36 works + +Win10 Build 10586 + New console + Japanese locale / 437 code page: + SetFont.exe -face Consolas -h 16 works + SetFont.exe -face Consolas -h 16 -tt selects Terminal instead + SetFont.exe -face Consolas -h 16 -family 0x36 works + +Win10 Build 14316 + Old console + English locale / 437 code page: + SetFont.exe -face Consolas -h 16 works + SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead + SetFont.exe -face Consolas -h 16 -family 0x36 works + Japanese locale / 932 code page: + SetFont.exe -face Consolas -h 16 works + SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead + SetFont.exe -face Consolas -h 16 -family 0x36 works + Japanese locale / 437 code page: + SetFont.exe -face Consolas -h 16 works + SetFont.exe -face Consolas -h 16 -tt selected very small Consolas font + SetFont.exe -face Consolas -h 16 -family 0x36 works + New console + English locale / 437 code page: + SetFont.exe -face Consolas -h 16 works + SetFont.exe -face Consolas -h 16 -tt works + SetFont.exe -face Consolas -h 16 -family 0x36 works + Japanese locale / 932 code page: + SetFont.exe -face Consolas -h 16 selects gothic instead + SetFont.exe -face Consolas -h 16 -tt selects gothic instead + SetFont.exe -face Consolas -h 16 -family 0x36 selects gothic instead + Japanese locale / 437 code page: + SetFont.exe -face Consolas -h 16 selects Terminal font instead + SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead + SetFont.exe -face Consolas -h 16 -family 0x36(*) selects Terminal font instead + +Win10 Build 14342 + Old Console + English locale / 437 code page: + SetFont.exe -face Consolas -h 16 works + SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead + SetFont.exe -face Consolas -h 16 -family 0x36 works + Japanese locale / 932 code page: + SetFont.exe -face Consolas -h 16 works + SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead + SetFont.exe -face Consolas -h 16 -family 0x36 works + Japanese locale / 437 code page: + SetFont.exe -face Consolas -h 16 works + SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead + SetFont.exe -face Consolas -h 16 -family 0x36 works + New console + English locale / 437 code page: + SetFont.exe -face Consolas -h 16 works + SetFont.exe -face Consolas -h 16 -tt works + SetFont.exe -face Consolas -h 16 -family 0x36 works + Japanese locale / 932 code page: + SetFont.exe -face Consolas -h 16 selects gothic instead + SetFont.exe -face Consolas -h 16 -tt selects gothic instead + SetFont.exe -face Consolas -h 16 -family 0x36 selects gothic instead + Japanese locale / 437 code page: + SetFont.exe -face Consolas -h 16 selects Terminal font instead + SetFont.exe -face Consolas -h 16 -tt works + SetFont.exe -face Consolas -h 16 -family 0x36 works + +(*) I was trying to figure out whether the inconsistency was at when I stumbled +onto this completely unexpected bug. Here's more detail: + + F:\>SetFont.exe -face Consolas -h 16 -family 0x36 -weight normal -w 8 + Setting to: nFont=0 dwFontSize=(8,16) FontFamily=0x36 FontWeight=400 FaceName="Consolas" + SetCurrentConsoleFontEx returned 1 + + F:\>GetFont.exe + largestConsoleWindowSize=(96,50) + maxWnd=0: nFont=0 dwFontSize=(12,16) FontFamily=0x30 FontWeight=400 FaceName=Terminal (54 65 72 6D 69 6E 61 6C) + maxWnd=1: nFont=0 dwFontSize=(96,25) FontFamily=0x30 FontWeight=400 FaceName=Terminal (54 65 72 6D 69 6E 61 6C) + 00-00: 12x16 + GetNumberOfConsoleFonts returned 0 + CP=437 OutputCP=437 + + F:\>SetFont.exe -face "Lucida Console" -h 16 -family 0x36 -weight normal + Setting to: nFont=0 dwFontSize=(0,16) FontFamily=0x36 FontWeight=400 FaceName="Lucida Console" + SetCurrentConsoleFontEx returned 1 + + F:\>GetFont.exe + largestConsoleWindowSize=(96,50) + maxWnd=0: nFont=0 dwFontSize=(12,16) FontFamily=0x30 FontWeight=400 FaceName=Terminal (54 65 72 6D 69 6E 61 6C) + maxWnd=1: nFont=0 dwFontSize=(96,25) FontFamily=0x30 FontWeight=400 FaceName=Terminal (54 65 72 6D 69 6E 61 6C) + 00-00: 12x16 + GetNumberOfConsoleFonts returned 0 + CP=437 OutputCP=437 + + F:\>SetFont.exe -face "Lucida Console" -h 12 -family 0x36 -weight normal + Setting to: nFont=0 dwFontSize=(0,12) FontFamily=0x36 FontWeight=400 FaceName="Lucida Console" + SetCurrentConsoleFontEx returned 1 + + F:\>GetFont.exe + largestConsoleWindowSize=(230,66) + maxWnd=0: nFont=0 dwFontSize=(5,12) FontFamily=0x30 FontWeight=400 FaceName=Terminal (54 65 72 6D 69 6E 61 6C) + maxWnd=1: nFont=0 dwFontSize=(116,36) FontFamily=0x30 FontWeight=400 FaceName=Terminal (54 65 72 6D 69 6E 61 6C) + 00-00: 5x12 + GetNumberOfConsoleFonts returned 0 + CP=437 OutputCP=437 + +Even attempting to set to a Lucida Console / Consolas font from the Console +properties dialog fails. diff --git a/src/libs/3rdparty/winpty/misc/FontSurvey.cc b/src/libs/3rdparty/winpty/misc/FontSurvey.cc new file mode 100644 index 0000000000..254bcc81a6 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/FontSurvey.cc @@ -0,0 +1,100 @@ +#include + +#include +#include +#include + +#include + +#include "TestUtil.cc" + +#define COUNT_OF(array) (sizeof(array) / sizeof((array)[0])) + +// See https://en.wikipedia.org/wiki/List_of_CJK_fonts +const wchar_t kMSGothic[] = { 0xff2d, 0xff33, 0x0020, 0x30b4, 0x30b7, 0x30c3, 0x30af, 0 }; // Japanese +const wchar_t kNSimSun[] = { 0x65b0, 0x5b8b, 0x4f53, 0 }; // Simplified Chinese +const wchar_t kMingLight[] = { 0x7d30, 0x660e, 0x9ad4, 0 }; // Traditional Chinese +const wchar_t kGulimChe[] = { 0xad74, 0xb9bc, 0xccb4, 0 }; // Korean + +std::vector condense(const std::vector &buf) { + std::vector ret; + size_t i = 0; + while (i < buf.size()) { + if (buf[i].Char.UnicodeChar == L' ' && + ((buf[i].Attributes & 0x300) == 0)) { + // end of line + break; + } else if (i + 1 < buf.size() && + ((buf[i].Attributes & 0x300) == 0x100) && + ((buf[i + 1].Attributes & 0x300) == 0x200) && + buf[i].Char.UnicodeChar != L' ' && + buf[i].Char.UnicodeChar == buf[i + 1].Char.UnicodeChar) { + // double-width + ret.push_back(true); + i += 2; + } else if ((buf[i].Attributes & 0x300) == 0) { + // single-width + ret.push_back(false); + i++; + } else { + ASSERT(false && "unexpected output"); + } + } + return ret; +} + +int main(int argc, char *argv[]) { + if (argc != 2) { + printf("Usage: %s \"arguments for SetFont.exe\"\n", argv[0]); + return 1; + } + + const char *setFontArgs = argv[1]; + + const wchar_t testLine[] = { 0xA2, 0xA3, 0x2014, 0x3044, 0x30FC, 0x4000, 0 }; + const HANDLE conout = openConout(); + + char setFontCmd[1024]; + for (int h = 1; h <= 100; ++h) { + sprintf(setFontCmd, ".\\SetFont.exe %s -h %d && cls", setFontArgs, h); + system(setFontCmd); + + CONSOLE_FONT_INFOEX infoex = {}; + infoex.cbSize = sizeof(infoex); + BOOL success = GetCurrentConsoleFontEx(conout, FALSE, &infoex); + ASSERT(success && "GetCurrentConsoleFontEx failed"); + + DWORD actual = 0; + success = WriteConsoleW(conout, testLine, wcslen(testLine), &actual, nullptr); + ASSERT(success && actual == wcslen(testLine)); + + std::vector readBuf(14); + const SMALL_RECT readRegion = {0, 0, static_cast(readBuf.size() - 1), 0}; + SMALL_RECT readRegion2 = readRegion; + success = ReadConsoleOutputW( + conout, readBuf.data(), + {static_cast(readBuf.size()), 1}, + {0, 0}, + &readRegion2); + ASSERT(success && !memcmp(&readRegion, &readRegion2, sizeof(readRegion))); + + const auto widths = condense(readBuf); + std::string widthsStr; + for (bool width : widths) { + widthsStr.append(width ? "F" : "H"); + } + char size[16]; + sprintf(size, "%d,%d", infoex.dwFontSize.X, infoex.dwFontSize.Y); + const char *status = ""; + if (widthsStr == "HHFFFF") { + status = "GOOD"; + } else if (widthsStr == "HHHFFF") { + status = "OK"; + } else { + status = "BAD"; + } + trace("Size %3d: %-7s %-4s (%s)", h, size, status, widthsStr.c_str()); + } + sprintf(setFontCmd, ".\\SetFont.exe %s -h 14", setFontArgs); + system(setFontCmd); +} diff --git a/src/libs/3rdparty/winpty/misc/FormatChar.h b/src/libs/3rdparty/winpty/misc/FormatChar.h new file mode 100644 index 0000000000..aade488f9e --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/FormatChar.h @@ -0,0 +1,21 @@ +#include +#include +#include + +static inline void formatChar(char *str, char ch) +{ + // Print some common control codes. + switch (ch) { + case '\r': strcpy(str, "CR "); break; + case '\n': strcpy(str, "LF "); break; + case ' ': strcpy(str, "SP "); break; + case 27: strcpy(str, "^[ "); break; + case 3: strcpy(str, "^C "); break; + default: + if (isgraph(ch)) + sprintf(str, "%c ", ch); + else + sprintf(str, "%02x ", ch); + break; + } +} diff --git a/src/libs/3rdparty/winpty/misc/FreezePerfTest.cc b/src/libs/3rdparty/winpty/misc/FreezePerfTest.cc new file mode 100644 index 0000000000..2c0b0086a1 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/FreezePerfTest.cc @@ -0,0 +1,62 @@ +#include + +#include "TestUtil.cc" + +const int SC_CONSOLE_MARK = 0xFFF2; +const int SC_CONSOLE_SELECT_ALL = 0xFFF5; + +int main(int argc, char *argv[0]) { + + if (argc != 2) { + printf("Usage: %s (mark|selectall|read)\n", argv[0]); + return 1; + } + + enum class Test { Mark, SelectAll, Read } test; + if (!strcmp(argv[1], "mark")) { + test = Test::Mark; + } else if (!strcmp(argv[1], "selectall")) { + test = Test::SelectAll; + } else if (!strcmp(argv[1], "read")) { + test = Test::Read; + } else { + printf("Invalid test: %s\n", argv[1]); + return 1; + } + + HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE); + TimeMeasurement tm; + HWND hwnd = GetConsoleWindow(); + + setWindowPos(0, 0, 1, 1); + setBufferSize(100, 3000); + system("cls"); + setWindowPos(0, 2975, 100, 25); + setCursorPos(0, 2999); + + ShowWindow(hwnd, SW_HIDE); + + for (int i = 0; i < 1000; ++i) { + // CONSOLE_SCREEN_BUFFER_INFO info = {}; + // GetConsoleScreenBufferInfo(conout, &info); + + if (test == Test::Mark) { + SendMessage(hwnd, WM_SYSCOMMAND, SC_CONSOLE_MARK, 0); + SendMessage(hwnd, WM_CHAR, 27, 0x00010001); + } else if (test == Test::SelectAll) { + SendMessage(hwnd, WM_SYSCOMMAND, SC_CONSOLE_SELECT_ALL, 0); + SendMessage(hwnd, WM_CHAR, 27, 0x00010001); + } else if (test == Test::Read) { + static CHAR_INFO buffer[100 * 3000]; + const SMALL_RECT readRegion = {0, 0, 99, 2999}; + SMALL_RECT tmp = readRegion; + BOOL ret = ReadConsoleOutput(conout, buffer, {100, 3000}, {0, 0}, &tmp); + ASSERT(ret && !memcmp(&tmp, &readRegion, sizeof(tmp))); + } + } + + ShowWindow(hwnd, SW_SHOW); + + printf("elapsed: %f\n", tm.elapsed()); + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/GetCh.cc b/src/libs/3rdparty/winpty/misc/GetCh.cc new file mode 100644 index 0000000000..cd6ed1943a --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/GetCh.cc @@ -0,0 +1,20 @@ +#include +#include +#include + +int main() { + printf("\nPress any keys -- Ctrl-D exits\n\n"); + + while (true) { + const int ch = getch(); + printf("0x%x", ch); + if (isgraph(ch)) { + printf(" '%c'", ch); + } + printf("\n"); + if (ch == 0x4) { // Ctrl-D + break; + } + } + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/GetConsolePos.cc b/src/libs/3rdparty/winpty/misc/GetConsolePos.cc new file mode 100644 index 0000000000..1f3cc5316f --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/GetConsolePos.cc @@ -0,0 +1,41 @@ +#include + +#include + +#include "TestUtil.cc" + +int main() { + const HANDLE conout = openConout(); + + CONSOLE_SCREEN_BUFFER_INFO info = {}; + BOOL ret = GetConsoleScreenBufferInfo(conout, &info); + ASSERT(ret && "GetConsoleScreenBufferInfo failed"); + + trace("cursor=%d,%d", info.dwCursorPosition.X, info.dwCursorPosition.Y); + printf("cursor=%d,%d\n", info.dwCursorPosition.X, info.dwCursorPosition.Y); + + trace("srWindow={L=%d,T=%d,R=%d,B=%d}", info.srWindow.Left, info.srWindow.Top, info.srWindow.Right, info.srWindow.Bottom); + printf("srWindow={L=%d,T=%d,R=%d,B=%d}\n", info.srWindow.Left, info.srWindow.Top, info.srWindow.Right, info.srWindow.Bottom); + + trace("dwSize=%d,%d", info.dwSize.X, info.dwSize.Y); + printf("dwSize=%d,%d\n", info.dwSize.X, info.dwSize.Y); + + const HWND hwnd = GetConsoleWindow(); + if (hwnd != NULL) { + RECT r = {}; + if (GetWindowRect(hwnd, &r)) { + const int w = r.right - r.left; + const int h = r.bottom - r.top; + trace("hwnd: pos=(%d,%d) size=(%d,%d)", r.left, r.top, w, h); + printf("hwnd: pos=(%d,%d) size=(%d,%d)\n", r.left, r.top, w, h); + } else { + trace("GetWindowRect failed"); + printf("GetWindowRect failed\n"); + } + } else { + trace("GetConsoleWindow returned NULL"); + printf("GetConsoleWindow returned NULL\n"); + } + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/GetFont.cc b/src/libs/3rdparty/winpty/misc/GetFont.cc new file mode 100644 index 0000000000..38625317ab --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/GetFont.cc @@ -0,0 +1,261 @@ +#include +#include +#include +#include + +#include "../src/shared/OsModule.h" +#include "../src/shared/StringUtil.h" + +#include "TestUtil.cc" +#include "../src/shared/StringUtil.cc" + +#define COUNT_OF(x) (sizeof(x) / sizeof((x)[0])) + +// Some of these types and functions are missing from the MinGW headers. +// Others are undocumented. + +struct AGENT_CONSOLE_FONT_INFO { + DWORD nFont; + COORD dwFontSize; +}; + +struct AGENT_CONSOLE_FONT_INFOEX { + ULONG cbSize; + DWORD nFont; + COORD dwFontSize; + UINT FontFamily; + UINT FontWeight; + WCHAR FaceName[LF_FACESIZE]; +}; + +// undocumented XP API +typedef BOOL WINAPI SetConsoleFont_t( + HANDLE hOutput, + DWORD dwFontIndex); + +// undocumented XP API +typedef DWORD WINAPI GetNumberOfConsoleFonts_t(); + +// XP and up +typedef BOOL WINAPI GetCurrentConsoleFont_t( + HANDLE hOutput, + BOOL bMaximumWindow, + AGENT_CONSOLE_FONT_INFO *lpConsoleCurrentFont); + +// XP and up +typedef COORD WINAPI GetConsoleFontSize_t( + HANDLE hConsoleOutput, + DWORD nFont); + +// Vista and up +typedef BOOL WINAPI GetCurrentConsoleFontEx_t( + HANDLE hConsoleOutput, + BOOL bMaximumWindow, + AGENT_CONSOLE_FONT_INFOEX *lpConsoleCurrentFontEx); + +// Vista and up +typedef BOOL WINAPI SetCurrentConsoleFontEx_t( + HANDLE hConsoleOutput, + BOOL bMaximumWindow, + AGENT_CONSOLE_FONT_INFOEX *lpConsoleCurrentFontEx); + +#define GET_MODULE_PROC(mod, funcName) \ + m_##funcName = reinterpret_cast((mod).proc(#funcName)); \ + +#define DEFINE_ACCESSOR(funcName) \ + funcName##_t &funcName() const { \ + ASSERT(valid()); \ + return *m_##funcName; \ + } + +class XPFontAPI { +public: + XPFontAPI() : m_kernel32(L"kernel32.dll") { + GET_MODULE_PROC(m_kernel32, GetCurrentConsoleFont); + GET_MODULE_PROC(m_kernel32, GetConsoleFontSize); + } + + bool valid() const { + return m_GetCurrentConsoleFont != NULL && + m_GetConsoleFontSize != NULL; + } + + DEFINE_ACCESSOR(GetCurrentConsoleFont) + DEFINE_ACCESSOR(GetConsoleFontSize) + +private: + OsModule m_kernel32; + GetCurrentConsoleFont_t *m_GetCurrentConsoleFont; + GetConsoleFontSize_t *m_GetConsoleFontSize; +}; + +class UndocumentedXPFontAPI : public XPFontAPI { +public: + UndocumentedXPFontAPI() : m_kernel32(L"kernel32.dll") { + GET_MODULE_PROC(m_kernel32, SetConsoleFont); + GET_MODULE_PROC(m_kernel32, GetNumberOfConsoleFonts); + } + + bool valid() const { + return this->XPFontAPI::valid() && + m_SetConsoleFont != NULL && + m_GetNumberOfConsoleFonts != NULL; + } + + DEFINE_ACCESSOR(SetConsoleFont) + DEFINE_ACCESSOR(GetNumberOfConsoleFonts) + +private: + OsModule m_kernel32; + SetConsoleFont_t *m_SetConsoleFont; + GetNumberOfConsoleFonts_t *m_GetNumberOfConsoleFonts; +}; + +class VistaFontAPI : public XPFontAPI { +public: + VistaFontAPI() : m_kernel32(L"kernel32.dll") { + GET_MODULE_PROC(m_kernel32, GetCurrentConsoleFontEx); + GET_MODULE_PROC(m_kernel32, SetCurrentConsoleFontEx); + } + + bool valid() const { + return this->XPFontAPI::valid() && + m_GetCurrentConsoleFontEx != NULL && + m_SetCurrentConsoleFontEx != NULL; + } + + DEFINE_ACCESSOR(GetCurrentConsoleFontEx) + DEFINE_ACCESSOR(SetCurrentConsoleFontEx) + +private: + OsModule m_kernel32; + GetCurrentConsoleFontEx_t *m_GetCurrentConsoleFontEx; + SetCurrentConsoleFontEx_t *m_SetCurrentConsoleFontEx; +}; + +static std::vector > readFontTable( + XPFontAPI &api, HANDLE conout, DWORD maxCount) { + std::vector > ret; + for (DWORD i = 0; i < maxCount; ++i) { + COORD size = api.GetConsoleFontSize()(conout, i); + if (size.X == 0 && size.Y == 0) { + break; + } + ret.push_back(std::make_pair(i, size)); + } + return ret; +} + +static void dumpFontTable(HANDLE conout) { + const int kMaxCount = 1000; + XPFontAPI api; + if (!api.valid()) { + printf("dumpFontTable: cannot dump font table -- missing APIs\n"); + return; + } + std::vector > table = + readFontTable(api, conout, kMaxCount); + std::string line; + char tmp[128]; + size_t first = 0; + while (first < table.size()) { + size_t last = std::min(table.size() - 1, first + 10 - 1); + winpty_snprintf(tmp, "%02u-%02u:", + static_cast(first), static_cast(last)); + line = tmp; + for (size_t i = first; i <= last; ++i) { + if (i % 10 == 5) { + line += " - "; + } + winpty_snprintf(tmp, " %2dx%-2d", + table[i].second.X, table[i].second.Y); + line += tmp; + } + printf("%s\n", line.c_str()); + first = last + 1; + } + if (table.size() == kMaxCount) { + printf("... stopped reading at %d fonts ...\n", kMaxCount); + } +} + +static std::string stringToCodePoints(const std::wstring &str) { + std::string ret = "("; + for (size_t i = 0; i < str.size(); ++i) { + char tmp[32]; + winpty_snprintf(tmp, "%X", str[i]); + if (ret.size() > 1) { + ret.push_back(' '); + } + ret += tmp; + } + ret.push_back(')'); + return ret; +} + +static void dumpFontInfoEx( + const AGENT_CONSOLE_FONT_INFOEX &infoex) { + std::wstring faceName(infoex.FaceName, + winpty_wcsnlen(infoex.FaceName, COUNT_OF(infoex.FaceName))); + cprintf(L"nFont=%u dwFontSize=(%d,%d) " + "FontFamily=0x%x FontWeight=%u FaceName=%ls %hs\n", + static_cast(infoex.nFont), + infoex.dwFontSize.X, infoex.dwFontSize.Y, + infoex.FontFamily, infoex.FontWeight, faceName.c_str(), + stringToCodePoints(faceName).c_str()); +} + +static void dumpVistaFont(VistaFontAPI &api, HANDLE conout, BOOL maxWindow) { + AGENT_CONSOLE_FONT_INFOEX infoex = {0}; + infoex.cbSize = sizeof(infoex); + if (!api.GetCurrentConsoleFontEx()(conout, maxWindow, &infoex)) { + printf("GetCurrentConsoleFontEx call failed\n"); + return; + } + dumpFontInfoEx(infoex); +} + +static void dumpXPFont(XPFontAPI &api, HANDLE conout, BOOL maxWindow) { + AGENT_CONSOLE_FONT_INFO info = {0}; + if (!api.GetCurrentConsoleFont()(conout, maxWindow, &info)) { + printf("GetCurrentConsoleFont call failed\n"); + return; + } + printf("nFont=%u dwFontSize=(%d,%d)\n", + static_cast(info.nFont), + info.dwFontSize.X, info.dwFontSize.Y); +} + +static void dumpFontAndTable(HANDLE conout) { + VistaFontAPI vista; + if (vista.valid()) { + printf("maxWnd=0: "); dumpVistaFont(vista, conout, FALSE); + printf("maxWnd=1: "); dumpVistaFont(vista, conout, TRUE); + dumpFontTable(conout); + return; + } + UndocumentedXPFontAPI xp; + if (xp.valid()) { + printf("maxWnd=0: "); dumpXPFont(xp, conout, FALSE); + printf("maxWnd=1: "); dumpXPFont(xp, conout, TRUE); + dumpFontTable(conout); + return; + } + printf("setSmallFont: neither Vista nor XP APIs detected -- giving up\n"); + dumpFontTable(conout); +} + +int main() { + const HANDLE conout = openConout(); + const COORD largest = GetLargestConsoleWindowSize(conout); + printf("largestConsoleWindowSize=(%d,%d)\n", largest.X, largest.Y); + dumpFontAndTable(conout); + UndocumentedXPFontAPI xp; + if (xp.valid()) { + printf("GetNumberOfConsoleFonts returned %u\n", xp.GetNumberOfConsoleFonts()()); + } else { + printf("The GetNumberOfConsoleFonts API was missing\n"); + } + printf("CP=%u OutputCP=%u\n", GetConsoleCP(), GetConsoleOutputCP()); + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/IdentifyConsoleWindow.ps1 b/src/libs/3rdparty/winpty/misc/IdentifyConsoleWindow.ps1 new file mode 100644 index 0000000000..0c488597bd --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/IdentifyConsoleWindow.ps1 @@ -0,0 +1,51 @@ +# +# Usage: powershell \IdentifyConsoleWindow.ps1 +# +# This script determines whether the process has a console attached, whether +# that console has a non-NULL window (e.g. HWND), and whether the window is on +# the current window station. +# + +$signature = @' +[DllImport("kernel32.dll", SetLastError=true)] +public static extern IntPtr GetConsoleWindow(); + +[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)] +public static extern bool SetConsoleTitle(String title); + +[DllImport("user32.dll", CharSet=CharSet.Auto, SetLastError=true)] +public static extern int GetWindowText(IntPtr hWnd, + System.Text.StringBuilder lpString, + int nMaxCount); +'@ + +$WinAPI = Add-Type -MemberDefinition $signature ` + -Name WinAPI -Namespace IdentifyConsoleWindow -PassThru + +if (!$WinAPI::SetConsoleTitle("ConsoleWindowScript")) { + echo "error: could not change console title -- is a console attached?" + exit 1 +} else { + echo "note: successfully set console title to ""ConsoleWindowScript""." +} + +$hwnd = $WinAPI::GetConsoleWindow() +if ($hwnd -eq 0) { + echo "note: GetConsoleWindow returned NULL." +} else { + echo "note: GetConsoleWindow returned 0x$($hwnd.ToString("X"))." + $sb = New-Object System.Text.StringBuilder -ArgumentList 4096 + if ($WinAPI::GetWindowText($hwnd, $sb, $sb.Capacity)) { + $title = $sb.ToString() + echo "note: GetWindowText returned ""${title}""." + if ($title -eq "ConsoleWindowScript") { + echo "success!" + } else { + echo "error: expected to see ""ConsoleWindowScript""." + echo " (Perhaps the console window is on a different window station?)" + } + } else { + echo "error: GetWindowText could not read the window title." + echo " (Perhaps the console window is on a different window station?)" + } +} diff --git a/src/libs/3rdparty/winpty/misc/IsNewConsole.cc b/src/libs/3rdparty/winpty/misc/IsNewConsole.cc new file mode 100644 index 0000000000..2b554c72c9 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/IsNewConsole.cc @@ -0,0 +1,87 @@ +// Determines whether this is a new console by testing whether MARK moves the +// cursor. +// +// WARNING: This test program may behave erratically if run under winpty. +// + +#include + +#include +#include + +#include "TestUtil.cc" + +const int SC_CONSOLE_MARK = 0xFFF2; +const int SC_CONSOLE_SELECT_ALL = 0xFFF5; + +static COORD getWindowPos(HANDLE conout) { + CONSOLE_SCREEN_BUFFER_INFO info = {}; + BOOL ret = GetConsoleScreenBufferInfo(conout, &info); + ASSERT(ret && "GetConsoleScreenBufferInfo failed"); + return { info.srWindow.Left, info.srWindow.Top }; +} + +static COORD getWindowSize(HANDLE conout) { + CONSOLE_SCREEN_BUFFER_INFO info = {}; + BOOL ret = GetConsoleScreenBufferInfo(conout, &info); + ASSERT(ret && "GetConsoleScreenBufferInfo failed"); + return { + static_cast(info.srWindow.Right - info.srWindow.Left + 1), + static_cast(info.srWindow.Bottom - info.srWindow.Top + 1) + }; +} + +static COORD getCursorPos(HANDLE conout) { + CONSOLE_SCREEN_BUFFER_INFO info = {}; + BOOL ret = GetConsoleScreenBufferInfo(conout, &info); + ASSERT(ret && "GetConsoleScreenBufferInfo failed"); + return info.dwCursorPosition; +} + +static void setCursorPos(HANDLE conout, COORD pos) { + BOOL ret = SetConsoleCursorPosition(conout, pos); + ASSERT(ret && "SetConsoleCursorPosition failed"); +} + +int main() { + const HANDLE conout = openConout(); + const HWND hwnd = GetConsoleWindow(); + ASSERT(hwnd != NULL && "GetConsoleWindow() returned NULL"); + + // With the legacy console, the Mark command moves the the cursor to the + // top-left cell of the visible console window. Determine whether this + // is the new console by seeing if the cursor moves. + + const auto windowSize = getWindowSize(conout); + if (windowSize.X <= 1) { + printf("Error: console window must be at least 2 columns wide\n"); + trace("Error: console window must be at least 2 columns wide"); + return 1; + } + + bool cursorMoved = false; + const auto initialPos = getCursorPos(conout); + + const auto windowPos = getWindowPos(conout); + setCursorPos(conout, { static_cast(windowPos.X + 1), windowPos.Y }); + + { + const auto posA = getCursorPos(conout); + SendMessage(hwnd, WM_SYSCOMMAND, SC_CONSOLE_MARK, 0); + const auto posB = getCursorPos(conout); + cursorMoved = memcmp(&posA, &posB, sizeof(posA)) != 0; + SendMessage(hwnd, WM_CHAR, 27, 0x00010001); // Send ESCAPE + } + + setCursorPos(conout, initialPos); + + if (cursorMoved) { + printf("Legacy console (i.e. MARK moved cursor)\n"); + trace("Legacy console (i.e. MARK moved cursor)"); + } else { + printf("Windows 10 new console (i.e MARK did not move cursor)\n"); + trace("Windows 10 new console (i.e MARK did not move cursor)"); + } + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/MouseInputNotes.txt b/src/libs/3rdparty/winpty/misc/MouseInputNotes.txt new file mode 100644 index 0000000000..18460c6861 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/MouseInputNotes.txt @@ -0,0 +1,90 @@ +Introduction +============ + +The only specification I could find describing mouse input escape sequences +was the /usr/share/doc/xterm/ctlseqs.txt.gz file installed on my Ubuntu +machine. + +Here are the relevant escape sequences: + + * [ON] CSI '?' M 'h' Enable mouse input mode M + * [OFF] CSI '?' M 'l' Disable mouse input mode M + * [EVT] CSI 'M' F X Y Mouse event (default or mode 1005) + * [EVT6] CSI '<' F ';' X ';' Y 'M' Mouse event with mode 1006 + * [EVT6] CSI '<' F ';' X ';' Y 'm' Mouse event with mode 1006 (up) + * [EVT15] CSI F ';' X ';' Y 'M' Mouse event with mode 1015 + +The first batch of modes affect what events are reported: + + * 9: Presses only (not as well-supported as the other modes) + * 1000: Presses and releases + * 1002: Presses, releases, and moves-while-pressed + * 1003: Presses, releases, and all moves + +The next batch of modes affect the encoding of the mouse events: + + * 1005: The X and Y coordinates are UTF-8 codepoints rather than bytes. + * 1006: Use the EVT6 sequences instead of EVT + * 1015: Use the EVT15 sequence instead of EVT (aka URVXT-mode) + +Support for modes in existing terminals +======================================= + + | 9 1000 1002 1003 | 1004 | overflow | defhi | 1005 1006 1015 +---------------------------------+---------------------+------+--------------+-------+---------------- +Eclipse TM Terminal (Neon) | _ _ _ _ | _ | n/a | n/a | _ _ _ +gnome-terminal 3.6.2 | X X X X | _ | suppressed*b | 0x07 | _ X X +iTerm2 2.1.4 | _ X X X | OI | wrap*z | n/a | X X X +jediterm/IntelliJ | _ X X X | _ | ch='?' | 0xff | X X X +Konsole 2.13.2 | _ X X *a | _ | suppressed | 0xff | X X X +mintty 2.2.2 | X X X X | OI | ch='\0' | 0xff | X X X +putty 0.66 | _ X X _ | _ | suppressed | 0xff | _ X X +rxvt 2.7.10 | X X _ _ | _ | wrap*z | n/a | _ _ _ +screen(under xterm) | X X X X | _ | suppressed | 0xff | _ _ _ +urxvt 9.21 | X X X X | _ | wrap*z | n/a | X _ X +xfce4-terminal 0.6.3 (GTK2 VTE) | X X X X | _ | wrap | n/a | _ _ _ +xterm | X X X X | OI | ch='\0' | 0xff | X X X + +*a: Mode 1003 is handled the same way as 1002. +*b: The coordinate wraps from 0xff to 0x00, then maxs out at 0x07. I'm + guessing this behavior is a bug? I'm using the Xubuntu 14.04 + gnome-terminal. +*z: These terminals have a bug where column 224 (and row 224, presumably) + yields a truncated escape sequence. 224 + 32 is 0, so it would normally + yield `CSI 'M' F '\0' Y`, but the '\0' is interpreted as a NUL-terminator. + +Problem 1: How do these flags work? +=================================== + +Terminals accept the OFF sequence with any of the input modes. This makes +little sense--there are two multi-value settings, not seven independent flags! + +All the terminals handle Granularity the same way. ON-Granularity sets +Granularity to the specified value, and OFF-Granularity sets Granularity to +OFF. + +Terminals vary in how they handle the Encoding modes. For example: + + * xterm. ON-Encoding sets Encoding. OFF-Encoding with a non-active Encoding + has no effect. OFF-Encoding otherwise resets Encoding to Default. + + * mintty (tested 2.2.2), iTerm2 2.1.4, and jediterm. ON-Encoding sets + Encoding. OFF-Encoding resets Encoding to Default. + + * Konsole (tested 2.13.2) seems to configure each encoding method + independently. The effective Encoding is the first enabled encoding in this + list: + - Mode 1006 + - Mode 1015 + - Mode 1005 + - Default + + * gnome-terminal (tested 3.6.2) also configures each encoding method + independently. The effective Encoding is the first enabled encoding in + this list: + - Mode 1006 + - Mode 1015 + - Default + Mode 1005 is not supported. + + * xfce4 terminal 0.6.3 (GTK2 VTE) always outputs the default encoding method. diff --git a/src/libs/3rdparty/winpty/misc/MoveConsoleWindow.cc b/src/libs/3rdparty/winpty/misc/MoveConsoleWindow.cc new file mode 100644 index 0000000000..7d9684fe94 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/MoveConsoleWindow.cc @@ -0,0 +1,34 @@ +#include + +#include "TestUtil.cc" + +int main(int argc, char *argv[]) { + if (argc != 3 && argc != 5) { + printf("Usage: %s x y\n", argv[0]); + printf("Usage: %s x y width height\n", argv[0]); + return 1; + } + + HWND hwnd = GetConsoleWindow(); + + const int x = atoi(argv[1]); + const int y = atoi(argv[2]); + + int w = 0, h = 0; + if (argc == 3) { + RECT r = {}; + BOOL ret = GetWindowRect(hwnd, &r); + ASSERT(ret && "GetWindowRect failed on console window"); + w = r.right - r.left; + h = r.bottom - r.top; + } else { + w = atoi(argv[3]); + h = atoi(argv[4]); + } + + BOOL ret = MoveWindow(hwnd, x, y, w, h, TRUE); + trace("MoveWindow: ret=%d", ret); + printf("MoveWindow: ret=%d\n", ret); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/Notes.txt b/src/libs/3rdparty/winpty/misc/Notes.txt new file mode 100644 index 0000000000..410e184198 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Notes.txt @@ -0,0 +1,219 @@ +Test programs +------------- + +Cygwin + emacs + vim + mc (Midnight Commander) + lynx + links + less + more + wget + +Capturing the console output +---------------------------- + +Initial idea: + +In the agent, keep track of the remote terminal state for N lines of +(window+history). Also keep track of the terminal size. Regularly poll for +changes to the console screen buffer, then use some number of edits to bring +the remote terminal into sync with the console. + +This idea seems to have trouble when a Unix terminal is resized. When the +server receives a resize notification, it can have a hard time figuring out +what the terminal did. Race conditions might also be a problem. + +The behavior of the terminal can be tricky: + + - When the window is expanded by one line, does the terminal add a blank line + to the bottom or move a line from the history into the top? + + - When the window is shrunk by one line, does the terminal delete the topmost + or the bottommost line? Can it delete the line with the cursor? + +Some popular behaviors for expanding: + - [all] If there are no history lines, then add a line at the bottom. + - [konsole] Always add a line at the bottom. + - [putty,xterm,rxvt] Pull in a history line from the top. + - [g-t] I can't tell. It seems to add a blank line, until the program writes + to stdout or until I click the scroll bar, then the output "snaps" back down, + pulling lines out of the history. I thought I saw different behavior + between Ubuntu 10.10 and 11.10, so maybe GNOME 3 changed something. Avoid + using "bash" to test this behavior because "bash" apparently always writes + the prompt after terminal resize. + +Some popular behaviors for shrinking: + - [konsole,putty,xterm,rxvt] If the line at the bottom is blank, then delete + it. Otherwise, move the topmost line into history. + - [g-t] If the line at the bottom has not been touched, then delete it. + Otherwise, move the topmost line into history. + +(TODO: I need to test my theories about the terminal behavior better still. +It's interesting to see how g-t handles clear differently than every other +terminal.) + +There is an ANSI escape sequence (DSR) that sends the current cursor location +to the terminal's input. One idea I had was to use this code to figure out how +the terminal had handled a resize. I currently think this idea won't work due +to race conditions. + +Newer idea: + +Keep track of the last N lines that have been sent to the remote terminal. +Poll for changes to console output. When the output changes, send just the +changed content to the terminal. In particular: + - Don't send a cursor position (CUP) code. Instead, if the line that's 3 + steps up from the latest line changes, send a relative cursor up (CUU) + code. It's OK to send an absolute column number code (CHA). + - At least in general, don't try to send complete screenshots of the current + console window. + +The idea is that sending just the changes should have good behavior for streams +of output, even when those streams modify the output (e.g. an archiver, or +maybe a downloader/packager/wget). I need to think about whether this works +for full-screen programs (e.g. emacs, less, lynx, the above list of programs). + +I noticed that console programs don't typically modify the window or buffer +coordinates. edit.com is an exception. + +I tested the pager in native Python (more?), and I verified that ENTER and SPACE +both paid no attention to the location of the console window within the screen +buffer. This makes sense -- why would they care? The Cygwin less, on the other +hand, does care. If I scroll the window up, then Cygwin less will write to a +position within the window. I didn't really expect this behavior, but it +doesn't seem to be a problem. + +Setting up a TestNetServer service +---------------------------------- + +First run the deploy.sh script to copy files into deploy. Make sure +TestNetServer.exe will run in a bare environment (no MinGW or Qt in the path). + +Install the Windows Server 2003 Resource Kit. It will have two programs in it, +instsrv and srvany. + +Run: + + InstSrv TestNetServer \srvany.exe + +This creates a service named "TestNetServer" that uses the Microsoft service +wrapper. To configure the new service to run TestNetServer, set a registry +value: + + [HKLM\SYSTEM\CurrentControlSet\Services\TestNetServer\Parameters] + Application=\TestNetServer.exe + +Also see http://www.iopus.com/guides/srvany.htm. + +To remove the service, run: + + InstSrv TestNetServer REMOVE + +TODO +---- + +Agent: When resizing the console, consider whether to add lines to the top +or bottom. I remember thinking the current behavior was wrong for some +application, but I forgot which one. + +Make the font as small as possible. The console window dimensions are limited by +the screen size, so making the font small reduces an unnecessary limitation on the +PseudoConsole size. There's a documented Vista/Win7 API for this +(SetCurrentConsoleFontEx), and apparently WinXP has an undocumented API +(SetConsoleFont): + http://blogs.microsoft.co.il/blogs/pavely/archive/2009/07/23/changing-console-fonts.aspx + +Make the agent work with DOS programs like edit and qbasic. + - Detect that the terminal program has resized the window/buffer and enter a + simple just-scrape-and-dont-resize mode. Track the client window size and + send the intersection of the console and the agent's client. + - I also need to generate keyboard scan codes. + - Solve the NTVDM.EXE console shutdown problem, probably by ignoring NTVDM.EXE + when it appears on the GetConsoleProcessList list. + +Rename the agent? Is the term "proxy" more accurate? + +Optimize the polling. e.g. Use a longer poll interval when the console is idle. +Do a minimal poll that checks whether the sync marker or window has moved. + +Increase the console buffer size to ~9000 lines. Beware making it so big that +reading the sync column exhausts the 32KB conhost<->agent heap. + +Reduce the memory overhead of the agent. The agent's m_bufferData array can +be small (a few hundred lines?) relative to the console buffer size. + +Try to handle console background color better. + Unix terminal emulators have a user-configurable foreground and background +color, and for best results, the agent really needs to avoid changing the colors, +especially the background color. It's undesirable/ugly to SSH into a machine +and see the command prompt change the colors. It's especially ugly that the +terminal retains its original colors and only drawn cells get the new colors. +(e.g. Resizing the window to the right uses the local terminal colors rather +than the remote colors.) It's especially ugly in gnome-terminal, which draws +user-configurable black as black, but VT100 black as dark-gray. + If there were a way to query the terminal emulator's colors, then I could +match the console's colors to the terminal and everything would just work. As +far as I know, that's not possible. + I thought of a kludge that might work. Instead of translating console white +and black to VT/100 white and black, I would translate them to "reset" and +"invert". I'd translate other colors normally. This approach should produce +ideal results for command-line work and tolerable results for full-screen +programs without configuration. Configuring the agent for black-on-white or +white-on-black would produce ideal results in all situations. + This kludge only really applies to the SSH application. For a Win32 Konsole +application, it should be easy to get the colors right all the time. + +Try using the screen reader API: + - To eliminate polling. + - To detect when a line wraps. When a line wraps, it'd be nice not to send a + CRLF to the terminal emulator so copy-and-paste works better. + - To detect hard tabs with Cygwin. + +Implement VT100/ANSI escape sequence recognition for input. Decide where this +functionality belongs. PseudoConsole.dll? Disambiguating ESC from an escape +sequence might be tricky. For the SSH server, I was thinking that when a small +SSH payload ended with an ESC character, I could assume the character was really +an ESC keypress, on the assumption that if it were an escape sequence, the +payload would probably contain the whole sequence. I'm not sure this works, +especially if there's a lot of other traffic multiplexed on the SSH socket. + +Support Unicode. + - Some DOS programs draw using line/box characters. Can these characters be + translated to the Unicode equivalents? + +Create automated tests. + +Experiment with the Terminator emulator, an emulator that doesn't wrap lines. +How many columns does it report having? What column does it report the cursor +in as it's writing past the right end of the window? Will Terminator be a +problem if I implement line wrapping detection in the agent? + +BUG: After the unix-adapter/pconsole.exe program exits, the blinking cursor is +replaced with a hidden cursor. + +Fix assert() in the agent. If it fails, the failure message needs to be +reported somewhere. Pop up a dialog box? Maybe switch the active desktop, +then show a dialog box? + +TODO: There's already a pconsole project on GitHub. Maybe rename this project +to something else? winpty? + +TODO: Can the DebugServer system be replaced with OutputDebugString? How +do we decide whose processes' output to collect? + +TODO: Three executables: + build/winpty-agent.exe + build/winpty.dll + build/console.exe + +BUG: Run the pconsole.exe inside another console. As I type dir, I see this: + D:\rprichard\pconsole> + D:\rprichard\pconsole>d + D:\rprichard\pconsole>di + D:\rprichard\pconsole>dir + In the output of "dir", every other line is blank. + There was a bug in Terminal::sendLine that was causing this to happen + frequently. Now that I fixed it, this bug should only manifest on lines + whose last column is not a space (i.e. a full line). diff --git a/src/libs/3rdparty/winpty/misc/OSVersion.cc b/src/libs/3rdparty/winpty/misc/OSVersion.cc new file mode 100644 index 0000000000..456708f05b --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/OSVersion.cc @@ -0,0 +1,27 @@ +#include + +#include +#include +#include + +#include + +int main() { + setlocale(LC_ALL, ""); + + OSVERSIONINFOEXW info = {0}; + info.dwOSVersionInfoSize = sizeof(info); + assert(GetVersionExW((OSVERSIONINFOW*)&info)); + + printf("dwMajorVersion = %d\n", (int)info.dwMajorVersion); + printf("dwMinorVersion = %d\n", (int)info.dwMinorVersion); + printf("dwBuildNumber = %d\n", (int)info.dwBuildNumber); + printf("dwPlatformId = %d\n", (int)info.dwPlatformId); + printf("szCSDVersion = %ls\n", info.szCSDVersion); + printf("wServicePackMajor = %d\n", info.wServicePackMajor); + printf("wServicePackMinor = %d\n", info.wServicePackMinor); + printf("wSuiteMask = 0x%x\n", (unsigned int)info.wSuiteMask); + printf("wProductType = 0x%x\n", (unsigned int)info.wProductType); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/ScreenBufferFreezeInactive.cc b/src/libs/3rdparty/winpty/misc/ScreenBufferFreezeInactive.cc new file mode 100644 index 0000000000..656d4f126d --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/ScreenBufferFreezeInactive.cc @@ -0,0 +1,101 @@ +// +// Verify that console selection blocks writes to an inactive console screen +// buffer. Writes TEST PASSED or TEST FAILED to the popup console window. +// + +#include +#include + +#include + +#include "TestUtil.cc" + +const int SC_CONSOLE_MARK = 0xFFF2; +const int SC_CONSOLE_SELECT_ALL = 0xFFF5; + +bool g_useMark = false; + +CALLBACK DWORD pausingThread(LPVOID dummy) +{ + HWND hwnd = GetConsoleWindow(); + trace("Sending selection to freeze"); + SendMessage(hwnd, WM_SYSCOMMAND, + g_useMark ? SC_CONSOLE_MARK : + SC_CONSOLE_SELECT_ALL, + 0); + Sleep(1000); + trace("Sending escape WM_CHAR to unfreeze"); + SendMessage(hwnd, WM_CHAR, 27, 0x00010001); + Sleep(1000); +} + +static HANDLE createBuffer() { + HANDLE buf = CreateConsoleScreenBuffer( + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + CONSOLE_TEXTMODE_BUFFER, + NULL); + ASSERT(buf != INVALID_HANDLE_VALUE); + return buf; +} + +static void runTest(bool useMark, bool createEarly) { + trace("======================================="); + trace("useMark=%d createEarly=%d", useMark, createEarly); + g_useMark = useMark; + HANDLE buf = INVALID_HANDLE_VALUE; + + if (createEarly) { + buf = createBuffer(); + } + + CreateThread(NULL, 0, + pausingThread, NULL, + 0, NULL); + Sleep(500); + + if (!createEarly) { + trace("Creating buffer"); + TimeMeasurement tm1; + buf = createBuffer(); + const double elapsed1 = tm1.elapsed(); + if (elapsed1 >= 0.250) { + printf("!!! TEST FAILED !!!\n"); + Sleep(2000); + return; + } + } + + trace("Writing to aux buffer"); + TimeMeasurement tm2; + DWORD actual = 0; + BOOL ret = WriteConsoleW(buf, L"HI", 2, &actual, NULL); + const double elapsed2 = tm2.elapsed(); + trace("Writing to aux buffer: finished: ret=%d actual=%d (elapsed=%1.3f)", ret, actual, elapsed2); + if (elapsed2 < 0.250) { + printf("!!! TEST FAILED !!!\n"); + } else { + printf("TEST PASSED\n"); + } + Sleep(2000); +} + +int main(int argc, char **argv) { + if (argc == 1) { + startChildProcess(L"child"); + return 0; + } + + std::string arg = argv[1]; + if (arg == "child") { + for (int useMark = 0; useMark <= 1; useMark++) { + for (int createEarly = 0; createEarly <= 1; createEarly++) { + runTest(useMark, createEarly); + } + } + printf("done...\n"); + Sleep(1000); + } + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/ScreenBufferTest.cc b/src/libs/3rdparty/winpty/misc/ScreenBufferTest.cc new file mode 100644 index 0000000000..fa584b9fae --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/ScreenBufferTest.cc @@ -0,0 +1,671 @@ +// +// Windows versions tested +// +// Vista Enterprise SP2 32-bit +// - ver reports [Version 6.0.6002] +// - kernel32.dll product/file versions are 6.0.6002.19381 +// +// Windows 7 Ultimate SP1 32-bit +// - ver reports [Version 6.1.7601] +// - conhost.exe product/file versions are 6.1.7601.18847 +// - kernel32.dll product/file versions are 6.1.7601.18847 +// +// Windows Server 2008 R2 Datacenter SP1 64-bit +// - ver reports [Version 6.1.7601] +// - conhost.exe product/file versions are 6.1.7601.23153 +// - kernel32.dll product/file versions are 6.1.7601.23153 +// +// Windows 8 Enterprise 32-bit +// - ver reports [Version 6.2.9200] +// - conhost.exe product/file versions are 6.2.9200.16578 +// - kernel32.dll product/file versions are 6.2.9200.16859 +// + +// +// Specific version details on working Server 2008 R2: +// +// dwMajorVersion = 6 +// dwMinorVersion = 1 +// dwBuildNumber = 7601 +// dwPlatformId = 2 +// szCSDVersion = Service Pack 1 +// wServicePackMajor = 1 +// wServicePackMinor = 0 +// wSuiteMask = 0x190 +// wProductType = 0x3 +// +// Specific version details on broken Win7: +// +// dwMajorVersion = 6 +// dwMinorVersion = 1 +// dwBuildNumber = 7601 +// dwPlatformId = 2 +// szCSDVersion = Service Pack 1 +// wServicePackMajor = 1 +// wServicePackMinor = 0 +// wSuiteMask = 0x100 +// wProductType = 0x1 +// + +#include +#include +#include + +#include "TestUtil.cc" + +const char *g_prefix = ""; + +static void dumpHandles() { + trace("%sSTDIN=0x%I64x STDOUT=0x%I64x STDERR=0x%I64x", + g_prefix, + (long long)GetStdHandle(STD_INPUT_HANDLE), + (long long)GetStdHandle(STD_OUTPUT_HANDLE), + (long long)GetStdHandle(STD_ERROR_HANDLE)); +} + +static const char *successOrFail(BOOL ret) { + return ret ? "ok" : "FAILED"; +} + +static void startChildInSameConsole(const wchar_t *args, BOOL + bInheritHandles=FALSE) { + wchar_t program[1024]; + wchar_t cmdline[1024]; + GetModuleFileNameW(NULL, program, 1024); + swprintf(cmdline, L"\"%ls\" %ls", program, args); + + STARTUPINFOW sui; + PROCESS_INFORMATION pi; + memset(&sui, 0, sizeof(sui)); + memset(&pi, 0, sizeof(pi)); + sui.cb = sizeof(sui); + + CreateProcessW(program, cmdline, + NULL, NULL, + /*bInheritHandles=*/bInheritHandles, + /*dwCreationFlags=*/0, + NULL, NULL, + &sui, &pi); +} + +static void closeHandle(HANDLE h) { + trace("%sClosing handle 0x%I64x...", g_prefix, (long long)h); + trace("%sClosing handle 0x%I64x... %s", g_prefix, (long long)h, successOrFail(CloseHandle(h))); +} + +static HANDLE createBuffer() { + + // If sa isn't provided, the handle defaults to not-inheritable. + SECURITY_ATTRIBUTES sa = {0}; + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + + trace("%sCreating a new buffer...", g_prefix); + HANDLE conout = CreateConsoleScreenBuffer( + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &sa, + CONSOLE_TEXTMODE_BUFFER, NULL); + + trace("%sCreating a new buffer... 0x%I64x", g_prefix, (long long)conout); + return conout; +} + +static HANDLE openConout() { + + // If sa isn't provided, the handle defaults to not-inheritable. + SECURITY_ATTRIBUTES sa = {0}; + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + + trace("%sOpening CONOUT...", g_prefix); + HANDLE conout = CreateFileW(L"CONOUT$", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &sa, + OPEN_EXISTING, 0, NULL); + trace("%sOpening CONOUT... 0x%I64x", g_prefix, (long long)conout); + return conout; +} + +static void setConsoleActiveScreenBuffer(HANDLE conout) { + trace("%sSetConsoleActiveScreenBuffer(0x%I64x) called...", + g_prefix, (long long)conout); + trace("%sSetConsoleActiveScreenBuffer(0x%I64x) called... %s", + g_prefix, (long long)conout, + successOrFail(SetConsoleActiveScreenBuffer(conout))); +} + +static void writeTest(HANDLE conout, const char *msg) { + char writeData[256]; + sprintf(writeData, "%s%s\n", g_prefix, msg); + + trace("%sWriting to 0x%I64x: '%s'...", + g_prefix, (long long)conout, msg); + DWORD actual = 0; + BOOL ret = WriteConsoleA(conout, writeData, strlen(writeData), &actual, NULL); + trace("%sWriting to 0x%I64x: '%s'... %s", + g_prefix, (long long)conout, msg, + successOrFail(ret && actual == strlen(writeData))); +} + +static void writeTest(const char *msg) { + writeTest(GetStdHandle(STD_OUTPUT_HANDLE), msg); +} + + + +/////////////////////////////////////////////////////////////////////////////// +// TEST 1 -- create new buffer, activate it, and close the handle. The console +// automatically switches the screen buffer back to the original. +// +// This test passes everywhere. +// + +static void test1(int argc, char *argv[]) { + if (!strcmp(argv[1], "1")) { + startChildProcess(L"1:child"); + return; + } + + HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE); + writeTest(origBuffer, "<-- origBuffer -->"); + + HANDLE newBuffer = createBuffer(); + writeTest(newBuffer, "<-- newBuffer -->"); + setConsoleActiveScreenBuffer(newBuffer); + Sleep(2000); + + writeTest(origBuffer, "TEST PASSED!"); + + // Closing the handle w/o switching the active screen buffer automatically + // switches the console back to the original buffer. + closeHandle(newBuffer); + + while (true) { + Sleep(1000); + } +} + + + +/////////////////////////////////////////////////////////////////////////////// +// TEST 2 -- Test program that creates and activates newBuffer, starts a child +// process, then closes its newBuffer handle. newBuffer remains activated, +// because the child keeps it active. (Also see TEST D.) +// + +static void test2(int argc, char *argv[]) { + if (!strcmp(argv[1], "2")) { + startChildProcess(L"2:parent"); + return; + } + + if (!strcmp(argv[1], "2:parent")) { + g_prefix = "parent: "; + dumpHandles(); + HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE); + writeTest(origBuffer, "<-- origBuffer -->"); + + HANDLE newBuffer = createBuffer(); + writeTest(newBuffer, "<-- newBuffer -->"); + setConsoleActiveScreenBuffer(newBuffer); + + Sleep(1000); + writeTest(newBuffer, "bInheritHandles=FALSE:"); + startChildInSameConsole(L"2:child", FALSE); + Sleep(1000); + writeTest(newBuffer, "bInheritHandles=TRUE:"); + startChildInSameConsole(L"2:child", TRUE); + + Sleep(1000); + trace("parent:----"); + + // Close the new buffer. The active screen buffer doesn't automatically + // switch back to origBuffer, because the child process has a handle open + // to the original buffer. + closeHandle(newBuffer); + + Sleep(600 * 1000); + return; + } + + if (!strcmp(argv[1], "2:child")) { + g_prefix = "child: "; + dumpHandles(); + // The child's output isn't visible, because it's still writing to + // origBuffer. + trace("child:----"); + writeTest("writing to STDOUT"); + + // Handle inheritability is curious. The console handles this program + // creates are inheritable, but CreateProcess is called with both + // bInheritHandles=TRUE and bInheritHandles=FALSE. + // + // Vista and Windows 7: bInheritHandles has no effect. The child and + // parent processes have the same STDIN/STDOUT/STDERR handles: + // 0x3, 0x7, and 0xB. The parent has a 0xF handle for newBuffer. + // The child can only write to 0x7, 0xB, and 0xF. Only the writes to + // 0xF are visible (i.e. they touch newBuffer). + // + // Windows 8 or Windows 10 (legacy or non-legacy): the lowest 2 bits of + // the HANDLE to WriteConsole seem to be ignored. The new process' + // console handles always refer to the buffer that was active when they + // started, but the values of the handles depend upon bInheritHandles. + // With bInheritHandles=TRUE, the child has the same + // STDIN/STDOUT/STDERR/newBuffer handles as the parent, and the three + // output handles all work, though their output is all visible. With + // bInheritHandles=FALSE, the child has different STDIN/STDOUT/STDERR + // handles, and only the new STDOUT/STDERR handles work. + // + for (unsigned int i = 0x1; i <= 0xB0; ++i) { + char msg[256]; + sprintf(msg, "Write to handle 0x%x", i); + HANDLE h = reinterpret_cast(i); + writeTest(h, msg); + } + + Sleep(600 * 1000); + return; + } +} + + + +/////////////////////////////////////////////////////////////////////////////// +// TEST A -- demonstrate an apparent Windows bug with screen buffers +// +// Steps: +// - The parent starts a child process. +// - The child process creates and activates newBuffer +// - The parent opens CONOUT$ and writes to it. +// - The parent closes CONOUT$. +// - At this point, broken Windows reactivates origBuffer. +// - The child writes to newBuffer again. +// - The child activates origBuffer again, then closes newBuffer. +// +// Test passes if the message "TEST PASSED!" is visible. +// Test commonly fails if conhost.exe crashes. +// +// Results: +// - Windows 7 Ultimate SP1 32-bit: conhost.exe crashes +// - Windows Server 2008 R2 Datacenter SP1 64-bit: PASS +// - Windows 8 Enterprise 32-bit: PASS +// - Windows 10 64-bit (legacy and non-legacy): PASS +// + +static void testA_parentWork() { + // Open an extra CONOUT$ handle so that the HANDLE values in parent and + // child don't collide. I think it's OK if they collide, but since we're + // trying to track down a Windows bug, it's best to avoid unnecessary + // complication. + HANDLE dummy = openConout(); + + Sleep(3000); + + // Step 2: Open CONOUT$ in the parent. This opens the active buffer, which + // was just created in the child. It's handle 0x13. Write to it. + + HANDLE newBuffer = openConout(); + writeTest(newBuffer, "step2: writing to newBuffer"); + + Sleep(3000); + + // Step 3: Close handle 0x13. With Windows 7, the console switches back to + // origBuffer, and (unless I'm missing something) it shouldn't. + + closeHandle(newBuffer); +} + +static void testA_childWork() { + HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE); + + // + // Step 1: Create the new screen buffer in the child process and make it + // active. (Typically, it's handle 0x0F.) + // + + HANDLE newBuffer = createBuffer(); + + setConsoleActiveScreenBuffer(newBuffer); + writeTest(newBuffer, "<-- newBuffer -->"); + + Sleep(9000); + trace("child:----"); + + // Step 4: write to the newBuffer again. + writeTest(newBuffer, "TEST PASSED!"); + + // + // Step 5: Switch back to the original screen buffer and close the new + // buffer. The switch call succeeds, but the CloseHandle call freezes for + // several seconds, because conhost.exe crashes. + // + Sleep(3000); + + setConsoleActiveScreenBuffer(origBuffer); + writeTest(origBuffer, "writing to origBuffer"); + + closeHandle(newBuffer); + + // The console HWND is NULL. + trace("child: console HWND=0x%I64x", (long long)GetConsoleWindow()); + + // At this point, the console window has closed, but the parent/child + // processes are still running. Calling AllocConsole would fail, but + // calling FreeConsole followed by AllocConsole would both succeed, and a + // new console would appear. +} + +static void testA(int argc, char *argv[]) { + + if (!strcmp(argv[1], "A")) { + startChildProcess(L"A:parent"); + return; + } + + if (!strcmp(argv[1], "A:parent")) { + g_prefix = "parent: "; + trace("parent:----"); + dumpHandles(); + writeTest("<-- origBuffer -->"); + startChildInSameConsole(L"A:child"); + testA_parentWork(); + Sleep(120000); + return; + } + + if (!strcmp(argv[1], "A:child")) { + g_prefix = "child: "; + dumpHandles(); + testA_childWork(); + Sleep(120000); + return; + } +} + + + +/////////////////////////////////////////////////////////////////////////////// +// TEST B -- invert TEST A -- also crashes conhost on Windows 7 +// +// Test passes if the message "TEST PASSED!" is visible. +// Test commonly fails if conhost.exe crashes. +// +// Results: +// - Windows 7 Ultimate SP1 32-bit: conhost.exe crashes +// - Windows Server 2008 R2 Datacenter SP1 64-bit: PASS +// - Windows 8 Enterprise 32-bit: PASS +// - Windows 10 64-bit (legacy and non-legacy): PASS +// + +static void testB(int argc, char *argv[]) { + if (!strcmp(argv[1], "B")) { + startChildProcess(L"B:parent"); + return; + } + + if (!strcmp(argv[1], "B:parent")) { + g_prefix = "parent: "; + startChildInSameConsole(L"B:child"); + writeTest("<-- origBuffer -->"); + HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE); + + // + // Step 1: Create the new buffer and make it active. + // + trace("%s----", g_prefix); + HANDLE newBuffer = createBuffer(); + setConsoleActiveScreenBuffer(newBuffer); + writeTest(newBuffer, "<-- newBuffer -->"); + + // + // Step 4: Attempt to write again to the new buffer. + // + Sleep(9000); + trace("%s----", g_prefix); + writeTest(newBuffer, "TEST PASSED!"); + + // + // Step 5: Switch back to the original buffer. + // + Sleep(3000); + trace("%s----", g_prefix); + setConsoleActiveScreenBuffer(origBuffer); + closeHandle(newBuffer); + writeTest(origBuffer, "writing to the initial buffer"); + + Sleep(60000); + return; + } + + if (!strcmp(argv[1], "B:child")) { + g_prefix = "child: "; + Sleep(3000); + trace("%s----", g_prefix); + + // + // Step 2: Open the newly active buffer and write to it. + // + HANDLE newBuffer = openConout(); + writeTest(newBuffer, "writing to newBuffer"); + + // + // Step 3: Close the newly active buffer. + // + Sleep(3000); + closeHandle(newBuffer); + + Sleep(60000); + return; + } +} + + + +/////////////////////////////////////////////////////////////////////////////// +// TEST C -- Interleaving open/close of console handles also seems to break on +// Windows 7. +// +// Test: +// - child creates and activates newBuf1 +// - parent opens newBuf1 +// - child creates and activates newBuf2 +// - parent opens newBuf2, then closes newBuf1 +// - child switches back to newBuf1 +// * At this point, the console starts malfunctioning. +// - parent and child close newBuf2 +// - child closes newBuf1 +// +// Test passes if the message "TEST PASSED!" is visible. +// Test commonly fails if conhost.exe crashes. +// +// Results: +// - Windows 7 Ultimate SP1 32-bit: conhost.exe crashes +// - Windows Server 2008 R2 Datacenter SP1 64-bit: PASS +// - Windows 8 Enterprise 32-bit: PASS +// - Windows 10 64-bit (legacy and non-legacy): PASS +// + +static void testC(int argc, char *argv[]) { + if (!strcmp(argv[1], "C")) { + startChildProcess(L"C:parent"); + return; + } + + if (!strcmp(argv[1], "C:parent")) { + startChildInSameConsole(L"C:child"); + writeTest("<-- origBuffer -->"); + g_prefix = "parent: "; + + // At time=4, open newBuffer1. + Sleep(4000); + trace("%s---- t=4", g_prefix); + const HANDLE newBuffer1 = openConout(); + + // At time=8, open newBuffer2, and close newBuffer1. + Sleep(4000); + trace("%s---- t=8", g_prefix); + const HANDLE newBuffer2 = openConout(); + closeHandle(newBuffer1); + + // At time=25, cleanup of newBuffer2. + Sleep(17000); + trace("%s---- t=25", g_prefix); + closeHandle(newBuffer2); + + Sleep(240000); + return; + } + + if (!strcmp(argv[1], "C:child")) { + g_prefix = "child: "; + + // At time=2, create newBuffer1 and activate it. + Sleep(2000); + trace("%s---- t=2", g_prefix); + const HANDLE newBuffer1 = createBuffer(); + setConsoleActiveScreenBuffer(newBuffer1); + writeTest(newBuffer1, "<-- newBuffer1 -->"); + + // At time=6, create newBuffer2 and activate it. + Sleep(4000); + trace("%s---- t=6", g_prefix); + const HANDLE newBuffer2 = createBuffer(); + setConsoleActiveScreenBuffer(newBuffer2); + writeTest(newBuffer2, "<-- newBuffer2 -->"); + + // At time=10, attempt to switch back to newBuffer1. The parent process + // has opened and closed its handle to newBuffer1, so does it still exist? + Sleep(4000); + trace("%s---- t=10", g_prefix); + setConsoleActiveScreenBuffer(newBuffer1); + writeTest(newBuffer1, "write to newBuffer1: TEST PASSED!"); + + // At time=25, cleanup of newBuffer2. + Sleep(15000); + trace("%s---- t=25", g_prefix); + closeHandle(newBuffer2); + + // At time=35, cleanup of newBuffer1. The console should switch to the + // initial buffer again. + Sleep(10000); + trace("%s---- t=35", g_prefix); + closeHandle(newBuffer1); + + Sleep(240000); + return; + } +} + + + +/////////////////////////////////////////////////////////////////////////////// +// TEST D -- parent creates a new buffer, child launches, writes, +// closes it output handle, then parent writes again. (Also see TEST 2.) +// +// On success, this will appear: +// +// parent: <-- newBuffer --> +// child: writing to newBuffer +// parent: TEST PASSED! +// +// If this appears, it indicates that the child's closing its output handle did +// not destroy newBuffer. +// +// Results: +// - Windows 7 Ultimate SP1 32-bit: PASS +// - Windows 8 Enterprise 32-bit: PASS +// - Windows 10 64-bit (legacy and non-legacy): PASS +// + +static void testD(int argc, char *argv[]) { + if (!strcmp(argv[1], "D")) { + startChildProcess(L"D:parent"); + return; + } + + if (!strcmp(argv[1], "D:parent")) { + g_prefix = "parent: "; + HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE); + writeTest(origBuffer, "<-- origBuffer -->"); + + HANDLE newBuffer = createBuffer(); + writeTest(newBuffer, "<-- newBuffer -->"); + setConsoleActiveScreenBuffer(newBuffer); + + // At t=2, start a child process, explicitly forcing it to use + // newBuffer for its standard handles. These calls are apparently + // redundant on Windows 8 and up. + Sleep(2000); + trace("parent:----"); + trace("parent: starting child process"); + SetStdHandle(STD_OUTPUT_HANDLE, newBuffer); + SetStdHandle(STD_ERROR_HANDLE, newBuffer); + startChildInSameConsole(L"D:child"); + SetStdHandle(STD_OUTPUT_HANDLE, origBuffer); + SetStdHandle(STD_ERROR_HANDLE, origBuffer); + + // At t=6, write again to newBuffer. + Sleep(4000); + trace("parent:----"); + writeTest(newBuffer, "TEST PASSED!"); + + // At t=8, close the newBuffer. In earlier versions of windows + // (including Server 2008 R2), the console then switches back to + // origBuffer. As of Windows 8, it doesn't, because somehow the child + // process is keeping the console on newBuffer, even though the child + // process closed its STDIN/STDOUT/STDERR handles. Killing the child + // process by hand after the test finishes *does* force the console + // back to origBuffer. + Sleep(2000); + closeHandle(newBuffer); + + Sleep(120000); + return; + } + + if (!strcmp(argv[1], "D:child")) { + g_prefix = "child: "; + // At t=2, the child starts. + trace("child:----"); + dumpHandles(); + writeTest("writing to newBuffer"); + + // At t=4, the child explicitly closes its handle. + Sleep(2000); + trace("child:----"); + if (GetStdHandle(STD_ERROR_HANDLE) != GetStdHandle(STD_OUTPUT_HANDLE)) { + closeHandle(GetStdHandle(STD_ERROR_HANDLE)); + } + closeHandle(GetStdHandle(STD_OUTPUT_HANDLE)); + closeHandle(GetStdHandle(STD_INPUT_HANDLE)); + + Sleep(120000); + return; + } +} + + + +int main(int argc, char *argv[]) { + if (argc == 1) { + printf("USAGE: %s testnum\n", argv[0]); + return 0; + } + + if (argv[1][0] == '1') { + test1(argc, argv); + } else if (argv[1][0] == '2') { + test2(argc, argv); + } else if (argv[1][0] == 'A') { + testA(argc, argv); + } else if (argv[1][0] == 'B') { + testB(argc, argv); + } else if (argv[1][0] == 'C') { + testC(argc, argv); + } else if (argv[1][0] == 'D') { + testD(argc, argv); + } + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/ScreenBufferTest2.cc b/src/libs/3rdparty/winpty/misc/ScreenBufferTest2.cc new file mode 100644 index 0000000000..2b648c9409 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/ScreenBufferTest2.cc @@ -0,0 +1,151 @@ +#include + +#include "TestUtil.cc" + +const char *g_prefix = ""; + +static void dumpHandles() { + trace("%sSTDIN=0x%I64x STDOUT=0x%I64x STDERR=0x%I64x", + g_prefix, + (long long)GetStdHandle(STD_INPUT_HANDLE), + (long long)GetStdHandle(STD_OUTPUT_HANDLE), + (long long)GetStdHandle(STD_ERROR_HANDLE)); +} + +static HANDLE createBuffer() { + + // If sa isn't provided, the handle defaults to not-inheritable. + SECURITY_ATTRIBUTES sa = {0}; + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + + trace("%sCreating a new buffer...", g_prefix); + HANDLE conout = CreateConsoleScreenBuffer( + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &sa, + CONSOLE_TEXTMODE_BUFFER, NULL); + + trace("%sCreating a new buffer... 0x%I64x", g_prefix, (long long)conout); + return conout; +} + +static const char *successOrFail(BOOL ret) { + return ret ? "ok" : "FAILED"; +} + +static void setConsoleActiveScreenBuffer(HANDLE conout) { + trace("%sSetConsoleActiveScreenBuffer(0x%I64x) called...", + g_prefix, (long long)conout); + trace("%sSetConsoleActiveScreenBuffer(0x%I64x) called... %s", + g_prefix, (long long)conout, + successOrFail(SetConsoleActiveScreenBuffer(conout))); +} + +static void writeTest(HANDLE conout, const char *msg) { + char writeData[256]; + sprintf(writeData, "%s%s\n", g_prefix, msg); + + trace("%sWriting to 0x%I64x: '%s'...", + g_prefix, (long long)conout, msg); + DWORD actual = 0; + BOOL ret = WriteConsoleA(conout, writeData, strlen(writeData), &actual, NULL); + trace("%sWriting to 0x%I64x: '%s'... %s", + g_prefix, (long long)conout, msg, + successOrFail(ret && actual == strlen(writeData))); +} + +static HANDLE startChildInSameConsole(const wchar_t *args, BOOL + bInheritHandles=FALSE) { + wchar_t program[1024]; + wchar_t cmdline[1024]; + GetModuleFileNameW(NULL, program, 1024); + swprintf(cmdline, L"\"%ls\" %ls", program, args); + + STARTUPINFOW sui; + PROCESS_INFORMATION pi; + memset(&sui, 0, sizeof(sui)); + memset(&pi, 0, sizeof(pi)); + sui.cb = sizeof(sui); + + CreateProcessW(program, cmdline, + NULL, NULL, + /*bInheritHandles=*/bInheritHandles, + /*dwCreationFlags=*/0, + NULL, NULL, + &sui, &pi); + + return pi.hProcess; +} + +static HANDLE dup(HANDLE h, HANDLE targetProcess) { + HANDLE h2 = INVALID_HANDLE_VALUE; + BOOL ret = DuplicateHandle( + GetCurrentProcess(), h, + targetProcess, &h2, + 0, TRUE, DUPLICATE_SAME_ACCESS); + trace("dup(0x%I64x) to process 0x%I64x... %s, 0x%I64x", + (long long)h, + (long long)targetProcess, + successOrFail(ret), + (long long)h2); + return h2; +} + +int main(int argc, char *argv[]) { + if (argc == 1) { + startChildProcess(L"parent"); + return 0; + } + + if (!strcmp(argv[1], "parent")) { + g_prefix = "parent: "; + dumpHandles(); + HANDLE hChild = startChildInSameConsole(L"child"); + + // Windows 10. + HANDLE orig1 = GetStdHandle(STD_OUTPUT_HANDLE); + HANDLE new1 = createBuffer(); + + Sleep(2000); + setConsoleActiveScreenBuffer(new1); + + // Handle duplication results to child process in same console: + // - Windows XP: fails + // - Windows 7 Ultimate SP1 32-bit: fails + // - Windows Server 2008 R2 Datacenter SP1 64-bit: fails + // - Windows 8 Enterprise 32-bit: succeeds + // - Windows 10: succeeds + HANDLE orig2 = dup(orig1, GetCurrentProcess()); + HANDLE new2 = dup(new1, GetCurrentProcess()); + + dup(orig1, hChild); + dup(new1, hChild); + + // The writes to orig1/orig2 are invisible. The writes to new1/new2 + // are visible. + writeTest(orig1, "write to orig1"); + writeTest(orig2, "write to orig2"); + writeTest(new1, "write to new1"); + writeTest(new2, "write to new2"); + + Sleep(120000); + return 0; + } + + if (!strcmp(argv[1], "child")) { + g_prefix = "child: "; + dumpHandles(); + Sleep(4000); + for (unsigned int i = 0x1; i <= 0xB0; ++i) { + char msg[256]; + sprintf(msg, "Write to handle 0x%x", i); + HANDLE h = reinterpret_cast(i); + writeTest(h, msg); + } + Sleep(120000); + return 0; + } + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/SelectAllTest.cc b/src/libs/3rdparty/winpty/misc/SelectAllTest.cc new file mode 100644 index 0000000000..a6c27739d8 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/SelectAllTest.cc @@ -0,0 +1,45 @@ +#define _WIN32_WINNT 0x0501 +#include +#include + +#include "../src/shared/DebugClient.cc" + +const int SC_CONSOLE_MARK = 0xFFF2; +const int SC_CONSOLE_SELECT_ALL = 0xFFF5; + +CALLBACK DWORD pausingThread(LPVOID dummy) +{ + HWND hwnd = GetConsoleWindow(); + while (true) { + SendMessage(hwnd, WM_SYSCOMMAND, SC_CONSOLE_SELECT_ALL, 0); + Sleep(1000); + SendMessage(hwnd, WM_CHAR, 27, 0x00010001); + Sleep(1000); + } +} + +int main() +{ + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO info; + + GetConsoleScreenBufferInfo(out, &info); + COORD initial = info.dwCursorPosition; + + CreateThread(NULL, 0, + pausingThread, NULL, + 0, NULL); + + for (int i = 0; i < 30; ++i) { + Sleep(100); + GetConsoleScreenBufferInfo(out, &info); + if (memcmp(&info.dwCursorPosition, &initial, sizeof(COORD)) != 0) { + trace("cursor moved to [%d,%d]", + info.dwCursorPosition.X, + info.dwCursorPosition.Y); + } else { + trace("cursor in expected position"); + } + } + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/SetBufInfo.cc b/src/libs/3rdparty/winpty/misc/SetBufInfo.cc new file mode 100644 index 0000000000..f37c31bdf7 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/SetBufInfo.cc @@ -0,0 +1,90 @@ +#include + +#include +#include +#include + +#include "TestUtil.cc" + +static void usage() { + printf("usage: SetBufInfo [-set] [-buf W H] [-win W H] [-pos X Y]\n"); +} + +int main(int argc, char *argv[]) { + const HANDLE conout = CreateFileW(L"CONOUT$", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + ASSERT(conout != INVALID_HANDLE_VALUE); + + bool change = false; + BOOL success; + CONSOLE_SCREEN_BUFFER_INFOEX info = {}; + info.cbSize = sizeof(info); + + success = GetConsoleScreenBufferInfoEx(conout, &info); + ASSERT(success && "GetConsoleScreenBufferInfoEx failed"); + + for (int i = 1; i < argc; ) { + std::string arg = argv[i]; + if (arg == "-buf" && (i + 2) < argc) { + info.dwSize.X = atoi(argv[i + 1]); + info.dwSize.Y = atoi(argv[i + 2]); + i += 3; + change = true; + } else if (arg == "-pos" && (i + 2) < argc) { + int dx = info.srWindow.Right - info.srWindow.Left; + int dy = info.srWindow.Bottom - info.srWindow.Top; + info.srWindow.Left = atoi(argv[i + 1]); + info.srWindow.Top = atoi(argv[i + 2]); + i += 3; + info.srWindow.Right = info.srWindow.Left + dx; + info.srWindow.Bottom = info.srWindow.Top + dy; + change = true; + } else if (arg == "-win" && (i + 2) < argc) { + info.srWindow.Right = info.srWindow.Left + atoi(argv[i + 1]) - 1; + info.srWindow.Bottom = info.srWindow.Top + atoi(argv[i + 2]) - 1; + i += 3; + change = true; + } else if (arg == "-set") { + change = true; + ++i; + } else if (arg == "--help" || arg == "-help") { + usage(); + exit(0); + } else { + fprintf(stderr, "error: unrecognized argument: %s\n", arg.c_str()); + usage(); + exit(1); + } + } + + if (change) { + success = SetConsoleScreenBufferInfoEx(conout, &info); + if (success) { + printf("success\n"); + } else { + printf("SetConsoleScreenBufferInfoEx call failed\n"); + } + success = GetConsoleScreenBufferInfoEx(conout, &info); + ASSERT(success && "GetConsoleScreenBufferInfoEx failed"); + } + + auto dump = [](const char *fmt, ...) { + char msg[256]; + va_list ap; + va_start(ap, fmt); + vsprintf(msg, fmt, ap); + va_end(ap); + trace("%s", msg); + printf("%s\n", msg); + }; + + dump("buffer-size: %d x %d", info.dwSize.X, info.dwSize.Y); + dump("window-size: %d x %d", + info.srWindow.Right - info.srWindow.Left + 1, + info.srWindow.Bottom - info.srWindow.Top + 1); + dump("window-pos: %d, %d", info.srWindow.Left, info.srWindow.Top); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/SetBufferSize.cc b/src/libs/3rdparty/winpty/misc/SetBufferSize.cc new file mode 100644 index 0000000000..b50a1f8dc3 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/SetBufferSize.cc @@ -0,0 +1,32 @@ +#include + +#include +#include + +#include "TestUtil.cc" + +int main(int argc, char *argv[]) { + if (argc != 3) { + printf("Usage: %s x y width height\n", argv[0]); + return 1; + } + + const HANDLE conout = CreateFileW(L"CONOUT$", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + ASSERT(conout != INVALID_HANDLE_VALUE); + + COORD size = { + (short)atoi(argv[1]), + (short)atoi(argv[2]), + }; + + BOOL ret = SetConsoleScreenBufferSize(conout, size); + const unsigned lastError = GetLastError(); + const char *const retStr = ret ? "OK" : "failed"; + trace("SetConsoleScreenBufferSize ret: %s (LastError=0x%x)", retStr, lastError); + printf("SetConsoleScreenBufferSize ret: %s (LastError=0x%x)\n", retStr, lastError); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/SetCursorPos.cc b/src/libs/3rdparty/winpty/misc/SetCursorPos.cc new file mode 100644 index 0000000000..d20fdbdfc0 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/SetCursorPos.cc @@ -0,0 +1,10 @@ +#include + +#include "TestUtil.cc" + +int main(int argc, char *argv[]) { + int col = atoi(argv[1]); + int row = atoi(argv[2]); + setCursorPos(col, row); + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/SetFont.cc b/src/libs/3rdparty/winpty/misc/SetFont.cc new file mode 100644 index 0000000000..9bcd4b4cc9 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/SetFont.cc @@ -0,0 +1,145 @@ +#include +#include +#include +#include +#include + +#include "TestUtil.cc" + +#define COUNT_OF(array) (sizeof(array) / sizeof((array)[0])) + +// See https://en.wikipedia.org/wiki/List_of_CJK_fonts +const wchar_t kMSGothic[] = { 0xff2d, 0xff33, 0x0020, 0x30b4, 0x30b7, 0x30c3, 0x30af, 0 }; // Japanese +const wchar_t kNSimSun[] = { 0x65b0, 0x5b8b, 0x4f53, 0 }; // Simplified Chinese +const wchar_t kMingLight[] = { 0x7d30, 0x660e, 0x9ad4, 0 }; // Traditional Chinese +const wchar_t kGulimChe[] = { 0xad74, 0xb9bc, 0xccb4, 0 }; // Korean + +int main() { + setlocale(LC_ALL, ""); + wchar_t *cmdline = GetCommandLineW(); + int argc = 0; + wchar_t **argv = CommandLineToArgvW(cmdline, &argc); + const HANDLE conout = openConout(); + + if (argc == 1) { + cprintf(L"Usage:\n"); + cprintf(L" SetFont \n"); + cprintf(L" SetFont options\n"); + cprintf(L"\n"); + cprintf(L"Options for SetCurrentConsoleFontEx:\n"); + cprintf(L" -idx INDEX\n"); + cprintf(L" -w WIDTH\n"); + cprintf(L" -h HEIGHT\n"); + cprintf(L" -family (0xNN|NN)\n"); + cprintf(L" -weight (normal|bold|NNN)\n"); + cprintf(L" -face FACENAME\n"); + cprintf(L" -face-{gothic|simsun|minglight|gulimche) [JP,CN-sim,CN-tra,KR]\n"); + cprintf(L" -tt\n"); + cprintf(L" -vec\n"); + cprintf(L" -vp\n"); + cprintf(L" -dev\n"); + cprintf(L" -roman\n"); + cprintf(L" -swiss\n"); + cprintf(L" -modern\n"); + cprintf(L" -script\n"); + cprintf(L" -decorative\n"); + return 0; + } + + if (isdigit(argv[1][0])) { + int index = _wtoi(argv[1]); + HMODULE kernel32 = LoadLibraryW(L"kernel32.dll"); + FARPROC proc = GetProcAddress(kernel32, "SetConsoleFont"); + if (proc == NULL) { + cprintf(L"Couldn't get address of SetConsoleFont\n"); + } else { + BOOL ret = reinterpret_cast(proc)( + conout, index); + cprintf(L"SetFont returned %d\n", ret); + } + return 0; + } + + CONSOLE_FONT_INFOEX fontex = {0}; + fontex.cbSize = sizeof(fontex); + + for (int i = 1; i < argc; ++i) { + std::wstring arg = argv[i]; + if (i + 1 < argc) { + std::wstring next = argv[i + 1]; + if (arg == L"-idx") { + fontex.nFont = _wtoi(next.c_str()); + ++i; continue; + } else if (arg == L"-w") { + fontex.dwFontSize.X = _wtoi(next.c_str()); + ++i; continue; + } else if (arg == L"-h") { + fontex.dwFontSize.Y = _wtoi(next.c_str()); + ++i; continue; + } else if (arg == L"-weight") { + if (next == L"normal") { + fontex.FontWeight = 400; + } else if (next == L"bold") { + fontex.FontWeight = 700; + } else { + fontex.FontWeight = _wtoi(next.c_str()); + } + ++i; continue; + } else if (arg == L"-face") { + wcsncpy(fontex.FaceName, next.c_str(), COUNT_OF(fontex.FaceName)); + ++i; continue; + } else if (arg == L"-family") { + fontex.FontFamily = strtol(narrowString(next).c_str(), nullptr, 0); + ++i; continue; + } + } + if (arg == L"-tt") { + fontex.FontFamily |= TMPF_TRUETYPE; + } else if (arg == L"-vec") { + fontex.FontFamily |= TMPF_VECTOR; + } else if (arg == L"-vp") { + // Setting the TMPF_FIXED_PITCH bit actually indicates variable + // pitch. + fontex.FontFamily |= TMPF_FIXED_PITCH; + } else if (arg == L"-dev") { + fontex.FontFamily |= TMPF_DEVICE; + } else if (arg == L"-roman") { + fontex.FontFamily = (fontex.FontFamily & ~0xF0) | FF_ROMAN; + } else if (arg == L"-swiss") { + fontex.FontFamily = (fontex.FontFamily & ~0xF0) | FF_SWISS; + } else if (arg == L"-modern") { + fontex.FontFamily = (fontex.FontFamily & ~0xF0) | FF_MODERN; + } else if (arg == L"-script") { + fontex.FontFamily = (fontex.FontFamily & ~0xF0) | FF_SCRIPT; + } else if (arg == L"-decorative") { + fontex.FontFamily = (fontex.FontFamily & ~0xF0) | FF_DECORATIVE; + } else if (arg == L"-face-gothic") { + wcsncpy(fontex.FaceName, kMSGothic, COUNT_OF(fontex.FaceName)); + } else if (arg == L"-face-simsun") { + wcsncpy(fontex.FaceName, kNSimSun, COUNT_OF(fontex.FaceName)); + } else if (arg == L"-face-minglight") { + wcsncpy(fontex.FaceName, kMingLight, COUNT_OF(fontex.FaceName)); + } else if (arg == L"-face-gulimche") { + wcsncpy(fontex.FaceName, kGulimChe, COUNT_OF(fontex.FaceName)); + } else { + cprintf(L"Unrecognized argument: %ls\n", arg.c_str()); + exit(1); + } + } + + cprintf(L"Setting to: nFont=%u dwFontSize=(%d,%d) " + L"FontFamily=0x%x FontWeight=%u " + L"FaceName=\"%ls\"\n", + static_cast(fontex.nFont), + fontex.dwFontSize.X, fontex.dwFontSize.Y, + fontex.FontFamily, fontex.FontWeight, + fontex.FaceName); + + BOOL ret = SetCurrentConsoleFontEx( + conout, + FALSE, + &fontex); + cprintf(L"SetCurrentConsoleFontEx returned %d\n", ret); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/SetWindowRect.cc b/src/libs/3rdparty/winpty/misc/SetWindowRect.cc new file mode 100644 index 0000000000..6291dd6745 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/SetWindowRect.cc @@ -0,0 +1,36 @@ +#include + +#include +#include + +#include "TestUtil.cc" + +int main(int argc, char *argv[]) { + if (argc != 5) { + printf("Usage: %s x y width height\n", argv[0]); + return 1; + } + + const HANDLE conout = CreateFileW(L"CONOUT$", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + ASSERT(conout != INVALID_HANDLE_VALUE); + + SMALL_RECT sr = { + (short)atoi(argv[1]), + (short)atoi(argv[2]), + (short)(atoi(argv[1]) + atoi(argv[3]) - 1), + (short)(atoi(argv[2]) + atoi(argv[4]) - 1), + }; + + trace("Calling SetConsoleWindowInfo with {L=%d,T=%d,R=%d,B=%d}", + sr.Left, sr.Top, sr.Right, sr.Bottom); + BOOL ret = SetConsoleWindowInfo(conout, TRUE, &sr); + const unsigned lastError = GetLastError(); + const char *const retStr = ret ? "OK" : "failed"; + trace("SetConsoleWindowInfo ret: %s (LastError=0x%x)", retStr, lastError); + printf("SetConsoleWindowInfo ret: %s (LastError=0x%x)\n", retStr, lastError); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/ShowArgv.cc b/src/libs/3rdparty/winpty/misc/ShowArgv.cc new file mode 100644 index 0000000000..29a0f09131 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/ShowArgv.cc @@ -0,0 +1,12 @@ +// This test program is useful for studying commandline<->argv conversion. + +#include +#include + +int main(int argc, char **argv) +{ + printf("cmdline = [%s]\n", GetCommandLine()); + for (int i = 0; i < argc; ++i) + printf("[%s]\n", argv[i]); + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/ShowConsoleInput.cc b/src/libs/3rdparty/winpty/misc/ShowConsoleInput.cc new file mode 100644 index 0000000000..75fbfb81f1 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/ShowConsoleInput.cc @@ -0,0 +1,40 @@ +#include +#include +#include + +int main(int argc, char *argv[]) +{ + static int escCount = 0; + + HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); + while (true) { + DWORD count; + INPUT_RECORD ir; + if (!ReadConsoleInput(hStdin, &ir, 1, &count)) { + printf("ReadConsoleInput failed\n"); + return 1; + } + + if (true) { + DWORD mode; + GetConsoleMode(hStdin, &mode); + SetConsoleMode(hStdin, mode & ~ENABLE_PROCESSED_INPUT); + } + + if (ir.EventType == KEY_EVENT) { + const KEY_EVENT_RECORD &ker = ir.Event.KeyEvent; + printf("%s", ker.bKeyDown ? "dn" : "up"); + printf(" ch="); + if (isprint(ker.uChar.AsciiChar)) + printf("'%c'", ker.uChar.AsciiChar); + printf("%d", ker.uChar.AsciiChar); + printf(" vk=%#x", ker.wVirtualKeyCode); + printf(" scan=%#x", ker.wVirtualScanCode); + printf(" state=%#x", (int)ker.dwControlKeyState); + printf(" repeat=%d", ker.wRepeatCount); + printf("\n"); + if (ker.uChar.AsciiChar == 27 && ++escCount == 6) + break; + } + } +} diff --git a/src/libs/3rdparty/winpty/misc/Spew.py b/src/libs/3rdparty/winpty/misc/Spew.py new file mode 100644 index 0000000000..9d1796af37 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Spew.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python +i = 0; +while True: + i += 1 + print(i) diff --git a/src/libs/3rdparty/winpty/misc/TestUtil.cc b/src/libs/3rdparty/winpty/misc/TestUtil.cc new file mode 100644 index 0000000000..c832a12b85 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/TestUtil.cc @@ -0,0 +1,172 @@ +// This file is included into test programs using #include + +#include +#include +#include +#include +#include +#include +#include + +#include "../src/shared/DebugClient.h" +#include "../src/shared/TimeMeasurement.h" + +#include "../src/shared/DebugClient.cc" +#include "../src/shared/WinptyAssert.cc" +#include "../src/shared/WinptyException.cc" + +// Launch this test program again, in a new console that we will destroy. +static void startChildProcess(const wchar_t *args) { + wchar_t program[1024]; + wchar_t cmdline[1024]; + GetModuleFileNameW(NULL, program, 1024); + swprintf(cmdline, L"\"%ls\" %ls", program, args); + + STARTUPINFOW sui; + PROCESS_INFORMATION pi; + memset(&sui, 0, sizeof(sui)); + memset(&pi, 0, sizeof(pi)); + sui.cb = sizeof(sui); + + CreateProcessW(program, cmdline, + NULL, NULL, + /*bInheritHandles=*/FALSE, + /*dwCreationFlags=*/CREATE_NEW_CONSOLE, + NULL, NULL, + &sui, &pi); +} + +static void setBufferSize(HANDLE conout, int x, int y) { + COORD size = { static_cast(x), static_cast(y) }; + BOOL success = SetConsoleScreenBufferSize(conout, size); + trace("setBufferSize: (%d,%d), result=%d", x, y, success); +} + +static void setWindowPos(HANDLE conout, int x, int y, int w, int h) { + SMALL_RECT r = { + static_cast(x), static_cast(y), + static_cast(x + w - 1), + static_cast(y + h - 1) + }; + BOOL success = SetConsoleWindowInfo(conout, /*bAbsolute=*/TRUE, &r); + trace("setWindowPos: (%d,%d,%d,%d), result=%d", x, y, w, h, success); +} + +static void setCursorPos(HANDLE conout, int x, int y) { + COORD coord = { static_cast(x), static_cast(y) }; + SetConsoleCursorPosition(conout, coord); +} + +static void setBufferSize(int x, int y) { + setBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), x, y); +} + +static void setWindowPos(int x, int y, int w, int h) { + setWindowPos(GetStdHandle(STD_OUTPUT_HANDLE), x, y, w, h); +} + +static void setCursorPos(int x, int y) { + setCursorPos(GetStdHandle(STD_OUTPUT_HANDLE), x, y); +} + +static void countDown(int sec) { + for (int i = sec; i > 0; --i) { + printf("%d.. ", i); + fflush(stdout); + Sleep(1000); + } + printf("\n"); +} + +static void writeBox(int x, int y, int w, int h, char ch, int attributes=7) { + CHAR_INFO info = { 0 }; + info.Char.AsciiChar = ch; + info.Attributes = attributes; + std::vector buf(w * h, info); + HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE); + COORD bufSize = { static_cast(w), static_cast(h) }; + COORD bufCoord = { 0, 0 }; + SMALL_RECT writeRegion = { + static_cast(x), + static_cast(y), + static_cast(x + w - 1), + static_cast(y + h - 1) + }; + WriteConsoleOutputA(conout, buf.data(), bufSize, bufCoord, &writeRegion); +} + +static void setChar(int x, int y, char ch, int attributes=7) { + writeBox(x, y, 1, 1, ch, attributes); +} + +static void fillChar(int x, int y, int repeat, char ch) { + COORD coord = { static_cast(x), static_cast(y) }; + DWORD actual = 0; + FillConsoleOutputCharacterA( + GetStdHandle(STD_OUTPUT_HANDLE), + ch, repeat, coord, &actual); +} + +static void repeatChar(int count, char ch) { + for (int i = 0; i < count; ++i) { + putchar(ch); + } + fflush(stdout); +} + +// I don't know why, but wprintf fails to print this face name, +// "MS ゴシック" (aka MS Gothic). It helps to use wprintf instead of printf, and +// it helps to call `setlocale(LC_ALL, "")`, but the Japanese symbols are +// ultimately converted to `?` symbols, even though MS Gothic is able to +// display its own name, and the current code page is 932 (Shift-JIS). +static void cvfprintf(HANDLE conout, const wchar_t *fmt, va_list ap) { + wchar_t buffer[256]; + vswprintf(buffer, 256 - 1, fmt, ap); + buffer[255] = L'\0'; + DWORD actual = 0; + if (!WriteConsoleW(conout, buffer, wcslen(buffer), &actual, NULL)) { + wprintf(L"WriteConsoleW call failed!\n"); + } +} + +static void cfprintf(HANDLE conout, const wchar_t *fmt, ...) { + va_list ap; + va_start(ap, fmt); + cvfprintf(conout, fmt, ap); + va_end(ap); +} + +static void cprintf(const wchar_t *fmt, ...) { + va_list ap; + va_start(ap, fmt); + cvfprintf(GetStdHandle(STD_OUTPUT_HANDLE), fmt, ap); + va_end(ap); +} + +static std::string narrowString(const std::wstring &input) +{ + int mblen = WideCharToMultiByte( + CP_UTF8, 0, + input.data(), input.size(), + NULL, 0, NULL, NULL); + if (mblen <= 0) { + return std::string(); + } + std::vector tmp(mblen); + int mblen2 = WideCharToMultiByte( + CP_UTF8, 0, + input.data(), input.size(), + tmp.data(), tmp.size(), + NULL, NULL); + assert(mblen2 == mblen); + return std::string(tmp.data(), tmp.size()); +} + +HANDLE openConout() { + const HANDLE conout = CreateFileW(L"CONOUT$", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + ASSERT(conout != INVALID_HANDLE_VALUE); + return conout; +} diff --git a/src/libs/3rdparty/winpty/misc/UnicodeDoubleWidthTest.cc b/src/libs/3rdparty/winpty/misc/UnicodeDoubleWidthTest.cc new file mode 100644 index 0000000000..7210d41032 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/UnicodeDoubleWidthTest.cc @@ -0,0 +1,102 @@ +// Demonstrates how U+30FC is sometimes handled as a single-width character +// when it should be handled as a double-width character. +// +// It only runs on computers where 932 is a valid code page. Set the system +// local to "Japanese (Japan)" to ensure this. +// +// The problem seems to happen when U+30FC is printed in a console using the +// Lucida Console font, and only when that font is at certain sizes. +// + +#include +#include +#include +#include +#include +#include + +#include "TestUtil.cc" + +#define COUNT_OF(x) (sizeof(x) / sizeof((x)[0])) + +static void setFont(const wchar_t *faceName, int pxSize) { + CONSOLE_FONT_INFOEX infoex = {0}; + infoex.cbSize = sizeof(infoex); + infoex.dwFontSize.Y = pxSize; + wcsncpy(infoex.FaceName, faceName, COUNT_OF(infoex.FaceName)); + BOOL ret = SetCurrentConsoleFontEx( + GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &infoex); + assert(ret); +} + +static bool performTest(const wchar_t testChar) { + const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE); + + SetConsoleTextAttribute(conout, 7); + + system("cls"); + DWORD actual = 0; + BOOL ret = WriteConsoleW(conout, &testChar, 1, &actual, NULL); + assert(ret && actual == 1); + + CHAR_INFO verify[2]; + COORD bufSize = {2, 1}; + COORD bufCoord = {0, 0}; + const SMALL_RECT readRegion = {0, 0, 1, 0}; + SMALL_RECT actualRegion = readRegion; + ret = ReadConsoleOutputW(conout, verify, bufSize, bufCoord, &actualRegion); + assert(ret && !memcmp(&readRegion, &actualRegion, sizeof(readRegion))); + assert(verify[0].Char.UnicodeChar == testChar); + + if (verify[1].Char.UnicodeChar == testChar) { + // Typical double-width behavior with a TrueType font. Pass. + assert(verify[0].Attributes == 0x107); + assert(verify[1].Attributes == 0x207); + return true; + } else if (verify[1].Char.UnicodeChar == 0) { + // Typical double-width behavior with a Raster Font. Pass. + assert(verify[0].Attributes == 7); + assert(verify[1].Attributes == 0); + return true; + } else if (verify[1].Char.UnicodeChar == L' ') { + // Single-width behavior. Fail. + assert(verify[0].Attributes == 7); + assert(verify[1].Attributes == 7); + return false; + } else { + // Unexpected output. + assert(false); + } +} + +int main(int argc, char *argv[]) { + setlocale(LC_ALL, ""); + if (argc == 1) { + startChildProcess(L"CHILD"); + return 0; + } + + assert(SetConsoleCP(932)); + assert(SetConsoleOutputCP(932)); + + const wchar_t testChar = 0x30FC; + const wchar_t *const faceNames[] = { + L"Lucida Console", + L"Consolas", + L"MS ゴシック", + }; + + trace("Test started"); + + for (auto faceName : faceNames) { + for (int px = 1; px <= 50; ++px) { + setFont(faceName, px); + if (!performTest(testChar)) { + trace("FAILURE: %s %dpx", narrowString(faceName).c_str(), px); + } + } + } + + trace("Test complete"); + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/UnicodeWideTest1.cc b/src/libs/3rdparty/winpty/misc/UnicodeWideTest1.cc new file mode 100644 index 0000000000..a8d798e70d --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/UnicodeWideTest1.cc @@ -0,0 +1,246 @@ +#include + +#include +#include + +#include "TestUtil.cc" + +#define COUNT_OF(x) (sizeof(x) / sizeof((x)[0])) + + +CHAR_INFO ci(wchar_t ch, WORD attributes) { + CHAR_INFO ret; + ret.Char.UnicodeChar = ch; + ret.Attributes = attributes; + return ret; +} + +CHAR_INFO ci(wchar_t ch) { + return ci(ch, 7); +} + +CHAR_INFO ci() { + return ci(L' '); +} + +bool operator==(SMALL_RECT x, SMALL_RECT y) { + return !memcmp(&x, &y, sizeof(x)); +} + +SMALL_RECT sr(COORD pt, COORD size) { + return { + pt.X, pt.Y, + static_cast(pt.X + size.X - 1), + static_cast(pt.Y + size.Y - 1) + }; +} + +static void set( + const COORD pt, + const COORD size, + const std::vector &data) { + assert(data.size() == size.X * size.Y); + SMALL_RECT writeRegion = sr(pt, size); + BOOL ret = WriteConsoleOutputW( + GetStdHandle(STD_OUTPUT_HANDLE), + data.data(), size, {0, 0}, &writeRegion); + assert(ret && writeRegion == sr(pt, size)); +} + +static void set( + const COORD pt, + const std::vector &data) { + set(pt, {static_cast(data.size()), 1}, data); +} + +static void writeAttrsAt( + const COORD pt, + const std::vector &data) { + DWORD actual = 0; + BOOL ret = WriteConsoleOutputAttribute( + GetStdHandle(STD_OUTPUT_HANDLE), + data.data(), data.size(), pt, &actual); + assert(ret && actual == data.size()); +} + +static void writeCharsAt( + const COORD pt, + const std::vector &data) { + DWORD actual = 0; + BOOL ret = WriteConsoleOutputCharacterW( + GetStdHandle(STD_OUTPUT_HANDLE), + data.data(), data.size(), pt, &actual); + assert(ret && actual == data.size()); +} + +static void writeChars( + const std::vector &data) { + DWORD actual = 0; + BOOL ret = WriteConsoleW( + GetStdHandle(STD_OUTPUT_HANDLE), + data.data(), data.size(), &actual, NULL); + assert(ret && actual == data.size()); +} + +std::vector get( + const COORD pt, + const COORD size) { + std::vector data(size.X * size.Y); + SMALL_RECT readRegion = sr(pt, size); + BOOL ret = ReadConsoleOutputW( + GetStdHandle(STD_OUTPUT_HANDLE), + data.data(), size, {0, 0}, &readRegion); + assert(ret && readRegion == sr(pt, size)); + return data; +} + +std::vector readCharsAt( + const COORD pt, + int size) { + std::vector data(size); + DWORD actual = 0; + BOOL ret = ReadConsoleOutputCharacterW( + GetStdHandle(STD_OUTPUT_HANDLE), + data.data(), data.size(), pt, &actual); + assert(ret); + data.resize(actual); // With double-width chars, we can read fewer than `size`. + return data; +} + +static void dump(const COORD pt, const COORD size) { + for (CHAR_INFO ci : get(pt, size)) { + printf("%04X %04X\n", ci.Char.UnicodeChar, ci.Attributes); + } +} + +static void dumpCharsAt(const COORD pt, int size) { + for (wchar_t ch : readCharsAt(pt, size)) { + printf("%04X\n", ch); + } +} + +static COORD getCursorPos() { + CONSOLE_SCREEN_BUFFER_INFO info = { sizeof(info) }; + assert(GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info)); + return info.dwCursorPosition; +} + +static void test1() { + // We write "䀀䀀", then write "䀁" in the middle of the two. The second + // write turns the first and last cells into spaces. The LEADING/TRAILING + // flags retain consistency. + printf("test1 - overlap full-width char with full-width char\n"); + writeCharsAt({1,0}, {0x4000, 0x4000}); + dump({0,0}, {6,1}); + printf("\n"); + writeCharsAt({2,0}, {0x4001}); + dump({0,0}, {6,1}); + printf("\n"); +} + +static void test2() { + // Like `test1`, but use a lower-level API to do the write. Consistency is + // preserved here too -- the first and last cells are replaced with spaces. + printf("test2 - overlap full-width char with full-width char (lowlevel)\n"); + writeCharsAt({1,0}, {0x4000, 0x4000}); + dump({0,0}, {6,1}); + printf("\n"); + set({2,0}, {ci(0x4001,0x107), ci(0x4001,0x207)}); + dump({0,0}, {6,1}); + printf("\n"); +} + +static void test3() { + // However, the lower-level API can break the LEADING/TRAILING invariant + // explicitly: + printf("test3 - explicitly violate LEADING/TRAILING using lowlevel API\n"); + set({1,0}, { + ci(0x4000, 0x207), + ci(0x4001, 0x107), + ci(0x3044, 7), + ci(L'X', 0x107), + ci(L'X', 0x207), + }); + dump({0,0}, {7,1}); +} + +static void test4() { + // It is possible for the two cells of a double-width character to have two + // colors. + printf("test4 - use lowlevel to assign two colors to one full-width char\n"); + set({0,0}, { + ci(0x4000, 0x142), + ci(0x4000, 0x224), + }); + dump({0,0}, {2,1}); +} + +static void test5() { + // WriteConsoleOutputAttribute doesn't seem to affect the LEADING/TRAILING + // flags. + printf("test5 - WriteConsoleOutputAttribute cannot affect LEADING/TRAILING\n"); + + // Trying to clear the flags doesn't work... + writeCharsAt({0,0}, {0x4000}); + dump({0,0}, {2,1}); + writeAttrsAt({0,0}, {0x42, 0x24}); + printf("\n"); + dump({0,0}, {2,1}); + + // ... and trying to add them also doesn't work. + writeCharsAt({0,1}, {'A', ' '}); + writeAttrsAt({0,1}, {0x107, 0x207}); + printf("\n"); + dump({0,1}, {2,1}); +} + +static void test6() { + // The cursor position may be on either cell of a double-width character. + // Visually, the cursor appears under both cells, regardless of which + // specific one has the cursor. + printf("test6 - cursor can be either left or right cell of full-width char\n"); + + writeCharsAt({2,1}, {0x4000}); + + setCursorPos(2, 1); + auto pos1 = getCursorPos(); + Sleep(1000); + + setCursorPos(3, 1); + auto pos2 = getCursorPos(); + Sleep(1000); + + setCursorPos(0, 15); + printf("%d,%d\n", pos1.X, pos1.Y); + printf("%d,%d\n", pos2.X, pos2.Y); +} + +static void runTest(void (&test)()) { + system("cls"); + setCursorPos(0, 14); + test(); + system("pause"); +} + +int main(int argc, char *argv[]) { + if (argc == 1) { + startChildProcess(L"CHILD"); + return 0; + } + + setWindowPos(0, 0, 1, 1); + setBufferSize(80, 40); + setWindowPos(0, 0, 80, 40); + + auto cp = GetConsoleOutputCP(); + assert(cp == 932 || cp == 936 || cp == 949 || cp == 950); + + runTest(test1); + runTest(test2); + runTest(test3); + runTest(test4); + runTest(test5); + runTest(test6); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/UnicodeWideTest2.cc b/src/libs/3rdparty/winpty/misc/UnicodeWideTest2.cc new file mode 100644 index 0000000000..05f80f70bd --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/UnicodeWideTest2.cc @@ -0,0 +1,130 @@ +// +// Test half-width vs full-width characters. +// + +#include +#include +#include +#include + +#include "TestUtil.cc" + +static void writeChars(const wchar_t *text) { + wcslen(text); + const int len = wcslen(text); + DWORD actual = 0; + BOOL ret = WriteConsoleW( + GetStdHandle(STD_OUTPUT_HANDLE), + text, len, &actual, NULL); + trace("writeChars: ret=%d, actual=%lld", ret, (long long)actual); +} + +static void dumpChars(int x, int y, int w, int h) { + BOOL ret; + const COORD bufSize = {w, h}; + const COORD bufCoord = {0, 0}; + const SMALL_RECT topLeft = {x, y, x + w - 1, y + h - 1}; + CHAR_INFO mbcsData[w * h]; + CHAR_INFO unicodeData[w * h]; + SMALL_RECT readRegion; + readRegion = topLeft; + ret = ReadConsoleOutputW(GetStdHandle(STD_OUTPUT_HANDLE), unicodeData, + bufSize, bufCoord, &readRegion); + assert(ret); + readRegion = topLeft; + ret = ReadConsoleOutputA(GetStdHandle(STD_OUTPUT_HANDLE), mbcsData, + bufSize, bufCoord, &readRegion); + assert(ret); + + printf("\n"); + for (int i = 0; i < w * h; ++i) { + printf("(%02d,%02d) CHAR: %04x %4x -- %02x %4x\n", + x + i % w, y + i / w, + (unsigned short)unicodeData[i].Char.UnicodeChar, + (unsigned short)unicodeData[i].Attributes, + (unsigned char)mbcsData[i].Char.AsciiChar, + (unsigned short)mbcsData[i].Attributes); + } +} + +int main(int argc, char *argv[]) { + system("cls"); + setWindowPos(0, 0, 1, 1); + setBufferSize(80, 38); + setWindowPos(0, 0, 80, 38); + + // Write text. + const wchar_t text1[] = { + 0x3044, // U+3044 (HIRAGANA LETTER I) + 0x2014, // U+2014 (EM DASH) + 0x3044, // U+3044 (HIRAGANA LETTER I) + 0xFF2D, // U+FF2D (FULLWIDTH LATIN CAPITAL LETTER M) + 0x30FC, // U+30FC (KATAKANA-HIRAGANA PROLONGED SOUND MARK) + 0x0031, // U+3031 (DIGIT ONE) + 0x2014, // U+2014 (EM DASH) + 0x0032, // U+0032 (DIGIT TWO) + 0x005C, // U+005C (REVERSE SOLIDUS) + 0x3044, // U+3044 (HIRAGANA LETTER I) + 0 + }; + setCursorPos(0, 0); + writeChars(text1); + + setCursorPos(78, 1); + writeChars(L"<>"); + + const wchar_t text2[] = { + 0x0032, // U+3032 (DIGIT TWO) + 0x3044, // U+3044 (HIRAGANA LETTER I) + 0, + }; + setCursorPos(78, 1); + writeChars(text2); + + system("pause"); + + dumpChars(0, 0, 17, 1); + dumpChars(2, 0, 2, 1); + dumpChars(2, 0, 1, 1); + dumpChars(3, 0, 1, 1); + dumpChars(78, 1, 2, 1); + dumpChars(0, 2, 2, 1); + + system("pause"); + system("cls"); + + const wchar_t text3[] = { + 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 1 + 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 2 + 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 3 + 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 4 + 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 5 + 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 6 + 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 7 + 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 8 + 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 9 + 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 10 + 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 11 + 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 12 + L'\r', '\n', + L'\r', '\n', + 0 + }; + writeChars(text3); + system("pause"); + { + const COORD bufSize = {80, 2}; + const COORD bufCoord = {0, 0}; + SMALL_RECT readRegion = {0, 0, 79, 1}; + CHAR_INFO unicodeData[160]; + BOOL ret = ReadConsoleOutputW(GetStdHandle(STD_OUTPUT_HANDLE), unicodeData, + bufSize, bufCoord, &readRegion); + assert(ret); + for (int i = 0; i < 96; ++i) { + printf("%04x ", unicodeData[i].Char.UnicodeChar); + } + printf("\n"); + } + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/UnixEcho.cc b/src/libs/3rdparty/winpty/misc/UnixEcho.cc new file mode 100644 index 0000000000..372e045157 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/UnixEcho.cc @@ -0,0 +1,89 @@ +/* + * Unix test code that puts the terminal into raw mode, then echos typed + * characters to stdout. Derived from sample code in the Stevens book, posted + * online at http://www.lafn.org/~dave/linux/terminalIO.html. + */ + +#include +#include +#include +#include +#include "FormatChar.h" + +static struct termios save_termios; +static int term_saved; + +/* RAW! mode */ +int tty_raw(int fd) +{ + struct termios buf; + + if (tcgetattr(fd, &save_termios) < 0) /* get the original state */ + return -1; + + buf = save_termios; + + /* echo off, canonical mode off, extended input + processing off, signal chars off */ + buf.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + + /* no SIGINT on BREAK, CR-to-NL off, input parity + check off, don't strip the 8th bit on input, + ouput flow control off */ + buf.c_iflag &= ~(BRKINT | ICRNL | ISTRIP | IXON); + + /* clear size bits, parity checking off */ + buf.c_cflag &= ~(CSIZE | PARENB); + + /* set 8 bits/char */ + buf.c_cflag |= CS8; + + /* output processing off */ + buf.c_oflag &= ~(OPOST); + + buf.c_cc[VMIN] = 1; /* 1 byte at a time */ + buf.c_cc[VTIME] = 0; /* no timer on input */ + + if (tcsetattr(fd, TCSAFLUSH, &buf) < 0) + return -1; + + term_saved = 1; + + return 0; +} + + +/* set it to normal! */ +int tty_reset(int fd) +{ + if (term_saved) + if (tcsetattr(fd, TCSAFLUSH, &save_termios) < 0) + return -1; + + return 0; +} + + +int main() +{ + tty_raw(0); + + int count = 0; + while (true) { + char ch; + char buf[16]; + int actual = read(0, &ch, 1); + if (actual != 1) { + perror("read error"); + break; + } + formatChar(buf, ch); + fputs(buf, stdout); + fflush(stdout); + if (ch == 3) // Ctrl-C + break; + } + + tty_reset(0); + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/Utf16Echo.cc b/src/libs/3rdparty/winpty/misc/Utf16Echo.cc new file mode 100644 index 0000000000..ef5f302de4 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Utf16Echo.cc @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +#include +#include + +int main(int argc, char *argv[]) { + system("cls"); + + if (argc == 1) { + printf("Usage: %s hhhh\n", argv[0]); + return 0; + } + + std::wstring dataToWrite; + for (int i = 1; i < argc; ++i) { + wchar_t ch = strtol(argv[i], NULL, 16); + dataToWrite.push_back(ch); + } + + DWORD actual = 0; + BOOL ret = WriteConsoleW( + GetStdHandle(STD_OUTPUT_HANDLE), + dataToWrite.data(), dataToWrite.size(), &actual, NULL); + assert(ret && actual == dataToWrite.size()); + + // Read it back. + std::vector readBuffer(dataToWrite.size() * 2); + COORD bufSize = {static_cast(readBuffer.size()), 1}; + COORD bufCoord = {0, 0}; + SMALL_RECT topLeft = {0, 0, static_cast(readBuffer.size() - 1), 0}; + ret = ReadConsoleOutputW( + GetStdHandle(STD_OUTPUT_HANDLE), readBuffer.data(), + bufSize, bufCoord, &topLeft); + assert(ret); + + printf("\n"); + for (int i = 0; i < readBuffer.size(); ++i) { + printf("CHAR: %04x %04x\n", + readBuffer[i].Char.UnicodeChar, + readBuffer[i].Attributes); + } + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/VeryLargeRead.cc b/src/libs/3rdparty/winpty/misc/VeryLargeRead.cc new file mode 100644 index 0000000000..58f0897022 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/VeryLargeRead.cc @@ -0,0 +1,122 @@ +// +// 2015-09-25 +// I measured these limits on the size of a single ReadConsoleOutputW call. +// The limit seems to more-or-less disppear with Windows 8, which is the first +// OS to stop using ALPCs for console I/O. My guess is that the new I/O +// method does not use the 64KiB shared memory buffer that the ALPC method +// uses. +// +// I'm guessing the remaining difference between Windows 8/8.1 and Windows 10 +// might be related to the 32-vs-64-bitness. +// +// Client OSs +// +// Windows XP 32-bit VM ==> up to 13304 characters +// - 13304x1 works, but 13305x1 fails instantly +// Windows 7 32-bit VM ==> between 16-17 thousand characters +// - 16000x1 works, 17000x1 fails instantly +// - 163x100 *crashes* conhost.exe but leaves VeryLargeRead.exe running +// Windows 8 32-bit VM ==> between 240-250 million characters +// - 10000x24000 works, but 10000x25000 does not +// Windows 8.1 32-bit VM ==> between 240-250 million characters +// - 10000x24000 works, but 10000x25000 does not +// Windows 10 64-bit VM ==> no limit (tested to 576 million characters) +// - 24000x24000 works +// - `ver` reports [Version 10.0.10240], conhost.exe and ConhostV1.dll are +// 10.0.10240.16384 for file and product version. ConhostV2.dll is +// 10.0.10240.16391 for file and product version. +// +// Server OSs +// +// Windows Server 2008 64-bit VM ==> 14300-14400 characters +// - 14300x1 works, 14400x1 fails instantly +// - This OS does not have conhost.exe. +// - `ver` reports [Version 6.0.6002] +// Windows Server 2008 R2 64-bit VM ==> 15600-15700 characters +// - 15600x1 works, 15700x1 fails instantly +// - This OS has conhost.exe, and procexp.exe reveals console ALPC ports in +// use in conhost.exe. +// - `ver` reports [Version 6.1.7601], conhost.exe is 6.1.7601.23153 for file +// and product version. +// Windows Server 2012 64-bit VM ==> at least 100 million characters +// - 10000x10000 works (VM had only 1GiB of RAM, so I skipped larger tests) +// - This OS has Windows 8's task manager and procexp.exe reveals the same +// lack of ALPC ports and the same \Device\ConDrv\* files as Windows 8. +// - `ver` reports [Version 6.2.9200], conhost.exe is 6.2.9200.16579 for file +// and product version. +// +// To summarize: +// +// client-OS server-OS notes +// --------------------------------------------------------------------------- +// XP Server 2008 CSRSS, small reads +// 7 Server 2008 R2 ALPC-to-conhost, small reads +// 8, 8.1 Server 2012 new I/O interface, large reads allowed +// 10 enhanced console w/rewrapping +// +// (Presumably, Win2K, Vista, and Win2K3 behave the same as XP. conhost.exe +// was announced as a Win7 feature.) +// + +#include +#include +#include + +#include "TestUtil.cc" + +int main(int argc, char *argv[]) { + long long width = 9000; + long long height = 9000; + + assert(argc >= 1); + if (argc == 4) { + width = atoi(argv[2]); + height = atoi(argv[3]); + } else { + if (argc == 3) { + width = atoi(argv[1]); + height = atoi(argv[2]); + } + wchar_t args[1024]; + swprintf(args, 1024, L"CHILD %lld %lld", width, height); + startChildProcess(args); + return 0; + } + + const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE); + + setWindowPos(0, 0, 1, 1); + setBufferSize(width, height); + setWindowPos(0, 0, std::min(80LL, width), std::min(50LL, height)); + + setCursorPos(0, 0); + printf("A"); + fflush(stdout); + setCursorPos(width - 2, height - 1); + printf("B"); + fflush(stdout); + + trace("sizeof(CHAR_INFO) = %d", (int)sizeof(CHAR_INFO)); + + trace("Allocating buffer..."); + CHAR_INFO *buffer = new CHAR_INFO[width * height]; + assert(buffer != NULL); + memset(&buffer[0], 0, sizeof(CHAR_INFO)); + memset(&buffer[width * height - 2], 0, sizeof(CHAR_INFO)); + + COORD bufSize = { width, height }; + COORD bufCoord = { 0, 0 }; + SMALL_RECT readRegion = { 0, 0, width - 1, height - 1 }; + trace("ReadConsoleOutputW: calling..."); + BOOL success = ReadConsoleOutputW(conout, buffer, bufSize, bufCoord, &readRegion); + trace("ReadConsoleOutputW: success=%d", success); + + assert(buffer[0].Char.UnicodeChar == L'A'); + assert(buffer[width * height - 2].Char.UnicodeChar == L'B'); + trace("Top-left and bottom-right characters read successfully!"); + + Sleep(30000); + + delete [] buffer; + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/VkEscapeTest.cc b/src/libs/3rdparty/winpty/misc/VkEscapeTest.cc new file mode 100644 index 0000000000..97bf59f998 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/VkEscapeTest.cc @@ -0,0 +1,56 @@ +/* + * Sending VK_PAUSE to the console window almost works as a mechanism for + * pausing it, but it doesn't because the console could turn off the + * ENABLE_LINE_INPUT console mode flag. + */ + +#define _WIN32_WINNT 0x0501 +#include +#include +#include + +CALLBACK DWORD pausingThread(LPVOID dummy) +{ + if (1) { + Sleep(1000); + HWND hwnd = GetConsoleWindow(); + SendMessage(hwnd, WM_KEYDOWN, VK_PAUSE, 1); + Sleep(1000); + SendMessage(hwnd, WM_KEYDOWN, VK_ESCAPE, 1); + } + + if (0) { + INPUT_RECORD ir; + memset(&ir, 0, sizeof(ir)); + ir.EventType = KEY_EVENT; + ir.Event.KeyEvent.bKeyDown = TRUE; + ir.Event.KeyEvent.wVirtualKeyCode = VK_PAUSE; + ir.Event.KeyEvent.wRepeatCount = 1; + } + + return 0; +} + +int main() +{ + HANDLE hin = GetStdHandle(STD_INPUT_HANDLE); + HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE); + COORD c = { 0, 0 }; + + DWORD mode; + GetConsoleMode(hin, &mode); + SetConsoleMode(hin, mode & + ~(ENABLE_LINE_INPUT)); + + CreateThread(NULL, 0, + pausingThread, NULL, + 0, NULL); + + int i = 0; + while (true) { + Sleep(100); + printf("%d\n", ++i); + } + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/Win10ResizeWhileFrozen.cc b/src/libs/3rdparty/winpty/misc/Win10ResizeWhileFrozen.cc new file mode 100644 index 0000000000..82feaf3c50 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Win10ResizeWhileFrozen.cc @@ -0,0 +1,52 @@ +/* + * Demonstrates a conhost hang that occurs when widening the console buffer + * while selection is in progress. The problem affects the new Windows 10 + * console, not the "legacy" console mode that Windows 10 also includes. + * + * First tested with: + * - Windows 10.0.10240 + * - conhost.exe version 10.0.10240.16384 + * - ConhostV1.dll version 10.0.10240.16384 + * - ConhostV2.dll version 10.0.10240.16391 + */ + +#include +#include +#include +#include + +#include "TestUtil.cc" + +const int SC_CONSOLE_MARK = 0xFFF2; +const int SC_CONSOLE_SELECT_ALL = 0xFFF5; + +int main(int argc, char *argv[]) { + if (argc == 1) { + startChildProcess(L"CHILD"); + return 0; + } + + setWindowPos(0, 0, 1, 1); + setBufferSize(80, 25); + setWindowPos(0, 0, 80, 25); + + countDown(5); + + SendMessage(GetConsoleWindow(), WM_SYSCOMMAND, SC_CONSOLE_SELECT_ALL, 0); + Sleep(2000); + + // This API call does not return. In the console window, the "Select All" + // operation appears to end. The console window becomes non-responsive, + // and the conhost.exe process must be killed from the Task Manager. + // (Killing this test program or closing the console window is not + // sufficient.) + // + // The same hang occurs whether line resizing is off or on. It happens + // with both "Mark" and "Select All". Calling setBufferSize with the + // existing buffer size does not hang, but calling it with only a changed + // buffer height *does* hang. Calling setWindowPos does not hang. + setBufferSize(120, 25); + + printf("Done...\n"); + Sleep(2000); +} diff --git a/src/libs/3rdparty/winpty/misc/Win10WrapTest1.cc b/src/libs/3rdparty/winpty/misc/Win10WrapTest1.cc new file mode 100644 index 0000000000..645fa95d54 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Win10WrapTest1.cc @@ -0,0 +1,57 @@ +/* + * Demonstrates some wrapping behaviors of the new Windows 10 console. + */ + +#include +#include +#include + +#include "TestUtil.cc" + +int main(int argc, char *argv[]) { + if (argc == 1) { + startChildProcess(L"CHILD"); + return 0; + } + + setWindowPos(0, 0, 1, 1); + setBufferSize(40, 20); + setWindowPos(0, 0, 40, 20); + + system("cls"); + + repeatChar(39, 'A'); repeatChar(1, ' '); + repeatChar(39, 'B'); repeatChar(1, ' '); + printf("\n"); + + repeatChar(39, 'C'); repeatChar(1, ' '); + repeatChar(39, 'D'); repeatChar(1, ' '); + printf("\n"); + + repeatChar(40, 'E'); + repeatChar(40, 'F'); + printf("\n"); + + repeatChar(39, 'G'); repeatChar(1, ' '); + repeatChar(39, 'H'); repeatChar(1, ' '); + printf("\n"); + + Sleep(2000); + + setChar(39, 0, '*', 0x24); + setChar(39, 1, '*', 0x24); + + setChar(39, 3, ' ', 0x24); + setChar(39, 4, ' ', 0x24); + + setChar(38, 6, ' ', 0x24); + setChar(38, 7, ' ', 0x24); + + Sleep(2000); + setWindowPos(0, 0, 35, 20); + setBufferSize(35, 20); + trace("DONE"); + + printf("Sleeping forever...\n"); + while(true) { Sleep(1000); } +} diff --git a/src/libs/3rdparty/winpty/misc/Win10WrapTest2.cc b/src/libs/3rdparty/winpty/misc/Win10WrapTest2.cc new file mode 100644 index 0000000000..50615fc8c7 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Win10WrapTest2.cc @@ -0,0 +1,30 @@ +#include + +#include "TestUtil.cc" + +int main(int argc, char *argv[]) { + if (argc == 1) { + startChildProcess(L"CHILD"); + return 0; + } + + const int WIDTH = 25; + + setWindowPos(0, 0, 1, 1); + setBufferSize(WIDTH, 40); + setWindowPos(0, 0, WIDTH, 20); + + system("cls"); + + for (int i = 0; i < 100; ++i) { + printf("FOO(%d)\n", i); + } + + repeatChar(5, '\n'); + repeatChar(WIDTH * 5, '.'); + repeatChar(10, '\n'); + setWindowPos(0, 20, WIDTH, 20); + writeBox(0, 5, 1, 10, '|'); + + Sleep(120000); +} diff --git a/src/libs/3rdparty/winpty/misc/Win32Echo1.cc b/src/libs/3rdparty/winpty/misc/Win32Echo1.cc new file mode 100644 index 0000000000..06fc79f794 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Win32Echo1.cc @@ -0,0 +1,26 @@ +/* + * A Win32 program that reads raw console input with ReadFile and echos + * it to stdout. + */ + +#include +#include +#include + +int main() +{ + int count = 0; + HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE); + HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleMode(hStdIn, 0); + + while (true) { + DWORD actual; + char ch; + ReadFile(hStdIn, &ch, 1, &actual, NULL); + printf("%02x ", ch); + if (++count == 50) + break; + } + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/Win32Echo2.cc b/src/libs/3rdparty/winpty/misc/Win32Echo2.cc new file mode 100644 index 0000000000..b2ea2ad1c5 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Win32Echo2.cc @@ -0,0 +1,19 @@ +/* + * A Win32 program that reads raw console input with getch and echos + * it to stdout. + */ + +#include +#include + +int main() +{ + int count = 0; + while (true) { + int ch = getch(); + printf("%02x ", ch); + if (++count == 50) + break; + } + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/Win32Test1.cc b/src/libs/3rdparty/winpty/misc/Win32Test1.cc new file mode 100644 index 0000000000..a40d318a98 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Win32Test1.cc @@ -0,0 +1,46 @@ +#define _WIN32_WINNT 0x0501 +#include "../src/shared/DebugClient.cc" +#include +#include + +const int SC_CONSOLE_MARK = 0xFFF2; + +CALLBACK DWORD writerThread(void*) +{ + while (true) { + Sleep(1000); + trace("writing"); + printf("X\n"); + trace("written"); + } +} + +int main() +{ + CreateThread(NULL, 0, writerThread, NULL, 0, NULL); + trace("marking console"); + HWND hwnd = GetConsoleWindow(); + PostMessage(hwnd, WM_SYSCOMMAND, SC_CONSOLE_MARK, 0); + + Sleep(2000); + + trace("reading output"); + CHAR_INFO buf[1]; + COORD bufSize = { 1, 1 }; + COORD zeroCoord = { 0, 0 }; + SMALL_RECT readRect = { 0, 0, 0, 0 }; + ReadConsoleOutput(GetStdHandle(STD_OUTPUT_HANDLE), + buf, + bufSize, + zeroCoord, + &readRect); + trace("done reading output"); + + Sleep(2000); + + PostMessage(hwnd, WM_CHAR, 27, 0x00010001); + + Sleep(1100); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/Win32Test2.cc b/src/libs/3rdparty/winpty/misc/Win32Test2.cc new file mode 100644 index 0000000000..2777bad456 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Win32Test2.cc @@ -0,0 +1,70 @@ +/* + * This test demonstrates that putting a console into selection mode does not + * block the low-level console APIs, even though it blocks WriteFile. + */ + +#define _WIN32_WINNT 0x0501 +#include "../src/shared/DebugClient.cc" +#include +#include + +const int SC_CONSOLE_MARK = 0xFFF2; + +CALLBACK DWORD writerThread(void*) +{ + CHAR_INFO xChar, fillChar; + memset(&xChar, 0, sizeof(xChar)); + xChar.Char.AsciiChar = 'X'; + xChar.Attributes = 7; + memset(&fillChar, 0, sizeof(fillChar)); + fillChar.Char.AsciiChar = ' '; + fillChar.Attributes = 7; + COORD oneCoord = { 1, 1 }; + COORD zeroCoord = { 0, 0 }; + + while (true) { + SMALL_RECT writeRegion = { 5, 5, 5, 5 }; + WriteConsoleOutput(GetStdHandle(STD_OUTPUT_HANDLE), + &xChar, oneCoord, + zeroCoord, + &writeRegion); + Sleep(500); + SMALL_RECT scrollRect = { 1, 1, 20, 20 }; + COORD destCoord = { 0, 0 }; + ScrollConsoleScreenBuffer(GetStdHandle(STD_OUTPUT_HANDLE), + &scrollRect, + NULL, + destCoord, + &fillChar); + } +} + +int main() +{ + CreateThread(NULL, 0, writerThread, NULL, 0, NULL); + trace("marking console"); + HWND hwnd = GetConsoleWindow(); + PostMessage(hwnd, WM_SYSCOMMAND, SC_CONSOLE_MARK, 0); + + Sleep(2000); + + trace("reading output"); + CHAR_INFO buf[1]; + COORD bufSize = { 1, 1 }; + COORD zeroCoord = { 0, 0 }; + SMALL_RECT readRect = { 0, 0, 0, 0 }; + ReadConsoleOutput(GetStdHandle(STD_OUTPUT_HANDLE), + buf, + bufSize, + zeroCoord, + &readRect); + trace("done reading output"); + + Sleep(2000); + + PostMessage(hwnd, WM_CHAR, 27, 0x00010001); + + Sleep(1100); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/Win32Test3.cc b/src/libs/3rdparty/winpty/misc/Win32Test3.cc new file mode 100644 index 0000000000..1fb92aff3d --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Win32Test3.cc @@ -0,0 +1,78 @@ +/* + * Creates a window station and starts a process under it. The new process + * also gets a new console. + */ + +#include +#include +#include + +int main() +{ + BOOL success; + + SECURITY_ATTRIBUTES sa; + memset(&sa, 0, sizeof(sa)); + sa.bInheritHandle = TRUE; + + HWINSTA originalStation = GetProcessWindowStation(); + printf("originalStation == 0x%x\n", originalStation); + HWINSTA station = CreateWindowStation(NULL, + 0, + WINSTA_ALL_ACCESS, + &sa); + printf("station == 0x%x\n", station); + if (!SetProcessWindowStation(station)) + printf("SetWindowStation failed!\n"); + HDESK desktop = CreateDesktop("Default", NULL, NULL, + /*dwFlags=*/0, GENERIC_ALL, + &sa); + printf("desktop = 0x%x\n", desktop); + + char stationName[256]; + stationName[0] = '\0'; + success = GetUserObjectInformation(station, UOI_NAME, + stationName, sizeof(stationName), + NULL); + printf("stationName = [%s]\n", stationName); + + char startupDesktop[256]; + sprintf(startupDesktop, "%s\\Default", stationName); + + STARTUPINFO sui; + PROCESS_INFORMATION pi; + memset(&sui, 0, sizeof(sui)); + memset(&pi, 0, sizeof(pi)); + sui.cb = sizeof(STARTUPINFO); + sui.lpDesktop = startupDesktop; + + // Start a cmd subprocess, and have it start its own cmd subprocess. + // Both subprocesses will connect to the same non-interactive window + // station. + + const char program[] = "c:\\windows\\system32\\cmd.exe"; + char cmdline[256]; + sprintf(cmdline, "%s /c cmd", program); + success = CreateProcess(program, + cmdline, + NULL, + NULL, + /*bInheritHandles=*/FALSE, + /*dwCreationFlags=*/CREATE_NEW_CONSOLE, + NULL, NULL, + &sui, + &pi); + + printf("pid == %d\n", pi.dwProcessId); + + // This sleep is necessary. We must give the child enough time to + // connect to the specified window station. + Sleep(5000); + + SetProcessWindowStation(originalStation); + CloseWindowStation(station); + CloseDesktop(desktop); + Sleep(5000); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/Win32Write1.cc b/src/libs/3rdparty/winpty/misc/Win32Write1.cc new file mode 100644 index 0000000000..6e5bf96682 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Win32Write1.cc @@ -0,0 +1,44 @@ +/* + * A Win32 program that scrolls and writes to the console using the ioctl-like + * interface. + */ + +#include +#include + +int main() +{ + HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE); + + for (int i = 0; i < 80; ++i) { + + CONSOLE_SCREEN_BUFFER_INFO info; + GetConsoleScreenBufferInfo(conout, &info); + + SMALL_RECT src = { 0, 1, info.dwSize.X - 1, info.dwSize.Y - 1 }; + COORD destOrigin = { 0, 0 }; + CHAR_INFO fillCharInfo = { 0 }; + fillCharInfo.Char.AsciiChar = ' '; + fillCharInfo.Attributes = 7; + ScrollConsoleScreenBuffer(conout, + &src, + NULL, + destOrigin, + &fillCharInfo); + + CHAR_INFO buffer = { 0 }; + buffer.Char.AsciiChar = 'X'; + buffer.Attributes = 7; + COORD bufferSize = { 1, 1 }; + COORD bufferCoord = { 0, 0 }; + SMALL_RECT writeRegion = { 0, 0, 0, 0 }; + writeRegion.Left = writeRegion.Right = i; + writeRegion.Top = writeRegion.Bottom = 5; + WriteConsoleOutput(conout, + &buffer, bufferSize, bufferCoord, + &writeRegion); + + Sleep(250); + } + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/WindowsBugCrashReader.cc b/src/libs/3rdparty/winpty/misc/WindowsBugCrashReader.cc new file mode 100644 index 0000000000..e6d9558df6 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/WindowsBugCrashReader.cc @@ -0,0 +1,27 @@ +// I noticed this on the ConEmu web site: +// +// https://social.msdn.microsoft.com/Forums/en-US/40c8e395-cca9-45c8-b9b8-2fbe6782ac2b/readconsoleoutput-cause-access-violation-writing-location-exception +// https://conemu.github.io/en/MicrosoftBugs.html +// +// In Windows 7, 8, and 8.1, a ReadConsoleOutputW with an out-of-bounds read +// region crashes the application. I have reproduced the problem on Windows 8 +// and 8.1, but not on Windows 7. +// + +#include + +#include "TestUtil.cc" + +int main() { + setWindowPos(0, 0, 1, 1); + setBufferSize(80, 25); + setWindowPos(0, 0, 80, 25); + + const HANDLE conout = openConout(); + static CHAR_INFO lineBuf[80]; + SMALL_RECT readRegion = { 0, 999, 79, 999 }; + const BOOL ret = ReadConsoleOutputW(conout, lineBuf, {80, 1}, {0, 0}, &readRegion); + ASSERT(!ret && "ReadConsoleOutputW should have failed"); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/WriteConsole.cc b/src/libs/3rdparty/winpty/misc/WriteConsole.cc new file mode 100644 index 0000000000..a03670ca92 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/WriteConsole.cc @@ -0,0 +1,106 @@ +#include + +#include +#include +#include + +#include +#include + +static std::wstring mbsToWcs(const std::string &s) { + const size_t len = mbstowcs(nullptr, s.c_str(), 0); + if (len == static_cast(-1)) { + assert(false && "mbsToWcs: invalid string"); + } + std::wstring ret; + ret.resize(len); + const size_t len2 = mbstowcs(&ret[0], s.c_str(), len); + assert(len == len2); + return ret; +} + +uint32_t parseHex(wchar_t ch, bool &invalid) { + if (ch >= L'0' && ch <= L'9') { + return ch - L'0'; + } else if (ch >= L'a' && ch <= L'f') { + return ch - L'a' + 10; + } else if (ch >= L'A' && ch <= L'F') { + return ch - L'A' + 10; + } else { + invalid = true; + return 0; + } +} + +int main(int argc, char *argv[]) { + std::vector args; + for (int i = 1; i < argc; ++i) { + args.push_back(mbsToWcs(argv[i])); + } + + std::wstring out; + for (const auto &arg : args) { + if (!out.empty()) { + out.push_back(L' '); + } + for (size_t i = 0; i < arg.size(); ++i) { + wchar_t ch = arg[i]; + wchar_t nch = i + 1 < arg.size() ? arg[i + 1] : L'\0'; + if (ch == L'\\') { + switch (nch) { + case L'a': ch = L'\a'; ++i; break; + case L'b': ch = L'\b'; ++i; break; + case L'e': ch = L'\x1b'; ++i; break; + case L'f': ch = L'\f'; ++i; break; + case L'n': ch = L'\n'; ++i; break; + case L'r': ch = L'\r'; ++i; break; + case L't': ch = L'\t'; ++i; break; + case L'v': ch = L'\v'; ++i; break; + case L'\\': ch = L'\\'; ++i; break; + case L'\'': ch = L'\''; ++i; break; + case L'\"': ch = L'\"'; ++i; break; + case L'\?': ch = L'\?'; ++i; break; + case L'x': + if (i + 3 < arg.size()) { + bool invalid = false; + uint32_t d1 = parseHex(arg[i + 2], invalid); + uint32_t d2 = parseHex(arg[i + 3], invalid); + if (!invalid) { + i += 3; + ch = (d1 << 4) | d2; + } + } + break; + case L'u': + if (i + 5 < arg.size()) { + bool invalid = false; + uint32_t d1 = parseHex(arg[i + 2], invalid); + uint32_t d2 = parseHex(arg[i + 3], invalid); + uint32_t d3 = parseHex(arg[i + 4], invalid); + uint32_t d4 = parseHex(arg[i + 5], invalid); + if (!invalid) { + i += 5; + ch = (d1 << 24) | (d2 << 16) | (d3 << 8) | d4; + } + } + break; + default: break; + } + } + out.push_back(ch); + } + } + + DWORD actual = 0; + if (!WriteConsoleW( + GetStdHandle(STD_OUTPUT_HANDLE), + out.c_str(), + out.size(), + &actual, + nullptr)) { + fprintf(stderr, "WriteConsole failed (is stdout a console?)\n"); + exit(1); + } + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/build32.sh b/src/libs/3rdparty/winpty/misc/build32.sh new file mode 100644 index 0000000000..162993ce33 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/build32.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e +name=$1 +name=${name%.} +name=${name%.cc} +name=${name%.exe} +echo Compiling $name.cc to $name.exe +i686-w64-mingw32-g++.exe -static -std=c++11 $name.cc -o $name.exe +i686-w64-mingw32-strip $name.exe diff --git a/src/libs/3rdparty/winpty/misc/build64.sh b/src/libs/3rdparty/winpty/misc/build64.sh new file mode 100644 index 0000000000..6757967684 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/build64.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e +name=$1 +name=${name%.} +name=${name%.cc} +name=${name%.exe} +echo Compiling $name.cc to $name.exe +x86_64-w64-mingw32-g++.exe -static -std=c++11 $name.cc -o $name.exe +x86_64-w64-mingw32-strip $name.exe diff --git a/src/libs/3rdparty/winpty/misc/color-test.sh b/src/libs/3rdparty/winpty/misc/color-test.sh new file mode 100644 index 0000000000..065c8094e2 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/color-test.sh @@ -0,0 +1,212 @@ +#!/bin/bash + +FORE=$1 +BACK=$2 +FILL=$3 + +if [ "$FORE" = "" ]; then + FORE=DefaultFore +fi +if [ "$BACK" = "" ]; then + BACK=DefaultBack +fi + +# To detect color changes, we want a character that fills the whole cell +# if possible. U+2588 is perfect, except that it becomes invisible in the +# original xterm, when bolded. For that terminal, use something else, like +# "#" or "@". +if [ "$FILL" = "" ]; then + FILL="█" +fi + +# SGR (Select Graphic Rendition) +s() { + printf '\033[0m' + while [ "$1" != "" ]; do + printf '\033['"$1"'m' + shift + done +} + +# Print +p() { + echo -n "$@" +} + +# Print with newline +pn() { + echo "$@" +} + +# For practical reasons, sandwich black and white in-between the other colors. +FORE_COLORS="31 30 37 32 33 34 35 36" +BACK_COLORS="41 40 47 42 43 44 45 46" + + + +### Test order of Invert(7) -- it does not matter what order it appears in. + +# The Red color setting here (31) is shadowed by the green setting (32). The +# Reverse flag does not cause (32) to alter the background color immediately; +# instead, the Reverse flag is applied once to determine the final effective +# Fore/Back colors. +s 7 31 32; p " -- Should be: $BACK-on-green -- "; s; pn +s 31 7 32; p " -- Should be: $BACK-on-green -- "; s; pn +s 31 32 7; p " -- Should be: $BACK-on-green -- "; s; pn + +# As above, but for the background color. +s 7 41 42; p " -- Should be: green-on-$FORE -- "; s; pn +s 41 7 42; p " -- Should be: green-on-$FORE -- "; s; pn +s 41 42 7; p " -- Should be: green-on-$FORE -- "; s; pn + +# One last, related test +s 7; p "Invert text"; s 7 1; p " with some words bold"; s; pn; +s 0; p "Normal text"; s 0 1; p " with some words bold"; s; pn; + +pn + + + +### Test effect of Bold(1) on color, with and without Invert(7). + +# The Bold flag does not affect the background color when Reverse is missing. +# There should always be 8 colored boxes. +p " " +for x in $BACK_COLORS; do + s $x; p "-"; s $x 1; p "-" +done +s; pn " Bold should not affect background" + +# On some terminals, Bold affects color, and on some it doesn't. If there +# are only 8 colored boxes, then the next two tests will also show 8 colored +# boxes. If there are 16 boxes, then exactly one of the next two tests will +# also have 16 boxes. +p " " +for x in $FORE_COLORS; do + s $x; p "$FILL"; s $x 1; p "$FILL" +done +s; pn " Does bold affect foreground color?" + +# On some terminals, Bold+Invert highlights the final Background color. +p " " +for x in $FORE_COLORS; do + s $x 7; p "-"; s $x 7 1; p "-" +done +s; pn " Test if Bold+Invert affects background color" + +# On some terminals, Bold+Invert highlights the final Foreground color. +p " " +for x in $BACK_COLORS; do + s $x 7; p "$FILL"; s $x 7 1; p "$FILL" +done +s; pn " Test if Bold+Invert affects foreground color" + +pn + + + +### Test for support of ForeHi and BackHi properties. + +# ForeHi +p " " +for x in $FORE_COLORS; do + hi=$(( $x + 60 )) + s $x; p "$FILL"; s $hi; p "$FILL" +done +s; pn " Test for support of ForeHi colors" +p " " +for x in $FORE_COLORS; do + hi=$(( $x + 60 )) + s $x; p "$FILL"; s $x $hi; p "$FILL" +done +s; pn " Test for support of ForeHi colors (w/compat)" + +# BackHi +p " " +for x in $BACK_COLORS; do + hi=$(( $x + 60 )) + s $x; p "-"; s $hi; p "-" +done +s; pn " Test for support of BackHi colors" +p " " +for x in $BACK_COLORS; do + hi=$(( $x + 60 )) + s $x; p "-"; s $x $hi; p "-" +done +s; pn " Test for support of BackHi colors (w/compat)" + +pn + + + +### Identify the default fore and back colors. + +pn "Match default fore and back colors against 16-color palette" +pn " ==fore== ==back==" +for fore in $FORE_COLORS; do + forehi=$(( $fore + 60 )) + back=$(( $fore + 10 )) + backhi=$(( $back + 60 )) + p " " + s $fore; p "$FILL"; s; p "$FILL"; s $fore; p "$FILL"; s; p " " + s $forehi; p "$FILL"; s; p "$FILL"; s $forehi; p "$FILL"; s; p " " + s $back; p "-"; s; p "-"; s $back; p "-"; s; p " " + s $backhi; p "-"; s; p "-"; s $backhi; p "-"; s; p " " + pn " $fore $forehi $back $backhi" +done + +pn + + + +### Test coloring of rest-of-line. + +# +# When a new line is scrolled in, every cell in the line receives the +# current background color, which can be the default/transparent color. +# + +p "Newline with red background: usually no red -->"; s 41; pn +s; pn "This text is plain, but rest is red if scrolled -->" +s; p " "; s 41; printf '\033[1K'; s; printf '\033[1C'; pn "<-- red Erase-in-Line to beginning" +s; p "red Erase-in-Line to end -->"; s 41; printf '\033[0K'; s; pn +pn + + + +### Moving the cursor around does not change colors of anything. + +pn "Test modifying uncolored lines with a colored SGR:" +pn "aaaa" +pn +pn "____e" +s 31 42; printf '\033[4C\033[3A'; pn "bb" +pn "cccc" +pn "dddd" +s; pn + +pn "Test modifying colored+inverted+bold line with plain text:" +s 42 31 7 1; printf 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\r'; +s; pn "This text is plain and followed by green-on-red -->" +pn + + + +### Full-width character overwriting + +pn 'Overwrite part of a full-width char with a half-width char' +p 'initial U+4000 ideographs -->'; s 31 42; p '䀀䀀'; s; pn +p 'write X to index #1 -->'; s 31 42; p '䀀䀀'; s 35 44; printf '\033[24G'; p X; s; pn +p 'write X to index #2 -->'; s 31 42; p '䀀䀀'; s 35 44; printf '\033[25G'; p X; s; pn +p 'write X to index #3 -->'; s 31 42; p '䀀䀀'; s 35 44; printf '\033[26G'; p X; s; pn +p 'write X to index #4 -->'; s 31 42; p '䀀䀀'; s 35 44; printf '\033[27G'; p X; s; pn +pn + +pn 'Verify that Erase-in-Line can "fix" last char in line' +p 'original -->'; s 31 42; p '䀀䀀'; s; pn +p 'overwrite -->'; s 31 42; p '䀀䀀'; s 35 44; printf '\033[30G'; p 'XXX'; s; pn +p 'overwrite + Erase-in-Line -->'; s 31 42; p '䀀䀀'; s 35 44; printf '\033[30G'; p 'XXX'; s; printf '\033[0K'; pn +p 'original -->'; s 31 42; p 'X䀀䀀'; s; pn +p 'overwrite -->'; s 31 42; p 'X䀀䀀'; s 35 44; printf '\033[30G'; p 'ーー'; s; pn +p 'overwrite + Erase-in-Line -->'; s 31 42; p 'X䀀䀀'; s 35 44; printf '\033[30G'; p 'ーー'; s; printf '\033[0K'; pn +pn diff --git a/src/libs/3rdparty/winpty/misc/font-notes.txt b/src/libs/3rdparty/winpty/misc/font-notes.txt new file mode 100644 index 0000000000..d4e36d8e25 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/font-notes.txt @@ -0,0 +1,300 @@ +================================================================== +Notes regarding fonts, code pages, and East Asian character widths +================================================================== + + +Registry settings +================= + + * There are console registry settings in `HKCU\Console`. That key has many + default settings (e.g. the default font settings) and also per-app subkeys + for app-specific overrides. + + * It is possible to override the code page with an app-specific setting. + + * There are registry settings in + `HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Console`. In particular, + the `TrueTypeFont` subkey has a list of suitable font names associated with + various CJK code pages, as well as default font names. + + * There are two values in `HKLM\SYSTEM\CurrentControlSet\Control\Nls\CodePage` + that specify the current code pages -- `OEMCP` and `ACP`. Setting the + system locale via the Control Panel's "Region" or "Language" dialogs seems + to change these code page values. + + +Console fonts +============= + + * The `FontFamily` field of `CONSOLE_FONT_INFOEX` has two parts: + - The high four bits can be exactly one of the `FF_xxxx` font families: + FF_DONTCARE(0x00) + FF_ROMAN(0x10) + FF_SWISS(0x20) + FF_MODERN(0x30) + FF_SCRIPT(0x40) + FF_DECORATIVE(0x50) + - The low four bits are a bitmask: + TMPF_FIXED_PITCH(1) -- actually means variable pitch + TMPF_VECTOR(2) + TMPF_TRUETYPE(4) + TMPF_DEVICE(8) + + * Each console has its own independent console font table. The current font + is identified with an index into this table. The size of the table is + returned by the undocumented `GetNumberOfConsoleFonts` API. It is apparently + possible to get the table size without this API, by instead calling + `GetConsoleFontSize` on each nonnegative index starting with 0 until the API + fails by returning (0, 0). + + * The font table grows dynamically. Each time the console is configured with + a previously-unused (FaceName, Size) combination, two entries are added to + the font table -- one with normal weight and one with bold weight. Fonts + added this way are always TrueType fonts. + + * Initially, the font table appears to contain only raster fonts. For + example, on an English Windows 8 installation, here is the initial font + table: + font 0: 4x6 + font 1: 6x8 + font 2: 8x8 + font 3: 16x8 + font 4: 5x12 + font 5: 7x12 + font 6: 8x12 -- the current font + font 7: 16x12 + font 8: 12x16 + font 9: 10x18 + `GetNumberOfConsoleFonts` returns 10, and this table matches the raster font + sizes according to the console properties dialog. + + * With a Japanese or Chinese locale, the initial font table appears to contain + the sizes applicable to both the East Asian raster font, as well as the + sizes for the CP437/CP1252 raster font. + + * The index passed to `SetCurrentConsoleFontEx` apparently has no effect. + The undocumented `SetConsoleFont` API, however, accepts *only* a font index, + and on Windows 8 English, it switches between all 10 fonts, even font index + #0. + + * If the index passed to `SetConsoleFont` identifies a Raster Font + incompatible with the current code page, then another Raster Font is + activated. + + * Passing "Terminal" to `SetCurrentConsoleFontEx` seems to have no effect. + Perhaps relatedly, `SetCurrentConsoleFontEx` does not fail if it is given a + bogus `FaceName`. Some font is still chosen and activated. Passing a face + name and height seems to work reliably, modulo the CP936 issue described + below. + + +Console fonts and code pages +============================ + + * On an English Windows installation, the default code page is 437, and it + cannot be set to 932 (Shift-JIS). (The API call fails.) Changing the + system locale to "Japanese (Japan)" using the Region/Language dialog + changes the default CP to 932 and permits changing the console CP between + 437 and 932. + + * A console has both an input code page and an output code page + (`{Get,Set}ConsoleCP` and `{Get,Set}ConsoleOutputCP`). I'm not going to + distinguish between the two for this document; presumably only the output + CP matters. The code page can change while the console is open, e.g. + by running `mode con: cp select={932,437,1252}` or by calling + `SetConsoleOutputCP`. + + * The current code page restricts which TrueType fonts and which Raster Font + sizes are available in the console properties dialog. This can change + while the console is open. + + * Changing the code page almost(?) always changes the current console font. + So far, I don't know how the new font is chosen. + + * With a CP of 932, the only TrueType font available in the console properties + dialog is "MS Gothic", displayed as "MS ゴシック". It is still possible to + use the English-default TrueType console fonts, Lucida Console and Consolas, + via `SetCurrentConsoleFontEx`. + + * When using a Raster Font and CP437 or CP1252, writing a UTF-16 codepoint not + representable in the code page instead writes a question mark ('?') to the + console. This conversion does not apply with a TrueType font, nor with the + Raster Font for CP932 or CP936. + + +ReadConsoleOutput and double-width characters +============================================== + + * With a Raster Font active, when `ReadConsoleOutputW` reads two cells of a + double-width character, it fills only a single `CHAR_INFO` structure. The + unused trailing `CHAR_INFO` structures are zero-filled. With a TrueType + font active, `ReadConsoleOutputW` instead fills two `CHAR_INFO` structures, + the first marked with `COMMON_LVB_LEADING_BYTE` and the second marked with + `COMMON_LVB_TRAILING_BYTE`. The flag is a misnomer--there aren't two + *bytes*, but two cells, and they have equal `CHAR_INFO.Char.UnicodeChar` + values. + + * `ReadConsoleOutputA`, on the other hand, reads two `CHAR_INFO` cells, and + if the UTF-16 value can be represented as two bytes in the ANSI/OEM CP, then + the two bytes are placed in the two `CHAR_INFO.Char.AsciiChar` values, and + the `COMMON_LVB_{LEADING,TRAILING}_BYTE` values are also used. If the + codepoint isn't representable, I don't remember what happens -- I think the + `AsciiChar` values take on an invalid marker. + + * Reading only one cell of a double-width character reads a space (U+0020) + instead. Raster-vs-TrueType and wide-vs-ANSI do not matter. + - XXX: what about attributes? Can a double-width character have mismatched + color attributes? + - XXX: what happens when writing to just one cell of a double-width + character? + + +Default Windows fonts for East Asian languages +============================================== +CP932 / Japanese: "MS ゴシック" (MS Gothic) +CP936 / Chinese Simplified: "新宋体" (SimSun) + + +Unreliable character width (half-width vs full-width) +===================================================== + +The half-width vs full-width status of a codepoint depends on at least these variables: + * OS version (Win10 legacy and new modes are different versions) + * system locale (English vs Japanese vs Chinese Simplified vs Chinese Traditional, etc) + * code page (437 vs 932 vs 936, etc) + * raster vs TrueType (Terminal vs MS Gothic vs SimSun, etc) + * font size + * rendered-vs-model (rendered width can be larger or smaller than model width) + +Example 1: U+2014 (EM DASH): East_Asian_Width: Ambiguous +-------------------------------------------------------- + rendered modeled +CP932: Win7/8 Raster Fonts half half +CP932: Win7/8 Gothic 14/15px half full +CP932: Win7/8 Consolas 14/15px half full +CP932: Win7/8 Lucida Console 14px half full +CP932: Win7/8 Lucida Console 15px half half +CP932: Win10New Raster Fonts half half +CP932: Win10New Gothic 14/15px half half +CP932: Win10New Consolas 14/15px half half +CP932: Win10New Lucida Console 14/15px half half + +CP936: Win7/8 Raster Fonts full full +CP936: Win7/8 SimSun 14px full full +CP936: Win7/8 SimSun 15px full half +CP936: Win7/8 Consolas 14/15px half full +CP936: Win10New Raster Fonts full full +CP936: Win10New SimSum 14/15px full full +CP936: Win10New Consolas 14/15px half half + +Example 2: U+3044 (HIRAGANA LETTER I): East_Asian_Width: Wide +------------------------------------------------------------- + rendered modeled +CP932: Win7/8/10N Raster Fonts full full +CP932: Win7/8/10N Gothic 14/15px full full +CP932: Win7/8/10N Consolas 14/15px half(*2) full +CP932: Win7/8/10N Lucida Console 14/15px half(*3) full + +CP936: Win7/8/10N Raster Fonts full full +CP936: Win7/8/10N SimSun 14/15px full full +CP936: Win7/8/10N Consolas 14/15px full full + +Example 3: U+30FC (KATAKANA-HIRAGANA PROLONGED SOUND MARK): East_Asian_Width: Wide +---------------------------------------------------------------------------------- + rendered modeled +CP932: Win7 Raster Fonts full full +CP932: Win7 Gothic 14/15px full full +CP932: Win7 Consolas 14/15px half(*2) full +CP932: Win7 Lucida Console 14px half(*3) full +CP932: Win7 Lucida Console 15px half(*3) half +CP932: Win8 Raster Fonts full full +CP932: Win8 Gothic 14px full half +CP932: Win8 Gothic 15px full full +CP932: Win8 Consolas 14/15px half(*2) full +CP932: Win8 Lucida Console 14px half(*3) full +CP932: Win8 Lucida Console 15px half(*3) half +CP932: Win10New Raster Fonts full full +CP932: Win10New Gothic 14/15px full full +CP932: Win10New Consolas 14/15px half(*2) half +CP932: Win10New Lucida Console 14/15px half(*2) half + +CP936: Win7/8 Raster Fonts full full +CP936: Win7/8 SimSun 14px full full +CP936: Win7/8 SimSun 15px full half +CP936: Win7/8 Consolas 14px full full +CP936: Win7/8 Consolas 15px full half +CP936: Win10New Raster Fonts full full +CP936: Win10New SimSum 14/15px full full +CP936: Win10New Consolas 14/15px full full + +Example 4: U+4000 (CJK UNIFIED IDEOGRAPH-4000): East_Asian_Width: Wide +---------------------------------------------------------------------- + rendered modeled +CP932: Win7 Raster Fonts half(*1) half +CP932: Win7 Gothic 14/15px full full +CP932: Win7 Consolas 14/15px half(*2) full +CP932: Win7 Lucida Console 14px half(*3) full +CP932: Win7 Lucida Console 15px half(*3) half +CP932: Win8 Raster Fonts half(*1) half +CP932: Win8 Gothic 14px full half +CP932: Win8 Gothic 15px full full +CP932: Win8 Consolas 14/15px half(*2) full +CP932: Win8 Lucida Console 14px half(*3) full +CP932: Win8 Lucida Console 15px half(*3) half +CP932: Win10New Raster Fonts half(*1) half +CP932: Win10New Gothic 14/15px full full +CP932: Win10New Consolas 14/15px half(*2) half +CP932: Win10New Lucida Console 14/15px half(*2) half + +CP936: Win7/8 Raster Fonts full full +CP936: Win7/8 SimSun 14px full full +CP936: Win7/8 SimSun 15px full half +CP936: Win7/8 Consolas 14px full full +CP936: Win7/8 Consolas 15px full half +CP936: Win10New Raster Fonts full full +CP936: Win10New SimSum 14/15px full full +CP936: Win10New Consolas 14/15px full full + +(*1) Rendered as a half-width filled white box +(*2) Rendered as a half-width box with a question mark inside +(*3) Rendered as a half-width empty box +(!!) One of the only places in Win10New where rendered and modeled width disagree + + +Windows quirk: unreliable font heights with CP936 / Chinese Simplified +====================================================================== + +When I set the font to 新宋体 17px, using either the properties dialog or +`SetCurrentConsoleFontEx`, the height reported by `GetCurrentConsoleFontEx` is +not 17, but is instead 19. The same problem does not affect Raster Fonts, +nor have I seen the problem in the English or Japanese locales. I observed +this with Windows 7 and Windows 10 new mode. + +If I set the font using the facename, width, *and* height, then the +`SetCurrentConsoleFontEx` and `GetCurrentConsoleFontEx` values agree. If I +set the font using *only* the facename and height, then the two values +disagree. + + +Windows bug: GetCurrentConsoleFontEx is initially invalid +========================================================= + + - Assume there is no configured console font name in the registry. In this + case, the console defaults to a raster font. + - Open a new console and call the `GetCurrentConsoleFontEx` API. + - The `FaceName` field of the returned `CONSOLE_FONT_INFOEX` data + structure is incorrect. On Windows 7, 8, and 10, I observed that the + field was blank. On Windows 8, occasionally, it instead contained: + U+AE72 U+75BE U+0001 + The other fields of the structure all appeared correct: + nFont=6 dwFontSize=(8,12) FontFamily=0x30 FontWeight=400 + - The `FaceName` field becomes initialized easily: + - Open the console properties dialog and click OK. (Cancel is not + sufficient.) + - Call the undocumented `SetConsoleFont` with the current font table + index, which is 6 in the example above. + - It seems that the console uncritically accepts whatever string is + stored in the registry, including a blank string, and passes it on the + the `GetCurrentConsoleFontEx` caller. It is possible to get the console + to *write* a blank setting into the registry -- simply open the console + (default or app-specific) properties and click OK. diff --git a/src/libs/3rdparty/winpty/misc/winbug-15048.cc b/src/libs/3rdparty/winpty/misc/winbug-15048.cc new file mode 100644 index 0000000000..0e98d648c5 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/winbug-15048.cc @@ -0,0 +1,201 @@ +/* + +Test program demonstrating a problem in Windows 15048's ReadConsoleOutput API. + +To compile: + + cl /nologo /EHsc winbug-15048.cc shell32.lib + +Example of regressed input: + +Case 1: + + > chcp 932 + > winbug-15048 -face-gothic 3044 + + Correct output: + + 1**34 (nb: U+3044 replaced with '**' to avoid MSVC encoding warning) + 5678 + + ReadConsoleOutputW (both rows, 3 cols) + row 0: U+0031(0007) U+3044(0107) U+3044(0207) U+0033(0007) + row 1: U+0035(0007) U+0036(0007) U+0037(0007) U+0038(0007) + + ReadConsoleOutputW (both rows, 4 cols) + row 0: U+0031(0007) U+3044(0107) U+3044(0207) U+0033(0007) U+0034(0007) + row 1: U+0035(0007) U+0036(0007) U+0037(0007) U+0038(0007) U+0020(0007) + + ReadConsoleOutputW (second row) + row 1: U+0035(0007) U+0036(0007) U+0037(0007) U+0038(0007) U+0020(0007) + + ... + + Win10 15048 bad output: + + 1**34 + 5678 + + ReadConsoleOutputW (both rows, 3 cols) + row 0: U+0031(0007) U+3044(0007) U+0033(0007) U+0035(0007) + row 1: U+0036(0007) U+0037(0007) U+0038(0007) U+0000(0000) + + ReadConsoleOutputW (both rows, 4 cols) + row 0: U+0031(0007) U+3044(0007) U+0033(0007) U+0034(0007) U+0035(0007) + row 1: U+0036(0007) U+0037(0007) U+0038(0007) U+0020(0007) U+0000(0000) + + ReadConsoleOutputW (second row) + row 1: U+0035(0007) U+0036(0007) U+0037(0007) U+0038(0007) U+0020(0007) + + ... + + The U+3044 character (HIRAGANA LETTER I) occupies two columns, but it only + fills one record in the ReadConsoleOutput output buffer, which has the + effect of shifting the first cell of the second row into the last cell of + the first row. Ordinarily, the first and second cells would also have the + COMMON_LVB_LEADING_BYTE and COMMON_LVB_TRAILING_BYTE attributes set, which + allows winpty to detect the double-column character. + +Case 2: + + > chcp 437 + > winbug-15048 -face "Lucida Console" -h 4 221A + + The same issue happens with U+221A (SQUARE ROOT), but only in certain + fonts. The console seems to think this character occupies two columns + if the font is sufficiently small. The Windows console properties dialog + doesn't allow fonts below 5 pt, but winpty tries to use 2pt and 4pt Lucida + Console to allow very large console windows. + +Case 3: + + > chcp 437 + > winbug-15048 -face "Lucida Console" -h 12 FF12 + + The console selection system thinks U+FF12 (FULLWIDTH DIGIT TWO) occupies + two columns, which happens to be correct, but it's displayed as a single + column unrecognized character. It otherwise behaves the same as the other + cases. + +*/ + +#include +#include +#include +#include +#include +#include + +#include + +#define COUNT_OF(array) (sizeof(array) / sizeof((array)[0])) + +// See https://en.wikipedia.org/wiki/List_of_CJK_fonts +const wchar_t kMSGothic[] = { 0xff2d, 0xff33, 0x0020, 0x30b4, 0x30b7, 0x30c3, 0x30af, 0 }; // Japanese +const wchar_t kNSimSun[] = { 0x65b0, 0x5b8b, 0x4f53, 0 }; // Simplified Chinese +const wchar_t kMingLight[] = { 0x7d30, 0x660e, 0x9ad4, 0 }; // Traditional Chinese +const wchar_t kGulimChe[] = { 0xad74, 0xb9bc, 0xccb4, 0 }; // Korean + +static void set_font(const wchar_t *name, int size) { + const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_FONT_INFOEX fontex {}; + fontex.cbSize = sizeof(fontex); + fontex.dwFontSize.Y = size; + fontex.FontWeight = 400; + fontex.FontFamily = 0x36; + wcsncpy(fontex.FaceName, name, COUNT_OF(fontex.FaceName)); + assert(SetCurrentConsoleFontEx(conout, FALSE, &fontex)); +} + +static void usage(const wchar_t *prog) { + printf("Usage: %ls [options]\n", prog); + printf(" -h HEIGHT\n"); + printf(" -face FACENAME\n"); + printf(" -face-{gothic|simsun|minglight|gulimche) [JP,CN-sim,CN-tra,KR]\n"); + printf(" hhhh -- print U+hhhh\n"); + exit(1); +} + +static void dump_region(SMALL_RECT region, const char *name) { + const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE); + + CHAR_INFO buf[1000]; + memset(buf, 0xcc, sizeof(buf)); + + const int w = region.Right - region.Left + 1; + const int h = region.Bottom - region.Top + 1; + + assert(ReadConsoleOutputW( + conout, buf, { (short)w, (short)h }, { 0, 0 }, + ®ion)); + + printf("\n"); + printf("ReadConsoleOutputW (%s)\n", name); + for (int y = 0; y < h; ++y) { + printf("row %d: ", region.Top + y); + for (int i = 0; i < region.Left * 13; ++i) { + printf(" "); + } + for (int x = 0; x < w; ++x) { + const int i = y * w + x; + printf("U+%04x(%04x) ", buf[i].Char.UnicodeChar, buf[i].Attributes); + } + printf("\n"); + } +} + +int main() { + wchar_t *cmdline = GetCommandLineW(); + int argc = 0; + wchar_t **argv = CommandLineToArgvW(cmdline, &argc); + const wchar_t *font_name = L"Lucida Console"; + int font_height = 8; + int test_ch = 0xff12; // U+FF12 FULLWIDTH DIGIT TWO + + for (int i = 1; i < argc; ++i) { + const std::wstring arg = argv[i]; + const std::wstring next = i + 1 < argc ? argv[i + 1] : L""; + if (arg == L"-face" && i + 1 < argc) { + font_name = argv[i + 1]; + i++; + } else if (arg == L"-face-gothic") { + font_name = kMSGothic; + } else if (arg == L"-face-simsun") { + font_name = kNSimSun; + } else if (arg == L"-face-minglight") { + font_name = kMingLight; + } else if (arg == L"-face-gulimche") { + font_name = kGulimChe; + } else if (arg == L"-h" && i + 1 < argc) { + font_height = _wtoi(next.c_str()); + i++; + } else if (arg.c_str()[0] != '-') { + test_ch = wcstol(arg.c_str(), NULL, 16); + } else { + printf("Unrecognized argument: %ls\n", arg.c_str()); + usage(argv[0]); + } + } + + const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE); + + set_font(font_name, font_height); + + system("cls"); + DWORD actual = 0; + wchar_t output[] = L"1234\n5678\n"; + output[1] = test_ch; + WriteConsoleW(conout, output, 10, &actual, nullptr); + + dump_region({ 0, 0, 3, 1 }, "both rows, 3 cols"); + dump_region({ 0, 0, 4, 1 }, "both rows, 4 cols"); + dump_region({ 0, 1, 4, 1 }, "second row"); + dump_region({ 0, 0, 4, 0 }, "first row"); + dump_region({ 1, 0, 4, 0 }, "first row, skip 1"); + dump_region({ 2, 0, 4, 0 }, "first row, skip 2"); + dump_region({ 3, 0, 4, 0 }, "first row, skip 3"); + + set_font(font_name, 14); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/ship/build-pty4j-libpty.bat b/src/libs/3rdparty/winpty/ship/build-pty4j-libpty.bat new file mode 100644 index 0000000000..b6bca7b079 --- /dev/null +++ b/src/libs/3rdparty/winpty/ship/build-pty4j-libpty.bat @@ -0,0 +1,36 @@ +@echo off + +setlocal +cd %~dp0.. +set Path=C:\Python27;C:\Program Files\Git\cmd;%Path% + +call "%VS140COMNTOOLS%\VsDevCmd.bat" || goto :fail + +rmdir /s/q build-libpty 2>NUL +mkdir build-libpty\win +mkdir build-libpty\win\x86 +mkdir build-libpty\win\x86_64 +mkdir build-libpty\win\xp + +rmdir /s/q src\Release 2>NUL +rmdir /s/q src\.vs 2>NUL +del src\*.vcxproj src\*.vcxproj.filters src\*.sln src\*.sdf 2>NUL + +call vcbuild.bat --msvc-platform Win32 --gyp-msvs-version 2015 --toolset v140_xp || goto :fail +copy src\Release\Win32\winpty.dll build-libpty\win\xp || goto :fail +copy src\Release\Win32\winpty-agent.exe build-libpty\win\xp || goto :fail + +call vcbuild.bat --msvc-platform Win32 --gyp-msvs-version 2015 || goto :fail +copy src\Release\Win32\winpty.dll build-libpty\win\x86 || goto :fail +copy src\Release\Win32\winpty-agent.exe build-libpty\win\x86 || goto :fail + +call vcbuild.bat --msvc-platform x64 --gyp-msvs-version 2015 || goto :fail +copy src\Release\x64\winpty.dll build-libpty\win\x86_64 || goto :fail +copy src\Release\x64\winpty-agent.exe build-libpty\win\x86_64 || goto :fail + +echo success +goto :EOF + +:fail +echo error: build failed +exit /b 1 diff --git a/src/libs/3rdparty/winpty/ship/common_ship.py b/src/libs/3rdparty/winpty/ship/common_ship.py new file mode 100644 index 0000000000..b587ac7ce1 --- /dev/null +++ b/src/libs/3rdparty/winpty/ship/common_ship.py @@ -0,0 +1,89 @@ +import os +import sys + +# These scripts need to continue using Python 2 rather than 3, because +# make_msvc_package.py puts the current Python interpreter on the PATH for the +# sake of gyp, and gyp doesn't work with Python 3 yet. +# https://bugs.chromium.org/p/gyp/issues/detail?id=36 +if os.name != "nt": + sys.exit("Error: ship scripts require native Python 2.7. (wrong os.name)") +if sys.version_info[0:2] != (2,7): + sys.exit("Error: ship scripts require native Python 2.7. (wrong version)") + +import glob +import hashlib +import shutil +import subprocess +from distutils.spawn import find_executable + +topDir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) + +with open(topDir + "/VERSION.txt", "rt") as f: + winptyVersion = f.read().strip() + +def rmrf(patterns): + for pattern in patterns: + for path in glob.glob(pattern): + if os.path.isdir(path) and not os.path.islink(path): + print("+ rm -r " + path) + sys.stdout.flush() + shutil.rmtree(path) + elif os.path.isfile(path): + print("+ rm " + path) + sys.stdout.flush() + os.remove(path) + +def mkdir(path): + if not os.path.isdir(path): + os.makedirs(path) + +def requireExe(name, guesses): + if find_executable(name) is None: + for guess in guesses: + if os.path.exists(guess): + newDir = os.path.dirname(guess) + print("Adding " + newDir + " to Path to provide " + name) + os.environ["Path"] = newDir + ";" + os.environ["Path"] + ret = find_executable(name) + if ret is None: + sys.exit("Error: required EXE is missing from Path: " + name) + return ret + +class ModifyEnv: + def __init__(self, **kwargs): + self._changes = dict(kwargs) + self._original = dict() + + def __enter__(self): + for var, val in self._changes.items(): + self._original[var] = os.environ[var] + os.environ[var] = val + + def __exit__(self, type, value, traceback): + for var, val in self._original.items(): + os.environ[var] = val + +def sha256(path): + with open(path, "rb") as fp: + return hashlib.sha256(fp.read()).hexdigest() + +def checkSha256(path, expected): + actual = sha256(path) + if actual != expected: + sys.exit("error: sha256 hash mismatch on {}: expected {}, found {}".format( + path, expected, actual)) + +requireExe("git.exe", [ + "C:\\Program Files\\Git\\cmd\\git.exe", + "C:\\Program Files (x86)\\Git\\cmd\\git.exe" +]) + +commitHash = subprocess.check_output(["git.exe", "rev-parse", "HEAD"]).strip() +defaultPathEnviron = "C:\\Windows\\System32;C:\\Windows" + +ZIP_TOOL = requireExe("7z.exe", [ + "C:\\Program Files\\7-Zip\\7z.exe", + "C:\\Program Files (x86)\\7-Zip\\7z.exe", +]) + +requireExe("curl.exe", []) diff --git a/src/libs/3rdparty/winpty/ship/make_msvc_package.py b/src/libs/3rdparty/winpty/ship/make_msvc_package.py new file mode 100644 index 0000000000..2d10aac787 --- /dev/null +++ b/src/libs/3rdparty/winpty/ship/make_msvc_package.py @@ -0,0 +1,163 @@ +#!python + +# Copyright (c) 2016 Ryan Prichard +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +# +# Run with native CPython 2.7. +# +# This script looks for MSVC using a version-specific environment variable, +# such as VS140COMNTOOLS for MSVC 2015. +# + +import common_ship + +import argparse +import os +import shutil +import subprocess +import sys + +os.chdir(common_ship.topDir) + +MSVC_VERSION_TABLE = { + "2015" : { + "package_name" : "msvc2015", + "gyp_version" : "2015", + "common_tools_env" : "VS140COMNTOOLS", + "xp_toolset" : "v140_xp", + }, + "2013" : { + "package_name" : "msvc2013", + "gyp_version" : "2013", + "common_tools_env" : "VS120COMNTOOLS", + "xp_toolset" : "v120_xp", + }, +} + +ARCH_TABLE = { + "x64" : { + "msvc_platform" : "x64", + }, + "ia32" : { + "msvc_platform" : "Win32", + }, +} + +def readArguments(): + parser = argparse.ArgumentParser() + parser.add_argument("--msvc-version", default="2015") + ret = parser.parse_args() + if ret.msvc_version not in MSVC_VERSION_TABLE: + sys.exit("Error: unrecognized version: " + ret.msvc_version + ". " + + "Versions: " + " ".join(sorted(MSVC_VERSION_TABLE.keys()))) + return ret + +ARGS = readArguments() + +def checkoutGyp(): + if os.path.isdir("build-gyp"): + return + subprocess.check_call([ + "git.exe", + "clone", + "https://chromium.googlesource.com/external/gyp", + "build-gyp" + ]) + +def cleanMsvc(): + common_ship.rmrf(""" + src/Release src/.vs src/gen + src/*.vcxproj src/*.vcxproj.filters src/*.sln src/*.sdf + """.split()) + +def build(arch, packageDir, xp=False): + archInfo = ARCH_TABLE[arch] + versionInfo = MSVC_VERSION_TABLE[ARGS.msvc_version] + + devCmdPath = os.path.join(os.environ[versionInfo["common_tools_env"]], "VsDevCmd.bat") + if not os.path.isfile(devCmdPath): + sys.exit("Error: MSVC environment script missing: " + devCmdPath) + + toolsetArgument = " --toolset {}".format(versionInfo["xp_toolset"]) if xp else "" + newEnv = os.environ.copy() + newEnv["PATH"] = os.path.dirname(sys.executable) + ";" + common_ship.defaultPathEnviron + commandLine = ( + '"' + devCmdPath + '" && ' + + " vcbuild.bat" + + " --gyp-msvs-version " + versionInfo["gyp_version"] + + " --msvc-platform " + archInfo["msvc_platform"] + + " --commit-hash " + common_ship.commitHash + + toolsetArgument + ) + + subprocess.check_call(commandLine, shell=True, env=newEnv) + + archPackageDir = os.path.join(packageDir, arch) + if xp: + archPackageDir += "_xp" + + common_ship.mkdir(archPackageDir + "/bin") + common_ship.mkdir(archPackageDir + "/lib") + + binSrc = os.path.join(common_ship.topDir, "src/Release", archInfo["msvc_platform"]) + + shutil.copy(binSrc + "/winpty.dll", archPackageDir + "/bin") + shutil.copy(binSrc + "/winpty-agent.exe", archPackageDir + "/bin") + shutil.copy(binSrc + "/winpty-debugserver.exe", archPackageDir + "/bin") + shutil.copy(binSrc + "/winpty.lib", archPackageDir + "/lib") + +def buildPackage(): + versionInfo = MSVC_VERSION_TABLE[ARGS.msvc_version] + + packageName = "winpty-%s-%s" % ( + common_ship.winptyVersion, + versionInfo["package_name"], + ) + + packageRoot = os.path.join(common_ship.topDir, "ship/packages") + packageDir = os.path.join(packageRoot, packageName) + packageFile = packageDir + ".zip" + + common_ship.rmrf([packageDir]) + common_ship.rmrf([packageFile]) + common_ship.mkdir(packageDir) + + checkoutGyp() + cleanMsvc() + build("ia32", packageDir, True) + build("x64", packageDir, True) + cleanMsvc() + build("ia32", packageDir) + build("x64", packageDir) + + topDir = common_ship.topDir + + common_ship.mkdir(packageDir + "/include") + shutil.copy(topDir + "/src/include/winpty.h", packageDir + "/include") + shutil.copy(topDir + "/src/include/winpty_constants.h", packageDir + "/include") + shutil.copy(topDir + "/LICENSE", packageDir) + shutil.copy(topDir + "/README.md", packageDir) + shutil.copy(topDir + "/RELEASES.md", packageDir) + + subprocess.check_call([common_ship.ZIP_TOOL, "a", packageFile, "."], cwd=packageDir) + +if __name__ == "__main__": + buildPackage() diff --git a/src/libs/3rdparty/winpty/ship/ship.py b/src/libs/3rdparty/winpty/ship/ship.py new file mode 100644 index 0000000000..44f5862e3e --- /dev/null +++ b/src/libs/3rdparty/winpty/ship/ship.py @@ -0,0 +1,104 @@ +#!python + +# Copyright (c) 2015 Ryan Prichard +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +# +# Run with native CPython 2.7 on a 64-bit computer. +# + +import common_ship + +from optparse import OptionParser +import multiprocessing +import os +import shutil +import subprocess +import sys + + +def dllVersion(path): + version = subprocess.check_output( + ["powershell.exe", + "[System.Diagnostics.FileVersionInfo]::GetVersionInfo(\"" + path + "\").FileVersion"]) + return version.strip() + + +os.chdir(common_ship.topDir) + + +# Determine other build parameters. +BUILD_KINDS = { + "cygwin": { + "path": ["bin"], + "dll": "bin\\cygwin1.dll", + }, + "msys2": { + "path": ["opt\\bin", "usr\\bin"], + "dll": "usr\\bin\\msys-2.0.dll", + }, +} + + +def buildTarget(kind, syspath, arch): + + binPaths = [os.path.join(syspath, p) for p in BUILD_KINDS[kind]["path"]] + binPaths += common_ship.defaultPathEnviron.split(";") + newPath = ";".join(binPaths) + + dllver = dllVersion(os.path.join(syspath, BUILD_KINDS[kind]["dll"])) + packageName = "winpty-{}-{}-{}-{}".format(common_ship.winptyVersion, kind, dllver, arch) + if os.path.exists("ship\\packages\\" + packageName): + shutil.rmtree("ship\\packages\\" + packageName) + + print("+ Setting PATH to: {}".format(newPath)) + with common_ship.ModifyEnv(PATH=newPath): + subprocess.check_call(["sh.exe", "configure"]) + subprocess.check_call(["make.exe", "clean"]) + makeBaseCmd = [ + "make.exe", + "COMMIT_HASH=" + common_ship.commitHash, + "PREFIX=ship/packages/" + packageName + ] + subprocess.check_call(makeBaseCmd + ["all", "tests", "-j%d" % multiprocessing.cpu_count()]) + subprocess.check_call(["build\\trivial_test.exe"]) + subprocess.check_call(makeBaseCmd + ["install"]) + subprocess.check_call(["tar.exe", "cvfz", + packageName + ".tar.gz", + packageName], cwd=os.path.join(os.getcwd(), "ship", "packages")) + + +def main(): + parser = OptionParser() + parser.add_option("--kind", type="choice", choices=["cygwin", "msys2"]) + parser.add_option("--syspath") + parser.add_option("--arch", type="choice", choices=["ia32", "x64"]) + (args, extra) = parser.parse_args() + + args.kind or parser.error("--kind must be specified") + args.arch or parser.error("--arch must be specified") + args.syspath or parser.error("--syspath must be specified") + extra and parser.error("unexpected positional argument(s)") + + buildTarget(args.kind, args.syspath, args.arch) + + +if __name__ == "__main__": + main() diff --git a/src/libs/3rdparty/winpty/src/CMakeLists.txt b/src/libs/3rdparty/winpty/src/CMakeLists.txt new file mode 100644 index 0000000000..5763955e8d --- /dev/null +++ b/src/libs/3rdparty/winpty/src/CMakeLists.txt @@ -0,0 +1,112 @@ +if (MSVC) + add_compile_definitions(NOMINMAX UNICODE _UNICODE) +endif() + +file(WRITE ${CMAKE_BINARY_DIR}/GenVersion.h.in [=[ +const char GenVersion_Version[] = "@VERSION@"; +const char GenVersion_Commit[] = "@COMMIT_HASH@"; +]=]) + +file(READ ../VERSION.txt VERSION) +string(REPLACE "\n" "" VERSION "${VERSION}") +configure_file(${CMAKE_BINARY_DIR}/GenVersion.h.in ${CMAKE_BINARY_DIR}/GenVersion.h @ONLY) + +set(shared_sources + shared/AgentMsg.h + shared/BackgroundDesktop.h + shared/BackgroundDesktop.cc + shared/Buffer.h + shared/Buffer.cc + shared/DebugClient.h + shared/DebugClient.cc + shared/GenRandom.h + shared/GenRandom.cc + shared/OsModule.h + shared/OwnedHandle.h + shared/OwnedHandle.cc + shared/StringBuilder.h + shared/StringUtil.cc + shared/StringUtil.h + shared/UnixCtrlChars.h + shared/WindowsSecurity.cc + shared/WindowsSecurity.h + shared/WindowsVersion.h + shared/WindowsVersion.cc + shared/WinptyAssert.h + shared/WinptyAssert.cc + shared/WinptyException.h + shared/WinptyException.cc + shared/WinptyVersion.h + shared/WinptyVersion.cc + shared/winpty_snprintf.h +) + +# +# winpty-agent +# + +add_qtc_executable(winpty-agent + DESTINATION ${IDE_PLUGIN_PATH} + INCLUDES + include ${CMAKE_BINARY_DIR} + DEFINES WINPTY_AGENT_ASSERT + PROPERTIES QT_COMPILE_OPTIONS_DISABLE_WARNINGS ON + SOURCES + agent/Agent.h + agent/Agent.cc + agent/AgentCreateDesktop.h + agent/AgentCreateDesktop.cc + agent/ConsoleFont.cc + agent/ConsoleFont.h + agent/ConsoleInput.cc + agent/ConsoleInput.h + agent/ConsoleInputReencoding.cc + agent/ConsoleInputReencoding.h + agent/ConsoleLine.cc + agent/ConsoleLine.h + agent/Coord.h + agent/DebugShowInput.h + agent/DebugShowInput.cc + agent/DefaultInputMap.h + agent/DefaultInputMap.cc + agent/DsrSender.h + agent/EventLoop.h + agent/EventLoop.cc + agent/InputMap.h + agent/InputMap.cc + agent/LargeConsoleRead.h + agent/LargeConsoleRead.cc + agent/NamedPipe.h + agent/NamedPipe.cc + agent/Scraper.h + agent/Scraper.cc + agent/SimplePool.h + agent/SmallRect.h + agent/Terminal.h + agent/Terminal.cc + agent/UnicodeEncoding.h + agent/Win32Console.cc + agent/Win32Console.h + agent/Win32ConsoleBuffer.cc + agent/Win32ConsoleBuffer.h + agent/main.cc + ${shared_sources} +) + +# +# libwinpty +# + +add_qtc_library(winpty STATIC + INCLUDES ${CMAKE_BINARY_DIR} + PUBLIC_DEFINES COMPILING_WINPTY_DLL + PROPERTIES QT_COMPILE_OPTIONS_DISABLE_WARNINGS ON + SOURCES + libwinpty/AgentLocation.cc + libwinpty/AgentLocation.h + libwinpty/winpty.cc + ${shared_sources} +) + +target_include_directories(winpty + PUBLIC $) diff --git a/src/libs/3rdparty/winpty/src/agent/Agent.cc b/src/libs/3rdparty/winpty/src/agent/Agent.cc new file mode 100644 index 0000000000..986edead13 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/Agent.cc @@ -0,0 +1,612 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "Agent.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../include/winpty_constants.h" + +#include "../shared/AgentMsg.h" +#include "../shared/Buffer.h" +#include "../shared/DebugClient.h" +#include "../shared/GenRandom.h" +#include "../shared/StringBuilder.h" +#include "../shared/StringUtil.h" +#include "../shared/WindowsVersion.h" +#include "../shared/WinptyAssert.h" + +#include "ConsoleFont.h" +#include "ConsoleInput.h" +#include "NamedPipe.h" +#include "Scraper.h" +#include "Terminal.h" +#include "Win32ConsoleBuffer.h" + +namespace { + +static BOOL WINAPI consoleCtrlHandler(DWORD dwCtrlType) +{ + if (dwCtrlType == CTRL_C_EVENT) { + // Do nothing and claim to have handled the event. + return TRUE; + } + return FALSE; +} + +// We can detect the new Windows 10 console by observing the effect of the +// Mark command. In older consoles, Mark temporarily moves the cursor to the +// top-left of the console window. In the new console, the cursor isn't +// initially moved. +// +// We might like to use Mark to freeze the console, but we can't, because when +// the Mark command ends, the console moves the cursor back to its starting +// point, even if the console application has moved it in the meantime. +static void detectNewWindows10Console( + Win32Console &console, Win32ConsoleBuffer &buffer) +{ + if (!isAtLeastWindows8()) { + return; + } + + ConsoleScreenBufferInfo info = buffer.bufferInfo(); + + // Make sure the window isn't 1x1. AFAIK, this should never happen + // accidentally. It is difficult to make it happen deliberately. + if (info.srWindow.Left == info.srWindow.Right && + info.srWindow.Top == info.srWindow.Bottom) { + trace("detectNewWindows10Console: Initial console window was 1x1 -- " + "expanding for test"); + setSmallFont(buffer.conout(), 400, false); + buffer.moveWindow(SmallRect(0, 0, 1, 1)); + buffer.resizeBuffer(Coord(400, 1)); + buffer.moveWindow(SmallRect(0, 0, 2, 1)); + // This use of GetLargestConsoleWindowSize ought to be unnecessary + // given the behavior I've seen from moveWindow(0, 0, 1, 1), but + // I'd like to be especially sure, considering that this code will + // rarely be tested. + const auto largest = GetLargestConsoleWindowSize(buffer.conout()); + buffer.moveWindow( + SmallRect(0, 0, std::min(largest.X, buffer.bufferSize().X), 1)); + info = buffer.bufferInfo(); + ASSERT(info.srWindow.Right > info.srWindow.Left && + "Could not expand console window from 1x1"); + } + + // Test whether MARK moves the cursor. + const Coord initialPosition(info.srWindow.Right, info.srWindow.Bottom); + buffer.setCursorPosition(initialPosition); + ASSERT(!console.frozen()); + console.setFreezeUsesMark(true); + console.setFrozen(true); + const bool isNewW10 = (buffer.cursorPosition() == initialPosition); + console.setFrozen(false); + buffer.setCursorPosition(Coord(0, 0)); + + trace("Attempting to detect new Windows 10 console using MARK: %s", + isNewW10 ? "detected" : "not detected"); + console.setFreezeUsesMark(false); + console.setNewW10(isNewW10); +} + +static inline WriteBuffer newPacket() { + WriteBuffer packet; + packet.putRawValue(0); // Reserve space for size. + return packet; +} + +static HANDLE duplicateHandle(HANDLE h) { + HANDLE ret = nullptr; + if (!DuplicateHandle( + GetCurrentProcess(), h, + GetCurrentProcess(), &ret, + 0, FALSE, DUPLICATE_SAME_ACCESS)) { + ASSERT(false && "DuplicateHandle failed!"); + } + return ret; +} + +// It's safe to truncate a handle from 64-bits to 32-bits, or to sign-extend it +// back to 64-bits. See the MSDN article, "Interprocess Communication Between +// 32-bit and 64-bit Applications". +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa384203.aspx +static int64_t int64FromHandle(HANDLE h) { + return static_cast(reinterpret_cast(h)); +} + +} // anonymous namespace + +Agent::Agent(LPCWSTR controlPipeName, + uint64_t agentFlags, + int mouseMode, + int initialCols, + int initialRows) : + m_useConerr((agentFlags & WINPTY_FLAG_CONERR) != 0), + m_plainMode((agentFlags & WINPTY_FLAG_PLAIN_OUTPUT) != 0), + m_mouseMode(mouseMode) +{ + trace("Agent::Agent entered"); + + ASSERT(initialCols >= 1 && initialRows >= 1); + initialCols = std::min(initialCols, MAX_CONSOLE_WIDTH); + initialRows = std::min(initialRows, MAX_CONSOLE_HEIGHT); + + const bool outputColor = + !m_plainMode || (agentFlags & WINPTY_FLAG_COLOR_ESCAPES); + const Coord initialSize(initialCols, initialRows); + + auto primaryBuffer = openPrimaryBuffer(); + if (m_useConerr) { + m_errorBuffer = Win32ConsoleBuffer::createErrorBuffer(); + } + + detectNewWindows10Console(m_console, *primaryBuffer); + + m_controlPipe = &connectToControlPipe(controlPipeName); + m_coninPipe = &createDataServerPipe(false, L"conin"); + m_conoutPipe = &createDataServerPipe(true, L"conout"); + if (m_useConerr) { + m_conerrPipe = &createDataServerPipe(true, L"conerr"); + } + + // Send an initial response packet to winpty.dll containing pipe names. + { + auto setupPacket = newPacket(); + setupPacket.putWString(m_coninPipe->name()); + setupPacket.putWString(m_conoutPipe->name()); + if (m_useConerr) { + setupPacket.putWString(m_conerrPipe->name()); + } + writePacket(setupPacket); + } + + std::unique_ptr primaryTerminal; + primaryTerminal.reset(new Terminal(*m_conoutPipe, + m_plainMode, + outputColor)); + m_primaryScraper.reset(new Scraper(m_console, + *primaryBuffer, + std::move(primaryTerminal), + initialSize)); + if (m_useConerr) { + std::unique_ptr errorTerminal; + errorTerminal.reset(new Terminal(*m_conerrPipe, + m_plainMode, + outputColor)); + m_errorScraper.reset(new Scraper(m_console, + *m_errorBuffer, + std::move(errorTerminal), + initialSize)); + } + + m_console.setTitle(m_currentTitle); + + const HANDLE conin = GetStdHandle(STD_INPUT_HANDLE); + m_consoleInput.reset( + new ConsoleInput(conin, m_mouseMode, *this, m_console)); + + // Setup Ctrl-C handling. First restore default handling of Ctrl-C. This + // attribute is inherited by child processes. Then register a custom + // Ctrl-C handler that does nothing. The handler will be called when the + // agent calls GenerateConsoleCtrlEvent. + SetConsoleCtrlHandler(NULL, FALSE); + SetConsoleCtrlHandler(consoleCtrlHandler, TRUE); + + setPollInterval(25); +} + +Agent::~Agent() +{ + trace("Agent::~Agent entered"); + agentShutdown(); + if (m_childProcess != NULL) { + CloseHandle(m_childProcess); + } +} + +// Write a "Device Status Report" command to the terminal. The terminal will +// reply with a row+col escape sequence. Presumably, the DSR reply will not +// split a keypress escape sequence, so it should be safe to assume that the +// bytes before it are complete keypresses. +void Agent::sendDsr() +{ + if (!m_plainMode && !m_conoutPipe->isClosed()) { + m_conoutPipe->write("\x1B[6n"); + } +} + +NamedPipe &Agent::connectToControlPipe(LPCWSTR pipeName) +{ + NamedPipe &pipe = createNamedPipe(); + pipe.connectToServer(pipeName, NamedPipe::OpenMode::Duplex); + pipe.setReadBufferSize(64 * 1024); + return pipe; +} + +// Returns a new server named pipe. It has not yet been connected. +NamedPipe &Agent::createDataServerPipe(bool write, const wchar_t *kind) +{ + const auto name = + (WStringBuilder(128) + << L"\\\\.\\pipe\\winpty-" + << kind << L'-' + << GenRandom().uniqueName()).str_moved(); + NamedPipe &pipe = createNamedPipe(); + pipe.openServerPipe( + name.c_str(), + write ? NamedPipe::OpenMode::Writing + : NamedPipe::OpenMode::Reading, + write ? 8192 : 0, + write ? 0 : 256); + if (!write) { + pipe.setReadBufferSize(64 * 1024); + } + return pipe; +} + +void Agent::onPipeIo(NamedPipe &namedPipe) +{ + if (&namedPipe == m_conoutPipe || &namedPipe == m_conerrPipe) { + autoClosePipesForShutdown(); + } else if (&namedPipe == m_coninPipe) { + pollConinPipe(); + } else if (&namedPipe == m_controlPipe) { + pollControlPipe(); + } +} + +void Agent::pollControlPipe() +{ + if (m_controlPipe->isClosed()) { + trace("Agent exiting (control pipe is closed)"); + shutdown(); + return; + } + + while (true) { + uint64_t packetSize = 0; + const auto amt1 = + m_controlPipe->peek(&packetSize, sizeof(packetSize)); + if (amt1 < sizeof(packetSize)) { + break; + } + ASSERT(packetSize >= sizeof(packetSize) && packetSize <= SIZE_MAX); + if (m_controlPipe->bytesAvailable() < packetSize) { + if (m_controlPipe->readBufferSize() < packetSize) { + m_controlPipe->setReadBufferSize(packetSize); + } + break; + } + std::vector packetData; + packetData.resize(packetSize); + const auto amt2 = m_controlPipe->read(packetData.data(), packetSize); + ASSERT(amt2 == packetSize); + try { + ReadBuffer buffer(std::move(packetData)); + buffer.getRawValue(); // Discard the size. + handlePacket(buffer); + } catch (const ReadBuffer::DecodeError&) { + ASSERT(false && "Decode error"); + } + } +} + +void Agent::handlePacket(ReadBuffer &packet) +{ + const int type = packet.getInt32(); + switch (type) { + case AgentMsg::StartProcess: + handleStartProcessPacket(packet); + break; + case AgentMsg::SetSize: + // TODO: I think it might make sense to collapse consecutive SetSize + // messages. i.e. The terminal process can probably generate SetSize + // messages faster than they can be processed, and some GUIs might + // generate a flood of them, so if we can read multiple SetSize packets + // at once, we can ignore the early ones. + handleSetSizePacket(packet); + break; + case AgentMsg::GetConsoleProcessList: + handleGetConsoleProcessListPacket(packet); + break; + default: + trace("Unrecognized message, id:%d", type); + } +} + +void Agent::writePacket(WriteBuffer &packet) +{ + const auto &bytes = packet.buf(); + packet.replaceRawValue(0, bytes.size()); + m_controlPipe->write(bytes.data(), bytes.size()); +} + +void Agent::handleStartProcessPacket(ReadBuffer &packet) +{ + ASSERT(m_childProcess == nullptr); + ASSERT(!m_closingOutputPipes); + + const uint64_t spawnFlags = packet.getInt64(); + const bool wantProcessHandle = packet.getInt32() != 0; + const bool wantThreadHandle = packet.getInt32() != 0; + const auto program = packet.getWString(); + const auto cmdline = packet.getWString(); + const auto cwd = packet.getWString(); + const auto env = packet.getWString(); + const auto desktop = packet.getWString(); + packet.assertEof(); + + auto cmdlineV = vectorWithNulFromString(cmdline); + auto desktopV = vectorWithNulFromString(desktop); + auto envV = vectorFromString(env); + + LPCWSTR programArg = program.empty() ? nullptr : program.c_str(); + LPWSTR cmdlineArg = cmdline.empty() ? nullptr : cmdlineV.data(); + LPCWSTR cwdArg = cwd.empty() ? nullptr : cwd.c_str(); + LPWSTR envArg = env.empty() ? nullptr : envV.data(); + + STARTUPINFOW sui = {}; + PROCESS_INFORMATION pi = {}; + sui.cb = sizeof(sui); + sui.lpDesktop = desktop.empty() ? nullptr : desktopV.data(); + BOOL inheritHandles = FALSE; + if (m_useConerr) { + inheritHandles = TRUE; + sui.dwFlags |= STARTF_USESTDHANDLES; + sui.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + sui.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + sui.hStdError = m_errorBuffer->conout(); + } + + const BOOL success = + CreateProcessW(programArg, cmdlineArg, nullptr, nullptr, + /*bInheritHandles=*/inheritHandles, + /*dwCreationFlags=*/CREATE_UNICODE_ENVIRONMENT, + envArg, cwdArg, &sui, &pi); + const int lastError = success ? 0 : GetLastError(); + + trace("CreateProcess: %s %u", + (success ? "success" : "fail"), + static_cast(pi.dwProcessId)); + + auto reply = newPacket(); + if (success) { + int64_t replyProcess = 0; + int64_t replyThread = 0; + if (wantProcessHandle) { + replyProcess = int64FromHandle(duplicateHandle(pi.hProcess)); + } + if (wantThreadHandle) { + replyThread = int64FromHandle(duplicateHandle(pi.hThread)); + } + CloseHandle(pi.hThread); + m_childProcess = pi.hProcess; + m_autoShutdown = (spawnFlags & WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN) != 0; + m_exitAfterShutdown = (spawnFlags & WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN) != 0; + reply.putInt32(static_cast(StartProcessResult::ProcessCreated)); + reply.putInt64(replyProcess); + reply.putInt64(replyThread); + } else { + reply.putInt32(static_cast(StartProcessResult::CreateProcessFailed)); + reply.putInt32(lastError); + } + writePacket(reply); +} + +void Agent::handleSetSizePacket(ReadBuffer &packet) +{ + const int cols = packet.getInt32(); + const int rows = packet.getInt32(); + packet.assertEof(); + resizeWindow(cols, rows); + auto reply = newPacket(); + writePacket(reply); +} + +void Agent::handleGetConsoleProcessListPacket(ReadBuffer &packet) +{ + packet.assertEof(); + + auto processList = std::vector(64); + auto processCount = GetConsoleProcessList(&processList[0], processList.size()); + + // The process list can change while we're trying to read it + while (processList.size() < processCount) { + // Multiplying by two caps the number of iterations + const auto newSize = std::max(processList.size() * 2, processCount); + processList.resize(newSize); + processCount = GetConsoleProcessList(&processList[0], processList.size()); + } + + if (processCount == 0) { + trace("GetConsoleProcessList failed"); + } + + auto reply = newPacket(); + reply.putInt32(processCount); + for (DWORD i = 0; i < processCount; i++) { + reply.putInt32(processList[i]); + } + writePacket(reply); +} + +void Agent::pollConinPipe() +{ + const std::string newData = m_coninPipe->readAllToString(); + if (hasDebugFlag("input_separated_bytes")) { + // This debug flag is intended to help with testing incomplete escape + // sequences and multibyte UTF-8 encodings. (I wonder if the normal + // code path ought to advance a state machine one byte at a time.) + for (size_t i = 0; i < newData.size(); ++i) { + m_consoleInput->writeInput(newData.substr(i, 1)); + } + } else { + m_consoleInput->writeInput(newData); + } +} + +void Agent::onPollTimeout() +{ + m_consoleInput->updateInputFlags(); + const bool enableMouseMode = m_consoleInput->shouldActivateTerminalMouse(); + + // Give the ConsoleInput object a chance to flush input from an incomplete + // escape sequence (e.g. pressing ESC). + m_consoleInput->flushIncompleteEscapeCode(); + + const bool shouldScrapeContent = !m_closingOutputPipes; + + // Check if the child process has exited. + if (m_autoShutdown && + m_childProcess != nullptr && + WaitForSingleObject(m_childProcess, 0) == WAIT_OBJECT_0) { + CloseHandle(m_childProcess); + m_childProcess = nullptr; + + // Close the data socket to signal to the client that the child + // process has exited. If there's any data left to send, send it + // before closing the socket. + m_closingOutputPipes = true; + } + + // Scrape for output *after* the above exit-check to ensure that we collect + // the child process's final output. + if (shouldScrapeContent) { + syncConsoleTitle(); + scrapeBuffers(); + } + + // We must ensure that we disable mouse mode before closing the CONOUT + // pipe, so update the mouse mode here. + m_primaryScraper->terminal().enableMouseMode( + enableMouseMode && !m_closingOutputPipes); + + autoClosePipesForShutdown(); +} + +void Agent::autoClosePipesForShutdown() +{ + if (m_closingOutputPipes) { + // We don't want to close a pipe before it's connected! If we do, the + // libwinpty client may try to connect to a non-existent pipe. This + // case is important for short-lived programs. + if (m_conoutPipe->isConnected() && + m_conoutPipe->bytesToSend() == 0) { + trace("Closing CONOUT pipe (auto-shutdown)"); + m_conoutPipe->closePipe(); + } + if (m_conerrPipe != nullptr && + m_conerrPipe->isConnected() && + m_conerrPipe->bytesToSend() == 0) { + trace("Closing CONERR pipe (auto-shutdown)"); + m_conerrPipe->closePipe(); + } + if (m_exitAfterShutdown && + m_conoutPipe->isClosed() && + (m_conerrPipe == nullptr || m_conerrPipe->isClosed())) { + trace("Agent exiting (exit-after-shutdown)"); + shutdown(); + } + } +} + +std::unique_ptr Agent::openPrimaryBuffer() +{ + // If we're using a separate buffer for stderr, and a program were to + // activate the stderr buffer, then we could accidentally scrape the same + // buffer twice. That probably shouldn't happen in ordinary use, but it + // can be avoided anyway by using the original console screen buffer in + // that mode. + if (!m_useConerr) { + return Win32ConsoleBuffer::openConout(); + } else { + return Win32ConsoleBuffer::openStdout(); + } +} + +void Agent::resizeWindow(int cols, int rows) +{ + ASSERT(cols >= 1 && rows >= 1); + cols = std::min(cols, MAX_CONSOLE_WIDTH); + rows = std::min(rows, MAX_CONSOLE_HEIGHT); + + Win32Console::FreezeGuard guard(m_console, m_console.frozen()); + const Coord newSize(cols, rows); + ConsoleScreenBufferInfo info; + auto primaryBuffer = openPrimaryBuffer(); + m_primaryScraper->resizeWindow(*primaryBuffer, newSize, info); + m_consoleInput->setMouseWindowRect(info.windowRect()); + if (m_errorScraper) { + m_errorScraper->resizeWindow(*m_errorBuffer, newSize, info); + } + + // Synthesize a WINDOW_BUFFER_SIZE_EVENT event. Normally, Windows + // generates this event only when the buffer size changes, not when the + // window size changes. This behavior is undesirable in two ways: + // - When winpty expands the window horizontally, it must expand the + // buffer first, then the window. At least some programs (e.g. the WSL + // bash.exe wrapper) use the window width rather than the buffer width, + // so there is a short timespan during which they can read the wrong + // value. + // - If the window's vertical size is changed, no event is generated, + // even though a typical well-behaved console program cares about the + // *window* height, not the *buffer* height. + // This synthesization works around a design flaw in the console. It's probably + // harmless. See https://github.com/rprichard/winpty/issues/110. + INPUT_RECORD sizeEvent {}; + sizeEvent.EventType = WINDOW_BUFFER_SIZE_EVENT; + sizeEvent.Event.WindowBufferSizeEvent.dwSize = primaryBuffer->bufferSize(); + DWORD actual {}; + WriteConsoleInputW(GetStdHandle(STD_INPUT_HANDLE), &sizeEvent, 1, &actual); +} + +void Agent::scrapeBuffers() +{ + Win32Console::FreezeGuard guard(m_console, m_console.frozen()); + ConsoleScreenBufferInfo info; + m_primaryScraper->scrapeBuffer(*openPrimaryBuffer(), info); + m_consoleInput->setMouseWindowRect(info.windowRect()); + if (m_errorScraper) { + m_errorScraper->scrapeBuffer(*m_errorBuffer, info); + } +} + +void Agent::syncConsoleTitle() +{ + std::wstring newTitle = m_console.title(); + if (newTitle != m_currentTitle) { + if (!m_plainMode && !m_conoutPipe->isClosed()) { + std::string command = std::string("\x1b]0;") + + utf8FromWide(newTitle) + "\x07"; + m_conoutPipe->write(command.c_str()); + } + m_currentTitle = newTitle; + } +} diff --git a/src/libs/3rdparty/winpty/src/agent/Agent.h b/src/libs/3rdparty/winpty/src/agent/Agent.h new file mode 100644 index 0000000000..1dde48fe4a --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/Agent.h @@ -0,0 +1,103 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef AGENT_H +#define AGENT_H + +#include +#include + +#include +#include + +#include "DsrSender.h" +#include "EventLoop.h" +#include "Win32Console.h" + +class ConsoleInput; +class NamedPipe; +class ReadBuffer; +class Scraper; +class WriteBuffer; +class Win32ConsoleBuffer; + +class Agent : public EventLoop, public DsrSender +{ +public: + Agent(LPCWSTR controlPipeName, + uint64_t agentFlags, + int mouseMode, + int initialCols, + int initialRows); + virtual ~Agent(); + void sendDsr() override; + +private: + NamedPipe &connectToControlPipe(LPCWSTR pipeName); + NamedPipe &createDataServerPipe(bool write, const wchar_t *kind); + +private: + void pollControlPipe(); + void handlePacket(ReadBuffer &packet); + void writePacket(WriteBuffer &packet); + void handleStartProcessPacket(ReadBuffer &packet); + void handleSetSizePacket(ReadBuffer &packet); + void handleGetConsoleProcessListPacket(ReadBuffer &packet); + void pollConinPipe(); + +protected: + virtual void onPollTimeout() override; + virtual void onPipeIo(NamedPipe &namedPipe) override; + +private: + void autoClosePipesForShutdown(); + std::unique_ptr openPrimaryBuffer(); + void resizeWindow(int cols, int rows); + void scrapeBuffers(); + void syncConsoleTitle(); + +private: + const bool m_useConerr; + const bool m_plainMode; + const int m_mouseMode; + Win32Console m_console; + std::unique_ptr m_primaryScraper; + std::unique_ptr m_errorScraper; + std::unique_ptr m_errorBuffer; + NamedPipe *m_controlPipe = nullptr; + NamedPipe *m_coninPipe = nullptr; + NamedPipe *m_conoutPipe = nullptr; + NamedPipe *m_conerrPipe = nullptr; + bool m_autoShutdown = false; + bool m_exitAfterShutdown = false; + bool m_closingOutputPipes = false; + std::unique_ptr m_consoleInput; + HANDLE m_childProcess = nullptr; + + // If the title is initialized to the empty string, then cmd.exe will + // sometimes print this error: + // Not enough storage is available to process this command. + // It happens on Windows 7 when logged into a Cygwin SSH session, for + // example. Using a title of a single space character avoids the problem. + // See https://github.com/rprichard/winpty/issues/74. + std::wstring m_currentTitle = L" "; +}; + +#endif // AGENT_H diff --git a/src/libs/3rdparty/winpty/src/agent/AgentCreateDesktop.cc b/src/libs/3rdparty/winpty/src/agent/AgentCreateDesktop.cc new file mode 100644 index 0000000000..9ad6503b1c --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/AgentCreateDesktop.cc @@ -0,0 +1,84 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "AgentCreateDesktop.h" + +#include "../shared/BackgroundDesktop.h" +#include "../shared/Buffer.h" +#include "../shared/DebugClient.h" +#include "../shared/StringUtil.h" + +#include "EventLoop.h" +#include "NamedPipe.h" + +namespace { + +static inline WriteBuffer newPacket() { + WriteBuffer packet; + packet.putRawValue(0); // Reserve space for size. + return packet; +} + +class CreateDesktopLoop : public EventLoop { +public: + CreateDesktopLoop(LPCWSTR controlPipeName); + +protected: + virtual void onPipeIo(NamedPipe &namedPipe) override; + +private: + void writePacket(WriteBuffer &packet); + + BackgroundDesktop m_desktop; + NamedPipe &m_pipe; +}; + +CreateDesktopLoop::CreateDesktopLoop(LPCWSTR controlPipeName) : + m_pipe(createNamedPipe()) { + m_pipe.connectToServer(controlPipeName, NamedPipe::OpenMode::Duplex); + auto packet = newPacket(); + packet.putWString(m_desktop.desktopName()); + writePacket(packet); +} + +void CreateDesktopLoop::writePacket(WriteBuffer &packet) { + const auto &bytes = packet.buf(); + packet.replaceRawValue(0, bytes.size()); + m_pipe.write(bytes.data(), bytes.size()); +} + +void CreateDesktopLoop::onPipeIo(NamedPipe &namedPipe) { + if (m_pipe.isClosed()) { + shutdown(); + } +} + +} // anonymous namespace + +void handleCreateDesktop(LPCWSTR controlPipeName) { + try { + CreateDesktopLoop loop(controlPipeName); + loop.run(); + trace("Agent exiting..."); + } catch (const WinptyException &e) { + trace("handleCreateDesktop: internal error: %s", + utf8FromWide(e.what()).c_str()); + } +} diff --git a/src/libs/3rdparty/winpty/src/agent/AgentCreateDesktop.h b/src/libs/3rdparty/winpty/src/agent/AgentCreateDesktop.h new file mode 100644 index 0000000000..2ae539c7fa --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/AgentCreateDesktop.h @@ -0,0 +1,28 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef AGENT_CREATE_DESKTOP_H +#define AGENT_CREATE_DESKTOP_H + +#include + +void handleCreateDesktop(LPCWSTR controlPipeName); + +#endif // AGENT_CREATE_DESKTOP_H diff --git a/src/libs/3rdparty/winpty/src/agent/ConsoleFont.cc b/src/libs/3rdparty/winpty/src/agent/ConsoleFont.cc new file mode 100644 index 0000000000..aa1f7876d3 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/ConsoleFont.cc @@ -0,0 +1,698 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "ConsoleFont.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../shared/DebugClient.h" +#include "../shared/OsModule.h" +#include "../shared/StringUtil.h" +#include "../shared/WindowsVersion.h" +#include "../shared/WinptyAssert.h" +#include "../shared/winpty_snprintf.h" + +namespace { + +#define COUNT_OF(x) (sizeof(x) / sizeof((x)[0])) + +// See https://en.wikipedia.org/wiki/List_of_CJK_fonts +const wchar_t kLucidaConsole[] = L"Lucida Console"; +const wchar_t kMSGothic[] = { 0xff2d, 0xff33, 0x0020, 0x30b4, 0x30b7, 0x30c3, 0x30af, 0 }; // 932, Japanese +const wchar_t kNSimSun[] = { 0x65b0, 0x5b8b, 0x4f53, 0 }; // 936, Chinese Simplified +const wchar_t kGulimChe[] = { 0xad74, 0xb9bc, 0xccb4, 0 }; // 949, Korean +const wchar_t kMingLight[] = { 0x7d30, 0x660e, 0x9ad4, 0 }; // 950, Chinese Traditional + +struct FontSize { + short size; + int width; +}; + +struct Font { + const wchar_t *faceName; + unsigned int family; + short size; +}; + +// Ideographs in East Asian languages take two columns rather than one. +// In the console screen buffer, a "full-width" character will occupy two +// cells of the buffer, the first with attribute 0x100 and the second with +// attribute 0x200. +// +// Windows does not correctly identify code points as double-width in all +// configurations. It depends heavily on the code page, the font facename, +// and (somehow) even the font size. In the 437 code page (MS-DOS), for +// example, no codepoints are interpreted as double-width. When the console +// is in an East Asian code page (932, 936, 949, or 950), then sometimes +// selecting a "Western" facename like "Lucida Console" or "Consolas" doesn't +// register, or if the font *can* be chosen, then the console doesn't handle +// double-width correctly. I tested the double-width handling by writing +// several code points with WriteConsole and checking whether one or two cells +// were filled. +// +// In the Japanese code page (932), Microsoft's default font is MS Gothic. +// MS Gothic double-width handling seems to be broken with console versions +// prior to Windows 10 (including Windows 10's legacy mode), and it's +// especially broken in Windows 8 and 8.1. +// +// Test by running: misc/Utf16Echo A2 A3 2014 3044 30FC 4000 +// +// The first three codepoints are always rendered as half-width with the +// Windows Japanese fonts. (Of these, the first two must be half-width, +// but U+2014 could be either.) The last three are rendered as full-width, +// and they are East_Asian_Width=Wide. +// +// Windows 7 fails by modeling all codepoints as full-width with font +// sizes 22 and above. +// +// Windows 8 gets U+00A2, U+00A3, U+2014, U+30FC, and U+4000 wrong, but +// using a point size not listed in the console properties dialog +// (e.g. "9") is less wrong: +// +// | code point | +// font | 00A2 00A3 2014 3044 30FC 4000 | cell size +// ------------+---------------------------------+---------- +// 8 | F F F F H H | 4x8 +// 9 | F F F F F F | 5x9 +// 16 | F F F F H H | 8x16 +// raster 6x13 | H H H F F H(*) | 6x13 +// +// (*) The Raster Font renders U+4000 as a white box (i.e. an unsupported +// character). +// + +// See: +// - misc/Font-Report-June2016 directory for per-size details +// - misc/font-notes.txt +// - misc/Utf16Echo.cc, misc/FontSurvey.cc, misc/SetFont.cc, misc/GetFont.cc + +const FontSize kLucidaFontSizes[] = { + { 5, 3 }, + { 6, 4 }, + { 8, 5 }, + { 10, 6 }, + { 12, 7 }, + { 14, 8 }, + { 16, 10 }, + { 18, 11 }, + { 20, 12 }, + { 36, 22 }, + { 48, 29 }, + { 60, 36 }, + { 72, 43 }, +}; + +// Japanese. Used on Vista and Windows 7. +const FontSize k932GothicVista[] = { + { 6, 3 }, + { 8, 4 }, + { 10, 5 }, + { 12, 6 }, + { 13, 7 }, + { 15, 8 }, + { 17, 9 }, + { 19, 10 }, + { 21, 11 }, + // All larger fonts are more broken w.r.t. full-size East Asian characters. +}; + +// Japanese. Used on Windows 8, 8.1, and the legacy 10 console. +const FontSize k932GothicWin8[] = { + // All of these characters are broken w.r.t. full-size East Asian + // characters, but they're equally broken. + { 5, 3 }, + { 7, 4 }, + { 9, 5 }, + { 11, 6 }, + { 13, 7 }, + { 15, 8 }, + { 17, 9 }, + { 20, 10 }, + { 22, 11 }, + { 24, 12 }, + // include extra-large fonts for small terminals + { 36, 18 }, + { 48, 24 }, + { 60, 30 }, + { 72, 36 }, +}; + +// Japanese. Used on the new Windows 10 console. +const FontSize k932GothicWin10[] = { + { 6, 3 }, + { 8, 4 }, + { 10, 5 }, + { 12, 6 }, + { 14, 7 }, + { 16, 8 }, + { 18, 9 }, + { 20, 10 }, + { 22, 11 }, + { 24, 12 }, + // include extra-large fonts for small terminals + { 36, 18 }, + { 48, 24 }, + { 60, 30 }, + { 72, 36 }, +}; + +// Chinese Simplified. +const FontSize k936SimSun[] = { + { 6, 3 }, + { 8, 4 }, + { 10, 5 }, + { 12, 6 }, + { 14, 7 }, + { 16, 8 }, + { 18, 9 }, + { 20, 10 }, + { 22, 11 }, + { 24, 12 }, + // include extra-large fonts for small terminals + { 36, 18 }, + { 48, 24 }, + { 60, 30 }, + { 72, 36 }, +}; + +// Korean. +const FontSize k949GulimChe[] = { + { 6, 3 }, + { 8, 4 }, + { 10, 5 }, + { 12, 6 }, + { 14, 7 }, + { 16, 8 }, + { 18, 9 }, + { 20, 10 }, + { 22, 11 }, + { 24, 12 }, + // include extra-large fonts for small terminals + { 36, 18 }, + { 48, 24 }, + { 60, 30 }, + { 72, 36 }, +}; + +// Chinese Traditional. +const FontSize k950MingLight[] = { + { 6, 3 }, + { 8, 4 }, + { 10, 5 }, + { 12, 6 }, + { 14, 7 }, + { 16, 8 }, + { 18, 9 }, + { 20, 10 }, + { 22, 11 }, + { 24, 12 }, + // include extra-large fonts for small terminals + { 36, 18 }, + { 48, 24 }, + { 60, 30 }, + { 72, 36 }, +}; + +// Some of these types and functions are missing from the MinGW headers. +// Others are undocumented. + +struct AGENT_CONSOLE_FONT_INFO { + DWORD nFont; + COORD dwFontSize; +}; + +struct AGENT_CONSOLE_FONT_INFOEX { + ULONG cbSize; + DWORD nFont; + COORD dwFontSize; + UINT FontFamily; + UINT FontWeight; + WCHAR FaceName[LF_FACESIZE]; +}; + +// undocumented XP API +typedef BOOL WINAPI SetConsoleFont_t( + HANDLE hOutput, + DWORD dwFontIndex); + +// undocumented XP API +typedef DWORD WINAPI GetNumberOfConsoleFonts_t(); + +// XP and up +typedef BOOL WINAPI GetCurrentConsoleFont_t( + HANDLE hOutput, + BOOL bMaximumWindow, + AGENT_CONSOLE_FONT_INFO *lpConsoleCurrentFont); + +// XP and up +typedef COORD WINAPI GetConsoleFontSize_t( + HANDLE hConsoleOutput, + DWORD nFont); + +// Vista and up +typedef BOOL WINAPI GetCurrentConsoleFontEx_t( + HANDLE hConsoleOutput, + BOOL bMaximumWindow, + AGENT_CONSOLE_FONT_INFOEX *lpConsoleCurrentFontEx); + +// Vista and up +typedef BOOL WINAPI SetCurrentConsoleFontEx_t( + HANDLE hConsoleOutput, + BOOL bMaximumWindow, + AGENT_CONSOLE_FONT_INFOEX *lpConsoleCurrentFontEx); + +#define GET_MODULE_PROC(mod, funcName) \ + m_##funcName = reinterpret_cast((mod).proc(#funcName)); \ + +#define DEFINE_ACCESSOR(funcName) \ + funcName##_t &funcName() const { \ + ASSERT(valid()); \ + return *m_##funcName; \ + } + +class XPFontAPI { +public: + XPFontAPI() : m_kernel32(L"kernel32.dll") { + GET_MODULE_PROC(m_kernel32, GetCurrentConsoleFont); + GET_MODULE_PROC(m_kernel32, GetConsoleFontSize); + } + + bool valid() const { + return m_GetCurrentConsoleFont != NULL && + m_GetConsoleFontSize != NULL; + } + + DEFINE_ACCESSOR(GetCurrentConsoleFont) + DEFINE_ACCESSOR(GetConsoleFontSize) + +private: + OsModule m_kernel32; + GetCurrentConsoleFont_t *m_GetCurrentConsoleFont; + GetConsoleFontSize_t *m_GetConsoleFontSize; +}; + +class UndocumentedXPFontAPI : public XPFontAPI { +public: + UndocumentedXPFontAPI() : m_kernel32(L"kernel32.dll") { + GET_MODULE_PROC(m_kernel32, SetConsoleFont); + GET_MODULE_PROC(m_kernel32, GetNumberOfConsoleFonts); + } + + bool valid() const { + return this->XPFontAPI::valid() && + m_SetConsoleFont != NULL && + m_GetNumberOfConsoleFonts != NULL; + } + + DEFINE_ACCESSOR(SetConsoleFont) + DEFINE_ACCESSOR(GetNumberOfConsoleFonts) + +private: + OsModule m_kernel32; + SetConsoleFont_t *m_SetConsoleFont; + GetNumberOfConsoleFonts_t *m_GetNumberOfConsoleFonts; +}; + +class VistaFontAPI : public XPFontAPI { +public: + VistaFontAPI() : m_kernel32(L"kernel32.dll") { + GET_MODULE_PROC(m_kernel32, GetCurrentConsoleFontEx); + GET_MODULE_PROC(m_kernel32, SetCurrentConsoleFontEx); + } + + bool valid() const { + return this->XPFontAPI::valid() && + m_GetCurrentConsoleFontEx != NULL && + m_SetCurrentConsoleFontEx != NULL; + } + + DEFINE_ACCESSOR(GetCurrentConsoleFontEx) + DEFINE_ACCESSOR(SetCurrentConsoleFontEx) + +private: + OsModule m_kernel32; + GetCurrentConsoleFontEx_t *m_GetCurrentConsoleFontEx; + SetCurrentConsoleFontEx_t *m_SetCurrentConsoleFontEx; +}; + +static std::vector > readFontTable( + XPFontAPI &api, HANDLE conout, DWORD maxCount) { + std::vector > ret; + for (DWORD i = 0; i < maxCount; ++i) { + COORD size = api.GetConsoleFontSize()(conout, i); + if (size.X == 0 && size.Y == 0) { + break; + } + ret.push_back(std::make_pair(i, size)); + } + return ret; +} + +static void dumpFontTable(HANDLE conout, const char *prefix) { + const int kMaxCount = 1000; + if (!isTracingEnabled()) { + return; + } + XPFontAPI api; + if (!api.valid()) { + trace("dumpFontTable: cannot dump font table -- missing APIs"); + return; + } + std::vector > table = + readFontTable(api, conout, kMaxCount); + std::string line; + char tmp[128]; + size_t first = 0; + while (first < table.size()) { + size_t last = std::min(table.size() - 1, first + 10 - 1); + winpty_snprintf(tmp, "%sfonts %02u-%02u:", + prefix, static_cast(first), static_cast(last)); + line = tmp; + for (size_t i = first; i <= last; ++i) { + if (i % 10 == 5) { + line += " - "; + } + winpty_snprintf(tmp, " %2dx%-2d", + table[i].second.X, table[i].second.Y); + line += tmp; + } + trace("%s", line.c_str()); + first = last + 1; + } + if (table.size() == kMaxCount) { + trace("%sfonts: ... stopped reading at %d fonts ...", + prefix, kMaxCount); + } +} + +static std::string stringToCodePoints(const std::wstring &str) { + std::string ret = "("; + for (size_t i = 0; i < str.size(); ++i) { + char tmp[32]; + winpty_snprintf(tmp, "%X", str[i]); + if (ret.size() > 1) { + ret.push_back(' '); + } + ret += tmp; + } + ret.push_back(')'); + return ret; +} + +static void dumpFontInfoEx( + const AGENT_CONSOLE_FONT_INFOEX &infoex, + const char *prefix) { + if (!isTracingEnabled()) { + return; + } + std::wstring faceName(infoex.FaceName, + winpty_wcsnlen(infoex.FaceName, COUNT_OF(infoex.FaceName))); + trace("%snFont=%u dwFontSize=(%d,%d) " + "FontFamily=0x%x FontWeight=%u FaceName=%s %s", + prefix, + static_cast(infoex.nFont), + infoex.dwFontSize.X, infoex.dwFontSize.Y, + infoex.FontFamily, infoex.FontWeight, utf8FromWide(faceName).c_str(), + stringToCodePoints(faceName).c_str()); +} + +static void dumpVistaFont(VistaFontAPI &api, HANDLE conout, const char *prefix) { + if (!isTracingEnabled()) { + return; + } + AGENT_CONSOLE_FONT_INFOEX infoex = {0}; + infoex.cbSize = sizeof(infoex); + if (!api.GetCurrentConsoleFontEx()(conout, FALSE, &infoex)) { + trace("GetCurrentConsoleFontEx call failed"); + return; + } + dumpFontInfoEx(infoex, prefix); +} + +static void dumpXPFont(XPFontAPI &api, HANDLE conout, const char *prefix) { + if (!isTracingEnabled()) { + return; + } + AGENT_CONSOLE_FONT_INFO info = {0}; + if (!api.GetCurrentConsoleFont()(conout, FALSE, &info)) { + trace("GetCurrentConsoleFont call failed"); + return; + } + trace("%snFont=%u dwFontSize=(%d,%d)", + prefix, + static_cast(info.nFont), + info.dwFontSize.X, info.dwFontSize.Y); +} + +static bool setFontVista( + VistaFontAPI &api, + HANDLE conout, + const Font &font) { + AGENT_CONSOLE_FONT_INFOEX infoex = {}; + infoex.cbSize = sizeof(AGENT_CONSOLE_FONT_INFOEX); + infoex.dwFontSize.Y = font.size; + infoex.FontFamily = font.family; + infoex.FontWeight = 400; + winpty_wcsncpy_nul(infoex.FaceName, font.faceName); + dumpFontInfoEx(infoex, "setFontVista: setting font to: "); + if (!api.SetCurrentConsoleFontEx()(conout, FALSE, &infoex)) { + trace("setFontVista: SetCurrentConsoleFontEx call failed"); + return false; + } + memset(&infoex, 0, sizeof(infoex)); + infoex.cbSize = sizeof(infoex); + if (!api.GetCurrentConsoleFontEx()(conout, FALSE, &infoex)) { + trace("setFontVista: GetCurrentConsoleFontEx call failed"); + return false; + } + if (wcsncmp(infoex.FaceName, font.faceName, + COUNT_OF(infoex.FaceName)) != 0) { + trace("setFontVista: face name was not set"); + dumpFontInfoEx(infoex, "setFontVista: post-call font: "); + return false; + } + // We'd like to verify that the new font size is correct, but we can't + // predict what it will be, even though we just set it to `pxSize` through + // an apprently symmetric interface. For the Chinese and Korean fonts, the + // new `infoex.dwFontSize.Y` value can be slightly larger than the height + // we specified. + return true; +} + +static Font selectSmallFont(int codePage, int columns, bool isNewW10) { + // Iterate over a set of font sizes according to the code page, and select + // one. + + const wchar_t *faceName = nullptr; + unsigned int fontFamily = 0; + const FontSize *table = nullptr; + size_t tableSize = 0; + + switch (codePage) { + case 932: // Japanese + faceName = kMSGothic; + fontFamily = 0x36; + if (isNewW10) { + table = k932GothicWin10; + tableSize = COUNT_OF(k932GothicWin10); + } else if (isAtLeastWindows8()) { + table = k932GothicWin8; + tableSize = COUNT_OF(k932GothicWin8); + } else { + table = k932GothicVista; + tableSize = COUNT_OF(k932GothicVista); + } + break; + case 936: // Chinese Simplified + faceName = kNSimSun; + fontFamily = 0x36; + table = k936SimSun; + tableSize = COUNT_OF(k936SimSun); + break; + case 949: // Korean + faceName = kGulimChe; + fontFamily = 0x36; + table = k949GulimChe; + tableSize = COUNT_OF(k949GulimChe); + break; + case 950: // Chinese Traditional + faceName = kMingLight; + fontFamily = 0x36; + table = k950MingLight; + tableSize = COUNT_OF(k950MingLight); + break; + default: + faceName = kLucidaConsole; + fontFamily = 0x36; + table = kLucidaFontSizes; + tableSize = COUNT_OF(kLucidaFontSizes); + break; + } + + size_t bestIndex = static_cast(-1); + std::tuple bestScore = std::make_tuple(-1, -1); + + // We might want to pick the smallest possible font, because we don't know + // how large the monitor is (and the monitor size can change). We might + // want to pick a larger font to accommodate console programs that resize + // the console on their own, like DOS edit.com, which tends to resize the + // console to 80 columns. + + for (size_t i = 0; i < tableSize; ++i) { + const int width = table[i].width * columns; + + // In general, we'd like to pick a font size where cutting the number + // of columns in half doesn't immediately violate the minimum width + // constraint. (e.g. To run DOS edit.com, a user might resize their + // terminal to ~100 columns so it's big enough to show the 80 columns + // post-resize.) To achieve this, give priority to fonts that allow + // this halving. We don't want to encourage *very* large fonts, + // though, so disable the effect as the number of columns scales from + // 80 to 40. + const int halfColumns = std::min(columns, std::max(40, columns / 2)); + const int halfWidth = table[i].width * halfColumns; + + std::tuple thisScore = std::make_tuple(-1, -1); + if (width >= 160 && halfWidth >= 160) { + // Both sizes are good. Prefer the smaller fonts. + thisScore = std::make_tuple(2, -width); + } else if (width >= 160) { + // Prefer the smaller fonts. + thisScore = std::make_tuple(1, -width); + } else { + // Otherwise, prefer the largest font in our table. + thisScore = std::make_tuple(0, width); + } + if (thisScore > bestScore) { + bestIndex = i; + bestScore = thisScore; + } + } + + ASSERT(bestIndex != static_cast(-1)); + return Font { faceName, fontFamily, table[bestIndex].size }; +} + +static void setSmallFontVista(VistaFontAPI &api, HANDLE conout, + int columns, bool isNewW10) { + int codePage = GetConsoleOutputCP(); + const auto font = selectSmallFont(codePage, columns, isNewW10); + if (setFontVista(api, conout, font)) { + trace("setSmallFontVista: success"); + return; + } + if (codePage == 932 || codePage == 936 || + codePage == 949 || codePage == 950) { + trace("setSmallFontVista: falling back to default codepage font instead"); + const auto fontFB = selectSmallFont(0, columns, isNewW10); + if (setFontVista(api, conout, fontFB)) { + trace("setSmallFontVista: fallback was successful"); + return; + } + } + trace("setSmallFontVista: failure"); +} + +struct FontSizeComparator { + bool operator()(const std::pair &obj1, + const std::pair &obj2) const { + int score1 = obj1.second.X + obj1.second.Y; + int score2 = obj2.second.X + obj2.second.Y; + return score1 < score2; + } +}; + +static void setSmallFontXP(UndocumentedXPFontAPI &api, HANDLE conout) { + // Read the console font table and sort it from smallest to largest. + const DWORD fontCount = api.GetNumberOfConsoleFonts()(); + trace("setSmallFontXP: number of console fonts: %u", + static_cast(fontCount)); + std::vector > table = + readFontTable(api, conout, fontCount); + std::sort(table.begin(), table.end(), FontSizeComparator()); + for (size_t i = 0; i < table.size(); ++i) { + // Skip especially narrow fonts to permit narrower terminals. + if (table[i].second.X < 4) { + continue; + } + trace("setSmallFontXP: setting font to %u", + static_cast(table[i].first)); + if (!api.SetConsoleFont()(conout, table[i].first)) { + trace("setSmallFontXP: SetConsoleFont call failed"); + continue; + } + AGENT_CONSOLE_FONT_INFO info; + if (!api.GetCurrentConsoleFont()(conout, FALSE, &info)) { + trace("setSmallFontXP: GetCurrentConsoleFont call failed"); + return; + } + if (info.nFont != table[i].first) { + trace("setSmallFontXP: font was not set"); + dumpXPFont(api, conout, "setSmallFontXP: post-call font: "); + continue; + } + trace("setSmallFontXP: success"); + return; + } + trace("setSmallFontXP: failure"); +} + +} // anonymous namespace + +// A Windows console window can never be larger than the desktop window. To +// maximize the possible size of the console in rows*cols, try to configure +// the console with a small font. Unfortunately, we cannot make the font *too* +// small, because there is also a minimum window size in pixels. +void setSmallFont(HANDLE conout, int columns, bool isNewW10) { + trace("setSmallFont: attempting to set a small font for %d columns " + "(CP=%u OutputCP=%u)", + columns, + static_cast(GetConsoleCP()), + static_cast(GetConsoleOutputCP())); + VistaFontAPI vista; + if (vista.valid()) { + dumpVistaFont(vista, conout, "previous font: "); + dumpFontTable(conout, "previous font table: "); + setSmallFontVista(vista, conout, columns, isNewW10); + dumpVistaFont(vista, conout, "new font: "); + dumpFontTable(conout, "new font table: "); + return; + } + UndocumentedXPFontAPI xp; + if (xp.valid()) { + dumpXPFont(xp, conout, "previous font: "); + dumpFontTable(conout, "previous font table: "); + setSmallFontXP(xp, conout); + dumpXPFont(xp, conout, "new font: "); + dumpFontTable(conout, "new font table: "); + return; + } + trace("setSmallFont: neither Vista nor XP APIs detected -- giving up"); + dumpFontTable(conout, "font table: "); +} diff --git a/src/libs/3rdparty/winpty/src/agent/ConsoleFont.h b/src/libs/3rdparty/winpty/src/agent/ConsoleFont.h new file mode 100644 index 0000000000..99cb10698d --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/ConsoleFont.h @@ -0,0 +1,28 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef CONSOLEFONT_H +#define CONSOLEFONT_H + +#include + +void setSmallFont(HANDLE conout, int columns, bool isNewW10); + +#endif // CONSOLEFONT_H diff --git a/src/libs/3rdparty/winpty/src/agent/ConsoleInput.cc b/src/libs/3rdparty/winpty/src/agent/ConsoleInput.cc new file mode 100644 index 0000000000..192cac2a29 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/ConsoleInput.cc @@ -0,0 +1,852 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "ConsoleInput.h" + +#include +#include + +#include +#include + +#include "../include/winpty_constants.h" + +#include "../shared/DebugClient.h" +#include "../shared/StringBuilder.h" +#include "../shared/UnixCtrlChars.h" + +#include "ConsoleInputReencoding.h" +#include "DebugShowInput.h" +#include "DefaultInputMap.h" +#include "DsrSender.h" +#include "UnicodeEncoding.h" +#include "Win32Console.h" + +// MAPVK_VK_TO_VSC isn't defined by the old MinGW. +#ifndef MAPVK_VK_TO_VSC +#define MAPVK_VK_TO_VSC 0 +#endif + +namespace { + +struct MouseRecord { + bool release; + int flags; + COORD coord; + + std::string toString() const; +}; + +std::string MouseRecord::toString() const { + StringBuilder sb(40); + sb << "pos=" << coord.X << ',' << coord.Y + << " flags=0x" << hexOfInt(flags); + if (release) { + sb << " release"; + } + return sb.str_moved(); +} + +const unsigned int kIncompleteEscapeTimeoutMs = 1000u; + +#define CHECK(cond) \ + do { \ + if (!(cond)) { return 0; } \ + } while(0) + +#define ADVANCE() \ + do { \ + pch++; \ + if (pch == stop) { return -1; } \ + } while(0) + +#define SCAN_INT(out, maxLen) \ + do { \ + (out) = 0; \ + CHECK(isdigit(*pch)); \ + const char *begin = pch; \ + do { \ + CHECK(pch - begin + 1 < maxLen); \ + (out) = (out) * 10 + *pch - '0'; \ + ADVANCE(); \ + } while (isdigit(*pch)); \ + } while(0) + +#define SCAN_SIGNED_INT(out, maxLen) \ + do { \ + bool negative = false; \ + if (*pch == '-') { \ + negative = true; \ + ADVANCE(); \ + } \ + SCAN_INT(out, maxLen); \ + if (negative) { \ + (out) = -(out); \ + } \ + } while(0) + +// Match the Device Status Report console input: ESC [ nn ; mm R +// Returns: +// 0 no match +// >0 match, returns length of match +// -1 incomplete match +static int matchDsr(const char *input, int inputSize) +{ + int32_t dummy = 0; + const char *pch = input; + const char *stop = input + inputSize; + CHECK(*pch == '\x1B'); ADVANCE(); + CHECK(*pch == '['); ADVANCE(); + SCAN_INT(dummy, 8); + CHECK(*pch == ';'); ADVANCE(); + SCAN_INT(dummy, 8); + CHECK(*pch == 'R'); + return pch - input + 1; +} + +static int matchMouseDefault(const char *input, int inputSize, + MouseRecord &out) +{ + const char *pch = input; + const char *stop = input + inputSize; + CHECK(*pch == '\x1B'); ADVANCE(); + CHECK(*pch == '['); ADVANCE(); + CHECK(*pch == 'M'); ADVANCE(); + out.flags = (*pch - 32) & 0xFF; ADVANCE(); + out.coord.X = (*pch - '!') & 0xFF; + ADVANCE(); + out.coord.Y = (*pch - '!') & 0xFF; + out.release = false; + return pch - input + 1; +} + +static int matchMouse1006(const char *input, int inputSize, MouseRecord &out) +{ + const char *pch = input; + const char *stop = input + inputSize; + int32_t temp; + CHECK(*pch == '\x1B'); ADVANCE(); + CHECK(*pch == '['); ADVANCE(); + CHECK(*pch == '<'); ADVANCE(); + SCAN_INT(out.flags, 8); + CHECK(*pch == ';'); ADVANCE(); + SCAN_SIGNED_INT(temp, 8); out.coord.X = temp - 1; + CHECK(*pch == ';'); ADVANCE(); + SCAN_SIGNED_INT(temp, 8); out.coord.Y = temp - 1; + CHECK(*pch == 'M' || *pch == 'm'); + out.release = (*pch == 'm'); + return pch - input + 1; +} + +static int matchMouse1015(const char *input, int inputSize, MouseRecord &out) +{ + const char *pch = input; + const char *stop = input + inputSize; + int32_t temp; + CHECK(*pch == '\x1B'); ADVANCE(); + CHECK(*pch == '['); ADVANCE(); + SCAN_INT(out.flags, 8); out.flags -= 32; + CHECK(*pch == ';'); ADVANCE(); + SCAN_SIGNED_INT(temp, 8); out.coord.X = temp - 1; + CHECK(*pch == ';'); ADVANCE(); + SCAN_SIGNED_INT(temp, 8); out.coord.Y = temp - 1; + CHECK(*pch == 'M'); + out.release = false; + return pch - input + 1; +} + +// Match a mouse input escape sequence of any kind. +// 0 no match +// >0 match, returns length of match +// -1 incomplete match +static int matchMouseRecord(const char *input, int inputSize, MouseRecord &out) +{ + memset(&out, 0, sizeof(out)); + int ret; + if ((ret = matchMouse1006(input, inputSize, out)) != 0) { return ret; } + if ((ret = matchMouse1015(input, inputSize, out)) != 0) { return ret; } + if ((ret = matchMouseDefault(input, inputSize, out)) != 0) { return ret; } + return 0; +} + +#undef CHECK +#undef ADVANCE +#undef SCAN_INT + +} // anonymous namespace + +ConsoleInput::ConsoleInput(HANDLE conin, int mouseMode, DsrSender &dsrSender, + Win32Console &console) : + m_console(console), + m_conin(conin), + m_mouseMode(mouseMode), + m_dsrSender(dsrSender) +{ + addDefaultEntriesToInputMap(m_inputMap); + if (hasDebugFlag("dump_input_map")) { + m_inputMap.dumpInputMap(); + } + + // Configure Quick Edit mode according to the mouse mode. Enable + // InsertMode for two reasons: + // - If it's OFF, it's difficult for the user to turn it ON. The + // properties dialog is inaccesible. winpty still faithfully handles + // the Insert key, which toggles between the insertion and overwrite + // modes. + // - When we modify the QuickEdit setting, if ExtendedFlags is OFF, + // then we must choose the InsertMode setting. I don't *think* this + // case happens, though, because a new console always has ExtendedFlags + // ON. + // See misc/EnableExtendedFlags.txt. + DWORD mode = 0; + if (!GetConsoleMode(conin, &mode)) { + trace("Agent startup: GetConsoleMode failed"); + } else { + mode |= ENABLE_EXTENDED_FLAGS; + mode |= ENABLE_INSERT_MODE; + if (m_mouseMode == WINPTY_MOUSE_MODE_AUTO) { + mode |= ENABLE_QUICK_EDIT_MODE; + } else { + mode &= ~ENABLE_QUICK_EDIT_MODE; + } + if (!SetConsoleMode(conin, mode)) { + trace("Agent startup: SetConsoleMode failed"); + } + } + + updateInputFlags(true); +} + +void ConsoleInput::writeInput(const std::string &input) +{ + if (input.size() == 0) { + return; + } + + if (isTracingEnabled()) { + static bool debugInput = hasDebugFlag("input"); + if (debugInput) { + std::string dumpString; + for (size_t i = 0; i < input.size(); ++i) { + const char ch = input[i]; + const char ctrl = decodeUnixCtrlChar(ch); + if (ctrl != '\0') { + dumpString += '^'; + dumpString += ctrl; + } else { + dumpString += ch; + } + } + dumpString += " ("; + for (size_t i = 0; i < input.size(); ++i) { + if (i > 0) { + dumpString += ' '; + } + const unsigned char uch = input[i]; + char buf[32]; + winpty_snprintf(buf, "%02X", uch); + dumpString += buf; + } + dumpString += ')'; + trace("input chars: %s", dumpString.c_str()); + } + } + + m_byteQueue.append(input); + doWrite(false); + if (!m_byteQueue.empty() && !m_dsrSent) { + trace("send DSR"); + m_dsrSender.sendDsr(); + m_dsrSent = true; + } + m_lastWriteTick = GetTickCount(); +} + +void ConsoleInput::flushIncompleteEscapeCode() +{ + if (!m_byteQueue.empty() && + (GetTickCount() - m_lastWriteTick) > kIncompleteEscapeTimeoutMs) { + doWrite(true); + m_byteQueue.clear(); + } +} + +void ConsoleInput::updateInputFlags(bool forceTrace) +{ + const DWORD mode = inputConsoleMode(); + const bool newFlagEE = (mode & ENABLE_EXTENDED_FLAGS) != 0; + const bool newFlagMI = (mode & ENABLE_MOUSE_INPUT) != 0; + const bool newFlagQE = (mode & ENABLE_QUICK_EDIT_MODE) != 0; + const bool newFlagEI = (mode & 0x200) != 0; + if (forceTrace || + newFlagEE != m_enableExtendedEnabled || + newFlagMI != m_mouseInputEnabled || + newFlagQE != m_quickEditEnabled || + newFlagEI != m_escapeInputEnabled) { + trace("CONIN modes: Extended=%s, MouseInput=%s QuickEdit=%s EscapeInput=%s", + newFlagEE ? "on" : "off", + newFlagMI ? "on" : "off", + newFlagQE ? "on" : "off", + newFlagEI ? "on" : "off"); + } + m_enableExtendedEnabled = newFlagEE; + m_mouseInputEnabled = newFlagMI; + m_quickEditEnabled = newFlagQE; + m_escapeInputEnabled = newFlagEI; +} + +bool ConsoleInput::shouldActivateTerminalMouse() +{ + // Return whether the agent should activate the terminal's mouse mode. + if (m_mouseMode == WINPTY_MOUSE_MODE_AUTO) { + // Some programs (e.g. Cygwin command-line programs like bash.exe and + // python2.7.exe) turn off ENABLE_EXTENDED_FLAGS and turn on + // ENABLE_MOUSE_INPUT, but do not turn off QuickEdit mode and do not + // actually care about mouse input. Only enable the terminal mouse + // mode if ENABLE_EXTENDED_FLAGS is on. See + // misc/EnableExtendedFlags.txt. + return m_mouseInputEnabled && !m_quickEditEnabled && + m_enableExtendedEnabled; + } else if (m_mouseMode == WINPTY_MOUSE_MODE_FORCE) { + return true; + } else { + return false; + } +} + +void ConsoleInput::doWrite(bool isEof) +{ + const char *data = m_byteQueue.c_str(); + std::vector records; + size_t idx = 0; + while (idx < m_byteQueue.size()) { + int charSize = scanInput(records, &data[idx], m_byteQueue.size() - idx, isEof); + if (charSize == -1) + break; + idx += charSize; + } + m_byteQueue.erase(0, idx); + flushInputRecords(records); +} + +void ConsoleInput::flushInputRecords(std::vector &records) +{ + if (records.size() == 0) { + return; + } + DWORD actual = 0; + if (!WriteConsoleInputW(m_conin, records.data(), records.size(), &actual)) { + trace("WriteConsoleInputW failed"); + } + records.clear(); +} + +// This behavior isn't strictly correct, because the keypresses (probably?) +// adopt the keyboard state (e.g. Ctrl/Alt/Shift modifiers) of the current +// window station's keyboard, which has no necessary relationship to the winpty +// instance. It's unlikely to be an issue in practice, but it's conceivable. +// (Imagine a foreground SSH server, where the local user holds down Ctrl, +// while the remote user tries to use WSL navigation keys.) I suspect using +// the BackgroundDesktop mechanism in winpty would fix the problem. +// +// https://github.com/rprichard/winpty/issues/116 +static void sendKeyMessage(HWND hwnd, bool isKeyDown, uint16_t virtualKey) +{ + uint32_t scanCode = MapVirtualKey(virtualKey, MAPVK_VK_TO_VSC); + if (scanCode > 255) { + scanCode = 0; + } + SendMessage(hwnd, isKeyDown ? WM_KEYDOWN : WM_KEYUP, virtualKey, + (scanCode << 16) | 1u | (isKeyDown ? 0u : 0xc0000000u)); +} + +int ConsoleInput::scanInput(std::vector &records, + const char *input, + int inputSize, + bool isEof) +{ + ASSERT(inputSize >= 1); + + // Ctrl-C. + // + // In processed mode, use GenerateConsoleCtrlEvent so that Ctrl-C handlers + // are called. GenerateConsoleCtrlEvent unfortunately doesn't interrupt + // ReadConsole calls[1]. Using WM_KEYDOWN/UP fixes the ReadConsole + // problem, but breaks in background window stations/desktops. + // + // In unprocessed mode, there's an entry for Ctrl-C in the SimpleEncoding + // table in DefaultInputMap. + // + // [1] https://github.com/rprichard/winpty/issues/116 + if (input[0] == '\x03' && (inputConsoleMode() & ENABLE_PROCESSED_INPUT)) { + flushInputRecords(records); + trace("Ctrl-C"); + const BOOL ret = GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); + trace("GenerateConsoleCtrlEvent: %d", ret); + return 1; + } + + if (input[0] == '\x1B') { + // Attempt to match the Device Status Report (DSR) reply. + int dsrLen = matchDsr(input, inputSize); + if (dsrLen > 0) { + trace("Received a DSR reply"); + m_dsrSent = false; + return dsrLen; + } else if (!isEof && dsrLen == -1) { + // Incomplete DSR match. + trace("Incomplete DSR match"); + return -1; + } + + int mouseLen = scanMouseInput(records, input, inputSize); + if (mouseLen > 0 || (!isEof && mouseLen == -1)) { + return mouseLen; + } + } + + // Search the input map. + InputMap::Key match; + bool incomplete; + int matchLen = m_inputMap.lookupKey(input, inputSize, match, incomplete); + if (!isEof && incomplete) { + // Incomplete match -- need more characters (or wait for a + // timeout to signify flushed input). + trace("Incomplete escape sequence"); + return -1; + } else if (matchLen > 0) { + uint32_t winCodePointDn = match.unicodeChar; + if ((match.keyState & LEFT_CTRL_PRESSED) && (match.keyState & LEFT_ALT_PRESSED)) { + winCodePointDn = '\0'; + } + uint32_t winCodePointUp = winCodePointDn; + if (match.keyState & LEFT_ALT_PRESSED) { + winCodePointUp = '\0'; + } + appendKeyPress(records, match.virtualKey, + winCodePointDn, winCodePointUp, match.keyState, + match.unicodeChar, match.keyState); + return matchLen; + } + + // Recognize Alt-. + // + // This code doesn't match Alt-ESC, which is encoded as `ESC ESC`, but + // maybe it should. I was concerned that pressing ESC rapidly enough could + // accidentally trigger Alt-ESC. (e.g. The user would have to be faster + // than the DSR flushing mechanism or use a decrepit terminal. The user + // might be on a slow network connection.) + if (input[0] == '\x1B' && inputSize >= 2 && input[1] != '\x1B') { + const int len = utf8CharLength(input[1]); + if (len > 0) { + if (1 + len > inputSize) { + // Incomplete character. + trace("Incomplete UTF-8 character in Alt-"); + return -1; + } + appendUtf8Char(records, &input[1], len, true); + return 1 + len; + } + } + + // A UTF-8 character. + const int len = utf8CharLength(input[0]); + if (len == 0) { + static bool debugInput = isTracingEnabled() && hasDebugFlag("input"); + if (debugInput) { + trace("Discarding invalid input byte: %02X", + static_cast(input[0])); + } + return 1; + } + if (len > inputSize) { + // Incomplete character. + trace("Incomplete UTF-8 character"); + return -1; + } + appendUtf8Char(records, &input[0], len, false); + return len; +} + +int ConsoleInput::scanMouseInput(std::vector &records, + const char *input, + int inputSize) +{ + MouseRecord record; + const int len = matchMouseRecord(input, inputSize, record); + if (len <= 0) { + return len; + } + + if (isTracingEnabled()) { + static bool debugInput = hasDebugFlag("input"); + if (debugInput) { + trace("mouse input: %s", record.toString().c_str()); + } + } + + const int button = record.flags & 0x03; + INPUT_RECORD newRecord = {0}; + newRecord.EventType = MOUSE_EVENT; + MOUSE_EVENT_RECORD &mer = newRecord.Event.MouseEvent; + + mer.dwMousePosition.X = + m_mouseWindowRect.Left + + std::max(0, std::min(record.coord.X, + m_mouseWindowRect.width() - 1)); + + mer.dwMousePosition.Y = + m_mouseWindowRect.Top + + std::max(0, std::min(record.coord.Y, + m_mouseWindowRect.height() - 1)); + + // The modifier state is neatly independent of everything else. + if (record.flags & 0x04) { mer.dwControlKeyState |= SHIFT_PRESSED; } + if (record.flags & 0x08) { mer.dwControlKeyState |= LEFT_ALT_PRESSED; } + if (record.flags & 0x10) { mer.dwControlKeyState |= LEFT_CTRL_PRESSED; } + + if (record.flags & 0x40) { + // Mouse wheel + mer.dwEventFlags |= MOUSE_WHEELED; + if (button == 0) { + // up + mer.dwButtonState |= 0x00780000; + } else if (button == 1) { + // down + mer.dwButtonState |= 0xff880000; + } else { + // Invalid -- do nothing + return len; + } + } else { + // Ordinary mouse event + if (record.flags & 0x20) { mer.dwEventFlags |= MOUSE_MOVED; } + if (button == 3) { + m_mouseButtonState = 0; + // Potentially advance double-click detection. + m_doubleClick.released = true; + } else { + const DWORD relevantFlag = + (button == 0) ? FROM_LEFT_1ST_BUTTON_PRESSED : + (button == 1) ? FROM_LEFT_2ND_BUTTON_PRESSED : + (button == 2) ? RIGHTMOST_BUTTON_PRESSED : + 0; + ASSERT(relevantFlag != 0); + if (record.release) { + m_mouseButtonState &= ~relevantFlag; + if (relevantFlag == m_doubleClick.button) { + // Potentially advance double-click detection. + m_doubleClick.released = true; + } else { + // End double-click detection. + m_doubleClick = DoubleClickDetection(); + } + } else if ((m_mouseButtonState & relevantFlag) == 0) { + // The button has been newly pressed. + m_mouseButtonState |= relevantFlag; + // Detect a double-click. This code looks for an exact + // coordinate match, which is stricter than what Windows does, + // but Windows has pixel coordinates, and we only have terminal + // coordinates. + if (m_doubleClick.button == relevantFlag && + m_doubleClick.pos == record.coord && + (GetTickCount() - m_doubleClick.tick < + GetDoubleClickTime())) { + // Record a double-click and end double-click detection. + mer.dwEventFlags |= DOUBLE_CLICK; + m_doubleClick = DoubleClickDetection(); + } else { + // Begin double-click detection. + m_doubleClick.button = relevantFlag; + m_doubleClick.pos = record.coord; + m_doubleClick.tick = GetTickCount(); + } + } + } + } + + mer.dwButtonState |= m_mouseButtonState; + + if (m_mouseInputEnabled && !m_quickEditEnabled) { + if (isTracingEnabled()) { + static bool debugInput = hasDebugFlag("input"); + if (debugInput) { + trace("mouse event: %s", mouseEventToString(mer).c_str()); + } + } + + records.push_back(newRecord); + } + + return len; +} + +void ConsoleInput::appendUtf8Char(std::vector &records, + const char *charBuffer, + const int charLen, + const bool terminalAltEscape) +{ + const uint32_t codePoint = decodeUtf8(charBuffer); + if (codePoint == static_cast(-1)) { + static bool debugInput = isTracingEnabled() && hasDebugFlag("input"); + if (debugInput) { + StringBuilder error(64); + error << "Discarding invalid UTF-8 sequence:"; + for (int i = 0; i < charLen; ++i) { + error << ' '; + error << hexOfInt(charBuffer[i]); + } + trace("%s", error.c_str()); + } + return; + } + + const short charScan = codePoint > 0xFFFF ? -1 : VkKeyScan(codePoint); + uint16_t virtualKey = 0; + uint16_t winKeyState = 0; + uint32_t winCodePointDn = codePoint; + uint32_t winCodePointUp = codePoint; + uint16_t vtKeyState = 0; + + if (charScan != -1) { + virtualKey = charScan & 0xFF; + if (charScan & 0x100) { + winKeyState |= SHIFT_PRESSED; + } + if (charScan & 0x200) { + winKeyState |= LEFT_CTRL_PRESSED; + } + if (charScan & 0x400) { + winKeyState |= RIGHT_ALT_PRESSED; + } + if (terminalAltEscape && (winKeyState & LEFT_CTRL_PRESSED)) { + // If the terminal escapes a Ctrl- with Alt, then set the + // codepoint to 0. On the other hand, if a character requires + // AltGr (like U+00B2 on a German layout), then VkKeyScan will + // report both Ctrl and Alt pressed, and we should keep the + // codepoint. See https://github.com/rprichard/winpty/issues/109. + winCodePointDn = 0; + winCodePointUp = 0; + } + } + if (terminalAltEscape) { + winCodePointUp = 0; + winKeyState |= LEFT_ALT_PRESSED; + vtKeyState |= LEFT_ALT_PRESSED; + } + + appendKeyPress(records, virtualKey, + winCodePointDn, winCodePointUp, winKeyState, + codePoint, vtKeyState); +} + +void ConsoleInput::appendKeyPress(std::vector &records, + const uint16_t virtualKey, + const uint32_t winCodePointDn, + const uint32_t winCodePointUp, + const uint16_t winKeyState, + const uint32_t vtCodePoint, + const uint16_t vtKeyState) +{ + const bool ctrl = (winKeyState & LEFT_CTRL_PRESSED) != 0; + const bool leftAlt = (winKeyState & LEFT_ALT_PRESSED) != 0; + const bool rightAlt = (winKeyState & RIGHT_ALT_PRESSED) != 0; + const bool shift = (winKeyState & SHIFT_PRESSED) != 0; + const bool enhanced = (winKeyState & ENHANCED_KEY) != 0; + bool hasDebugInput = false; + + if (isTracingEnabled()) { + static bool debugInput = hasDebugFlag("input"); + if (debugInput) { + hasDebugInput = true; + InputMap::Key key = { virtualKey, winCodePointDn, winKeyState }; + trace("keypress: %s", key.toString().c_str()); + } + } + + if (m_escapeInputEnabled && + (virtualKey == VK_UP || + virtualKey == VK_DOWN || + virtualKey == VK_LEFT || + virtualKey == VK_RIGHT || + virtualKey == VK_HOME || + virtualKey == VK_END) && + !ctrl && !leftAlt && !rightAlt && !shift) { + flushInputRecords(records); + if (hasDebugInput) { + trace("sending keypress to console HWND"); + } + sendKeyMessage(m_console.hwnd(), true, virtualKey); + sendKeyMessage(m_console.hwnd(), false, virtualKey); + return; + } + + uint16_t stepKeyState = 0; + if (ctrl) { + stepKeyState |= LEFT_CTRL_PRESSED; + appendInputRecord(records, TRUE, VK_CONTROL, 0, stepKeyState); + } + if (leftAlt) { + stepKeyState |= LEFT_ALT_PRESSED; + appendInputRecord(records, TRUE, VK_MENU, 0, stepKeyState); + } + if (rightAlt) { + stepKeyState |= RIGHT_ALT_PRESSED; + appendInputRecord(records, TRUE, VK_MENU, 0, stepKeyState | ENHANCED_KEY); + } + if (shift) { + stepKeyState |= SHIFT_PRESSED; + appendInputRecord(records, TRUE, VK_SHIFT, 0, stepKeyState); + } + if (enhanced) { + stepKeyState |= ENHANCED_KEY; + } + if (m_escapeInputEnabled) { + reencodeEscapedKeyPress(records, virtualKey, vtCodePoint, vtKeyState); + } else { + appendCPInputRecords(records, TRUE, virtualKey, winCodePointDn, stepKeyState); + } + appendCPInputRecords(records, FALSE, virtualKey, winCodePointUp, stepKeyState); + if (enhanced) { + stepKeyState &= ~ENHANCED_KEY; + } + if (shift) { + stepKeyState &= ~SHIFT_PRESSED; + appendInputRecord(records, FALSE, VK_SHIFT, 0, stepKeyState); + } + if (rightAlt) { + stepKeyState &= ~RIGHT_ALT_PRESSED; + appendInputRecord(records, FALSE, VK_MENU, 0, stepKeyState | ENHANCED_KEY); + } + if (leftAlt) { + stepKeyState &= ~LEFT_ALT_PRESSED; + appendInputRecord(records, FALSE, VK_MENU, 0, stepKeyState); + } + if (ctrl) { + stepKeyState &= ~LEFT_CTRL_PRESSED; + appendInputRecord(records, FALSE, VK_CONTROL, 0, stepKeyState); + } +} + +void ConsoleInput::appendCPInputRecords(std::vector &records, + BOOL keyDown, + uint16_t virtualKey, + uint32_t codePoint, + uint16_t keyState) +{ + // This behavior really doesn't match that of the Windows console (in + // normal, non-escape-mode). Judging by the copy-and-paste behavior, + // Windows apparently handles everything outside of the keyboard layout by + // first sending a sequence of Alt+KeyPad events, then finally a key-up + // event whose UnicodeChar has the appropriate value. For U+00A2 (CENT + // SIGN): + // + // key: dn rpt=1 scn=56 LAlt-MENU ch=0 + // key: dn rpt=1 scn=79 LAlt-NUMPAD1 ch=0 + // key: up rpt=1 scn=79 LAlt-NUMPAD1 ch=0 + // key: dn rpt=1 scn=76 LAlt-NUMPAD5 ch=0 + // key: up rpt=1 scn=76 LAlt-NUMPAD5 ch=0 + // key: dn rpt=1 scn=76 LAlt-NUMPAD5 ch=0 + // key: up rpt=1 scn=76 LAlt-NUMPAD5 ch=0 + // key: up rpt=1 scn=56 MENU ch=0xa2 + // + // The Alt+155 value matches the encoding of U+00A2 in CP-437. Curiously, + // if I use "chcp 1252" to change the encoding, then copy-and-pasting + // produces Alt+162 instead. (U+00A2 is 162 in CP-1252.) However, typing + // Alt+155 or Alt+162 produce the same characters regardless of console + // code page. (That is, they use CP-437 and yield U+00A2 and U+00F3.) + // + // For characters outside the BMP, Windows repeats the process for both + // UTF-16 code units, e.g, for U+1F300 (CYCLONE): + // + // key: dn rpt=1 scn=56 LAlt-MENU ch=0 + // key: dn rpt=1 scn=77 LAlt-NUMPAD6 ch=0 + // key: up rpt=1 scn=77 LAlt-NUMPAD6 ch=0 + // key: dn rpt=1 scn=81 LAlt-NUMPAD3 ch=0 + // key: up rpt=1 scn=81 LAlt-NUMPAD3 ch=0 + // key: up rpt=1 scn=56 MENU ch=0xd83c + // key: dn rpt=1 scn=56 LAlt-MENU ch=0 + // key: dn rpt=1 scn=77 LAlt-NUMPAD6 ch=0 + // key: up rpt=1 scn=77 LAlt-NUMPAD6 ch=0 + // key: dn rpt=1 scn=81 LAlt-NUMPAD3 ch=0 + // key: up rpt=1 scn=81 LAlt-NUMPAD3 ch=0 + // key: up rpt=1 scn=56 MENU ch=0xdf00 + // + // In this case, it sends Alt+63 twice, which signifies '?'. Apparently + // CMD and Cygwin bash are both able to decode this. + // + // Also note that typing Alt+NNN still works if NumLock is off, e.g.: + // + // key: dn rpt=1 scn=56 LAlt-MENU ch=0 + // key: dn rpt=1 scn=79 LAlt-END ch=0 + // key: up rpt=1 scn=79 LAlt-END ch=0 + // key: dn rpt=1 scn=76 LAlt-CLEAR ch=0 + // key: up rpt=1 scn=76 LAlt-CLEAR ch=0 + // key: dn rpt=1 scn=76 LAlt-CLEAR ch=0 + // key: up rpt=1 scn=76 LAlt-CLEAR ch=0 + // key: up rpt=1 scn=56 MENU ch=0xa2 + // + // Evidently, the Alt+NNN key events are not intended to be decoded to a + // character. Maybe programs are looking for a key-up ALT/MENU event with + // a non-zero character? + + wchar_t ws[2]; + const int wslen = encodeUtf16(ws, codePoint); + + if (wslen == 1) { + appendInputRecord(records, keyDown, virtualKey, ws[0], keyState); + } else if (wslen == 2) { + appendInputRecord(records, keyDown, virtualKey, ws[0], keyState); + appendInputRecord(records, keyDown, virtualKey, ws[1], keyState); + } else { + // This situation isn't that bad, but it should never happen, + // because invalid codepoints shouldn't reach this point. + trace("INTERNAL ERROR: appendInputRecordCP: invalid codePoint: " + "U+%04X", codePoint); + } +} + +void ConsoleInput::appendInputRecord(std::vector &records, + BOOL keyDown, + uint16_t virtualKey, + wchar_t utf16Char, + uint16_t keyState) +{ + INPUT_RECORD ir = {}; + ir.EventType = KEY_EVENT; + ir.Event.KeyEvent.bKeyDown = keyDown; + ir.Event.KeyEvent.wRepeatCount = 1; + ir.Event.KeyEvent.wVirtualKeyCode = virtualKey; + ir.Event.KeyEvent.wVirtualScanCode = + MapVirtualKey(virtualKey, MAPVK_VK_TO_VSC); + ir.Event.KeyEvent.uChar.UnicodeChar = utf16Char; + ir.Event.KeyEvent.dwControlKeyState = keyState; + records.push_back(ir); +} + +DWORD ConsoleInput::inputConsoleMode() +{ + DWORD mode = 0; + if (!GetConsoleMode(m_conin, &mode)) { + trace("GetConsoleMode failed"); + return 0; + } + return mode; +} diff --git a/src/libs/3rdparty/winpty/src/agent/ConsoleInput.h b/src/libs/3rdparty/winpty/src/agent/ConsoleInput.h new file mode 100644 index 0000000000..e807d973ba --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/ConsoleInput.h @@ -0,0 +1,109 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef CONSOLEINPUT_H +#define CONSOLEINPUT_H + +#include +#include + +#include +#include +#include + +#include "Coord.h" +#include "InputMap.h" +#include "SmallRect.h" + +class Win32Console; +class DsrSender; + +class ConsoleInput +{ +public: + ConsoleInput(HANDLE conin, int mouseMode, DsrSender &dsrSender, + Win32Console &console); + void writeInput(const std::string &input); + void flushIncompleteEscapeCode(); + void setMouseWindowRect(SmallRect val) { m_mouseWindowRect = val; } + void updateInputFlags(bool forceTrace=false); + bool shouldActivateTerminalMouse(); + +private: + void doWrite(bool isEof); + void flushInputRecords(std::vector &records); + int scanInput(std::vector &records, + const char *input, + int inputSize, + bool isEof); + int scanMouseInput(std::vector &records, + const char *input, + int inputSize); + void appendUtf8Char(std::vector &records, + const char *charBuffer, + int charLen, + bool terminalAltEscape); + void appendKeyPress(std::vector &records, + uint16_t virtualKey, + uint32_t winCodePointDn, + uint32_t winCodePointUp, + uint16_t winKeyState, + uint32_t vtCodePoint, + uint16_t vtKeyState); + +public: + static void appendCPInputRecords(std::vector &records, + BOOL keyDown, + uint16_t virtualKey, + uint32_t codePoint, + uint16_t keyState); + static void appendInputRecord(std::vector &records, + BOOL keyDown, + uint16_t virtualKey, + wchar_t utf16Char, + uint16_t keyState); + +private: + DWORD inputConsoleMode(); + +private: + Win32Console &m_console; + HANDLE m_conin = nullptr; + int m_mouseMode = 0; + DsrSender &m_dsrSender; + bool m_dsrSent = false; + std::string m_byteQueue; + InputMap m_inputMap; + DWORD m_lastWriteTick = 0; + DWORD m_mouseButtonState = 0; + struct DoubleClickDetection { + DWORD button = 0; + Coord pos; + DWORD tick = 0; + bool released = false; + } m_doubleClick; + bool m_enableExtendedEnabled = false; + bool m_mouseInputEnabled = false; + bool m_quickEditEnabled = false; + bool m_escapeInputEnabled = false; + SmallRect m_mouseWindowRect; +}; + +#endif // CONSOLEINPUT_H diff --git a/src/libs/3rdparty/winpty/src/agent/ConsoleInputReencoding.cc b/src/libs/3rdparty/winpty/src/agent/ConsoleInputReencoding.cc new file mode 100644 index 0000000000..b79545eea9 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/ConsoleInputReencoding.cc @@ -0,0 +1,121 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "ConsoleInputReencoding.h" + +#include "ConsoleInput.h" + +namespace { + +static void outch(std::vector &out, wchar_t ch) { + ConsoleInput::appendInputRecord(out, TRUE, 0, ch, 0); +} + +} // anonymous namespace + +void reencodeEscapedKeyPress( + std::vector &out, + uint16_t virtualKey, + uint32_t codePoint, + uint16_t keyState) { + + struct EscapedKey { + enum { None, Numeric, Letter } kind; + wchar_t content[2]; + }; + + EscapedKey escapeCode = {}; + switch (virtualKey) { + case VK_UP: escapeCode = { EscapedKey::Letter, {'A'} }; break; + case VK_DOWN: escapeCode = { EscapedKey::Letter, {'B'} }; break; + case VK_RIGHT: escapeCode = { EscapedKey::Letter, {'C'} }; break; + case VK_LEFT: escapeCode = { EscapedKey::Letter, {'D'} }; break; + case VK_CLEAR: escapeCode = { EscapedKey::Letter, {'E'} }; break; + case VK_F1: escapeCode = { EscapedKey::Numeric, {'1', '1'} }; break; + case VK_F2: escapeCode = { EscapedKey::Numeric, {'1', '2'} }; break; + case VK_F3: escapeCode = { EscapedKey::Numeric, {'1', '3'} }; break; + case VK_F4: escapeCode = { EscapedKey::Numeric, {'1', '4'} }; break; + case VK_F5: escapeCode = { EscapedKey::Numeric, {'1', '5'} }; break; + case VK_F6: escapeCode = { EscapedKey::Numeric, {'1', '7'} }; break; + case VK_F7: escapeCode = { EscapedKey::Numeric, {'1', '8'} }; break; + case VK_F8: escapeCode = { EscapedKey::Numeric, {'1', '9'} }; break; + case VK_F9: escapeCode = { EscapedKey::Numeric, {'2', '0'} }; break; + case VK_F10: escapeCode = { EscapedKey::Numeric, {'2', '1'} }; break; + case VK_F11: escapeCode = { EscapedKey::Numeric, {'2', '3'} }; break; + case VK_F12: escapeCode = { EscapedKey::Numeric, {'2', '4'} }; break; + case VK_HOME: escapeCode = { EscapedKey::Letter, {'H'} }; break; + case VK_INSERT: escapeCode = { EscapedKey::Numeric, {'2'} }; break; + case VK_DELETE: escapeCode = { EscapedKey::Numeric, {'3'} }; break; + case VK_END: escapeCode = { EscapedKey::Letter, {'F'} }; break; + case VK_PRIOR: escapeCode = { EscapedKey::Numeric, {'5'} }; break; + case VK_NEXT: escapeCode = { EscapedKey::Numeric, {'6'} }; break; + } + if (escapeCode.kind != EscapedKey::None) { + int flags = 0; + if (keyState & SHIFT_PRESSED) { flags |= 0x1; } + if (keyState & LEFT_ALT_PRESSED) { flags |= 0x2; } + if (keyState & LEFT_CTRL_PRESSED) { flags |= 0x4; } + outch(out, L'\x1b'); + outch(out, L'['); + if (escapeCode.kind == EscapedKey::Numeric) { + for (wchar_t ch : escapeCode.content) { + if (ch != L'\0') { + outch(out, ch); + } + } + } else if (flags != 0) { + outch(out, L'1'); + } + if (flags != 0) { + outch(out, L';'); + outch(out, L'1' + flags); + } + if (escapeCode.kind == EscapedKey::Numeric) { + outch(out, L'~'); + } else { + outch(out, escapeCode.content[0]); + } + return; + } + + switch (virtualKey) { + case VK_BACK: + if (keyState & LEFT_ALT_PRESSED) { + outch(out, L'\x1b'); + } + outch(out, L'\x7f'); + return; + case VK_TAB: + if (keyState & SHIFT_PRESSED) { + outch(out, L'\x1b'); + outch(out, L'['); + outch(out, L'Z'); + return; + } + break; + } + + if (codePoint != 0) { + if (keyState & LEFT_ALT_PRESSED) { + outch(out, L'\x1b'); + } + ConsoleInput::appendCPInputRecords(out, TRUE, 0, codePoint, 0); + } +} diff --git a/src/libs/3rdparty/winpty/src/agent/ConsoleInputReencoding.h b/src/libs/3rdparty/winpty/src/agent/ConsoleInputReencoding.h new file mode 100644 index 0000000000..63bc006b5a --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/ConsoleInputReencoding.h @@ -0,0 +1,36 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef AGENT_CONSOLE_INPUT_REENCODING_H +#define AGENT_CONSOLE_INPUT_REENCODING_H + +#include + +#include + +#include + +void reencodeEscapedKeyPress( + std::vector &records, + uint16_t virtualKey, + uint32_t codePoint, + uint16_t keyState); + +#endif // AGENT_CONSOLE_INPUT_REENCODING_H diff --git a/src/libs/3rdparty/winpty/src/agent/ConsoleLine.cc b/src/libs/3rdparty/winpty/src/agent/ConsoleLine.cc new file mode 100644 index 0000000000..1d2bcb7685 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/ConsoleLine.cc @@ -0,0 +1,152 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +// +// ConsoleLine +// +// This data structure keep tracks of the previous CHAR_INFO content of an +// output line and determines when a line has changed. Detecting line changes +// is made complicated by terminal resizing. +// + +#include "ConsoleLine.h" + +#include + +#include "../shared/WinptyAssert.h" + +static CHAR_INFO blankChar(WORD attributes) +{ + // N.B.: As long as we write to UnicodeChar rather than AsciiChar, there + // are no padding bytes that could contain uninitialized bytes. This fact + // is important for efficient comparison. + CHAR_INFO ret; + ret.Attributes = attributes; + ret.Char.UnicodeChar = L' '; + return ret; +} + +static bool isLineBlank(const CHAR_INFO *line, int length, WORD attributes) +{ + for (int col = 0; col < length; ++col) { + if (line[col].Attributes != attributes || + line[col].Char.UnicodeChar != L' ') { + return false; + } + } + return true; +} + +static inline bool areLinesEqual( + const CHAR_INFO *line1, + const CHAR_INFO *line2, + int length) +{ + return memcmp(line1, line2, sizeof(CHAR_INFO) * length) == 0; +} + +ConsoleLine::ConsoleLine() : m_prevLength(0) +{ +} + +void ConsoleLine::reset() +{ + m_prevLength = 0; + m_prevData.clear(); +} + +// Determines whether the given line is sufficiently different from the +// previously seen line as to justify reoutputting the line. The function +// also sets the `ConsoleLine` to the given line, exactly as if `setLine` had +// been called. +bool ConsoleLine::detectChangeAndSetLine(const CHAR_INFO *const line, const int newLength) +{ + ASSERT(newLength >= 1); + ASSERT(m_prevLength <= static_cast(m_prevData.size())); + + if (newLength == m_prevLength) { + bool equalLines = areLinesEqual(m_prevData.data(), line, newLength); + if (!equalLines) { + setLine(line, newLength); + } + return !equalLines; + } else { + if (m_prevLength == 0) { + setLine(line, newLength); + return true; + } + + ASSERT(m_prevLength >= 1); + const WORD prevBlank = m_prevData[m_prevLength - 1].Attributes; + const WORD newBlank = line[newLength - 1].Attributes; + + bool equalLines = false; + if (newLength < m_prevLength) { + // The line has become shorter. The lines are equal if the common + // part is equal, and if the newly truncated characters were blank. + equalLines = + areLinesEqual(m_prevData.data(), line, newLength) && + isLineBlank(m_prevData.data() + newLength, + m_prevLength - newLength, + newBlank); + } else { + // + // The line has become longer. The lines are equal if the common + // part is equal, and if both the extra characters and any + // potentially reexposed characters are blank. + // + // Two of the most relevant terminals for winpty--mintty and + // jediterm--don't (currently) erase the obscured content when a + // line is cleared, so we should anticipate its existence when + // making a terminal wider and reoutput the line. See: + // + // * https://github.com/mintty/mintty/issues/480 + // * https://github.com/JetBrains/jediterm/issues/118 + // + ASSERT(newLength > m_prevLength); + equalLines = + areLinesEqual(m_prevData.data(), line, m_prevLength) && + isLineBlank(m_prevData.data() + m_prevLength, + std::min(m_prevData.size(), newLength) - m_prevLength, + prevBlank) && + isLineBlank(line + m_prevLength, + newLength - m_prevLength, + prevBlank); + } + setLine(line, newLength); + return !equalLines; + } +} + +void ConsoleLine::setLine(const CHAR_INFO *const line, const int newLength) +{ + if (static_cast(m_prevData.size()) < newLength) { + m_prevData.resize(newLength); + } + memcpy(m_prevData.data(), line, sizeof(CHAR_INFO) * newLength); + m_prevLength = newLength; +} + +void ConsoleLine::blank(WORD attributes) +{ + m_prevData.resize(1); + m_prevData[0] = blankChar(attributes); + m_prevLength = 1; +} diff --git a/src/libs/3rdparty/winpty/src/agent/ConsoleLine.h b/src/libs/3rdparty/winpty/src/agent/ConsoleLine.h new file mode 100644 index 0000000000..802c189c75 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/ConsoleLine.h @@ -0,0 +1,41 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef CONSOLE_LINE_H +#define CONSOLE_LINE_H + +#include + +#include + +class ConsoleLine +{ +public: + ConsoleLine(); + void reset(); + bool detectChangeAndSetLine(const CHAR_INFO *line, int newLength); + void setLine(const CHAR_INFO *line, int newLength); + void blank(WORD attributes); +private: + int m_prevLength; + std::vector m_prevData; +}; + +#endif // CONSOLE_LINE_H diff --git a/src/libs/3rdparty/winpty/src/agent/Coord.h b/src/libs/3rdparty/winpty/src/agent/Coord.h new file mode 100644 index 0000000000..74c98addac --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/Coord.h @@ -0,0 +1,87 @@ +// Copyright (c) 2011-2012 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef COORD_H +#define COORD_H + +#include + +#include + +#include "../shared/winpty_snprintf.h" + +struct Coord : COORD { + Coord() + { + X = 0; + Y = 0; + } + + Coord(SHORT x, SHORT y) + { + X = x; + Y = y; + } + + Coord(COORD other) + { + *(COORD*)this = other; + } + + Coord(const Coord &other) + { + *(COORD*)this = *(const COORD*)&other; + } + + Coord &operator=(const Coord &other) + { + *(COORD*)this = *(const COORD*)&other; + return *this; + } + + bool operator==(const Coord &other) const + { + return X == other.X && Y == other.Y; + } + + bool operator!=(const Coord &other) const + { + return !(*this == other); + } + + Coord operator+(const Coord &other) const + { + return Coord(X + other.X, Y + other.Y); + } + + bool isEmpty() const + { + return X <= 0 || Y <= 0; + } + + std::string toString() const + { + char ret[32]; + winpty_snprintf(ret, "(%d,%d)", X, Y); + return std::string(ret); + } +}; + +#endif // COORD_H diff --git a/src/libs/3rdparty/winpty/src/agent/DebugShowInput.cc b/src/libs/3rdparty/winpty/src/agent/DebugShowInput.cc new file mode 100644 index 0000000000..191b2e1466 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/DebugShowInput.cc @@ -0,0 +1,239 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "DebugShowInput.h" + +#include +#include +#include +#include + +#include + +#include "../shared/StringBuilder.h" +#include "InputMap.h" + +namespace { + +struct Flag { + DWORD value; + const char *text; +}; + +static const Flag kButtonStates[] = { + { FROM_LEFT_1ST_BUTTON_PRESSED, "1" }, + { FROM_LEFT_2ND_BUTTON_PRESSED, "2" }, + { FROM_LEFT_3RD_BUTTON_PRESSED, "3" }, + { FROM_LEFT_4TH_BUTTON_PRESSED, "4" }, + { RIGHTMOST_BUTTON_PRESSED, "R" }, +}; + +static const Flag kControlKeyStates[] = { + { CAPSLOCK_ON, "CapsLock" }, + { ENHANCED_KEY, "Enhanced" }, + { LEFT_ALT_PRESSED, "LAlt" }, + { LEFT_CTRL_PRESSED, "LCtrl" }, + { NUMLOCK_ON, "NumLock" }, + { RIGHT_ALT_PRESSED, "RAlt" }, + { RIGHT_CTRL_PRESSED, "RCtrl" }, + { SCROLLLOCK_ON, "ScrollLock" }, + { SHIFT_PRESSED, "Shift" }, +}; + +static const Flag kMouseEventFlags[] = { + { DOUBLE_CLICK, "Double" }, + { 8/*MOUSE_HWHEELED*/, "HWheel" }, + { MOUSE_MOVED, "Move" }, + { MOUSE_WHEELED, "Wheel" }, +}; + +static void writeFlags(StringBuilder &out, DWORD flags, + const char *remainderName, + const Flag *table, size_t tableSize, + char pre, char sep, char post) { + DWORD remaining = flags; + bool wroteSomething = false; + for (size_t i = 0; i < tableSize; ++i) { + const Flag &f = table[i]; + if ((f.value & flags) == f.value) { + if (!wroteSomething && pre != '\0') { + out << pre; + } else if (wroteSomething && sep != '\0') { + out << sep; + } + out << f.text; + wroteSomething = true; + remaining &= ~f.value; + } + } + if (remaining != 0) { + if (!wroteSomething && pre != '\0') { + out << pre; + } else if (wroteSomething && sep != '\0') { + out << sep; + } + out << remainderName << "(0x" << hexOfInt(remaining) << ')'; + wroteSomething = true; + } + if (wroteSomething && post != '\0') { + out << post; + } +} + +template +static void writeFlags(StringBuilder &out, DWORD flags, + const char *remainderName, + const Flag (&table)[n], + char pre, char sep, char post) { + writeFlags(out, flags, remainderName, table, n, pre, sep, post); +} + +} // anonymous namespace + +std::string controlKeyStatePrefix(DWORD controlKeyState) { + StringBuilder sb; + writeFlags(sb, controlKeyState, + "keyState", kControlKeyStates, '\0', '-', '-'); + return sb.str_moved(); +} + +std::string mouseEventToString(const MOUSE_EVENT_RECORD &mer) { + const uint16_t buttons = mer.dwButtonState & 0xFFFF; + const int16_t wheel = mer.dwButtonState >> 16; + StringBuilder sb; + sb << "pos=" << mer.dwMousePosition.X << ',' + << mer.dwMousePosition.Y; + writeFlags(sb, mer.dwControlKeyState, "keyState", kControlKeyStates, ' ', ' ', '\0'); + writeFlags(sb, mer.dwEventFlags, "flags", kMouseEventFlags, ' ', ' ', '\0'); + writeFlags(sb, buttons, "buttons", kButtonStates, ' ', ' ', '\0'); + if (wheel != 0) { + sb << " wheel=" << wheel; + } + return sb.str_moved(); +} + +void debugShowInput(bool enableMouse, bool escapeInput) { + HANDLE conin = GetStdHandle(STD_INPUT_HANDLE); + DWORD origConsoleMode = 0; + if (!GetConsoleMode(conin, &origConsoleMode)) { + fprintf(stderr, "Error: could not read console mode -- " + "is STDIN a console handle?\n"); + exit(1); + } + DWORD restoreConsoleMode = origConsoleMode; + if (enableMouse && !(restoreConsoleMode & ENABLE_EXTENDED_FLAGS)) { + // We need to disable QuickEdit mode, because it blocks mouse events. + // If ENABLE_EXTENDED_FLAGS wasn't originally in the console mode, then + // we have no way of knowning whether QuickEdit or InsertMode are + // currently enabled. Enable them both (eventually), because they're + // sensible defaults. This case shouldn't happen typically. See + // misc/EnableExtendedFlags.txt. + restoreConsoleMode |= ENABLE_EXTENDED_FLAGS; + restoreConsoleMode |= ENABLE_QUICK_EDIT_MODE; + restoreConsoleMode |= ENABLE_INSERT_MODE; + } + DWORD newConsoleMode = restoreConsoleMode; + newConsoleMode &= ~ENABLE_PROCESSED_INPUT; + newConsoleMode &= ~ENABLE_LINE_INPUT; + newConsoleMode &= ~ENABLE_ECHO_INPUT; + newConsoleMode |= ENABLE_WINDOW_INPUT; + if (enableMouse) { + newConsoleMode |= ENABLE_MOUSE_INPUT; + newConsoleMode &= ~ENABLE_QUICK_EDIT_MODE; + } else { + newConsoleMode &= ~ENABLE_MOUSE_INPUT; + } + if (escapeInput) { + // As of this writing (2016-06-05), Microsoft has shipped two preview + // builds of Windows 10 (14316 and 14342) that include a new "Windows + // Subsystem for Linux" that runs Ubuntu in a new subsystem. Running + // bash in this subsystem requires the non-legacy console mode, and the + // console input buffer is put into a special mode where escape + // sequences are written into the console input buffer. This mode is + // enabled with the 0x200 flag, which is as-yet undocumented. + // See https://github.com/rprichard/winpty/issues/82. + newConsoleMode |= 0x200; + } + if (!SetConsoleMode(conin, newConsoleMode)) { + fprintf(stderr, "Error: could not set console mode " + "(0x%x -> 0x%x -> 0x%x)\n", + static_cast(origConsoleMode), + static_cast(newConsoleMode), + static_cast(restoreConsoleMode)); + exit(1); + } + printf("\nPress any keys -- Ctrl-D exits\n\n"); + INPUT_RECORD records[32]; + DWORD actual = 0; + bool finished = false; + while (!finished && + ReadConsoleInputW(conin, records, 32, &actual) && actual >= 1) { + StringBuilder sb; + for (DWORD i = 0; i < actual; ++i) { + const INPUT_RECORD &record = records[i]; + if (record.EventType == KEY_EVENT) { + const KEY_EVENT_RECORD &ker = record.Event.KeyEvent; + InputMap::Key key = { + ker.wVirtualKeyCode, + ker.uChar.UnicodeChar, + static_cast(ker.dwControlKeyState), + }; + sb << "key: " << (ker.bKeyDown ? "dn" : "up") + << " rpt=" << ker.wRepeatCount + << " scn=" << (ker.wVirtualScanCode ? "0x" : "") << hexOfInt(ker.wVirtualScanCode) + << ' ' << key.toString() << '\n'; + if ((ker.dwControlKeyState & + (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) && + ker.wVirtualKeyCode == 'D') { + finished = true; + break; + } else if (ker.wVirtualKeyCode == 0 && + ker.wVirtualScanCode == 0 && + ker.uChar.UnicodeChar == 4) { + // Also look for a zeroed-out Ctrl-D record generated for + // ENABLE_VIRTUAL_TERMINAL_INPUT. + finished = true; + break; + } + } else if (record.EventType == MOUSE_EVENT) { + const MOUSE_EVENT_RECORD &mer = record.Event.MouseEvent; + sb << "mouse: " << mouseEventToString(mer) << '\n'; + } else if (record.EventType == WINDOW_BUFFER_SIZE_EVENT) { + const WINDOW_BUFFER_SIZE_RECORD &wbsr = + record.Event.WindowBufferSizeEvent; + sb << "buffer-resized: dwSize=(" + << wbsr.dwSize.X << ',' + << wbsr.dwSize.Y << ")\n"; + } else if (record.EventType == MENU_EVENT) { + const MENU_EVENT_RECORD &mer = record.Event.MenuEvent; + sb << "menu-event: commandId=0x" + << hexOfInt(mer.dwCommandId) << '\n'; + } else if (record.EventType == FOCUS_EVENT) { + const FOCUS_EVENT_RECORD &fer = record.Event.FocusEvent; + sb << "focus: " << (fer.bSetFocus ? "gained" : "lost") << '\n'; + } + } + + const auto str = sb.str_moved(); + fwrite(str.data(), 1, str.size(), stdout); + fflush(stdout); + } + SetConsoleMode(conin, restoreConsoleMode); +} diff --git a/src/libs/3rdparty/winpty/src/agent/DebugShowInput.h b/src/libs/3rdparty/winpty/src/agent/DebugShowInput.h new file mode 100644 index 0000000000..4fa13604bd --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/DebugShowInput.h @@ -0,0 +1,32 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef AGENT_DEBUG_SHOW_INPUT_H +#define AGENT_DEBUG_SHOW_INPUT_H + +#include + +#include + +std::string controlKeyStatePrefix(DWORD controlKeyState); +std::string mouseEventToString(const MOUSE_EVENT_RECORD &mer); +void debugShowInput(bool enableMouse, bool escapeInput); + +#endif // AGENT_DEBUG_SHOW_INPUT_H diff --git a/src/libs/3rdparty/winpty/src/agent/DefaultInputMap.cc b/src/libs/3rdparty/winpty/src/agent/DefaultInputMap.cc new file mode 100644 index 0000000000..5e29d98e4e --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/DefaultInputMap.cc @@ -0,0 +1,422 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "DefaultInputMap.h" + +#include +#include + +#include + +#include "../shared/StringBuilder.h" +#include "../shared/WinptyAssert.h" +#include "InputMap.h" + +#define ESC "\x1B" +#define DIM(x) (sizeof(x) / sizeof((x)[0])) + +namespace { + +struct EscapeEncoding { + bool alt_prefix_allowed; + char prefix; + char id; + int modifiers; + InputMap::Key key; +}; + +// Modifiers. A "modifier" is an integer from 2 to 8 that conveys the status +// of Shift(1), Alt(2), and Ctrl(4). The value is constructed by OR'ing the +// appropriate value for each active modifier, then adding 1. +// +// Details: +// - kBare: expands to: ESC +// - kSemiMod: expands to: ESC ; +// - kBareMod: expands to: ESC +const int kBare = 0x01; +const int kSemiMod = 0x02; +const int kBareMod = 0x04; + +// Numeric escape sequences suffixes: +// - with no flag: accept: ~ +// - kSuffixCtrl: accept: ~ ^ +// - kSuffixShift: accept: ~ $ +// - kSuffixBoth: accept: ~ ^ $ @ +const int kSuffixCtrl = 0x08; +const int kSuffixShift = 0x10; +const int kSuffixBoth = kSuffixCtrl | kSuffixShift; + +static const EscapeEncoding escapeLetterEncodings[] = { + // Conventional arrow keys + // kBareMod: Ubuntu /etc/inputrc and IntelliJ/JediTerm use escapes like: ESC [ n ABCD + { true, '[', 'A', kBare | kBareMod | kSemiMod, { VK_UP, '\0', 0 } }, + { true, '[', 'B', kBare | kBareMod | kSemiMod, { VK_DOWN, '\0', 0 } }, + { true, '[', 'C', kBare | kBareMod | kSemiMod, { VK_RIGHT, '\0', 0 } }, + { true, '[', 'D', kBare | kBareMod | kSemiMod, { VK_LEFT, '\0', 0 } }, + + // putty. putty uses this sequence for Ctrl-Arrow, Shift-Arrow, and + // Ctrl-Shift-Arrow, but I can only decode to one choice, so I'm just + // leaving the modifier off altogether. + { true, 'O', 'A', kBare, { VK_UP, '\0', 0 } }, + { true, 'O', 'B', kBare, { VK_DOWN, '\0', 0 } }, + { true, 'O', 'C', kBare, { VK_RIGHT, '\0', 0 } }, + { true, 'O', 'D', kBare, { VK_LEFT, '\0', 0 } }, + + // rxvt, rxvt-unicode + // Shift-Ctrl-Arrow can't be identified. It's the same as Shift-Arrow. + { true, '[', 'a', kBare, { VK_UP, '\0', SHIFT_PRESSED } }, + { true, '[', 'b', kBare, { VK_DOWN, '\0', SHIFT_PRESSED } }, + { true, '[', 'c', kBare, { VK_RIGHT, '\0', SHIFT_PRESSED } }, + { true, '[', 'd', kBare, { VK_LEFT, '\0', SHIFT_PRESSED } }, + { true, 'O', 'a', kBare, { VK_UP, '\0', LEFT_CTRL_PRESSED } }, + { true, 'O', 'b', kBare, { VK_DOWN, '\0', LEFT_CTRL_PRESSED } }, + { true, 'O', 'c', kBare, { VK_RIGHT, '\0', LEFT_CTRL_PRESSED } }, + { true, 'O', 'd', kBare, { VK_LEFT, '\0', LEFT_CTRL_PRESSED } }, + + // Numpad 5 with NumLock off + // * xterm, mintty, and gnome-terminal use `ESC [ E`. + // * putty, TERM=cygwin, TERM=linux all use `ESC [ G` for 5 + // * putty uses `ESC O G` for Ctrl-5 and Shift-5. Omit the modifier + // as with putty's arrow keys. + // * I never saw modifiers inserted into these escapes, but I think + // it should be completely OK with the CSI escapes. + { true, '[', 'E', kBare | kSemiMod, { VK_CLEAR, '\0', 0 } }, + { true, '[', 'G', kBare | kSemiMod, { VK_CLEAR, '\0', 0 } }, + { true, 'O', 'G', kBare, { VK_CLEAR, '\0', 0 } }, + + // Home/End, letter version + // * gnome-terminal uses `ESC O [HF]`. I never saw it modified. + // kBareMod: IntelliJ/JediTerm uses escapes like: ESC [ n HF + { true, '[', 'H', kBare | kBareMod | kSemiMod, { VK_HOME, '\0', 0 } }, + { true, '[', 'F', kBare | kBareMod | kSemiMod, { VK_END, '\0', 0 } }, + { true, 'O', 'H', kBare, { VK_HOME, '\0', 0 } }, + { true, 'O', 'F', kBare, { VK_END, '\0', 0 } }, + + // F1-F4, letter version (xterm, VTE, konsole) + { true, '[', 'P', kSemiMod, { VK_F1, '\0', 0 } }, + { true, '[', 'Q', kSemiMod, { VK_F2, '\0', 0 } }, + { true, '[', 'R', kSemiMod, { VK_F3, '\0', 0 } }, + { true, '[', 'S', kSemiMod, { VK_F4, '\0', 0 } }, + + // GNOME VTE and Konsole have special encodings for modified F1-F4: + // * [VTE] ESC O 1 ; n [PQRS] + // * [Konsole] ESC O n [PQRS] + { false, 'O', 'P', kBare | kBareMod | kSemiMod, { VK_F1, '\0', 0 } }, + { false, 'O', 'Q', kBare | kBareMod | kSemiMod, { VK_F2, '\0', 0 } }, + { false, 'O', 'R', kBare | kBareMod | kSemiMod, { VK_F3, '\0', 0 } }, + { false, 'O', 'S', kBare | kBareMod | kSemiMod, { VK_F4, '\0', 0 } }, + + // Handle the "application numpad" escape sequences. + // + // Terminals output these codes under various circumstances: + // * rxvt-unicode: numpad, hold down SHIFT + // * rxvt: numpad, by default + // * xterm: numpad, after enabling app-mode using DECPAM (`ESC =`). xterm + // generates `ESC O ` for modified numpad presses, + // necessitating kBareMod. + // * mintty: by combining Ctrl with various keys such as '1' or ','. + // Handling those keys is difficult, because mintty is generating the + // same sequence for Ctrl-1 and Ctrl-NumPadEnd -- should the virtualKey + // be '1' or VK_HOME? + + { true, 'O', 'M', kBare | kBareMod, { VK_RETURN, '\r', 0 } }, + { true, 'O', 'j', kBare | kBareMod, { VK_MULTIPLY, '*', 0 } }, + { true, 'O', 'k', kBare | kBareMod, { VK_ADD, '+', 0 } }, + { true, 'O', 'm', kBare | kBareMod, { VK_SUBTRACT, '-', 0 } }, + { true, 'O', 'n', kBare | kBareMod, { VK_DELETE, '\0', 0 } }, + { true, 'O', 'o', kBare | kBareMod, { VK_DIVIDE, '/', 0 } }, + { true, 'O', 'p', kBare | kBareMod, { VK_INSERT, '\0', 0 } }, + { true, 'O', 'q', kBare | kBareMod, { VK_END, '\0', 0 } }, + { true, 'O', 'r', kBare | kBareMod, { VK_DOWN, '\0', 0 } }, + { true, 'O', 's', kBare | kBareMod, { VK_NEXT, '\0', 0 } }, + { true, 'O', 't', kBare | kBareMod, { VK_LEFT, '\0', 0 } }, + { true, 'O', 'u', kBare | kBareMod, { VK_CLEAR, '\0', 0 } }, + { true, 'O', 'v', kBare | kBareMod, { VK_RIGHT, '\0', 0 } }, + { true, 'O', 'w', kBare | kBareMod, { VK_HOME, '\0', 0 } }, + { true, 'O', 'x', kBare | kBareMod, { VK_UP, '\0', 0 } }, + { true, 'O', 'y', kBare | kBareMod, { VK_PRIOR, '\0', 0 } }, + + { true, '[', 'M', kBare | kSemiMod, { VK_RETURN, '\r', 0 } }, + { true, '[', 'j', kBare | kSemiMod, { VK_MULTIPLY, '*', 0 } }, + { true, '[', 'k', kBare | kSemiMod, { VK_ADD, '+', 0 } }, + { true, '[', 'm', kBare | kSemiMod, { VK_SUBTRACT, '-', 0 } }, + { true, '[', 'n', kBare | kSemiMod, { VK_DELETE, '\0', 0 } }, + { true, '[', 'o', kBare | kSemiMod, { VK_DIVIDE, '/', 0 } }, + { true, '[', 'p', kBare | kSemiMod, { VK_INSERT, '\0', 0 } }, + { true, '[', 'q', kBare | kSemiMod, { VK_END, '\0', 0 } }, + { true, '[', 'r', kBare | kSemiMod, { VK_DOWN, '\0', 0 } }, + { true, '[', 's', kBare | kSemiMod, { VK_NEXT, '\0', 0 } }, + { true, '[', 't', kBare | kSemiMod, { VK_LEFT, '\0', 0 } }, + { true, '[', 'u', kBare | kSemiMod, { VK_CLEAR, '\0', 0 } }, + { true, '[', 'v', kBare | kSemiMod, { VK_RIGHT, '\0', 0 } }, + { true, '[', 'w', kBare | kSemiMod, { VK_HOME, '\0', 0 } }, + { true, '[', 'x', kBare | kSemiMod, { VK_UP, '\0', 0 } }, + { true, '[', 'y', kBare | kSemiMod, { VK_PRIOR, '\0', 0 } }, + + { false, '[', 'Z', kBare, { VK_TAB, '\t', SHIFT_PRESSED } }, +}; + +static const EscapeEncoding escapeNumericEncodings[] = { + { true, '[', 1, kBare | kSemiMod | kSuffixBoth, { VK_HOME, '\0', 0 } }, + { true, '[', 2, kBare | kSemiMod | kSuffixBoth, { VK_INSERT, '\0', 0 } }, + { true, '[', 3, kBare | kSemiMod | kSuffixBoth, { VK_DELETE, '\0', 0 } }, + { true, '[', 4, kBare | kSemiMod | kSuffixBoth, { VK_END, '\0', 0 } }, + { true, '[', 5, kBare | kSemiMod | kSuffixBoth, { VK_PRIOR, '\0', 0 } }, + { true, '[', 6, kBare | kSemiMod | kSuffixBoth, { VK_NEXT, '\0', 0 } }, + { true, '[', 7, kBare | kSemiMod | kSuffixBoth, { VK_HOME, '\0', 0 } }, + { true, '[', 8, kBare | kSemiMod | kSuffixBoth, { VK_END, '\0', 0 } }, + { true, '[', 11, kBare | kSemiMod | kSuffixBoth, { VK_F1, '\0', 0 } }, + { true, '[', 12, kBare | kSemiMod | kSuffixBoth, { VK_F2, '\0', 0 } }, + { true, '[', 13, kBare | kSemiMod | kSuffixBoth, { VK_F3, '\0', 0 } }, + { true, '[', 14, kBare | kSemiMod | kSuffixBoth, { VK_F4, '\0', 0 } }, + { true, '[', 15, kBare | kSemiMod | kSuffixBoth, { VK_F5, '\0', 0 } }, + { true, '[', 17, kBare | kSemiMod | kSuffixBoth, { VK_F6, '\0', 0 } }, + { true, '[', 18, kBare | kSemiMod | kSuffixBoth, { VK_F7, '\0', 0 } }, + { true, '[', 19, kBare | kSemiMod | kSuffixBoth, { VK_F8, '\0', 0 } }, + { true, '[', 20, kBare | kSemiMod | kSuffixBoth, { VK_F9, '\0', 0 } }, + { true, '[', 21, kBare | kSemiMod | kSuffixBoth, { VK_F10, '\0', 0 } }, + { true, '[', 23, kBare | kSemiMod | kSuffixBoth, { VK_F11, '\0', 0 } }, + { true, '[', 24, kBare | kSemiMod | kSuffixBoth, { VK_F12, '\0', 0 } }, + { true, '[', 25, kBare | kSemiMod | kSuffixBoth, { VK_F3, '\0', SHIFT_PRESSED } }, + { true, '[', 26, kBare | kSemiMod | kSuffixBoth, { VK_F4, '\0', SHIFT_PRESSED } }, + { true, '[', 28, kBare | kSemiMod | kSuffixBoth, { VK_F5, '\0', SHIFT_PRESSED } }, + { true, '[', 29, kBare | kSemiMod | kSuffixBoth, { VK_F6, '\0', SHIFT_PRESSED } }, + { true, '[', 31, kBare | kSemiMod | kSuffixBoth, { VK_F7, '\0', SHIFT_PRESSED } }, + { true, '[', 32, kBare | kSemiMod | kSuffixBoth, { VK_F8, '\0', SHIFT_PRESSED } }, + { true, '[', 33, kBare | kSemiMod | kSuffixBoth, { VK_F9, '\0', SHIFT_PRESSED } }, + { true, '[', 34, kBare | kSemiMod | kSuffixBoth, { VK_F10, '\0', SHIFT_PRESSED } }, +}; + +const int kCsiShiftModifier = 1; +const int kCsiAltModifier = 2; +const int kCsiCtrlModifier = 4; + +static inline bool useEnhancedForVirtualKey(uint16_t vk) { + switch (vk) { + case VK_UP: + case VK_DOWN: + case VK_LEFT: + case VK_RIGHT: + case VK_INSERT: + case VK_DELETE: + case VK_HOME: + case VK_END: + case VK_PRIOR: + case VK_NEXT: + return true; + default: + return false; + } +} + +static void addSimpleEntries(InputMap &inputMap) { + struct SimpleEncoding { + const char *encoding; + InputMap::Key key; + }; + + static const SimpleEncoding simpleEncodings[] = { + // Ctrl- seems to be handled OK by the default code path. + + { "\x7F", { VK_BACK, '\x08', 0, } }, + { ESC "\x7F", { VK_BACK, '\x08', LEFT_ALT_PRESSED, } }, + { "\x03", { 'C', '\x03', LEFT_CTRL_PRESSED, } }, + + // Handle special F1-F5 for TERM=linux and TERM=cygwin. + { ESC "[[A", { VK_F1, '\0', 0 } }, + { ESC "[[B", { VK_F2, '\0', 0 } }, + { ESC "[[C", { VK_F3, '\0', 0 } }, + { ESC "[[D", { VK_F4, '\0', 0 } }, + { ESC "[[E", { VK_F5, '\0', 0 } }, + + { ESC ESC "[[A", { VK_F1, '\0', LEFT_ALT_PRESSED } }, + { ESC ESC "[[B", { VK_F2, '\0', LEFT_ALT_PRESSED } }, + { ESC ESC "[[C", { VK_F3, '\0', LEFT_ALT_PRESSED } }, + { ESC ESC "[[D", { VK_F4, '\0', LEFT_ALT_PRESSED } }, + { ESC ESC "[[E", { VK_F5, '\0', LEFT_ALT_PRESSED } }, + }; + + for (size_t i = 0; i < DIM(simpleEncodings); ++i) { + auto k = simpleEncodings[i].key; + if (useEnhancedForVirtualKey(k.virtualKey)) { + k.keyState |= ENHANCED_KEY; + } + inputMap.set(simpleEncodings[i].encoding, + strlen(simpleEncodings[i].encoding), + k); + } +} + +struct ExpandContext { + InputMap &inputMap; + const EscapeEncoding &e; + char *buffer; + char *bufferEnd; +}; + +static inline void setEncoding(const ExpandContext &ctx, char *end, + uint16_t extraKeyState) { + InputMap::Key k = ctx.e.key; + k.keyState |= extraKeyState; + if (k.keyState & LEFT_CTRL_PRESSED) { + switch (k.virtualKey) { + case VK_ADD: + case VK_DIVIDE: + case VK_MULTIPLY: + case VK_SUBTRACT: + k.unicodeChar = '\0'; + break; + case VK_RETURN: + k.unicodeChar = '\n'; + break; + } + } + if (useEnhancedForVirtualKey(k.virtualKey)) { + k.keyState |= ENHANCED_KEY; + } + ctx.inputMap.set(ctx.buffer, end - ctx.buffer, k); +} + +static inline uint16_t keyStateForMod(int mod) { + int ret = 0; + if ((mod - 1) & kCsiShiftModifier) ret |= SHIFT_PRESSED; + if ((mod - 1) & kCsiAltModifier) ret |= LEFT_ALT_PRESSED; + if ((mod - 1) & kCsiCtrlModifier) ret |= LEFT_CTRL_PRESSED; + return ret; +} + +static void expandNumericEncodingSuffix(const ExpandContext &ctx, char *p, + uint16_t extraKeyState) { + ASSERT(p <= ctx.bufferEnd - 1); + { + char *q = p; + *q++ = '~'; + setEncoding(ctx, q, extraKeyState); + } + if (ctx.e.modifiers & kSuffixShift) { + char *q = p; + *q++ = '$'; + setEncoding(ctx, q, extraKeyState | SHIFT_PRESSED); + } + if (ctx.e.modifiers & kSuffixCtrl) { + char *q = p; + *q++ = '^'; + setEncoding(ctx, q, extraKeyState | LEFT_CTRL_PRESSED); + } + if (ctx.e.modifiers & (kSuffixCtrl | kSuffixShift)) { + char *q = p; + *q++ = '@'; + setEncoding(ctx, q, extraKeyState | SHIFT_PRESSED | LEFT_CTRL_PRESSED); + } +} + +template +static inline void expandEncodingAfterAltPrefix( + const ExpandContext &ctx, char *p, uint16_t extraKeyState) { + auto appendId = [&](char *&ptr) { + const auto idstr = decOfInt(ctx.e.id); + ASSERT(ptr <= ctx.bufferEnd - idstr.size()); + std::copy(idstr.data(), idstr.data() + idstr.size(), ptr); + ptr += idstr.size(); + }; + ASSERT(p <= ctx.bufferEnd - 2); + *p++ = '\x1b'; + *p++ = ctx.e.prefix; + if (ctx.e.modifiers & kBare) { + char *q = p; + if (is_numeric) { + appendId(q); + expandNumericEncodingSuffix(ctx, q, extraKeyState); + } else { + ASSERT(q <= ctx.bufferEnd - 1); + *q++ = ctx.e.id; + setEncoding(ctx, q, extraKeyState); + } + } + if (ctx.e.modifiers & kBareMod) { + ASSERT(!is_numeric && "kBareMod is invalid with numeric sequences"); + for (int mod = 2; mod <= 8; ++mod) { + char *q = p; + ASSERT(q <= ctx.bufferEnd - 2); + *q++ = '0' + mod; + *q++ = ctx.e.id; + setEncoding(ctx, q, extraKeyState | keyStateForMod(mod)); + } + } + if (ctx.e.modifiers & kSemiMod) { + for (int mod = 2; mod <= 8; ++mod) { + char *q = p; + if (is_numeric) { + appendId(q); + ASSERT(q <= ctx.bufferEnd - 2); + *q++ = ';'; + *q++ = '0' + mod; + expandNumericEncodingSuffix( + ctx, q, extraKeyState | keyStateForMod(mod)); + } else { + ASSERT(q <= ctx.bufferEnd - 4); + *q++ = '1'; + *q++ = ';'; + *q++ = '0' + mod; + *q++ = ctx.e.id; + setEncoding(ctx, q, extraKeyState | keyStateForMod(mod)); + } + } + } +} + +template +static inline void expandEncoding(const ExpandContext &ctx) { + if (ctx.e.alt_prefix_allowed) { + // For better or for worse, this code expands all of: + // * ESC [ -- + // * ESC ESC [ -- Alt- + // * ESC [ 1 ; 3 -- Alt- + // * ESC ESC [ 1 ; 3 -- Alt- specified twice + // I suspect no terminal actually emits the last one (i.e. specifying + // the Alt modifier using both methods), but I have seen a terminal + // that emitted a prefix ESC for Alt and a non-Alt modifier. + char *p = ctx.buffer; + ASSERT(p <= ctx.bufferEnd - 1); + *p++ = '\x1b'; + expandEncodingAfterAltPrefix(ctx, p, LEFT_ALT_PRESSED); + } + expandEncodingAfterAltPrefix(ctx, ctx.buffer, 0); +} + +template +static void addEscapes(InputMap &inputMap, const EscapeEncoding (&encodings)[N]) { + char buffer[32]; + for (size_t i = 0; i < DIM(encodings); ++i) { + ExpandContext ctx = { + inputMap, encodings[i], + buffer, buffer + sizeof(buffer) + }; + expandEncoding(ctx); + } +} + +} // anonymous namespace + +void addDefaultEntriesToInputMap(InputMap &inputMap) { + addEscapes(inputMap, escapeLetterEncodings); + addEscapes(inputMap, escapeNumericEncodings); + addSimpleEntries(inputMap); +} diff --git a/src/libs/3rdparty/winpty/src/agent/DefaultInputMap.h b/src/libs/3rdparty/winpty/src/agent/DefaultInputMap.h new file mode 100644 index 0000000000..c4b9083678 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/DefaultInputMap.h @@ -0,0 +1,28 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef DEFAULT_INPUT_MAP_H +#define DEFAULT_INPUT_MAP_H + +class InputMap; + +void addDefaultEntriesToInputMap(InputMap &inputMap); + +#endif // DEFAULT_INPUT_MAP_H diff --git a/src/libs/3rdparty/winpty/src/agent/DsrSender.h b/src/libs/3rdparty/winpty/src/agent/DsrSender.h new file mode 100644 index 0000000000..1ec0a97d2e --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/DsrSender.h @@ -0,0 +1,30 @@ +// Copyright (c) 2011-2012 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef DSRSENDER_H +#define DSRSENDER_H + +class DsrSender +{ +public: + virtual void sendDsr() = 0; +}; + +#endif // DSRSENDER_H diff --git a/src/libs/3rdparty/winpty/src/agent/EventLoop.cc b/src/libs/3rdparty/winpty/src/agent/EventLoop.cc new file mode 100644 index 0000000000..ba5cf18cc8 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/EventLoop.cc @@ -0,0 +1,99 @@ +// Copyright (c) 2011-2012 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "EventLoop.h" + +#include + +#include "NamedPipe.h" +#include "../shared/DebugClient.h" +#include "../shared/WinptyAssert.h" + +EventLoop::~EventLoop() { + for (NamedPipe *pipe : m_pipes) { + delete pipe; + } + m_pipes.clear(); +} + +// Enter the event loop. Runs until the I/O or timeout handler calls exit(). +void EventLoop::run() +{ + std::vector waitHandles; + DWORD lastTime = GetTickCount(); + while (!m_exiting) { + bool didSomething = false; + + // Attempt to make progress with the pipes. + waitHandles.clear(); + for (size_t i = 0; i < m_pipes.size(); ++i) { + if (m_pipes[i]->serviceIo(&waitHandles)) { + onPipeIo(*m_pipes[i]); + didSomething = true; + } + } + + // Call the timeout if enough time has elapsed. + if (m_pollInterval > 0) { + int elapsed = GetTickCount() - lastTime; + if (elapsed >= m_pollInterval) { + onPollTimeout(); + lastTime = GetTickCount(); + didSomething = true; + } + } + + if (didSomething) + continue; + + // If there's nothing to do, wait. + DWORD timeout = INFINITE; + if (m_pollInterval > 0) + timeout = std::max(0, (int)(lastTime + m_pollInterval - GetTickCount())); + if (waitHandles.size() == 0) { + ASSERT(timeout != INFINITE); + if (timeout > 0) + Sleep(timeout); + } else { + DWORD result = WaitForMultipleObjects(waitHandles.size(), + waitHandles.data(), + FALSE, + timeout); + ASSERT(result != WAIT_FAILED); + } + } +} + +NamedPipe &EventLoop::createNamedPipe() +{ + NamedPipe *ret = new NamedPipe(); + m_pipes.push_back(ret); + return *ret; +} + +void EventLoop::setPollInterval(int ms) +{ + m_pollInterval = ms; +} + +void EventLoop::shutdown() +{ + m_exiting = true; +} diff --git a/src/libs/3rdparty/winpty/src/agent/EventLoop.h b/src/libs/3rdparty/winpty/src/agent/EventLoop.h new file mode 100644 index 0000000000..eddb0f6267 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/EventLoop.h @@ -0,0 +1,47 @@ +// Copyright (c) 2011-2012 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef EVENTLOOP_H +#define EVENTLOOP_H + +#include + +class NamedPipe; + +class EventLoop +{ +public: + virtual ~EventLoop(); + void run(); + +protected: + NamedPipe &createNamedPipe(); + void setPollInterval(int ms); + void shutdown(); + virtual void onPollTimeout() {} + virtual void onPipeIo(NamedPipe &namedPipe) {} + +private: + bool m_exiting = false; + std::vector m_pipes; + int m_pollInterval = 0; +}; + +#endif // EVENTLOOP_H diff --git a/src/libs/3rdparty/winpty/src/agent/InputMap.cc b/src/libs/3rdparty/winpty/src/agent/InputMap.cc new file mode 100644 index 0000000000..b1fbfc2e30 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/InputMap.cc @@ -0,0 +1,246 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "InputMap.h" + +#include +#include +#include +#include + +#include "DebugShowInput.h" +#include "SimplePool.h" +#include "../shared/DebugClient.h" +#include "../shared/UnixCtrlChars.h" +#include "../shared/WinptyAssert.h" +#include "../shared/winpty_snprintf.h" + +namespace { + +static const char *getVirtualKeyString(int virtualKey) +{ + switch (virtualKey) { +#define WINPTY_GVKS_KEY(x) case VK_##x: return #x; + WINPTY_GVKS_KEY(RBUTTON) WINPTY_GVKS_KEY(F9) + WINPTY_GVKS_KEY(CANCEL) WINPTY_GVKS_KEY(F10) + WINPTY_GVKS_KEY(MBUTTON) WINPTY_GVKS_KEY(F11) + WINPTY_GVKS_KEY(XBUTTON1) WINPTY_GVKS_KEY(F12) + WINPTY_GVKS_KEY(XBUTTON2) WINPTY_GVKS_KEY(F13) + WINPTY_GVKS_KEY(BACK) WINPTY_GVKS_KEY(F14) + WINPTY_GVKS_KEY(TAB) WINPTY_GVKS_KEY(F15) + WINPTY_GVKS_KEY(CLEAR) WINPTY_GVKS_KEY(F16) + WINPTY_GVKS_KEY(RETURN) WINPTY_GVKS_KEY(F17) + WINPTY_GVKS_KEY(SHIFT) WINPTY_GVKS_KEY(F18) + WINPTY_GVKS_KEY(CONTROL) WINPTY_GVKS_KEY(F19) + WINPTY_GVKS_KEY(MENU) WINPTY_GVKS_KEY(F20) + WINPTY_GVKS_KEY(PAUSE) WINPTY_GVKS_KEY(F21) + WINPTY_GVKS_KEY(CAPITAL) WINPTY_GVKS_KEY(F22) + WINPTY_GVKS_KEY(HANGUL) WINPTY_GVKS_KEY(F23) + WINPTY_GVKS_KEY(JUNJA) WINPTY_GVKS_KEY(F24) + WINPTY_GVKS_KEY(FINAL) WINPTY_GVKS_KEY(NUMLOCK) + WINPTY_GVKS_KEY(KANJI) WINPTY_GVKS_KEY(SCROLL) + WINPTY_GVKS_KEY(ESCAPE) WINPTY_GVKS_KEY(LSHIFT) + WINPTY_GVKS_KEY(CONVERT) WINPTY_GVKS_KEY(RSHIFT) + WINPTY_GVKS_KEY(NONCONVERT) WINPTY_GVKS_KEY(LCONTROL) + WINPTY_GVKS_KEY(ACCEPT) WINPTY_GVKS_KEY(RCONTROL) + WINPTY_GVKS_KEY(MODECHANGE) WINPTY_GVKS_KEY(LMENU) + WINPTY_GVKS_KEY(SPACE) WINPTY_GVKS_KEY(RMENU) + WINPTY_GVKS_KEY(PRIOR) WINPTY_GVKS_KEY(BROWSER_BACK) + WINPTY_GVKS_KEY(NEXT) WINPTY_GVKS_KEY(BROWSER_FORWARD) + WINPTY_GVKS_KEY(END) WINPTY_GVKS_KEY(BROWSER_REFRESH) + WINPTY_GVKS_KEY(HOME) WINPTY_GVKS_KEY(BROWSER_STOP) + WINPTY_GVKS_KEY(LEFT) WINPTY_GVKS_KEY(BROWSER_SEARCH) + WINPTY_GVKS_KEY(UP) WINPTY_GVKS_KEY(BROWSER_FAVORITES) + WINPTY_GVKS_KEY(RIGHT) WINPTY_GVKS_KEY(BROWSER_HOME) + WINPTY_GVKS_KEY(DOWN) WINPTY_GVKS_KEY(VOLUME_MUTE) + WINPTY_GVKS_KEY(SELECT) WINPTY_GVKS_KEY(VOLUME_DOWN) + WINPTY_GVKS_KEY(PRINT) WINPTY_GVKS_KEY(VOLUME_UP) + WINPTY_GVKS_KEY(EXECUTE) WINPTY_GVKS_KEY(MEDIA_NEXT_TRACK) + WINPTY_GVKS_KEY(SNAPSHOT) WINPTY_GVKS_KEY(MEDIA_PREV_TRACK) + WINPTY_GVKS_KEY(INSERT) WINPTY_GVKS_KEY(MEDIA_STOP) + WINPTY_GVKS_KEY(DELETE) WINPTY_GVKS_KEY(MEDIA_PLAY_PAUSE) + WINPTY_GVKS_KEY(HELP) WINPTY_GVKS_KEY(LAUNCH_MAIL) + WINPTY_GVKS_KEY(LWIN) WINPTY_GVKS_KEY(LAUNCH_MEDIA_SELECT) + WINPTY_GVKS_KEY(RWIN) WINPTY_GVKS_KEY(LAUNCH_APP1) + WINPTY_GVKS_KEY(APPS) WINPTY_GVKS_KEY(LAUNCH_APP2) + WINPTY_GVKS_KEY(SLEEP) WINPTY_GVKS_KEY(OEM_1) + WINPTY_GVKS_KEY(NUMPAD0) WINPTY_GVKS_KEY(OEM_PLUS) + WINPTY_GVKS_KEY(NUMPAD1) WINPTY_GVKS_KEY(OEM_COMMA) + WINPTY_GVKS_KEY(NUMPAD2) WINPTY_GVKS_KEY(OEM_MINUS) + WINPTY_GVKS_KEY(NUMPAD3) WINPTY_GVKS_KEY(OEM_PERIOD) + WINPTY_GVKS_KEY(NUMPAD4) WINPTY_GVKS_KEY(OEM_2) + WINPTY_GVKS_KEY(NUMPAD5) WINPTY_GVKS_KEY(OEM_3) + WINPTY_GVKS_KEY(NUMPAD6) WINPTY_GVKS_KEY(OEM_4) + WINPTY_GVKS_KEY(NUMPAD7) WINPTY_GVKS_KEY(OEM_5) + WINPTY_GVKS_KEY(NUMPAD8) WINPTY_GVKS_KEY(OEM_6) + WINPTY_GVKS_KEY(NUMPAD9) WINPTY_GVKS_KEY(OEM_7) + WINPTY_GVKS_KEY(MULTIPLY) WINPTY_GVKS_KEY(OEM_8) + WINPTY_GVKS_KEY(ADD) WINPTY_GVKS_KEY(OEM_102) + WINPTY_GVKS_KEY(SEPARATOR) WINPTY_GVKS_KEY(PROCESSKEY) + WINPTY_GVKS_KEY(SUBTRACT) WINPTY_GVKS_KEY(PACKET) + WINPTY_GVKS_KEY(DECIMAL) WINPTY_GVKS_KEY(ATTN) + WINPTY_GVKS_KEY(DIVIDE) WINPTY_GVKS_KEY(CRSEL) + WINPTY_GVKS_KEY(F1) WINPTY_GVKS_KEY(EXSEL) + WINPTY_GVKS_KEY(F2) WINPTY_GVKS_KEY(EREOF) + WINPTY_GVKS_KEY(F3) WINPTY_GVKS_KEY(PLAY) + WINPTY_GVKS_KEY(F4) WINPTY_GVKS_KEY(ZOOM) + WINPTY_GVKS_KEY(F5) WINPTY_GVKS_KEY(NONAME) + WINPTY_GVKS_KEY(F6) WINPTY_GVKS_KEY(PA1) + WINPTY_GVKS_KEY(F7) WINPTY_GVKS_KEY(OEM_CLEAR) + WINPTY_GVKS_KEY(F8) +#undef WINPTY_GVKS_KEY + default: return NULL; + } +} + +} // anonymous namespace + +std::string InputMap::Key::toString() const { + std::string ret; + ret += controlKeyStatePrefix(keyState); + char buf[256]; + const char *vkString = getVirtualKeyString(virtualKey); + if (vkString != NULL) { + ret += vkString; + } else if ((virtualKey >= 'A' && virtualKey <= 'Z') || + (virtualKey >= '0' && virtualKey <= '9')) { + ret += static_cast(virtualKey); + } else { + winpty_snprintf(buf, "%#x", virtualKey); + ret += buf; + } + if (unicodeChar >= 32 && unicodeChar <= 126) { + winpty_snprintf(buf, " ch='%c'", + static_cast(unicodeChar)); + } else { + winpty_snprintf(buf, " ch=%#x", + static_cast(unicodeChar)); + } + ret += buf; + return ret; +} + +void InputMap::set(const char *encoding, int encodingLen, const Key &key) { + ASSERT(encodingLen > 0); + setHelper(m_root, encoding, encodingLen, key); +} + +void InputMap::setHelper(Node &node, const char *encoding, int encodingLen, const Key &key) { + if (encodingLen == 0) { + node.key = key; + } else { + setHelper(getOrCreateChild(node, encoding[0]), encoding + 1, encodingLen - 1, key); + } +} + +InputMap::Node &InputMap::getOrCreateChild(Node &node, unsigned char ch) { + Node *ret = getChild(node, ch); + if (ret != NULL) { + return *ret; + } + if (node.childCount < Node::kTinyCount) { + // Maintain sorted order for the sake of the InputMap dumping. + int insertIndex = node.childCount; + for (int i = 0; i < node.childCount; ++i) { + if (ch < node.u.tiny.values[i]) { + insertIndex = i; + break; + } + } + for (int j = node.childCount; j > insertIndex; --j) { + node.u.tiny.values[j] = node.u.tiny.values[j - 1]; + node.u.tiny.children[j] = node.u.tiny.children[j - 1]; + } + node.u.tiny.values[insertIndex] = ch; + node.u.tiny.children[insertIndex] = ret = m_nodePool.alloc(); + ++node.childCount; + return *ret; + } + if (node.childCount == Node::kTinyCount) { + Branch *branch = m_branchPool.alloc(); + for (int i = 0; i < node.childCount; ++i) { + branch->children[node.u.tiny.values[i]] = node.u.tiny.children[i]; + } + node.u.branch = branch; + } + node.u.branch->children[ch] = ret = m_nodePool.alloc(); + ++node.childCount; + return *ret; +} + +// Find the longest matching key and node. +int InputMap::lookupKey(const char *input, int inputSize, + Key &keyOut, bool &incompleteOut) const { + keyOut = kKeyZero; + incompleteOut = false; + + const Node *node = &m_root; + InputMap::Key longestMatch = kKeyZero; + int longestMatchLen = 0; + + for (int i = 0; i < inputSize; ++i) { + unsigned char ch = input[i]; + node = getChild(*node, ch); + if (node == NULL) { + keyOut = longestMatch; + return longestMatchLen; + } else if (node->hasKey()) { + longestMatchLen = i + 1; + longestMatch = node->key; + } + } + keyOut = longestMatch; + incompleteOut = node->childCount > 0; + return longestMatchLen; +} + +void InputMap::dumpInputMap() const { + std::string encoding; + dumpInputMapHelper(m_root, encoding); +} + +void InputMap::dumpInputMapHelper( + const Node &node, std::string &encoding) const { + if (node.hasKey()) { + trace("%s -> %s", + encoding.c_str(), + node.key.toString().c_str()); + } + for (int i = 0; i < 256; ++i) { + const Node *child = getChild(node, i); + if (child != NULL) { + size_t oldSize = encoding.size(); + if (!encoding.empty()) { + encoding.push_back(' '); + } + char ctrlChar = decodeUnixCtrlChar(i); + if (ctrlChar != '\0') { + encoding.push_back('^'); + encoding.push_back(static_cast(ctrlChar)); + } else if (i == ' ') { + encoding.append("' '"); + } else { + encoding.push_back(static_cast(i)); + } + dumpInputMapHelper(*child, encoding); + encoding.resize(oldSize); + } + } +} diff --git a/src/libs/3rdparty/winpty/src/agent/InputMap.h b/src/libs/3rdparty/winpty/src/agent/InputMap.h new file mode 100644 index 0000000000..9a666c7976 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/InputMap.h @@ -0,0 +1,114 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef INPUT_MAP_H +#define INPUT_MAP_H + +#include +#include +#include + +#include + +#include "SimplePool.h" +#include "../shared/WinptyAssert.h" + +class InputMap { +public: + struct Key { + uint16_t virtualKey; + uint32_t unicodeChar; + uint16_t keyState; + + std::string toString() const; + }; + +private: + struct Node; + + struct Branch { + Branch() { + memset(&children, 0, sizeof(children)); + } + + Node *children[256]; + }; + + struct Node { + Node() : childCount(0) { + Key zeroKey = { 0, 0, 0 }; + key = zeroKey; + } + + Key key; + int childCount; + enum { kTinyCount = 8 }; + union { + Branch *branch; + struct { + unsigned char values[kTinyCount]; + Node *children[kTinyCount]; + } tiny; + } u; + + bool hasKey() const { + return key.virtualKey != 0 || key.unicodeChar != 0; + } + }; + +private: + SimplePool m_nodePool; + SimplePool m_branchPool; + Node m_root; + +public: + void set(const char *encoding, int encodingLen, const Key &key); + int lookupKey(const char *input, int inputSize, + Key &keyOut, bool &incompleteOut) const; + void dumpInputMap() const; + +private: + Node *getChild(Node &node, unsigned char ch) { + return const_cast(getChild(static_cast(node), ch)); + } + + const Node *getChild(const Node &node, unsigned char ch) const { + if (node.childCount <= Node::kTinyCount) { + for (int i = 0; i < node.childCount; ++i) { + if (node.u.tiny.values[i] == ch) { + return node.u.tiny.children[i]; + } + } + return NULL; + } else { + return node.u.branch->children[ch]; + } + } + + void setHelper(Node &node, const char *encoding, int encodingLen, const Key &key); + Node &getOrCreateChild(Node &node, unsigned char ch); + void dumpInputMapHelper(const Node &node, std::string &encoding) const; +}; + +const InputMap::Key kKeyZero = { 0, 0, 0 }; + +void dumpInputMap(InputMap &inputMap); + +#endif // INPUT_MAP_H diff --git a/src/libs/3rdparty/winpty/src/agent/LargeConsoleRead.cc b/src/libs/3rdparty/winpty/src/agent/LargeConsoleRead.cc new file mode 100644 index 0000000000..80ac640e48 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/LargeConsoleRead.cc @@ -0,0 +1,71 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "LargeConsoleRead.h" + +#include + +#include "../shared/WindowsVersion.h" +#include "Scraper.h" +#include "Win32ConsoleBuffer.h" + +LargeConsoleReadBuffer::LargeConsoleReadBuffer() : + m_rect(0, 0, 0, 0), m_rectWidth(0) +{ +} + +void largeConsoleRead(LargeConsoleReadBuffer &out, + Win32ConsoleBuffer &buffer, + const SmallRect &readArea, + WORD attributesMask) { + ASSERT(readArea.Left >= 0 && + readArea.Top >= 0 && + readArea.Right >= readArea.Left && + readArea.Bottom >= readArea.Top && + readArea.width() <= MAX_CONSOLE_WIDTH); + const size_t count = readArea.width() * readArea.height(); + if (out.m_data.size() < count) { + out.m_data.resize(count); + } + out.m_rect = readArea; + out.m_rectWidth = readArea.width(); + + static const bool useLargeReads = isAtLeastWindows8(); + if (useLargeReads) { + buffer.read(readArea, out.m_data.data()); + } else { + const int maxReadLines = std::max(1, MAX_CONSOLE_WIDTH / readArea.width()); + int curLine = readArea.Top; + while (curLine <= readArea.Bottom) { + const SmallRect subReadArea( + readArea.Left, + curLine, + readArea.width(), + std::min(maxReadLines, readArea.Bottom + 1 - curLine)); + buffer.read(subReadArea, out.lineDataMut(curLine)); + curLine = subReadArea.Bottom + 1; + } + } + if (attributesMask != static_cast(~0)) { + for (size_t i = 0; i < count; ++i) { + out.m_data[i].Attributes &= attributesMask; + } + } +} diff --git a/src/libs/3rdparty/winpty/src/agent/LargeConsoleRead.h b/src/libs/3rdparty/winpty/src/agent/LargeConsoleRead.h new file mode 100644 index 0000000000..1bcf2c0232 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/LargeConsoleRead.h @@ -0,0 +1,68 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef LARGE_CONSOLE_READ_H +#define LARGE_CONSOLE_READ_H + +#include +#include + +#include + +#include "SmallRect.h" +#include "../shared/DebugClient.h" +#include "../shared/WinptyAssert.h" + +class Win32ConsoleBuffer; + +class LargeConsoleReadBuffer { +public: + LargeConsoleReadBuffer(); + const SmallRect &rect() const { return m_rect; } + const CHAR_INFO *lineData(int line) const { + validateLineNumber(line); + return &m_data[(line - m_rect.Top) * m_rectWidth]; + } + +private: + CHAR_INFO *lineDataMut(int line) { + validateLineNumber(line); + return &m_data[(line - m_rect.Top) * m_rectWidth]; + } + + void validateLineNumber(int line) const { + if (line < m_rect.Top || line > m_rect.Bottom) { + trace("Fatal error: LargeConsoleReadBuffer: invalid line %d for " + "read rect %s", line, m_rect.toString().c_str()); + abort(); + } + } + + SmallRect m_rect; + int m_rectWidth; + std::vector m_data; + + friend void largeConsoleRead(LargeConsoleReadBuffer &out, + Win32ConsoleBuffer &buffer, + const SmallRect &readArea, + WORD attributesMask); +}; + +#endif // LARGE_CONSOLE_READ_H diff --git a/src/libs/3rdparty/winpty/src/agent/NamedPipe.cc b/src/libs/3rdparty/winpty/src/agent/NamedPipe.cc new file mode 100644 index 0000000000..64044e6e5d --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/NamedPipe.cc @@ -0,0 +1,378 @@ +// Copyright (c) 2011-2012 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include + +#include + +#include "EventLoop.h" +#include "NamedPipe.h" +#include "../shared/DebugClient.h" +#include "../shared/StringUtil.h" +#include "../shared/WindowsSecurity.h" +#include "../shared/WinptyAssert.h" + +// Returns true if anything happens (data received, data sent, pipe error). +bool NamedPipe::serviceIo(std::vector *waitHandles) +{ + bool justConnected = false; + const auto kError = ServiceResult::Error; + const auto kProgress = ServiceResult::Progress; + const auto kNoProgress = ServiceResult::NoProgress; + if (m_handle == NULL) { + return false; + } + if (m_connectEvent.get() != nullptr) { + // We're still connecting this server pipe. Check whether the pipe is + // now connected. If it isn't, add the pipe to the list of handles to + // wait on. + DWORD actual = 0; + BOOL success = + GetOverlappedResult(m_handle, &m_connectOver, &actual, FALSE); + if (!success && GetLastError() == ERROR_PIPE_CONNECTED) { + // I'm not sure this can happen, but it's easy to handle if it + // does. + success = TRUE; + } + if (!success) { + ASSERT(GetLastError() == ERROR_IO_INCOMPLETE && + "Pended ConnectNamedPipe call failed"); + waitHandles->push_back(m_connectEvent.get()); + } else { + TRACE("Server pipe [%s] connected", + utf8FromWide(m_name).c_str()); + m_connectEvent.dispose(); + startPipeWorkers(); + justConnected = true; + } + } + const auto readProgress = m_inputWorker ? m_inputWorker->service() : kNoProgress; + const auto writeProgress = m_outputWorker ? m_outputWorker->service() : kNoProgress; + if (readProgress == kError || writeProgress == kError) { + closePipe(); + return true; + } + if (m_inputWorker && m_inputWorker->getWaitEvent() != nullptr) { + waitHandles->push_back(m_inputWorker->getWaitEvent()); + } + if (m_outputWorker && m_outputWorker->getWaitEvent() != nullptr) { + waitHandles->push_back(m_outputWorker->getWaitEvent()); + } + return justConnected + || readProgress == kProgress + || writeProgress == kProgress; +} + +// manual reset, initially unset +static OwnedHandle createEvent() { + HANDLE ret = CreateEventW(nullptr, TRUE, FALSE, nullptr); + ASSERT(ret != nullptr && "CreateEventW failed"); + return OwnedHandle(ret); +} + +NamedPipe::IoWorker::IoWorker(NamedPipe &namedPipe) : + m_namedPipe(namedPipe), + m_event(createEvent()) +{ +} + +NamedPipe::ServiceResult NamedPipe::IoWorker::service() +{ + ServiceResult progress = ServiceResult::NoProgress; + if (m_pending) { + DWORD actual = 0; + BOOL ret = GetOverlappedResult(m_namedPipe.m_handle, &m_over, &actual, FALSE); + if (!ret) { + if (GetLastError() == ERROR_IO_INCOMPLETE) { + // There is a pending I/O. + return progress; + } else { + // Pipe error. + return ServiceResult::Error; + } + } + ResetEvent(m_event.get()); + m_pending = false; + completeIo(actual); + m_currentIoSize = 0; + progress = ServiceResult::Progress; + } + DWORD nextSize = 0; + bool isRead = false; + while (shouldIssueIo(&nextSize, &isRead)) { + m_currentIoSize = nextSize; + DWORD actual = 0; + memset(&m_over, 0, sizeof(m_over)); + m_over.hEvent = m_event.get(); + BOOL ret = isRead + ? ReadFile(m_namedPipe.m_handle, m_buffer, nextSize, &actual, &m_over) + : WriteFile(m_namedPipe.m_handle, m_buffer, nextSize, &actual, &m_over); + if (!ret) { + if (GetLastError() == ERROR_IO_PENDING) { + // There is a pending I/O. + m_pending = true; + return progress; + } else { + // Pipe error. + return ServiceResult::Error; + } + } + ResetEvent(m_event.get()); + completeIo(actual); + m_currentIoSize = 0; + progress = ServiceResult::Progress; + } + return progress; +} + +// This function is called after CancelIo has returned. We need to block until +// the I/O operations have completed, which should happen very quickly. +// https://blogs.msdn.microsoft.com/oldnewthing/20110202-00/?p=11613 +void NamedPipe::IoWorker::waitForCanceledIo() +{ + if (m_pending) { + DWORD actual = 0; + GetOverlappedResult(m_namedPipe.m_handle, &m_over, &actual, TRUE); + m_pending = false; + } +} + +HANDLE NamedPipe::IoWorker::getWaitEvent() +{ + return m_pending ? m_event.get() : NULL; +} + +void NamedPipe::InputWorker::completeIo(DWORD size) +{ + m_namedPipe.m_inQueue.append(m_buffer, size); +} + +bool NamedPipe::InputWorker::shouldIssueIo(DWORD *size, bool *isRead) +{ + *isRead = true; + ASSERT(!m_namedPipe.isConnecting()); + if (m_namedPipe.isClosed()) { + return false; + } else if (m_namedPipe.m_inQueue.size() < m_namedPipe.readBufferSize()) { + *size = kIoSize; + return true; + } else { + return false; + } +} + +void NamedPipe::OutputWorker::completeIo(DWORD size) +{ + ASSERT(size == m_currentIoSize); +} + +bool NamedPipe::OutputWorker::shouldIssueIo(DWORD *size, bool *isRead) +{ + *isRead = false; + if (!m_namedPipe.m_outQueue.empty()) { + auto &out = m_namedPipe.m_outQueue; + const DWORD writeSize = std::min(out.size(), kIoSize); + std::copy(&out[0], &out[writeSize], m_buffer); + out.erase(0, writeSize); + *size = writeSize; + return true; + } else { + return false; + } +} + +DWORD NamedPipe::OutputWorker::getPendingIoSize() +{ + return m_pending ? m_currentIoSize : 0; +} + +void NamedPipe::openServerPipe(LPCWSTR pipeName, OpenMode::t openMode, + int outBufferSize, int inBufferSize) { + ASSERT(isClosed()); + ASSERT((openMode & OpenMode::Duplex) != 0); + const DWORD winOpenMode = + ((openMode & OpenMode::Reading) ? PIPE_ACCESS_INBOUND : 0) + | ((openMode & OpenMode::Writing) ? PIPE_ACCESS_OUTBOUND : 0) + | FILE_FLAG_FIRST_PIPE_INSTANCE + | FILE_FLAG_OVERLAPPED; + const auto sd = createPipeSecurityDescriptorOwnerFullControl(); + ASSERT(sd && "error creating data pipe SECURITY_DESCRIPTOR"); + SECURITY_ATTRIBUTES sa = {}; + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = sd.get(); + HANDLE handle = CreateNamedPipeW( + pipeName, + /*dwOpenMode=*/winOpenMode, + /*dwPipeMode=*/rejectRemoteClientsPipeFlag(), + /*nMaxInstances=*/1, + /*nOutBufferSize=*/outBufferSize, + /*nInBufferSize=*/inBufferSize, + /*nDefaultTimeOut=*/30000, + &sa); + TRACE("opened server pipe [%s], handle == %p", + utf8FromWide(pipeName).c_str(), handle); + ASSERT(handle != INVALID_HANDLE_VALUE && "Could not open server pipe"); + m_name = pipeName; + m_handle = handle; + m_openMode = openMode; + + // Start an asynchronous connection attempt. + m_connectEvent = createEvent(); + memset(&m_connectOver, 0, sizeof(m_connectOver)); + m_connectOver.hEvent = m_connectEvent.get(); + BOOL success = ConnectNamedPipe(m_handle, &m_connectOver); + const auto err = GetLastError(); + if (!success && err == ERROR_PIPE_CONNECTED) { + success = TRUE; + } + if (success) { + TRACE("Server pipe [%s] connected", utf8FromWide(pipeName).c_str()); + m_connectEvent.dispose(); + startPipeWorkers(); + } else if (err != ERROR_IO_PENDING) { + ASSERT(false && "ConnectNamedPipe call failed"); + } +} + +void NamedPipe::connectToServer(LPCWSTR pipeName, OpenMode::t openMode) +{ + ASSERT(isClosed()); + ASSERT((openMode & OpenMode::Duplex) != 0); + HANDLE handle = CreateFileW( + pipeName, + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION | FILE_FLAG_OVERLAPPED, + NULL); + TRACE("connected to [%s], handle == %p", + utf8FromWide(pipeName).c_str(), handle); + ASSERT(handle != INVALID_HANDLE_VALUE && "Could not connect to pipe"); + m_name = pipeName; + m_handle = handle; + m_openMode = openMode; + startPipeWorkers(); +} + +void NamedPipe::startPipeWorkers() +{ + if (m_openMode & OpenMode::Reading) { + m_inputWorker.reset(new InputWorker(*this)); + } + if (m_openMode & OpenMode::Writing) { + m_outputWorker.reset(new OutputWorker(*this)); + } +} + +size_t NamedPipe::bytesToSend() +{ + ASSERT(m_openMode & OpenMode::Writing); + auto ret = m_outQueue.size(); + if (m_outputWorker != NULL) { + ret += m_outputWorker->getPendingIoSize(); + } + return ret; +} + +void NamedPipe::write(const void *data, size_t size) +{ + ASSERT(m_openMode & OpenMode::Writing); + m_outQueue.append(reinterpret_cast(data), size); +} + +void NamedPipe::write(const char *text) +{ + write(text, strlen(text)); +} + +size_t NamedPipe::readBufferSize() +{ + ASSERT(m_openMode & OpenMode::Reading); + return m_readBufferSize; +} + +void NamedPipe::setReadBufferSize(size_t size) +{ + ASSERT(m_openMode & OpenMode::Reading); + m_readBufferSize = size; +} + +size_t NamedPipe::bytesAvailable() +{ + ASSERT(m_openMode & OpenMode::Reading); + return m_inQueue.size(); +} + +size_t NamedPipe::peek(void *data, size_t size) +{ + ASSERT(m_openMode & OpenMode::Reading); + const auto out = reinterpret_cast(data); + const size_t ret = std::min(size, m_inQueue.size()); + std::copy(&m_inQueue[0], &m_inQueue[ret], out); + return ret; +} + +size_t NamedPipe::read(void *data, size_t size) +{ + size_t ret = peek(data, size); + m_inQueue.erase(0, ret); + return ret; +} + +std::string NamedPipe::readToString(size_t size) +{ + ASSERT(m_openMode & OpenMode::Reading); + size_t retSize = std::min(size, m_inQueue.size()); + std::string ret = m_inQueue.substr(0, retSize); + m_inQueue.erase(0, retSize); + return ret; +} + +std::string NamedPipe::readAllToString() +{ + ASSERT(m_openMode & OpenMode::Reading); + std::string ret = m_inQueue; + m_inQueue.clear(); + return ret; +} + +void NamedPipe::closePipe() +{ + if (m_handle == NULL) { + return; + } + CancelIo(m_handle); + if (m_connectEvent.get() != nullptr) { + DWORD actual = 0; + GetOverlappedResult(m_handle, &m_connectOver, &actual, TRUE); + m_connectEvent.dispose(); + } + if (m_inputWorker) { + m_inputWorker->waitForCanceledIo(); + m_inputWorker.reset(); + } + if (m_outputWorker) { + m_outputWorker->waitForCanceledIo(); + m_outputWorker.reset(); + } + CloseHandle(m_handle); + m_handle = NULL; +} diff --git a/src/libs/3rdparty/winpty/src/agent/NamedPipe.h b/src/libs/3rdparty/winpty/src/agent/NamedPipe.h new file mode 100644 index 0000000000..0a4d8b0c75 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/NamedPipe.h @@ -0,0 +1,125 @@ +// Copyright (c) 2011-2012 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef NAMEDPIPE_H +#define NAMEDPIPE_H + +#include + +#include +#include +#include + +#include "../shared/OwnedHandle.h" + +class EventLoop; + +class NamedPipe +{ +private: + // The EventLoop uses these private members. + friend class EventLoop; + NamedPipe() {} + ~NamedPipe() { closePipe(); } + bool serviceIo(std::vector *waitHandles); + void startPipeWorkers(); + + enum class ServiceResult { NoProgress, Error, Progress }; + +private: + class IoWorker + { + public: + IoWorker(NamedPipe &namedPipe); + virtual ~IoWorker() {} + ServiceResult service(); + void waitForCanceledIo(); + HANDLE getWaitEvent(); + protected: + NamedPipe &m_namedPipe; + bool m_pending = false; + DWORD m_currentIoSize = 0; + OwnedHandle m_event; + OVERLAPPED m_over = {}; + enum { kIoSize = 64 * 1024 }; + char m_buffer[kIoSize]; + virtual void completeIo(DWORD size) = 0; + virtual bool shouldIssueIo(DWORD *size, bool *isRead) = 0; + }; + + class InputWorker : public IoWorker + { + public: + InputWorker(NamedPipe &namedPipe) : IoWorker(namedPipe) {} + protected: + virtual void completeIo(DWORD size) override; + virtual bool shouldIssueIo(DWORD *size, bool *isRead) override; + }; + + class OutputWorker : public IoWorker + { + public: + OutputWorker(NamedPipe &namedPipe) : IoWorker(namedPipe) {} + DWORD getPendingIoSize(); + protected: + virtual void completeIo(DWORD size) override; + virtual bool shouldIssueIo(DWORD *size, bool *isRead) override; + }; + +public: + struct OpenMode { + typedef int t; + enum { None = 0, Reading = 1, Writing = 2, Duplex = 3 }; + }; + + std::wstring name() const { return m_name; } + void openServerPipe(LPCWSTR pipeName, OpenMode::t openMode, + int outBufferSize, int inBufferSize); + void connectToServer(LPCWSTR pipeName, OpenMode::t openMode); + size_t bytesToSend(); + void write(const void *data, size_t size); + void write(const char *text); + size_t readBufferSize(); + void setReadBufferSize(size_t size); + size_t bytesAvailable(); + size_t peek(void *data, size_t size); + size_t read(void *data, size_t size); + std::string readToString(size_t size); + std::string readAllToString(); + void closePipe(); + bool isClosed() { return m_handle == nullptr; } + bool isConnected() { return !isClosed() && !isConnecting(); } + bool isConnecting() { return m_connectEvent.get() != nullptr; } + +private: + // Input/output buffers + std::wstring m_name; + OVERLAPPED m_connectOver = {}; + OwnedHandle m_connectEvent; + OpenMode::t m_openMode = OpenMode::None; + size_t m_readBufferSize = 64 * 1024; + std::string m_inQueue; + std::string m_outQueue; + HANDLE m_handle = nullptr; + std::unique_ptr m_inputWorker; + std::unique_ptr m_outputWorker; +}; + +#endif // NAMEDPIPE_H diff --git a/src/libs/3rdparty/winpty/src/agent/Scraper.cc b/src/libs/3rdparty/winpty/src/agent/Scraper.cc new file mode 100644 index 0000000000..21f9c67104 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/Scraper.cc @@ -0,0 +1,699 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "Scraper.h" + +#include + +#include + +#include +#include + +#include "../shared/WinptyAssert.h" +#include "../shared/winpty_snprintf.h" + +#include "ConsoleFont.h" +#include "Win32Console.h" +#include "Win32ConsoleBuffer.h" + +namespace { + +template +T constrained(T min, T val, T max) { + ASSERT(min <= max); + return std::min(std::max(min, val), max); +} + +} // anonymous namespace + +Scraper::Scraper( + Win32Console &console, + Win32ConsoleBuffer &buffer, + std::unique_ptr terminal, + Coord initialSize) : + m_console(console), + m_terminal(std::move(terminal)), + m_ptySize(initialSize) +{ + m_consoleBuffer = &buffer; + + resetConsoleTracking(Terminal::OmitClear, buffer.windowRect().top()); + + m_bufferData.resize(BUFFER_LINE_COUNT); + + // Setup the initial screen buffer and window size. + // + // Use SetConsoleWindowInfo to shrink the console window as much as + // possible -- to a 1x1 cell at the top-left. This call always succeeds. + // Prior to the new Windows 10 console, it also actually resizes the GUI + // window to 1x1 cell. Nevertheless, even though the GUI window can + // therefore be narrower than its minimum, calling + // SetConsoleScreenBufferSize with a 1x1 size still fails. + // + // While the small font intends to support large buffers, a user could + // still hit a limit imposed by their monitor width, so cap the new window + // size to GetLargestConsoleWindowSize(). + setSmallFont(buffer.conout(), initialSize.X, m_console.isNewW10()); + buffer.moveWindow(SmallRect(0, 0, 1, 1)); + buffer.resizeBufferRange(Coord(initialSize.X, BUFFER_LINE_COUNT)); + const auto largest = GetLargestConsoleWindowSize(buffer.conout()); + buffer.moveWindow(SmallRect( + 0, 0, + std::min(initialSize.X, largest.X), + std::min(initialSize.Y, largest.Y))); + buffer.setCursorPosition(Coord(0, 0)); + + // For the sake of the color translation heuristic, set the console color + // to LtGray-on-Black. + buffer.setTextAttribute(Win32ConsoleBuffer::kDefaultAttributes); + buffer.clearAllLines(m_consoleBuffer->bufferInfo()); + + m_consoleBuffer = nullptr; +} + +Scraper::~Scraper() +{ +} + +// Whether or not the agent is frozen on entry, it will be frozen on exit. +void Scraper::resizeWindow(Win32ConsoleBuffer &buffer, + Coord newSize, + ConsoleScreenBufferInfo &finalInfoOut) +{ + m_consoleBuffer = &buffer; + m_ptySize = newSize; + syncConsoleContentAndSize(true, finalInfoOut); + m_consoleBuffer = nullptr; +} + +// This function may freeze the agent, but it will not unfreeze it. +void Scraper::scrapeBuffer(Win32ConsoleBuffer &buffer, + ConsoleScreenBufferInfo &finalInfoOut) +{ + m_consoleBuffer = &buffer; + syncConsoleContentAndSize(false, finalInfoOut); + m_consoleBuffer = nullptr; +} + +void Scraper::resetConsoleTracking( + Terminal::SendClearFlag sendClear, int64_t scrapedLineCount) +{ + for (ConsoleLine &line : m_bufferData) { + line.reset(); + } + m_syncRow = -1; + m_scrapedLineCount = scrapedLineCount; + m_scrolledCount = 0; + m_maxBufferedLine = -1; + m_dirtyWindowTop = -1; + m_dirtyLineCount = 0; + m_terminal->reset(sendClear, m_scrapedLineCount); +} + +// Detect window movement. If the window moves down (presumably as a +// result of scrolling), then assume that all screen buffer lines down to +// the bottom of the window are dirty. +void Scraper::markEntireWindowDirty(const SmallRect &windowRect) +{ + m_dirtyLineCount = std::max(m_dirtyLineCount, + windowRect.top() + windowRect.height()); +} + +// Scan the screen buffer and advance the dirty line count when we find +// non-empty lines. +void Scraper::scanForDirtyLines(const SmallRect &windowRect) +{ + const int w = m_readBuffer.rect().width(); + ASSERT(m_dirtyLineCount >= 1); + const CHAR_INFO *const prevLine = + m_readBuffer.lineData(m_dirtyLineCount - 1); + WORD prevLineAttr = prevLine[w - 1].Attributes; + const int stopLine = windowRect.top() + windowRect.height(); + + for (int line = m_dirtyLineCount; line < stopLine; ++line) { + const CHAR_INFO *lineData = m_readBuffer.lineData(line); + for (int col = 0; col < w; ++col) { + const WORD colAttr = lineData[col].Attributes; + if (lineData[col].Char.UnicodeChar != L' ' || + colAttr != prevLineAttr) { + m_dirtyLineCount = line + 1; + break; + } + } + prevLineAttr = lineData[w - 1].Attributes; + } +} + +// Clear lines in the line buffer. The `firstRow` parameter is in +// screen-buffer coordinates. +void Scraper::clearBufferLines( + const int firstRow, + const int count) +{ + ASSERT(!m_directMode); + for (int row = firstRow; row < firstRow + count; ++row) { + const int64_t bufLine = row + m_scrolledCount; + m_maxBufferedLine = std::max(m_maxBufferedLine, bufLine); + m_bufferData[bufLine % BUFFER_LINE_COUNT].blank( + Win32ConsoleBuffer::kDefaultAttributes); + } +} + +static bool cursorInWindow(const ConsoleScreenBufferInfo &info) +{ + return info.dwCursorPosition.Y >= info.srWindow.Top && + info.dwCursorPosition.Y <= info.srWindow.Bottom; +} + +void Scraper::resizeImpl(const ConsoleScreenBufferInfo &origInfo) +{ + ASSERT(m_console.frozen()); + const int cols = m_ptySize.X; + const int rows = m_ptySize.Y; + Coord finalBufferSize; + + { + // + // To accommodate Windows 10, erase all lines up to the top of the + // visible window. It's hard to tell whether this is strictly + // necessary. It ensures that the sync marker won't move downward, + // and it ensures that we won't repeat lines that have already scrolled + // up into the scrollback. + // + // It *is* possible for these blank lines to reappear in the visible + // window (e.g. if the window is made taller), but because we blanked + // the lines in the line buffer, we still don't output them again. + // + const Coord origBufferSize = origInfo.bufferSize(); + const SmallRect origWindowRect = origInfo.windowRect(); + + if (m_directMode) { + for (ConsoleLine &line : m_bufferData) { + line.reset(); + } + } else { + m_consoleBuffer->clearLines(0, origWindowRect.Top, origInfo); + clearBufferLines(0, origWindowRect.Top); + if (m_syncRow != -1) { + createSyncMarker(std::min( + m_syncRow, + BUFFER_LINE_COUNT - rows + - SYNC_MARKER_LEN + - SYNC_MARKER_MARGIN)); + } + } + + finalBufferSize = Coord( + cols, + // If there was previously no scrollback (e.g. a full-screen app + // in direct mode) and we're reducing the window height, then + // reduce the console buffer's height too. + (origWindowRect.height() == origBufferSize.Y) + ? rows + : std::max(rows, origBufferSize.Y)); + + // Reset the console font size. We need to do this before shrinking + // the window, because we might need to make the font bigger to permit + // a smaller window width. Making the font smaller could expand the + // screen buffer, which would hang the conhost process in the + // Windows 10 (10240 build) if the console selection is in progress, so + // unfreeze it first. + m_console.setFrozen(false); + setSmallFont(m_consoleBuffer->conout(), cols, m_console.isNewW10()); + } + + // We try to make the font small enough so that the entire screen buffer + // fits on the monitor, but it can't be guaranteed. + const auto largest = + GetLargestConsoleWindowSize(m_consoleBuffer->conout()); + const short visibleCols = std::min(cols, largest.X); + const short visibleRows = std::min(rows, largest.Y); + + { + // Make the window small enough. We want the console frozen during + // this step so we don't accidentally move the window above the cursor. + m_console.setFrozen(true); + const auto info = m_consoleBuffer->bufferInfo(); + const auto &bufferSize = info.dwSize; + const int tmpWindowWidth = std::min(bufferSize.X, visibleCols); + const int tmpWindowHeight = std::min(bufferSize.Y, visibleRows); + SmallRect tmpWindowRect( + 0, + std::min(bufferSize.Y - tmpWindowHeight, + info.windowRect().Top), + tmpWindowWidth, + tmpWindowHeight); + if (cursorInWindow(info)) { + tmpWindowRect = tmpWindowRect.ensureLineIncluded( + info.cursorPosition().Y); + } + m_consoleBuffer->moveWindow(tmpWindowRect); + } + + { + // Resize the buffer to the final desired size. + m_console.setFrozen(false); + m_consoleBuffer->resizeBufferRange(finalBufferSize); + } + + { + // Expand the window to its full size. + m_console.setFrozen(true); + const ConsoleScreenBufferInfo info = m_consoleBuffer->bufferInfo(); + + SmallRect finalWindowRect( + 0, + std::min(info.bufferSize().Y - visibleRows, + info.windowRect().Top), + visibleCols, + visibleRows); + + // + // Once a line in the screen buffer is "dirty", it should stay visible + // in the console window, so that we continue to update its content in + // the terminal. This code is particularly (only?) necessary on + // Windows 10, where making the buffer wider can rewrap lines and move + // the console window upward. + // + if (!m_directMode && m_dirtyLineCount > finalWindowRect.Bottom + 1) { + // In theory, we avoid ensureLineIncluded, because, a massive + // amount of output could have occurred while the console was + // unfrozen, so that the *top* of the window is now below the + // dirtiest tracked line. + finalWindowRect = SmallRect( + 0, m_dirtyLineCount - visibleRows, + visibleCols, visibleRows); + } + + // Highest priority constraint: ensure that the cursor remains visible. + if (cursorInWindow(info)) { + finalWindowRect = finalWindowRect.ensureLineIncluded( + info.cursorPosition().Y); + } + + m_consoleBuffer->moveWindow(finalWindowRect); + m_dirtyWindowTop = finalWindowRect.Top; + } + + ASSERT(m_console.frozen()); +} + +void Scraper::syncConsoleContentAndSize( + bool forceResize, + ConsoleScreenBufferInfo &finalInfoOut) +{ + // We'll try to avoid freezing the console by reading large chunks (or + // all!) of the screen buffer without otherwise attempting to synchronize + // with the console application. We can only do this on Windows 10 and up + // because: + // - Prior to Windows 8, the size of a ReadConsoleOutputW call was limited + // by the ~32KB RPC buffer. + // - Prior to Windows 10, an out-of-range read region crashes the caller. + // (See misc/WindowsBugCrashReader.cc.) + // + if (!m_console.isNewW10() || forceResize) { + m_console.setFrozen(true); + } + + const ConsoleScreenBufferInfo info = m_consoleBuffer->bufferInfo(); + bool cursorVisible = true; + CONSOLE_CURSOR_INFO cursorInfo = {}; + if (!GetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursorInfo)) { + trace("GetConsoleCursorInfo failed"); + } else { + cursorVisible = cursorInfo.bVisible != 0; + } + + // If an app resizes the buffer height, then we enter "direct mode", where + // we stop trying to track incremental console changes. + const bool newDirectMode = (info.bufferSize().Y != BUFFER_LINE_COUNT); + if (newDirectMode != m_directMode) { + trace("Entering %s mode", newDirectMode ? "direct" : "scrolling"); + resetConsoleTracking(Terminal::SendClear, + newDirectMode ? 0 : info.windowRect().top()); + m_directMode = newDirectMode; + + // When we switch from direct->scrolling mode, make sure the console is + // the right size. + if (!m_directMode) { + m_console.setFrozen(true); + forceResize = true; + } + } + + if (m_directMode) { + // In direct-mode, resizing the console redraws the terminal, so do it + // before scraping. + if (forceResize) { + resizeImpl(info); + } + directScrapeOutput(info, cursorVisible); + } else { + if (!m_console.frozen()) { + if (!scrollingScrapeOutput(info, cursorVisible, true)) { + m_console.setFrozen(true); + } + } + if (m_console.frozen()) { + scrollingScrapeOutput(info, cursorVisible, false); + } + // In scrolling mode, we want to scrape before resizing, because we'll + // erase everything in the console buffer up to the top of the console + // window. + if (forceResize) { + resizeImpl(info); + } + } + + finalInfoOut = forceResize ? m_consoleBuffer->bufferInfo() : info; +} + +// Try to match Windows' behavior w.r.t. to the LVB attribute flags. In some +// situations, Windows ignores the LVB flags on a character cell because of +// backwards compatibility -- apparently some programs set the flags without +// intending to enable reverse-video or underscores. +// +// [rprichard 2017-01-15] I haven't actually noticed any old programs that need +// this treatment -- the motivation for this function comes from the MSDN +// documentation for SetConsoleMode and ENABLE_LVB_GRID_WORLDWIDE. +WORD Scraper::attributesMask() +{ + const auto WINPTY_ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4u; + const auto WINPTY_ENABLE_LVB_GRID_WORLDWIDE = 0x10u; + const auto WINPTY_COMMON_LVB_REVERSE_VIDEO = 0x4000u; + const auto WINPTY_COMMON_LVB_UNDERSCORE = 0x8000u; + + const auto cp = GetConsoleOutputCP(); + const auto isCjk = (cp == 932 || cp == 936 || cp == 949 || cp == 950); + + const DWORD outputMode = [this]{ + ASSERT(this->m_consoleBuffer != nullptr); + DWORD mode = 0; + if (!GetConsoleMode(this->m_consoleBuffer->conout(), &mode)) { + mode = 0; + } + return mode; + }(); + const bool hasEnableLvbGridWorldwide = + (outputMode & WINPTY_ENABLE_LVB_GRID_WORLDWIDE) != 0; + const bool hasEnableVtProcessing = + (outputMode & WINPTY_ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0; + + // The new Windows 10 console (as of 14393) seems to respect + // COMMON_LVB_REVERSE_VIDEO even in CP437 w/o the other enabling modes, so + // try to match that behavior. + const auto isReverseSupported = + isCjk || hasEnableLvbGridWorldwide || hasEnableVtProcessing || m_console.isNewW10(); + const auto isUnderscoreSupported = + isCjk || hasEnableLvbGridWorldwide || hasEnableVtProcessing; + + WORD mask = ~0; + if (!isReverseSupported) { mask &= ~WINPTY_COMMON_LVB_REVERSE_VIDEO; } + if (!isUnderscoreSupported) { mask &= ~WINPTY_COMMON_LVB_UNDERSCORE; } + return mask; +} + +void Scraper::directScrapeOutput(const ConsoleScreenBufferInfo &info, + bool consoleCursorVisible) +{ + const SmallRect windowRect = info.windowRect(); + + const SmallRect scrapeRect( + windowRect.left(), windowRect.top(), + std::min(std::min(windowRect.width(), m_ptySize.X), + MAX_CONSOLE_WIDTH), + std::min(std::min(windowRect.height(), m_ptySize.Y), + BUFFER_LINE_COUNT)); + const int w = scrapeRect.width(); + const int h = scrapeRect.height(); + + const Coord cursor = info.cursorPosition(); + const bool showTerminalCursor = + consoleCursorVisible && scrapeRect.contains(cursor); + const int cursorColumn = !showTerminalCursor ? -1 : cursor.X - scrapeRect.Left; + const int cursorLine = !showTerminalCursor ? -1 : cursor.Y - scrapeRect.Top; + + if (!showTerminalCursor) { + m_terminal->hideTerminalCursor(); + } + + largeConsoleRead(m_readBuffer, *m_consoleBuffer, scrapeRect, attributesMask()); + + for (int line = 0; line < h; ++line) { + const CHAR_INFO *const curLine = + m_readBuffer.lineData(scrapeRect.top() + line); + ConsoleLine &bufLine = m_bufferData[line]; + if (bufLine.detectChangeAndSetLine(curLine, w)) { + const int lineCursorColumn = + line == cursorLine ? cursorColumn : -1; + m_terminal->sendLine(line, curLine, w, lineCursorColumn); + } + } + + if (showTerminalCursor) { + m_terminal->showTerminalCursor(cursorColumn, cursorLine); + } +} + +bool Scraper::scrollingScrapeOutput(const ConsoleScreenBufferInfo &info, + bool consoleCursorVisible, + bool tentative) +{ + const Coord cursor = info.cursorPosition(); + const SmallRect windowRect = info.windowRect(); + + if (m_syncRow != -1) { + // If a synchronizing marker was placed into the history, look for it + // and adjust the scroll count. + const int markerRow = findSyncMarker(); + if (markerRow == -1) { + if (tentative) { + // I *think* it's possible to keep going, but it's simple to + // bail out. + return false; + } + // Something has happened. Reset the terminal. + trace("Sync marker has disappeared -- resetting the terminal" + " (m_syncCounter=%u)", + m_syncCounter); + resetConsoleTracking(Terminal::SendClear, windowRect.top()); + } else if (markerRow != m_syncRow) { + ASSERT(markerRow < m_syncRow); + m_scrolledCount += (m_syncRow - markerRow); + m_syncRow = markerRow; + // If the buffer has scrolled, then the entire window is dirty. + markEntireWindowDirty(windowRect); + } + } + + // Creating a new sync row requires clearing part of the console buffer, so + // avoid doing it if there's already a sync row that's good enough. + const int newSyncRow = + static_cast(windowRect.top()) - SYNC_MARKER_LEN - SYNC_MARKER_MARGIN; + const bool shouldCreateSyncRow = + newSyncRow >= m_syncRow + SYNC_MARKER_LEN + SYNC_MARKER_MARGIN; + if (tentative && shouldCreateSyncRow) { + // It's difficult even in principle to put down a new marker if the + // console can scroll an arbitrarily amount while we're writing. + return false; + } + + // Update the dirty line count: + // - If the window has moved, the entire window is dirty. + // - Everything up to the cursor is dirty. + // - All lines above the window are dirty. + // - Any non-blank lines are dirty. + if (m_dirtyWindowTop != -1) { + if (windowRect.top() > m_dirtyWindowTop) { + // The window has moved down, presumably as a result of scrolling. + markEntireWindowDirty(windowRect); + } else if (windowRect.top() < m_dirtyWindowTop) { + if (tentative) { + // I *think* it's possible to keep going, but it's simple to + // bail out. + return false; + } + // The window has moved upward. This is generally not expected to + // happen, but the CMD/PowerShell CLS command will move the window + // to the top as part of clearing everything else in the console. + trace("Window moved upward -- resetting the terminal" + " (m_syncCounter=%u)", + m_syncCounter); + resetConsoleTracking(Terminal::SendClear, windowRect.top()); + } + } + m_dirtyWindowTop = windowRect.top(); + m_dirtyLineCount = std::max(m_dirtyLineCount, cursor.Y + 1); + m_dirtyLineCount = std::max(m_dirtyLineCount, (int)windowRect.top()); + + // There will be at least one dirty line, because there is a cursor. + ASSERT(m_dirtyLineCount >= 1); + + // The first line to scrape, in virtual line coordinates. + const int64_t firstVirtLine = std::min(m_scrapedLineCount, + windowRect.top() + m_scrolledCount); + + // Read all the data we will need from the console. Start reading with the + // first line to scrape, but adjust the the read area upward to account for + // scanForDirtyLines' need to read the previous attribute. Read to the + // bottom of the window. (It's not clear to me whether the + // m_dirtyLineCount adjustment here is strictly necessary. It isn't + // necessary so long as the cursor is inside the current window.) + const int firstReadLine = std::min(firstVirtLine - m_scrolledCount, + m_dirtyLineCount - 1); + const int stopReadLine = std::max(windowRect.top() + windowRect.height(), + m_dirtyLineCount); + ASSERT(firstReadLine >= 0 && stopReadLine > firstReadLine); + largeConsoleRead(m_readBuffer, + *m_consoleBuffer, + SmallRect(0, firstReadLine, + std::min(info.bufferSize().X, + MAX_CONSOLE_WIDTH), + stopReadLine - firstReadLine), + attributesMask()); + + // If we're scraping the buffer without freezing it, we have to query the + // buffer position data separately from the buffer content, so the two + // could easily be out-of-sync. If they *are* out-of-sync, abort the + // scrape operation and restart it frozen. (We may have updated the + // dirty-line high-water-mark, but that should be OK.) + if (tentative) { + const auto infoCheck = m_consoleBuffer->bufferInfo(); + if (info.bufferSize() != infoCheck.bufferSize() || + info.windowRect() != infoCheck.windowRect() || + info.cursorPosition() != infoCheck.cursorPosition()) { + return false; + } + if (m_syncRow != -1 && m_syncRow != findSyncMarker()) { + return false; + } + } + + if (shouldCreateSyncRow) { + ASSERT(!tentative); + createSyncMarker(newSyncRow); + } + + // At this point, we're finished interacting (reading or writing) the + // console, and we just need to convert our collected data into terminal + // output. + + scanForDirtyLines(windowRect); + + // Note that it's possible for all the lines on the current window to + // be non-dirty. + + // The line to stop scraping at, in virtual line coordinates. + const int64_t stopVirtLine = + std::min(m_dirtyLineCount, windowRect.top() + windowRect.height()) + + m_scrolledCount; + + const bool showTerminalCursor = + consoleCursorVisible && windowRect.contains(cursor); + const int64_t cursorLine = !showTerminalCursor ? -1 : cursor.Y + m_scrolledCount; + const int cursorColumn = !showTerminalCursor ? -1 : cursor.X; + + if (!showTerminalCursor) { + m_terminal->hideTerminalCursor(); + } + + bool sawModifiedLine = false; + + const int w = m_readBuffer.rect().width(); + for (int64_t line = firstVirtLine; line < stopVirtLine; ++line) { + const CHAR_INFO *curLine = + m_readBuffer.lineData(line - m_scrolledCount); + ConsoleLine &bufLine = m_bufferData[line % BUFFER_LINE_COUNT]; + if (line > m_maxBufferedLine) { + m_maxBufferedLine = line; + sawModifiedLine = true; + } + if (sawModifiedLine) { + bufLine.setLine(curLine, w); + } else { + sawModifiedLine = bufLine.detectChangeAndSetLine(curLine, w); + } + if (sawModifiedLine) { + const int lineCursorColumn = + line == cursorLine ? cursorColumn : -1; + m_terminal->sendLine(line, curLine, w, lineCursorColumn); + } + } + + m_scrapedLineCount = windowRect.top() + m_scrolledCount; + + if (showTerminalCursor) { + m_terminal->showTerminalCursor(cursorColumn, cursorLine); + } + + return true; +} + +void Scraper::syncMarkerText(CHAR_INFO (&output)[SYNC_MARKER_LEN]) +{ + // XXX: The marker text generated here could easily collide with ordinary + // console output. Does it make sense to try to avoid the collision? + char str[SYNC_MARKER_LEN + 1]; + winpty_snprintf(str, "S*Y*N*C*%08x", m_syncCounter); + for (int i = 0; i < SYNC_MARKER_LEN; ++i) { + output[i].Char.UnicodeChar = str[i]; + output[i].Attributes = 7; + } +} + +int Scraper::findSyncMarker() +{ + ASSERT(m_syncRow >= 0); + CHAR_INFO marker[SYNC_MARKER_LEN]; + CHAR_INFO column[BUFFER_LINE_COUNT]; + syncMarkerText(marker); + SmallRect rect(0, 0, 1, m_syncRow + SYNC_MARKER_LEN); + m_consoleBuffer->read(rect, column); + int i; + for (i = m_syncRow; i >= 0; --i) { + int j; + for (j = 0; j < SYNC_MARKER_LEN; ++j) { + if (column[i + j].Char.UnicodeChar != marker[j].Char.UnicodeChar) + break; + } + if (j == SYNC_MARKER_LEN) + return i; + } + return -1; +} + +void Scraper::createSyncMarker(int row) +{ + ASSERT(row >= 1); + + // Clear the lines around the marker to ensure that Windows 10's rewrapping + // does not affect the marker. + m_consoleBuffer->clearLines(row - 1, SYNC_MARKER_LEN + 1, + m_consoleBuffer->bufferInfo()); + + // Write a new marker. + m_syncCounter++; + CHAR_INFO marker[SYNC_MARKER_LEN]; + syncMarkerText(marker); + m_syncRow = row; + SmallRect markerRect(0, m_syncRow, 1, SYNC_MARKER_LEN); + m_consoleBuffer->write(markerRect, marker); +} diff --git a/src/libs/3rdparty/winpty/src/agent/Scraper.h b/src/libs/3rdparty/winpty/src/agent/Scraper.h new file mode 100644 index 0000000000..9c10d80aed --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/Scraper.h @@ -0,0 +1,103 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef AGENT_SCRAPER_H +#define AGENT_SCRAPER_H + +#include + +#include + +#include +#include + +#include "ConsoleLine.h" +#include "Coord.h" +#include "LargeConsoleRead.h" +#include "SmallRect.h" +#include "Terminal.h" + +class ConsoleScreenBufferInfo; +class Win32Console; +class Win32ConsoleBuffer; + +// We must be able to issue a single ReadConsoleOutputW call of +// MAX_CONSOLE_WIDTH characters, and a single read of approximately several +// hundred fewer characters than BUFFER_LINE_COUNT. +const int BUFFER_LINE_COUNT = 3000; +const int MAX_CONSOLE_WIDTH = 2500; +const int MAX_CONSOLE_HEIGHT = 2000; +const int SYNC_MARKER_LEN = 16; +const int SYNC_MARKER_MARGIN = 200; + +class Scraper { +public: + Scraper( + Win32Console &console, + Win32ConsoleBuffer &buffer, + std::unique_ptr terminal, + Coord initialSize); + ~Scraper(); + void resizeWindow(Win32ConsoleBuffer &buffer, + Coord newSize, + ConsoleScreenBufferInfo &finalInfoOut); + void scrapeBuffer(Win32ConsoleBuffer &buffer, + ConsoleScreenBufferInfo &finalInfoOut); + Terminal &terminal() { return *m_terminal; } + +private: + void resetConsoleTracking( + Terminal::SendClearFlag sendClear, int64_t scrapedLineCount); + void markEntireWindowDirty(const SmallRect &windowRect); + void scanForDirtyLines(const SmallRect &windowRect); + void clearBufferLines(int firstRow, int count); + void resizeImpl(const ConsoleScreenBufferInfo &origInfo); + void syncConsoleContentAndSize(bool forceResize, + ConsoleScreenBufferInfo &finalInfoOut); + WORD attributesMask(); + void directScrapeOutput(const ConsoleScreenBufferInfo &info, + bool consoleCursorVisible); + bool scrollingScrapeOutput(const ConsoleScreenBufferInfo &info, + bool consoleCursorVisible, + bool tentative); + void syncMarkerText(CHAR_INFO (&output)[SYNC_MARKER_LEN]); + int findSyncMarker(); + void createSyncMarker(int row); + +private: + Win32Console &m_console; + Win32ConsoleBuffer *m_consoleBuffer = nullptr; + std::unique_ptr m_terminal; + + int m_syncRow = -1; + unsigned int m_syncCounter = 0; + + bool m_directMode = false; + Coord m_ptySize; + int64_t m_scrapedLineCount = 0; + int64_t m_scrolledCount = 0; + int64_t m_maxBufferedLine = -1; + LargeConsoleReadBuffer m_readBuffer; + std::vector m_bufferData; + int m_dirtyWindowTop = -1; + int m_dirtyLineCount = 0; +}; + +#endif // AGENT_SCRAPER_H diff --git a/src/libs/3rdparty/winpty/src/agent/SimplePool.h b/src/libs/3rdparty/winpty/src/agent/SimplePool.h new file mode 100644 index 0000000000..41ff94a90d --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/SimplePool.h @@ -0,0 +1,75 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef SIMPLE_POOL_H +#define SIMPLE_POOL_H + +#include + +#include + +#include "../shared/WinptyAssert.h" + +template +class SimplePool { +public: + ~SimplePool(); + T *alloc(); + void clear(); +private: + struct Chunk { + size_t count; + T *data; + }; + std::vector m_chunks; +}; + +template +SimplePool::~SimplePool() { + clear(); +} + +template +void SimplePool::clear() { + for (size_t ci = 0; ci < m_chunks.size(); ++ci) { + Chunk &chunk = m_chunks[ci]; + for (size_t ti = 0; ti < chunk.count; ++ti) { + chunk.data[ti].~T(); + } + free(chunk.data); + } + m_chunks.clear(); +} + +template +T *SimplePool::alloc() { + if (m_chunks.empty() || m_chunks.back().count == chunkSize) { + T *newData = reinterpret_cast(malloc(sizeof(T) * chunkSize)); + ASSERT(newData != NULL); + Chunk newChunk = { 0, newData }; + m_chunks.push_back(newChunk); + } + Chunk &chunk = m_chunks.back(); + T *ret = &chunk.data[chunk.count++]; + new (ret) T(); + return ret; +} + +#endif // SIMPLE_POOL_H diff --git a/src/libs/3rdparty/winpty/src/agent/SmallRect.h b/src/libs/3rdparty/winpty/src/agent/SmallRect.h new file mode 100644 index 0000000000..bad0b88683 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/SmallRect.h @@ -0,0 +1,143 @@ +// Copyright (c) 2011-2012 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef SMALLRECT_H +#define SMALLRECT_H + +#include + +#include +#include + +#include "../shared/winpty_snprintf.h" +#include "Coord.h" + +struct SmallRect : SMALL_RECT +{ + SmallRect() + { + Left = Right = Top = Bottom = 0; + } + + SmallRect(SHORT x, SHORT y, SHORT width, SHORT height) + { + Left = x; + Top = y; + Right = x + width - 1; + Bottom = y + height - 1; + } + + SmallRect(const COORD &topLeft, const COORD &size) + { + Left = topLeft.X; + Top = topLeft.Y; + Right = Left + size.X - 1; + Bottom = Top + size.Y - 1; + } + + SmallRect(const SMALL_RECT &other) + { + *(SMALL_RECT*)this = other; + } + + SmallRect(const SmallRect &other) + { + *(SMALL_RECT*)this = *(const SMALL_RECT*)&other; + } + + SmallRect &operator=(const SmallRect &other) + { + *(SMALL_RECT*)this = *(const SMALL_RECT*)&other; + return *this; + } + + bool contains(const SmallRect &other) const + { + return other.Left >= Left && + other.Right <= Right && + other.Top >= Top && + other.Bottom <= Bottom; + } + + bool contains(const Coord &other) const + { + return other.X >= Left && + other.X <= Right && + other.Y >= Top && + other.Y <= Bottom; + } + + SmallRect intersected(const SmallRect &other) const + { + int x1 = std::max(Left, other.Left); + int x2 = std::min(Right, other.Right); + int y1 = std::max(Top, other.Top); + int y2 = std::min(Bottom, other.Bottom); + return SmallRect(x1, + y1, + std::max(0, x2 - x1 + 1), + std::max(0, y2 - y1 + 1)); + } + + SmallRect ensureLineIncluded(SHORT line) const + { + const SHORT h = height(); + if (line < Top) { + return SmallRect(Left, line, width(), h); + } else if (line > Bottom) { + return SmallRect(Left, line - h + 1, width(), h); + } else { + return *this; + } + } + + SHORT top() const { return Top; } + SHORT left() const { return Left; } + SHORT width() const { return Right - Left + 1; } + SHORT height() const { return Bottom - Top + 1; } + void setTop(SHORT top) { Top = top; } + void setLeft(SHORT left) { Left = left; } + void setWidth(SHORT width) { Right = Left + width - 1; } + void setHeight(SHORT height) { Bottom = Top + height - 1; } + Coord size() const { return Coord(width(), height()); } + + bool operator==(const SmallRect &other) const + { + return Left == other.Left && + Right == other.Right && + Top == other.Top && + Bottom == other.Bottom; + } + + bool operator!=(const SmallRect &other) const + { + return !(*this == other); + } + + std::string toString() const + { + char ret[64]; + winpty_snprintf(ret, "(x=%d,y=%d,w=%d,h=%d)", + Left, Top, width(), height()); + return std::string(ret); + } +}; + +#endif // SMALLRECT_H diff --git a/src/libs/3rdparty/winpty/src/agent/Terminal.cc b/src/libs/3rdparty/winpty/src/agent/Terminal.cc new file mode 100644 index 0000000000..afa0a36260 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/Terminal.cc @@ -0,0 +1,535 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "Terminal.h" + +#include +#include +#include + +#include + +#include "NamedPipe.h" +#include "UnicodeEncoding.h" +#include "../shared/DebugClient.h" +#include "../shared/WinptyAssert.h" +#include "../shared/winpty_snprintf.h" + +#define CSI "\x1b[" + +// Work around the old MinGW, which lacks COMMON_LVB_LEADING_BYTE and +// COMMON_LVB_TRAILING_BYTE. +const int WINPTY_COMMON_LVB_LEADING_BYTE = 0x100; +const int WINPTY_COMMON_LVB_TRAILING_BYTE = 0x200; +const int WINPTY_COMMON_LVB_REVERSE_VIDEO = 0x4000; +const int WINPTY_COMMON_LVB_UNDERSCORE = 0x8000; + +const int COLOR_ATTRIBUTE_MASK = + FOREGROUND_BLUE | + FOREGROUND_GREEN | + FOREGROUND_RED | + FOREGROUND_INTENSITY | + BACKGROUND_BLUE | + BACKGROUND_GREEN | + BACKGROUND_RED | + BACKGROUND_INTENSITY | + WINPTY_COMMON_LVB_REVERSE_VIDEO | + WINPTY_COMMON_LVB_UNDERSCORE; + +const int FLAG_RED = 1; +const int FLAG_GREEN = 2; +const int FLAG_BLUE = 4; +const int FLAG_BRIGHT = 8; + +const int BLACK = 0; +const int DKGRAY = BLACK | FLAG_BRIGHT; +const int LTGRAY = FLAG_RED | FLAG_GREEN | FLAG_BLUE; +const int WHITE = LTGRAY | FLAG_BRIGHT; + +// SGR parameters (Select Graphic Rendition) +const int SGR_FORE = 30; +const int SGR_FORE_HI = 90; +const int SGR_BACK = 40; +const int SGR_BACK_HI = 100; + +namespace { + +static void outUInt(std::string &out, unsigned int n) +{ + char buf[32]; + char *pbuf = &buf[32]; + *(--pbuf) = '\0'; + do { + *(--pbuf) = '0' + n % 10; + n /= 10; + } while (n != 0); + out.append(pbuf); +} + +static void outputSetColorSgrParams(std::string &out, bool isFore, int color) +{ + out.push_back(';'); + const int sgrBase = isFore ? SGR_FORE : SGR_BACK; + if (color & FLAG_BRIGHT) { + // Some terminals don't support the 9X/10X "intensive" color parameters + // (e.g. the Eclipse TM terminal as of this writing). Those terminals + // will quietly ignore a 9X/10X code, and the other terminals will + // ignore a 3X/4X code if it's followed by a 9X/10X code. Therefore, + // output a 3X/4X code as a fallback, then override it. + const int colorBase = color & ~FLAG_BRIGHT; + outUInt(out, sgrBase + colorBase); + out.push_back(';'); + outUInt(out, sgrBase + (SGR_FORE_HI - SGR_FORE) + colorBase); + } else { + outUInt(out, sgrBase + color); + } +} + +static void outputSetColor(std::string &out, int color) +{ + int fore = 0; + int back = 0; + if (color & FOREGROUND_RED) fore |= FLAG_RED; + if (color & FOREGROUND_GREEN) fore |= FLAG_GREEN; + if (color & FOREGROUND_BLUE) fore |= FLAG_BLUE; + if (color & FOREGROUND_INTENSITY) fore |= FLAG_BRIGHT; + if (color & BACKGROUND_RED) back |= FLAG_RED; + if (color & BACKGROUND_GREEN) back |= FLAG_GREEN; + if (color & BACKGROUND_BLUE) back |= FLAG_BLUE; + if (color & BACKGROUND_INTENSITY) back |= FLAG_BRIGHT; + + if (color & WINPTY_COMMON_LVB_REVERSE_VIDEO) { + // n.b.: The COMMON_LVB_REVERSE_VIDEO flag also swaps + // FOREGROUND_INTENSITY and BACKGROUND_INTENSITY. Tested on + // Windows 10 v14393. + std::swap(fore, back); + } + + // Translate the fore/back colors into terminal escape codes using + // a heuristic that works OK with common white-on-black or + // black-on-white color schemes. We don't know which color scheme + // the terminal is using. It is ugly to force white-on-black text + // on a black-on-white terminal, and it's even ugly to force the + // matching scheme. It's probably relevant that the default + // fore/back terminal colors frequently do not match any of the 16 + // palette colors. + + // Typical default terminal color schemes (according to palette, + // when possible): + // - mintty: LtGray-on-Black(A) + // - putty: LtGray-on-Black(A) + // - xterm: LtGray-on-Black(A) + // - Konsole: LtGray-on-Black(A) + // - JediTerm/JetBrains: Black-on-White(B) + // - rxvt: Black-on-White(B) + + // If the background is the default color (black), then it will + // map to Black(A) or White(B). If we translate White to White, + // then a Black background and a White background in the console + // are both White with (B). Therefore, we should translate White + // using SGR 7 (Invert). The typical finished mapping table for + // background grayscale colors is: + // + // (A) White => LtGray(fore) + // (A) Black => Black(back) + // (A) LtGray => LtGray + // (A) DkGray => DkGray + // + // (B) White => Black(fore) + // (B) Black => White(back) + // (B) LtGray => LtGray + // (B) DkGray => DkGray + // + + out.append(CSI "0"); + if (back == BLACK) { + if (fore == LTGRAY) { + // The "default" foreground color. Use the terminal's + // default colors. + } else if (fore == WHITE) { + // Sending the literal color white would behave poorly if + // the terminal were black-on-white. Sending Bold is not + // guaranteed to alter the color, but it will make the text + // visually distinct, so do that instead. + out.append(";1"); + } else if (fore == DKGRAY) { + // Set the foreground color to DkGray(90) with a fallback + // of LtGray(37) for terminals that don't handle the 9X SGR + // parameters (e.g. Eclipse's TM Terminal as of this + // writing). + out.append(";37;90"); + } else { + outputSetColorSgrParams(out, true, fore); + } + } else if (back == WHITE) { + // Set the background color using Invert on the default + // foreground color, and set the foreground color by setting a + // background color. + + // Use the terminal's inverted colors. + out.append(";7"); + if (fore == LTGRAY || fore == BLACK) { + // We're likely mapping Console White to terminal LtGray or + // Black. If they are the Console foreground color, then + // don't set a terminal foreground color to avoid creating + // invisible text. + } else { + outputSetColorSgrParams(out, false, fore); + } + } else { + // Set the foreground and background to match exactly that in + // the Windows console. + outputSetColorSgrParams(out, true, fore); + outputSetColorSgrParams(out, false, back); + } + if (fore == back) { + // The foreground and background colors are exactly equal, so + // attempt to hide the text using the Conceal SGR parameter, + // which some terminals support. + out.append(";8"); + } + if (color & WINPTY_COMMON_LVB_UNDERSCORE) { + out.append(";4"); + } + out.push_back('m'); +} + +static inline unsigned int fixSpecialCharacters(unsigned int ch) +{ + if (ch <= 0x1b) { + switch (ch) { + // The Windows Console has a popup window (e.g. that appears with + // F7) that is sometimes bordered with box-drawing characters. + // With the Japanese and Korean system locales (CP932 and CP949), + // the UnicodeChar values for the box-drawing characters are 1 + // through 6. Detect this and map the values to the correct + // Unicode values. + // + // N.B. In the English locale, the UnicodeChar values are correct, + // and they identify single-line characters rather than + // double-line. In the Chinese Simplified and Traditional locales, + // the popups use ASCII characters instead. + case 1: return 0x2554; // BOX DRAWINGS DOUBLE DOWN AND RIGHT + case 2: return 0x2557; // BOX DRAWINGS DOUBLE DOWN AND LEFT + case 3: return 0x255A; // BOX DRAWINGS DOUBLE UP AND RIGHT + case 4: return 0x255D; // BOX DRAWINGS DOUBLE UP AND LEFT + case 5: return 0x2551; // BOX DRAWINGS DOUBLE VERTICAL + case 6: return 0x2550; // BOX DRAWINGS DOUBLE HORIZONTAL + + // Convert an escape character to some other character. This + // conversion only applies to console cells containing an escape + // character. In newer versions of Windows 10 (e.g. 10.0.10586), + // the non-legacy console recognizes escape sequences in + // WriteConsole and interprets them without writing them to the + // cells of the screen buffer. In that case, the conversion here + // does not apply. + case 0x1b: return '?'; + } + } + return ch; +} + +static inline bool isFullWidthCharacter(const CHAR_INFO *data, int width) +{ + if (width < 2) { + return false; + } + return + (data[0].Attributes & WINPTY_COMMON_LVB_LEADING_BYTE) && + (data[1].Attributes & WINPTY_COMMON_LVB_TRAILING_BYTE) && + data[0].Char.UnicodeChar == data[1].Char.UnicodeChar; +} + +// Scan to find a single Unicode Scalar Value. Full-width characters occupy +// two console cells, and this code also tries to handle UTF-16 surrogate +// pairs. +// +// Windows expands at least some wide characters outside the Basic +// Multilingual Plane into four cells, such as U+20000: +// 1. 0xD840, attr=0x107 +// 2. 0xD840, attr=0x207 +// 3. 0xDC00, attr=0x107 +// 4. 0xDC00, attr=0x207 +// Even in the Traditional Chinese locale on Windows 10, this text is rendered +// as two boxes, but if those boxes are copied-and-pasted, the character is +// copied correctly. +static inline void scanUnicodeScalarValue( + const CHAR_INFO *data, int width, + int &outCellCount, unsigned int &outCharValue) +{ + ASSERT(width >= 1); + + const int w1 = isFullWidthCharacter(data, width) ? 2 : 1; + const wchar_t c1 = data[0].Char.UnicodeChar; + + if ((c1 & 0xF800) == 0xD800) { + // The first cell is either a leading or trailing surrogate pair. + if ((c1 & 0xFC00) != 0xD800 || + width <= w1 || + ((data[w1].Char.UnicodeChar & 0xFC00) != 0xDC00)) { + // Invalid surrogate pair + outCellCount = w1; + outCharValue = '?'; + } else { + // Valid surrogate pair + outCellCount = w1 + (isFullWidthCharacter(&data[w1], width - w1) ? 2 : 1); + outCharValue = decodeSurrogatePair(c1, data[w1].Char.UnicodeChar); + } + } else { + outCellCount = w1; + outCharValue = c1; + } +} + +} // anonymous namespace + +void Terminal::reset(SendClearFlag sendClearFirst, int64_t newLine) +{ + if (sendClearFirst == SendClear && !m_plainMode) { + // 0m ==> reset SGR parameters + // 1;1H ==> move cursor to top-left position + // 2J ==> clear the entire screen + m_output.write(CSI "0m" CSI "1;1H" CSI "2J"); + } + m_remoteLine = newLine; + m_remoteColumn = 0; + m_lineData.clear(); + m_cursorHidden = false; + m_remoteColor = -1; +} + +void Terminal::sendLine(int64_t line, const CHAR_INFO *lineData, int width, + int cursorColumn) +{ + ASSERT(width >= 1); + + moveTerminalToLine(line); + + // If possible, see if we can append to what we've already output for this + // line. + if (m_lineDataValid) { + ASSERT(m_lineData.size() == static_cast(m_remoteColumn)); + if (m_remoteColumn > 0) { + // In normal mode, if m_lineData.size() equals `width`, then we + // will have trouble outputing the "erase rest of line" command, + // which must be output before reaching the end of the line. In + // plain mode, we don't output that command, so we're OK with a + // full line. + bool okWidth = false; + if (m_plainMode) { + okWidth = static_cast(width) >= m_lineData.size(); + } else { + okWidth = static_cast(width) > m_lineData.size(); + } + if (!okWidth || + memcmp(m_lineData.data(), lineData, + sizeof(CHAR_INFO) * m_lineData.size()) != 0) { + m_lineDataValid = false; + } + } + } + if (!m_lineDataValid) { + // We can't reuse, so we must reset this line. + hideTerminalCursor(); + if (m_plainMode) { + // We can't backtrack, so repeat this line. + m_output.write("\r\n"); + } else { + m_output.write("\r"); + } + m_lineDataValid = true; + m_lineData.clear(); + m_remoteColumn = 0; + } + + std::string &termLine = m_termLineWorkingBuffer; + termLine.clear(); + size_t trimmedLineLength = 0; + int trimmedCellCount = m_lineData.size(); + bool alreadyErasedLine = false; + + int cellCount = 1; + for (int i = m_lineData.size(); i < width; i += cellCount) { + if (m_outputColor) { + int color = lineData[i].Attributes & COLOR_ATTRIBUTE_MASK; + if (color != m_remoteColor) { + outputSetColor(termLine, color); + trimmedLineLength = termLine.size(); + m_remoteColor = color; + + // All the cells just up to this color change will be output. + trimmedCellCount = i; + } + } + unsigned int ch; + scanUnicodeScalarValue(&lineData[i], width - i, cellCount, ch); + if (ch == ' ') { + // Tentatively add this space character. We'll only output it if + // we see something interesting after it. + termLine.push_back(' '); + } else { + if (i + cellCount == width) { + // We'd like to erase the line after outputting all non-blank + // characters, but this doesn't work if the last cell in the + // line is non-blank. At the point, the cursor is positioned + // just past the end of the line, but in many terminals, + // issuing a CSI 0K at that point also erases the last cell in + // the line. Work around this behavior by issuing the erase + // one character early in that case. + if (!m_plainMode) { + termLine.append(CSI "0K"); // Erase from cursor to EOL + } + alreadyErasedLine = true; + } + ch = fixSpecialCharacters(ch); + char enc[4]; + int enclen = encodeUtf8(enc, ch); + if (enclen == 0) { + enc[0] = '?'; + enclen = 1; + } + termLine.append(enc, enclen); + trimmedLineLength = termLine.size(); + + // All the cells up to and including this cell will be output. + trimmedCellCount = i + cellCount; + } + } + + if (cursorColumn != -1 && trimmedCellCount > cursorColumn) { + // The line content would run past the cursor, so hide it before we + // output. + hideTerminalCursor(); + } + + m_output.write(termLine.data(), trimmedLineLength); + if (!alreadyErasedLine && !m_plainMode) { + m_output.write(CSI "0K"); // Erase from cursor to EOL + } + + ASSERT(trimmedCellCount <= width); + m_lineData.insert(m_lineData.end(), + &lineData[m_lineData.size()], + &lineData[trimmedCellCount]); + m_remoteColumn = trimmedCellCount; +} + +void Terminal::showTerminalCursor(int column, int64_t line) +{ + moveTerminalToLine(line); + if (!m_plainMode) { + if (m_remoteColumn != column) { + char buffer[32]; + winpty_snprintf(buffer, CSI "%dG", column + 1); + m_output.write(buffer); + m_lineDataValid = (column == 0); + m_lineData.clear(); + m_remoteColumn = column; + } + if (m_cursorHidden) { + m_output.write(CSI "?25h"); + m_cursorHidden = false; + } + } +} + +void Terminal::hideTerminalCursor() +{ + if (!m_plainMode) { + if (m_cursorHidden) { + return; + } + m_output.write(CSI "?25l"); + m_cursorHidden = true; + } +} + +void Terminal::moveTerminalToLine(int64_t line) +{ + if (line == m_remoteLine) { + return; + } + + // Do not use CPL or CNL. Konsole 2.5.4 does not support Cursor Previous + // Line (CPL) -- there are "Undecodable sequence" errors. gnome-terminal + // 2.32.0 does handle it. Cursor Next Line (CNL) does nothing if the + // cursor is on the last line already. + + hideTerminalCursor(); + + if (line < m_remoteLine) { + if (m_plainMode) { + // We can't backtrack, so instead repeat the lines again. + m_output.write("\r\n"); + m_remoteLine = line; + } else { + // Backtrack and overwrite previous lines. + // CUrsor Up (CUU) + char buffer[32]; + winpty_snprintf(buffer, "\r" CSI "%uA", + static_cast(m_remoteLine - line)); + m_output.write(buffer); + m_remoteLine = line; + } + } else if (line > m_remoteLine) { + while (line > m_remoteLine) { + m_output.write("\r\n"); + m_remoteLine++; + } + } + + m_lineDataValid = true; + m_lineData.clear(); + m_remoteColumn = 0; +} + +void Terminal::enableMouseMode(bool enabled) +{ + if (m_mouseModeEnabled == enabled || m_plainMode) { + return; + } + m_mouseModeEnabled = enabled; + if (enabled) { + // Start by disabling UTF-8 coordinate mode (1005), just in case we + // have a terminal that does not support 1006/1015 modes, and 1005 + // happens to be enabled. The UTF-8 coordinates can't be unambiguously + // decoded. + // + // Enable basic mouse support first (1000), then try to switch to + // button-move mode (1002), then try full mouse-move mode (1003). + // Terminals that don't support a mode will be stuck at the highest + // mode they do support. + // + // Enable encoding mode 1015 first, then try to switch to 1006. On + // some terminals, both modes will be enabled, but 1006 will have + // priority. On other terminals, 1006 wins because it's listed last. + // + // See misc/MouseInputNotes.txt for details. + m_output.write( + CSI "?1005l" + CSI "?1000h" CSI "?1002h" CSI "?1003h" CSI "?1015h" CSI "?1006h"); + } else { + // Resetting both encoding modes (1006 and 1015) is necessary, but + // apparently we only need to use reset on one of the 100[023] modes. + // Doing both doesn't hurt. + m_output.write( + CSI "?1006l" CSI "?1015l" CSI "?1003l" CSI "?1002l" CSI "?1000l"); + } +} diff --git a/src/libs/3rdparty/winpty/src/agent/Terminal.h b/src/libs/3rdparty/winpty/src/agent/Terminal.h new file mode 100644 index 0000000000..058eb2650e --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/Terminal.h @@ -0,0 +1,69 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef TERMINAL_H +#define TERMINAL_H + +#include +#include + +#include +#include + +#include "Coord.h" + +class NamedPipe; + +class Terminal +{ +public: + explicit Terminal(NamedPipe &output, bool plainMode, bool outputColor) + : m_output(output), m_plainMode(plainMode), m_outputColor(outputColor) + { + } + + enum SendClearFlag { OmitClear, SendClear }; + void reset(SendClearFlag sendClearFirst, int64_t newLine); + void sendLine(int64_t line, const CHAR_INFO *lineData, int width, + int cursorColumn); + void showTerminalCursor(int column, int64_t line); + void hideTerminalCursor(); + +private: + void moveTerminalToLine(int64_t line); + +public: + void enableMouseMode(bool enabled); + +private: + NamedPipe &m_output; + int64_t m_remoteLine = 0; + int m_remoteColumn = 0; + bool m_lineDataValid = true; + std::vector m_lineData; + bool m_cursorHidden = false; + int m_remoteColor = -1; + std::string m_termLineWorkingBuffer; + bool m_plainMode = false; + bool m_outputColor = true; + bool m_mouseModeEnabled = false; +}; + +#endif // TERMINAL_H diff --git a/src/libs/3rdparty/winpty/src/agent/UnicodeEncoding.h b/src/libs/3rdparty/winpty/src/agent/UnicodeEncoding.h new file mode 100644 index 0000000000..6b0de3eff9 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/UnicodeEncoding.h @@ -0,0 +1,157 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef UNICODE_ENCODING_H +#define UNICODE_ENCODING_H + +#include + +// Encode the Unicode codepoint with UTF-8. The buffer must be at least 4 +// bytes in size. +static inline int encodeUtf8(char *out, uint32_t code) { + if (code < 0x80) { + out[0] = code; + return 1; + } else if (code < 0x800) { + out[0] = ((code >> 6) & 0x1F) | 0xC0; + out[1] = ((code >> 0) & 0x3F) | 0x80; + return 2; + } else if (code < 0x10000) { + if (code >= 0xD800 && code <= 0xDFFF) { + // The code points 0xD800 to 0xDFFF are reserved for UTF-16 + // surrogate pairs and do not have an encoding in UTF-8. + return 0; + } + out[0] = ((code >> 12) & 0x0F) | 0xE0; + out[1] = ((code >> 6) & 0x3F) | 0x80; + out[2] = ((code >> 0) & 0x3F) | 0x80; + return 3; + } else if (code < 0x110000) { + out[0] = ((code >> 18) & 0x07) | 0xF0; + out[1] = ((code >> 12) & 0x3F) | 0x80; + out[2] = ((code >> 6) & 0x3F) | 0x80; + out[3] = ((code >> 0) & 0x3F) | 0x80; + return 4; + } else { + // Encoding error + return 0; + } +} + +// Encode the Unicode codepoint with UTF-16. The buffer must be large enough +// to hold the output -- either 1 or 2 elements. +static inline int encodeUtf16(wchar_t *out, uint32_t code) { + if (code < 0x10000) { + if (code >= 0xD800 && code <= 0xDFFF) { + // The code points 0xD800 to 0xDFFF are reserved for UTF-16 + // surrogate pairs and do not have an encoding in UTF-16. + return 0; + } + out[0] = code; + return 1; + } else if (code < 0x110000) { + code -= 0x10000; + out[0] = 0xD800 | (code >> 10); + out[1] = 0xDC00 | (code & 0x3FF); + return 2; + } else { + // Encoding error + return 0; + } +} + +// Return the byte size of a UTF-8 character using the value of the first +// byte. +static inline int utf8CharLength(char firstByte) { + // This code would probably be faster if it used __builtin_clz. + if ((firstByte & 0x80) == 0) { + return 1; + } else if ((firstByte & 0xE0) == 0xC0) { + return 2; + } else if ((firstByte & 0xF0) == 0xE0) { + return 3; + } else if ((firstByte & 0xF8) == 0xF0) { + return 4; + } else { + // Malformed UTF-8. + return 0; + } +} + +// The pointer must point to 1-4 bytes, as indicated by the first byte. +// Returns -1 on decoding error. +static inline uint32_t decodeUtf8(const char *in) { + const uint32_t kInvalid = static_cast(-1); + switch (utf8CharLength(in[0])) { + case 1: { + return in[0]; + } + case 2: { + if ((in[1] & 0xC0) != 0x80) { + return kInvalid; + } + uint32_t tmp = 0; + tmp = (in[0] & 0x1F) << 6; + tmp |= (in[1] & 0x3F); + return tmp <= 0x7F ? kInvalid : tmp; + } + case 3: { + if ((in[1] & 0xC0) != 0x80 || + (in[2] & 0xC0) != 0x80) { + return kInvalid; + } + uint32_t tmp = 0; + tmp = (in[0] & 0x0F) << 12; + tmp |= (in[1] & 0x3F) << 6; + tmp |= (in[2] & 0x3F); + if (tmp <= 0x07FF || (tmp >= 0xD800 && tmp <= 0xDFFF)) { + return kInvalid; + } else { + return tmp; + } + } + case 4: { + if ((in[1] & 0xC0) != 0x80 || + (in[2] & 0xC0) != 0x80 || + (in[3] & 0xC0) != 0x80) { + return kInvalid; + } + uint32_t tmp = 0; + tmp = (in[0] & 0x07) << 18; + tmp |= (in[1] & 0x3F) << 12; + tmp |= (in[2] & 0x3F) << 6; + tmp |= (in[3] & 0x3F); + if (tmp <= 0xFFFF || tmp > 0x10FFFF) { + return kInvalid; + } else { + return tmp; + } + } + default: { + return kInvalid; + } + } +} + +static inline uint32_t decodeSurrogatePair(wchar_t ch1, wchar_t ch2) { + return ((ch1 - 0xD800) << 10) + (ch2 - 0xDC00) + 0x10000; +} + +#endif // UNICODE_ENCODING_H diff --git a/src/libs/3rdparty/winpty/src/agent/UnicodeEncodingTest.cc b/src/libs/3rdparty/winpty/src/agent/UnicodeEncodingTest.cc new file mode 100644 index 0000000000..cd4abeb191 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/UnicodeEncodingTest.cc @@ -0,0 +1,189 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +// Encode every code-point using this module and verify that it matches the +// encoding generated using Windows WideCharToMultiByte. + +#include "UnicodeEncoding.h" + +#include +#include +#include +#include +#include + +static void correctnessByCode() +{ + char mbstr1[4]; + char mbstr2[4]; + wchar_t wch[2]; + for (unsigned int code = 0; code < 0x110000; ++code) { + + // Surrogate pair reserved region. + const bool isReserved = (code >= 0xD800 && code <= 0xDFFF); + + int mblen1 = encodeUtf8(mbstr1, code); + if (isReserved ? mblen1 != 0 : mblen1 <= 0) { + printf("Error: 0x%04X: mblen1=%d\n", code, mblen1); + continue; + } + + int wlen = encodeUtf16(wch, code); + if (isReserved ? wlen != 0 : wlen <= 0) { + printf("Error: 0x%04X: wlen=%d\n", code, wlen); + continue; + } + + if (isReserved) { + continue; + } + + if (mblen1 != utf8CharLength(mbstr1[0])) { + printf("Error: 0x%04X: mblen1=%d, utf8CharLength(mbstr1[0])=%d\n", + code, mblen1, utf8CharLength(mbstr1[0])); + continue; + } + + if (code != decodeUtf8(mbstr1)) { + printf("Error: 0x%04X: decodeUtf8(mbstr1)=%u\n", + code, decodeUtf8(mbstr1)); + continue; + } + + int mblen2 = WideCharToMultiByte(CP_UTF8, 0, wch, wlen, mbstr2, 4, NULL, NULL); + if (mblen1 != mblen2) { + printf("Error: 0x%04X: mblen1=%d, mblen2=%d\n", code, mblen1, mblen2); + continue; + } + + if (memcmp(mbstr1, mbstr2, mblen1) != 0) { + printf("Error: 0x%04x: encodings are different\n", code); + continue; + } + } +} + +static const char *encodingStr(char (&output)[128], char (&buf)[4]) +{ + sprintf(output, "Encoding %02X %02X %02X %02X", + static_cast(buf[0]), + static_cast(buf[1]), + static_cast(buf[2]), + static_cast(buf[3])); + return output; +} + +// This test can take a couple of minutes to run. +static void correctnessByUtf8Encoding() +{ + for (uint64_t encoding = 0; encoding <= 0xFFFFFFFF; ++encoding) { + + char mb[4]; + mb[0] = encoding; + mb[1] = encoding >> 8; + mb[2] = encoding >> 16; + mb[3] = encoding >> 24; + + const int mblen = utf8CharLength(mb[0]); + if (mblen == 0) { + continue; + } + + // Test this module. + const uint32_t code1 = decodeUtf8(mb); + wchar_t ws1[2] = {}; + const int wslen1 = encodeUtf16(ws1, code1); + + // Test using Windows. We can't decode a codepoint directly; we have + // to do UTF8->UTF16, then decode the surrogate pair. + wchar_t ws2[2] = {}; + const int wslen2 = MultiByteToWideChar( + CP_UTF8, MB_ERR_INVALID_CHARS, mb, mblen, ws2, 2); + const uint32_t code2 = + (wslen2 == 1 ? ws2[0] : + wslen2 == 2 ? decodeSurrogatePair(ws2[0], ws2[1]) : + static_cast(-1)); + + // Verify that the two implementations match. + char prefix[128]; + if (code1 != code2) { + printf("%s: code1=0x%04x code2=0x%04x\n", + encodingStr(prefix, mb), + code1, code2); + continue; + } + if (wslen1 != wslen2) { + printf("%s: wslen1=%d wslen2=%d\n", + encodingStr(prefix, mb), + wslen1, wslen2); + continue; + } + if (memcmp(ws1, ws2, wslen1 * sizeof(wchar_t)) != 0) { + printf("%s: ws1 != ws2\n", encodingStr(prefix, mb)); + continue; + } + } +} + +wchar_t g_wch_TEST[] = { 0xD840, 0xDC00 }; +char g_ch_TEST[4]; +wchar_t *volatile g_pwch = g_wch_TEST; +char *volatile g_pch = g_ch_TEST; +unsigned int volatile g_code = 0xA2000; + +static void performance() +{ + { + clock_t start = clock(); + for (long long i = 0; i < 250000000LL; ++i) { + int mblen = WideCharToMultiByte(CP_UTF8, 0, g_pwch, 2, g_pch, 4, NULL, NULL); + assert(mblen == 4); + } + clock_t stop = clock(); + printf("%.3fns per char\n", (double)(stop - start) / CLOCKS_PER_SEC * 4.0); + } + + { + clock_t start = clock(); + for (long long i = 0; i < 3000000000LL; ++i) { + int mblen = encodeUtf8(g_pch, g_code); + assert(mblen == 4); + } + clock_t stop = clock(); + printf("%.3fns per char\n", (double)(stop - start) / CLOCKS_PER_SEC / 3.0); + } +} + +int main() +{ + printf("Testing correctnessByCode...\n"); + fflush(stdout); + correctnessByCode(); + + printf("Testing correctnessByUtf8Encoding... (may take a couple minutes)\n"); + fflush(stdout); + correctnessByUtf8Encoding(); + + printf("Testing performance...\n"); + fflush(stdout); + performance(); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/src/agent/Win32Console.cc b/src/libs/3rdparty/winpty/src/agent/Win32Console.cc new file mode 100644 index 0000000000..d53de021f5 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/Win32Console.cc @@ -0,0 +1,107 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "Win32Console.h" + +#include +#include + +#include + +#include "../shared/DebugClient.h" +#include "../shared/WinptyAssert.h" + +Win32Console::Win32Console() : m_titleWorkBuf(16) +{ + // The console window must be non-NULL. It is used for two purposes: + // (1) "Freezing" the console to detect the exact number of lines that + // have scrolled. + // (2) Killing processes attached to the console, by posting a WM_CLOSE + // message to the console window. + m_hwnd = GetConsoleWindow(); + ASSERT(m_hwnd != nullptr); +} + +std::wstring Win32Console::title() +{ + while (true) { + // Calling GetConsoleTitleW is tricky, because its behavior changed + // from XP->Vista, then again from Win7->Win8. The Vista+Win7 behavior + // is especially broken. + // + // The MSDN documentation documents nSize as the "size of the buffer + // pointed to by the lpConsoleTitle parameter, in characters" and the + // successful return value as "the length of the console window's + // title, in characters." + // + // On XP, the function returns the title length, AFTER truncation + // (excluding the NUL terminator). If the title is blank, the API + // returns 0 and does not NUL-terminate the buffer. To accommodate + // XP, the function must: + // * Terminate the buffer itself. + // * Double the size of the title buffer in a loop. + // + // On Vista and up, the function returns the non-truncated title + // length (excluding the NUL terminator). + // + // On Vista and Windows 7, there is a bug where the buffer size is + // interpreted as a byte count rather than a wchar_t count. To + // work around this, we must pass GetConsoleTitleW a buffer that is + // twice as large as what is actually needed. + // + // See misc/*/Test_GetConsoleTitleW.cc for tests demonstrating Windows' + // behavior. + + DWORD count = GetConsoleTitleW(m_titleWorkBuf.data(), + m_titleWorkBuf.size()); + const size_t needed = (count + 1) * sizeof(wchar_t); + if (m_titleWorkBuf.size() < needed) { + m_titleWorkBuf.resize(needed); + continue; + } + m_titleWorkBuf[count] = L'\0'; + return m_titleWorkBuf.data(); + } +} + +void Win32Console::setTitle(const std::wstring &title) +{ + if (!SetConsoleTitleW(title.c_str())) { + trace("SetConsoleTitleW failed"); + } +} + +void Win32Console::setFrozen(bool frozen) { + const int SC_CONSOLE_MARK = 0xFFF2; + const int SC_CONSOLE_SELECT_ALL = 0xFFF5; + if (frozen == m_frozen) { + // Do nothing. + } else if (frozen) { + // Enter selection mode by activating either Mark or SelectAll. + const int command = m_freezeUsesMark ? SC_CONSOLE_MARK + : SC_CONSOLE_SELECT_ALL; + SendMessage(m_hwnd, WM_SYSCOMMAND, command, 0); + m_frozen = true; + } else { + // Send Escape to cancel the selection. + SendMessage(m_hwnd, WM_CHAR, 27, 0x00010001); + m_frozen = false; + } +} diff --git a/src/libs/3rdparty/winpty/src/agent/Win32Console.h b/src/libs/3rdparty/winpty/src/agent/Win32Console.h new file mode 100644 index 0000000000..ed83877e99 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/Win32Console.h @@ -0,0 +1,67 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef AGENT_WIN32_CONSOLE_H +#define AGENT_WIN32_CONSOLE_H + +#include + +#include +#include + +class Win32Console +{ +public: + class FreezeGuard { + public: + FreezeGuard(Win32Console &console, bool frozen) : + m_console(console), m_previous(console.frozen()) { + m_console.setFrozen(frozen); + } + ~FreezeGuard() { + m_console.setFrozen(m_previous); + } + FreezeGuard(const FreezeGuard &other) = delete; + FreezeGuard &operator=(const FreezeGuard &other) = delete; + private: + Win32Console &m_console; + bool m_previous; + }; + + Win32Console(); + + HWND hwnd() { return m_hwnd; } + std::wstring title(); + void setTitle(const std::wstring &title); + void setFreezeUsesMark(bool useMark) { m_freezeUsesMark = useMark; } + void setNewW10(bool isNewW10) { m_isNewW10 = isNewW10; } + bool isNewW10() { return m_isNewW10; } + void setFrozen(bool frozen=true); + bool frozen() { return m_frozen; } + +private: + HWND m_hwnd = nullptr; + bool m_frozen = false; + bool m_freezeUsesMark = false; + bool m_isNewW10 = false; + std::vector m_titleWorkBuf; +}; + +#endif // AGENT_WIN32_CONSOLE_H diff --git a/src/libs/3rdparty/winpty/src/agent/Win32ConsoleBuffer.cc b/src/libs/3rdparty/winpty/src/agent/Win32ConsoleBuffer.cc new file mode 100644 index 0000000000..ed93f4081f --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/Win32ConsoleBuffer.cc @@ -0,0 +1,193 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "Win32ConsoleBuffer.h" + +#include + +#include "../shared/DebugClient.h" +#include "../shared/StringBuilder.h" +#include "../shared/WinptyAssert.h" + +std::unique_ptr Win32ConsoleBuffer::openStdout() { + return std::unique_ptr( + new Win32ConsoleBuffer(GetStdHandle(STD_OUTPUT_HANDLE), false)); +} + +std::unique_ptr Win32ConsoleBuffer::openConout() { + const HANDLE conout = CreateFileW(L"CONOUT$", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + ASSERT(conout != INVALID_HANDLE_VALUE); + return std::unique_ptr( + new Win32ConsoleBuffer(conout, true)); +} + +std::unique_ptr Win32ConsoleBuffer::createErrorBuffer() { + SECURITY_ATTRIBUTES sa = {}; + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + const HANDLE conout = + CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &sa, + CONSOLE_TEXTMODE_BUFFER, + nullptr); + ASSERT(conout != INVALID_HANDLE_VALUE); + return std::unique_ptr( + new Win32ConsoleBuffer(conout, true)); +} + +HANDLE Win32ConsoleBuffer::conout() { + return m_conout; +} + +void Win32ConsoleBuffer::clearLines( + int row, + int count, + const ConsoleScreenBufferInfo &info) { + // TODO: error handling + const int width = info.bufferSize().X; + DWORD actual = 0; + if (!FillConsoleOutputCharacterW( + m_conout, L' ', width * count, Coord(0, row), + &actual) || static_cast(actual) != width * count) { + trace("FillConsoleOutputCharacterW failed"); + } + if (!FillConsoleOutputAttribute( + m_conout, kDefaultAttributes, width * count, Coord(0, row), + &actual) || static_cast(actual) != width * count) { + trace("FillConsoleOutputAttribute failed"); + } +} + +void Win32ConsoleBuffer::clearAllLines(const ConsoleScreenBufferInfo &info) { + clearLines(0, info.bufferSize().Y, info); +} + +ConsoleScreenBufferInfo Win32ConsoleBuffer::bufferInfo() { + // TODO: error handling + ConsoleScreenBufferInfo info; + if (!GetConsoleScreenBufferInfo(m_conout, &info)) { + trace("GetConsoleScreenBufferInfo failed"); + } + return info; +} + +Coord Win32ConsoleBuffer::bufferSize() { + return bufferInfo().bufferSize(); +} + +SmallRect Win32ConsoleBuffer::windowRect() { + return bufferInfo().windowRect(); +} + +bool Win32ConsoleBuffer::resizeBufferRange(const Coord &initialSize, + Coord &finalSize) { + if (SetConsoleScreenBufferSize(m_conout, initialSize)) { + finalSize = initialSize; + return true; + } + // The font might be too small to accommodate a very narrow console window. + // In that case, rather than simply give up, it's better to try wider + // buffer sizes until the call succeeds. + Coord size = initialSize; + while (size.X < 20) { + size.X++; + if (SetConsoleScreenBufferSize(m_conout, size)) { + finalSize = size; + trace("SetConsoleScreenBufferSize: initial size (%d,%d) failed, " + "but wider size (%d,%d) succeeded", + initialSize.X, initialSize.Y, + finalSize.X, finalSize.Y); + return true; + } + } + trace("SetConsoleScreenBufferSize failed: " + "tried (%d,%d) through (%d,%d)", + initialSize.X, initialSize.Y, + size.X, size.Y); + return false; +} + +void Win32ConsoleBuffer::resizeBuffer(const Coord &size) { + // TODO: error handling + if (!SetConsoleScreenBufferSize(m_conout, size)) { + trace("SetConsoleScreenBufferSize failed: size=(%d,%d)", + size.X, size.Y); + } +} + +void Win32ConsoleBuffer::moveWindow(const SmallRect &rect) { + // TODO: error handling + if (!SetConsoleWindowInfo(m_conout, TRUE, &rect)) { + trace("SetConsoleWindowInfo failed"); + } +} + +Coord Win32ConsoleBuffer::cursorPosition() { + return bufferInfo().dwCursorPosition; +} + +void Win32ConsoleBuffer::setCursorPosition(const Coord &coord) { + // TODO: error handling + if (!SetConsoleCursorPosition(m_conout, coord)) { + trace("SetConsoleCursorPosition failed"); + } +} + +void Win32ConsoleBuffer::read(const SmallRect &rect, CHAR_INFO *data) { + // TODO: error handling + SmallRect tmp(rect); + if (!ReadConsoleOutputW(m_conout, data, rect.size(), Coord(), &tmp) && + isTracingEnabled()) { + StringBuilder sb(256); + auto outStruct = [&](const SMALL_RECT &sr) { + sb << "{L=" << sr.Left << ",T=" << sr.Top + << ",R=" << sr.Right << ",B=" << sr.Bottom << '}'; + }; + sb << "Win32ConsoleBuffer::read: ReadConsoleOutput failed: readRegion="; + outStruct(rect); + CONSOLE_SCREEN_BUFFER_INFO info = {}; + if (GetConsoleScreenBufferInfo(m_conout, &info)) { + sb << ", dwSize=(" << info.dwSize.X << ',' << info.dwSize.Y + << "), srWindow="; + outStruct(info.srWindow); + } else { + sb << ", GetConsoleScreenBufferInfo also failed"; + } + trace("%s", sb.c_str()); + } +} + +void Win32ConsoleBuffer::write(const SmallRect &rect, const CHAR_INFO *data) { + // TODO: error handling + SmallRect tmp(rect); + if (!WriteConsoleOutputW(m_conout, data, rect.size(), Coord(), &tmp)) { + trace("WriteConsoleOutput failed"); + } +} + +void Win32ConsoleBuffer::setTextAttribute(WORD attributes) { + if (!SetConsoleTextAttribute(m_conout, attributes)) { + trace("SetConsoleTextAttribute failed"); + } +} diff --git a/src/libs/3rdparty/winpty/src/agent/Win32ConsoleBuffer.h b/src/libs/3rdparty/winpty/src/agent/Win32ConsoleBuffer.h new file mode 100644 index 0000000000..a68d8d304f --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/Win32ConsoleBuffer.h @@ -0,0 +1,99 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef AGENT_WIN32_CONSOLE_BUFFER_H +#define AGENT_WIN32_CONSOLE_BUFFER_H + +#include + +#include + +#include + +#include "Coord.h" +#include "SmallRect.h" + +class ConsoleScreenBufferInfo : public CONSOLE_SCREEN_BUFFER_INFO { +public: + ConsoleScreenBufferInfo() + { + memset(this, 0, sizeof(*this)); + } + + Coord bufferSize() const { return dwSize; } + SmallRect windowRect() const { return srWindow; } + Coord cursorPosition() const { return dwCursorPosition; } +}; + +class Win32ConsoleBuffer { +private: + Win32ConsoleBuffer(HANDLE conout, bool owned) : + m_conout(conout), m_owned(owned) + { + } + +public: + static const int kDefaultAttributes = 7; + + ~Win32ConsoleBuffer() { + if (m_owned) { + CloseHandle(m_conout); + } + } + + static std::unique_ptr openStdout(); + static std::unique_ptr openConout(); + static std::unique_ptr createErrorBuffer(); + + Win32ConsoleBuffer(const Win32ConsoleBuffer &other) = delete; + Win32ConsoleBuffer &operator=(const Win32ConsoleBuffer &other) = delete; + + HANDLE conout(); + void clearLines(int row, int count, const ConsoleScreenBufferInfo &info); + void clearAllLines(const ConsoleScreenBufferInfo &info); + + // Buffer and window sizes. + ConsoleScreenBufferInfo bufferInfo(); + Coord bufferSize(); + SmallRect windowRect(); + void resizeBuffer(const Coord &size); + bool resizeBufferRange(const Coord &initialSize, Coord &finalSize); + bool resizeBufferRange(const Coord &initialSize) { + Coord dummy; + return resizeBufferRange(initialSize, dummy); + } + void moveWindow(const SmallRect &rect); + + // Cursor. + Coord cursorPosition(); + void setCursorPosition(const Coord &point); + + // Screen content. + void read(const SmallRect &rect, CHAR_INFO *data); + void write(const SmallRect &rect, const CHAR_INFO *data); + + void setTextAttribute(WORD attributes); + +private: + HANDLE m_conout = nullptr; + bool m_owned = false; +}; + +#endif // AGENT_WIN32_CONSOLE_BUFFER_H diff --git a/src/libs/3rdparty/winpty/src/agent/main.cc b/src/libs/3rdparty/winpty/src/agent/main.cc new file mode 100644 index 0000000000..427cb3a3aa --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/main.cc @@ -0,0 +1,120 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include +#include +#include + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#include + +#include "../shared/StringUtil.h" +#include "../shared/WindowsVersion.h" +#include "../shared/WinptyAssert.h" +#include "../shared/WinptyVersion.h" + +#include "Agent.h" +#include "AgentCreateDesktop.h" +#include "DebugShowInput.h" + +const char USAGE[] = +"Usage: %ls controlPipeName flags mouseMode cols rows\n" +"Usage: %ls controlPipeName --create-desktop\n" +"\n" +"Ordinarily, this program is launched by winpty.dll and is not directly\n" +"useful to winpty users. However, it also has options intended for\n" +"debugging winpty.\n" +"\n" +"Usage: %ls [options]\n" +"\n" +"Options:\n" +" --show-input [--with-mouse] [--escape-input]\n" +" Dump INPUT_RECORDs from the console input buffer\n" +" --with-mouse: Include MOUSE_INPUT_RECORDs in the dump\n" +" output\n" +" --escape-input: Direct the new Windows 10 console to use\n" +" escape sequences for input\n" +" --version Print the winpty version\n"; + +static uint64_t winpty_atoi64(const char *str) { + return strtoll(str, NULL, 10); +} + +int main() { + dumpWindowsVersion(); + dumpVersionToTrace(); + + // Technically, we should free the CommandLineToArgvW return value using + // a single call to LocalFree, but the call will never actually happen in + // the normal case. + int argc = 0; + wchar_t *cmdline = GetCommandLineW(); + ASSERT(cmdline != nullptr && "GetCommandLineW returned NULL"); + wchar_t **argv = CommandLineToArgvW(cmdline, &argc); + ASSERT(argv != nullptr && "CommandLineToArgvW returned NULL"); + + if (argc == 2 && !wcscmp(argv[1], L"--version")) { + dumpVersionToStdout(); + return 0; + } + + if (argc >= 2 && !wcscmp(argv[1], L"--show-input")) { + bool withMouse = false; + bool escapeInput = false; + for (int i = 2; i < argc; ++i) { + if (!wcscmp(argv[i], L"--with-mouse")) { + withMouse = true; + } else if (!wcscmp(argv[i], L"--escape-input")) { + escapeInput = true; + } else { + fprintf(stderr, "Unrecognized --show-input option: %ls\n", + argv[i]); + return 1; + } + } + debugShowInput(withMouse, escapeInput); + return 0; + } + + if (argc == 3 && !wcscmp(argv[2], L"--create-desktop")) { + handleCreateDesktop(argv[1]); + return 0; + } + + if (argc != 6) { + fprintf(stderr, USAGE, argv[0], argv[0], argv[0]); + return 1; + } + + Agent agent(argv[1], + winpty_atoi64(utf8FromWide(argv[2]).c_str()), + atoi(utf8FromWide(argv[3]).c_str()), + atoi(utf8FromWide(argv[4]).c_str()), + atoi(utf8FromWide(argv[5]).c_str())); + agent.run(); + + // The Agent destructor shouldn't return, but if it does, exit + // unsuccessfully. + return 1; +} diff --git a/src/libs/3rdparty/winpty/src/agent/subdir.mk b/src/libs/3rdparty/winpty/src/agent/subdir.mk new file mode 100644 index 0000000000..1c7d37e3e5 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/subdir.mk @@ -0,0 +1,61 @@ +# Copyright (c) 2011-2015 Ryan Prichard +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +ALL_TARGETS += build/winpty-agent.exe + +$(eval $(call def_mingw_target,agent,-DWINPTY_AGENT_ASSERT)) + +AGENT_OBJECTS = \ + build/agent/agent/Agent.o \ + build/agent/agent/AgentCreateDesktop.o \ + build/agent/agent/ConsoleFont.o \ + build/agent/agent/ConsoleInput.o \ + build/agent/agent/ConsoleInputReencoding.o \ + build/agent/agent/ConsoleLine.o \ + build/agent/agent/DebugShowInput.o \ + build/agent/agent/DefaultInputMap.o \ + build/agent/agent/EventLoop.o \ + build/agent/agent/InputMap.o \ + build/agent/agent/LargeConsoleRead.o \ + build/agent/agent/NamedPipe.o \ + build/agent/agent/Scraper.o \ + build/agent/agent/Terminal.o \ + build/agent/agent/Win32Console.o \ + build/agent/agent/Win32ConsoleBuffer.o \ + build/agent/agent/main.o \ + build/agent/shared/BackgroundDesktop.o \ + build/agent/shared/Buffer.o \ + build/agent/shared/DebugClient.o \ + build/agent/shared/GenRandom.o \ + build/agent/shared/OwnedHandle.o \ + build/agent/shared/StringUtil.o \ + build/agent/shared/WindowsSecurity.o \ + build/agent/shared/WindowsVersion.o \ + build/agent/shared/WinptyAssert.o \ + build/agent/shared/WinptyException.o \ + build/agent/shared/WinptyVersion.o + +build/agent/shared/WinptyVersion.o : build/gen/GenVersion.h + +build/winpty-agent.exe : $(AGENT_OBJECTS) + $(info Linking $@) + @$(MINGW_CXX) $(MINGW_LDFLAGS) -o $@ $^ + +-include $(AGENT_OBJECTS:.o=.d) diff --git a/src/libs/3rdparty/winpty/src/configurations.gypi b/src/libs/3rdparty/winpty/src/configurations.gypi new file mode 100644 index 0000000000..e990a60338 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/configurations.gypi @@ -0,0 +1,60 @@ +# By default gyp/msbuild build for 32-bit Windows. This gyp include file +# defines configurations for both 32-bit and 64-bit Windows. To use it, run: +# +# C:\...\winpty\src>gyp -I configurations.gypi +# +# This command generates Visual Studio project files with a Release +# configuration and two Platforms--Win32 and x64. Both can be built: +# +# C:\...\winpty\src>msbuild winpty.sln /p:Platform=Win32 +# C:\...\winpty\src>msbuild winpty.sln /p:Platform=x64 +# +# The output is placed in: +# +# C:\...\winpty\src\Release\Win32 +# C:\...\winpty\src\Release\x64 +# +# Windows XP note: By default, the project files will use the default "toolset" +# for the given MSVC version. For MSVC 2013 and MSVC 2015, the default toolset +# generates binaries that do not run on Windows XP. To target Windows XP, +# select the XP-specific toolset by passing +# -D WINPTY_MSBUILD_TOOLSET={v120_xp,v140_xp} to gyp (v120_xp == MSVC 2013, +# v140_xp == MSVC 2015). Unfortunately, it isn't possible to have a single +# project file with configurations for both XP and post-XP. This seems to be a +# limitation of the MSVC project file format. +# +# This file is not included by default, because I suspect it would interfere +# with node-gyp, which has a different system for building 32-vs-64-bit +# binaries. It uses a common.gypi, and the project files it generates can only +# build a single architecture, the output paths are not differentiated by +# architecture. + +{ + 'variables': { + 'WINPTY_MSBUILD_TOOLSET%': '', + }, + 'target_defaults': { + 'default_configuration': 'Release_Win32', + 'configurations': { + 'Release_Win32': { + 'msvs_configuration_platform': 'Win32', + }, + 'Release_x64': { + 'msvs_configuration_platform': 'x64', + }, + }, + 'msvs_configuration_attributes': { + 'OutputDirectory': '$(SolutionDir)$(ConfigurationName)\\$(Platform)', + 'IntermediateDirectory': '$(ConfigurationName)\\$(Platform)\\obj\\$(ProjectName)', + }, + 'msvs_settings': { + 'VCLinkerTool': { + 'SubSystem': '1', # /SUBSYSTEM:CONSOLE + }, + 'VCCLCompilerTool': { + 'RuntimeLibrary': '0', # MultiThreaded (/MT) + }, + }, + 'msbuild_toolset' : '<(WINPTY_MSBUILD_TOOLSET)', + } +} diff --git a/src/libs/3rdparty/winpty/src/debugserver/DebugServer.cc b/src/libs/3rdparty/winpty/src/debugserver/DebugServer.cc new file mode 100644 index 0000000000..353d31c1c6 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/debugserver/DebugServer.cc @@ -0,0 +1,117 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include +#include + +#include + +#include "../shared/WindowsSecurity.h" +#include "../shared/WinptyException.h" + +const wchar_t *kPipeName = L"\\\\.\\pipe\\DebugServer"; + +// A message may not be larger than this size. +const int MSG_SIZE = 4096; + +static void usage(const char *program, int code) { + printf("Usage: %s [--everyone]\n" + "\n" + "Creates the named pipe %ls and reads messages. Prints each\n" + "message to stdout. By default, only the current user can send messages.\n" + "Pass --everyone to let anyone send a message.\n" + "\n" + "Use the WINPTY_DEBUG environment variable to enable winpty trace output.\n" + "(e.g. WINPTY_DEBUG=trace for the default trace output.) Set WINPTYDBG=1\n" + "to enable trace with older winpty versions.\n", + program, kPipeName); + exit(code); +} + +int main(int argc, char *argv[]) { + bool everyone = false; + for (int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + if (arg == "--everyone") { + everyone = true; + } else if (arg == "-h" || arg == "--help") { + usage(argv[0], 0); + } else { + usage(argv[0], 1); + } + } + + SecurityDescriptor sd; + PSECURITY_ATTRIBUTES psa = nullptr; + SECURITY_ATTRIBUTES sa = {}; + if (everyone) { + try { + sd = createPipeSecurityDescriptorOwnerFullControlEveryoneWrite(); + } catch (const WinptyException &e) { + fprintf(stderr, + "error creating security descriptor: %ls\n", e.what()); + exit(1); + } + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = sd.get(); + psa = &sa; + } + + HANDLE serverPipe = CreateNamedPipeW( + kPipeName, + /*dwOpenMode=*/PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE, + /*dwPipeMode=*/PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | + rejectRemoteClientsPipeFlag(), + /*nMaxInstances=*/1, + /*nOutBufferSize=*/MSG_SIZE, + /*nInBufferSize=*/MSG_SIZE, + /*nDefaultTimeOut=*/10 * 1000, + psa); + + if (serverPipe == INVALID_HANDLE_VALUE) { + fprintf(stderr, "error: could not create %ls pipe: error %u\n", + kPipeName, static_cast(GetLastError())); + exit(1); + } + + char msgBuffer[MSG_SIZE + 1]; + + while (true) { + if (!ConnectNamedPipe(serverPipe, nullptr)) { + fprintf(stderr, "error: ConnectNamedPipe failed\n"); + fflush(stderr); + exit(1); + } + DWORD bytesRead = 0; + if (!ReadFile(serverPipe, msgBuffer, MSG_SIZE, &bytesRead, nullptr)) { + fprintf(stderr, "error: ReadFile on pipe failed\n"); + fflush(stderr); + DisconnectNamedPipe(serverPipe); + continue; + } + msgBuffer[bytesRead] = '\n'; + fwrite(msgBuffer, 1, bytesRead + 1, stdout); + fflush(stdout); + + DWORD bytesWritten = 0; + WriteFile(serverPipe, "OK", 2, &bytesWritten, nullptr); + DisconnectNamedPipe(serverPipe); + } +} diff --git a/src/libs/3rdparty/winpty/src/debugserver/subdir.mk b/src/libs/3rdparty/winpty/src/debugserver/subdir.mk new file mode 100644 index 0000000000..beed1bd597 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/debugserver/subdir.mk @@ -0,0 +1,41 @@ +# Copyright (c) 2015 Ryan Prichard +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +ALL_TARGETS += build/winpty-debugserver.exe + +$(eval $(call def_mingw_target,debugserver,)) + +DEBUGSERVER_OBJECTS = \ + build/debugserver/debugserver/DebugServer.o \ + build/debugserver/shared/DebugClient.o \ + build/debugserver/shared/OwnedHandle.o \ + build/debugserver/shared/StringUtil.o \ + build/debugserver/shared/WindowsSecurity.o \ + build/debugserver/shared/WindowsVersion.o \ + build/debugserver/shared/WinptyAssert.o \ + build/debugserver/shared/WinptyException.o + +build/debugserver/shared/WindowsVersion.o : build/gen/GenVersion.h + +build/winpty-debugserver.exe : $(DEBUGSERVER_OBJECTS) + $(info Linking $@) + @$(MINGW_CXX) $(MINGW_LDFLAGS) -o $@ $^ + +-include $(DEBUGSERVER_OBJECTS:.o=.d) diff --git a/src/libs/3rdparty/winpty/src/include/winpty.h b/src/libs/3rdparty/winpty/src/include/winpty.h new file mode 100644 index 0000000000..fdfe4bca21 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/include/winpty.h @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2011-2016 Ryan Prichard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef WINPTY_H +#define WINPTY_H + +#include + +#include "winpty_constants.h" + +/* On 32-bit Windows, winpty functions have the default __cdecl (not __stdcall) + * calling convention. (64-bit Windows has only a single calling convention.) + * When compiled with __declspec(dllexport), with either MinGW or MSVC, the + * winpty functions are unadorned--no underscore prefix or '@nn' suffix--so + * GetProcAddress can be used easily. */ +#ifdef COMPILING_WINPTY_DLL +#define WINPTY_API __declspec(dllexport) +#else +#define WINPTY_API __declspec(dllimport) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* The winpty API uses wide characters, instead of UTF-8, to avoid conversion + * complications related to surrogates. Windows generally tolerates unpaired + * surrogates in text, which makes conversion to and from UTF-8 ambiguous and + * complicated. (There are different UTF-8 variants that deal with UTF-16 + * surrogates differently.) */ + + + +/***************************************************************************** + * Error handling. */ + +/* All the APIs have an optional winpty_error_t output parameter. If a + * non-NULL argument is specified, then either the API writes NULL to the + * value (on success) or writes a newly allocated winpty_error_t object. The + * object must be freed using winpty_error_free. */ + +/* An error object. */ +typedef struct winpty_error_s winpty_error_t; +typedef winpty_error_t *winpty_error_ptr_t; + +/* An error code -- one of WINPTY_ERROR_xxx. */ +typedef DWORD winpty_result_t; + +/* Gets the error code from the error object. */ +WINPTY_API winpty_result_t winpty_error_code(winpty_error_ptr_t err); + +/* Returns a textual representation of the error. The string is freed when + * the error is freed. */ +WINPTY_API LPCWSTR winpty_error_msg(winpty_error_ptr_t err); + +/* Free the error object. Every error returned from the winpty API must be + * freed. */ +WINPTY_API void winpty_error_free(winpty_error_ptr_t err); + + + +/***************************************************************************** + * Configuration of a new agent. */ + +/* The winpty_config_t object is not thread-safe. */ +typedef struct winpty_config_s winpty_config_t; + +/* Allocate a winpty_config_t value. Returns NULL on error. There are no + * required settings -- the object may immediately be used. agentFlags is a + * set of zero or more WINPTY_FLAG_xxx values. An unrecognized flag results + * in an assertion failure. */ +WINPTY_API winpty_config_t * +winpty_config_new(UINT64 agentFlags, winpty_error_ptr_t *err /*OPTIONAL*/); + +/* Free the cfg object after passing it to winpty_open. */ +WINPTY_API void winpty_config_free(winpty_config_t *cfg); + +WINPTY_API void +winpty_config_set_initial_size(winpty_config_t *cfg, int cols, int rows); + +/* Set the mouse mode to one of the WINPTY_MOUSE_MODE_xxx constants. */ +WINPTY_API void +winpty_config_set_mouse_mode(winpty_config_t *cfg, int mouseMode); + +/* Amount of time to wait for the agent to startup and to wait for any given + * agent RPC request. Must be greater than 0. Can be INFINITE. */ +WINPTY_API void +winpty_config_set_agent_timeout(winpty_config_t *cfg, DWORD timeoutMs); + + + +/***************************************************************************** + * Start the agent. */ + +/* The winpty_t object is thread-safe. */ +typedef struct winpty_s winpty_t; + +/* Starts the agent. Returns NULL on error. This process will connect to the + * agent over a control pipe, and the agent will open data pipes (e.g. CONIN + * and CONOUT). */ +WINPTY_API winpty_t * +winpty_open(const winpty_config_t *cfg, + winpty_error_ptr_t *err /*OPTIONAL*/); + +/* A handle to the agent process. This value is valid for the lifetime of the + * winpty_t object. Do not close it. */ +WINPTY_API HANDLE winpty_agent_process(winpty_t *wp); + + + +/***************************************************************************** + * I/O pipes. */ + +/* Returns the names of named pipes used for terminal I/O. Each input or + * output direction uses a different half-duplex pipe. The agent creates + * these pipes, and the client can connect to them using ordinary I/O methods. + * The strings are freed when the winpty_t object is freed. + * + * winpty_conerr_name returns NULL unless WINPTY_FLAG_CONERR is specified. + * + * N.B.: CreateFile does not block when connecting to a local server pipe. If + * the server pipe does not exist or is already connected, then it fails + * instantly. */ +WINPTY_API LPCWSTR winpty_conin_name(winpty_t *wp); +WINPTY_API LPCWSTR winpty_conout_name(winpty_t *wp); +WINPTY_API LPCWSTR winpty_conerr_name(winpty_t *wp); + + + +/***************************************************************************** + * winpty agent RPC call: process creation. */ + +/* The winpty_spawn_config_t object is not thread-safe. */ +typedef struct winpty_spawn_config_s winpty_spawn_config_t; + +/* winpty_spawn_config strings do not need to live as long as the config + * object. They are copied. Returns NULL on error. spawnFlags is a set of + * zero or more WINPTY_SPAWN_FLAG_xxx values. An unrecognized flag results in + * an assertion failure. + * + * env is a a pointer to an environment block like that passed to + * CreateProcess--a contiguous array of NUL-terminated "VAR=VAL" strings + * followed by a final NUL terminator. + * + * N.B.: If you want to gather all of the child's output, you may want the + * WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN flag. + */ +WINPTY_API winpty_spawn_config_t * +winpty_spawn_config_new(UINT64 spawnFlags, + LPCWSTR appname /*OPTIONAL*/, + LPCWSTR cmdline /*OPTIONAL*/, + LPCWSTR cwd /*OPTIONAL*/, + LPCWSTR env /*OPTIONAL*/, + winpty_error_ptr_t *err /*OPTIONAL*/); + +/* Free the cfg object after passing it to winpty_spawn. */ +WINPTY_API void winpty_spawn_config_free(winpty_spawn_config_t *cfg); + +/* + * Spawns the new process. + * + * The function initializes all output parameters to zero or NULL. + * + * On success, the function returns TRUE. For each of process_handle and + * thread_handle that is non-NULL, the HANDLE returned from CreateProcess is + * duplicated from the agent and returned to the winpty client. The client is + * responsible for closing these HANDLES. + * + * On failure, the function returns FALSE, and if err is non-NULL, then *err + * is set to an error object. + * + * If the agent's CreateProcess call failed, then *create_process_error is set + * to GetLastError(), and the WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED error + * is returned. + * + * winpty_spawn can only be called once per winpty_t object. If it is called + * before the output data pipe(s) is/are connected, then collected output is + * buffered until the pipes are connected, rather than being discarded. + * + * N.B.: GetProcessId works even if the process has exited. The PID is not + * recycled until the NT process object is freed. + * (https://blogs.msdn.microsoft.com/oldnewthing/20110107-00/?p=11803) + */ +WINPTY_API BOOL +winpty_spawn(winpty_t *wp, + const winpty_spawn_config_t *cfg, + HANDLE *process_handle /*OPTIONAL*/, + HANDLE *thread_handle /*OPTIONAL*/, + DWORD *create_process_error /*OPTIONAL*/, + winpty_error_ptr_t *err /*OPTIONAL*/); + + + +/***************************************************************************** + * winpty agent RPC calls: everything else */ + +/* Change the size of the Windows console window. */ +WINPTY_API BOOL +winpty_set_size(winpty_t *wp, int cols, int rows, + winpty_error_ptr_t *err /*OPTIONAL*/); + +/* Gets a list of processes attached to the console. */ +WINPTY_API int +winpty_get_console_process_list(winpty_t *wp, int *processList, const int processCount, + winpty_error_ptr_t *err /*OPTIONAL*/); + +/* Frees the winpty_t object and the OS resources contained in it. This + * call breaks the connection with the agent, which should then close its + * console, terminating the processes attached to it. + * + * This function must not be called if any other threads are using the + * winpty_t object. Undefined behavior results. */ +WINPTY_API void winpty_free(winpty_t *wp); + + + +/****************************************************************************/ + +#ifdef __cplusplus +} +#endif + +#endif /* WINPTY_H */ diff --git a/src/libs/3rdparty/winpty/src/include/winpty_constants.h b/src/libs/3rdparty/winpty/src/include/winpty_constants.h new file mode 100644 index 0000000000..11e34cf171 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/include/winpty_constants.h @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2016 Ryan Prichard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef WINPTY_CONSTANTS_H +#define WINPTY_CONSTANTS_H + +/* + * You may want to include winpty.h instead, which includes this header. + * + * This file is split out from winpty.h so that the agent can access the + * winpty flags without also declaring the libwinpty APIs. + */ + +/***************************************************************************** + * Error codes. */ + +#define WINPTY_ERROR_SUCCESS 0 +#define WINPTY_ERROR_OUT_OF_MEMORY 1 +#define WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED 2 +#define WINPTY_ERROR_LOST_CONNECTION 3 +#define WINPTY_ERROR_AGENT_EXE_MISSING 4 +#define WINPTY_ERROR_UNSPECIFIED 5 +#define WINPTY_ERROR_AGENT_DIED 6 +#define WINPTY_ERROR_AGENT_TIMEOUT 7 +#define WINPTY_ERROR_AGENT_CREATION_FAILED 8 + + + +/***************************************************************************** + * Configuration of a new agent. */ + +/* Create a new screen buffer (connected to the "conerr" terminal pipe) and + * pass it to child processes as the STDERR handle. This flag also prevents + * the agent from reopening CONOUT$ when it polls -- regardless of whether the + * active screen buffer changes, winpty continues to monitor the original + * primary screen buffer. */ +#define WINPTY_FLAG_CONERR 0x1ull + +/* Don't output escape sequences. */ +#define WINPTY_FLAG_PLAIN_OUTPUT 0x2ull + +/* Do output color escape sequences. These escapes are output by default, but + * are suppressed with WINPTY_FLAG_PLAIN_OUTPUT. Use this flag to reenable + * them. */ +#define WINPTY_FLAG_COLOR_ESCAPES 0x4ull + +/* On XP and Vista, winpty needs to put the hidden console on a desktop in a + * service window station so that its polling does not interfere with other + * (visible) console windows. To create this desktop, it must change the + * process' window station (i.e. SetProcessWindowStation) for the duration of + * the winpty_open call. In theory, this change could interfere with the + * winpty client (e.g. other threads, spawning children), so winpty by default + * spawns a special agent process to create the hidden desktop. Spawning + * processes on Windows is slow, though, so if + * WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION is set, winpty changes this + * process' window station instead. + * See https://github.com/rprichard/winpty/issues/58. */ +#define WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION 0x8ull + +#define WINPTY_FLAG_MASK (0ull \ + | WINPTY_FLAG_CONERR \ + | WINPTY_FLAG_PLAIN_OUTPUT \ + | WINPTY_FLAG_COLOR_ESCAPES \ + | WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION \ +) + +/* QuickEdit mode is initially disabled, and the agent does not send mouse + * mode sequences to the terminal. If it receives mouse input, though, it + * still writes MOUSE_EVENT_RECORD values into CONIN. */ +#define WINPTY_MOUSE_MODE_NONE 0 + +/* QuickEdit mode is initially enabled. As CONIN enters or leaves mouse + * input mode (i.e. where ENABLE_MOUSE_INPUT is on and ENABLE_QUICK_EDIT_MODE + * is off), the agent enables or disables mouse input on the terminal. + * + * This is the default mode. */ +#define WINPTY_MOUSE_MODE_AUTO 1 + +/* QuickEdit mode is initially disabled, and the agent enables the terminal's + * mouse input mode. It does not disable terminal mouse mode (until exit). */ +#define WINPTY_MOUSE_MODE_FORCE 2 + + + +/***************************************************************************** + * winpty agent RPC call: process creation. */ + +/* If the spawn is marked "auto-shutdown", then the agent shuts down console + * output once the process exits. The agent stops polling for new console + * output, and once all pending data has been written to the output pipe, the + * agent closes the pipe. (At that point, the pipe may still have data in it, + * which the client may read. Once all the data has been read, further reads + * return EOF.) */ +#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ull + +/* After the agent shuts down output, and after all output has been written + * into the pipe(s), exit the agent by closing the console. If there any + * surviving processes still attached to the console, they are killed. + * + * Note: With this flag, an RPC call (e.g. winpty_set_size) issued after the + * agent exits will fail with an I/O or dead-agent error. */ +#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull + +/* All the spawn flags. */ +#define WINPTY_SPAWN_FLAG_MASK (0ull \ + | WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN \ + | WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN \ +) + + + +#endif /* WINPTY_CONSTANTS_H */ diff --git a/src/libs/3rdparty/winpty/src/libwinpty/AgentLocation.cc b/src/libs/3rdparty/winpty/src/libwinpty/AgentLocation.cc new file mode 100644 index 0000000000..82d00b2da2 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/libwinpty/AgentLocation.cc @@ -0,0 +1,75 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "AgentLocation.h" + +#include + +#include + +#include "../shared/WinptyAssert.h" + +#include "LibWinptyException.h" + +#define AGENT_EXE L"winpty-agent.exe" + +static HMODULE getCurrentModule() { + HMODULE module; + if (!GetModuleHandleExW( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast(getCurrentModule), + &module)) { + ASSERT(false && "GetModuleHandleEx failed"); + } + return module; +} + +static std::wstring getModuleFileName(HMODULE module) { + const int bufsize = 4096; + wchar_t path[bufsize]; + int size = GetModuleFileNameW(module, path, bufsize); + ASSERT(size != 0 && size != bufsize); + return std::wstring(path); +} + +static std::wstring dirname(const std::wstring &path) { + std::wstring::size_type pos = path.find_last_of(L"\\/"); + if (pos == std::wstring::npos) { + return L""; + } else { + return path.substr(0, pos); + } +} + +static bool pathExists(const std::wstring &path) { + return GetFileAttributesW(path.c_str()) != 0xFFFFFFFF; +} + +std::wstring findAgentProgram() { + std::wstring progDir = dirname(getModuleFileName(getCurrentModule())); + std::wstring ret = progDir + (L"\\" AGENT_EXE); + if (!pathExists(ret)) { + throw LibWinptyException( + WINPTY_ERROR_AGENT_EXE_MISSING, + (L"agent executable does not exist: '" + ret + L"'").c_str()); + } + return ret; +} diff --git a/src/libs/3rdparty/winpty/src/libwinpty/AgentLocation.h b/src/libs/3rdparty/winpty/src/libwinpty/AgentLocation.h new file mode 100644 index 0000000000..a96b854cd2 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/libwinpty/AgentLocation.h @@ -0,0 +1,28 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef LIBWINPTY_AGENT_LOCATION_H +#define LIBWINPTY_AGENT_LOCATION_H + +#include + +std::wstring findAgentProgram(); + +#endif // LIBWINPTY_AGENT_LOCATION_H diff --git a/src/libs/3rdparty/winpty/src/libwinpty/LibWinptyException.h b/src/libs/3rdparty/winpty/src/libwinpty/LibWinptyException.h new file mode 100644 index 0000000000..2274798d23 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/libwinpty/LibWinptyException.h @@ -0,0 +1,54 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef LIB_WINPTY_EXCEPTION_H +#define LIB_WINPTY_EXCEPTION_H + +#include "../include/winpty.h" + +#include "../shared/WinptyException.h" + +#include +#include + +class LibWinptyException : public WinptyException { +public: + LibWinptyException(winpty_result_t code, const wchar_t *what) : + m_code(code), m_what(std::make_shared(what)) {} + + winpty_result_t code() const WINPTY_NOEXCEPT { + return m_code; + } + + const wchar_t *what() const WINPTY_NOEXCEPT override { + return m_what->c_str(); + } + + std::shared_ptr whatSharedStr() const WINPTY_NOEXCEPT { + return m_what; + } + +private: + winpty_result_t m_code; + // Using a shared_ptr ensures that copying the object raises no exception. + std::shared_ptr m_what; +}; + +#endif // LIB_WINPTY_EXCEPTION_H diff --git a/src/libs/3rdparty/winpty/src/libwinpty/WinptyInternal.h b/src/libs/3rdparty/winpty/src/libwinpty/WinptyInternal.h new file mode 100644 index 0000000000..93e992d5c5 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/libwinpty/WinptyInternal.h @@ -0,0 +1,72 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef LIBWINPTY_WINPTY_INTERNAL_H +#define LIBWINPTY_WINPTY_INTERNAL_H + +#include +#include + +#include "../include/winpty.h" + +#include "../shared/Mutex.h" +#include "../shared/OwnedHandle.h" + +// The structures in this header are not intended to be accessed directly by +// client programs. + +struct winpty_error_s { + winpty_result_t code; + const wchar_t *msgStatic; + // Use a pointer to a std::shared_ptr so that the struct remains simple + // enough to statically initialize, for the benefit of static error + // objects like kOutOfMemory. + std::shared_ptr *msgDynamic; +}; + +struct winpty_config_s { + uint64_t flags = 0; + int cols = 80; + int rows = 25; + int mouseMode = WINPTY_MOUSE_MODE_AUTO; + DWORD timeoutMs = 30000; +}; + +struct winpty_s { + Mutex mutex; + OwnedHandle agentProcess; + OwnedHandle controlPipe; + DWORD agentTimeoutMs = 0; + OwnedHandle ioEvent; + std::wstring spawnDesktopName; + std::wstring coninPipeName; + std::wstring conoutPipeName; + std::wstring conerrPipeName; +}; + +struct winpty_spawn_config_s { + uint64_t winptyFlags = 0; + std::wstring appname; + std::wstring cmdline; + std::wstring cwd; + std::wstring env; +}; + +#endif // LIBWINPTY_WINPTY_INTERNAL_H diff --git a/src/libs/3rdparty/winpty/src/libwinpty/subdir.mk b/src/libs/3rdparty/winpty/src/libwinpty/subdir.mk new file mode 100644 index 0000000000..ba32bad6e6 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/libwinpty/subdir.mk @@ -0,0 +1,46 @@ +# Copyright (c) 2011-2015 Ryan Prichard +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +ALL_TARGETS += build/winpty.dll + +$(eval $(call def_mingw_target,libwinpty,-DCOMPILING_WINPTY_DLL)) + +LIBWINPTY_OBJECTS = \ + build/libwinpty/libwinpty/AgentLocation.o \ + build/libwinpty/libwinpty/winpty.o \ + build/libwinpty/shared/BackgroundDesktop.o \ + build/libwinpty/shared/Buffer.o \ + build/libwinpty/shared/DebugClient.o \ + build/libwinpty/shared/GenRandom.o \ + build/libwinpty/shared/OwnedHandle.o \ + build/libwinpty/shared/StringUtil.o \ + build/libwinpty/shared/WindowsSecurity.o \ + build/libwinpty/shared/WindowsVersion.o \ + build/libwinpty/shared/WinptyAssert.o \ + build/libwinpty/shared/WinptyException.o \ + build/libwinpty/shared/WinptyVersion.o + +build/libwinpty/shared/WinptyVersion.o : build/gen/GenVersion.h + +build/winpty.dll : $(LIBWINPTY_OBJECTS) + $(info Linking $@) + @$(MINGW_CXX) $(MINGW_LDFLAGS) -shared -o $@ $^ -Wl,--out-implib,build/winpty.lib + +-include $(LIBWINPTY_OBJECTS:.o=.d) diff --git a/src/libs/3rdparty/winpty/src/libwinpty/winpty.cc b/src/libs/3rdparty/winpty/src/libwinpty/winpty.cc new file mode 100644 index 0000000000..3d977498ef --- /dev/null +++ b/src/libs/3rdparty/winpty/src/libwinpty/winpty.cc @@ -0,0 +1,970 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include + +#include +#include +#include + +#include +#include +#include + +#include "../include/winpty.h" + +#include "../shared/AgentMsg.h" +#include "../shared/BackgroundDesktop.h" +#include "../shared/Buffer.h" +#include "../shared/DebugClient.h" +#include "../shared/GenRandom.h" +#include "../shared/OwnedHandle.h" +#include "../shared/StringBuilder.h" +#include "../shared/StringUtil.h" +#include "../shared/WindowsSecurity.h" +#include "../shared/WindowsVersion.h" +#include "../shared/WinptyAssert.h" +#include "../shared/WinptyException.h" +#include "../shared/WinptyVersion.h" + +#include "AgentLocation.h" +#include "LibWinptyException.h" +#include "WinptyInternal.h" + + + +/***************************************************************************** + * Error handling -- translate C++ exceptions to an optional error object + * output and log the result. */ + +static const winpty_error_s kOutOfMemory = { + WINPTY_ERROR_OUT_OF_MEMORY, + L"Out of memory", + nullptr +}; + +static const winpty_error_s kBadRpcPacket = { + WINPTY_ERROR_UNSPECIFIED, + L"Bad RPC packet", + nullptr +}; + +static const winpty_error_s kUncaughtException = { + WINPTY_ERROR_UNSPECIFIED, + L"Uncaught C++ exception", + nullptr +}; + +/* Gets the error code from the error object. */ +WINPTY_API winpty_result_t winpty_error_code(winpty_error_ptr_t err) { + return err != nullptr ? err->code : WINPTY_ERROR_SUCCESS; +} + +/* Returns a textual representation of the error. The string is freed when + * the error is freed. */ +WINPTY_API LPCWSTR winpty_error_msg(winpty_error_ptr_t err) { + if (err != nullptr) { + if (err->msgStatic != nullptr) { + return err->msgStatic; + } else { + ASSERT(err->msgDynamic != nullptr); + std::wstring *msgPtr = err->msgDynamic->get(); + ASSERT(msgPtr != nullptr); + return msgPtr->c_str(); + } + } else { + return L"Success"; + } +} + +/* Free the error object. Every error returned from the winpty API must be + * freed. */ +WINPTY_API void winpty_error_free(winpty_error_ptr_t err) { + if (err != nullptr && err->msgDynamic != nullptr) { + delete err->msgDynamic; + delete err; + } +} + +static void translateException(winpty_error_ptr_t *&err) { + winpty_error_ptr_t ret = nullptr; + try { + try { + throw; + } catch (const ReadBuffer::DecodeError&) { + ret = const_cast(&kBadRpcPacket); + } catch (const LibWinptyException &e) { + std::unique_ptr obj(new winpty_error_t); + obj->code = e.code(); + obj->msgStatic = nullptr; + obj->msgDynamic = + new std::shared_ptr(e.whatSharedStr()); + ret = obj.release(); + } catch (const WinptyException &e) { + std::unique_ptr obj(new winpty_error_t); + std::shared_ptr msg(new std::wstring(e.what())); + obj->code = WINPTY_ERROR_UNSPECIFIED; + obj->msgStatic = nullptr; + obj->msgDynamic = new std::shared_ptr(msg); + ret = obj.release(); + } + } catch (const std::bad_alloc&) { + ret = const_cast(&kOutOfMemory); + } catch (...) { + ret = const_cast(&kUncaughtException); + } + trace("libwinpty error: code=%u msg='%s'", + static_cast(ret->code), + utf8FromWide(winpty_error_msg(ret)).c_str()); + if (err != nullptr) { + *err = ret; + } else { + winpty_error_free(ret); + } +} + +#define API_TRY \ + if (err != nullptr) { *err = nullptr; } \ + try + +#define API_CATCH(ret) \ + catch (...) { translateException(err); return (ret); } + + + +/***************************************************************************** + * Configuration of a new agent. */ + +WINPTY_API winpty_config_t * +winpty_config_new(UINT64 flags, winpty_error_ptr_t *err /*OPTIONAL*/) { + API_TRY { + ASSERT((flags & WINPTY_FLAG_MASK) == flags); + std::unique_ptr ret(new winpty_config_t); + ret->flags = flags; + return ret.release(); + } API_CATCH(nullptr) +} + +WINPTY_API void winpty_config_free(winpty_config_t *cfg) { + delete cfg; +} + +WINPTY_API void +winpty_config_set_initial_size(winpty_config_t *cfg, int cols, int rows) { + ASSERT(cfg != nullptr && cols > 0 && rows > 0); + cfg->cols = cols; + cfg->rows = rows; +} + +WINPTY_API void +winpty_config_set_mouse_mode(winpty_config_t *cfg, int mouseMode) { + ASSERT(cfg != nullptr && + mouseMode >= WINPTY_MOUSE_MODE_NONE && + mouseMode <= WINPTY_MOUSE_MODE_FORCE); + cfg->mouseMode = mouseMode; +} + +WINPTY_API void +winpty_config_set_agent_timeout(winpty_config_t *cfg, DWORD timeoutMs) { + ASSERT(cfg != nullptr && timeoutMs > 0); + cfg->timeoutMs = timeoutMs; +} + + + +/***************************************************************************** + * Agent I/O. */ + +namespace { + +// Once an I/O operation fails with ERROR_IO_PENDING, the caller *must* wait +// for it to complete, even after calling CancelIo on it! See +// https://blogs.msdn.microsoft.com/oldnewthing/20110202-00/?p=11613. This +// class enforces that requirement. +class PendingIo { + HANDLE m_file; + OVERLAPPED &m_over; + bool m_finished; +public: + // The file handle and OVERLAPPED object must live as long as the PendingIo + // object. + PendingIo(HANDLE file, OVERLAPPED &over) : + m_file(file), m_over(over), m_finished(false) {} + ~PendingIo() { + if (!m_finished) { + // We're not usually that interested in CancelIo's return value. + // In any case, we must not throw an exception in this dtor. + CancelIo(m_file); + waitForCompletion(); + } + } + std::tuple waitForCompletion(DWORD &actual) WINPTY_NOEXCEPT { + m_finished = true; + const BOOL success = + GetOverlappedResult(m_file, &m_over, &actual, TRUE); + return std::make_tuple(success, GetLastError()); + } + std::tuple waitForCompletion() WINPTY_NOEXCEPT { + DWORD actual = 0; + return waitForCompletion(actual); + } +}; + +} // anonymous namespace + +static void handlePendingIo(winpty_t &wp, OVERLAPPED &over, BOOL &success, + DWORD &lastError, DWORD &actual) { + if (!success && lastError == ERROR_IO_PENDING) { + PendingIo io(wp.controlPipe.get(), over); + const HANDLE waitHandles[2] = { wp.ioEvent.get(), + wp.agentProcess.get() }; + DWORD waitRet = WaitForMultipleObjects( + 2, waitHandles, FALSE, wp.agentTimeoutMs); + if (waitRet != WAIT_OBJECT_0) { + // The I/O is still pending. Cancel it, close the I/O event, and + // throw an exception. + if (waitRet == WAIT_OBJECT_0 + 1) { + throw LibWinptyException(WINPTY_ERROR_AGENT_DIED, L"agent died"); + } else if (waitRet == WAIT_TIMEOUT) { + throw LibWinptyException(WINPTY_ERROR_AGENT_TIMEOUT, + L"agent timed out"); + } else if (waitRet == WAIT_FAILED) { + throwWindowsError(L"WaitForMultipleObjects failed"); + } else { + ASSERT(false && + "unexpected WaitForMultipleObjects return value"); + } + } + std::tie(success, lastError) = io.waitForCompletion(actual); + } +} + +static void handlePendingIo(winpty_t &wp, OVERLAPPED &over, BOOL &success, + DWORD &lastError) { + DWORD actual = 0; + handlePendingIo(wp, over, success, lastError, actual); +} + +static void handleReadWriteErrors(winpty_t &wp, BOOL success, DWORD lastError, + const wchar_t *genericErrMsg) { + if (!success) { + // If the pipe connection is broken after it's been connected, then + // later I/O operations fail with ERROR_BROKEN_PIPE (reads) or + // ERROR_NO_DATA (writes). With Wine, they may also fail with + // ERROR_PIPE_NOT_CONNECTED. See this gist[1]. + // + // [1] https://gist.github.com/rprichard/8dd8ca134b39534b7da2733994aa07ba + if (lastError == ERROR_BROKEN_PIPE || lastError == ERROR_NO_DATA || + lastError == ERROR_PIPE_NOT_CONNECTED) { + throw LibWinptyException(WINPTY_ERROR_LOST_CONNECTION, + L"lost connection to agent"); + } else { + throwWindowsError(genericErrMsg, lastError); + } + } +} + +// Calls ConnectNamedPipe to wait until the agent connects to the control pipe. +static void +connectControlPipe(winpty_t &wp) { + OVERLAPPED over = {}; + over.hEvent = wp.ioEvent.get(); + BOOL success = ConnectNamedPipe(wp.controlPipe.get(), &over); + DWORD lastError = GetLastError(); + handlePendingIo(wp, over, success, lastError); + if (!success && lastError == ERROR_PIPE_CONNECTED) { + success = TRUE; + } + if (!success) { + throwWindowsError(L"ConnectNamedPipe failed", lastError); + } +} + +static void writeData(winpty_t &wp, const void *data, size_t amount) { + // Perform a single pipe write. + DWORD actual = 0; + OVERLAPPED over = {}; + over.hEvent = wp.ioEvent.get(); + BOOL success = WriteFile(wp.controlPipe.get(), data, amount, + &actual, &over); + DWORD lastError = GetLastError(); + if (!success) { + handlePendingIo(wp, over, success, lastError, actual); + handleReadWriteErrors(wp, success, lastError, L"WriteFile failed"); + ASSERT(success); + } + // TODO: Can a partial write actually happen somehow? + ASSERT(actual == amount && "WriteFile wrote fewer bytes than requested"); +} + +static inline WriteBuffer newPacket() { + WriteBuffer packet; + packet.putRawValue(0); // Reserve space for size. + return packet; +} + +static void writePacket(winpty_t &wp, WriteBuffer &packet) { + const auto &buf = packet.buf(); + packet.replaceRawValue(0, buf.size()); + writeData(wp, buf.data(), buf.size()); +} + +static size_t readData(winpty_t &wp, void *data, size_t amount) { + DWORD actual = 0; + OVERLAPPED over = {}; + over.hEvent = wp.ioEvent.get(); + BOOL success = ReadFile(wp.controlPipe.get(), data, amount, + &actual, &over); + DWORD lastError = GetLastError(); + if (!success) { + handlePendingIo(wp, over, success, lastError, actual); + handleReadWriteErrors(wp, success, lastError, L"ReadFile failed"); + } + return actual; +} + +static void readAll(winpty_t &wp, void *data, size_t amount) { + while (amount > 0) { + const size_t chunk = readData(wp, data, amount); + ASSERT(chunk <= amount && "readData result is larger than amount"); + data = reinterpret_cast(data) + chunk; + amount -= chunk; + } +} + +static uint64_t readUInt64(winpty_t &wp) { + uint64_t ret = 0; + readAll(wp, &ret, sizeof(ret)); + return ret; +} + +// Returns a reply packet's payload. +static ReadBuffer readPacket(winpty_t &wp) { + const uint64_t packetSize = readUInt64(wp); + if (packetSize < sizeof(packetSize) || packetSize > SIZE_MAX) { + throwWinptyException(L"Agent RPC error: invalid packet size"); + } + const size_t payloadSize = packetSize - sizeof(packetSize); + std::vector bytes(payloadSize); + readAll(wp, bytes.data(), bytes.size()); + return ReadBuffer(std::move(bytes)); +} + +static OwnedHandle createControlPipe(const std::wstring &name) { + const auto sd = createPipeSecurityDescriptorOwnerFullControl(); + if (!sd) { + throwWinptyException( + L"could not create the control pipe's SECURITY_DESCRIPTOR"); + } + SECURITY_ATTRIBUTES sa = {}; + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = sd.get(); + HANDLE ret = CreateNamedPipeW(name.c_str(), + /*dwOpenMode=*/ + PIPE_ACCESS_DUPLEX | + FILE_FLAG_FIRST_PIPE_INSTANCE | + FILE_FLAG_OVERLAPPED, + /*dwPipeMode=*/rejectRemoteClientsPipeFlag(), + /*nMaxInstances=*/1, + /*nOutBufferSize=*/8192, + /*nInBufferSize=*/256, + /*nDefaultTimeOut=*/30000, + &sa); + if (ret == INVALID_HANDLE_VALUE) { + throwWindowsError(L"CreateNamedPipeW failed"); + } + return OwnedHandle(ret); +} + + + +/***************************************************************************** + * Start the agent. */ + +static OwnedHandle createEvent() { + // manual reset, initially unset + HANDLE h = CreateEventW(nullptr, TRUE, FALSE, nullptr); + if (h == nullptr) { + throwWindowsError(L"CreateEventW failed"); + } + return OwnedHandle(h); +} + +// For debugging purposes, provide a way to keep the console on the main window +// station, visible. +static bool shouldShowConsoleWindow() { + char buf[32]; + return GetEnvironmentVariableA("WINPTY_SHOW_CONSOLE", buf, sizeof(buf)) > 0; +} + +static bool shouldCreateBackgroundDesktop(bool &createUsingAgent) { + // Prior to Windows 7, winpty's repeated selection-deselection loop + // prevented the user from interacting with their *visible* console + // windows, unless we placed the console onto a background desktop. + // The SetProcessWindowStation call interferes with the clipboard and + // isn't thread-safe, though[1]. The call should perhaps occur in a + // special agent subprocess. Spawning a process in a background desktop + // also breaks ConEmu, but marking the process SW_HIDE seems to correct + // that[2]. + // + // Windows 7 moved a lot of console handling out of csrss.exe and into + // a per-console conhost.exe process, which may explain why it isn't + // affected. + // + // This is a somewhat risky change, so there are low-level flags to + // assist in debugging if there are issues. + // + // [1] https://github.com/rprichard/winpty/issues/58 + // [2] https://github.com/rprichard/winpty/issues/70 + bool ret = !shouldShowConsoleWindow() && !isAtLeastWindows7(); + const bool force = hasDebugFlag("force_desktop"); + const bool force_spawn = hasDebugFlag("force_desktop_spawn"); + const bool force_curproc = hasDebugFlag("force_desktop_curproc"); + const bool suppress = hasDebugFlag("no_desktop"); + if (force + force_spawn + force_curproc + suppress > 1) { + trace("error: Only one of force_desktop, force_desktop_spawn, " + "force_desktop_curproc, and no_desktop may be set"); + } else if (force) { + ret = true; + } else if (force_spawn) { + ret = true; + createUsingAgent = true; + } else if (force_curproc) { + ret = true; + createUsingAgent = false; + } else if (suppress) { + ret = false; + } + return ret; +} + +static bool shouldSpecifyHideFlag() { + const bool force = hasDebugFlag("force_sw_hide"); + const bool suppress = hasDebugFlag("no_sw_hide"); + bool ret = !shouldShowConsoleWindow(); + if (force && suppress) { + trace("error: Both the force_sw_hide and no_sw_hide flags are set"); + } else if (force) { + ret = true; + } else if (suppress) { + ret = false; + } + return ret; +} + +static OwnedHandle startAgentProcess( + const std::wstring &desktop, + const std::wstring &controlPipeName, + const std::wstring ¶ms, + DWORD creationFlags, + DWORD &agentPid) { + const std::wstring exePath = findAgentProgram(); + const std::wstring cmdline = + (WStringBuilder(256) + << L"\"" << exePath << L"\" " + << controlPipeName << L' ' + << params).str_moved(); + + auto cmdlineV = vectorWithNulFromString(cmdline); + auto desktopV = vectorWithNulFromString(desktop); + + // Start the agent. + STARTUPINFOW sui = {}; + sui.cb = sizeof(sui); + sui.lpDesktop = desktop.empty() ? nullptr : desktopV.data(); + + if (shouldSpecifyHideFlag()) { + sui.dwFlags |= STARTF_USESHOWWINDOW; + sui.wShowWindow = SW_HIDE; + } + PROCESS_INFORMATION pi = {}; + const BOOL success = + CreateProcessW(exePath.c_str(), + cmdlineV.data(), + nullptr, nullptr, + /*bInheritHandles=*/FALSE, + /*dwCreationFlags=*/creationFlags, + nullptr, nullptr, + &sui, &pi); + if (!success) { + const DWORD lastError = GetLastError(); + const auto errStr = + (WStringBuilder(256) + << L"winpty-agent CreateProcess failed: cmdline='" << cmdline + << L"' err=0x" << whexOfInt(lastError)).str_moved(); + throw LibWinptyException( + WINPTY_ERROR_AGENT_CREATION_FAILED, errStr.c_str()); + } + CloseHandle(pi.hThread); + TRACE("Created agent successfully, pid=%u, cmdline=%s", + static_cast(pi.dwProcessId), + utf8FromWide(cmdline).c_str()); + agentPid = pi.dwProcessId; + return OwnedHandle(pi.hProcess); +} + +static void verifyPipeClientPid(HANDLE serverPipe, DWORD agentPid) { + const auto client = getNamedPipeClientProcessId(serverPipe); + const auto success = std::get<0>(client); + const auto lastError = std::get<2>(client); + if (success == GetNamedPipeClientProcessId_Result::Success) { + const auto clientPid = std::get<1>(client); + if (clientPid != agentPid) { + WStringBuilder errMsg; + errMsg << L"Security check failed: pipe client pid (" << clientPid + << L") does not match agent pid (" << agentPid << L")"; + throwWinptyException(errMsg.c_str()); + } + } else if (success == GetNamedPipeClientProcessId_Result::UnsupportedOs) { + trace("Pipe client PID security check skipped: " + "GetNamedPipeClientProcessId unsupported on this OS version"); + } else { + throwWindowsError(L"GetNamedPipeClientProcessId failed", lastError); + } +} + +static std::unique_ptr +createAgentSession(const winpty_config_t *cfg, + const std::wstring &desktop, + const std::wstring ¶ms, + DWORD creationFlags) { + std::unique_ptr wp(new winpty_t); + wp->agentTimeoutMs = cfg->timeoutMs; + wp->ioEvent = createEvent(); + + // Create control server pipe. + const auto pipeName = + L"\\\\.\\pipe\\winpty-control-" + GenRandom().uniqueName(); + wp->controlPipe = createControlPipe(pipeName); + + DWORD agentPid = 0; + wp->agentProcess = startAgentProcess( + desktop, pipeName, params, creationFlags, agentPid); + connectControlPipe(*wp.get()); + verifyPipeClientPid(wp->controlPipe.get(), agentPid); + + return std::move(wp); +} + +namespace { + +class AgentDesktop { +public: + virtual std::wstring name() = 0; + virtual ~AgentDesktop() {} +}; + +class AgentDesktopDirect : public AgentDesktop { +public: + AgentDesktopDirect(BackgroundDesktop &&desktop) : + m_desktop(std::move(desktop)) + { + } + std::wstring name() override { return m_desktop.desktopName(); } +private: + BackgroundDesktop m_desktop; +}; + +class AgentDesktopIndirect : public AgentDesktop { +public: + AgentDesktopIndirect(std::unique_ptr &&wp, + std::wstring &&desktopName) : + m_wp(std::move(wp)), + m_desktopName(std::move(desktopName)) + { + } + std::wstring name() override { return m_desktopName; } +private: + std::unique_ptr m_wp; + std::wstring m_desktopName; +}; + +} // anonymous namespace + +std::unique_ptr +setupBackgroundDesktop(const winpty_config_t *cfg) { + bool useDesktopAgent = + !(cfg->flags & WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION); + const bool useDesktop = shouldCreateBackgroundDesktop(useDesktopAgent); + + if (!useDesktop) { + return std::unique_ptr(); + } + + if (useDesktopAgent) { + auto wp = createAgentSession( + cfg, std::wstring(), L"--create-desktop", DETACHED_PROCESS); + + // Read the desktop name. + auto packet = readPacket(*wp.get()); + auto desktopName = packet.getWString(); + packet.assertEof(); + + if (desktopName.empty()) { + return std::unique_ptr(); + } else { + return std::unique_ptr( + new AgentDesktopIndirect(std::move(wp), + std::move(desktopName))); + } + } else { + try { + BackgroundDesktop desktop; + return std::unique_ptr(new AgentDesktopDirect( + std::move(desktop))); + } catch (const WinptyException &e) { + trace("Error: failed to create background desktop, " + "using original desktop instead: %s", + utf8FromWide(e.what()).c_str()); + return std::unique_ptr(); + } + } +} + +WINPTY_API winpty_t * +winpty_open(const winpty_config_t *cfg, + winpty_error_ptr_t *err /*OPTIONAL*/) { + API_TRY { + ASSERT(cfg != nullptr); + dumpWindowsVersion(); + dumpVersionToTrace(); + + // Setup a background desktop for the agent. + auto desktop = setupBackgroundDesktop(cfg); + const auto desktopName = desktop ? desktop->name() : std::wstring(); + + // Start the primary agent session. + const auto params = + (WStringBuilder(128) + << cfg->flags << L' ' + << cfg->mouseMode << L' ' + << cfg->cols << L' ' + << cfg->rows).str_moved(); + auto wp = createAgentSession(cfg, desktopName, params, + CREATE_NEW_CONSOLE); + + // Close handles to the background desktop and restore the original + // window station. This must wait until we know the agent is running + // -- if we close these handles too soon, then the desktop and + // windowstation will be destroyed before the agent can connect with + // them. + // + // If we used a separate agent process to create the desktop, we + // disconnect from that process here, allowing it to exit. + desktop.reset(); + + // If we ran the agent process on a background desktop, then when we + // spawn a child process from the agent, it will need to be explicitly + // placed back onto the original desktop. + if (!desktopName.empty()) { + wp->spawnDesktopName = getCurrentDesktopName(); + } + + // Get the CONIN/CONOUT pipe names. + auto packet = readPacket(*wp.get()); + wp->coninPipeName = packet.getWString(); + wp->conoutPipeName = packet.getWString(); + if (cfg->flags & WINPTY_FLAG_CONERR) { + wp->conerrPipeName = packet.getWString(); + } + packet.assertEof(); + + return wp.release(); + } API_CATCH(nullptr) +} + +WINPTY_API HANDLE winpty_agent_process(winpty_t *wp) { + ASSERT(wp != nullptr); + return wp->agentProcess.get(); +} + + + +/***************************************************************************** + * I/O pipes. */ + +static const wchar_t *cstrFromWStringOrNull(const std::wstring &str) { + try { + return str.c_str(); + } catch (const std::bad_alloc&) { + return nullptr; + } +} + +WINPTY_API LPCWSTR winpty_conin_name(winpty_t *wp) { + ASSERT(wp != nullptr); + return cstrFromWStringOrNull(wp->coninPipeName); +} + +WINPTY_API LPCWSTR winpty_conout_name(winpty_t *wp) { + ASSERT(wp != nullptr); + return cstrFromWStringOrNull(wp->conoutPipeName); +} + +WINPTY_API LPCWSTR winpty_conerr_name(winpty_t *wp) { + ASSERT(wp != nullptr); + if (wp->conerrPipeName.empty()) { + return nullptr; + } else { + return cstrFromWStringOrNull(wp->conerrPipeName); + } +} + + + +/***************************************************************************** + * winpty agent RPC calls. */ + +namespace { + +// Close the control pipe if something goes wrong with the pipe communication, +// which could leave the control pipe in an inconsistent state. +class RpcOperation { +public: + RpcOperation(winpty_t &wp) : m_wp(wp) { + if (m_wp.controlPipe.get() == nullptr) { + throwWinptyException(L"Agent shutdown due to RPC failure"); + } + } + ~RpcOperation() { + if (!m_success) { + trace("~RpcOperation: Closing control pipe"); + m_wp.controlPipe.dispose(true); + } + } + void success() { m_success = true; } +private: + winpty_t &m_wp; + bool m_success = false; +}; + +} // anonymous namespace + + + +/***************************************************************************** + * winpty agent RPC call: process creation. */ + +// Return a std::wstring containing every character of the environment block. +// Typically, the block is non-empty, so the std::wstring returned ends with +// two NUL terminators. (These two terminators are counted in size(), so +// calling c_str() produces a triply-terminated string.) +static std::wstring wstringFromEnvBlock(const wchar_t *env) { + std::wstring envStr; + if (env != NULL) { + const wchar_t *p = env; + while (*p != L'\0') { + p += wcslen(p) + 1; + } + p++; + envStr.assign(env, p); + + // Assuming the environment was non-empty, envStr now ends with two NUL + // terminators. + // + // If the environment were empty, though, then envStr would only be + // singly terminated, but the MSDN documentation thinks an env block is + // always doubly-terminated, so add an extra NUL just in case it + // matters. + const auto envStrSz = envStr.size(); + if (envStrSz == 1) { + ASSERT(envStr[0] == L'\0'); + envStr.push_back(L'\0'); + } else { + ASSERT(envStrSz >= 3); + ASSERT(envStr[envStrSz - 3] != L'\0'); + ASSERT(envStr[envStrSz - 2] == L'\0'); + ASSERT(envStr[envStrSz - 1] == L'\0'); + } + } + return envStr; +} + +WINPTY_API winpty_spawn_config_t * +winpty_spawn_config_new(UINT64 winptyFlags, + LPCWSTR appname /*OPTIONAL*/, + LPCWSTR cmdline /*OPTIONAL*/, + LPCWSTR cwd /*OPTIONAL*/, + LPCWSTR env /*OPTIONAL*/, + winpty_error_ptr_t *err /*OPTIONAL*/) { + API_TRY { + ASSERT((winptyFlags & WINPTY_SPAWN_FLAG_MASK) == winptyFlags); + std::unique_ptr cfg(new winpty_spawn_config_t); + cfg->winptyFlags = winptyFlags; + if (appname != nullptr) { cfg->appname = appname; } + if (cmdline != nullptr) { cfg->cmdline = cmdline; } + if (cwd != nullptr) { cfg->cwd = cwd; } + if (env != nullptr) { cfg->env = wstringFromEnvBlock(env); } + return cfg.release(); + } API_CATCH(nullptr) +} + +WINPTY_API void winpty_spawn_config_free(winpty_spawn_config_t *cfg) { + delete cfg; +} + +// It's safe to truncate a handle from 64-bits to 32-bits, or to sign-extend it +// back to 64-bits. See the MSDN article, "Interprocess Communication Between +// 32-bit and 64-bit Applications". +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa384203.aspx +static inline HANDLE handleFromInt64(int64_t i) { + return reinterpret_cast(static_cast(i)); +} + +// Given a process and a handle in that process, duplicate the handle into the +// current process and close it in the originating process. +static inline OwnedHandle stealHandle(HANDLE process, HANDLE handle) { + HANDLE result = nullptr; + if (!DuplicateHandle(process, handle, + GetCurrentProcess(), + &result, 0, FALSE, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + throwWindowsError(L"DuplicateHandle of process handle"); + } + return OwnedHandle(result); +} + +WINPTY_API BOOL +winpty_spawn(winpty_t *wp, + const winpty_spawn_config_t *cfg, + HANDLE *process_handle /*OPTIONAL*/, + HANDLE *thread_handle /*OPTIONAL*/, + DWORD *create_process_error /*OPTIONAL*/, + winpty_error_ptr_t *err /*OPTIONAL*/) { + API_TRY { + ASSERT(wp != nullptr && cfg != nullptr); + + if (process_handle != nullptr) { *process_handle = nullptr; } + if (thread_handle != nullptr) { *thread_handle = nullptr; } + if (create_process_error != nullptr) { *create_process_error = 0; } + + LockGuard lock(wp->mutex); + RpcOperation rpc(*wp); + + // Send spawn request. + auto packet = newPacket(); + packet.putInt32(AgentMsg::StartProcess); + packet.putInt64(cfg->winptyFlags); + packet.putInt32(process_handle != nullptr); + packet.putInt32(thread_handle != nullptr); + packet.putWString(cfg->appname); + packet.putWString(cfg->cmdline); + packet.putWString(cfg->cwd); + packet.putWString(cfg->env); + packet.putWString(wp->spawnDesktopName); + writePacket(*wp, packet); + + // Receive reply. + auto reply = readPacket(*wp); + const auto result = static_cast(reply.getInt32()); + if (result == StartProcessResult::CreateProcessFailed) { + const DWORD lastError = reply.getInt32(); + reply.assertEof(); + if (create_process_error != nullptr) { + *create_process_error = lastError; + } + rpc.success(); + throw LibWinptyException(WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED, + L"CreateProcess failed"); + } else if (result == StartProcessResult::ProcessCreated) { + const HANDLE remoteProcess = handleFromInt64(reply.getInt64()); + const HANDLE remoteThread = handleFromInt64(reply.getInt64()); + reply.assertEof(); + OwnedHandle localProcess; + OwnedHandle localThread; + if (remoteProcess != nullptr) { + localProcess = + stealHandle(wp->agentProcess.get(), remoteProcess); + } + if (remoteThread != nullptr) { + localThread = + stealHandle(wp->agentProcess.get(), remoteThread); + } + if (process_handle != nullptr) { + *process_handle = localProcess.release(); + } + if (thread_handle != nullptr) { + *thread_handle = localThread.release(); + } + rpc.success(); + } else { + throwWinptyException( + L"Agent RPC error: invalid StartProcessResult"); + } + return TRUE; + } API_CATCH(FALSE) +} + + + +/***************************************************************************** + * winpty agent RPC calls: everything else */ + +WINPTY_API BOOL +winpty_set_size(winpty_t *wp, int cols, int rows, + winpty_error_ptr_t *err /*OPTIONAL*/) { + API_TRY { + ASSERT(wp != nullptr && cols > 0 && rows > 0); + LockGuard lock(wp->mutex); + RpcOperation rpc(*wp); + auto packet = newPacket(); + packet.putInt32(AgentMsg::SetSize); + packet.putInt32(cols); + packet.putInt32(rows); + writePacket(*wp, packet); + readPacket(*wp).assertEof(); + rpc.success(); + return TRUE; + } API_CATCH(FALSE) +} + +WINPTY_API int +winpty_get_console_process_list(winpty_t *wp, int *processList, const int processCount, + winpty_error_ptr_t *err /*OPTIONAL*/) { + API_TRY { + ASSERT(wp != nullptr); + ASSERT(processList != nullptr); + LockGuard lock(wp->mutex); + RpcOperation rpc(*wp); + auto packet = newPacket(); + packet.putInt32(AgentMsg::GetConsoleProcessList); + writePacket(*wp, packet); + auto reply = readPacket(*wp); + + auto actualProcessCount = reply.getInt32(); + + if (actualProcessCount <= processCount) { + for (auto i = 0; i < actualProcessCount; i++) { + processList[i] = reply.getInt32(); + } + } + + reply.assertEof(); + rpc.success(); + return actualProcessCount; + } API_CATCH(0) +} + +WINPTY_API void winpty_free(winpty_t *wp) { + // At least in principle, CloseHandle can fail, so this deletion can + // fail. It won't throw an exception, but maybe there's an error that + // should be propagated? + delete wp; +} diff --git a/src/libs/3rdparty/winpty/src/shared/AgentMsg.h b/src/libs/3rdparty/winpty/src/shared/AgentMsg.h new file mode 100644 index 0000000000..ab60c6b961 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/AgentMsg.h @@ -0,0 +1,38 @@ +// Copyright (c) 2011-2012 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_SHARED_AGENT_MSG_H +#define WINPTY_SHARED_AGENT_MSG_H + +struct AgentMsg +{ + enum Type { + StartProcess, + SetSize, + GetConsoleProcessList, + }; +}; + +enum class StartProcessResult { + CreateProcessFailed, + ProcessCreated, +}; + +#endif // WINPTY_SHARED_AGENT_MSG_H diff --git a/src/libs/3rdparty/winpty/src/shared/BackgroundDesktop.cc b/src/libs/3rdparty/winpty/src/shared/BackgroundDesktop.cc new file mode 100644 index 0000000000..1bea7e53dd --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/BackgroundDesktop.cc @@ -0,0 +1,122 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "BackgroundDesktop.h" + +#include + +#include "DebugClient.h" +#include "StringUtil.h" +#include "WinptyException.h" + +namespace { + +static std::wstring getObjectName(HANDLE object) { + BOOL success; + DWORD lengthNeeded = 0; + GetUserObjectInformationW(object, UOI_NAME, + nullptr, 0, + &lengthNeeded); + ASSERT(lengthNeeded % sizeof(wchar_t) == 0); + std::unique_ptr tmp( + new wchar_t[lengthNeeded / sizeof(wchar_t)]); + success = GetUserObjectInformationW(object, UOI_NAME, + tmp.get(), lengthNeeded, + nullptr); + if (!success) { + throwWindowsError(L"GetUserObjectInformationW failed"); + } + return std::wstring(tmp.get()); +} + +static std::wstring getDesktopName(HWINSTA winsta, HDESK desk) { + return getObjectName(winsta) + L"\\" + getObjectName(desk); +} + +} // anonymous namespace + +// Get a non-interactive window station for the agent. +// TODO: review security w.r.t. windowstation and desktop. +BackgroundDesktop::BackgroundDesktop() { + try { + m_originalStation = GetProcessWindowStation(); + if (m_originalStation == nullptr) { + throwWindowsError( + L"BackgroundDesktop ctor: " + L"GetProcessWindowStation returned NULL"); + } + m_newStation = + CreateWindowStationW(nullptr, 0, WINSTA_ALL_ACCESS, nullptr); + if (m_newStation == nullptr) { + throwWindowsError( + L"BackgroundDesktop ctor: CreateWindowStationW returned NULL"); + } + if (!SetProcessWindowStation(m_newStation)) { + throwWindowsError( + L"BackgroundDesktop ctor: SetProcessWindowStation failed"); + } + m_newDesktop = CreateDesktopW( + L"Default", nullptr, nullptr, 0, GENERIC_ALL, nullptr); + if (m_newDesktop == nullptr) { + throwWindowsError( + L"BackgroundDesktop ctor: CreateDesktopW failed"); + } + m_newDesktopName = getDesktopName(m_newStation, m_newDesktop); + TRACE("Created background desktop: %s", + utf8FromWide(m_newDesktopName).c_str()); + } catch (...) { + dispose(); + throw; + } +} + +void BackgroundDesktop::dispose() WINPTY_NOEXCEPT { + if (m_originalStation != nullptr) { + SetProcessWindowStation(m_originalStation); + m_originalStation = nullptr; + } + if (m_newDesktop != nullptr) { + CloseDesktop(m_newDesktop); + m_newDesktop = nullptr; + } + if (m_newStation != nullptr) { + CloseWindowStation(m_newStation); + m_newStation = nullptr; + } +} + +std::wstring getCurrentDesktopName() { + // MSDN says that the handles returned by GetProcessWindowStation and + // GetThreadDesktop do not need to be passed to CloseWindowStation and + // CloseDesktop, respectively. + const HWINSTA winsta = GetProcessWindowStation(); + if (winsta == nullptr) { + throwWindowsError( + L"getCurrentDesktopName: " + L"GetProcessWindowStation returned NULL"); + } + const HDESK desk = GetThreadDesktop(GetCurrentThreadId()); + if (desk == nullptr) { + throwWindowsError( + L"getCurrentDesktopName: " + L"GetThreadDesktop returned NULL"); + } + return getDesktopName(winsta, desk); +} diff --git a/src/libs/3rdparty/winpty/src/shared/BackgroundDesktop.h b/src/libs/3rdparty/winpty/src/shared/BackgroundDesktop.h new file mode 100644 index 0000000000..c692e57dc4 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/BackgroundDesktop.h @@ -0,0 +1,73 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_SHARED_BACKGROUND_DESKTOP_H +#define WINPTY_SHARED_BACKGROUND_DESKTOP_H + +#include + +#include + +#include "WinptyException.h" + +class BackgroundDesktop { +public: + BackgroundDesktop(); + ~BackgroundDesktop() { dispose(); } + void dispose() WINPTY_NOEXCEPT; + const std::wstring &desktopName() const { return m_newDesktopName; } + + BackgroundDesktop(const BackgroundDesktop &other) = delete; + BackgroundDesktop &operator=(const BackgroundDesktop &other) = delete; + + // We can't default the move constructor and assignment operator with + // MSVC 2013. We *could* if we required at least MSVC 2015 to build. + + BackgroundDesktop(BackgroundDesktop &&other) : + m_originalStation(other.m_originalStation), + m_newStation(other.m_newStation), + m_newDesktop(other.m_newDesktop), + m_newDesktopName(std::move(other.m_newDesktopName)) { + other.m_originalStation = nullptr; + other.m_newStation = nullptr; + other.m_newDesktop = nullptr; + } + BackgroundDesktop &operator=(BackgroundDesktop &&other) { + dispose(); + m_originalStation = other.m_originalStation; + m_newStation = other.m_newStation; + m_newDesktop = other.m_newDesktop; + m_newDesktopName = std::move(other.m_newDesktopName); + other.m_originalStation = nullptr; + other.m_newStation = nullptr; + other.m_newDesktop = nullptr; + return *this; + } + +private: + HWINSTA m_originalStation = nullptr; + HWINSTA m_newStation = nullptr; + HDESK m_newDesktop = nullptr; + std::wstring m_newDesktopName; +}; + +std::wstring getCurrentDesktopName(); + +#endif // WINPTY_SHARED_BACKGROUND_DESKTOP_H diff --git a/src/libs/3rdparty/winpty/src/shared/Buffer.cc b/src/libs/3rdparty/winpty/src/shared/Buffer.cc new file mode 100644 index 0000000000..158a629d56 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/Buffer.cc @@ -0,0 +1,103 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "Buffer.h" + +#include + +#include "DebugClient.h" +#include "WinptyAssert.h" + +// Define the READ_BUFFER_CHECK() macro. It *must* evaluate its condition, +// exactly once. +#define READ_BUFFER_CHECK(cond) \ + do { \ + if (!(cond)) { \ + trace("decode error: %s", #cond); \ + throw DecodeError(); \ + } \ + } while (false) + +enum class Piece : uint8_t { Int32, Int64, WString }; + +void WriteBuffer::putRawData(const void *data, size_t len) { + const auto p = reinterpret_cast(data); + m_buf.insert(m_buf.end(), p, p + len); +} + +void WriteBuffer::replaceRawData(size_t pos, const void *data, size_t len) { + ASSERT(pos <= m_buf.size() && len <= m_buf.size() - pos); + const auto p = reinterpret_cast(data); + std::copy(p, p + len, &m_buf[pos]); +} + +void WriteBuffer::putInt32(int32_t i) { + putRawValue(Piece::Int32); + putRawValue(i); +} + +void WriteBuffer::putInt64(int64_t i) { + putRawValue(Piece::Int64); + putRawValue(i); +} + +// len is in characters, excluding NUL, i.e. the number of wchar_t elements +void WriteBuffer::putWString(const wchar_t *str, size_t len) { + putRawValue(Piece::WString); + putRawValue(static_cast(len)); + putRawData(str, sizeof(wchar_t) * len); +} + +void ReadBuffer::getRawData(void *data, size_t len) { + ASSERT(m_off <= m_buf.size()); + READ_BUFFER_CHECK(len <= m_buf.size() - m_off); + const char *const inp = &m_buf[m_off]; + std::copy(inp, inp + len, reinterpret_cast(data)); + m_off += len; +} + +int32_t ReadBuffer::getInt32() { + READ_BUFFER_CHECK(getRawValue() == Piece::Int32); + return getRawValue(); +} + +int64_t ReadBuffer::getInt64() { + READ_BUFFER_CHECK(getRawValue() == Piece::Int64); + return getRawValue(); +} + +std::wstring ReadBuffer::getWString() { + READ_BUFFER_CHECK(getRawValue() == Piece::WString); + const uint64_t charLen = getRawValue(); + READ_BUFFER_CHECK(charLen <= SIZE_MAX / sizeof(wchar_t)); + // To be strictly conforming, we can't use the convenient wstring + // constructor, because the string in m_buf mightn't be aligned. + std::wstring ret; + if (charLen > 0) { + const size_t byteLen = charLen * sizeof(wchar_t); + ret.resize(charLen); + getRawData(&ret[0], byteLen); + } + return ret; +} + +void ReadBuffer::assertEof() { + READ_BUFFER_CHECK(m_off == m_buf.size()); +} diff --git a/src/libs/3rdparty/winpty/src/shared/Buffer.h b/src/libs/3rdparty/winpty/src/shared/Buffer.h new file mode 100644 index 0000000000..c2dd382e5b --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/Buffer.h @@ -0,0 +1,102 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_SHARED_BUFFER_H +#define WINPTY_SHARED_BUFFER_H + +#include +#include + +#include +#include +#include +#include + +#include "WinptyException.h" + +class WriteBuffer { +private: + std::vector m_buf; + +public: + WriteBuffer() {} + + template void putRawValue(const T &t) { + putRawData(&t, sizeof(t)); + } + template void replaceRawValue(size_t pos, const T &t) { + replaceRawData(pos, &t, sizeof(t)); + } + + void putRawData(const void *data, size_t len); + void replaceRawData(size_t pos, const void *data, size_t len); + void putInt32(int32_t i); + void putInt64(int64_t i); + void putWString(const wchar_t *str, size_t len); + void putWString(const wchar_t *str) { putWString(str, wcslen(str)); } + void putWString(const std::wstring &str) { putWString(str.data(), str.size()); } + std::vector &buf() { return m_buf; } + + // MSVC 2013 does not generate these automatically, so help it out. + WriteBuffer(WriteBuffer &&other) : m_buf(std::move(other.m_buf)) {} + WriteBuffer &operator=(WriteBuffer &&other) { + m_buf = std::move(other.m_buf); + return *this; + } +}; + +class ReadBuffer { +public: + class DecodeError : public WinptyException { + virtual const wchar_t *what() const WINPTY_NOEXCEPT override { + return L"DecodeError: RPC message decoding error"; + } + }; + +private: + std::vector m_buf; + size_t m_off = 0; + +public: + explicit ReadBuffer(std::vector &&buf) : m_buf(std::move(buf)) {} + + template T getRawValue() { + T ret = {}; + getRawData(&ret, sizeof(ret)); + return ret; + } + + void getRawData(void *data, size_t len); + int32_t getInt32(); + int64_t getInt64(); + std::wstring getWString(); + void assertEof(); + + // MSVC 2013 does not generate these automatically, so help it out. + ReadBuffer(ReadBuffer &&other) : + m_buf(std::move(other.m_buf)), m_off(other.m_off) {} + ReadBuffer &operator=(ReadBuffer &&other) { + m_buf = std::move(other.m_buf); + m_off = other.m_off; + return *this; + } +}; + +#endif // WINPTY_SHARED_BUFFER_H diff --git a/src/libs/3rdparty/winpty/src/shared/DebugClient.cc b/src/libs/3rdparty/winpty/src/shared/DebugClient.cc new file mode 100644 index 0000000000..bafe0c8954 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/DebugClient.cc @@ -0,0 +1,187 @@ +// Copyright (c) 2011-2012 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "DebugClient.h" + +#include +#include +#include +#include + +#include +#include + +#include "winpty_snprintf.h" + +const wchar_t *const kPipeName = L"\\\\.\\pipe\\DebugServer"; + +void *volatile g_debugConfig; + +namespace { + +// It would be easy to accidentally trample on the Windows LastError value +// by adding logging/debugging code. Ensure that can't happen by saving and +// restoring the value. This saving and restoring doesn't happen along the +// fast path. +class PreserveLastError { +public: + PreserveLastError() : m_lastError(GetLastError()) {} + ~PreserveLastError() { SetLastError(m_lastError); } +private: + DWORD m_lastError; +}; + +} // anonymous namespace + +static void sendToDebugServer(const char *message) +{ + HANDLE tracePipe = INVALID_HANDLE_VALUE; + + do { + // The default impersonation level is SECURITY_IMPERSONATION, which allows + // a sufficiently authorized named pipe server to impersonate the client. + // There's no need for impersonation in this debugging system, so reduce + // the impersonation level to SECURITY_IDENTIFICATION, which allows a + // server to merely identify us. + tracePipe = CreateFileW( + kPipeName, + GENERIC_READ | GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, + SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION, + NULL); + } while (tracePipe == INVALID_HANDLE_VALUE && + GetLastError() == ERROR_PIPE_BUSY && + WaitNamedPipeW(kPipeName, NMPWAIT_WAIT_FOREVER)); + + if (tracePipe != INVALID_HANDLE_VALUE) { + DWORD newMode = PIPE_READMODE_MESSAGE; + SetNamedPipeHandleState(tracePipe, &newMode, NULL, NULL); + char response[16]; + DWORD actual = 0; + TransactNamedPipe(tracePipe, + const_cast(message), strlen(message), + response, sizeof(response), &actual, NULL); + CloseHandle(tracePipe); + } +} + +// Get the current UTC time as milliseconds from the epoch (ignoring leap +// seconds). Use the Unix epoch for consistency with DebugClient.py. There +// are 134774 days between 1601-01-01 (the Win32 epoch) and 1970-01-01 (the +// Unix epoch). +static long long unixTimeMillis() +{ + FILETIME fileTime; + GetSystemTimeAsFileTime(&fileTime); + long long msTime = (((long long)fileTime.dwHighDateTime << 32) + + fileTime.dwLowDateTime) / 10000; + return msTime - 134774LL * 24 * 3600 * 1000; +} + +static const char *getDebugConfig() +{ + if (g_debugConfig == NULL) { + PreserveLastError preserve; + const int bufSize = 256; + char buf[bufSize]; + DWORD actualSize = + GetEnvironmentVariableA("WINPTY_DEBUG", buf, bufSize); + if (actualSize == 0 || actualSize >= static_cast(bufSize)) { + buf[0] = '\0'; + } + const size_t len = strlen(buf) + 1; + char *newConfig = new char[len]; + std::copy(buf, buf + len, newConfig); + void *oldValue = InterlockedCompareExchangePointer( + &g_debugConfig, newConfig, NULL); + if (oldValue != NULL) { + delete [] newConfig; + } + } + return static_cast(g_debugConfig); +} + +bool isTracingEnabled() +{ + static bool disabled, enabled; + if (disabled) { + return false; + } else if (enabled) { + return true; + } else { + // Recognize WINPTY_DEBUG=1 for backwards compatibility. + PreserveLastError preserve; + bool value = hasDebugFlag("trace") || hasDebugFlag("1"); + disabled = !value; + enabled = value; + return value; + } +} + +bool hasDebugFlag(const char *flag) +{ + if (strchr(flag, ',') != NULL) { + trace("INTERNAL ERROR: hasDebugFlag flag has comma: '%s'", flag); + abort(); + } + const char *const configCStr = getDebugConfig(); + if (configCStr[0] == '\0') { + return false; + } + PreserveLastError preserve; + std::string config(configCStr); + std::string flagStr(flag); + config = "," + config + ","; + flagStr = "," + flagStr + ","; + return config.find(flagStr) != std::string::npos; +} + +void trace(const char *format, ...) +{ + if (!isTracingEnabled()) + return; + + PreserveLastError preserve; + char message[1024]; + + va_list ap; + va_start(ap, format); + winpty_vsnprintf(message, format, ap); + message[sizeof(message) - 1] = '\0'; + va_end(ap); + + const int currentTime = (int)(unixTimeMillis() % (100000 * 1000)); + + char moduleName[1024]; + moduleName[0] = '\0'; + GetModuleFileNameA(NULL, moduleName, sizeof(moduleName)); + const char *baseName = strrchr(moduleName, '\\'); + baseName = (baseName != NULL) ? baseName + 1 : moduleName; + + char fullMessage[1024]; + winpty_snprintf(fullMessage, + "[%05d.%03d %s,p%04d,t%04d]: %s", + currentTime / 1000, currentTime % 1000, + baseName, (int)GetCurrentProcessId(), (int)GetCurrentThreadId(), + message); + fullMessage[sizeof(fullMessage) - 1] = '\0'; + + sendToDebugServer(fullMessage); +} diff --git a/src/libs/3rdparty/winpty/src/shared/DebugClient.h b/src/libs/3rdparty/winpty/src/shared/DebugClient.h new file mode 100644 index 0000000000..b126071130 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/DebugClient.h @@ -0,0 +1,38 @@ +// Copyright (c) 2011-2012 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef DEBUGCLIENT_H +#define DEBUGCLIENT_H + +#include "winpty_snprintf.h" + +bool isTracingEnabled(); +bool hasDebugFlag(const char *flag); +void trace(const char *format, ...) WINPTY_SNPRINTF_FORMAT(1, 2); + +// This macro calls trace without evaluating the arguments. +#define TRACE(format, ...) \ + do { \ + if (isTracingEnabled()) { \ + trace((format), ## __VA_ARGS__); \ + } \ + } while (false) + +#endif // DEBUGCLIENT_H diff --git a/src/libs/3rdparty/winpty/src/shared/GenRandom.cc b/src/libs/3rdparty/winpty/src/shared/GenRandom.cc new file mode 100644 index 0000000000..6d7920643a --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/GenRandom.cc @@ -0,0 +1,138 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "GenRandom.h" + +#include +#include + +#include "DebugClient.h" +#include "StringBuilder.h" + +static volatile LONG g_pipeCounter; + +GenRandom::GenRandom() : m_advapi32(L"advapi32.dll") { + // First try to use the pseudo-documented RtlGenRandom function from + // advapi32.dll. Creating a CryptoAPI context is slow, and RtlGenRandom + // avoids the overhead. It's documented in this blog post[1] and on + // MSDN[2] with a disclaimer about future breakage. This technique is + // apparently built-in into the MSVC CRT, though, for the rand_s function, + // so perhaps it is stable enough. + // + // [1] http://blogs.msdn.com/b/michael_howard/archive/2005/01/14/353379.aspx + // [2] https://msdn.microsoft.com/en-us/library/windows/desktop/aa387694(v=vs.85).aspx + // + // Both RtlGenRandom and the Crypto API functions exist in XP and up. + m_rtlGenRandom = reinterpret_cast( + m_advapi32.proc("SystemFunction036")); + // The OsModule class logs an error message if the proc is nullptr. + if (m_rtlGenRandom != nullptr) { + return; + } + + // Fall back to the crypto API. + m_cryptProvIsValid = + CryptAcquireContext(&m_cryptProv, nullptr, nullptr, + PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) != 0; + if (!m_cryptProvIsValid) { + trace("GenRandom: CryptAcquireContext failed: %u", + static_cast(GetLastError())); + } +} + +GenRandom::~GenRandom() { + if (m_cryptProvIsValid) { + CryptReleaseContext(m_cryptProv, 0); + } +} + +// Returns false if the context is invalid or the generation fails. +bool GenRandom::fillBuffer(void *buffer, size_t size) { + memset(buffer, 0, size); + bool success = false; + if (m_rtlGenRandom != nullptr) { + success = m_rtlGenRandom(buffer, size) != 0; + if (!success) { + trace("GenRandom: RtlGenRandom/SystemFunction036 failed: %u", + static_cast(GetLastError())); + } + } else if (m_cryptProvIsValid) { + success = + CryptGenRandom(m_cryptProv, size, + reinterpret_cast(buffer)) != 0; + if (!success) { + trace("GenRandom: CryptGenRandom failed, size=%d, lasterror=%u", + static_cast(size), + static_cast(GetLastError())); + } + } + return success; +} + +// Returns an empty string if either of CryptAcquireContext or CryptGenRandom +// fail. +std::string GenRandom::randomBytes(size_t numBytes) { + std::string ret(numBytes, '\0'); + if (!fillBuffer(&ret[0], numBytes)) { + return std::string(); + } + return ret; +} + +std::wstring GenRandom::randomHexString(size_t numBytes) { + const std::string bytes = randomBytes(numBytes); + std::wstring ret(bytes.size() * 2, L'\0'); + for (size_t i = 0; i < bytes.size(); ++i) { + static const wchar_t hex[] = L"0123456789abcdef"; + ret[i * 2] = hex[static_cast(bytes[i]) >> 4]; + ret[i * 2 + 1] = hex[static_cast(bytes[i]) & 0xF]; + } + return ret; +} + +// Returns a 64-bit value representing the number of 100-nanosecond intervals +// since January 1, 1601. +static uint64_t systemTimeAsUInt64() { + FILETIME monotonicTime = {}; + GetSystemTimeAsFileTime(&monotonicTime); + return (static_cast(monotonicTime.dwHighDateTime) << 32) | + static_cast(monotonicTime.dwLowDateTime); +} + +// Generates a unique and hard-to-guess case-insensitive string suitable for +// use in a pipe filename or a Windows object name. +std::wstring GenRandom::uniqueName() { + // First include enough information to avoid collisions assuming + // cooperative software. This code assumes that a process won't die and + // be replaced with a recycled PID within a single GetSystemTimeAsFileTime + // interval. + WStringBuilder sb(64); + sb << GetCurrentProcessId() + << L'-' << InterlockedIncrement(&g_pipeCounter) + << L'-' << whexOfInt(systemTimeAsUInt64()); + // It isn't clear to me how the crypto APIs would fail. It *probably* + // doesn't matter that much anyway? In principle, a predictable pipe name + // is subject to a local denial-of-service attack. + auto random = randomHexString(16); + if (!random.empty()) { + sb << L'-' << random; + } + return sb.str_moved(); +} diff --git a/src/libs/3rdparty/winpty/src/shared/GenRandom.h b/src/libs/3rdparty/winpty/src/shared/GenRandom.h new file mode 100644 index 0000000000..746cb1ecf7 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/GenRandom.h @@ -0,0 +1,55 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_GEN_RANDOM_H +#define WINPTY_GEN_RANDOM_H + +// The original MinGW requires that we include wincrypt.h. With MinGW-w64 and +// MSVC, including windows.h is sufficient. +#include +#include + +#include + +#include "OsModule.h" + +class GenRandom { + typedef BOOLEAN WINAPI RtlGenRandom_t(PVOID, ULONG); + + OsModule m_advapi32; + RtlGenRandom_t *m_rtlGenRandom = nullptr; + bool m_cryptProvIsValid = false; + HCRYPTPROV m_cryptProv = 0; + +public: + GenRandom(); + ~GenRandom(); + bool fillBuffer(void *buffer, size_t size); + std::string randomBytes(size_t numBytes); + std::wstring randomHexString(size_t numBytes); + std::wstring uniqueName(); + + // Return true if the crypto context was successfully initialized. + bool valid() const { + return m_rtlGenRandom != nullptr || m_cryptProvIsValid; + } +}; + +#endif // WINPTY_GEN_RANDOM_H diff --git a/src/libs/3rdparty/winpty/src/shared/GetCommitHash.bat b/src/libs/3rdparty/winpty/src/shared/GetCommitHash.bat new file mode 100644 index 0000000000..a9f8e9cef0 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/GetCommitHash.bat @@ -0,0 +1,13 @@ +@echo off + +REM -- Echo the git commit hash. If git isn't available for some reason, +REM -- output nothing instead. + +git rev-parse HEAD >NUL 2>NUL && ( + git rev-parse HEAD +) || ( + echo none +) + +REM -- Set ERRORLEVEL to 0 using this cryptic syntax. +(call ) diff --git a/src/libs/3rdparty/winpty/src/shared/Mutex.h b/src/libs/3rdparty/winpty/src/shared/Mutex.h new file mode 100644 index 0000000000..98215365ad --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/Mutex.h @@ -0,0 +1,54 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +// Recent 4.x MinGW and MinGW-w64 gcc compilers lack std::mutex and +// std::lock_guard. I have a 5.2.0 MinGW-w64 compiler packaged through MSYS2 +// that *is* new enough, but that's one compiler against several deficient +// ones. Wrap CRITICAL_SECTION instead. + +#ifndef WINPTY_SHARED_MUTEX_H +#define WINPTY_SHARED_MUTEX_H + +#include + +class Mutex { + CRITICAL_SECTION m_mutex; +public: + Mutex() { InitializeCriticalSection(&m_mutex); } + ~Mutex() { DeleteCriticalSection(&m_mutex); } + void lock() { EnterCriticalSection(&m_mutex); } + void unlock() { LeaveCriticalSection(&m_mutex); } + + Mutex(const Mutex &other) = delete; + Mutex &operator=(const Mutex &other) = delete; +}; + +template +class LockGuard { + T &m_lock; +public: + LockGuard(T &lock) : m_lock(lock) { m_lock.lock(); } + ~LockGuard() { m_lock.unlock(); } + + LockGuard(const LockGuard &other) = delete; + LockGuard &operator=(const LockGuard &other) = delete; +}; + +#endif // WINPTY_SHARED_MUTEX_H diff --git a/src/libs/3rdparty/winpty/src/shared/OsModule.h b/src/libs/3rdparty/winpty/src/shared/OsModule.h new file mode 100644 index 0000000000..9713fa2b2d --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/OsModule.h @@ -0,0 +1,63 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_SHARED_OS_MODULE_H +#define WINPTY_SHARED_OS_MODULE_H + +#include + +#include + +#include "DebugClient.h" +#include "WinptyAssert.h" +#include "WinptyException.h" + +class OsModule { + HMODULE m_module; +public: + enum class LoadErrorBehavior { Abort, Throw }; + OsModule(const wchar_t *fileName, + LoadErrorBehavior behavior=LoadErrorBehavior::Abort) { + m_module = LoadLibraryW(fileName); + if (behavior == LoadErrorBehavior::Abort) { + ASSERT(m_module != NULL); + } else { + if (m_module == nullptr) { + const auto err = GetLastError(); + throwWindowsError( + (L"LoadLibraryW error: " + std::wstring(fileName)).c_str(), + err); + } + } + } + ~OsModule() { + FreeLibrary(m_module); + } + HMODULE handle() const { return m_module; } + FARPROC proc(const char *funcName) { + FARPROC ret = GetProcAddress(m_module, funcName); + if (ret == NULL) { + trace("GetProcAddress: %s is missing", funcName); + } + return ret; + } +}; + +#endif // WINPTY_SHARED_OS_MODULE_H diff --git a/src/libs/3rdparty/winpty/src/shared/OwnedHandle.cc b/src/libs/3rdparty/winpty/src/shared/OwnedHandle.cc new file mode 100644 index 0000000000..7b173536e6 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/OwnedHandle.cc @@ -0,0 +1,36 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "OwnedHandle.h" + +#include "DebugClient.h" +#include "WinptyException.h" + +void OwnedHandle::dispose(bool nothrow) { + if (m_h != nullptr && m_h != INVALID_HANDLE_VALUE) { + if (!CloseHandle(m_h)) { + trace("CloseHandle(%p) failed", m_h); + if (!nothrow) { + throwWindowsError(L"CloseHandle failed"); + } + } + } + m_h = nullptr; +} diff --git a/src/libs/3rdparty/winpty/src/shared/OwnedHandle.h b/src/libs/3rdparty/winpty/src/shared/OwnedHandle.h new file mode 100644 index 0000000000..70a8d6163a --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/OwnedHandle.h @@ -0,0 +1,45 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_SHARED_OWNED_HANDLE_H +#define WINPTY_SHARED_OWNED_HANDLE_H + +#include + +class OwnedHandle { + HANDLE m_h; +public: + OwnedHandle() : m_h(nullptr) {} + explicit OwnedHandle(HANDLE h) : m_h(h) {} + ~OwnedHandle() { dispose(true); } + void dispose(bool nothrow=false); + HANDLE get() const { return m_h; } + HANDLE release() { HANDLE ret = m_h; m_h = nullptr; return ret; } + OwnedHandle(const OwnedHandle &other) = delete; + OwnedHandle(OwnedHandle &&other) : m_h(other.release()) {} + OwnedHandle &operator=(const OwnedHandle &other) = delete; + OwnedHandle &operator=(OwnedHandle &&other) { + dispose(); + m_h = other.release(); + return *this; + } +}; + +#endif // WINPTY_SHARED_OWNED_HANDLE_H diff --git a/src/libs/3rdparty/winpty/src/shared/PrecompiledHeader.h b/src/libs/3rdparty/winpty/src/shared/PrecompiledHeader.h new file mode 100644 index 0000000000..7d9b8f8b4a --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/PrecompiledHeader.h @@ -0,0 +1,43 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_PRECOMPILED_HEADER_H +#define WINPTY_PRECOMPILED_HEADER_H + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif // WINPTY_PRECOMPILED_HEADER_H diff --git a/src/libs/3rdparty/winpty/src/shared/StringBuilder.h b/src/libs/3rdparty/winpty/src/shared/StringBuilder.h new file mode 100644 index 0000000000..f3155bdd29 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/StringBuilder.h @@ -0,0 +1,227 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +// Efficient integer->string conversion and string concatenation. The +// hexadecimal conversion may optionally have leading zeros. Other ways to +// convert integers to strings in C++ suffer these drawbacks: +// +// * std::stringstream: Inefficient, even more so than stdio. +// +// * std::to_string: No hexadecimal output, tends to use heap allocation, not +// supported on Cygwin. +// +// * stdio routines: Requires parsing a format string (inefficient). The +// caller *must* know how large the content is for correctness. The +// string-printf functions are extremely inconsistent on Windows. In +// particular, 64-bit integers, wide strings, and return values are +// problem areas. +// +// StringBuilderTest.cc is a standalone program that tests this header. + +#ifndef WINPTY_STRING_BUILDER_H +#define WINPTY_STRING_BUILDER_H + +#include +#include +#include + +#ifdef STRING_BUILDER_TESTING +#include +#define STRING_BUILDER_CHECK(cond) assert(cond) +#else +#define STRING_BUILDER_CHECK(cond) +#endif // STRING_BUILDER_TESTING + +#include "WinptyAssert.h" + +template +struct ValueString { + std::array m_array; + size_t m_offset; + size_t m_size; + + const C *c_str() const { return m_array.data() + m_offset; } + const C *data() const { return m_array.data() + m_offset; } + size_t size() const { return m_size; } + std::basic_string str() const { + return std::basic_string(data(), m_size); + } +}; + +#ifdef _MSC_VER +// Disable an MSVC /SDL error that forbids unsigned negation. Signed negation +// invokes undefined behavior for INTxx_MIN, so unsigned negation is simpler to +// reason about. (We assume twos-complement in any case.) +#define STRING_BUILDER_ALLOW_UNSIGNED_NEGATE(x) \ + ( \ + __pragma(warning(push)) \ + __pragma(warning(disable:4146)) \ + (x) \ + __pragma(warning(pop)) \ + ) +#else +#define STRING_BUILDER_ALLOW_UNSIGNED_NEGATE(x) (x) +#endif + +// Formats an integer as decimal without leading zeros. +template +ValueString gdecOfInt(const I value) { + typedef typename std::make_unsigned::type U; + auto unsValue = static_cast(value); + const bool isNegative = (value < 0); + if (isNegative) { + unsValue = STRING_BUILDER_ALLOW_UNSIGNED_NEGATE(-unsValue); + } + decltype(gdecOfInt(value)) out; + auto &arr = out.m_array; + C *const endp = arr.data() + arr.size(); + C *outp = endp; + *(--outp) = '\0'; + STRING_BUILDER_CHECK(outp >= arr.data()); + do { + const int digit = unsValue % 10; + unsValue /= 10; + *(--outp) = '0' + digit; + STRING_BUILDER_CHECK(outp >= arr.data()); + } while (unsValue != 0); + if (isNegative) { + *(--outp) = '-'; + STRING_BUILDER_CHECK(outp >= arr.data()); + } + out.m_offset = outp - arr.data(); + out.m_size = endp - outp - 1; + return out; +} + +template decltype(gdecOfInt(0)) decOfInt(I i) { + return gdecOfInt(i); +} + +template decltype(gdecOfInt(0)) wdecOfInt(I i) { + return gdecOfInt(i); +} + +// Formats an integer as hexadecimal, with or without leading zeros. +template +ValueString ghexOfInt(const I value) { + typedef typename std::make_unsigned::type U; + const auto unsValue = static_cast(value); + static const C hex[16] = {'0','1','2','3','4','5','6','7', + '8','9','a','b','c','d','e','f'}; + decltype(ghexOfInt(value)) out; + auto &arr = out.m_array; + C *outp = arr.data(); + int inIndex = 0; + int shift = sizeof(I) * 8 - 4; + const int len = sizeof(I) * 2; + if (!leadingZeros) { + for (; inIndex < len - 1; ++inIndex, shift -= 4) { + STRING_BUILDER_CHECK(shift >= 0 && shift < sizeof(unsValue) * 8); + const int digit = (unsValue >> shift) & 0xF; + if (digit != 0) { + break; + } + } + } + for (; inIndex < len; ++inIndex, shift -= 4) { + const int digit = (unsValue >> shift) & 0xF; + *(outp++) = hex[digit]; + STRING_BUILDER_CHECK(outp <= arr.data() + arr.size()); + } + *(outp++) = '\0'; + STRING_BUILDER_CHECK(outp <= arr.data() + arr.size()); + out.m_offset = 0; + out.m_size = outp - arr.data() - 1; + return out; +} + +template +decltype(ghexOfInt(0)) hexOfInt(I i) { + return ghexOfInt(i); +} + +template +decltype(ghexOfInt(0)) whexOfInt(I i) { + return ghexOfInt(i); +} + +template +class GStringBuilder { +public: + typedef std::basic_string StringType; + + GStringBuilder() {} + GStringBuilder(size_t capacity) { + m_out.reserve(capacity); + } + + GStringBuilder &operator<<(C ch) { m_out.push_back(ch); return *this; } + GStringBuilder &operator<<(const C *str) { m_out.append(str); return *this; } + GStringBuilder &operator<<(const StringType &str) { m_out.append(str); return *this; } + + template + GStringBuilder &operator<<(const ValueString &str) { + m_out.append(str.data(), str.size()); + return *this; + } + +private: + // Forbid output of char/wchar_t for GStringBuilder if the type doesn't + // exactly match the builder element type. The code still allows + // signed char and unsigned char, but I'm a little worried about what + // happens if a user tries to output int8_t or uint8_t. + template + typename std::enable_if< + (std::is_same::value || std::is_same::value) && + !std::is_same::value, GStringBuilder&>::type + operator<<(P ch) { + ASSERT(false && "Method was not supposed to be reachable."); + return *this; + } + +public: + GStringBuilder &operator<<(short i) { return *this << gdecOfInt(i); } + GStringBuilder &operator<<(unsigned short i) { return *this << gdecOfInt(i); } + GStringBuilder &operator<<(int i) { return *this << gdecOfInt(i); } + GStringBuilder &operator<<(unsigned int i) { return *this << gdecOfInt(i); } + GStringBuilder &operator<<(long i) { return *this << gdecOfInt(i); } + GStringBuilder &operator<<(unsigned long i) { return *this << gdecOfInt(i); } + GStringBuilder &operator<<(long long i) { return *this << gdecOfInt(i); } + GStringBuilder &operator<<(unsigned long long i) { return *this << gdecOfInt(i); } + + GStringBuilder &operator<<(const void *p) { + m_out.push_back(static_cast('0')); + m_out.push_back(static_cast('x')); + *this << ghexOfInt(reinterpret_cast(p)); + return *this; + } + + StringType str() { return m_out; } + StringType str_moved() { return std::move(m_out); } + const C *c_str() const { return m_out.c_str(); } + +private: + StringType m_out; +}; + +typedef GStringBuilder StringBuilder; +typedef GStringBuilder WStringBuilder; + +#endif // WINPTY_STRING_BUILDER_H diff --git a/src/libs/3rdparty/winpty/src/shared/StringBuilderTest.cc b/src/libs/3rdparty/winpty/src/shared/StringBuilderTest.cc new file mode 100644 index 0000000000..e6c2d3138c --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/StringBuilderTest.cc @@ -0,0 +1,114 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#define STRING_BUILDER_TESTING + +#include "StringBuilder.h" + +#include +#include + +#include +#include + +void display(const std::string &str) { fprintf(stderr, "%s", str.c_str()); } +void display(const std::wstring &str) { fprintf(stderr, "%ls", str.c_str()); } + +#define CHECK_EQ(x, y) \ + do { \ + const auto xval = (x); \ + const auto yval = (y); \ + if (xval != yval) { \ + fprintf(stderr, "error: %s:%d: %s != %s: ", \ + __FILE__, __LINE__, #x, #y); \ + display(xval); \ + fprintf(stderr, " != "); \ + display(yval); \ + fprintf(stderr, "\n"); \ + } \ + } while(0) + +template +std::basic_string decOfIntSS(const I value) { + // std::to_string and std::to_wstring are missing in Cygwin as of this + // writing (early 2016). + std::basic_stringstream ss; + ss << +value; // We must promote char to print it as an integer. + return ss.str(); +} + + +template +std::basic_string hexOfIntSS(const I value) { + typedef typename std::make_unsigned::type U; + const unsigned long long u64Value = value & static_cast(~0); + std::basic_stringstream ss; + if (leadingZeros) { + ss << std::setfill(static_cast('0')) << std::setw(sizeof(I) * 2); + } + ss << std::hex << u64Value; + return ss.str(); +} + +template +void testValue(I value) { + CHECK_EQ(decOfInt(value).str(), (decOfIntSS(value))); + CHECK_EQ(wdecOfInt(value).str(), (decOfIntSS(value))); + CHECK_EQ((hexOfInt(value).str()), (hexOfIntSS(value))); + CHECK_EQ((hexOfInt(value).str()), (hexOfIntSS(value))); + CHECK_EQ((whexOfInt(value).str()), (hexOfIntSS(value))); + CHECK_EQ((whexOfInt(value).str()), (hexOfIntSS(value))); +} + +template +void testType() { + typedef typename std::make_unsigned::type U; + const U quarter = static_cast(1) << (sizeof(U) * 8 - 2); + for (unsigned quarterIndex = 0; quarterIndex < 4; ++quarterIndex) { + for (int offset = -18; offset <= 18; ++offset) { + const I value = quarter * quarterIndex + static_cast(offset); + testValue(value); + } + } + testValue(static_cast(42)); + testValue(static_cast(123456)); + testValue(static_cast(0xdeadfacecafebeefull)); +} + +int main() { + testType(); + + testType(); + testType(); + testType(); + testType(); + testType(); + + testType(); + testType(); + testType(); + testType(); + testType(); + + StringBuilder() << static_cast("TEST"); + WStringBuilder() << static_cast("TEST"); + + fprintf(stderr, "All tests completed!\n"); +} diff --git a/src/libs/3rdparty/winpty/src/shared/StringUtil.cc b/src/libs/3rdparty/winpty/src/shared/StringUtil.cc new file mode 100644 index 0000000000..3a85a3ec94 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/StringUtil.cc @@ -0,0 +1,55 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "StringUtil.h" + +#include + +#include "WinptyAssert.h" + +// Workaround. MinGW (from mingw.org) does not have wcsnlen. MinGW-w64 *does* +// have wcsnlen, but use this function for consistency. +size_t winpty_wcsnlen(const wchar_t *s, size_t maxlen) { + ASSERT(s != NULL); + for (size_t i = 0; i < maxlen; ++i) { + if (s[i] == L'\0') { + return i; + } + } + return maxlen; +} + +std::string utf8FromWide(const std::wstring &input) { + int mblen = WideCharToMultiByte( + CP_UTF8, 0, + input.data(), input.size(), + NULL, 0, NULL, NULL); + if (mblen <= 0) { + return std::string(); + } + std::vector tmp(mblen); + int mblen2 = WideCharToMultiByte( + CP_UTF8, 0, + input.data(), input.size(), + tmp.data(), tmp.size(), + NULL, NULL); + ASSERT(mblen2 == mblen); + return std::string(tmp.data(), tmp.size()); +} diff --git a/src/libs/3rdparty/winpty/src/shared/StringUtil.h b/src/libs/3rdparty/winpty/src/shared/StringUtil.h new file mode 100644 index 0000000000..e4bf3c9121 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/StringUtil.h @@ -0,0 +1,80 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_SHARED_STRING_UTIL_H +#define WINPTY_SHARED_STRING_UTIL_H + +#include +#include +#include + +#include +#include +#include + +#include "WinptyAssert.h" + +size_t winpty_wcsnlen(const wchar_t *s, size_t maxlen); +std::string utf8FromWide(const std::wstring &input); + +// Return a vector containing each character in the string. +template +std::vector vectorFromString(const std::basic_string &str) { + return std::vector(str.begin(), str.end()); +} + +// Return a vector containing each character in the string, followed by a +// NUL terminator. +template +std::vector vectorWithNulFromString(const std::basic_string &str) { + std::vector ret; + ret.reserve(str.size() + 1); + ret.insert(ret.begin(), str.begin(), str.end()); + ret.push_back('\0'); + return ret; +} + +// A safer(?) version of wcsncpy that is accepted by MSVC's /SDL mode. +template +wchar_t *winpty_wcsncpy(wchar_t (&d)[N], const wchar_t *s) { + ASSERT(s != nullptr); + size_t i = 0; + for (; i < N; ++i) { + if (s[i] == L'\0') { + break; + } + d[i] = s[i]; + } + for (; i < N; ++i) { + d[i] = L'\0'; + } + return d; +} + +// Like wcsncpy, but ensure that the destination buffer is NUL-terminated. +template +wchar_t *winpty_wcsncpy_nul(wchar_t (&d)[N], const wchar_t *s) { + static_assert(N > 0, "array cannot be 0-size"); + winpty_wcsncpy(d, s); + d[N - 1] = L'\0'; + return d; +} + +#endif // WINPTY_SHARED_STRING_UTIL_H diff --git a/src/libs/3rdparty/winpty/src/shared/TimeMeasurement.h b/src/libs/3rdparty/winpty/src/shared/TimeMeasurement.h new file mode 100644 index 0000000000..716a027fcb --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/TimeMeasurement.h @@ -0,0 +1,63 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +// Convenience header library for using the high-resolution performance counter +// to measure how long some process takes. + +#ifndef TIME_MEASUREMENT_H +#define TIME_MEASUREMENT_H + +#include +#include +#include + +class TimeMeasurement { +public: + TimeMeasurement() { + static double freq = static_cast(getFrequency()); + m_freq = freq; + m_start = value(); + } + + double elapsed() { + uint64_t elapsedTicks = value() - m_start; + return static_cast(elapsedTicks) / m_freq; + } + +private: + uint64_t getFrequency() { + LARGE_INTEGER freq; + BOOL success = QueryPerformanceFrequency(&freq); + assert(success && "QueryPerformanceFrequency failed"); + return freq.QuadPart; + } + + uint64_t value() { + LARGE_INTEGER ret; + BOOL success = QueryPerformanceCounter(&ret); + assert(success && "QueryPerformanceCounter failed"); + return ret.QuadPart; + } + + uint64_t m_start; + double m_freq; +}; + +#endif // TIME_MEASUREMENT_H diff --git a/src/libs/3rdparty/winpty/src/shared/UnixCtrlChars.h b/src/libs/3rdparty/winpty/src/shared/UnixCtrlChars.h new file mode 100644 index 0000000000..39dfa62ec9 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/UnixCtrlChars.h @@ -0,0 +1,45 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef UNIX_CTRL_CHARS_H +#define UNIX_CTRL_CHARS_H + +inline char decodeUnixCtrlChar(char ch) { + const char ctrlKeys[] = { + /* 0x00 */ '@', /* 0x01 */ 'A', /* 0x02 */ 'B', /* 0x03 */ 'C', + /* 0x04 */ 'D', /* 0x05 */ 'E', /* 0x06 */ 'F', /* 0x07 */ 'G', + /* 0x08 */ 'H', /* 0x09 */ 'I', /* 0x0A */ 'J', /* 0x0B */ 'K', + /* 0x0C */ 'L', /* 0x0D */ 'M', /* 0x0E */ 'N', /* 0x0F */ 'O', + /* 0x10 */ 'P', /* 0x11 */ 'Q', /* 0x12 */ 'R', /* 0x13 */ 'S', + /* 0x14 */ 'T', /* 0x15 */ 'U', /* 0x16 */ 'V', /* 0x17 */ 'W', + /* 0x18 */ 'X', /* 0x19 */ 'Y', /* 0x1A */ 'Z', /* 0x1B */ '[', + /* 0x1C */ '\\', /* 0x1D */ ']', /* 0x1E */ '^', /* 0x1F */ '_', + }; + unsigned char uch = ch; + if (uch < 32) { + return ctrlKeys[uch]; + } else if (uch == 127) { + return '?'; + } else { + return '\0'; + } +} + +#endif // UNIX_CTRL_CHARS_H diff --git a/src/libs/3rdparty/winpty/src/shared/UpdateGenVersion.bat b/src/libs/3rdparty/winpty/src/shared/UpdateGenVersion.bat new file mode 100644 index 0000000000..ea2a7d64ed --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/UpdateGenVersion.bat @@ -0,0 +1,20 @@ +@echo off + +rem -- Echo the git commit hash. If git isn't available for some reason, +rem -- output nothing instead. + +mkdir ..\gen 2>nul + +set /p VERSION=<..\..\VERSION.txt +set COMMIT=%1 + +echo // AUTO-GENERATED BY %0 %*>..\gen\GenVersion.h +echo const char GenVersion_Version[] = "%VERSION%";>>..\gen\GenVersion.h +echo const char GenVersion_Commit[] = "%COMMIT%";>>..\gen\GenVersion.h + +rem -- The winpty.gyp file expects the script to output the include directory, +rem -- relative to src. +echo gen + +rem -- Set ERRORLEVEL to 0 using this cryptic syntax. +(call ) diff --git a/src/libs/3rdparty/winpty/src/shared/WindowsSecurity.cc b/src/libs/3rdparty/winpty/src/shared/WindowsSecurity.cc new file mode 100644 index 0000000000..711a8637c8 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/WindowsSecurity.cc @@ -0,0 +1,460 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "WindowsSecurity.h" + +#include + +#include "DebugClient.h" +#include "OsModule.h" +#include "OwnedHandle.h" +#include "StringBuilder.h" +#include "WindowsVersion.h" +#include "WinptyAssert.h" +#include "WinptyException.h" + +namespace { + +struct LocalFreer { + void operator()(void *ptr) { + if (ptr != nullptr) { + LocalFree(reinterpret_cast(ptr)); + } + } +}; + +typedef std::unique_ptr PointerLocal; + +template +SecurityItem localItem(typename T::type v) { + typedef typename T::type P; + struct Impl : SecurityItem::Impl { + P m_v; + Impl(P v) : m_v(v) {} + virtual ~Impl() { + LocalFree(reinterpret_cast(m_v)); + } + }; + return SecurityItem(v, std::unique_ptr(new Impl { v })); +} + +Sid allocatedSid(PSID v) { + struct Impl : Sid::Impl { + PSID m_v; + Impl(PSID v) : m_v(v) {} + virtual ~Impl() { + if (m_v != nullptr) { + FreeSid(m_v); + } + } + }; + return Sid(v, std::unique_ptr(new Impl { v })); +} + +} // anonymous namespace + +// Returns a handle to the thread's effective security token. If the thread +// is impersonating another user, its token is returned, and otherwise, the +// process' security token is opened. The handle is opened with TOKEN_QUERY. +static OwnedHandle openSecurityTokenForQuery() { + HANDLE token = nullptr; + // It is unclear to me whether OpenAsSelf matters for winpty, or what the + // most appropriate value is. + if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, + /*OpenAsSelf=*/FALSE, &token)) { + if (GetLastError() != ERROR_NO_TOKEN) { + throwWindowsError(L"OpenThreadToken failed"); + } + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) { + throwWindowsError(L"OpenProcessToken failed"); + } + } + ASSERT(token != nullptr && + "OpenThreadToken/OpenProcessToken token is NULL"); + return OwnedHandle(token); +} + +// Returns the TokenOwner of the thread's effective security token. +Sid getOwnerSid() { + struct Impl : Sid::Impl { + std::unique_ptr buffer; + }; + + OwnedHandle token = openSecurityTokenForQuery(); + DWORD actual = 0; + BOOL success; + success = GetTokenInformation(token.get(), TokenOwner, + nullptr, 0, &actual); + if (success) { + throwWinptyException(L"getOwnerSid: GetTokenInformation: " + L"expected ERROR_INSUFFICIENT_BUFFER"); + } else if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + throwWindowsError(L"getOwnerSid: GetTokenInformation: " + L"expected ERROR_INSUFFICIENT_BUFFER"); + } + std::unique_ptr impl(new Impl); + impl->buffer = std::unique_ptr(new char[actual]); + success = GetTokenInformation(token.get(), TokenOwner, + impl->buffer.get(), actual, &actual); + if (!success) { + throwWindowsError(L"getOwnerSid: GetTokenInformation"); + } + TOKEN_OWNER tmp; + ASSERT(actual >= sizeof(tmp)); + std::copy( + impl->buffer.get(), + impl->buffer.get() + sizeof(tmp), + reinterpret_cast(&tmp)); + return Sid(tmp.Owner, std::move(impl)); +} + +Sid wellKnownSid( + const wchar_t *debuggingName, + SID_IDENTIFIER_AUTHORITY authority, + BYTE authorityCount, + DWORD subAuthority0/*=0*/, + DWORD subAuthority1/*=0*/) { + PSID psid = nullptr; + if (!AllocateAndInitializeSid(&authority, authorityCount, + subAuthority0, + subAuthority1, + 0, 0, 0, 0, 0, 0, + &psid)) { + const auto err = GetLastError(); + const auto msg = + std::wstring(L"wellKnownSid: error getting ") + + debuggingName + L" SID"; + throwWindowsError(msg.c_str(), err); + } + return allocatedSid(psid); +} + +Sid builtinAdminsSid() { + // S-1-5-32-544 + SID_IDENTIFIER_AUTHORITY authority = { SECURITY_NT_AUTHORITY }; + return wellKnownSid(L"BUILTIN\\Administrators group", + authority, 2, + SECURITY_BUILTIN_DOMAIN_RID, // 32 + DOMAIN_ALIAS_RID_ADMINS); // 544 +} + +Sid localSystemSid() { + // S-1-5-18 + SID_IDENTIFIER_AUTHORITY authority = { SECURITY_NT_AUTHORITY }; + return wellKnownSid(L"LocalSystem account", + authority, 1, + SECURITY_LOCAL_SYSTEM_RID); // 18 +} + +Sid everyoneSid() { + // S-1-1-0 + SID_IDENTIFIER_AUTHORITY authority = { SECURITY_WORLD_SID_AUTHORITY }; + return wellKnownSid(L"Everyone account", + authority, 1, + SECURITY_WORLD_RID); // 0 +} + +static SecurityDescriptor finishSecurityDescriptor( + size_t daclEntryCount, + EXPLICIT_ACCESSW *daclEntries, + Acl &outAcl) { + { + PACL aclRaw = nullptr; + DWORD aclError = + SetEntriesInAclW(daclEntryCount, + daclEntries, + nullptr, &aclRaw); + if (aclError != ERROR_SUCCESS) { + WStringBuilder sb(64); + sb << L"finishSecurityDescriptor: " + << L"SetEntriesInAcl failed: " << aclError; + throwWinptyException(sb.c_str()); + } + outAcl = localItem(aclRaw); + } + + const PSECURITY_DESCRIPTOR sdRaw = + reinterpret_cast( + LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH)); + if (sdRaw == nullptr) { + throwWinptyException(L"finishSecurityDescriptor: LocalAlloc failed"); + } + SecurityDescriptor sd = localItem(sdRaw); + if (!InitializeSecurityDescriptor(sdRaw, SECURITY_DESCRIPTOR_REVISION)) { + throwWindowsError( + L"finishSecurityDescriptor: InitializeSecurityDescriptor"); + } + if (!SetSecurityDescriptorDacl(sdRaw, TRUE, outAcl.get(), FALSE)) { + throwWindowsError( + L"finishSecurityDescriptor: SetSecurityDescriptorDacl"); + } + + return std::move(sd); +} + +// Create a security descriptor that grants full control to the local system +// account, built-in administrators, and the owner. +SecurityDescriptor +createPipeSecurityDescriptorOwnerFullControl() { + + struct Impl : SecurityDescriptor::Impl { + Sid localSystem; + Sid builtinAdmins; + Sid owner; + std::array daclEntries = {}; + Acl dacl; + SecurityDescriptor value; + }; + + std::unique_ptr impl(new Impl); + impl->localSystem = localSystemSid(); + impl->builtinAdmins = builtinAdminsSid(); + impl->owner = getOwnerSid(); + + for (auto &ea : impl->daclEntries) { + ea.grfAccessPermissions = GENERIC_ALL; + ea.grfAccessMode = SET_ACCESS; + ea.grfInheritance = NO_INHERITANCE; + ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; + } + impl->daclEntries[0].Trustee.ptstrName = + reinterpret_cast(impl->localSystem.get()); + impl->daclEntries[1].Trustee.ptstrName = + reinterpret_cast(impl->builtinAdmins.get()); + impl->daclEntries[2].Trustee.ptstrName = + reinterpret_cast(impl->owner.get()); + + impl->value = finishSecurityDescriptor( + impl->daclEntries.size(), + impl->daclEntries.data(), + impl->dacl); + + const auto retValue = impl->value.get(); + return SecurityDescriptor(retValue, std::move(impl)); +} + +SecurityDescriptor +createPipeSecurityDescriptorOwnerFullControlEveryoneWrite() { + + struct Impl : SecurityDescriptor::Impl { + Sid localSystem; + Sid builtinAdmins; + Sid owner; + Sid everyone; + std::array daclEntries = {}; + Acl dacl; + SecurityDescriptor value; + }; + + std::unique_ptr impl(new Impl); + impl->localSystem = localSystemSid(); + impl->builtinAdmins = builtinAdminsSid(); + impl->owner = getOwnerSid(); + impl->everyone = everyoneSid(); + + for (auto &ea : impl->daclEntries) { + ea.grfAccessPermissions = GENERIC_ALL; + ea.grfAccessMode = SET_ACCESS; + ea.grfInheritance = NO_INHERITANCE; + ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; + } + impl->daclEntries[0].Trustee.ptstrName = + reinterpret_cast(impl->localSystem.get()); + impl->daclEntries[1].Trustee.ptstrName = + reinterpret_cast(impl->builtinAdmins.get()); + impl->daclEntries[2].Trustee.ptstrName = + reinterpret_cast(impl->owner.get()); + impl->daclEntries[3].Trustee.ptstrName = + reinterpret_cast(impl->everyone.get()); + // Avoid using FILE_GENERIC_WRITE because it includes FILE_APPEND_DATA, + // which is equal to FILE_CREATE_PIPE_INSTANCE. Instead, include all the + // flags that comprise FILE_GENERIC_WRITE, except for the one. + impl->daclEntries[3].grfAccessPermissions = + FILE_GENERIC_READ | + FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA | FILE_WRITE_EA | + STANDARD_RIGHTS_WRITE | SYNCHRONIZE; + + impl->value = finishSecurityDescriptor( + impl->daclEntries.size(), + impl->daclEntries.data(), + impl->dacl); + + const auto retValue = impl->value.get(); + return SecurityDescriptor(retValue, std::move(impl)); +} + +SecurityDescriptor getObjectSecurityDescriptor(HANDLE handle) { + PACL dacl = nullptr; + PSECURITY_DESCRIPTOR sd = nullptr; + const DWORD errCode = GetSecurityInfo(handle, SE_KERNEL_OBJECT, + OWNER_SECURITY_INFORMATION | + GROUP_SECURITY_INFORMATION | + DACL_SECURITY_INFORMATION, + nullptr, nullptr, &dacl, nullptr, &sd); + if (errCode != ERROR_SUCCESS) { + throwWindowsError(L"GetSecurityInfo failed"); + } + return localItem(sd); +} + +// The (SID/SD)<->string conversion APIs are useful for testing/debugging, so +// create convenient accessor functions for them. They're too slow for +// ordinary use. The APIs exist in XP and up, but the MinGW headers only +// declare the SID<->string APIs, not the SD APIs. MinGW also gets the +// prototype wrong for ConvertStringSidToSidW (LPWSTR instead of LPCWSTR) and +// requires WINVER to be defined. MSVC and MinGW-w64 get everything right, but +// for consistency, use LoadLibrary/GetProcAddress for all four APIs. + +typedef BOOL WINAPI ConvertStringSidToSidW_t( + LPCWSTR StringSid, + PSID *Sid); + +typedef BOOL WINAPI ConvertSidToStringSidW_t( + PSID Sid, + LPWSTR *StringSid); + +typedef BOOL WINAPI ConvertStringSecurityDescriptorToSecurityDescriptorW_t( + LPCWSTR StringSecurityDescriptor, + DWORD StringSDRevision, + PSECURITY_DESCRIPTOR *SecurityDescriptor, + PULONG SecurityDescriptorSize); + +typedef BOOL WINAPI ConvertSecurityDescriptorToStringSecurityDescriptorW_t( + PSECURITY_DESCRIPTOR SecurityDescriptor, + DWORD RequestedStringSDRevision, + SECURITY_INFORMATION SecurityInformation, + LPWSTR *StringSecurityDescriptor, + PULONG StringSecurityDescriptorLen); + +#define GET_MODULE_PROC(mod, funcName) \ + const auto p##funcName = \ + reinterpret_cast( \ + mod.proc(#funcName)); \ + if (p##funcName == nullptr) { \ + throwWinptyException( \ + L"" L ## #funcName L" API is missing from ADVAPI32.DLL"); \ + } + +const DWORD kSDDL_REVISION_1 = 1; + +std::wstring sidToString(PSID sid) { + OsModule advapi32(L"advapi32.dll"); + GET_MODULE_PROC(advapi32, ConvertSidToStringSidW); + wchar_t *sidString = NULL; + BOOL success = pConvertSidToStringSidW(sid, &sidString); + if (!success) { + throwWindowsError(L"ConvertSidToStringSidW failed"); + } + PointerLocal freer(sidString); + return std::wstring(sidString); +} + +Sid stringToSid(const std::wstring &str) { + // Cast the string from const wchar_t* to LPWSTR because the function is + // incorrectly prototyped in the MinGW sddl.h header. The API does not + // modify the string -- it is correctly prototyped as taking LPCWSTR in + // MinGW-w64, MSVC, and MSDN. + OsModule advapi32(L"advapi32.dll"); + GET_MODULE_PROC(advapi32, ConvertStringSidToSidW); + PSID psid = nullptr; + BOOL success = pConvertStringSidToSidW(const_cast(str.c_str()), + &psid); + if (!success) { + const auto err = GetLastError(); + throwWindowsError( + (std::wstring(L"ConvertStringSidToSidW failed on \"") + + str + L'"').c_str(), + err); + } + return localItem(psid); +} + +SecurityDescriptor stringToSd(const std::wstring &str) { + OsModule advapi32(L"advapi32.dll"); + GET_MODULE_PROC(advapi32, ConvertStringSecurityDescriptorToSecurityDescriptorW); + PSECURITY_DESCRIPTOR desc = nullptr; + if (!pConvertStringSecurityDescriptorToSecurityDescriptorW( + str.c_str(), kSDDL_REVISION_1, &desc, nullptr)) { + const auto err = GetLastError(); + throwWindowsError( + (std::wstring(L"ConvertStringSecurityDescriptorToSecurityDescriptorW failed on \"") + + str + L'"').c_str(), + err); + } + return localItem(desc); +} + +std::wstring sdToString(PSECURITY_DESCRIPTOR sd) { + OsModule advapi32(L"advapi32.dll"); + GET_MODULE_PROC(advapi32, ConvertSecurityDescriptorToStringSecurityDescriptorW); + wchar_t *sdString = nullptr; + if (!pConvertSecurityDescriptorToStringSecurityDescriptorW( + sd, + kSDDL_REVISION_1, + OWNER_SECURITY_INFORMATION | + GROUP_SECURITY_INFORMATION | + DACL_SECURITY_INFORMATION, + &sdString, + nullptr)) { + throwWindowsError( + L"ConvertSecurityDescriptorToStringSecurityDescriptor failed"); + } + PointerLocal freer(sdString); + return std::wstring(sdString); +} + +// Vista added a useful flag to CreateNamedPipe, PIPE_REJECT_REMOTE_CLIENTS, +// that rejects remote connections. Return this flag on Vista, or return 0 +// otherwise. +DWORD rejectRemoteClientsPipeFlag() { + if (isAtLeastWindowsVista()) { + // MinGW lacks this flag; MinGW-w64 has it. + const DWORD kPIPE_REJECT_REMOTE_CLIENTS = 8; + return kPIPE_REJECT_REMOTE_CLIENTS; + } else { + trace("Omitting PIPE_REJECT_REMOTE_CLIENTS on pre-Vista OS"); + return 0; + } +} + +typedef BOOL WINAPI GetNamedPipeClientProcessId_t( + HANDLE Pipe, + PULONG ClientProcessId); + +std::tuple +getNamedPipeClientProcessId(HANDLE serverPipe) { + OsModule kernel32(L"kernel32.dll"); + const auto pGetNamedPipeClientProcessId = + reinterpret_cast( + kernel32.proc("GetNamedPipeClientProcessId")); + if (pGetNamedPipeClientProcessId == nullptr) { + return std::make_tuple( + GetNamedPipeClientProcessId_Result::UnsupportedOs, 0, 0); + } + ULONG pid = 0; + if (!pGetNamedPipeClientProcessId(serverPipe, &pid)) { + return std::make_tuple( + GetNamedPipeClientProcessId_Result::Failure, 0, GetLastError()); + } + return std::make_tuple( + GetNamedPipeClientProcessId_Result::Success, + static_cast(pid), + 0); +} diff --git a/src/libs/3rdparty/winpty/src/shared/WindowsSecurity.h b/src/libs/3rdparty/winpty/src/shared/WindowsSecurity.h new file mode 100644 index 0000000000..5f9d53aff6 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/WindowsSecurity.h @@ -0,0 +1,104 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_WINDOWS_SECURITY_H +#define WINPTY_WINDOWS_SECURITY_H + +#include +#include + +#include +#include +#include +#include + +// PSID and PSECURITY_DESCRIPTOR are both pointers to void, but we want +// Sid and SecurityDescriptor to be different types. +struct SidTag { typedef PSID type; }; +struct AclTag { typedef PACL type; }; +struct SecurityDescriptorTag { typedef PSECURITY_DESCRIPTOR type; }; + +template +class SecurityItem { +public: + struct Impl { + virtual ~Impl() {} + }; + +private: + typedef typename T::type P; + P m_v; + std::unique_ptr m_pimpl; + +public: + P get() const { return m_v; } + operator bool() const { return m_v != nullptr; } + + SecurityItem() : m_v(nullptr) {} + SecurityItem(P v, std::unique_ptr &&pimpl) : + m_v(v), m_pimpl(std::move(pimpl)) {} + SecurityItem(SecurityItem &&other) : + m_v(other.m_v), m_pimpl(std::move(other.m_pimpl)) { + other.m_v = nullptr; + } + SecurityItem &operator=(SecurityItem &&other) { + m_v = other.m_v; + other.m_v = nullptr; + m_pimpl = std::move(other.m_pimpl); + return *this; + } +}; + +typedef SecurityItem Sid; +typedef SecurityItem Acl; +typedef SecurityItem SecurityDescriptor; + +Sid getOwnerSid(); +Sid wellKnownSid( + const wchar_t *debuggingName, + SID_IDENTIFIER_AUTHORITY authority, + BYTE authorityCount, + DWORD subAuthority0=0, + DWORD subAuthority1=0); +Sid builtinAdminsSid(); +Sid localSystemSid(); +Sid everyoneSid(); + +SecurityDescriptor createPipeSecurityDescriptorOwnerFullControl(); +SecurityDescriptor createPipeSecurityDescriptorOwnerFullControlEveryoneWrite(); +SecurityDescriptor getObjectSecurityDescriptor(HANDLE handle); + +std::wstring sidToString(PSID sid); +Sid stringToSid(const std::wstring &str); +SecurityDescriptor stringToSd(const std::wstring &str); +std::wstring sdToString(PSECURITY_DESCRIPTOR sd); + +DWORD rejectRemoteClientsPipeFlag(); + +enum class GetNamedPipeClientProcessId_Result { + Success, + Failure, + UnsupportedOs, +}; + +std::tuple +getNamedPipeClientProcessId(HANDLE serverPipe); + +#endif // WINPTY_WINDOWS_SECURITY_H diff --git a/src/libs/3rdparty/winpty/src/shared/WindowsVersion.cc b/src/libs/3rdparty/winpty/src/shared/WindowsVersion.cc new file mode 100644 index 0000000000..d89b00d838 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/WindowsVersion.cc @@ -0,0 +1,252 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "WindowsVersion.h" + +#include +#include + +#include +#include +#include + +#include "DebugClient.h" +#include "OsModule.h" +#include "StringBuilder.h" +#include "StringUtil.h" +#include "WinptyAssert.h" +#include "WinptyException.h" + +namespace { + +typedef std::tuple Version; + +// This function can only return a version up to 6.2 unless the executable is +// manifested for a newer version of Windows. See the MSDN documentation for +// GetVersionEx. +OSVERSIONINFOEX getWindowsVersionInfo() { + // Allow use of deprecated functions (i.e. GetVersionEx). We need to use + // GetVersionEx for the old MinGW toolchain and with MSVC when it targets XP. + // Having two code paths makes code harder to test, and it's not obvious how + // to detect the presence of a new enough SDK. (Including ntverp.h and + // examining VER_PRODUCTBUILD apparently works, but even then, MinGW-w64 and + // MSVC seem to use different version numbers.) +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4996) +#endif + OSVERSIONINFOEX info = {}; + info.dwOSVersionInfoSize = sizeof(info); + const auto success = GetVersionEx(reinterpret_cast(&info)); + ASSERT(success && "GetVersionEx failed"); + return info; +#ifdef _MSC_VER +#pragma warning(pop) +#endif +} + +Version getWindowsVersion() { + const auto info = getWindowsVersionInfo(); + return Version(info.dwMajorVersion, info.dwMinorVersion); +} + +struct ModuleNotFound : WinptyException { + virtual const wchar_t *what() const WINPTY_NOEXCEPT override { + return L"ModuleNotFound"; + } +}; + +// Throws WinptyException on error. +std::wstring getSystemDirectory() { + wchar_t systemDirectory[MAX_PATH]; + const UINT size = GetSystemDirectoryW(systemDirectory, MAX_PATH); + if (size == 0) { + throwWindowsError(L"GetSystemDirectory failed"); + } else if (size >= MAX_PATH) { + throwWinptyException( + L"GetSystemDirectory: path is longer than MAX_PATH"); + } + return systemDirectory; +} + +#define GET_VERSION_DLL_API(name) \ + const auto p ## name = \ + reinterpret_cast( \ + versionDll.proc(#name)); \ + if (p ## name == nullptr) { \ + throwWinptyException(L ## #name L" is missing"); \ + } + +// Throws WinptyException on error. +VS_FIXEDFILEINFO getFixedFileInfo(const std::wstring &path) { + // version.dll is not a conventional KnownDll, so if we link to it, there's + // a danger of accidentally loading a malicious DLL. In a more typical + // application, perhaps we'd guard against this security issue by + // controlling which directories this code runs in (e.g. *not* the + // "Downloads" directory), but that's harder for the winpty library. + OsModule versionDll( + (getSystemDirectory() + L"\\version.dll").c_str(), + OsModule::LoadErrorBehavior::Throw); + GET_VERSION_DLL_API(GetFileVersionInfoSizeW); + GET_VERSION_DLL_API(GetFileVersionInfoW); + GET_VERSION_DLL_API(VerQueryValueW); + DWORD size = pGetFileVersionInfoSizeW(path.c_str(), nullptr); + if (!size) { + // I see ERROR_FILE_NOT_FOUND on Win7 and + // ERROR_RESOURCE_DATA_NOT_FOUND on WinXP. + if (GetLastError() == ERROR_FILE_NOT_FOUND || + GetLastError() == ERROR_RESOURCE_DATA_NOT_FOUND) { + throw ModuleNotFound(); + } else { + throwWindowsError( + (L"GetFileVersionInfoSizeW failed on " + path).c_str()); + } + } + std::unique_ptr versionBuffer(new char[size]); + if (!pGetFileVersionInfoW(path.c_str(), 0, size, versionBuffer.get())) { + throwWindowsError((L"GetFileVersionInfoW failed on " + path).c_str()); + } + VS_FIXEDFILEINFO *versionInfo = nullptr; + UINT versionInfoSize = 0; + if (!pVerQueryValueW( + versionBuffer.get(), L"\\", + reinterpret_cast(&versionInfo), &versionInfoSize) || + versionInfo == nullptr || + versionInfoSize != sizeof(VS_FIXEDFILEINFO) || + versionInfo->dwSignature != 0xFEEF04BD) { + throwWinptyException((L"VerQueryValueW failed on " + path).c_str()); + } + return *versionInfo; +} + +uint64_t productVersionFromInfo(const VS_FIXEDFILEINFO &info) { + return (static_cast(info.dwProductVersionMS) << 32) | + (static_cast(info.dwProductVersionLS)); +} + +uint64_t fileVersionFromInfo(const VS_FIXEDFILEINFO &info) { + return (static_cast(info.dwFileVersionMS) << 32) | + (static_cast(info.dwFileVersionLS)); +} + +std::string versionToString(uint64_t version) { + StringBuilder b(32); + b << ((uint16_t)(version >> 48)); + b << '.'; + b << ((uint16_t)(version >> 32)); + b << '.'; + b << ((uint16_t)(version >> 16)); + b << '.'; + b << ((uint16_t)(version >> 0)); + return b.str_moved(); +} + +} // anonymous namespace + +// Returns true for Windows Vista (or Windows Server 2008) or newer. +bool isAtLeastWindowsVista() { + return getWindowsVersion() >= Version(6, 0); +} + +// Returns true for Windows 7 (or Windows Server 2008 R2) or newer. +bool isAtLeastWindows7() { + return getWindowsVersion() >= Version(6, 1); +} + +// Returns true for Windows 8 (or Windows Server 2012) or newer. +bool isAtLeastWindows8() { + return getWindowsVersion() >= Version(6, 2); +} + +#define WINPTY_IA32 1 +#define WINPTY_X64 2 + +#if defined(_M_IX86) || defined(__i386__) +#define WINPTY_ARCH WINPTY_IA32 +#elif defined(_M_X64) || defined(__x86_64__) +#define WINPTY_ARCH WINPTY_X64 +#endif + +typedef BOOL WINAPI IsWow64Process_t(HANDLE hProcess, PBOOL Wow64Process); + +void dumpWindowsVersion() { + if (!isTracingEnabled()) { + return; + } + const auto info = getWindowsVersionInfo(); + StringBuilder b; + b << info.dwMajorVersion << '.' << info.dwMinorVersion + << '.' << info.dwBuildNumber << ' ' + << "SP" << info.wServicePackMajor << '.' << info.wServicePackMinor + << ' '; + switch (info.wProductType) { + case VER_NT_WORKSTATION: b << "Client"; break; + case VER_NT_DOMAIN_CONTROLLER: b << "DomainController"; break; + case VER_NT_SERVER: b << "Server"; break; + default: + b << "product=" << info.wProductType; break; + } + b << ' '; +#if WINPTY_ARCH == WINPTY_IA32 + b << "IA32"; + OsModule kernel32(L"kernel32.dll"); + IsWow64Process_t *pIsWow64Process = + reinterpret_cast( + kernel32.proc("IsWow64Process")); + if (pIsWow64Process != nullptr) { + BOOL result = false; + const BOOL success = pIsWow64Process(GetCurrentProcess(), &result); + if (!success) { + b << " WOW64:error"; + } else if (success && result) { + b << " WOW64"; + } + } else { + b << " WOW64:missingapi"; + } +#elif WINPTY_ARCH == WINPTY_X64 + b << "X64"; +#endif + const auto dllVersion = [](const wchar_t *dllPath) -> std::string { + try { + const auto info = getFixedFileInfo(dllPath); + StringBuilder fb(64); + fb << utf8FromWide(dllPath) << ':'; + fb << "F:" << versionToString(fileVersionFromInfo(info)) << '/' + << "P:" << versionToString(productVersionFromInfo(info)); + return fb.str_moved(); + } catch (const ModuleNotFound&) { + return utf8FromWide(dllPath) + ":none"; + } catch (const WinptyException &e) { + trace("Error getting %s version: %s", + utf8FromWide(dllPath).c_str(), utf8FromWide(e.what()).c_str()); + return utf8FromWide(dllPath) + ":error"; + } + }; + b << ' ' << dllVersion(L"kernel32.dll"); + // ConEmu provides a DLL that hooks many Windows APIs, especially console + // APIs. Its existence and version number could be useful in debugging. +#if WINPTY_ARCH == WINPTY_IA32 + b << ' ' << dllVersion(L"ConEmuHk.dll"); +#elif WINPTY_ARCH == WINPTY_X64 + b << ' ' << dllVersion(L"ConEmuHk64.dll"); +#endif + trace("Windows version: %s", b.c_str()); +} diff --git a/src/libs/3rdparty/winpty/src/shared/WindowsVersion.h b/src/libs/3rdparty/winpty/src/shared/WindowsVersion.h new file mode 100644 index 0000000000..a80798417e --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/WindowsVersion.h @@ -0,0 +1,29 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_SHARED_WINDOWS_VERSION_H +#define WINPTY_SHARED_WINDOWS_VERSION_H + +bool isAtLeastWindowsVista(); +bool isAtLeastWindows7(); +bool isAtLeastWindows8(); +void dumpWindowsVersion(); + +#endif // WINPTY_SHARED_WINDOWS_VERSION_H diff --git a/src/libs/3rdparty/winpty/src/shared/WinptyAssert.cc b/src/libs/3rdparty/winpty/src/shared/WinptyAssert.cc new file mode 100644 index 0000000000..1ff0de475a --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/WinptyAssert.cc @@ -0,0 +1,55 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "WinptyAssert.h" + +#include +#include + +#include "DebugClient.h" + +void assertTrace(const char *file, int line, const char *cond) { + trace("Assertion failed: %s, file %s, line %d", + cond, file, line); +} + +#ifdef WINPTY_AGENT_ASSERT + +void agentShutdown() { + HWND hwnd = GetConsoleWindow(); + if (hwnd != NULL) { + PostMessage(hwnd, WM_CLOSE, 0, 0); + Sleep(30000); + trace("Agent shutdown: WM_CLOSE did not end agent process"); + } else { + trace("Agent shutdown: GetConsoleWindow() is NULL"); + } + // abort() prints a message to the console, and if it is frozen, then the + // process would hang, so instead use exit(). (We shouldn't ever get here, + // though, because the WM_CLOSE message should have ended this process.) + exit(1); +} + +void agentAssertFail(const char *file, int line, const char *cond) { + assertTrace(file, line, cond); + agentShutdown(); +} + +#endif diff --git a/src/libs/3rdparty/winpty/src/shared/WinptyAssert.h b/src/libs/3rdparty/winpty/src/shared/WinptyAssert.h new file mode 100644 index 0000000000..b2b8b5e64c --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/WinptyAssert.h @@ -0,0 +1,64 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_ASSERT_H +#define WINPTY_ASSERT_H + +#ifdef WINPTY_AGENT_ASSERT + +void agentShutdown(); +void agentAssertFail(const char *file, int line, const char *cond); + +// Calling the standard assert() function does not work in the agent because +// the error message would be printed to the console, and the only way the +// user can see the console is via a working agent! Moreover, the console may +// be frozen, so attempting to write to it would block forever. This custom +// assert function instead sends the message to the DebugServer, then attempts +// to close the console, then quietly exits. +#define ASSERT(cond) \ + do { \ + if (!(cond)) { \ + agentAssertFail(__FILE__, __LINE__, #cond); \ + } \ + } while(0) + +#else + +void assertTrace(const char *file, int line, const char *cond); + +// In the other targets, log the assert failure to the debugserver, then fail +// using the ordinary assert mechanism. In case assert is compiled out, fail +// using abort. The amount of code inlined is unfortunate, but asserts aren't +// used much outside the agent. +#include +#include +#define ASSERT_CONDITION(cond) (false && (cond)) +#define ASSERT(cond) \ + do { \ + if (!(cond)) { \ + assertTrace(__FILE__, __LINE__, #cond); \ + assert(ASSERT_CONDITION(#cond)); \ + abort(); \ + } \ + } while(0) + +#endif + +#endif // WINPTY_ASSERT_H diff --git a/src/libs/3rdparty/winpty/src/shared/WinptyException.cc b/src/libs/3rdparty/winpty/src/shared/WinptyException.cc new file mode 100644 index 0000000000..d0d48823d2 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/WinptyException.cc @@ -0,0 +1,57 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "WinptyException.h" + +#include +#include + +#include "StringBuilder.h" + +namespace { + +class ExceptionImpl : public WinptyException { +public: + ExceptionImpl(const wchar_t *what) : + m_what(std::make_shared(what)) {} + virtual const wchar_t *what() const WINPTY_NOEXCEPT override { + return m_what->c_str(); + } +private: + // Using a shared_ptr ensures that copying the object raises no exception. + std::shared_ptr m_what; +}; + +} // anonymous namespace + +void throwWinptyException(const wchar_t *what) { + throw ExceptionImpl(what); +} + +void throwWindowsError(const wchar_t *prefix, DWORD errorCode) { + WStringBuilder sb(64); + if (prefix != nullptr) { + sb << prefix << L": "; + } + // It might make sense to use FormatMessage here, but IIRC, its API is hard + // to figure out. + sb << L"Windows error " << errorCode; + throwWinptyException(sb.c_str()); +} diff --git a/src/libs/3rdparty/winpty/src/shared/WinptyException.h b/src/libs/3rdparty/winpty/src/shared/WinptyException.h new file mode 100644 index 0000000000..ec353369e5 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/WinptyException.h @@ -0,0 +1,43 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_EXCEPTION_H +#define WINPTY_EXCEPTION_H + +#include + +#if defined(__GNUC__) +#define WINPTY_NOEXCEPT noexcept +#elif defined(_MSC_VER) && _MSC_VER >= 1900 +#define WINPTY_NOEXCEPT noexcept +#else +#define WINPTY_NOEXCEPT +#endif + +class WinptyException { +public: + virtual const wchar_t *what() const WINPTY_NOEXCEPT = 0; + virtual ~WinptyException() {} +}; + +void throwWinptyException(const wchar_t *what); +void throwWindowsError(const wchar_t *prefix, DWORD error=GetLastError()); + +#endif // WINPTY_EXCEPTION_H diff --git a/src/libs/3rdparty/winpty/src/shared/WinptyVersion.cc b/src/libs/3rdparty/winpty/src/shared/WinptyVersion.cc new file mode 100644 index 0000000000..76bb8a584d --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/WinptyVersion.cc @@ -0,0 +1,42 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "WinptyVersion.h" + +#include +#include + +#include "DebugClient.h" + +// This header is auto-generated by either the Makefile (Unix) or +// UpdateGenVersion.bat (gyp). It is placed in a 'gen' directory, which is +// added to the search path. +#include "GenVersion.h" + +void dumpVersionToStdout() { + printf("winpty version %s\n", GenVersion_Version); + printf("commit %s\n", GenVersion_Commit); +} + +void dumpVersionToTrace() { + trace("winpty version %s (commit %s)", + GenVersion_Version, + GenVersion_Commit); +} diff --git a/src/libs/3rdparty/winpty/src/shared/WinptyVersion.h b/src/libs/3rdparty/winpty/src/shared/WinptyVersion.h new file mode 100644 index 0000000000..e6224d7b84 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/WinptyVersion.h @@ -0,0 +1,27 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_VERSION_H +#define WINPTY_VERSION_H + +void dumpVersionToStdout(); +void dumpVersionToTrace(); + +#endif // WINPTY_VERSION_H diff --git a/src/libs/3rdparty/winpty/src/shared/winpty_snprintf.h b/src/libs/3rdparty/winpty/src/shared/winpty_snprintf.h new file mode 100644 index 0000000000..e716f245e8 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/winpty_snprintf.h @@ -0,0 +1,99 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_SNPRINTF_H +#define WINPTY_SNPRINTF_H + +#include +#include +#include + +#include "WinptyAssert.h" + +#if defined(__CYGWIN__) || defined(__MSYS__) +#define WINPTY_SNPRINTF_FORMAT(fmtarg, vararg) \ + __attribute__((format(printf, (fmtarg), ((vararg))))) +#elif defined(__GNUC__) +#define WINPTY_SNPRINTF_FORMAT(fmtarg, vararg) \ + __attribute__((format(ms_printf, (fmtarg), ((vararg))))) +#else +#define WINPTY_SNPRINTF_FORMAT(fmtarg, vararg) +#endif + +// Returns a value between 0 and size - 1 (inclusive) on success. Returns -1 +// on failure (including truncation). The output buffer is always +// NUL-terminated. +inline int +winpty_vsnprintf(char *out, size_t size, const char *fmt, va_list ap) { + ASSERT(size > 0); + out[0] = '\0'; +#if defined(_MSC_VER) && _MSC_VER < 1900 + // MSVC 2015 added a C99-conforming vsnprintf. + int count = _vsnprintf_s(out, size, _TRUNCATE, fmt, ap); +#else + // MinGW configurations frequently provide a vsnprintf function that simply + // calls one of the MS _vsnprintf* functions, which are not C99 conformant. + int count = vsnprintf(out, size, fmt, ap); +#endif + if (count < 0 || static_cast(count) >= size) { + // On truncation, some *printf* implementations return the + // non-truncated size, but other implementations returns -1. Return + // -1 for consistency. + count = -1; + // Guarantee NUL termination. + out[size - 1] = '\0'; + } else { + // Guarantee NUL termination. + out[count] = '\0'; + } + return count; +} + +// Wraps winpty_vsnprintf. +inline int winpty_snprintf(char *out, size_t size, const char *fmt, ...) + WINPTY_SNPRINTF_FORMAT(3, 4); +inline int winpty_snprintf(char *out, size_t size, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + const int count = winpty_vsnprintf(out, size, fmt, ap); + va_end(ap); + return count; +} + +// Wraps winpty_vsnprintf with automatic size determination. +template +int winpty_vsnprintf(char (&out)[size], const char *fmt, va_list ap) { + return winpty_vsnprintf(out, size, fmt, ap); +} + +// Wraps winpty_vsnprintf with automatic size determination. +template +int winpty_snprintf(char (&out)[size], const char *fmt, ...) + WINPTY_SNPRINTF_FORMAT(2, 3); +template +int winpty_snprintf(char (&out)[size], const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + const int count = winpty_vsnprintf(out, size, fmt, ap); + va_end(ap); + return count; +} + +#endif // WINPTY_SNPRINTF_H diff --git a/src/libs/3rdparty/winpty/src/subdir.mk b/src/libs/3rdparty/winpty/src/subdir.mk new file mode 100644 index 0000000000..9ae8031b08 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/subdir.mk @@ -0,0 +1,5 @@ +include src/agent/subdir.mk +include src/debugserver/subdir.mk +include src/libwinpty/subdir.mk +include src/tests/subdir.mk +include src/unix-adapter/subdir.mk diff --git a/src/libs/3rdparty/winpty/src/tests/subdir.mk b/src/libs/3rdparty/winpty/src/tests/subdir.mk new file mode 100644 index 0000000000..18799c4a5a --- /dev/null +++ b/src/libs/3rdparty/winpty/src/tests/subdir.mk @@ -0,0 +1,28 @@ +# Copyright (c) 2015 Ryan Prichard +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +build/%.exe : src/tests/%.cc build/winpty.dll + $(info Building $@) + @$(MINGW_CXX) $(MINGW_CXXFLAGS) $(MINGW_LDFLAGS) -o $@ $^ + +TEST_PROGRAMS = \ + build/trivial_test.exe + +-include $(TEST_PROGRAMS:.exe=.d) diff --git a/src/libs/3rdparty/winpty/src/tests/trivial_test.cc b/src/libs/3rdparty/winpty/src/tests/trivial_test.cc new file mode 100644 index 0000000000..2188a4befb --- /dev/null +++ b/src/libs/3rdparty/winpty/src/tests/trivial_test.cc @@ -0,0 +1,158 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include + +#include +#include +#include +#include +#include +#include + +#include "../include/winpty.h" +#include "../shared/DebugClient.h" + +static std::vector filterContent( + const std::vector &content) { + std::vector result; + auto it = content.begin(); + const auto itEnd = content.end(); + while (it < itEnd) { + if (*it == '\r') { + // Filter out carriage returns. Sometimes the output starts with + // a single CR; other times, it has multiple CRs. + it++; + } else if (*it == '\x1b' && (it + 1) < itEnd && *(it + 1) == '[') { + // Filter out escape sequences. They have no interior letters and + // end with a single letter. + it += 2; + while (it < itEnd && !isalpha(*it)) { + it++; + } + it++; + } else { + // Let everything else through. + result.push_back(*it); + it++; + } + } + return result; +} + +// Read bytes from the non-overlapped file handle until the file is closed or +// until an I/O error occurs. +static std::vector readAll(HANDLE handle) { + unsigned char buf[1024]; + std::vector result; + while (true) { + DWORD amount = 0; + BOOL ret = ReadFile(handle, buf, sizeof(buf), &amount, nullptr); + if (!ret || amount == 0) { + break; + } + result.insert(result.end(), buf, buf + amount); + } + return result; +} + +static void parentTest() { + wchar_t program[1024]; + wchar_t cmdline[1024]; + GetModuleFileNameW(nullptr, program, 1024); + + { + // XXX: We'd like to use swprintf, which is part of C99 and takes a + // size_t maxlen argument. MinGW-w64 has this function, as does MSVC. + // The old MinGW doesn't, though -- instead, it apparently provides an + // swprintf taking no maxlen argument. This *might* be a regression? + // (There is also no swnprintf, but that function is obsolescent with a + // correct swprintf, and it isn't in POSIX or ISO C.) + // + // Visual C++ 6 also provided this non-conformant swprintf, and I'm + // guessing MSVCRT.DLL does too. (My impression is that the old MinGW + // prefers to rely on MSVCRT.DLL for convenience?) + // + // I could compile differently for old MinGW, but what if it fixes its + // function later? Instead, use a workaround. It's starting to make + // sense to drop MinGW support in favor of MinGW-w64. This is too + // annoying. + // + // grepbait: OLD-MINGW / WINPTY_TARGET_MSYS1 + cmdline[0] = L'\0'; + wcscat(cmdline, L"\""); + wcscat(cmdline, program); + wcscat(cmdline, L"\" CHILD"); + } + // swnprintf(cmdline, sizeof(cmdline) / sizeof(cmdline[0]), + // L"\"%ls\" CHILD", program); + + auto agentCfg = winpty_config_new(0, nullptr); + assert(agentCfg != nullptr); + auto pty = winpty_open(agentCfg, nullptr); + assert(pty != nullptr); + winpty_config_free(agentCfg); + + HANDLE conin = CreateFileW( + winpty_conin_name(pty), + GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); + HANDLE conout = CreateFileW( + winpty_conout_name(pty), + GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr); + assert(conin != INVALID_HANDLE_VALUE); + assert(conout != INVALID_HANDLE_VALUE); + + auto spawnCfg = winpty_spawn_config_new( + WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, program, cmdline, + nullptr, nullptr, nullptr); + assert(spawnCfg != nullptr); + HANDLE process = nullptr; + BOOL spawnSuccess = winpty_spawn( + pty, spawnCfg, &process, nullptr, nullptr, nullptr); + assert(spawnSuccess && process != nullptr); + + auto content = readAll(conout); + content = filterContent(content); + + std::vector expectedContent = { + 'H', 'I', '\n', 'X', 'Y', '\n' + }; + DWORD exitCode = 0; + assert(GetExitCodeProcess(process, &exitCode) && exitCode == 42); + CloseHandle(process); + CloseHandle(conin); + CloseHandle(conout); + assert(content == expectedContent); + winpty_free(pty); +} + +static void childTest() { + printf("HI\nXY\n"); + exit(42); +} + +int main(int argc, char *argv[]) { + if (argc == 1) { + parentTest(); + } else { + childTest(); + } + return 0; +} diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/InputHandler.cc b/src/libs/3rdparty/winpty/src/unix-adapter/InputHandler.cc new file mode 100644 index 0000000000..39f1e09685 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/unix-adapter/InputHandler.cc @@ -0,0 +1,114 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "InputHandler.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "../shared/DebugClient.h" +#include "Util.h" +#include "WakeupFd.h" + +InputHandler::InputHandler( + HANDLE conin, int inputfd, WakeupFd &completionWakeup) : + m_conin(conin), + m_inputfd(inputfd), + m_completionWakeup(completionWakeup), + m_threadHasBeenJoined(false), + m_shouldShutdown(0), + m_threadCompleted(0) +{ + pthread_create(&m_thread, NULL, InputHandler::threadProcS, this); +} + +void InputHandler::shutdown() { + startShutdown(); + if (!m_threadHasBeenJoined) { + int ret = pthread_join(m_thread, NULL); + assert(ret == 0 && "pthread_join failed"); + m_threadHasBeenJoined = true; + } +} + +void InputHandler::threadProc() { + std::vector buffer(4096); + fd_set readfds; + FD_ZERO(&readfds); + while (true) { + // Handle shutdown. + m_wakeup.reset(); + if (m_shouldShutdown) { + trace("InputHandler: shutting down"); + break; + } + + // Block until data arrives. + { + const int max_fd = std::max(m_inputfd, m_wakeup.fd()); + FD_SET(m_inputfd, &readfds); + FD_SET(m_wakeup.fd(), &readfds); + selectWrapper("InputHandler", max_fd + 1, &readfds); + if (!FD_ISSET(m_inputfd, &readfds)) { + continue; + } + } + + const int numRead = read(m_inputfd, &buffer[0], buffer.size()); + if (numRead == -1 && errno == EINTR) { + // Apparently, this read is interrupted on Cygwin 1.7 by a SIGWINCH + // signal even though I set the SA_RESTART flag on the handler. + continue; + } + + // tty is closed, or the read failed for some unexpected reason. + if (numRead <= 0) { + trace("InputHandler: tty read failed: numRead=%d", numRead); + break; + } + + DWORD written = 0; + BOOL ret = WriteFile(m_conin, + &buffer[0], numRead, + &written, NULL); + if (!ret || written != static_cast(numRead)) { + if (!ret && GetLastError() == ERROR_BROKEN_PIPE) { + trace("InputHandler: pipe closed: written=%u", + static_cast(written)); + } else { + trace("InputHandler: write failed: " + "ret=%d lastError=0x%x numRead=%d written=%u", + ret, + static_cast(GetLastError()), + numRead, + static_cast(written)); + } + break; + } + } + m_threadCompleted = 1; + m_completionWakeup.set(); +} diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/InputHandler.h b/src/libs/3rdparty/winpty/src/unix-adapter/InputHandler.h new file mode 100644 index 0000000000..9c3f540d63 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/unix-adapter/InputHandler.h @@ -0,0 +1,56 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef UNIX_ADAPTER_INPUT_HANDLER_H +#define UNIX_ADAPTER_INPUT_HANDLER_H + +#include +#include +#include + +#include "WakeupFd.h" + +// Connect a Cygwin blocking fd to winpty CONIN. +class InputHandler { +public: + InputHandler(HANDLE conin, int inputfd, WakeupFd &completionWakeup); + ~InputHandler() { shutdown(); } + bool isComplete() { return m_threadCompleted; } + void startShutdown() { m_shouldShutdown = 1; m_wakeup.set(); } + void shutdown(); + +private: + static void *threadProcS(void *pvthis) { + reinterpret_cast(pvthis)->threadProc(); + return NULL; + } + void threadProc(); + + HANDLE m_conin; + int m_inputfd; + pthread_t m_thread; + WakeupFd &m_completionWakeup; + WakeupFd m_wakeup; + bool m_threadHasBeenJoined; + volatile sig_atomic_t m_shouldShutdown; + volatile sig_atomic_t m_threadCompleted; +}; + +#endif // UNIX_ADAPTER_INPUT_HANDLER_H diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/OutputHandler.cc b/src/libs/3rdparty/winpty/src/unix-adapter/OutputHandler.cc new file mode 100644 index 0000000000..573b8adced --- /dev/null +++ b/src/libs/3rdparty/winpty/src/unix-adapter/OutputHandler.cc @@ -0,0 +1,80 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "OutputHandler.h" + +#include +#include +#include +#include + +#include +#include + +#include "../shared/DebugClient.h" +#include "Util.h" +#include "WakeupFd.h" + +OutputHandler::OutputHandler( + HANDLE conout, int outputfd, WakeupFd &completionWakeup) : + m_conout(conout), + m_outputfd(outputfd), + m_completionWakeup(completionWakeup), + m_threadHasBeenJoined(false), + m_threadCompleted(0) +{ + pthread_create(&m_thread, NULL, OutputHandler::threadProcS, this); +} + +void OutputHandler::shutdown() { + if (!m_threadHasBeenJoined) { + int ret = pthread_join(m_thread, NULL); + assert(ret == 0 && "pthread_join failed"); + m_threadHasBeenJoined = true; + } +} + +void OutputHandler::threadProc() { + std::vector buffer(4096); + while (true) { + DWORD numRead = 0; + BOOL ret = ReadFile(m_conout, + &buffer[0], buffer.size(), + &numRead, NULL); + if (!ret || numRead == 0) { + if (!ret && GetLastError() == ERROR_BROKEN_PIPE) { + trace("OutputHandler: pipe closed: numRead=%u", + static_cast(numRead)); + } else { + trace("OutputHandler: read failed: " + "ret=%d lastError=0x%x numRead=%u", + ret, + static_cast(GetLastError()), + static_cast(numRead)); + } + break; + } + if (!writeAll(m_outputfd, &buffer[0], numRead)) { + break; + } + } + m_threadCompleted = 1; + m_completionWakeup.set(); +} diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/OutputHandler.h b/src/libs/3rdparty/winpty/src/unix-adapter/OutputHandler.h new file mode 100644 index 0000000000..48241c5538 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/unix-adapter/OutputHandler.h @@ -0,0 +1,53 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef UNIX_ADAPTER_OUTPUT_HANDLER_H +#define UNIX_ADAPTER_OUTPUT_HANDLER_H + +#include +#include +#include + +#include "WakeupFd.h" + +// Connect winpty CONOUT/CONERR to a Cygwin blocking fd. +class OutputHandler { +public: + OutputHandler(HANDLE conout, int outputfd, WakeupFd &completionWakeup); + ~OutputHandler() { shutdown(); } + bool isComplete() { return m_threadCompleted; } + void shutdown(); + +private: + static void *threadProcS(void *pvthis) { + reinterpret_cast(pvthis)->threadProc(); + return NULL; + } + void threadProc(); + + HANDLE m_conout; + int m_outputfd; + pthread_t m_thread; + WakeupFd &m_completionWakeup; + bool m_threadHasBeenJoined; + volatile sig_atomic_t m_threadCompleted; +}; + +#endif // UNIX_ADAPTER_OUTPUT_HANDLER_H diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/Util.cc b/src/libs/3rdparty/winpty/src/unix-adapter/Util.cc new file mode 100644 index 0000000000..e13f84a529 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/unix-adapter/Util.cc @@ -0,0 +1,86 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "Util.h" + +#include +#include +#include +#include +#include + +#include "../shared/DebugClient.h" + +// Write the entire buffer, restarting it as necessary. +bool writeAll(int fd, const void *buffer, size_t size) { + size_t written = 0; + while (written < size) { + int ret = write(fd, + reinterpret_cast(buffer) + written, + size - written); + if (ret == -1 && errno == EINTR) { + continue; + } + if (ret <= 0) { + trace("write failed: " + "fd=%d errno=%d size=%u written=%d ret=%d", + fd, + errno, + static_cast(size), + static_cast(written), + ret); + return false; + } + assert(static_cast(ret) <= size - written); + written += ret; + } + assert(written == size); + return true; +} + +bool writeStr(int fd, const char *str) { + return writeAll(fd, str, strlen(str)); +} + +void selectWrapper(const char *diagName, int nfds, fd_set *readfds) { + int ret = select(nfds, readfds, NULL, NULL, NULL); + if (ret < 0) { + if (errno == EINTR) { + FD_ZERO(readfds); + return; + } +#ifdef WINPTY_TARGET_MSYS1 + // The select system call sometimes fails with EAGAIN instead of EINTR. + // This apparantly only happens with the old Cygwin fork "MSYS" used in + // the mingw.org project. select is not supposed to fail with EAGAIN, + // and EAGAIN does not make much sense as an error code. (The whole + // point of select is to block.) + if (errno == EAGAIN) { + trace("%s select returned EAGAIN: interpreting like EINTR", + diagName); + FD_ZERO(readfds); + return; + } +#endif + fprintf(stderr, "Internal error: %s select failed: " + "error %d", diagName, errno); + abort(); + } +} diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/Util.h b/src/libs/3rdparty/winpty/src/unix-adapter/Util.h new file mode 100644 index 0000000000..cadb4c82a9 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/unix-adapter/Util.h @@ -0,0 +1,31 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef UNIX_ADAPTER_UTIL_H +#define UNIX_ADAPTER_UTIL_H + +#include +#include + +bool writeAll(int fd, const void *buffer, size_t size); +bool writeStr(int fd, const char *str); +void selectWrapper(const char *diagName, int nfds, fd_set *readfds); + +#endif // UNIX_ADAPTER_UTIL_H diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/WakeupFd.cc b/src/libs/3rdparty/winpty/src/unix-adapter/WakeupFd.cc new file mode 100644 index 0000000000..6b47379015 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/unix-adapter/WakeupFd.cc @@ -0,0 +1,70 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "WakeupFd.h" + +#include +#include +#include +#include +#include + +static void setFdNonBlock(int fd) { + int status = fcntl(fd, F_GETFL); + fcntl(fd, F_SETFL, status | O_NONBLOCK); +} + +WakeupFd::WakeupFd() { + int pipeFd[2]; + if (pipe(pipeFd) != 0) { + perror("Could not create internal wakeup pipe"); + abort(); + } + m_pipeReadFd = pipeFd[0]; + m_pipeWriteFd = pipeFd[1]; + setFdNonBlock(m_pipeReadFd); + setFdNonBlock(m_pipeWriteFd); +} + +WakeupFd::~WakeupFd() { + close(m_pipeReadFd); + close(m_pipeWriteFd); +} + +void WakeupFd::set() { + char dummy = 0; + int ret; + do { + ret = write(m_pipeWriteFd, &dummy, 1); + } while (ret < 0 && errno == EINTR); +} + +void WakeupFd::reset() { + char tmpBuf[256]; + while (true) { + int amount = read(m_pipeReadFd, tmpBuf, sizeof(tmpBuf)); + if (amount < 0 && errno == EAGAIN) { + break; + } else if (amount <= 0) { + perror("error reading from internal wakeup pipe"); + abort(); + } + } +} diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/WakeupFd.h b/src/libs/3rdparty/winpty/src/unix-adapter/WakeupFd.h new file mode 100644 index 0000000000..dd8d362aa1 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/unix-adapter/WakeupFd.h @@ -0,0 +1,42 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef UNIX_ADAPTER_WAKEUP_FD_H +#define UNIX_ADAPTER_WAKEUP_FD_H + +class WakeupFd { +public: + WakeupFd(); + ~WakeupFd(); + int fd() { return m_pipeReadFd; } + void set(); + void reset(); + +private: + // Do not allow copying the WakeupFd object. + WakeupFd(const WakeupFd &other); + WakeupFd &operator=(const WakeupFd &other); + +private: + int m_pipeReadFd; + int m_pipeWriteFd; +}; + +#endif // UNIX_ADAPTER_WAKEUP_FD_H diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/main.cc b/src/libs/3rdparty/winpty/src/unix-adapter/main.cc new file mode 100644 index 0000000000..992cb70e44 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/unix-adapter/main.cc @@ -0,0 +1,729 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +// MSYS's sys/cygwin.h header only declares cygwin_internal if WINVER is +// defined, which is defined in windows.h. Therefore, include windows.h early. +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include "../shared/DebugClient.h" +#include "../shared/UnixCtrlChars.h" +#include "../shared/WinptyVersion.h" +#include "InputHandler.h" +#include "OutputHandler.h" +#include "Util.h" +#include "WakeupFd.h" + +#define CSI "\x1b[" + +static WakeupFd *g_mainWakeup = NULL; + +static WakeupFd &mainWakeup() +{ + if (g_mainWakeup == NULL) { + static const char msg[] = "Internal error: g_mainWakeup is NULL\r\n"; + write(STDERR_FILENO, msg, sizeof(msg) - 1); + abort(); + } + return *g_mainWakeup; +} + +struct SavedTermiosMode { + int count; + bool valid[3]; + termios mode[3]; +}; + +// Put the input terminal into non-canonical mode. +static SavedTermiosMode setRawTerminalMode( + bool allowNonTtys, bool setStdout, bool setStderr) +{ + SavedTermiosMode ret; + const char *const kNames[3] = { "stdin", "stdout", "stderr" }; + + ret.valid[0] = true; + ret.valid[1] = setStdout; + ret.valid[2] = setStderr; + + for (int i = 0; i < 3; ++i) { + if (!ret.valid[i]) { + continue; + } + if (!isatty(i)) { + ret.valid[i] = false; + if (!allowNonTtys) { + fprintf(stderr, "%s is not a tty\n", kNames[i]); + exit(1); + } + } else { + ret.valid[i] = true; + if (tcgetattr(i, &ret.mode[i]) < 0) { + perror("tcgetattr failed"); + exit(1); + } + } + } + + if (ret.valid[STDIN_FILENO]) { + termios buf; + if (tcgetattr(STDIN_FILENO, &buf) < 0) { + perror("tcgetattr failed"); + exit(1); + } + buf.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + buf.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + buf.c_cflag &= ~(CSIZE | PARENB); + buf.c_cflag |= CS8; + buf.c_cc[VMIN] = 1; // blocking read + buf.c_cc[VTIME] = 0; + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &buf) < 0) { + fprintf(stderr, "tcsetattr failed\n"); + exit(1); + } + } + + for (int i = STDOUT_FILENO; i <= STDERR_FILENO; ++i) { + if (!ret.valid[i]) { + continue; + } + termios buf; + if (tcgetattr(i, &buf) < 0) { + perror("tcgetattr failed"); + exit(1); + } + buf.c_cflag &= ~(CSIZE | PARENB); + buf.c_cflag |= CS8; + buf.c_oflag &= ~OPOST; + if (tcsetattr(i, TCSAFLUSH, &buf) < 0) { + fprintf(stderr, "tcsetattr failed\n"); + exit(1); + } + } + + return ret; +} + +static void restoreTerminalMode(const SavedTermiosMode &original) +{ + for (int i = 0; i < 3; ++i) { + if (!original.valid[i]) { + continue; + } + if (tcsetattr(i, TCSAFLUSH, &original.mode[i]) < 0) { + perror("error restoring terminal mode"); + exit(1); + } + } +} + +static void debugShowKey(bool allowNonTtys) +{ + printf("\nPress any keys -- Ctrl-D exits\n\n"); + const SavedTermiosMode saved = + setRawTerminalMode(allowNonTtys, false, false); + char buf[128]; + while (true) { + const ssize_t len = read(STDIN_FILENO, buf, sizeof(buf)); + if (len <= 0) { + break; + } + for (int i = 0; i < len; ++i) { + char ctrl = decodeUnixCtrlChar(buf[i]); + if (ctrl == '\0') { + putchar(buf[i]); + } else { + putchar('^'); + putchar(ctrl); + } + } + for (int i = 0; i < len; ++i) { + unsigned char uch = buf[i]; + printf("\t%3d %04o 0x%02x\n", uch, uch, uch); + fflush(stdout); + } + if (buf[0] == 4) { + // Ctrl-D + break; + } + } + restoreTerminalMode(saved); +} + +static void terminalResized(int signo) +{ + mainWakeup().set(); +} + +static void registerResizeSignalHandler() +{ + struct sigaction resizeSigAct; + memset(&resizeSigAct, 0, sizeof(resizeSigAct)); + resizeSigAct.sa_handler = terminalResized; + resizeSigAct.sa_flags = SA_RESTART; + sigaction(SIGWINCH, &resizeSigAct, NULL); +} + +// Convert the path to a Win32 path if it is a POSIX path, and convert slashes +// to backslashes. +static std::string convertPosixPathToWin(const std::string &path) +{ + char *tmp; +#if defined(CYGWIN_VERSION_CYGWIN_CONV) && \ + CYGWIN_VERSION_API_MINOR >= CYGWIN_VERSION_CYGWIN_CONV + // MSYS2 and versions of Cygwin released after 2009 or so use this API. + // The original MSYS still lacks this API. + ssize_t newSize = cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE, + path.c_str(), NULL, 0); + assert(newSize >= 0); + tmp = new char[newSize + 1]; + ssize_t success = cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE, + path.c_str(), tmp, newSize + 1); + assert(success == 0); +#else + // In the current Cygwin header file, this API is documented as deprecated + // because it's restricted to paths of MAX_PATH length. In the CVS version + // of MSYS, the newer API doesn't exist, and this older API is implemented + // using msys_p2w, which seems like it would handle paths larger than + // MAX_PATH, but there's no way to query how large the new path is. + // Hopefully, this is large enough. + tmp = new char[MAX_PATH + path.size()]; + cygwin_conv_to_win32_path(path.c_str(), tmp); +#endif + for (int i = 0; tmp[i] != '\0'; ++i) { + if (tmp[i] == '/') + tmp[i] = '\\'; + } + std::string ret(tmp); + delete [] tmp; + return ret; +} + +static std::string resolvePath(const std::string &path) +{ + char ret[PATH_MAX]; + ret[0] = '\0'; + if (realpath(path.c_str(), ret) != ret) { + return std::string(); + } + return ret; +} + +template +static bool endsWith(const std::string &path, const char (&suf)[N]) +{ + const size_t suffixLen = N - 1; + char actualSuf[N]; + if (path.size() < suffixLen) { + return false; + } + strcpy(actualSuf, &path.c_str()[path.size() - suffixLen]); + for (size_t i = 0; i < suffixLen; ++i) { + actualSuf[i] = tolower(actualSuf[i]); + } + return !strcmp(actualSuf, suf); +} + +static std::string findProgram( + const char *winptyProgName, + const std::string &prog) +{ + std::string candidate; + if (prog.find('/') == std::string::npos && + prog.find('\\') == std::string::npos) { + // XXX: It would be nice to use a lambda here (once/if old MSYS support + // is dropped). + // Search the PATH. + const char *const pathVar = getenv("PATH"); + const std::string pathList(pathVar ? pathVar : ""); + size_t elpos = 0; + while (true) { + const size_t elend = pathList.find(':', elpos); + candidate = pathList.substr(elpos, elend - elpos); + if (!candidate.empty() && *(candidate.end() - 1) != '/') { + candidate += '/'; + } + candidate += prog; + candidate = resolvePath(candidate); + if (!candidate.empty()) { + int perm = X_OK; + if (endsWith(candidate, ".bat") || endsWith(candidate, ".cmd")) { +#ifdef __MSYS__ + // In MSYS/MSYS2, batch files don't have the execute bit + // set, so just check that they're readable. + perm = R_OK; +#endif + } else if (endsWith(candidate, ".com") || endsWith(candidate, ".exe")) { + // Do nothing. + } else { + // Make the exe extension explicit so that we don't try to + // run shell scripts with CreateProcess/winpty_spawn. + candidate += ".exe"; + } + if (!access(candidate.c_str(), perm)) { + break; + } + } + if (elend == std::string::npos) { + fprintf(stderr, "%s: error: cannot start '%s': Not found in PATH\n", + winptyProgName, prog.c_str()); + exit(1); + } else { + elpos = elend + 1; + } + } + } else { + candidate = resolvePath(prog); + if (candidate.empty()) { + std::string errstr(strerror(errno)); + fprintf(stderr, "%s: error: cannot start '%s': %s\n", + winptyProgName, prog.c_str(), errstr.c_str()); + exit(1); + } + } + return convertPosixPathToWin(candidate); +} + +// Convert argc/argv into a Win32 command-line following the escaping convention +// documented on MSDN. (e.g. see CommandLineToArgvW documentation) +static std::string argvToCommandLine(const std::vector &argv) +{ + std::string result; + for (size_t argIndex = 0; argIndex < argv.size(); ++argIndex) { + if (argIndex > 0) + result.push_back(' '); + const char *arg = argv[argIndex].c_str(); + const bool quote = + strchr(arg, ' ') != NULL || + strchr(arg, '\t') != NULL || + *arg == '\0'; + if (quote) + result.push_back('\"'); + int bsCount = 0; + for (const char *p = arg; *p != '\0'; ++p) { + if (*p == '\\') { + bsCount++; + } else if (*p == '\"') { + result.append(bsCount * 2 + 1, '\\'); + result.push_back('\"'); + bsCount = 0; + } else { + result.append(bsCount, '\\'); + bsCount = 0; + result.push_back(*p); + } + } + if (quote) { + result.append(bsCount * 2, '\\'); + result.push_back('\"'); + } else { + result.append(bsCount, '\\'); + } + } + return result; +} + +static wchar_t *heapMbsToWcs(const char *text) +{ + // Calling mbstowcs with a NULL first argument seems to be broken on MSYS. + // Instead of returning the size of the converted string, it returns 0. + // Using strlen(text) * 2 is probably big enough. + size_t maxLen = strlen(text) * 2 + 1; + wchar_t *ret = new wchar_t[maxLen]; + size_t len = mbstowcs(ret, text, maxLen); + assert(len != (size_t)-1 && len < maxLen); + return ret; +} + +static char *heapWcsToMbs(const wchar_t *text) +{ + // Calling wcstombs with a NULL first argument seems to be broken on MSYS. + // Instead of returning the size of the converted string, it returns 0. + // Using wcslen(text) * 3 is big enough for UTF-8 and probably other + // encodings. For UTF-8, codepoints that fit in a single wchar + // (U+0000 to U+FFFF) are encoded using 1-3 bytes. The remaining code + // points needs two wchar's and are encoded using 4 bytes. + size_t maxLen = wcslen(text) * 3 + 1; + char *ret = new char[maxLen]; + size_t len = wcstombs(ret, text, maxLen); + if (len == (size_t)-1 || len >= maxLen) { + delete [] ret; + return NULL; + } else { + return ret; + } +} + +static std::string wcsToMbs(const wchar_t *text) +{ + std::string ret; + const char *ptr = heapWcsToMbs(text); + if (ptr != NULL) { + ret = ptr; + delete [] ptr; + } + return ret; +} + +void setupWin32Environment() +{ + std::map varsToCopy; + const char *vars[] = { + "WINPTY_DEBUG", + "WINPTY_SHOW_CONSOLE", + NULL + }; + for (int i = 0; vars[i] != NULL; ++i) { + const char *cstr = getenv(vars[i]); + if (cstr != NULL && cstr[0] != '\0') { + varsToCopy[vars[i]] = cstr; + } + } + +#if defined(__MSYS__) && CYGWIN_VERSION_API_MINOR >= 48 || \ + !defined(__MSYS__) && CYGWIN_VERSION_API_MINOR >= 153 + // Use CW_SYNC_WINENV to copy the Unix environment to the Win32 + // environment. The command performs special translation on some variables + // (such as PATH and TMP). It also copies the debugging environment + // variables. + // + // Note that the API minor versions have diverged in Cygwin and MSYS. + // CW_SYNC_WINENV was added to Cygwin in version 153. (Cygwin's + // include/cygwin/version.h says that CW_SETUP_WINENV was added in 153. + // The flag was renamed 8 days after it was added, but the API docs weren't + // updated.) The flag was added to MSYS in version 48. + // + // Also, in my limited testing, this call seems to be necessary with Cygwin + // but unnecessary with MSYS. Perhaps MSYS is automatically syncing the + // Unix environment with the Win32 environment before starting console.exe? + // It shouldn't hurt to call it for MSYS. + cygwin_internal(CW_SYNC_WINENV); +#endif + + // Copy debugging environment variables from the Cygwin environment + // to the Win32 environment so the agent will inherit it. + for (std::map::iterator it = varsToCopy.begin(); + it != varsToCopy.end(); + ++it) { + wchar_t *nameW = heapMbsToWcs(it->first.c_str()); + wchar_t *valueW = heapMbsToWcs(it->second.c_str()); + SetEnvironmentVariableW(nameW, valueW); + delete [] nameW; + delete [] valueW; + } + + // Clear the TERM variable. The child process's immediate console/terminal + // environment is a Windows console, not the terminal that winpty is + // communicating with. Leaving the TERM variable set can break programs in + // various ways. (e.g. arrows keys broken in Cygwin less, IronPython's + // help(...) function doesn't start, misc programs decide they should + // output color escape codes on pre-Win10). See + // https://github.com/rprichard/winpty/issues/43. + SetEnvironmentVariableW(L"TERM", NULL); +} + +static void usage(const char *program, int exitCode) +{ + printf("Usage: %s [options] [--] program [args]\n", program); + printf("\n"); + printf("Options:\n"); + printf(" -h, --help Show this help message\n"); + printf(" --mouse Enable terminal mouse input\n"); + printf(" --showkey Dump STDIN escape sequences\n"); + printf(" --version Show the winpty version number\n"); + exit(exitCode); +} + +struct Arguments { + std::vector childArgv; + bool mouseInput; + bool testAllowNonTtys; + bool testConerr; + bool testPlainOutput; + bool testColorEscapes; +}; + +static void parseArguments(int argc, char *argv[], Arguments &out) +{ + out.mouseInput = false; + out.testAllowNonTtys = false; + out.testConerr = false; + out.testPlainOutput = false; + out.testColorEscapes = false; + bool doShowKeys = false; + const char *const program = argc >= 1 ? argv[0] : ""; + int argi = 1; + while (argi < argc) { + std::string arg(argv[argi++]); + if (arg.size() >= 1 && arg[0] == '-') { + if (arg == "-h" || arg == "--help") { + usage(program, 0); + } else if (arg == "--mouse") { + out.mouseInput = true; + } else if (arg == "--showkey") { + doShowKeys = true; + } else if (arg == "--version") { + dumpVersionToStdout(); + exit(0); + } else if (arg == "-Xallow-non-tty") { + out.testAllowNonTtys = true; + } else if (arg == "-Xconerr") { + out.testConerr = true; + } else if (arg == "-Xplain") { + out.testPlainOutput = true; + } else if (arg == "-Xcolor") { + out.testColorEscapes = true; + } else if (arg == "--") { + break; + } else { + fprintf(stderr, "Error: unrecognized option: '%s'\n", + arg.c_str()); + exit(1); + } + } else { + out.childArgv.push_back(arg); + break; + } + } + for (; argi < argc; ++argi) { + out.childArgv.push_back(argv[argi]); + } + if (doShowKeys) { + debugShowKey(out.testAllowNonTtys); + exit(0); + } + if (out.childArgv.size() == 0) { + usage(program, 1); + } +} + +static std::string errorMessageToString(DWORD err) +{ + // Use FormatMessageW rather than FormatMessageA, because we want to use + // wcstombs to convert to the Cygwin locale, which might not match the + // codepage FormatMessageA would use. We need to convert using wcstombs, + // rather than print using %ls, because %ls doesn't work in the original + // MSYS. + wchar_t *wideMsgPtr = NULL; + const DWORD formatRet = FormatMessageW( + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&wideMsgPtr), + 0, + NULL); + if (formatRet == 0 || wideMsgPtr == NULL) { + return std::string(); + } + std::string msg = wcsToMbs(wideMsgPtr); + LocalFree(wideMsgPtr); + const size_t pos = msg.find_last_not_of(" \r\n\t"); + if (pos == std::string::npos) { + msg.clear(); + } else { + msg.erase(pos + 1); + } + return msg; +} + +static std::string formatErrorMessage(DWORD err) +{ + char buf[64]; + sprintf(buf, "error %#x", static_cast(err)); + std::string ret = errorMessageToString(err); + if (ret.empty()) { + ret += buf; + } else { + ret += " ("; + ret += buf; + ret += ")"; + } + return ret; +} + +int main(int argc, char *argv[]) +{ + setlocale(LC_ALL, ""); + + g_mainWakeup = new WakeupFd(); + + Arguments args; + parseArguments(argc, argv, args); + + setupWin32Environment(); + + winsize sz = { 0 }; + sz.ws_col = 80; + sz.ws_row = 25; + ioctl(STDIN_FILENO, TIOCGWINSZ, &sz); + + DWORD agentFlags = WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION; + if (args.testConerr) { agentFlags |= WINPTY_FLAG_CONERR; } + if (args.testPlainOutput) { agentFlags |= WINPTY_FLAG_PLAIN_OUTPUT; } + if (args.testColorEscapes) { agentFlags |= WINPTY_FLAG_COLOR_ESCAPES; } + winpty_config_t *agentCfg = winpty_config_new(agentFlags, NULL); + assert(agentCfg != NULL); + winpty_config_set_initial_size(agentCfg, sz.ws_col, sz.ws_row); + if (args.mouseInput) { + winpty_config_set_mouse_mode(agentCfg, WINPTY_MOUSE_MODE_FORCE); + } + + winpty_error_ptr_t openErr = NULL; + winpty_t *wp = winpty_open(agentCfg, &openErr); + if (wp == NULL) { + fprintf(stderr, "Error creating winpty: %s\n", + wcsToMbs(winpty_error_msg(openErr)).c_str()); + exit(1); + } + winpty_config_free(agentCfg); + winpty_error_free(openErr); + + HANDLE conin = CreateFileW(winpty_conin_name(wp), GENERIC_WRITE, 0, NULL, + OPEN_EXISTING, 0, NULL); + HANDLE conout = CreateFileW(winpty_conout_name(wp), GENERIC_READ, 0, NULL, + OPEN_EXISTING, 0, NULL); + assert(conin != INVALID_HANDLE_VALUE); + assert(conout != INVALID_HANDLE_VALUE); + HANDLE conerr = NULL; + if (args.testConerr) { + conerr = CreateFileW(winpty_conerr_name(wp), GENERIC_READ, 0, NULL, + OPEN_EXISTING, 0, NULL); + assert(conerr != INVALID_HANDLE_VALUE); + } + + HANDLE childHandle = NULL; + + { + // Start the child process under the console. + args.childArgv[0] = findProgram(argv[0], args.childArgv[0]); + std::string cmdLine = argvToCommandLine(args.childArgv); + wchar_t *cmdLineW = heapMbsToWcs(cmdLine.c_str()); + + winpty_spawn_config_t *spawnCfg = winpty_spawn_config_new( + WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, + NULL, cmdLineW, NULL, NULL, NULL); + assert(spawnCfg != NULL); + + winpty_error_ptr_t spawnErr = NULL; + DWORD lastError = 0; + BOOL spawnRet = winpty_spawn(wp, spawnCfg, &childHandle, NULL, + &lastError, &spawnErr); + winpty_spawn_config_free(spawnCfg); + + if (!spawnRet) { + winpty_result_t spawnCode = winpty_error_code(spawnErr); + if (spawnCode == WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED) { + fprintf(stderr, "%s: error: cannot start '%s': %s\n", + argv[0], + cmdLine.c_str(), + formatErrorMessage(lastError).c_str()); + } else { + fprintf(stderr, "%s: error: cannot start '%s': internal error: %s\n", + argv[0], + cmdLine.c_str(), + wcsToMbs(winpty_error_msg(spawnErr)).c_str()); + } + exit(1); + } + winpty_error_free(spawnErr); + delete [] cmdLineW; + } + + registerResizeSignalHandler(); + SavedTermiosMode mode = + setRawTerminalMode(args.testAllowNonTtys, true, args.testConerr); + + InputHandler inputHandler(conin, STDIN_FILENO, mainWakeup()); + OutputHandler outputHandler(conout, STDOUT_FILENO, mainWakeup()); + OutputHandler *errorHandler = NULL; + if (args.testConerr) { + errorHandler = new OutputHandler(conerr, STDERR_FILENO, mainWakeup()); + } + + while (true) { + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(mainWakeup().fd(), &readfds); + selectWrapper("main thread", mainWakeup().fd() + 1, &readfds); + mainWakeup().reset(); + + // Check for terminal resize. + { + winsize sz2; + ioctl(STDIN_FILENO, TIOCGWINSZ, &sz2); + if (memcmp(&sz, &sz2, sizeof(sz)) != 0) { + sz = sz2; + winpty_set_size(wp, sz.ws_col, sz.ws_row, NULL); + } + } + + // Check for an I/O handler shutting down (possibly indicating that the + // child process has exited). + if (inputHandler.isComplete() || outputHandler.isComplete() || + (errorHandler != NULL && errorHandler->isComplete())) { + break; + } + } + + // Kill the agent connection. This will kill the agent, closing the CONIN + // and CONOUT pipes on the agent pipe, prompting our I/O handler to shut + // down. + winpty_free(wp); + + inputHandler.shutdown(); + outputHandler.shutdown(); + CloseHandle(conin); + CloseHandle(conout); + + if (errorHandler != NULL) { + errorHandler->shutdown(); + delete errorHandler; + CloseHandle(conerr); + } + + restoreTerminalMode(mode); + + DWORD exitCode = 0; + if (!GetExitCodeProcess(childHandle, &exitCode)) { + exitCode = 1; + } + CloseHandle(childHandle); + return exitCode; +} diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/subdir.mk b/src/libs/3rdparty/winpty/src/unix-adapter/subdir.mk new file mode 100644 index 0000000000..200193a1b1 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/unix-adapter/subdir.mk @@ -0,0 +1,41 @@ +# Copyright (c) 2011-2015 Ryan Prichard +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +ALL_TARGETS += build/$(UNIX_ADAPTER_EXE) + +$(eval $(call def_unix_target,unix-adapter,)) + +UNIX_ADAPTER_OBJECTS = \ + build/unix-adapter/unix-adapter/InputHandler.o \ + build/unix-adapter/unix-adapter/OutputHandler.o \ + build/unix-adapter/unix-adapter/Util.o \ + build/unix-adapter/unix-adapter/WakeupFd.o \ + build/unix-adapter/unix-adapter/main.o \ + build/unix-adapter/shared/DebugClient.o \ + build/unix-adapter/shared/WinptyAssert.o \ + build/unix-adapter/shared/WinptyVersion.o + +build/unix-adapter/shared/WinptyVersion.o : build/gen/GenVersion.h + +build/$(UNIX_ADAPTER_EXE) : $(UNIX_ADAPTER_OBJECTS) build/winpty.dll + $(info Linking $@) + @$(UNIX_CXX) $(UNIX_LDFLAGS) -o $@ $^ + +-include $(UNIX_ADAPTER_OBJECTS:.o=.d) diff --git a/src/libs/3rdparty/winpty/src/winpty.gyp b/src/libs/3rdparty/winpty/src/winpty.gyp new file mode 100644 index 0000000000..7ee68d55e6 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/winpty.gyp @@ -0,0 +1,206 @@ +{ + # The MSVC generator is the default. Select the compiler version by + # passing -G msvs_version= to gyp. is a string like 2013e. + # See gyp\pylib\gyp\MSVSVersion.py for sample version strings. You + # can also pass configurations.gypi to gyp for 32-bit and 64-bit builds. + # See that file for details. + # + # Pass --format=make to gyp to generate a Makefile instead. The Makefile + # can be configured by passing variables to make, e.g.: + # make -j4 CXX=i686-w64-mingw32-g++ LDFLAGS="-static -static-libgcc -static-libstdc++" + + 'variables': { + 'WINPTY_COMMIT_HASH%': ' Date: Wed, 22 Feb 2023 14:29:18 +0100 Subject: ADS: Use only one logging category Change-Id: I0366338d5605fea7b4b096605577e90dfa79c389 Reviewed-by: Qt CI Bot Reviewed-by: Reviewed-by: hjk --- src/libs/advanceddockingsystem/ads_globals_p.h | 8 ++++++++ src/libs/advanceddockingsystem/dockareatabbar.cpp | 3 +-- src/libs/advanceddockingsystem/dockareatitlebar.cpp | 3 +-- src/libs/advanceddockingsystem/dockareawidget.cpp | 3 +-- src/libs/advanceddockingsystem/dockcontainerwidget.cpp | 3 +-- src/libs/advanceddockingsystem/dockmanager.cpp | 3 ++- src/libs/advanceddockingsystem/docksplitter.cpp | 3 +-- src/libs/advanceddockingsystem/dockwidget.cpp | 3 +-- src/libs/advanceddockingsystem/dockwidgettab.cpp | 3 +-- src/libs/advanceddockingsystem/floatingdockcontainer.cpp | 3 +-- src/libs/advanceddockingsystem/floatingdragpreview.cpp | 3 +-- 11 files changed, 19 insertions(+), 19 deletions(-) create mode 100644 src/libs/advanceddockingsystem/ads_globals_p.h (limited to 'src/libs') diff --git a/src/libs/advanceddockingsystem/ads_globals_p.h b/src/libs/advanceddockingsystem/ads_globals_p.h new file mode 100644 index 0000000000..e4b32eb201 --- /dev/null +++ b/src/libs/advanceddockingsystem/ads_globals_p.h @@ -0,0 +1,8 @@ +// Copyright (C) 2023 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later + +#pragma once + +#include + +Q_DECLARE_LOGGING_CATEGORY(adsLog) diff --git a/src/libs/advanceddockingsystem/dockareatabbar.cpp b/src/libs/advanceddockingsystem/dockareatabbar.cpp index ec1bf782ff..62f0b80b49 100644 --- a/src/libs/advanceddockingsystem/dockareatabbar.cpp +++ b/src/libs/advanceddockingsystem/dockareatabbar.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later #include "dockareatabbar.h" +#include "ads_globals_p.h" #include "dockareawidget.h" #include "dockwidget.h" @@ -16,8 +17,6 @@ #include -static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg) - namespace ADS { /** diff --git a/src/libs/advanceddockingsystem/dockareatitlebar.cpp b/src/libs/advanceddockingsystem/dockareatitlebar.cpp index ed1d76b67a..178170777d 100644 --- a/src/libs/advanceddockingsystem/dockareatitlebar.cpp +++ b/src/libs/advanceddockingsystem/dockareatitlebar.cpp @@ -4,6 +4,7 @@ #include "dockareatitlebar.h" #include "ads_globals.h" +#include "ads_globals_p.h" #include "advanceddockingsystemtr.h" #include "dockareatabbar.h" #include "dockareawidget.h" @@ -26,8 +27,6 @@ #include -static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg) - namespace ADS { /** diff --git a/src/libs/advanceddockingsystem/dockareawidget.cpp b/src/libs/advanceddockingsystem/dockareawidget.cpp index b4c21f7119..522b6b985e 100644 --- a/src/libs/advanceddockingsystem/dockareawidget.cpp +++ b/src/libs/advanceddockingsystem/dockareawidget.cpp @@ -3,6 +3,7 @@ #include "dockareawidget.h" +#include "ads_globals_p.h" #include "dockareatabbar.h" #include "dockareatitlebar.h" #include "dockcomponentsfactory.h" @@ -29,8 +30,6 @@ #include -static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg) - namespace ADS { static const char *const INDEX_PROPERTY = "index"; diff --git a/src/libs/advanceddockingsystem/dockcontainerwidget.cpp b/src/libs/advanceddockingsystem/dockcontainerwidget.cpp index 80393a4671..253937c4d1 100644 --- a/src/libs/advanceddockingsystem/dockcontainerwidget.cpp +++ b/src/libs/advanceddockingsystem/dockcontainerwidget.cpp @@ -4,6 +4,7 @@ #include "dockcontainerwidget.h" #include "ads_globals.h" +#include "ads_globals_p.h" #include "dockareawidget.h" #include "dockingstatereader.h" #include "dockmanager.h" @@ -25,8 +26,6 @@ #include #include -static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg) - namespace ADS { static unsigned int zOrderCounter = 0; diff --git a/src/libs/advanceddockingsystem/dockmanager.cpp b/src/libs/advanceddockingsystem/dockmanager.cpp index 59fc2f64fc..2694f2986e 100644 --- a/src/libs/advanceddockingsystem/dockmanager.cpp +++ b/src/libs/advanceddockingsystem/dockmanager.cpp @@ -4,6 +4,7 @@ #include "dockmanager.h" #include "ads_globals.h" +#include "ads_globals_p.h" #include "dockareawidget.h" #include "dockfocuscontroller.h" #include "dockingstatereader.h" @@ -38,7 +39,7 @@ #include #include -static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg); +Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg); using namespace Utils; diff --git a/src/libs/advanceddockingsystem/docksplitter.cpp b/src/libs/advanceddockingsystem/docksplitter.cpp index e8537dbd77..22efb63204 100644 --- a/src/libs/advanceddockingsystem/docksplitter.cpp +++ b/src/libs/advanceddockingsystem/docksplitter.cpp @@ -3,14 +3,13 @@ #include "docksplitter.h" +#include "ads_globals_p.h" #include "dockareawidget.h" #include #include #include -static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg) - namespace ADS { /** diff --git a/src/libs/advanceddockingsystem/dockwidget.cpp b/src/libs/advanceddockingsystem/dockwidget.cpp index b39c58f4c2..64269ebfdb 100644 --- a/src/libs/advanceddockingsystem/dockwidget.cpp +++ b/src/libs/advanceddockingsystem/dockwidget.cpp @@ -4,6 +4,7 @@ #include "dockwidget.h" #include "ads_globals.h" +#include "ads_globals_p.h" #include "dockareawidget.h" #include "dockcomponentsfactory.h" #include "dockcontainerwidget.h" @@ -25,8 +26,6 @@ #include #include -static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg) - namespace ADS { /** diff --git a/src/libs/advanceddockingsystem/dockwidgettab.cpp b/src/libs/advanceddockingsystem/dockwidgettab.cpp index 6775478497..4531e0da2d 100644 --- a/src/libs/advanceddockingsystem/dockwidgettab.cpp +++ b/src/libs/advanceddockingsystem/dockwidgettab.cpp @@ -4,6 +4,7 @@ #include "dockwidgettab.h" #include "ads_globals.h" +#include "ads_globals_p.h" #include "advanceddockingsystemtr.h" #include "dockareawidget.h" #include "dockmanager.h" @@ -32,8 +33,6 @@ #include -static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg) - namespace ADS { using TabLabelType = ElidingLabel; diff --git a/src/libs/advanceddockingsystem/floatingdockcontainer.cpp b/src/libs/advanceddockingsystem/floatingdockcontainer.cpp index 4b8ae0913d..c99044a3d5 100644 --- a/src/libs/advanceddockingsystem/floatingdockcontainer.cpp +++ b/src/libs/advanceddockingsystem/floatingdockcontainer.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later #include "floatingdockcontainer.h" +#include "ads_globals_p.h" #include "dockareawidget.h" #include "dockcontainerwidget.h" @@ -29,8 +30,6 @@ #include #include -static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg) - namespace ADS { #ifdef Q_OS_WIN diff --git a/src/libs/advanceddockingsystem/floatingdragpreview.cpp b/src/libs/advanceddockingsystem/floatingdragpreview.cpp index f427ab85ed..3111e46c6b 100644 --- a/src/libs/advanceddockingsystem/floatingdragpreview.cpp +++ b/src/libs/advanceddockingsystem/floatingdragpreview.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later #include "floatingdragpreview.h" +#include "ads_globals_p.h" #include "dockareawidget.h" #include "dockcontainerwidget.h" @@ -19,8 +20,6 @@ #include -static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg) - namespace ADS { /** -- cgit v1.2.3 From a8d62298985329c5d2a3ba923664372bc9938a0e Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Fri, 24 Feb 2023 09:34:28 +0100 Subject: Terminal: Fix libvterm -fPIC Change-Id: Iac8a9a0ec01058edd00399c3de0746d97de6fec3 Reviewed-by: hjk --- src/libs/3rdparty/libvterm/CMakeLists.txt | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) (limited to 'src/libs') diff --git a/src/libs/3rdparty/libvterm/CMakeLists.txt b/src/libs/3rdparty/libvterm/CMakeLists.txt index 4a0ede6f0f..232217d9f5 100644 --- a/src/libs/3rdparty/libvterm/CMakeLists.txt +++ b/src/libs/3rdparty/libvterm/CMakeLists.txt @@ -1,4 +1,7 @@ -add_library(libvterm STATIC +add_qtc_library(libvterm STATIC + PUBLIC_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/include + PROPERTIES QT_COMPILE_OPTIONS_DISABLE_WARNINGS ON + SOURCES src/encoding.c src/fullwidth.inc src/keyboard.c @@ -13,13 +16,3 @@ add_library(libvterm STATIC src/vterm.c src/vterm_internal.h ) - -target_include_directories(libvterm PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/include -) - -set_target_properties(libvterm - PROPERTIES - QT_COMPILE_OPTIONS_DISABLE_WARNINGS ON - POSITION_INDEPENDENT_CODE ON  -) -- cgit v1.2.3 From d5a9e28a968d13f9c437033d647b5da8eaf09dc9 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Thu, 9 Feb 2023 13:58:01 +0100 Subject: Utils: Add tst_filepath Moves all tests that are applicable only to FilePath over from tst_fileutils. Change-Id: Ic331e1470a7479ee2f8ba38baec84124982a000a Reviewed-by: Christian Stenger --- src/libs/utils/filepath.cpp | 283 +++++++++++++++++++++---------------------- src/libs/utils/filepath.h | 3 + src/libs/utils/fileutils.cpp | 2 +- src/libs/utils/fileutils.h | 4 - 4 files changed, 144 insertions(+), 148 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 8c0832df58..7e61ac0d37 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -780,6 +780,146 @@ int FilePath::schemeAndHostLength(const QStringView path) return pos + 1; // scheme://host/ plus something } +static QString normalizePathSegmentHelper(const QString &name) +{ + const int len = name.length(); + + if (len == 0 || name.contains("%{")) + return name; + + int i = len - 1; + QVarLengthArray outVector(len); + int used = len; + char16_t *out = outVector.data(); + const ushort *p = reinterpret_cast(name.data()); + const ushort *prefix = p; + int up = 0; + + const int prefixLength = name.at(0) == u'/' ? 1 : 0; + + p += prefixLength; + i -= prefixLength; + + // replicate trailing slash (i > 0 checks for emptiness of input string p) + // except for remote paths because there can be /../ or /./ ending + if (i > 0 && p[i] == '/') { + out[--used] = '/'; + --i; + } + + while (i >= 0) { + if (p[i] == '/') { + --i; + continue; + } + + // remove current directory + if (p[i] == '.' && (i == 0 || p[i - 1] == '/')) { + --i; + continue; + } + + // detect up dir + if (i >= 1 && p[i] == '.' && p[i - 1] == '.' && (i < 2 || p[i - 2] == '/')) { + ++up; + i -= i >= 2 ? 3 : 2; + continue; + } + + // prepend a slash before copying when not empty + if (!up && used != len && out[used] != '/') + out[--used] = '/'; + + // skip or copy + while (i >= 0) { + if (p[i] == '/') { + --i; + break; + } + + // actual copy + if (!up) + out[--used] = p[i]; + --i; + } + + // decrement up after copying/skipping + if (up) + --up; + } + + // Indicate failure when ".." are left over for an absolute path. + // if (ok) + // *ok = prefixLength == 0 || up == 0; + + // add remaining '..' + while (up) { + if (used != len && out[used] != '/') // is not empty and there isn't already a '/' + out[--used] = '/'; + out[--used] = '.'; + out[--used] = '.'; + --up; + } + + bool isEmpty = used == len; + + if (prefixLength) { + if (!isEmpty && out[used] == '/') { + // Even though there is a prefix the out string is a slash. This happens, if the input + // string only consists of a prefix followed by one or more slashes. Just skip the slash. + ++used; + } + for (int i = prefixLength - 1; i >= 0; --i) + out[--used] = prefix[i]; + } else { + if (isEmpty) { + // After resolving the input path, the resulting string is empty (e.g. "foo/.."). Return + // a dot in that case. + out[--used] = '.'; + } else if (out[used] == '/') { + // After parsing the input string, out only contains a slash. That happens whenever all + // parts are resolved and there is a trailing slash ("./" or "foo/../" for example). + // Prepend a dot to have the correct return value. + out[--used] = '.'; + } + } + + // If path was not modified return the original value + if (used == 0) + return name; + return QString::fromUtf16(out + used, len - used); +} + +QString doCleanPath(const QString &input_) +{ + QString input = input_; + if (input.contains('\\')) + input.replace('\\', '/'); + + if (input.startsWith("//?/")) { + input = input.mid(4); + if (input.startsWith("UNC/")) + input = '/' + input.mid(3); // trick it into reporting two slashs at start + } + + int prefixLen = 0; + const int shLen = FilePath::schemeAndHostLength(input); + if (shLen > 0) { + prefixLen = shLen + FilePath::rootLength(input.mid(shLen)); + } else { + prefixLen = FilePath::rootLength(input); + if (prefixLen > 0 && input.at(prefixLen - 1) == '/') + --prefixLen; + } + + QString path = normalizePathSegmentHelper(input.mid(prefixLen)); + + // Strip away last slash except for root directories + if (path.size() > 1 && path.endsWith(u'/')) + path.chop(1); + + return input.left(prefixLen) + path; +} /*! Find the parent directory of a given directory. @@ -1788,150 +1928,7 @@ QTextStream &operator<<(QTextStream &s, const FilePath &fn) return s << fn.toString(); } -static QString normalizePathSegmentHelper(const QString &name) -{ - const int len = name.length(); - - if (len == 0 || name.contains("%{")) - return name; - - int i = len - 1; - QVarLengthArray outVector(len); - int used = len; - char16_t *out = outVector.data(); - const ushort *p = reinterpret_cast(name.data()); - const ushort *prefix = p; - int up = 0; - - const int prefixLength = name.at(0) == u'/' ? 1 : 0; - - p += prefixLength; - i -= prefixLength; - - // replicate trailing slash (i > 0 checks for emptiness of input string p) - // except for remote paths because there can be /../ or /./ ending - if (i > 0 && p[i] == '/') { - out[--used] = '/'; - --i; - } - - while (i >= 0) { - if (p[i] == '/') { - --i; - continue; - } - - // remove current directory - if (p[i] == '.' && (i == 0 || p[i-1] == '/')) { - --i; - continue; - } - - // detect up dir - if (i >= 1 && p[i] == '.' && p[i-1] == '.' && (i < 2 || p[i - 2] == '/')) { - ++up; - i -= i >= 2 ? 3 : 2; - continue; - } - - // prepend a slash before copying when not empty - if (!up && used != len && out[used] != '/') - out[--used] = '/'; - - // skip or copy - while (i >= 0) { - if (p[i] == '/') { - --i; - break; - } - - // actual copy - if (!up) - out[--used] = p[i]; - --i; - } - - // decrement up after copying/skipping - if (up) - --up; - } - - // Indicate failure when ".." are left over for an absolute path. -// if (ok) -// *ok = prefixLength == 0 || up == 0; - - // add remaining '..' - while (up) { - if (used != len && out[used] != '/') // is not empty and there isn't already a '/' - out[--used] = '/'; - out[--used] = '.'; - out[--used] = '.'; - --up; - } - - bool isEmpty = used == len; - - if (prefixLength) { - if (!isEmpty && out[used] == '/') { - // Even though there is a prefix the out string is a slash. This happens, if the input - // string only consists of a prefix followed by one or more slashes. Just skip the slash. - ++used; - } - for (int i = prefixLength - 1; i >= 0; --i) - out[--used] = prefix[i]; - } else { - if (isEmpty) { - // After resolving the input path, the resulting string is empty (e.g. "foo/.."). Return - // a dot in that case. - out[--used] = '.'; - } else if (out[used] == '/') { - // After parsing the input string, out only contains a slash. That happens whenever all - // parts are resolved and there is a trailing slash ("./" or "foo/../" for example). - // Prepend a dot to have the correct return value. - out[--used] = '.'; - } - } - - // If path was not modified return the original value - if (used == 0) - return name; - return QString::fromUtf16(out + used, len - used); -} - -QString doCleanPath(const QString &input_) -{ - QString input = input_; - if (input.contains('\\')) - input.replace('\\', '/'); - - if (input.startsWith("//?/")) { - input = input.mid(4); - if (input.startsWith("UNC/")) - input = '/' + input.mid(3); // trick it into reporting two slashs at start - } - - int prefixLen = 0; - const int shLen = FilePath::schemeAndHostLength(input); - if (shLen > 0) { - prefixLen = shLen + FilePath::rootLength(input.mid(shLen)); - } else { - prefixLen = FilePath::rootLength(input); - if (prefixLen > 0 && input.at(prefixLen - 1) == '/') - --prefixLen; - } - - QString path = normalizePathSegmentHelper(input.mid(prefixLen)); - - // Strip away last slash except for root directories - if (path.size() > 1 && path.endsWith(u'/')) - path.chop(1); - - return input.left(prefixLen) + path; -} - - // FileFilter - FileFilter::FileFilter(const QStringList &nameFilters, const QDir::Filters fileFilters, const QDirIterator::IteratorFlags flags) diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h index 4c61786eba..8aa59eb5d9 100644 --- a/src/libs/utils/filepath.h +++ b/src/libs/utils/filepath.h @@ -293,6 +293,9 @@ public: std::function openTerminal; }; +// For testing +QTCREATOR_UTILS_EXPORT QString doCleanPath(const QString &input); + } // Utils Q_DECLARE_METATYPE(Utils::FilePath) diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index 69c3172914..d128f21974 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -802,7 +802,7 @@ FilePath FileUtils::commonPath(const FilePath &oldCommonPath, const FilePath &fi FilePath FileUtils::homePath() { - return FilePath::fromString(doCleanPath(QDir::homePath())); + return FilePath::fromUserInput(QDir::homePath()); } FilePaths FileUtils::toFilePathList(const QStringList &paths) { diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index f34206d8b7..ec3de7a5ba 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -232,9 +232,5 @@ QTCREATOR_UTILS_EXPORT QTextStream &operator<<(QTextStream &s, const FilePath &f bool isRelativePathHelper(const QString &path, OsType osType); -// For testing -QTCREATOR_UTILS_EXPORT QString doCleanPath(const QString &input); -QTCREATOR_UTILS_EXPORT QString cleanPathHelper(const QString &path); - } // namespace Utils -- cgit v1.2.3 From 682ef157d8561cb9648be8efcf45bdc8d0e47ea7 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Thu, 23 Feb 2023 12:47:39 +0100 Subject: Terminal: Add Terminal plugin Adds a new Terminal plugin that provides a Terminal pane inside Qt Creator. Fixes: QTCREATORBUG-8511 Change-Id: I7eacb3efa2463d7df9f383ae3fc33254fb9019a9 Reviewed-by: Reviewed-by: Qt CI Bot Reviewed-by: hjk --- src/libs/utils/CMakeLists.txt | 1 + src/libs/utils/aspects.cpp | 71 ++++++++++++++++++++++++++++++++++++++++ src/libs/utils/aspects.h | 26 +++++++++++++++ src/libs/utils/qtcprocess.cpp | 3 +- src/libs/utils/terminalhooks.cpp | 48 +++++++++++++++++++++++++++ src/libs/utils/terminalhooks.h | 70 +++++++++++++++++++++++++++++++++++++++ src/libs/utils/utils.qbs | 2 ++ 7 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 src/libs/utils/terminalhooks.cpp create mode 100644 src/libs/utils/terminalhooks.h (limited to 'src/libs') diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index 34b24ff78c..04f475afb7 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -170,6 +170,7 @@ add_qtc_library(Utils temporarydirectory.cpp temporarydirectory.h temporaryfile.cpp temporaryfile.h terminalcommand.cpp terminalcommand.h + terminalhooks.cpp terminalhooks.h terminalprocess.cpp terminalprocess_p.h textfieldcheckbox.cpp textfieldcheckbox.h textfieldcombobox.cpp textfieldcombobox.h diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index 72e667d76a..51be951a8f 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -9,6 +9,7 @@ #include "layoutbuilder.h" #include "pathchooser.h" #include "qtcassert.h" +#include "qtcolorbutton.h" #include "qtcsettings.h" #include "utilstr.h" #include "variablechooser.h" @@ -579,6 +580,12 @@ public: QPointer m_groupBox; // For BoolAspects handling GroupBox check boxes }; +class ColorAspectPrivate +{ +public: + QPointer m_colorButton; // Owned by configuration widget +}; + class SelectionAspectPrivate { public: @@ -1287,6 +1294,70 @@ void StringAspect::makeCheckable(CheckBoxPlacement checkBoxPlacement, update(); } +/*! + \class Utils::ColorAspect + \inmodule QtCreator + + \brief A color aspect is a color property of some object, together with + a description of its behavior for common operations like visualizing or + persisting. + + The color aspect is displayed using a QtColorButton. +*/ + +ColorAspect::ColorAspect(const QString &settingsKey) + : d(new Internal::ColorAspectPrivate) +{ + setDefaultValue(QColor::fromRgb(0, 0, 0)); + setSettingsKey(settingsKey); + setSpan(1, 1); + + addDataExtractor(this, &ColorAspect::value, &Data::value); +} + +ColorAspect::~ColorAspect() = default; + +void ColorAspect::addToLayout(Layouting::LayoutBuilder &builder) +{ + QTC_CHECK(!d->m_colorButton); + d->m_colorButton = createSubWidget(); + builder.addItem(d->m_colorButton.data()); + d->m_colorButton->setColor(value()); + if (isAutoApply()) { + connect(d->m_colorButton.data(), + &QtColorButton::colorChanged, + this, + [this](const QColor &color) { setValue(color); }); + } +} + +QColor ColorAspect::value() const +{ + return BaseAspect::value().value(); +} + +void ColorAspect::setValue(const QColor &value) +{ + if (BaseAspect::setValueQuietly(value)) + emit changed(); +} + +QVariant ColorAspect::volatileValue() const +{ + QTC_CHECK(!isAutoApply()); + if (d->m_colorButton) + return d->m_colorButton->color(); + QTC_CHECK(false); + return {}; +} + +void ColorAspect::setVolatileValue(const QVariant &val) +{ + QTC_CHECK(!isAutoApply()); + if (d->m_colorButton) + d->m_colorButton->setColor(val.value()); +} + /*! \class Utils::BoolAspect \inmodule QtCreator diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index 413a17e624..1aaaca3f29 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -31,6 +31,7 @@ namespace Internal { class AspectContainerPrivate; class BaseAspectPrivate; class BoolAspectPrivate; +class ColorAspectPrivate; class DoubleAspectPrivate; class IntegerAspectPrivate; class MultiSelectionAspectPrivate; @@ -245,6 +246,31 @@ private: std::unique_ptr d; }; +class QTCREATOR_UTILS_EXPORT ColorAspect : public BaseAspect +{ + Q_OBJECT + +public: + explicit ColorAspect(const QString &settingsKey = QString()); + ~ColorAspect() override; + + struct Data : BaseAspect::Data + { + QColor value; + }; + + void addToLayout(Layouting::LayoutBuilder &builder) override; + + QColor value() const; + void setValue(const QColor &val); + + QVariant volatileValue() const override; + void setVolatileValue(const QVariant &val) override; + +private: + std::unique_ptr d; +}; + class QTCREATOR_UTILS_EXPORT SelectionAspect : public BaseAspect { Q_OBJECT diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/qtcprocess.cpp index 74bd28560b..096f644561 100644 --- a/src/libs/utils/qtcprocess.cpp +++ b/src/libs/utils/qtcprocess.cpp @@ -12,6 +12,7 @@ #include "processreaper.h" #include "processutils.h" #include "stringutils.h" +#include "terminalhooks.h" #include "terminalprocess_p.h" #include "threadutils.h" #include "utilstr.h" @@ -630,7 +631,7 @@ public: ProcessInterface *createProcessInterface() { if (m_setup.m_terminalMode != TerminalMode::Off) - return new TerminalImpl(); + return Terminal::Hooks::instance().createTerminalProcessInterfaceHook()(); const ProcessImpl impl = m_setup.m_processImpl == ProcessImpl::Default ? defaultProcessImpl() : m_setup.m_processImpl; diff --git a/src/libs/utils/terminalhooks.cpp b/src/libs/utils/terminalhooks.cpp new file mode 100644 index 0000000000..d0b4807568 --- /dev/null +++ b/src/libs/utils/terminalhooks.cpp @@ -0,0 +1,48 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "terminalhooks.h" + +#include "filepath.h" +#include "terminalprocess_p.h" + +namespace Utils::Terminal { + +struct HooksPrivate +{ + HooksPrivate() + : m_openTerminalHook([](const OpenTerminalParameters ¶meters) { + DeviceFileHooks::instance().openTerminal(parameters.workingDirectory.value_or( + FilePath{}), + parameters.environment.value_or(Environment{})); + }) + , m_createTerminalProcessInterfaceHook( + []() -> ProcessInterface * { return new Internal::TerminalImpl(); }) + {} + + Hooks::OpenTerminalHook m_openTerminalHook; + Hooks::CreateTerminalProcessInterfaceHook m_createTerminalProcessInterfaceHook; +}; + +Hooks &Hooks::instance() +{ + static Hooks manager; + return manager; +} + +Hooks::Hooks() + : d(new HooksPrivate()) +{} + +Hooks::~Hooks() = default; + +Hooks::OpenTerminalHook &Hooks::openTerminalHook() +{ + return d->m_openTerminalHook; +} +Hooks::CreateTerminalProcessInterfaceHook &Hooks::createTerminalProcessInterfaceHook() +{ + return d->m_createTerminalProcessInterfaceHook; +} + +} // namespace Utils::Terminal diff --git a/src/libs/utils/terminalhooks.h b/src/libs/utils/terminalhooks.h new file mode 100644 index 0000000000..57012afc90 --- /dev/null +++ b/src/libs/utils/terminalhooks.h @@ -0,0 +1,70 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "commandline.h" +#include "environment.h" +#include "filepath.h" + +#include +#include + +namespace Utils { +class ProcessInterface; + +template +class Hook +{ +public: + using Callback = std::function; + +public: + Hook() = delete; + Hook(const Hook &other) = delete; + Hook(Hook &&other) = delete; + Hook &operator=(const Hook &other) = delete; + Hook &operator=(Hook &&other) = delete; + + explicit Hook(Callback defaultCallback) { set(defaultCallback); } + + void set(Callback cb) { m_callback = cb; } + R operator()(Params &&...params) { return m_callback(std::forward(params)...); } + +private: + Callback m_callback; +}; + +namespace Terminal { +struct HooksPrivate; + +enum class ExitBehavior { Close, Restart, Keep }; + +struct OpenTerminalParameters +{ + std::optional shellCommand; + std::optional workingDirectory; + std::optional environment; + ExitBehavior m_exitBehavior{ExitBehavior::Close}; +}; + +class QTCREATOR_UTILS_EXPORT Hooks +{ +public: + using OpenTerminalHook = Hook; + using CreateTerminalProcessInterfaceHook = Hook; + +public: + static Hooks &instance(); + + OpenTerminalHook &openTerminalHook(); + CreateTerminalProcessInterfaceHook &createTerminalProcessInterfaceHook(); + + ~Hooks(); +private: + Hooks(); + std::unique_ptr d; +}; + +} // namespace Terminal +} // namespace Utils diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index 11c0e34282..a1b3415e71 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -307,6 +307,8 @@ Project { "temporaryfile.h", "terminalcommand.cpp", "terminalcommand.h", + "terminalhooks.cpp", + "terminalhooks.h", "terminalprocess.cpp", "terminalprocess_p.h", "textfieldcheckbox.cpp", -- cgit v1.2.3 From a62c5cf89a1bf2bd16af4462bfb56635e8e34867 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Thu, 9 Feb 2023 14:41:59 +0100 Subject: Utils: Cleanup searchInX functions Also move some often used types into new file "utiltypes.h" Change-Id: I3f152d1dc2f96ba0259ad6c098d9ac5ee03a59f1 Reviewed-by: Qt CI Bot Reviewed-by: hjk --- src/libs/utils/environment.cpp | 173 ++++++++++++++++++++++------------------- src/libs/utils/environment.h | 8 +- src/libs/utils/filepath.cpp | 4 +- src/libs/utils/filepath.h | 7 +- src/libs/utils/filesearch.cpp | 4 +- src/libs/utils/utiltypes.h | 14 ++++ 6 files changed, 117 insertions(+), 93 deletions(-) create mode 100644 src/libs/utils/utiltypes.h (limited to 'src/libs') diff --git a/src/libs/utils/environment.cpp b/src/libs/utils/environment.cpp index c99293329c..96e7ea1775 100644 --- a/src/libs/utils/environment.cpp +++ b/src/libs/utils/environment.cpp @@ -4,6 +4,7 @@ #include "environment.h" #include "algorithm.h" +#include "filepath.h" #include "qtcassert.h" #include @@ -130,36 +131,44 @@ void Environment::setupEnglishOutput() m_dict.set("LANGUAGE", "en_US:en"); } -static FilePath searchInDirectory(const QStringList &execs, - const FilePath &directory, - QSet &alreadyChecked) +using SearchResultCallback = std::function; + +static IterationPolicy searchInDirectory(const SearchResultCallback &resultCallback, + const FilePaths &execs, + const FilePath &directory, + QSet &alreadyCheckedDirectories, + const FilePathPredicate &filter = {}) { - const int checkedCount = alreadyChecked.count(); - alreadyChecked.insert(directory); + // Compare the initial size of the set with the size after insertion to check if the directory + // was already checked. + const int initialCount = alreadyCheckedDirectories.count(); + alreadyCheckedDirectories.insert(directory); + const bool wasAlreadyChecked = alreadyCheckedDirectories.count() == initialCount; - if (directory.isEmpty() || alreadyChecked.count() == checkedCount) - return FilePath(); + if (directory.isEmpty() || wasAlreadyChecked) + return IterationPolicy::Continue; - for (const QString &exec : execs) { - const FilePath filePath = directory.pathAppended(exec); - if (filePath.isExecutableFile()) - return filePath; + for (const FilePath &exec : execs) { + const FilePath filePath = directory / exec.path(); + if (filePath.isExecutableFile() && (!filter || filter(filePath))) { + if (resultCallback(filePath) == IterationPolicy::Stop) + return IterationPolicy::Stop; + } } - return FilePath(); + return IterationPolicy::Continue; } -static QStringList appendExeExtensions(const Environment &env, const QString &executable) +static FilePaths appendExeExtensions(const Environment &env, const FilePath &executable) { - QStringList execs(executable); + FilePaths execs{executable}; if (env.osType() == OsTypeWindows) { - const QFileInfo fi(executable); // Check all the executable extensions on windows: // PATHEXT is only used if the executable has no extension - if (fi.suffix().isEmpty()) { + if (executable.suffix().isEmpty()) { const QStringList extensions = env.expandedValueForKey("PATHEXT").split(';'); for (const QString &ext : extensions) - execs << executable + ext.toLower(); + execs << executable.stringAppended(ext.toLower()); } } return execs; @@ -170,99 +179,101 @@ QString Environment::expandedValueForKey(const QString &key) const return expandVariables(m_dict.value(key)); } -static FilePath searchInDirectoriesHelper(const Environment &env, - const QString &executable, - const FilePaths &dirs, - const Environment::PathFilter &func, - bool usePath) +static void searchInDirectoriesHelper(const SearchResultCallback &resultCallback, + const Environment &env, + const QString &executable, + const FilePaths &dirs, + const FilePathPredicate &func, + bool usePath) { if (executable.isEmpty()) - return FilePath(); - - const QString exec = QDir::cleanPath(env.expandVariables(executable)); - const QFileInfo fi(exec); + return; - const QStringList execs = appendExeExtensions(env, exec); + const FilePath exec = FilePath::fromUserInput(QDir::cleanPath(env.expandVariables(executable))); + const FilePaths execs = appendExeExtensions(env, exec); - if (fi.isAbsolute()) { - for (const QString &path : execs) { - QFileInfo pfi = QFileInfo(path); - if (pfi.isFile() && pfi.isExecutable()) - return FilePath::fromString(path); + if (exec.isAbsolutePath()) { + for (const FilePath &path : execs) { + if (path.isExecutableFile() && (!func || func(path))) + if (resultCallback(path) == IterationPolicy::Stop) + return; } - return FilePath::fromString(exec); + return; } - QSet alreadyChecked; + QSet alreadyCheckedDirectories; for (const FilePath &dir : dirs) { - FilePath tmp = searchInDirectory(execs, dir, alreadyChecked); - if (!tmp.isEmpty() && (!func || func(tmp))) - return tmp; + if (searchInDirectory(resultCallback, execs, dir, alreadyCheckedDirectories, func) + == IterationPolicy::Stop) + return; } if (usePath) { - if (executable.contains('/')) - return FilePath(); + QTC_ASSERT(!executable.contains('/'), return); for (const FilePath &p : env.path()) { - FilePath tmp = searchInDirectory(execs, p, alreadyChecked); - if (!tmp.isEmpty() && (!func || func(tmp))) - return tmp; + if (searchInDirectory(resultCallback, execs, p, alreadyCheckedDirectories, func) + == IterationPolicy::Stop) + return; } } - return FilePath(); + return; } FilePath Environment::searchInDirectories(const QString &executable, const FilePaths &dirs, - const PathFilter &func) const -{ - return searchInDirectoriesHelper(*this, executable, dirs, func, false); + const FilePathPredicate &func) const +{ + FilePath result; + searchInDirectoriesHelper( + [&result](const FilePath &path) { + result = path; + return IterationPolicy::Stop; + }, + *this, + executable, + dirs, + func, + false); + + return result; } FilePath Environment::searchInPath(const QString &executable, const FilePaths &additionalDirs, - const PathFilter &func) const -{ - return searchInDirectoriesHelper(*this, executable, additionalDirs, func, true); + const FilePathPredicate &func) const +{ + FilePath result; + searchInDirectoriesHelper( + [&result](const FilePath &path) { + result = path; + return IterationPolicy::Stop; + }, + *this, + executable, + additionalDirs, + func, + true); + + return result; } FilePaths Environment::findAllInPath(const QString &executable, - const FilePaths &additionalDirs, - const Environment::PathFilter &func) const + const FilePaths &additionalDirs, + const FilePathPredicate &func) const { - if (executable.isEmpty()) - return {}; - - const QString exec = QDir::cleanPath(expandVariables(executable)); - const QFileInfo fi(exec); - - const QStringList execs = appendExeExtensions(*this, exec); - - if (fi.isAbsolute()) { - for (const QString &path : execs) { - QFileInfo pfi = QFileInfo(path); - if (pfi.isFile() && pfi.isExecutable()) - return {FilePath::fromString(path)}; - } - return {FilePath::fromString(exec)}; - } - QSet result; - QSet alreadyChecked; - for (const FilePath &dir : additionalDirs) { - FilePath tmp = searchInDirectory(execs, dir, alreadyChecked); - if (!tmp.isEmpty() && (!func || func(tmp))) - result << tmp; - } + searchInDirectoriesHelper( + [&result](const FilePath &path) { + result.insert(path); + return IterationPolicy::Continue; + }, + *this, + executable, + additionalDirs, + func, + true); - if (!executable.contains('/')) { - for (const FilePath &p : path()) { - FilePath tmp = searchInDirectory(execs, p, alreadyChecked); - if (!tmp.isEmpty() && (!func || func(tmp))) - result << tmp; - } - } return result.values(); } diff --git a/src/libs/utils/environment.h b/src/libs/utils/environment.h index 2671e972cf..e6bf46b32d 100644 --- a/src/libs/utils/environment.h +++ b/src/libs/utils/environment.h @@ -8,6 +8,7 @@ #include "environmentfwd.h" #include "filepath.h" #include "namevaluedictionary.h" +#include "utiltypes.h" #include #include @@ -56,16 +57,15 @@ public: void setupEnglishOutput(); - using PathFilter = std::function; FilePath searchInPath(const QString &executable, const FilePaths &additionalDirs = FilePaths(), - const PathFilter &func = PathFilter()) const; + const FilePathPredicate &func = {}) const; FilePath searchInDirectories(const QString &executable, const FilePaths &dirs, - const PathFilter &func = {}) const; + const FilePathPredicate &func = {}) const; FilePaths findAllInPath(const QString &executable, const FilePaths &additionalDirs = {}, - const PathFilter &func = {}) const; + const FilePathPredicate &func = {}) const; FilePaths path() const; FilePaths pathListValue(const QString &varName) const; diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 7e61ac0d37..7d2b07f8c2 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -1451,7 +1451,7 @@ FilePath FilePath::withNewPath(const QString &newPath) const assert(fullPath == FilePath::fromUrl("docker://123/usr/bin/make")) \endcode */ -FilePath FilePath::searchInDirectories(const FilePaths &dirs, const PathFilter &filter) const +FilePath FilePath::searchInDirectories(const FilePaths &dirs, const FilePathPredicate &filter) const { if (isAbsolutePath()) return *this; @@ -1460,7 +1460,7 @@ FilePath FilePath::searchInDirectories(const FilePaths &dirs, const PathFilter & FilePath FilePath::searchInPath(const FilePaths &additionalDirs, PathAmending amending, - const PathFilter &filter) const + const FilePathPredicate &filter) const { if (isAbsolutePath()) return *this; diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h index 8aa59eb5d9..77a3b84382 100644 --- a/src/libs/utils/filepath.h +++ b/src/libs/utils/filepath.h @@ -8,6 +8,7 @@ #include "expected.h" #include "filepathinfo.h" #include "osspecificaspects.h" +#include "utiltypes.h" #include #include @@ -51,8 +52,6 @@ public: using FilePaths = QList; -enum class IterationPolicy { Stop, Continue }; - class QTCREATOR_UTILS_EXPORT FilePath { public: @@ -159,7 +158,7 @@ public: [[nodiscard]] FilePath relativeChildPath(const FilePath &parent) const; [[nodiscard]] FilePath relativePathFrom(const FilePath &anchor) const; [[nodiscard]] FilePath searchInDirectories(const FilePaths &dirs, - const PathFilter &filter = {}) const; + const FilePathPredicate &filter = {}) const; [[nodiscard]] Environment deviceEnvironment() const; [[nodiscard]] FilePath onDevice(const FilePath &deviceTemplate) const; [[nodiscard]] FilePath withNewPath(const QString &newPath) const; @@ -182,7 +181,7 @@ public: enum PathAmending { AppendToPath, PrependToPath }; [[nodiscard]] FilePath searchInPath(const FilePaths &additionalDirs = {}, PathAmending = AppendToPath, - const PathFilter &filter = {}) const; + const FilePathPredicate &filter = {}) const; enum MatchScope { ExactMatchOnly, WithExeSuffix, WithBatSuffix, WithExeOrBatSuffix, WithAnySuffix }; diff --git a/src/libs/utils/filesearch.cpp b/src/libs/utils/filesearch.cpp index ccbcfbfb9f..d0fa1c71c3 100644 --- a/src/libs/utils/filesearch.cpp +++ b/src/libs/utils/filesearch.cpp @@ -9,6 +9,7 @@ #include "qtcassert.h" #include "stringutils.h" #include "utilstr.h" +#include "utiltypes.h" #include #include @@ -496,8 +497,7 @@ static bool isFileIncluded(const QList &filterRegs, return isIncluded && (exclusionRegs.isEmpty() || !matches(exclusionRegs, filePath)); } -std::function filterFileFunction(const QStringList &filters, - const QStringList &exclusionFilters) +FilePathPredicate filterFileFunction(const QStringList &filters, const QStringList &exclusionFilters) { const QList filterRegs = filtersToRegExps(filters); const QList exclusionRegs = filtersToRegExps(exclusionFilters); diff --git a/src/libs/utils/utiltypes.h b/src/libs/utils/utiltypes.h new file mode 100644 index 0000000000..967eecb5a5 --- /dev/null +++ b/src/libs/utils/utiltypes.h @@ -0,0 +1,14 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +namespace Utils { +class FilePath; + +enum class IterationPolicy { Stop, Continue }; + +using FilePathPredicate = std::function; +} // namespace Utils -- cgit v1.2.3 From c41d30711a03ada34091c40499a41ffdb0da3d84 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Thu, 23 Feb 2023 15:34:05 +0100 Subject: Utils: Make UnixDeviceFileAccess macOS compatible "stat" on macOS has slightly different formatting options. Also adds unittests for UnixDeviceFileAccess Task-number: QTCREATORBUG-28142 Change-Id: Ib42fc1c22ef2771365e915df34f2286e2c705568 Reviewed-by: hjk Reviewed-by: Qt CI Bot Reviewed-by: --- src/libs/utils/devicefileaccess.cpp | 94 ++++++++++++++++++++++++++----------- src/libs/utils/devicefileaccess.h | 10 ++++ src/libs/utils/fileutils.cpp | 9 ++-- src/libs/utils/fileutils.h | 2 +- 4 files changed, 82 insertions(+), 33 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/devicefileaccess.cpp b/src/libs/utils/devicefileaccess.cpp index 3d8354d7a0..c50f3b145c 100644 --- a/src/libs/utils/devicefileaccess.cpp +++ b/src/libs/utils/devicefileaccess.cpp @@ -8,6 +8,7 @@ #include "environment.h" #include "expected.h" #include "hostosinfo.h" +#include "osspecificaspects.h" #include "qtcassert.h" #include "utilstr.h" @@ -820,7 +821,7 @@ QByteArray DesktopDeviceFileAccess::fileId(const FilePath &filePath) const OsType DesktopDeviceFileAccess::osType(const FilePath &filePath) const { - Q_UNUSED(filePath); + Q_UNUSED(filePath) return HostOsInfo::hostOs(); } @@ -1054,10 +1055,28 @@ expected_str UnixDeviceFileAccess::createTempFile(const FilePath &file return newPath; } +OsType UnixDeviceFileAccess::osType() const +{ + if (m_osType) + return *m_osType; + + const RunResult result = runInShell({"uname", {"-s"}, OsType::OsTypeLinux}); + QTC_ASSERT(result.exitCode == 0, return OsTypeLinux); + const QString osName = QString::fromUtf8(result.stdOut).trimmed(); + if (osName == "Darwin") + m_osType = OsTypeMac; + else if (osName == "Linux") + m_osType = OsTypeLinux; + else + m_osType = OsTypeOtherUnix; + + return *m_osType; +} + OsType UnixDeviceFileAccess::osType(const FilePath &filePath) const { - Q_UNUSED(filePath) - return OsTypeLinux; + Q_UNUSED(filePath); + return osType(); } QDateTime UnixDeviceFileAccess::lastModified(const FilePath &filePath) const @@ -1069,10 +1088,19 @@ QDateTime UnixDeviceFileAccess::lastModified(const FilePath &filePath) const return dt; } +QStringList UnixDeviceFileAccess::statArgs(const FilePath &filePath, + const QString &linuxFormat, + const QString &macFormat) const +{ + return (osType() == OsTypeMac ? QStringList{"-f", macFormat} : QStringList{"-c", linuxFormat}) + << "-L" << filePath.path(); +} + QFile::Permissions UnixDeviceFileAccess::permissions(const FilePath &filePath) const { - const RunResult result = runInShell( - {"stat", {"-L", "-c", "%a", filePath.path()}, OsType::OsTypeLinux}); + QStringList args = statArgs(filePath, "%a", "%p"); + + const RunResult result = runInShell({"stat", args, OsType::OsTypeLinux}); const uint bits = result.stdOut.toUInt(nullptr, 8); QFileDevice::Permissions perm = {}; #define BIT(n, p) \ @@ -1100,8 +1128,8 @@ bool UnixDeviceFileAccess::setPermissions(const FilePath &filePath, QFile::Permi qint64 UnixDeviceFileAccess::fileSize(const FilePath &filePath) const { - const RunResult result = runInShell( - {"stat", {"-L", "-c", "%s", filePath.path()}, OsType::OsTypeLinux}); + const QStringList args = statArgs(filePath, "%s", "%z"); + const RunResult result = runInShell({"stat", args, OsType::OsTypeLinux}); return result.stdOut.toLongLong(); } @@ -1113,8 +1141,9 @@ qint64 UnixDeviceFileAccess::bytesAvailable(const FilePath &filePath) const QByteArray UnixDeviceFileAccess::fileId(const FilePath &filePath) const { - const RunResult result = runInShell( - {"stat", {"-L", "-c", "%D:%i", filePath.path()}, OsType::OsTypeLinux}); + const QStringList args = statArgs(filePath, "%D:%i", "%d:%i"); + + const RunResult result = runInShell({"stat", args, OsType::OsTypeLinux}); if (result.exitCode != 0) return {}; @@ -1136,9 +1165,12 @@ FilePathInfo UnixDeviceFileAccess::filePathInfo(const FilePath &filePath) const return r; } - const RunResult stat = runInShell( - {"stat", {"-L", "-c", "%f %Y %s", filePath.path()}, OsType::OsTypeLinux}); - return FileUtils::filePathInfoFromTriple(QString::fromLatin1(stat.stdOut)); + + const QStringList args = statArgs(filePath, "%f %Y %s", "%p %m %z"); + + const RunResult stat = runInShell({"stat", args, OsType::OsTypeLinux}); + return FileUtils::filePathInfoFromTriple(QString::fromLatin1(stat.stdOut), + osType() != OsTypeMac); } // returns whether 'find' could be used. @@ -1152,8 +1184,13 @@ bool UnixDeviceFileAccess::iterateWithFind(const FilePath &filePath, // TODO: Using stat -L will always return the link target, not the link itself. // We may wan't to add the information that it is a link at some point. + + const QString statFormat = osType() == OsTypeMac + ? QLatin1String("-f \"%p %m %z\"") : QLatin1String("-c \"%f %Y %s\""); + if (callBack.index() == 1) - cmdLine.addArgs(R"(-exec echo -n \"{}\"" " \; -exec stat -L -c "%f %Y %s" "{}" \;)", + cmdLine.addArgs(QString(R"(-exec echo -n \"{}\"" " \; -exec stat -L %1 "{}" \;)") + .arg(statFormat), CommandLine::Raw); const RunResult result = runInShell(cmdLine); @@ -1175,23 +1212,26 @@ bool UnixDeviceFileAccess::iterateWithFind(const FilePath &filePath, if (entries.isEmpty()) return true; - const auto toFilePath = [&filePath, &callBack](const QString &entry) { - if (callBack.index() == 0) - return std::get<0>(callBack)(filePath.withNewPath(entry)); + const int modeBase = osType() == OsTypeMac ? 8 : 16; - const QString fileName = entry.mid(1, entry.lastIndexOf('\"') - 1); - const QString infos = entry.mid(fileName.length() + 3); + const auto toFilePath = + [&filePath, &callBack, modeBase](const QString &entry) { + if (callBack.index() == 0) + return std::get<0>(callBack)(filePath.withNewPath(entry)); - const FilePathInfo fi = FileUtils::filePathInfoFromTriple(infos); - if (!fi.fileFlags) - return IterationPolicy::Continue; + const QString fileName = entry.mid(1, entry.lastIndexOf('\"') - 1); + const QString infos = entry.mid(fileName.length() + 3); - const FilePath fp = filePath.withNewPath(fileName); - // Do not return the entry for the directory we are searching in. - if (fp.path() == filePath.path()) - return IterationPolicy::Continue; - return std::get<1>(callBack)(fp, fi); - }; + const FilePathInfo fi = FileUtils::filePathInfoFromTriple(infos, modeBase); + if (!fi.fileFlags) + return IterationPolicy::Continue; + + const FilePath fp = filePath.withNewPath(fileName); + // Do not return the entry for the directory we are searching in. + if (fp.path() == filePath.path()) + return IterationPolicy::Continue; + return std::get<1>(callBack)(fp, fi); + }; // Remove the first line, this can be the directory we are searching in. // as long as we do not specify "mindepth > 0" diff --git a/src/libs/utils/devicefileaccess.h b/src/libs/utils/devicefileaccess.h index 8fe8201e08..238bab5e77 100644 --- a/src/libs/utils/devicefileaccess.h +++ b/src/libs/utils/devicefileaccess.h @@ -3,10 +3,13 @@ #pragma once +#include "hostosinfo.h" #include "utils_global.h" #include "fileutils.h" +class tst_unixdevicefileaccess; // For testing. + namespace Utils { // Base class including dummy implementation usable as fallback. @@ -19,6 +22,7 @@ public: protected: friend class FilePath; + friend class ::tst_unixdevicefileaccess; // For testing. virtual QString mapToDevicePath(const QString &hostPath) const; @@ -204,8 +208,14 @@ private: const FileFilter &filter, QStringList *found) const; + Utils::OsType osType() const; + QStringList statArgs(const FilePath &filePath, + const QString &linuxFormat, + const QString &macFormat) const; + mutable bool m_tryUseFind = true; mutable std::optional m_hasMkTemp; + mutable std::optional m_osType; }; } // Utils diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index d128f21974..c0a574f9c3 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -586,8 +586,7 @@ FilePaths FileUtils::getOpenFilePaths(QWidget *parent, #endif // QT_WIDGETS_LIB -// Converts a hex string of the st_mode field of a stat structure to FileFlags. -FilePathInfo::FileFlags fileInfoFlagsfromStatRawModeHex(const QString &hexString) +FilePathInfo::FileFlags fileInfoFlagsfromStatMode(const QString &hexString, int modeBase) { // Copied from stat.h enum st_mode { @@ -617,7 +616,7 @@ FilePathInfo::FileFlags fileInfoFlagsfromStatRawModeHex(const QString &hexString }; bool ok = false; - uint mode = hexString.toUInt(&ok, 16); + uint mode = hexString.toUInt(&ok, modeBase); QTC_ASSERT(ok, return {}); @@ -657,13 +656,13 @@ FilePathInfo::FileFlags fileInfoFlagsfromStatRawModeHex(const QString &hexString return result; } -FilePathInfo FileUtils::filePathInfoFromTriple(const QString &infos) +FilePathInfo FileUtils::filePathInfoFromTriple(const QString &infos, int modeBase) { const QStringList parts = infos.split(' ', Qt::SkipEmptyParts); if (parts.size() != 3) return {}; - FilePathInfo::FileFlags flags = fileInfoFlagsfromStatRawModeHex(parts[0]); + FilePathInfo::FileFlags flags = fileInfoFlagsfromStatMode(parts[0], modeBase); const QDateTime dt = QDateTime::fromSecsSinceEpoch(parts[1].toLongLong(), Qt::UTC); qint64 size = parts[2].toLongLong(); diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index ec3de7a5ba..19f17a6d71 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -83,7 +83,7 @@ public: static qint64 bytesAvailableFromDFOutput(const QByteArray &dfOutput); - static FilePathInfo filePathInfoFromTriple(const QString &infos); + static FilePathInfo filePathInfoFromTriple(const QString &infos, int modeBase); #ifdef QT_WIDGETS_LIB static void setDialogParentGetter(const std::function &getter); -- cgit v1.2.3 From d4ac8aeaa6063554fdef51c81a752417b0fd0de7 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Sat, 25 Feb 2023 10:47:21 +0100 Subject: Terminal: Add remote devices to shell selection Change-Id: Id28471aaf3e91ef493f48ab28207230f3fb513c2 Reviewed-by: Cristian Adam --- src/libs/utils/terminalhooks.cpp | 8 ++++++++ src/libs/utils/terminalhooks.h | 10 +++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/terminalhooks.cpp b/src/libs/utils/terminalhooks.cpp index d0b4807568..df27e0404b 100644 --- a/src/libs/utils/terminalhooks.cpp +++ b/src/libs/utils/terminalhooks.cpp @@ -18,10 +18,12 @@ struct HooksPrivate }) , m_createTerminalProcessInterfaceHook( []() -> ProcessInterface * { return new Internal::TerminalImpl(); }) + , m_getTerminalCommandsForDevicesHook([]() -> QList { return {}; }) {} Hooks::OpenTerminalHook m_openTerminalHook; Hooks::CreateTerminalProcessInterfaceHook m_createTerminalProcessInterfaceHook; + Hooks::GetTerminalCommandsForDevicesHook m_getTerminalCommandsForDevicesHook; }; Hooks &Hooks::instance() @@ -40,9 +42,15 @@ Hooks::OpenTerminalHook &Hooks::openTerminalHook() { return d->m_openTerminalHook; } + Hooks::CreateTerminalProcessInterfaceHook &Hooks::createTerminalProcessInterfaceHook() { return d->m_createTerminalProcessInterfaceHook; } +Hooks::GetTerminalCommandsForDevicesHook &Hooks::getTerminalCommandsForDevicesHook() +{ + return d->m_getTerminalCommandsForDevicesHook; +} + } // namespace Utils::Terminal diff --git a/src/libs/utils/terminalhooks.h b/src/libs/utils/terminalhooks.h index 57012afc90..492c53da81 100644 --- a/src/libs/utils/terminalhooks.h +++ b/src/libs/utils/terminalhooks.h @@ -48,19 +48,27 @@ struct OpenTerminalParameters ExitBehavior m_exitBehavior{ExitBehavior::Close}; }; +struct NameAndCommandLine +{ + QString name; + CommandLine commandLine; +}; + class QTCREATOR_UTILS_EXPORT Hooks { public: using OpenTerminalHook = Hook; using CreateTerminalProcessInterfaceHook = Hook; + using GetTerminalCommandsForDevicesHook = Hook>; public: static Hooks &instance(); + ~Hooks(); OpenTerminalHook &openTerminalHook(); CreateTerminalProcessInterfaceHook &createTerminalProcessInterfaceHook(); + GetTerminalCommandsForDevicesHook &getTerminalCommandsForDevicesHook(); - ~Hooks(); private: Hooks(); std::unique_ptr d; -- cgit v1.2.3 From 3e73fe302e65af7860f47ec22153e2c0479f99ff Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Mon, 27 Feb 2023 11:06:14 +0100 Subject: Terminal: Coverity warning fixes Change-Id: If96291ff6df97f7e85840eb0951cc3f4abfab0f6 Reviewed-by: Eike Ziller --- src/libs/3rdparty/libptyqt/unixptyprocess.cpp | 7 ++++--- src/libs/3rdparty/libvterm/src/state.c | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'src/libs') diff --git a/src/libs/3rdparty/libptyqt/unixptyprocess.cpp b/src/libs/3rdparty/libptyqt/unixptyprocess.cpp index a9167e7696..7cb8237a60 100644 --- a/src/libs/3rdparty/libptyqt/unixptyprocess.cpp +++ b/src/libs/3rdparty/libptyqt/unixptyprocess.cpp @@ -343,15 +343,16 @@ void ShellProcess::configChildProcess() struct utmpx utmpxInfo; memset(&utmpxInfo, 0, sizeof(utmpxInfo)); - strncpy(utmpxInfo.ut_user, qgetenv("USER"), sizeof(utmpxInfo.ut_user)); + strncpy(utmpxInfo.ut_user, qgetenv("USER"), sizeof(utmpxInfo.ut_user) - 1); QString device(m_handleSlaveName); if (device.startsWith("/dev/")) device = device.mid(5); - const char *d = device.toLatin1().constData(); + const auto deviceAsLatin1 = device.toLatin1(); + const char *d = deviceAsLatin1.constData(); - strncpy(utmpxInfo.ut_line, d, sizeof(utmpxInfo.ut_line)); + strncpy(utmpxInfo.ut_line, d, sizeof(utmpxInfo.ut_line) - 1); strncpy(utmpxInfo.ut_id, d + strlen(d) - sizeof(utmpxInfo.ut_id), sizeof(utmpxInfo.ut_id)); diff --git a/src/libs/3rdparty/libvterm/src/state.c b/src/libs/3rdparty/libvterm/src/state.c index b22fba706b..313e746e77 100644 --- a/src/libs/3rdparty/libvterm/src/state.c +++ b/src/libs/3rdparty/libvterm/src/state.c @@ -1842,7 +1842,7 @@ static void request_status_string(VTermState *state, VTermStringFragment frag) case ' '|('q'<<8): { // Query DECSCUSR - int reply; + int reply = 2; switch(state->mode.cursor_shape) { case VTERM_PROP_CURSORSHAPE_BLOCK: reply = 2; break; case VTERM_PROP_CURSORSHAPE_UNDERLINE: reply = 4; break; -- cgit v1.2.3 From 61de69ea907bfd5e7d0b6eb97975194352c9c5f3 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Thu, 9 Feb 2023 15:11:14 +0100 Subject: CPlusPlus: Handle C++20 concepts in parser Change-Id: I8c6b8b1ba3f36b83cd1d667bec9830271147b1ac Reviewed-by: Qt CI Bot Reviewed-by: Reviewed-by: Christian Stenger --- src/libs/3rdparty/cplusplus/AST.cpp | 30 +++++ src/libs/3rdparty/cplusplus/AST.h | 93 +++++++++++++++ src/libs/3rdparty/cplusplus/ASTClone.cpp | 54 +++++++++ src/libs/3rdparty/cplusplus/ASTMatch0.cpp | 30 +++++ src/libs/3rdparty/cplusplus/ASTMatcher.cpp | 66 ++++++++++ src/libs/3rdparty/cplusplus/ASTMatcher.h | 4 + src/libs/3rdparty/cplusplus/ASTVisit.cpp | 34 ++++++ src/libs/3rdparty/cplusplus/ASTVisitor.h | 8 ++ src/libs/3rdparty/cplusplus/ASTfwd.h | 4 + src/libs/3rdparty/cplusplus/Bind.cpp | 5 + src/libs/3rdparty/cplusplus/Bind.h | 1 + src/libs/3rdparty/cplusplus/Parser.cpp | 185 ++++++++++++++++++++++++++++- src/libs/3rdparty/cplusplus/Parser.h | 5 + 13 files changed, 518 insertions(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/3rdparty/cplusplus/AST.cpp b/src/libs/3rdparty/cplusplus/AST.cpp index 5d7c9e2a90..260b358271 100644 --- a/src/libs/3rdparty/cplusplus/AST.cpp +++ b/src/libs/3rdparty/cplusplus/AST.cpp @@ -4634,3 +4634,33 @@ int NoExceptOperatorExpressionAST::lastToken() const return noexcept_token + 1; return 1; } + +int TypeConstraintAST::firstToken() const +{ + if (nestedName) + return nestedName->firstToken(); + return conceptName->firstToken(); +} + +int TypeConstraintAST::lastToken() const +{ + if (greaterToken) + return greaterToken + 1; + return conceptName->lastToken(); +} + +int PlaceholderTypeSpecifierAST::firstToken() const +{ + if (typeConstraint) + return typeConstraint->firstToken(); + if (declTypetoken) + return declTypetoken; + return autoToken; +} + +int PlaceholderTypeSpecifierAST::lastToken() const +{ + if (rparenToken) + return rparenToken + 1; + return autoToken + 1; +} diff --git a/src/libs/3rdparty/cplusplus/AST.h b/src/libs/3rdparty/cplusplus/AST.h index 9aa81ccdf6..9288725edd 100644 --- a/src/libs/3rdparty/cplusplus/AST.h +++ b/src/libs/3rdparty/cplusplus/AST.h @@ -290,6 +290,7 @@ public: virtual OperatorFunctionIdAST *asOperatorFunctionId() { return nullptr; } virtual ParameterDeclarationAST *asParameterDeclaration() { return nullptr; } virtual ParameterDeclarationClauseAST *asParameterDeclarationClause() { return nullptr; } + virtual PlaceholderTypeSpecifierAST *asPlaceholderTypeSpecifier() { return nullptr; } virtual PointerAST *asPointer() { return nullptr; } virtual PointerLiteralAST *asPointerLiteral() { return nullptr; } virtual PointerToMemberAST *asPointerToMember() { return nullptr; } @@ -322,6 +323,7 @@ public: virtual StringLiteralAST *asStringLiteral() { return nullptr; } virtual SwitchStatementAST *asSwitchStatement() { return nullptr; } virtual TemplateDeclarationAST *asTemplateDeclaration() { return nullptr; } + virtual ConceptDeclarationAST *asConceptDeclaration() { return nullptr; } virtual TemplateIdAST *asTemplateId() { return nullptr; } virtual TemplateTypeParameterAST *asTemplateTypeParameter() { return nullptr; } virtual ThisExpressionAST *asThisExpression() { return nullptr; } @@ -329,6 +331,7 @@ public: virtual TrailingReturnTypeAST *asTrailingReturnType() { return nullptr; } virtual TranslationUnitAST *asTranslationUnit() { return nullptr; } virtual TryBlockStatementAST *asTryBlockStatement() { return nullptr; } + virtual TypeConstraintAST *asTypeConstraint() { return nullptr; } virtual TypeConstructorCallAST *asTypeConstructorCall() { return nullptr; } virtual TypeIdAST *asTypeId() { return nullptr; } virtual TypeidExpressionAST *asTypeidExpression() { return nullptr; } @@ -336,6 +339,7 @@ public: virtual TypenameTypeParameterAST *asTypenameTypeParameter() { return nullptr; } virtual TypeofSpecifierAST *asTypeofSpecifier() { return nullptr; } virtual UnaryExpressionAST *asUnaryExpression() { return nullptr; } + virtual RequiresExpressionAST *asRequiresExpression() { return nullptr; } virtual UsingAST *asUsing() { return nullptr; } virtual UsingDirectiveAST *asUsingDirective() { return nullptr; } virtual WhileStatementAST *asWhileStatement() { return nullptr; } @@ -636,6 +640,49 @@ protected: bool match0(AST *, ASTMatcher *) override; }; +class CPLUSPLUS_EXPORT TypeConstraintAST: public AST +{ +public: + NestedNameSpecifierListAST *nestedName = nullptr; + NameAST *conceptName = nullptr; + int lessToken = 0; + ExpressionListAST *templateArgs = nullptr; + int greaterToken = 0; + + TypeConstraintAST *asTypeConstraint() override { return this; } + + int firstToken() const override; + int lastToken() const override; + + TypeConstraintAST *clone(MemoryPool *pool) const override; + + void accept0(ASTVisitor *visitor) override; + bool match0(AST *, ASTMatcher *) override; +}; + +class CPLUSPLUS_EXPORT PlaceholderTypeSpecifierAST: public SpecifierAST +{ +public: + TypeConstraintAST *typeConstraint = nullptr; + int declTypetoken = 0; + int lparenToken = 0; + int decltypeToken = 0; + int autoToken = 0; + int rparenToken = 0; + +public: + PlaceholderTypeSpecifierAST *asPlaceholderTypeSpecifier() override { return this; } + + int firstToken() const override; + int lastToken() const override; + + PlaceholderTypeSpecifierAST *clone(MemoryPool *pool) const override; + +protected: + void accept0(ASTVisitor *visitor) override; + bool match0(AST *, ASTMatcher *) override; +}; + class CPLUSPLUS_EXPORT DeclaratorAST: public AST { public: @@ -2734,6 +2781,29 @@ protected: bool match0(AST *, ASTMatcher *) override; }; +class CPLUSPLUS_EXPORT ConceptDeclarationAST: public DeclarationAST +{ +public: + int concept_token = 0; + NameAST *name = nullptr; + SpecifierListAST *attributes = nullptr; + int equals_token = 0; + ExpressionAST *constraint = nullptr; + int semicolon_token = 0; + +public: + ConceptDeclarationAST *asConceptDeclaration() override { return this; } + + int firstToken() const override { return concept_token; } + int lastToken() const override { return semicolon_token + 1; } + + ConceptDeclarationAST *clone(MemoryPool *pool) const override; + +protected: + void accept0(ASTVisitor *visitor) override; + bool match0(AST *, ASTMatcher *) override; +}; + class CPLUSPLUS_EXPORT ThrowExpressionAST: public ExpressionAST { public: @@ -2927,6 +2997,29 @@ protected: bool match0(AST *, ASTMatcher *) override; }; +class CPLUSPLUS_EXPORT RequiresExpressionAST: public ExpressionAST +{ +public: + int requires_token = 0; + int lparen_token = 0; + ParameterDeclarationClauseAST *parameters = nullptr; + int rparen_token = 0; + int lbrace_token = 0; + int rbrace_token = 0; + +public: + RequiresExpressionAST *asRequiresExpression() override { return this; } + + int firstToken() const override { return requires_token; } + int lastToken() const override { return rbrace_token + 1; } + + RequiresExpressionAST *clone(MemoryPool *pool) const override; + +protected: + void accept0(ASTVisitor *visitor) override; + bool match0(AST *, ASTMatcher *) override; +}; + class CPLUSPLUS_EXPORT UsingAST: public DeclarationAST { public: diff --git a/src/libs/3rdparty/cplusplus/ASTClone.cpp b/src/libs/3rdparty/cplusplus/ASTClone.cpp index 9e5a773bdb..4e4cda40f9 100644 --- a/src/libs/3rdparty/cplusplus/ASTClone.cpp +++ b/src/libs/3rdparty/cplusplus/ASTClone.cpp @@ -141,6 +141,32 @@ DecltypeSpecifierAST *DecltypeSpecifierAST::clone(MemoryPool *pool) const return ast; } +TypeConstraintAST *TypeConstraintAST::clone(MemoryPool *pool) const +{ + const auto ast = new (pool) TypeConstraintAST; + for (NestedNameSpecifierListAST *iter = nestedName, **ast_iter = &ast->nestedName; iter; + iter = iter->next, ast_iter = &(*ast_iter)->next) + *ast_iter = new (pool) NestedNameSpecifierListAST((iter->value) ? iter->value->clone(pool) : nullptr); + ast->lessToken = lessToken; + for (ExpressionListAST *iter = templateArgs, **ast_iter = &ast->templateArgs; + iter; iter = iter->next, ast_iter = &(*ast_iter)->next) + *ast_iter = new (pool) ExpressionListAST((iter->value) ? iter->value->clone(pool) : nullptr); + ast->greaterToken = greaterToken; + return ast; +} + +PlaceholderTypeSpecifierAST *PlaceholderTypeSpecifierAST::clone(MemoryPool *pool) const +{ + const auto ast = new (pool) PlaceholderTypeSpecifierAST; + if (typeConstraint) + ast->typeConstraint = typeConstraint->clone(pool); + ast->lparenToken = lparenToken; + ast->declTypetoken = declTypetoken; + ast->autoToken = autoToken; + ast->rparenToken = rparenToken; + return ast; +} + DeclaratorAST *DeclaratorAST::clone(MemoryPool *pool) const { DeclaratorAST *ast = new (pool) DeclaratorAST; @@ -1284,6 +1310,34 @@ TemplateDeclarationAST *TemplateDeclarationAST::clone(MemoryPool *pool) const return ast; } +ConceptDeclarationAST *ConceptDeclarationAST::clone(MemoryPool *pool) const +{ + const auto ast = new (pool) ConceptDeclarationAST; + ast->concept_token = concept_token; + ast->name = name->clone(pool); + ast->equals_token = equals_token; + ast->semicolon_token = semicolon_token; + for (SpecifierListAST *iter = attributes, **ast_iter = &ast->attributes; + iter; iter = iter->next, ast_iter = &(*ast_iter)->next) { + *ast_iter = new (pool) SpecifierListAST((iter->value) ? iter->value->clone(pool) : nullptr); + } + ast->constraint = constraint->clone(pool); + return ast; +} + +RequiresExpressionAST *RequiresExpressionAST::clone(MemoryPool *pool) const +{ + const auto ast = new (pool) RequiresExpressionAST; + ast->requires_token = requires_token; + ast->lparen_token = lparen_token; + if (parameters) + ast->parameters = parameters->clone(pool); + ast->rparen_token = rparen_token; + ast->lbrace_token = lbrace_token; + ast->rbrace_token = rbrace_token; + return ast; +} + ThrowExpressionAST *ThrowExpressionAST::clone(MemoryPool *pool) const { ThrowExpressionAST *ast = new (pool) ThrowExpressionAST; diff --git a/src/libs/3rdparty/cplusplus/ASTMatch0.cpp b/src/libs/3rdparty/cplusplus/ASTMatch0.cpp index 4105ec3c52..afe94aba06 100644 --- a/src/libs/3rdparty/cplusplus/ASTMatch0.cpp +++ b/src/libs/3rdparty/cplusplus/ASTMatch0.cpp @@ -112,6 +112,21 @@ bool DecltypeSpecifierAST::match0(AST *pattern, ASTMatcher *matcher) return false; } +bool TypeConstraintAST::match0(AST *pattern, ASTMatcher *matcher) +{ + if (const auto _other = pattern->asTypeConstraint()) + return matcher->match(this, _other); + + return false; +} + +bool PlaceholderTypeSpecifierAST::match0(AST *pattern, ASTMatcher *matcher) +{ + if (const auto _other = pattern->asPlaceholderTypeSpecifier()) + return matcher->match(this, _other); + return false; +} + bool DeclaratorAST::match0(AST *pattern, ASTMatcher *matcher) { if (DeclaratorAST *_other = pattern->asDeclarator()) @@ -896,6 +911,21 @@ bool TemplateDeclarationAST::match0(AST *pattern, ASTMatcher *matcher) return false; } +bool ConceptDeclarationAST::match0(AST *pattern, ASTMatcher *matcher) +{ + if (ConceptDeclarationAST *_other = pattern->asConceptDeclaration()) + return matcher->match(this, _other); + + return false; +} + +bool RequiresExpressionAST::match0(AST *pattern, ASTMatcher *matcher) +{ + if (const auto other = pattern->asRequiresExpression()) + return matcher->match(this, other); + return false; +} + bool ThrowExpressionAST::match0(AST *pattern, ASTMatcher *matcher) { if (ThrowExpressionAST *_other = pattern->asThrowExpression()) diff --git a/src/libs/3rdparty/cplusplus/ASTMatcher.cpp b/src/libs/3rdparty/cplusplus/ASTMatcher.cpp index fcfe86ab57..ccf41dbae2 100644 --- a/src/libs/3rdparty/cplusplus/ASTMatcher.cpp +++ b/src/libs/3rdparty/cplusplus/ASTMatcher.cpp @@ -216,6 +216,25 @@ bool ASTMatcher::match(DecltypeSpecifierAST *node, DecltypeSpecifierAST *pattern return true; } +bool ASTMatcher::match(TypeConstraintAST *node, TypeConstraintAST *pattern) +{ + if (!pattern->nestedName) + pattern->nestedName = node->nestedName; + else if (!AST::match(node->nestedName, pattern->nestedName, this)) + return false; + if (!pattern->conceptName) + pattern->conceptName = node->conceptName; + else if (!AST::match(node->conceptName, pattern->conceptName, this)) + return false; + pattern->lessToken = node->lessToken; + if (!pattern->templateArgs) + pattern->templateArgs = node->templateArgs; + else if (!AST::match(node->templateArgs, pattern->templateArgs, this)) + return false; + pattern->greaterToken = node->greaterToken; + return true; +} + bool ASTMatcher::match(DeclaratorAST *node, DeclaratorAST *pattern) { (void) node; @@ -1749,6 +1768,19 @@ bool ASTMatcher::match(ParameterDeclarationClauseAST *node, ParameterDeclaration return true; } +bool ASTMatcher::match(PlaceholderTypeSpecifierAST *node, PlaceholderTypeSpecifierAST *pattern) +{ + if (!pattern->typeConstraint) + pattern->typeConstraint = node->typeConstraint; + else if (!AST::match(node->typeConstraint, pattern->typeConstraint, this)) + return false; + pattern->declTypetoken = node->declTypetoken; + pattern->lparenToken = node->lparenToken; + pattern->autoToken = node->autoToken; + pattern->rparenToken = node->rparenToken; + return true; +} + bool ASTMatcher::match(CallAST *node, CallAST *pattern) { (void) node; @@ -2181,6 +2213,40 @@ bool ASTMatcher::match(TemplateDeclarationAST *node, TemplateDeclarationAST *pat return true; } +bool ASTMatcher::match(ConceptDeclarationAST *node, ConceptDeclarationAST *pattern) +{ + pattern->concept_token = node->concept_token; + pattern->equals_token = node->equals_token; + pattern->semicolon_token = node->semicolon_token; + + if (!pattern->attributes) + pattern->attributes = node->attributes; + else if (!AST::match(node->attributes, pattern->attributes, this)) + return false; + + if (!pattern->constraint) + pattern->constraint = node->constraint; + else if (! AST::match(node->constraint, pattern->constraint, this)) + return false; + + return true; +} + +bool ASTMatcher::match(RequiresExpressionAST *node, RequiresExpressionAST *pattern) +{ + pattern->requires_token = node->requires_token; + pattern->lparen_token = node->lparen_token; + pattern->lbrace_token = node->lbrace_token; + pattern->rbrace_token = node->rbrace_token; + + if (!pattern->parameters) + pattern->parameters = node->parameters; + else if (!AST::match(node->parameters, pattern->parameters, this)) + return false; + + return true; +} + bool ASTMatcher::match(ThrowExpressionAST *node, ThrowExpressionAST *pattern) { (void) node; diff --git a/src/libs/3rdparty/cplusplus/ASTMatcher.h b/src/libs/3rdparty/cplusplus/ASTMatcher.h index c243e18b7b..214c37a283 100644 --- a/src/libs/3rdparty/cplusplus/ASTMatcher.h +++ b/src/libs/3rdparty/cplusplus/ASTMatcher.h @@ -54,6 +54,7 @@ public: virtual bool match(CompoundExpressionAST *node, CompoundExpressionAST *pattern); virtual bool match(CompoundLiteralAST *node, CompoundLiteralAST *pattern); virtual bool match(CompoundStatementAST *node, CompoundStatementAST *pattern); + virtual bool match(ConceptDeclarationAST *node, ConceptDeclarationAST *pattern); virtual bool match(ConditionAST *node, ConditionAST *pattern); virtual bool match(ConditionalExpressionAST *node, ConditionalExpressionAST *pattern); virtual bool match(ContinueStatementAST *node, ContinueStatementAST *pattern); @@ -139,6 +140,7 @@ public: virtual bool match(OperatorFunctionIdAST *node, OperatorFunctionIdAST *pattern); virtual bool match(ParameterDeclarationAST *node, ParameterDeclarationAST *pattern); virtual bool match(ParameterDeclarationClauseAST *node, ParameterDeclarationClauseAST *pattern); + virtual bool match(PlaceholderTypeSpecifierAST *node, PlaceholderTypeSpecifierAST *pattern); virtual bool match(PointerAST *node, PointerAST *pattern); virtual bool match(PointerLiteralAST *node, PointerLiteralAST *pattern); virtual bool match(PointerToMemberAST *node, PointerToMemberAST *pattern); @@ -156,6 +158,7 @@ public: virtual bool match(QualifiedNameAST *node, QualifiedNameAST *pattern); virtual bool match(RangeBasedForStatementAST *node, RangeBasedForStatementAST *pattern); virtual bool match(ReferenceAST *node, ReferenceAST *pattern); + virtual bool match(RequiresExpressionAST *node, RequiresExpressionAST *pattern); virtual bool match(ReturnStatementAST *node, ReturnStatementAST *pattern); virtual bool match(SimpleDeclarationAST *node, SimpleDeclarationAST *pattern); virtual bool match(SimpleNameAST *node, SimpleNameAST *pattern); @@ -173,6 +176,7 @@ public: virtual bool match(TrailingReturnTypeAST *node, TrailingReturnTypeAST *pattern); virtual bool match(TranslationUnitAST *node, TranslationUnitAST *pattern); virtual bool match(TryBlockStatementAST *node, TryBlockStatementAST *pattern); + virtual bool match(TypeConstraintAST *node, TypeConstraintAST *pattern); virtual bool match(TypeConstructorCallAST *node, TypeConstructorCallAST *pattern); virtual bool match(TypeIdAST *node, TypeIdAST *pattern); virtual bool match(TypeidExpressionAST *node, TypeidExpressionAST *pattern); diff --git a/src/libs/3rdparty/cplusplus/ASTVisit.cpp b/src/libs/3rdparty/cplusplus/ASTVisit.cpp index 2248f51a50..cb05dbde62 100644 --- a/src/libs/3rdparty/cplusplus/ASTVisit.cpp +++ b/src/libs/3rdparty/cplusplus/ASTVisit.cpp @@ -110,6 +110,23 @@ void DecltypeSpecifierAST::accept0(ASTVisitor *visitor) visitor->endVisit(this); } +void TypeConstraintAST::accept0(ASTVisitor *visitor) +{ + if (visitor->visit(this)) { + accept(nestedName, visitor); + accept(conceptName, visitor); + accept(templateArgs, visitor); + } + visitor->endVisit(this); +} + +void PlaceholderTypeSpecifierAST::accept0(ASTVisitor *visitor) +{ + if (visitor->visit(this)) + accept(typeConstraint, visitor); + visitor->endVisit(this); +} + void DeclaratorAST::accept0(ASTVisitor *visitor) { if (visitor->visit(this)) { @@ -946,6 +963,23 @@ void TemplateDeclarationAST::accept0(ASTVisitor *visitor) visitor->endVisit(this); } +void ConceptDeclarationAST::accept0(ASTVisitor *visitor) +{ + if (visitor->visit(this)) { + accept(name, visitor); + accept(attributes, visitor); + accept(constraint, visitor); + } + visitor->endVisit(this); +} + +void RequiresExpressionAST::accept0(ASTVisitor *visitor) +{ + if (visitor->visit(this)) + accept(parameters, visitor); + visitor->endVisit(this); +} + void ThrowExpressionAST::accept0(ASTVisitor *visitor) { if (visitor->visit(this)) { diff --git a/src/libs/3rdparty/cplusplus/ASTVisitor.h b/src/libs/3rdparty/cplusplus/ASTVisitor.h index 691b57e9ac..455936f984 100644 --- a/src/libs/3rdparty/cplusplus/ASTVisitor.h +++ b/src/libs/3rdparty/cplusplus/ASTVisitor.h @@ -96,6 +96,7 @@ public: virtual bool visit(CompoundExpressionAST *) { return true; } virtual bool visit(CompoundLiteralAST *) { return true; } virtual bool visit(CompoundStatementAST *) { return true; } + virtual bool visit(ConceptDeclarationAST *) { return true; } virtual bool visit(ConditionAST *) { return true; } virtual bool visit(ConditionalExpressionAST *) { return true; } virtual bool visit(ContinueStatementAST *) { return true; } @@ -181,6 +182,7 @@ public: virtual bool visit(OperatorFunctionIdAST *) { return true; } virtual bool visit(ParameterDeclarationAST *) { return true; } virtual bool visit(ParameterDeclarationClauseAST *) { return true; } + virtual bool visit(PlaceholderTypeSpecifierAST *) { return true; } virtual bool visit(PointerAST *) { return true; } virtual bool visit(PointerLiteralAST *) { return true; } virtual bool visit(PointerToMemberAST *) { return true; } @@ -198,6 +200,7 @@ public: virtual bool visit(QualifiedNameAST *) { return true; } virtual bool visit(RangeBasedForStatementAST *) { return true; } virtual bool visit(ReferenceAST *) { return true; } + virtual bool visit(RequiresExpressionAST *) { return true; } virtual bool visit(ReturnStatementAST *) { return true; } virtual bool visit(SimpleDeclarationAST *) { return true; } virtual bool visit(SimpleNameAST *) { return true; } @@ -215,6 +218,7 @@ public: virtual bool visit(TrailingReturnTypeAST *) { return true; } virtual bool visit(TranslationUnitAST *) { return true; } virtual bool visit(TryBlockStatementAST *) { return true; } + virtual bool visit(TypeConstraintAST *) { return true; } virtual bool visit(TypeConstructorCallAST *) { return true; } virtual bool visit(TypeIdAST *) { return true; } virtual bool visit(TypeidExpressionAST *) { return true; } @@ -250,6 +254,7 @@ public: virtual void endVisit(CompoundExpressionAST *) {} virtual void endVisit(CompoundLiteralAST *) {} virtual void endVisit(CompoundStatementAST *) {} + virtual void endVisit(ConceptDeclarationAST *) {} virtual void endVisit(ConditionAST *) {} virtual void endVisit(ConditionalExpressionAST *) {} virtual void endVisit(ContinueStatementAST *) {} @@ -335,6 +340,7 @@ public: virtual void endVisit(OperatorFunctionIdAST *) {} virtual void endVisit(ParameterDeclarationAST *) {} virtual void endVisit(ParameterDeclarationClauseAST *) {} + virtual void endVisit(PlaceholderTypeSpecifierAST *) {} virtual void endVisit(PointerAST *) {} virtual void endVisit(PointerLiteralAST *) {} virtual void endVisit(PointerToMemberAST *) {} @@ -352,6 +358,7 @@ public: virtual void endVisit(QualifiedNameAST *) {} virtual void endVisit(RangeBasedForStatementAST *) {} virtual void endVisit(ReferenceAST *) {} + virtual void endVisit(RequiresExpressionAST *) {} virtual void endVisit(ReturnStatementAST *) {} virtual void endVisit(SimpleDeclarationAST *) {} virtual void endVisit(SimpleNameAST *) {} @@ -369,6 +376,7 @@ public: virtual void endVisit(TrailingReturnTypeAST *) {} virtual void endVisit(TranslationUnitAST *) {} virtual void endVisit(TryBlockStatementAST *) {} + virtual void endVisit(TypeConstraintAST *) {} virtual void endVisit(TypeConstructorCallAST *) {} virtual void endVisit(TypeIdAST *) {} virtual void endVisit(TypeidExpressionAST *) {} diff --git a/src/libs/3rdparty/cplusplus/ASTfwd.h b/src/libs/3rdparty/cplusplus/ASTfwd.h index 4b31aaf590..f0899b2e1c 100644 --- a/src/libs/3rdparty/cplusplus/ASTfwd.h +++ b/src/libs/3rdparty/cplusplus/ASTfwd.h @@ -146,6 +146,7 @@ class OperatorAST; class OperatorFunctionIdAST; class ParameterDeclarationAST; class ParameterDeclarationClauseAST; +class PlaceholderTypeSpecifierAST; class PointerAST; class PointerLiteralAST; class PointerToMemberAST; @@ -166,6 +167,7 @@ class QtPropertyDeclarationItemAST; class QualifiedNameAST; class RangeBasedForStatementAST; class ReferenceAST; +class RequiresExpressionAST; class ReturnStatementAST; class SimpleDeclarationAST; class SimpleNameAST; @@ -178,6 +180,7 @@ class StdAttributeSpecifierAST; class StringLiteralAST; class SwitchStatementAST; class TemplateDeclarationAST; +class ConceptDeclarationAST; class TemplateIdAST; class TemplateTypeParameterAST; class ThisExpressionAST; @@ -185,6 +188,7 @@ class ThrowExpressionAST; class TrailingReturnTypeAST; class TranslationUnitAST; class TryBlockStatementAST; +class TypeConstraintAST; class TypeConstructorCallAST; class TypeIdAST; class TypeidExpressionAST; diff --git a/src/libs/3rdparty/cplusplus/Bind.cpp b/src/libs/3rdparty/cplusplus/Bind.cpp index 7f10c79428..6293d732e0 100644 --- a/src/libs/3rdparty/cplusplus/Bind.cpp +++ b/src/libs/3rdparty/cplusplus/Bind.cpp @@ -933,6 +933,11 @@ bool Bind::visit(ParameterDeclarationClauseAST *ast) return false; } +bool Bind::visit(RequiresExpressionAST *) +{ + return false; +} + void Bind::parameterDeclarationClause(ParameterDeclarationClauseAST *ast, int lparen_token, Function *fun) { if (! ast) diff --git a/src/libs/3rdparty/cplusplus/Bind.h b/src/libs/3rdparty/cplusplus/Bind.h index 3947e57026..4d12beee86 100644 --- a/src/libs/3rdparty/cplusplus/Bind.h +++ b/src/libs/3rdparty/cplusplus/Bind.h @@ -128,6 +128,7 @@ protected: bool visit(NewTypeIdAST *ast) override; bool visit(OperatorAST *ast) override; bool visit(ParameterDeclarationClauseAST *ast) override; + bool visit(RequiresExpressionAST *ast) override; bool visit(TranslationUnitAST *ast) override; bool visit(ObjCProtocolRefsAST *ast) override; bool visit(ObjCMessageArgumentAST *ast) override; diff --git a/src/libs/3rdparty/cplusplus/Parser.cpp b/src/libs/3rdparty/cplusplus/Parser.cpp index cb7bfa3a1b..ed023b15f0 100644 --- a/src/libs/3rdparty/cplusplus/Parser.cpp +++ b/src/libs/3rdparty/cplusplus/Parser.cpp @@ -1255,6 +1255,8 @@ bool Parser::parseTemplateDeclaration(DeclarationAST *&node) ast->declaration = nullptr; if (parseDeclaration(ast->declaration)) break; + if (parseConceptDeclaration(ast->declaration)) + break; error(start_declaration, "expected a declaration"); rewind(start_declaration + 1); @@ -1265,6 +1267,173 @@ bool Parser::parseTemplateDeclaration(DeclarationAST *&node) return true; } +bool Parser::parseConceptDeclaration(DeclarationAST *&node) +{ + if (!_languageFeatures.cxx20Enabled) + return false; + if (LA() != T_CONCEPT) + return false; + + const auto ast = new (_pool) ConceptDeclarationAST; + ast->concept_token = consumeToken(); + if (!parseName(ast->name)) + return false; + parseAttributeSpecifier(ast->attributes); + if (LA() != T_EQUAL) + return false; + ast->equals_token = consumeToken(); + if (!parseLogicalOrExpression(ast->constraint)) + return false; + if (LA() != T_SEMICOLON) + return false; + ast->semicolon_token = consumeToken(); + node = ast; + return true; +} + +bool Parser::parsePlaceholderTypeSpecifier(PlaceholderTypeSpecifierAST *&node) +{ + if ((lookAtBuiltinTypeSpecifier() || _translationUnit->tokenAt(_tokenIndex).isKeyword()) + && (LA() != T_AUTO && LA() != T_DECLTYPE)) { + return false; + } + + TypeConstraintAST *typeConstraint = nullptr; + const int savedCursor = cursor(); + parseTypeConstraint(typeConstraint); + if (LA() != T_AUTO && (LA() != T_DECLTYPE || LA(1) != T_LPAREN || LA(2) != T_AUTO)) { + rewind(savedCursor); + return false; + } + const auto spec = new (_pool) PlaceholderTypeSpecifierAST; + spec->typeConstraint = typeConstraint; + if (LA() == T_DECLTYPE) { + spec->declTypetoken = consumeToken(); + if (LA() != T_LPAREN) + return false; + spec->lparenToken = consumeToken(); + if (LA() != T_AUTO) + return false; + spec->autoToken = consumeToken(); + if (LA() != T_RPAREN) + return false; + spec->rparenToken = consumeToken(); + } else { + spec->autoToken = consumeToken(); + } + node = spec; + return true; +} + +bool Parser::parseTypeConstraint(TypeConstraintAST *&node) +{ + NestedNameSpecifierListAST *nestedName = nullptr; + parseNestedNameSpecifierOpt(nestedName, true); + NameAST *conceptName = nullptr; + if (!parseUnqualifiedName(conceptName, true)) + return false; + const auto typeConstraint = new (_pool) TypeConstraintAST; + typeConstraint->nestedName = nestedName; + typeConstraint->conceptName = conceptName; + if (LA() != T_LESS) + return true; + typeConstraint->lessToken = consumeToken(); + if (LA() != T_GREATER) { + if (!parseTemplateArgumentList(typeConstraint->templateArgs)) + return false; + } + if (LA() != T_GREATER) + return false; + typeConstraint->greaterToken = consumeToken(); + node = typeConstraint; + return true; +} + +bool Parser::parseRequirement() +{ + if (LA() == T_TYPENAME) { // type-requirement + consumeToken(); + NameAST *name = nullptr; + if (!parseName(name, true)) + return false; + if (LA() != T_SEMICOLON) + return false; + consumeToken(); + return true; + } + if (LA() == T_LBRACE) { // compound-requirement + consumeToken(); + ExpressionAST *expr = nullptr; + if (!parseExpression(expr)) + return false; + if (LA() != T_RBRACE) + return false; + consumeToken(); + if (LA() == T_NOEXCEPT) + consumeToken(); + if (LA() == T_SEMICOLON) { + consumeToken(); + return true; + } + TypeConstraintAST *typeConstraint = nullptr; + if (!parseTypeConstraint(typeConstraint)) + return false; + if (LA() != T_SEMICOLON) + return false; + consumeToken(); + return true; + } + if (LA() == T_REQUIRES) { // nested-requirement + consumeToken(); + ExpressionAST *constraintExpr = nullptr; + if (!parseLogicalOrExpression(constraintExpr)) + return false; + if (LA() != T_SEMICOLON) + return false; + consumeToken(); + return true; + } + ExpressionAST *simpleExpr; + if (!parseExpression(simpleExpr)) // simple-requirement + return false; + if (LA() != T_SEMICOLON) + return false; + consumeToken(); + return true; +} + +bool Parser::parseRequiresExpression(ExpressionAST *&node) +{ + if (!_languageFeatures.cxx20Enabled) + return false; + if (LA() != T_REQUIRES) + return false; + + const auto ast = new (_pool) RequiresExpressionAST; + ast->requires_token = consumeToken(); + if (LA() == T_LPAREN) { + ast->lparen_token = consumeToken(); + if (!parseParameterDeclarationClause(ast->parameters)) + return false; + if (LA() != T_RPAREN) + return false; + ast->rparen_token = consumeToken(); + } + if (LA() != T_LBRACE) + return false; + ast->lbrace_token = consumeToken(); + if (!parseRequirement()) + return false; + while (LA() != T_RBRACE) { + if (!parseRequirement()) + return false; + } + ast->rbrace_token = consumeToken(); + + node = ast; + return true; +} + bool Parser::parseOperator(OperatorAST *&node) // ### FIXME { DEBUG_THIS_RULE(); @@ -1500,6 +1669,14 @@ bool Parser::parseDeclSpecifierSeq(SpecifierListAST *&decl_specifier_seq, NameAST *named_type_specifier = nullptr; SpecifierListAST **decl_specifier_seq_ptr = &decl_specifier_seq; for (;;) { + PlaceholderTypeSpecifierAST *placeholderSpec = nullptr; + // A simple auto is also technically a placeholder-type-specifier, but for historical + // reasons, it is handled further below. + if (LA() != T_AUTO && parsePlaceholderTypeSpecifier(placeholderSpec)) { + *decl_specifier_seq_ptr = new (_pool) SpecifierListAST(placeholderSpec); + decl_specifier_seq_ptr = &(*decl_specifier_seq_ptr)->next; + continue; + } if (! noStorageSpecifiers && ! onlySimpleTypeSpecifiers && lookAtStorageClassSpecifier()) { // storage-class-specifier SimpleSpecifierAST *spec = new (_pool) SimpleSpecifierAST; @@ -1550,8 +1727,9 @@ bool Parser::parseDeclSpecifierSeq(SpecifierListAST *&decl_specifier_seq, } decl_specifier_seq_ptr = &(*decl_specifier_seq_ptr)->next; has_type_specifier = true; - } else + } else { break; + } } return decl_specifier_seq != nullptr; @@ -1694,6 +1872,8 @@ bool Parser::hasAuto(SpecifierListAST *decl_specifier_list) const if (_translationUnit->tokenKind(simpleSpec->specifier_token) == T_AUTO) return true; } + if (spec->asPlaceholderTypeSpecifier()) + return true; } return false; } @@ -4731,6 +4911,9 @@ bool Parser::parsePrimaryExpression(ExpressionAST *&node) case T_AT_SELECTOR: return parseObjCExpression(node); + case T_REQUIRES: + return parseRequiresExpression(node); + default: { NameAST *name = nullptr; if (parseNameId(name)) { diff --git a/src/libs/3rdparty/cplusplus/Parser.h b/src/libs/3rdparty/cplusplus/Parser.h index ba101ccd46..dbbb563097 100644 --- a/src/libs/3rdparty/cplusplus/Parser.h +++ b/src/libs/3rdparty/cplusplus/Parser.h @@ -143,6 +143,11 @@ public: bool parseTemplateArgument(ExpressionAST *&node); bool parseTemplateArgumentList(ExpressionListAST *&node); bool parseTemplateDeclaration(DeclarationAST *&node); + bool parseConceptDeclaration(DeclarationAST *&node); + bool parsePlaceholderTypeSpecifier(PlaceholderTypeSpecifierAST *&node); + bool parseTypeConstraint(TypeConstraintAST *&node); + bool parseRequirement(); + bool parseRequiresExpression(ExpressionAST *&node); bool parseTemplateParameter(DeclarationAST *&node); bool parseTemplateParameterList(DeclarationListAST *&node); bool parseThrowExpression(ExpressionAST *&node); -- cgit v1.2.3 From 29f634a8caf69a374edb00df7a19146352a14d6f Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Mon, 20 Feb 2023 21:32:00 +0100 Subject: TaskTree: Introduce WaitFor, Condition and ConditionActivator WaitFor(condition) Group element enables postponing Group's children execution until the condition is met. Use ConditionActivator::activate() method to signal that the condition is met. A call to ConditionActivator::activate() schedules a request to the TaskTree instructing it to resume execution of awaiting condition. The Group containing the WaitFor element is started itself, and its setup handler is being called. If setup handler returned TaskAction::Continue, the children execution is being postponed. Otherwise, when StopWithDone or StopWithError is returned, the Group finishes and WaitFor element is no-op in this context. This functionality is going to be used when some part of the task tree may continue only after some data has been collected, and data collection took place not from inside start or done handlers. The example is running debugger for already started process - the debugger may run after the process already started, delivered its PID and it's still running. In this way we may start a debugger process in parallel in right point of time. This patch implements the 5th point inside QTCREATORBUG-28741. Task-number: QTCREATORBUG-28741 Change-Id: I4afaedb0b34fe0383c16a6d1f74bf07f74cc088a Reviewed-by: Christian Kandeler Reviewed-by: hjk Reviewed-by: --- src/libs/utils/tasktree.cpp | 434 ++++++++++++++++++++++++++++++++------------ src/libs/utils/tasktree.h | 63 ++++++- 2 files changed, 381 insertions(+), 116 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/tasktree.cpp b/src/libs/utils/tasktree.cpp index 90e4073e03..8ea5136932 100644 --- a/src/libs/utils/tasktree.cpp +++ b/src/libs/utils/tasktree.cpp @@ -74,6 +74,54 @@ void TreeStorageBase::activateStorage(int id) const m_storageData->m_activeStorage = id; } +Condition::Condition() + : m_conditionData(new ConditionData()) {} + +Condition::ConditionData::~ConditionData() +{ + QTC_CHECK(m_activatorHash.isEmpty()); + qDeleteAll(m_activatorHash); +} + +ConditionActivator *Condition::activator() const +{ + QTC_ASSERT(m_conditionData->m_activeActivator, return nullptr); + const auto it = m_conditionData->m_activatorHash.constFind(m_conditionData->m_activeActivator); + QTC_ASSERT(it != m_conditionData->m_activatorHash.constEnd(), return nullptr); + return it.value(); +} + +int Condition::createActivator(TaskNode *node) const +{ + QTC_ASSERT(m_conditionData->m_activeActivator == 0, return 0); // TODO: should be allowed? + const int newId = ++m_conditionData->m_activatorCounter; + m_conditionData->m_activatorHash.insert(newId, new ConditionActivator(node)); + return newId; +} + +void Condition::deleteActivator(int id) const +{ + QTC_ASSERT(m_conditionData->m_activeActivator == 0, return); // TODO: should be allowed? + const auto it = m_conditionData->m_activatorHash.constFind(id); + QTC_ASSERT(it != m_conditionData->m_activatorHash.constEnd(), return); + delete it.value(); + m_conditionData->m_activatorHash.erase(it); +} + +// passing 0 deactivates currently active condition +void Condition::activateActivator(int id) const +{ + if (id == 0) { + QTC_ASSERT(m_conditionData->m_activeActivator, return); + m_conditionData->m_activeActivator = 0; + return; + } + QTC_ASSERT(m_conditionData->m_activeActivator == 0, return); + const auto it = m_conditionData->m_activatorHash.find(id); + QTC_ASSERT(it != m_conditionData->m_activatorHash.end(), return); + m_conditionData->m_activeActivator = id; +} + ParallelLimit sequential(1); ParallelLimit parallel(0); Workflow stopOnError(WorkflowPolicy::StopOnError); @@ -84,20 +132,21 @@ Workflow optional(WorkflowPolicy::Optional); void TaskItem::addChildren(const QList &children) { - QTC_ASSERT(m_type == Type::Group, qWarning("Only Task may have children, skipping..."); return); + QTC_ASSERT(m_type == Type::Group, qWarning("Only Group may have children, skipping..."); + return); for (const TaskItem &child : children) { switch (child.m_type) { case Type::Group: m_children.append(child); break; case Type::Limit: - QTC_ASSERT(m_type == Type::Group, - qWarning("Mode may only be a child of Group, skipping..."); return); + QTC_ASSERT(m_type == Type::Group, qWarning("Execution Mode may only be a child of a " + "Group, skipping..."); return); m_parallelLimit = child.m_parallelLimit; // TODO: Assert on redefinition? break; case Type::Policy: - QTC_ASSERT(m_type == Type::Group, - qWarning("Workflow Policy may only be a child of Group, skipping..."); return); + QTC_ASSERT(m_type == Type::Group, qWarning("Workflow Policy may only be a child of a " + "Group, skipping..."); return); m_workflowPolicy = child.m_workflowPolicy; // TODO: Assert on redefinition? break; case Type::TaskHandler: @@ -109,7 +158,7 @@ void TaskItem::addChildren(const QList &children) break; case Type::GroupHandler: QTC_ASSERT(m_type == Type::Group, qWarning("Group Handler may only be a " - "child of Group, skipping..."); break); + "child of a Group, skipping..."); break); QTC_ASSERT(!child.m_groupHandler.m_setupHandler || !m_groupHandler.m_setupHandler, qWarning("Group Setup Handler redefinition, overriding...")); @@ -126,6 +175,12 @@ void TaskItem::addChildren(const QList &children) if (child.m_groupHandler.m_errorHandler) m_groupHandler.m_errorHandler = child.m_groupHandler.m_errorHandler; break; + case Type::Condition: + QTC_ASSERT(m_type == Type::Group, qWarning("WaitFor may only be a child of a Group, " + "skipping..."); break); + QTC_ASSERT(!m_condition, qWarning("WaitFor redefinition, overriding...")); + m_condition = child.m_condition; + break; case Type::Storage: m_storageList.append(child.m_storageList); break; @@ -140,26 +195,82 @@ using namespace Tasking; class TaskTreePrivate; class TaskNode; +class TaskTreePrivate +{ +public: + TaskTreePrivate(TaskTree *taskTree) + : q(taskTree) {} + + void start(); + void stop(); + void advanceProgress(int byValue); + void emitStartedAndProgress(); + void emitProgress(); + void emitDone(); + void emitError(); + bool addCondition(const TaskItem &task, TaskContainer *container); + void createConditionActivators(); + void deleteConditionActivators(); + void activateConditions(); + void deactivateConditions(); + QList addStorages(const QList &storages); + void callSetupHandler(TreeStorageBase storage, int storageId) { + callStorageHandler(storage, storageId, &StorageHandler::m_setupHandler); + } + void callDoneHandler(TreeStorageBase storage, int storageId) { + callStorageHandler(storage, storageId, &StorageHandler::m_doneHandler); + } + struct StorageHandler { + TaskTree::StorageVoidHandler m_setupHandler = {}; + TaskTree::StorageVoidHandler m_doneHandler = {}; + }; + typedef TaskTree::StorageVoidHandler StorageHandler::*HandlerPtr; // ptr to class member + void callStorageHandler(TreeStorageBase storage, int storageId, HandlerPtr ptr) + { + const auto it = m_storageHandlers.constFind(storage); + if (it == m_storageHandlers.constEnd()) + return; + GuardLocker locker(m_guard); + const StorageHandler storageHandler = *it; + storage.activateStorage(storageId); + if (storageHandler.*ptr) + (storageHandler.*ptr)(storage.activeStorageVoid()); + storage.activateStorage(0); + } + + TaskTree *q = nullptr; + Guard m_guard; + int m_progressValue = 0; + QHash m_conditions; + QSet m_storages; + QHash m_storageHandlers; + std::unique_ptr m_root = nullptr; // Keep me last in order to destruct first +}; + class TaskContainer { public: TaskContainer(TaskTreePrivate *taskTreePrivate, const TaskItem &task, - TaskContainer *parentContainer) - : m_constData(taskTreePrivate, task, parentContainer, this) {} + TaskNode *parentNode, TaskContainer *parentContainer) + : m_constData(taskTreePrivate, task, parentNode, parentContainer, this) + , m_conditionData(taskTreePrivate->addCondition(task, this) + ? ConditionData() : std::optional()) {} TaskAction start(); TaskAction continueStart(TaskAction startAction, int nextChild); TaskAction startChildren(int nextChild); TaskAction childDone(bool success); + void activateCondition(); void stop(); void invokeEndHandler(); bool isRunning() const { return m_runtimeData.has_value(); } bool isStarting() const { return isRunning() && m_runtimeData->m_startGuard.isLocked(); } struct ConstData { - ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task, + ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task, TaskNode *parentNode, TaskContainer *parentContainer, TaskContainer *thisContainer); ~ConstData() { qDeleteAll(m_children); } TaskTreePrivate * const m_taskTreePrivate = nullptr; + TaskNode * const m_parentNode = nullptr; TaskContainer * const m_parentContainer = nullptr; const int m_parallelLimit = 1; @@ -170,6 +281,11 @@ public: const int m_taskCount = 0; }; + struct ConditionData { + bool m_activated = false; + int m_conditionId = 0; + }; + struct RuntimeData { RuntimeData(const ConstData &constData); ~RuntimeData(); @@ -187,6 +303,7 @@ public: }; const ConstData m_constData; + std::optional m_conditionData; std::optional m_runtimeData; }; @@ -196,7 +313,7 @@ public: TaskNode(TaskTreePrivate *taskTreePrivate, const TaskItem &task, TaskContainer *parentContainer) : m_taskHandler(task.taskHandler()) - , m_container(taskTreePrivate, task, parentContainer) + , m_container(taskTreePrivate, task, this, parentContainer) {} // If returned value != Continue, childDone() needs to be called in parent container (in caller) @@ -208,6 +325,7 @@ public: bool isTask() const { return m_taskHandler.m_createHandler && m_taskHandler.m_setupHandler; } int taskCount() const { return isTask() ? 1 : m_container.m_constData.m_taskCount; } TaskContainer *parentContainer() const { return m_container.m_constData.m_parentContainer; } + void activateCondition(); private: const TaskItem::TaskHandler m_taskHandler; @@ -215,129 +333,160 @@ private: std::unique_ptr m_task; }; -class TaskTreePrivate +void TaskTreePrivate::start() { -public: - TaskTreePrivate(TaskTree *taskTree) - : q(taskTree) {} - - void start() { - QTC_ASSERT(m_root, return); - m_progressValue = 0; - emitStartedAndProgress(); - // TODO: check storage handlers for not existing storages in tree - for (auto it = m_storageHandlers.cbegin(); it != m_storageHandlers.cend(); ++it) { - QTC_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't " - "exist in task tree. Its handlers will never be called.")); - } - m_root->start(); - } - void stop() { - QTC_ASSERT(m_root, return); - if (!m_root->isRunning()) - return; - // TODO: should we have canceled flag (passed to handler)? - // Just one done handler with result flag: - // FinishedWithSuccess, FinishedWithError, Canceled, TimedOut. - // Canceled either directly by user, or by workflow policy - doesn't matter, in both - // cases canceled from outside. - m_root->stop(); - emitError(); - } - void advanceProgress(int byValue) { - if (byValue == 0) - return; - QTC_CHECK(byValue > 0); - QTC_CHECK(m_progressValue + byValue <= m_root->taskCount()); - m_progressValue += byValue; - emitProgress(); - } - void emitStartedAndProgress() { - GuardLocker locker(m_guard); - emit q->started(); - emit q->progressValueChanged(m_progressValue); - } - void emitProgress() { - GuardLocker locker(m_guard); - emit q->progressValueChanged(m_progressValue); + QTC_ASSERT(m_root, return); + m_progressValue = 0; + emitStartedAndProgress(); + // TODO: check storage handlers for not existing storages in tree + for (auto it = m_storageHandlers.cbegin(); it != m_storageHandlers.cend(); ++it) { + QTC_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't " + "exist in task tree. Its handlers will never be called.")); } - void emitDone() { - QTC_CHECK(m_progressValue == m_root->taskCount()); - GuardLocker locker(m_guard); - emit q->done(); - } - void emitError() { - QTC_CHECK(m_progressValue == m_root->taskCount()); - GuardLocker locker(m_guard); - emit q->errorOccurred(); - } - QList addStorages(const QList &storages) { - QList addedStorages; - for (const TreeStorageBase &storage : storages) { - QTC_ASSERT(!m_storages.contains(storage), qWarning("Can't add the same storage into " - "one TaskTree twice, skipping..."); continue); - addedStorages << storage; - m_storages << storage; - } - return addedStorages; - } - void callSetupHandler(TreeStorageBase storage, int storageId) { - callStorageHandler(storage, storageId, &StorageHandler::m_setupHandler); + createConditionActivators(); + m_root->start(); +} + +void TaskTreePrivate::stop() +{ + QTC_ASSERT(m_root, return); + if (!m_root->isRunning()) + return; + // TODO: should we have canceled flag (passed to handler)? + // Just one done handler with result flag: + // FinishedWithSuccess, FinishedWithError, Canceled, TimedOut. + // Canceled either directly by user, or by workflow policy - doesn't matter, in both + // cases canceled from outside. + m_root->stop(); + emitError(); +} + +void TaskTreePrivate::advanceProgress(int byValue) +{ + if (byValue == 0) + return; + QTC_CHECK(byValue > 0); + QTC_CHECK(m_progressValue + byValue <= m_root->taskCount()); + m_progressValue += byValue; + emitProgress(); +} + +void TaskTreePrivate::emitStartedAndProgress() +{ + GuardLocker locker(m_guard); + emit q->started(); + emit q->progressValueChanged(m_progressValue); +} + +void TaskTreePrivate::emitProgress() +{ + GuardLocker locker(m_guard); + emit q->progressValueChanged(m_progressValue); +} + +void TaskTreePrivate::emitDone() +{ + deleteConditionActivators(); + QTC_CHECK(m_progressValue == m_root->taskCount()); + GuardLocker locker(m_guard); + emit q->done(); +} + +void TaskTreePrivate::emitError() +{ + deleteConditionActivators(); + QTC_CHECK(m_progressValue == m_root->taskCount()); + GuardLocker locker(m_guard); + emit q->errorOccurred(); +} + +bool TaskTreePrivate::addCondition(const TaskItem &task, TaskContainer *container) +{ + if (!task.condition()) + return false; + QTC_ASSERT(!m_conditions.contains(*task.condition()), qWarning("Can't add the same condition " + "into one TaskTree twice, skipping..."); return false); + m_conditions.insert(*task.condition(), container); + return true; +} + +void TaskTreePrivate::createConditionActivators() +{ + for (auto it = m_conditions.cbegin(); it != m_conditions.cend(); ++it) { + Condition condition = it.key(); + TaskContainer *container = it.value(); + container->m_conditionData->m_conditionId + = condition.createActivator(container->m_constData.m_parentNode); } - void callDoneHandler(TreeStorageBase storage, int storageId) { - callStorageHandler(storage, storageId, &StorageHandler::m_doneHandler); +} + +void TaskTreePrivate::deleteConditionActivators() +{ + for (auto it = m_conditions.cbegin(); it != m_conditions.cend(); ++it) { + Condition condition = it.key(); + TaskContainer *container = it.value(); + condition.deleteActivator(container->m_conditionData->m_conditionId); + container->m_conditionData = TaskContainer::ConditionData(); } - struct StorageHandler { - TaskTree::StorageVoidHandler m_setupHandler = {}; - TaskTree::StorageVoidHandler m_doneHandler = {}; - }; - typedef TaskTree::StorageVoidHandler StorageHandler::*HandlerPtr; // ptr to class member - void callStorageHandler(TreeStorageBase storage, int storageId, HandlerPtr ptr) - { - const auto it = m_storageHandlers.constFind(storage); - if (it == m_storageHandlers.constEnd()) - return; - GuardLocker locker(m_guard); - const StorageHandler storageHandler = *it; - storage.activateStorage(storageId); - if (storageHandler.*ptr) - (storageHandler.*ptr)(storage.activeStorageVoid()); - storage.activateStorage(0); +} + +void TaskTreePrivate::activateConditions() +{ + for (auto it = m_conditions.cbegin(); it != m_conditions.cend(); ++it) { + Condition condition = it.key(); + TaskContainer *container = it.value(); + condition.activateActivator(container->m_conditionData->m_conditionId); } +} - TaskTree *q = nullptr; - Guard m_guard; - int m_progressValue = 0; - QSet m_storages; - QHash m_storageHandlers; - std::unique_ptr m_root = nullptr; // Keep me last in order to destruct first -}; +void TaskTreePrivate::deactivateConditions() +{ + for (auto it = m_conditions.cbegin(); it != m_conditions.cend(); ++it) + it.key().activateActivator(0); +} -class StorageActivator +QList TaskTreePrivate::addStorages(const QList &storages) +{ + QList addedStorages; + for (const TreeStorageBase &storage : storages) { + QTC_ASSERT(!m_storages.contains(storage), qWarning("Can't add the same storage into " + "one TaskTree twice, skipping..."); continue); + addedStorages << storage; + m_storages << storage; + } + return addedStorages; +} + +// TODO: Activate/deactivate Conditions +class ExecutionContextActivator { public: - StorageActivator(TaskContainer *container) - : m_container(container) { activateStorages(m_container); } - ~StorageActivator() { deactivateStorages(m_container); } + ExecutionContextActivator(TaskContainer *container) + : m_container(container) { activateContext(m_container); } + ~ExecutionContextActivator() { deactivateContext(m_container); } private: - static void activateStorages(TaskContainer *container) + static void activateContext(TaskContainer *container) { QTC_ASSERT(container && container->isRunning(), return); const TaskContainer::ConstData &constData = container->m_constData; if (constData.m_parentContainer) - activateStorages(constData.m_parentContainer); + activateContext(constData.m_parentContainer); + else + constData.m_taskTreePrivate->activateConditions(); for (int i = 0; i < constData.m_storageList.size(); ++i) constData.m_storageList[i].activateStorage(container->m_runtimeData->m_storageIdList.value(i)); } - static void deactivateStorages(TaskContainer *container) + static void deactivateContext(TaskContainer *container) { QTC_ASSERT(container && container->isRunning(), return); const TaskContainer::ConstData &constData = container->m_constData; for (int i = constData.m_storageList.size() - 1; i >= 0; --i) // iterate in reverse order constData.m_storageList[i].activateStorage(0); if (constData.m_parentContainer) - deactivateStorages(constData.m_parentContainer); + deactivateContext(constData.m_parentContainer); + else + constData.m_taskTreePrivate->deactivateConditions(); } TaskContainer *m_container = nullptr; }; @@ -346,7 +495,7 @@ template > ReturnType invokeHandler(TaskContainer *container, Handler &&handler, Args &&...args) { - StorageActivator activator(container); + ExecutionContextActivator activator(container); GuardLocker locker(container->m_constData.m_taskTreePrivate->m_guard); return std::invoke(std::forward(handler), std::forward(args)...); } @@ -362,8 +511,10 @@ static QList createChildren(TaskTreePrivate *taskTreePrivate, TaskCo } TaskContainer::ConstData::ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task, - TaskContainer *parentContainer, TaskContainer *thisContainer) + TaskNode *parentNode, TaskContainer *parentContainer, + TaskContainer *thisContainer) : m_taskTreePrivate(taskTreePrivate) + , m_parentNode(parentNode) , m_parentContainer(parentContainer) , m_parallelLimit(task.parallelLimit()) , m_workflowPolicy(task.workflowPolicy()) @@ -440,8 +591,12 @@ TaskAction TaskContainer::start() if (startAction != TaskAction::Continue) m_constData.m_taskTreePrivate->advanceProgress(m_constData.m_taskCount); } - if (m_constData.m_children.isEmpty() && startAction == TaskAction::Continue) - startAction = TaskAction::StopWithDone; + if (startAction == TaskAction::Continue) { + if (m_conditionData && !m_conditionData->m_activated) // Group has condition and it wasn't activated yet + return TaskAction::Continue; + if (m_constData.m_children.isEmpty()) + startAction = TaskAction::StopWithDone; + } return continueStart(startAction, 0); } @@ -513,6 +668,35 @@ TaskAction TaskContainer::childDone(bool success) return continueStart(startAction, limit); } +void ConditionActivator::activate() +{ + m_node->activateCondition(); +} + +void TaskContainer::activateCondition() +{ + QTC_ASSERT(m_conditionData, return); + if (!m_constData.m_taskTreePrivate->m_root->isRunning()) + return; + + if (!isRunning()) + return; // Condition not run yet or group already skipped or stopped + + if (!m_conditionData->m_activated) + return; // May it happen that scheduled call is coming from previous TaskTree's start? + + if (m_runtimeData->m_doneCount != 0) + return; // In meantime the group was started + + for (TaskNode *child : m_constData.m_children) { + if (child->isRunning()) + return; // In meantime the group was started + } + const TaskAction startAction = m_constData.m_children.isEmpty() ? TaskAction::StopWithDone + : TaskAction::Continue; + continueStart(startAction, 0); +} + void TaskContainer::stop() { if (!isRunning()) @@ -597,6 +781,26 @@ void TaskNode::invokeEndHandler(bool success) m_container.m_constData.m_taskTreePrivate->advanceProgress(1); } +void TaskNode::activateCondition() +{ + QTC_ASSERT(m_container.m_conditionData, return); + QTC_ASSERT(m_container.m_constData.m_taskTreePrivate->m_root->isRunning(), return); + + if (m_container.m_conditionData->m_activated) + return; // Was already activated + + m_container.m_conditionData->m_activated = true; + if (!isRunning()) + return; // Condition not run yet or group already skipped or stopped + + QTC_CHECK(m_container.m_runtimeData->m_doneCount == 0); + for (TaskNode *child : m_container.m_constData.m_children) + QTC_CHECK(!child->isRunning()); + + QMetaObject::invokeMethod(this, [this] { m_container.activateCondition(); }, + Qt::QueuedConnection); +} + /*! \class Utils::TaskTree \inheaderfile utils/tasktree.h @@ -1324,6 +1528,9 @@ TaskTree::~TaskTree() { QTC_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting TaskTree instance directly from " "one of its handlers will lead to crash!")); + if (isRunning()) + d->deleteConditionActivators(); + // TODO: delete storages explicitly here? delete d; } @@ -1332,6 +1539,7 @@ void TaskTree::setupRoot(const Tasking::Group &root) QTC_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return); QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The setupRoot() is called from one of the" "TaskTree handlers, ingoring..."); return); + d->m_conditions.clear(); d->m_storages.clear(); d->m_root.reset(new TaskNode(d, root, nullptr)); } diff --git a/src/libs/utils/tasktree.h b/src/libs/utils/tasktree.h index 8a44b8a5b0..8e9c0a6c17 100644 --- a/src/libs/utils/tasktree.h +++ b/src/libs/utils/tasktree.h @@ -11,8 +11,9 @@ namespace Utils { -class StorageActivator; +class ExecutionContextActivator; class TaskContainer; +class TaskNode; class TaskTreePrivate; namespace Tasking { @@ -66,7 +67,7 @@ private: QSharedPointer m_storageData; friend TaskContainer; friend TaskTreePrivate; - friend StorageActivator; + friend ExecutionContextActivator; }; template @@ -87,6 +88,50 @@ private: } }; +class QTCREATOR_UTILS_EXPORT ConditionActivator +{ +public: + void activate(); + +private: + ConditionActivator(TaskNode *container) : m_node(container) {} + TaskNode *m_node = nullptr; + friend class Condition; +}; + +class QTCREATOR_UTILS_EXPORT Condition +{ +public: + Condition(); + ConditionActivator &operator*() const noexcept { return *activator(); } + ConditionActivator *operator->() const noexcept { return activator(); } + ConditionActivator *activator() const; + +private: + int createActivator(TaskNode *node) const; + void deleteActivator(int id) const; + void activateActivator(int id) const; + + friend bool operator==(const Condition &first, const Condition &second) + { return first.m_conditionData == second.m_conditionData; } + + friend bool operator!=(const Condition &first, const Condition &second) + { return first.m_conditionData != second.m_conditionData; } + + friend size_t qHash(const Condition &storage, uint seed = 0) + { return size_t(storage.m_conditionData.get()) ^ seed; } + + struct ConditionData { + ~ConditionData(); + QHash m_activatorHash = {}; + int m_activeActivator = 0; // 0 means no active activator + int m_activatorCounter = 0; + }; + QSharedPointer m_conditionData; + friend TaskTreePrivate; + friend ExecutionContextActivator; +}; + // WorkflowPolicy: // 1. When all children finished with done -> report done, otherwise: // a) Report error on first error and stop executing other children (including their subtree) @@ -143,11 +188,13 @@ public: TaskHandler taskHandler() const { return m_taskHandler; } GroupHandler groupHandler() const { return m_groupHandler; } QList children() const { return m_children; } + std::optional condition() const { return m_condition; } QList storageList() const { return m_storageList; } protected: enum class Type { Group, + Condition, Storage, Limit, Policy, @@ -168,6 +215,9 @@ protected: TaskItem(const GroupHandler &handler) : m_type(Type::GroupHandler) , m_groupHandler(handler) {} + TaskItem(const Condition &condition) + : m_type(Type::Condition) + , m_condition{condition} {} TaskItem(const TreeStorageBase &storage) : m_type(Type::Storage) , m_storageList{storage} {} @@ -179,6 +229,7 @@ private: WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError; TaskHandler m_taskHandler; GroupHandler m_groupHandler; + std::optional m_condition; QList m_storageList; QList m_children; }; @@ -196,6 +247,12 @@ public: Storage(const TreeStorageBase &storage) : TaskItem(storage) { } }; +class QTCREATOR_UTILS_EXPORT WaitFor : public TaskItem +{ +public: + WaitFor(const Condition &condition) : TaskItem(condition) { } +}; + class QTCREATOR_UTILS_EXPORT ParallelLimit : public TaskItem { public: @@ -321,7 +378,7 @@ private: class TaskTreePrivate; -class QTCREATOR_UTILS_EXPORT TaskTree : public QObject +class QTCREATOR_UTILS_EXPORT TaskTree final : public QObject { Q_OBJECT -- cgit v1.2.3 From b6f2ff8705f5efa4244d5d4ad13946887faf7d0a Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Tue, 28 Feb 2023 11:31:24 +0100 Subject: Utils: Fix handling of "/somedir/.." in FSEngine The change 249d613a60aa6a1347ebc1c29902049247b93324 in qtbase makes it so that QFileInfo::fileName() actually calls the underlying FSEngine now. Paths which resolve to "Root" like "/somedir/.." would return "" as their filename since they were immediately cleaned and therefore converted to "/" which does not have a filename. The same issue did exists for paths such as "/__qtc_devices__/ssh/.." and "/__qtc_devices__/ssh/devicename/.." This patch makes it so that the incoming filename is kept "unclean" until it is actually used. Change-Id: Id5b7d1105e2d59d776aa1df5bbf6273a9fcb5d27 Reviewed-by: hjk --- src/libs/utils/fsengine/fileiteratordevicesappender.h | 5 ++++- src/libs/utils/fsengine/fixedlistfsengine.h | 2 ++ src/libs/utils/fsengine/fsengine_impl.cpp | 2 ++ src/libs/utils/fsengine/fsenginehandler.cpp | 11 +++++++---- 4 files changed, 15 insertions(+), 5 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/fsengine/fileiteratordevicesappender.h b/src/libs/utils/fsengine/fileiteratordevicesappender.h index c08c6cb3f7..9aec917d6b 100644 --- a/src/libs/utils/fsengine/fileiteratordevicesappender.h +++ b/src/libs/utils/fsengine/fileiteratordevicesappender.h @@ -93,7 +93,10 @@ private: void setPath() const { if (!m_hasSetPath) { - const QString p = path(); + // path() can be "/somedir/.." so we need to clean it first. + // We only need QDir::cleanPath here, as the path is always + // a fs engine path and will not contain scheme:// etc. + const QString p = QDir::cleanPath(path()); if (p.compare(QDir::rootPath(), Qt::CaseInsensitive) == 0) m_status = State::IteratingRoot; diff --git a/src/libs/utils/fsengine/fixedlistfsengine.h b/src/libs/utils/fsengine/fixedlistfsengine.h index 991bc08b1b..3518b5d0b5 100644 --- a/src/libs/utils/fsengine/fixedlistfsengine.h +++ b/src/libs/utils/fsengine/fixedlistfsengine.h @@ -43,6 +43,8 @@ public: return chopIfEndsWith(m_filePath.toString(), '/'); break; case QAbstractFileEngine::BaseName: + if (m_filePath.fileName().isEmpty()) + return m_filePath.host().toString(); return m_filePath.fileName(); break; case QAbstractFileEngine::PathName: diff --git a/src/libs/utils/fsengine/fsengine_impl.cpp b/src/libs/utils/fsengine/fsengine_impl.cpp index 6654e741ac..ade8236d5e 100644 --- a/src/libs/utils/fsengine/fsengine_impl.cpp +++ b/src/libs/utils/fsengine/fsengine_impl.cpp @@ -238,6 +238,8 @@ QString FSEngineImpl::fileName(FileName file) const return m_filePath.toFSPathString(); break; case QAbstractFileEngine::BaseName: + if (m_filePath.fileName().isEmpty()) + return m_filePath.host().toString(); return m_filePath.fileName(); break; case QAbstractFileEngine::PathName: diff --git a/src/libs/utils/fsengine/fsenginehandler.cpp b/src/libs/utils/fsengine/fsenginehandler.cpp index a713315ae3..81e063971b 100644 --- a/src/libs/utils/fsengine/fsenginehandler.cpp +++ b/src/libs/utils/fsengine/fsenginehandler.cpp @@ -29,7 +29,8 @@ QAbstractFileEngine *FSEngineHandler::create(const QString &fileName) const return rootFilePath.pathAppended(scheme); }); - return new FixedListFSEngine(rootFilePath, paths); + // We need to use fromString() here so the path is not cleaned + return new FixedListFSEngine(FilePath::fromString(fileName), paths); } if (fixedFileName.startsWith(rootPath)) { @@ -41,17 +42,19 @@ QAbstractFileEngine *FSEngineHandler::create(const QString &fileName) const return root.scheme() == scheme; }); - return new FixedListFSEngine(rootFilePath.pathAppended(scheme), filteredRoots); + // We need to use fromString() here so the path is not cleaned + return new FixedListFSEngine(FilePath::fromString(fileName), filteredRoots); } } - FilePath filePath = FilePath::fromString(fixedFileName); + // We need to use fromString() here so the path is not cleaned + FilePath filePath = FilePath::fromString(fileName); if (filePath.needsDevice()) return new FSEngineImpl(filePath); } if (fixedFileName.compare(QDir::rootPath(), Qt::CaseInsensitive) == 0) - return new RootInjectFSEngine(fixedFileName); + return new RootInjectFSEngine(fileName); return nullptr; } -- cgit v1.2.3 From d14834ad45e0c8bee0ff960606f1764bf1ad3588 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Tue, 28 Feb 2023 13:49:08 +0100 Subject: Utils: Fix handling of multi slash in fsengine Change-Id: Iaf525423f5ea0933b202f23042173c51edb3d4b0 Reviewed-by: hjk --- src/libs/utils/fsengine/fsenginehandler.cpp | 34 ++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 8 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/fsengine/fsenginehandler.cpp b/src/libs/utils/fsengine/fsenginehandler.cpp index 81e063971b..c5fa71e786 100644 --- a/src/libs/utils/fsengine/fsenginehandler.cpp +++ b/src/libs/utils/fsengine/fsenginehandler.cpp @@ -13,6 +13,26 @@ namespace Utils::Internal { +static FilePath removeDoubleSlash(const QString &fileName) +{ + // Reduce every two or more slashes to a single slash. + QString result; + const QChar slash = QChar('/'); + bool lastWasSlash = false; + for (const QChar &ch : fileName) { + if (ch == slash) { + if (!lastWasSlash) + result.append(ch); + lastWasSlash = true; + } else { + result.append(ch); + lastWasSlash = false; + } + } + // We use fromString() here to not normalize / clean the path anymore. + return FilePath::fromString(result); +} + QAbstractFileEngine *FSEngineHandler::create(const QString &fileName) const { if (fileName.startsWith(':')) @@ -29,8 +49,7 @@ QAbstractFileEngine *FSEngineHandler::create(const QString &fileName) const return rootFilePath.pathAppended(scheme); }); - // We need to use fromString() here so the path is not cleaned - return new FixedListFSEngine(FilePath::fromString(fileName), paths); + return new FixedListFSEngine(removeDoubleSlash(fileName), paths); } if (fixedFileName.startsWith(rootPath)) { @@ -42,15 +61,14 @@ QAbstractFileEngine *FSEngineHandler::create(const QString &fileName) const return root.scheme() == scheme; }); - // We need to use fromString() here so the path is not cleaned - return new FixedListFSEngine(FilePath::fromString(fileName), filteredRoots); + return new FixedListFSEngine(removeDoubleSlash(fileName), filteredRoots); } } - // We need to use fromString() here so the path is not cleaned - FilePath filePath = FilePath::fromString(fileName); - if (filePath.needsDevice()) - return new FSEngineImpl(filePath); + FilePath fixedPath = FilePath::fromString(fixedFileName); + + if (fixedPath.needsDevice()) + return new FSEngineImpl(removeDoubleSlash(fileName)); } if (fixedFileName.compare(QDir::rootPath(), Qt::CaseInsensitive) == 0) -- cgit v1.2.3 From 3287e14dd11fce2c41013351841fbf719b7323ad Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Thu, 2 Mar 2023 08:11:30 +0100 Subject: Utils: Add QTC_USE_WINPTY environment variable Change-Id: I769bfe8bd92529f672694da38d04914e0a51ed1b Reviewed-by: Cristian Adam Reviewed-by: --- src/libs/3rdparty/libptyqt/ptyqt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/3rdparty/libptyqt/ptyqt.cpp b/src/libs/3rdparty/libptyqt/ptyqt.cpp index 3883395e22..b3e7aa1b16 100644 --- a/src/libs/3rdparty/libptyqt/ptyqt.cpp +++ b/src/libs/3rdparty/libptyqt/ptyqt.cpp @@ -34,7 +34,7 @@ IPtyProcess *PtyQt::createPtyProcess(IPtyProcess::PtyType ptyType) } #ifdef Q_OS_WIN - if (ConPtyProcess().isAvailable()) + if (ConPtyProcess().isAvailable() && qgetenv("QTC_USE_WINPTY").isEmpty()) return new ConPtyProcess(); else return new WinPtyProcess(); -- cgit v1.2.3 From 007c47a2d425eab45925eb4d642f35b20360a508 Mon Sep 17 00:00:00 2001 From: Cristian Adam Date: Wed, 22 Feb 2023 15:12:48 +0100 Subject: Terminal: Use QWinEventNotifier for shell process termination Change-Id: I59ddcaa76714a0b15987b9e0912f4701a2951648 Reviewed-by: Cristian Adam --- src/libs/3rdparty/libptyqt/conptyprocess.cpp | 35 ++++++++++++++------------- src/libs/3rdparty/libptyqt/conptyprocess.h | 4 +++- src/libs/3rdparty/libptyqt/winptyprocess.cpp | 36 +++++++++++++++------------- src/libs/3rdparty/libptyqt/winptyprocess.h | 5 ++-- 4 files changed, 44 insertions(+), 36 deletions(-) (limited to 'src/libs') diff --git a/src/libs/3rdparty/libptyqt/conptyprocess.cpp b/src/libs/3rdparty/libptyqt/conptyprocess.cpp index b50f319ebf..d112f5e153 100644 --- a/src/libs/3rdparty/libptyqt/conptyprocess.cpp +++ b/src/libs/3rdparty/libptyqt/conptyprocess.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -179,21 +180,19 @@ bool ConPtyProcess::startProcess(const QString &executable, m_pid = m_shellProcessInformation.dwProcessId; // Notify when the shell process has been terminated - RegisterWaitForSingleObject( - &m_shellCloseWaitHandle, - m_shellProcessInformation.hProcess, - [](PVOID data, BOOLEAN) { - auto self = static_cast(data); - DWORD exitCode = 0; - GetExitCodeProcess(self->m_shellProcessInformation.hProcess, &exitCode); - self->m_exitCode = exitCode; - // Do not respawn if the object is about to be destructed - if (!self->m_aboutToDestruct) - emit self->notifier()->aboutToClose(); - }, - this, - INFINITE, - WT_EXECUTEONLYONCE); + m_shellCloseWaitNotifier = new QWinEventNotifier(m_shellProcessInformation.hProcess, notifier()); + QObject::connect(m_shellCloseWaitNotifier, + &QWinEventNotifier::activated, + notifier(), + [this](HANDLE hEvent) { + DWORD exitCode = 0; + GetExitCodeProcess(hEvent, &exitCode); + m_exitCode = exitCode; + // Do not respawn if the object is about to be destructed + if (!m_aboutToDestruct) + emit notifier()->aboutToClose(); + m_shellCloseWaitNotifier->setEnabled(false); + }); //this code runned in separate thread m_readThread = QThread::create([this]() @@ -220,6 +219,8 @@ bool ConPtyProcess::startProcess(const QString &executable, if (QThread::currentThread()->isInterruptionRequested() || brokenPipe) break; } + + CancelIoEx(m_hPipeIn, nullptr); }); //start read thread @@ -269,6 +270,9 @@ bool ConPtyProcess::kill() m_readThread->deleteLater(); m_readThread = nullptr; + delete m_shellCloseWaitNotifier; + m_shellCloseWaitNotifier = nullptr; + m_pid = 0; m_ptyHandler = INVALID_HANDLE_VALUE; m_hPipeIn = INVALID_HANDLE_VALUE; @@ -276,7 +280,6 @@ bool ConPtyProcess::kill() CloseHandle(m_shellProcessInformation.hThread); CloseHandle(m_shellProcessInformation.hProcess); - UnregisterWait(m_shellCloseWaitHandle); // Cleanup attribute list if (m_shellStartupInfo.lpAttributeList) { diff --git a/src/libs/3rdparty/libptyqt/conptyprocess.h b/src/libs/3rdparty/libptyqt/conptyprocess.h index b3f77664c2..a22b6290c7 100644 --- a/src/libs/3rdparty/libptyqt/conptyprocess.h +++ b/src/libs/3rdparty/libptyqt/conptyprocess.h @@ -23,6 +23,8 @@ typedef VOID* HPCON; #define TOO_OLD_WINSDK #endif +class QWinEventNotifier; + template std::vector vectorFromString(const std::basic_string &str) { @@ -160,7 +162,7 @@ private: PtyBuffer m_buffer; bool m_aboutToDestruct{false}; PROCESS_INFORMATION m_shellProcessInformation{}; - HANDLE m_shellCloseWaitHandle{INVALID_HANDLE_VALUE}; + QWinEventNotifier* m_shellCloseWaitNotifier; STARTUPINFOEX m_shellStartupInfo{}; }; diff --git a/src/libs/3rdparty/libptyqt/winptyprocess.cpp b/src/libs/3rdparty/libptyqt/winptyprocess.cpp index 90ac139b9c..be3a0de609 100644 --- a/src/libs/3rdparty/libptyqt/winptyprocess.cpp +++ b/src/libs/3rdparty/libptyqt/winptyprocess.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include #define DEBUG_VAR_LEGACY "WINPTYDBG" #define DEBUG_VAR_ACTUAL "WINPTY_DEBUG" @@ -132,22 +134,22 @@ bool WinPtyProcess::startProcess(const QString &executable, m_pid = (int)GetProcessId(m_innerHandle); + m_outSocket = new QLocalSocket(); + // Notify when the shell process has been terminated - RegisterWaitForSingleObject( - &m_shellCloseWaitHandle, - m_innerHandle, - [](PVOID data, BOOLEAN) { - auto self = static_cast(data); - // Do not respawn if the object is about to be destructed - DWORD exitCode = 0; - GetExitCodeProcess(self->m_innerHandle, &exitCode); - self->m_exitCode = exitCode; - if (!self->m_aboutToDestruct) - emit self->notifier()->aboutToClose(); - }, - this, - INFINITE, - WT_EXECUTEONLYONCE); + m_shellCloseWaitNotifier = new QWinEventNotifier(m_innerHandle, notifier()); + QObject::connect(m_shellCloseWaitNotifier, + &QWinEventNotifier::activated, + notifier(), + [this](HANDLE hEvent) { + DWORD exitCode = 0; + GetExitCodeProcess(hEvent, &exitCode); + m_exitCode = exitCode; + // Do not respawn if the object is about to be destructed + if (!m_aboutToDestruct) + emit notifier()->aboutToClose(); + m_shellCloseWaitNotifier->setEnabled(false); + }); //get pipe names LPCWSTR conInPipeName = winpty_conin_name(m_ptyHandler); @@ -158,7 +160,6 @@ bool WinPtyProcess::startProcess(const QString &executable, LPCWSTR conOutPipeName = winpty_conout_name(m_ptyHandler); m_conOutName = QString::fromStdWString(std::wstring(conOutPipeName)); - m_outSocket = new QLocalSocket(); m_outSocket->connectToServer(m_conOutName, QIODevice::ReadOnly); m_outSocket->waitForConnected(); @@ -214,7 +215,8 @@ bool WinPtyProcess::kill() winpty_free(m_ptyHandler); exitCode = CloseHandle(m_innerHandle); - UnregisterWait(m_shellCloseWaitHandle); + delete m_shellCloseWaitNotifier; + m_shellCloseWaitNotifier = nullptr; m_ptyHandler = nullptr; m_innerHandle = nullptr; diff --git a/src/libs/3rdparty/libptyqt/winptyprocess.h b/src/libs/3rdparty/libptyqt/winptyprocess.h index 547bcf7c97..0bfb27c02c 100644 --- a/src/libs/3rdparty/libptyqt/winptyprocess.h +++ b/src/libs/3rdparty/libptyqt/winptyprocess.h @@ -4,7 +4,8 @@ #include "iptyprocess.h" #include "winpty.h" -#include +class QLocalSocket; +class QWinEventNotifier; class WinPtyProcess : public IPtyProcess { @@ -36,7 +37,7 @@ private: QLocalSocket *m_inSocket; QLocalSocket *m_outSocket; bool m_aboutToDestruct{false}; - HANDLE m_shellCloseWaitHandle{INVALID_HANDLE_VALUE}; + QWinEventNotifier* m_shellCloseWaitNotifier; }; #endif // WINPTYPROCESS_H -- cgit v1.2.3 From 4b93f47565f70860f20ddf6ea49aca0899f6855b Mon Sep 17 00:00:00 2001 From: hjk Date: Wed, 1 Mar 2023 13:50:06 +0100 Subject: Utils: Iterate environment via callback Iterators expose the underlying datastructure and get in the way of moving towards "env as stack of changes" Task-number: QTCREATORBUG-28357 Change-Id: I69e3b53e62ed4c9ab394779e97afbc6fd1986838 Reviewed-by: Marcus Tillmanns --- src/libs/utils/environment.cpp | 6 ++++++ src/libs/utils/environment.h | 2 ++ 2 files changed, 8 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/environment.cpp b/src/libs/utils/environment.cpp index 96e7ea1775..6d8785c9d7 100644 --- a/src/libs/utils/environment.cpp +++ b/src/libs/utils/environment.cpp @@ -24,6 +24,12 @@ NameValueItems Environment::diff(const Environment &other, bool checkAppendPrepe return m_dict.diff(other.m_dict, checkAppendPrepend); } +void Environment::forEachEntry(const std::function &callBack) const +{ + for (auto it = m_dict.m_values.constBegin(); it != m_dict.m_values.constEnd(); ++it) + callBack(it.key().name, it.value().first, it.value().second); +} + bool Environment::hasChanges() const { return m_dict.size() != 0; diff --git a/src/libs/utils/environment.h b/src/libs/utils/environment.h index e6bf46b32d..7d670a4ae7 100644 --- a/src/libs/utils/environment.h +++ b/src/libs/utils/environment.h @@ -93,6 +93,8 @@ public: const_iterator constEnd() const { return m_dict.constEnd(); } // FIXME: avoid const_iterator constFind(const QString &name) const { return m_dict.constFind(name); } // FIXME: avoid + void forEachEntry(const std::function &callBack) const; + friend bool operator!=(const Environment &first, const Environment &second) { return first.m_dict != second.m_dict; -- cgit v1.2.3 From 8b09ad889803c764862b58df312a842037f4458e Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Thu, 2 Mar 2023 14:03:54 +0100 Subject: QtcProcess: Introduce PtyData That's going to be used by PtyProcessImpl. Change-Id: Ifc1a7886ceed73272c9e415414db49452175a334 Reviewed-by: Marcus Tillmanns --- src/libs/utils/processinterface.cpp | 11 +++++++++++ src/libs/utils/processinterface.h | 29 +++++++++++++++++++++++++++++ src/libs/utils/qtcprocess.cpp | 11 ++++++++++- src/libs/utils/qtcprocess.h | 4 ++++ 4 files changed, 54 insertions(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/processinterface.cpp b/src/libs/utils/processinterface.cpp index 9e13494cda..a5e60c97a3 100644 --- a/src/libs/utils/processinterface.cpp +++ b/src/libs/utils/processinterface.cpp @@ -7,6 +7,17 @@ namespace Utils { +namespace Pty { + +void Data::resize(const QSize &size) +{ + m_size = size; + if (m_data->m_handler) + m_data->m_handler(size); +} + +} // namespace Pty + /*! * \brief controlSignalToInt * \param controlSignal diff --git a/src/libs/utils/processinterface.h b/src/libs/utils/processinterface.h index d398d8fe13..210df7d268 100644 --- a/src/libs/utils/processinterface.h +++ b/src/libs/utils/processinterface.h @@ -10,11 +10,39 @@ #include "processenums.h" #include +#include namespace Utils { namespace Internal { class QtcProcessPrivate; } +namespace Pty { + +using ResizeHandler = std::function; + +class QTCREATOR_UTILS_EXPORT SharedData +{ +public: + ResizeHandler m_handler; +}; + +class QTCREATOR_UTILS_EXPORT Data +{ +public: + Data() : m_data(new SharedData) {} + + void setResizeHandler(const ResizeHandler &handler) { m_data->m_handler = handler; } + + QSize size() const { return m_size; } + void resize(const QSize &size); + +private: + QSize m_size{80, 60}; + QSharedPointer m_data; +}; + +} // namespace Pty + class QTCREATOR_UTILS_EXPORT ProcessSetupData { public: @@ -22,6 +50,7 @@ public: ProcessMode m_processMode = ProcessMode::Reader; TerminalMode m_terminalMode = TerminalMode::Off; + Pty::Data m_ptyData; CommandLine m_commandLine; FilePath m_workingDirectory; Environment m_environment; diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/qtcprocess.cpp index 096f644561..42a29366bd 100644 --- a/src/libs/utils/qtcprocess.cpp +++ b/src/libs/utils/qtcprocess.cpp @@ -13,7 +13,6 @@ #include "processutils.h" #include "stringutils.h" #include "terminalhooks.h" -#include "terminalprocess_p.h" #include "threadutils.h" #include "utilstr.h" @@ -1026,6 +1025,16 @@ void QtcProcess::setProcessImpl(ProcessImpl processImpl) d->m_setup.m_processImpl = processImpl; } +void QtcProcess::setPtyData(const Pty::Data &data) +{ + d->m_setup.m_ptyData = data; +} + +Pty::Data QtcProcess::ptyData() const +{ + return d->m_setup.m_ptyData; +} + ProcessMode QtcProcess::processMode() const { return d->m_setup.m_processMode; diff --git a/src/libs/utils/qtcprocess.h b/src/libs/utils/qtcprocess.h index 67898ba855..7218105149 100644 --- a/src/libs/utils/qtcprocess.h +++ b/src/libs/utils/qtcprocess.h @@ -21,6 +21,7 @@ class tst_QtcProcess; namespace Utils { namespace Internal { class QtcProcessPrivate; } +namespace Pty { class Data; } class Environment; class DeviceProcessHooks; @@ -76,6 +77,9 @@ public: void setProcessImpl(ProcessImpl processImpl); + void setPtyData(const Pty::Data &data); + Pty::Data ptyData() const; + void setTerminalMode(TerminalMode mode); TerminalMode terminalMode() const; bool usesTerminal() const { return terminalMode() != TerminalMode::Off; } -- cgit v1.2.3 From 1da18a4b62c9fcb5d499b098b0e13049f0c34193 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Wed, 1 Mar 2023 08:15:58 +0100 Subject: Utils: Integrate ptyqt into qtcprocess Integrating PtyQt directly into QtcProcess allows us to start Pseudo terminal processes using the existing QtcProcess functionality such as starting remote process on e.g. docker or remote linux devices. This is needed for the new Terminal plugin. Change-Id: Iaeed5ff9b341ba4646d955b2ed9577a18cd7100f Reviewed-by: Jarek Kobus Reviewed-by: Cristian Adam --- src/libs/3rdparty/libptyqt/conptyprocess.cpp | 16 +++-- src/libs/3rdparty/libptyqt/unixptyprocess.cpp | 4 +- src/libs/3rdparty/libptyqt/winptyprocess.cpp | 1 + src/libs/3rdparty/winpty/src/CMakeLists.txt | 1 - src/libs/utils/CMakeLists.txt | 2 +- src/libs/utils/processenums.h | 1 + src/libs/utils/qtcprocess.cpp | 98 +++++++++++++++++++++++++++ 7 files changed, 114 insertions(+), 9 deletions(-) (limited to 'src/libs') diff --git a/src/libs/3rdparty/libptyqt/conptyprocess.cpp b/src/libs/3rdparty/libptyqt/conptyprocess.cpp index d112f5e153..c788e74c92 100644 --- a/src/libs/3rdparty/libptyqt/conptyprocess.cpp +++ b/src/libs/3rdparty/libptyqt/conptyprocess.cpp @@ -123,6 +123,7 @@ bool ConPtyProcess::startProcess(const QString &executable, } m_shellPath = executable; + m_shellPath.replace('/', '\\'); m_size = QPair(cols, rows); //env @@ -134,6 +135,7 @@ bool ConPtyProcess::startProcess(const QString &executable, envBlock << L'\0'; std::wstring env = envBlock.str(); LPWSTR envArg = env.empty() ? nullptr : env.data(); + LPCWSTR workingDirPointer = workingDir.isEmpty() ? nullptr : workingDir.toStdWString().c_str(); QStringList exeAndArgs = arguments; exeAndArgs.prepend(m_shellPath); @@ -165,7 +167,7 @@ bool ConPtyProcess::startProcess(const QString &executable, FALSE, // Inherit handles EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT, // Creation flags envArg, // Environment block - workingDir.toStdWString().c_str(), // Use parent's starting directory + workingDirPointer, // Use parent's starting directory &m_shellStartupInfo.StartupInfo, // Pointer to STARTUPINFO &m_shellProcessInformation) // Pointer to PROCESS_INFORMATION ? S_OK @@ -264,11 +266,13 @@ bool ConPtyProcess::kill() if (INVALID_HANDLE_VALUE != m_hPipeIn) CloseHandle(m_hPipeIn); - m_readThread->requestInterruption(); - if (!m_readThread->wait(1000)) - m_readThread->terminate(); - m_readThread->deleteLater(); - m_readThread = nullptr; + if (m_readThread) { + m_readThread->requestInterruption(); + if (!m_readThread->wait(1000)) + m_readThread->terminate(); + m_readThread->deleteLater(); + m_readThread = nullptr; + } delete m_shellCloseWaitNotifier; m_shellCloseWaitNotifier = nullptr; diff --git a/src/libs/3rdparty/libptyqt/unixptyprocess.cpp b/src/libs/3rdparty/libptyqt/unixptyprocess.cpp index 7cb8237a60..e049a2abb9 100644 --- a/src/libs/3rdparty/libptyqt/unixptyprocess.cpp +++ b/src/libs/3rdparty/libptyqt/unixptyprocess.cpp @@ -182,6 +182,7 @@ bool UnixPtyProcess::startProcess(const QString &shellPath, QObject::connect(&m_shellProcess, &QProcess::finished, &m_shellProcess, [this](int exitCode) { m_exitCode = exitCode; emit m_shellProcess.aboutToClose(); + m_readMasterNotify->disconnect(); }); QStringList defaultVars; @@ -216,7 +217,8 @@ bool UnixPtyProcess::startProcess(const QString &shellPath, m_shellProcess.setProcessEnvironment(envFormat); m_shellProcess.setReadChannel(QProcess::StandardOutput); m_shellProcess.start(m_shellPath, arguments); - m_shellProcess.waitForStarted(); + if (!m_shellProcess.waitForStarted()) + return false; m_pid = m_shellProcess.processId(); diff --git a/src/libs/3rdparty/libptyqt/winptyprocess.cpp b/src/libs/3rdparty/libptyqt/winptyprocess.cpp index be3a0de609..0509bb77c3 100644 --- a/src/libs/3rdparty/libptyqt/winptyprocess.cpp +++ b/src/libs/3rdparty/libptyqt/winptyprocess.cpp @@ -60,6 +60,7 @@ bool WinPtyProcess::startProcess(const QString &executable, } m_shellPath = executable; + m_shellPath.replace('/', '\\'); m_size = QPair(cols, rows); #ifdef PTYQT_DEBUG diff --git a/src/libs/3rdparty/winpty/src/CMakeLists.txt b/src/libs/3rdparty/winpty/src/CMakeLists.txt index 5763955e8d..22b15111d4 100644 --- a/src/libs/3rdparty/winpty/src/CMakeLists.txt +++ b/src/libs/3rdparty/winpty/src/CMakeLists.txt @@ -46,7 +46,6 @@ set(shared_sources # add_qtc_executable(winpty-agent - DESTINATION ${IDE_PLUGIN_PATH} INCLUDES include ${CMAKE_BINARY_DIR} DEFINES WINPTY_AGENT_ASSERT diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index 04f475afb7..d6b6efb4f2 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -257,7 +257,7 @@ extend_qtc_library(Utils CONDITION UNIX AND NOT APPLE extend_qtc_library(Utils CONDITION TARGET Qt::CorePrivate - DEPENDS Qt::CorePrivate + DEPENDS Qt::CorePrivate ptyqt DEFINES QTC_UTILS_WITH_FSENGINE SOURCES fsengine/fsengine_impl.cpp fsengine/fsengine_impl.h diff --git a/src/libs/utils/processenums.h b/src/libs/utils/processenums.h index 6ea37a2d37..1c16f22dcb 100644 --- a/src/libs/utils/processenums.h +++ b/src/libs/utils/processenums.h @@ -24,6 +24,7 @@ enum class ProcessImpl { enum class TerminalMode { Off, + Pty, Run, Debug, Suspend, diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/qtcprocess.cpp index 42a29366bd..4808764ddd 100644 --- a/src/libs/utils/qtcprocess.cpp +++ b/src/libs/utils/qtcprocess.cpp @@ -16,6 +16,9 @@ #include "threadutils.h" #include "utilstr.h" +#include +#include + #include #include #include @@ -304,6 +307,99 @@ private: QProcess *m_process = nullptr; }; +class PtyProcessImpl final : public DefaultImpl +{ +public: + ~PtyProcessImpl() { m_setup.m_ptyData.setResizeHandler({}); } + + qint64 write(const QByteArray &data) final + { + if (m_ptyProcess) + return m_ptyProcess->write(data); + return -1; + } + + void sendControlSignal(ControlSignal controlSignal) final + { + if (!m_ptyProcess) + return; + + switch (controlSignal) { + case ControlSignal::Terminate: + m_ptyProcess.reset(); + break; + case ControlSignal::Kill: + m_ptyProcess->kill(); + break; + default: + QTC_CHECK(false); + } + } + + void doDefaultStart(const QString &program, const QStringList &arguments) final + { + m_setup.m_ptyData.setResizeHandler([this](const QSize &size) { + if (m_ptyProcess) + m_ptyProcess->resize(size.width(), size.height()); + }); + m_ptyProcess.reset(PtyQt::createPtyProcess(IPtyProcess::AutoPty)); + if (!m_ptyProcess) { + const ProcessResultData result = {-1, + QProcess::CrashExit, + QProcess::FailedToStart, + "Failed to create pty process"}; + emit done(result); + return; + } + + bool startResult + = m_ptyProcess->startProcess(program, + arguments, + m_setup.m_workingDirectory.path(), + m_setup.m_environment.toProcessEnvironment().toStringList(), + m_setup.m_ptyData.size().width(), + m_setup.m_ptyData.size().height()); + + if (!startResult) { + const ProcessResultData result = {-1, + QProcess::CrashExit, + QProcess::FailedToStart, + "Failed to start pty process: " + + m_ptyProcess->lastError()}; + emit done(result); + return; + } + + if (!m_ptyProcess->lastError().isEmpty()) { + const ProcessResultData result + = {-1, QProcess::CrashExit, QProcess::FailedToStart, m_ptyProcess->lastError()}; + emit done(result); + return; + } + + connect(m_ptyProcess->notifier(), &QIODevice::readyRead, this, [this] { + emit readyRead(m_ptyProcess->readAll(), {}); + }); + + connect(m_ptyProcess->notifier(), &QIODevice::aboutToClose, this, [this] { + if (m_ptyProcess) { + const ProcessResultData result + = {m_ptyProcess->exitCode(), QProcess::NormalExit, QProcess::UnknownError, {}}; + emit done(result); + return; + } + + const ProcessResultData result = {0, QProcess::NormalExit, QProcess::UnknownError, {}}; + emit done(result); + }); + + emit started(m_ptyProcess->pid()); + } + +private: + std::unique_ptr m_ptyProcess; +}; + class QProcessImpl final : public DefaultImpl { public: @@ -629,6 +725,8 @@ public: ProcessInterface *createProcessInterface() { + if (m_setup.m_terminalMode == TerminalMode::Pty) + return new PtyProcessImpl(); if (m_setup.m_terminalMode != TerminalMode::Off) return Terminal::Hooks::instance().createTerminalProcessInterfaceHook()(); -- cgit v1.2.3 From 86da87d3062adea1a1eb64426750978d19043492 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Wed, 1 Mar 2023 09:57:43 +0100 Subject: ProjectExplorer: Remove IDevice::terminalCommand Since Terminals can now be started for device file paths, there is no need anymore for IDevice::terminalCommand. Change-Id: I01c831ea7ee29d53efa6880631e8c6d54a4316aa Reviewed-by: Cristian Adam --- src/libs/utils/terminalhooks.cpp | 18 ++++++++++++++++++ src/libs/utils/terminalhooks.h | 2 ++ 2 files changed, 20 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/terminalhooks.cpp b/src/libs/utils/terminalhooks.cpp index df27e0404b..f4a5944dbd 100644 --- a/src/libs/utils/terminalhooks.cpp +++ b/src/libs/utils/terminalhooks.cpp @@ -8,6 +8,24 @@ namespace Utils::Terminal { +FilePath defaultShellForDevice(const FilePath &deviceRoot) +{ + if (!deviceRoot.needsDevice()) + return {}; + + // TODO: Windows ? + const Environment env = deviceRoot.deviceEnvironment(); + FilePath shell = FilePath::fromUserInput(env.value_or("SHELL", "/bin/sh")); + + if (!shell.isAbsolutePath()) + shell = env.searchInPath(shell.nativePath()); + + if (shell.isEmpty()) + return shell; + + return shell.onDevice(deviceRoot); +} + struct HooksPrivate { HooksPrivate() diff --git a/src/libs/utils/terminalhooks.h b/src/libs/utils/terminalhooks.h index 492c53da81..3f3d848581 100644 --- a/src/libs/utils/terminalhooks.h +++ b/src/libs/utils/terminalhooks.h @@ -54,6 +54,8 @@ struct NameAndCommandLine CommandLine commandLine; }; +QTCREATOR_UTILS_EXPORT FilePath defaultShellForDevice(const FilePath &deviceRoot); + class QTCREATOR_UTILS_EXPORT Hooks { public: -- cgit v1.2.3 From 928bef59efe4fa65078b9160f22ee430ccc4ac49 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Thu, 2 Mar 2023 16:20:28 +0100 Subject: Fix qbs build Change-Id: Iddc8bdc08367cb596b16c22494c9289ea7eb4c86 Reviewed-by: hjk --- src/libs/utils/utils.qbs | 1 + 1 file changed, 1 insertion(+) (limited to 'src/libs') diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index a1b3415e71..cf952ae62d 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -36,6 +36,7 @@ Project { Depends { name: "Qt"; submodules: ["concurrent", "core-private", "network", "qml", "widgets", "xml"] } Depends { name: "Qt.macextras"; condition: Qt.core.versionMajor < 6 && qbs.targetOS.contains("macos") } Depends { name: "app_version_header" } + Depends { name: "ptyqt" } files: [ "QtConcurrentTools", -- cgit v1.2.3 From 3e5d14b02047794669db82620e9411065988b4d7 Mon Sep 17 00:00:00 2001 From: hjk Date: Thu, 2 Mar 2023 13:26:26 +0100 Subject: Utils: Replace Environment::find iterator use Task-number: QTCREATORBUG-28357 Change-Id: I2723ffd6b7842f88009701eccea9aacac8cbf516 Reviewed-by: Christian Kandeler --- src/libs/utils/commandline.cpp | 12 ++++++------ src/libs/utils/environment.cpp | 32 +++++++++++++++++++++----------- src/libs/utils/environment.h | 5 ++++- 3 files changed, 31 insertions(+), 18 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/commandline.cpp b/src/libs/utils/commandline.cpp index 6f8d4a4f5d..81285259ac 100644 --- a/src/libs/utils/commandline.cpp +++ b/src/libs/utils/commandline.cpp @@ -362,12 +362,12 @@ static QStringList splitArgsUnix(const QString &args, bool abortOnMeta, if (var == pwdName && pwd && !pwd->isEmpty()) { cret += *pwd; } else { - Environment::const_iterator vit = env->constFind(var); - if (vit == env->constEnd()) { + const Environment::FindResult res = env->find(var); + if (!res) { if (abortOnMeta) goto metaerr; // Assume this is a shell builtin } else { - cret += env->expandedValueForKey(env->key(vit)); + cret += env->expandedValueForKey(res->key); } } if (!braced) @@ -412,12 +412,12 @@ static QStringList splitArgsUnix(const QString &args, bool abortOnMeta, if (var == pwdName && pwd && !pwd->isEmpty()) { val = *pwd; } else { - Environment::const_iterator vit = env->constFind(var); - if (vit == env->constEnd()) { + const Environment::FindResult res = env->find(var); + if (!res) { if (abortOnMeta) goto metaerr; // Assume this is a shell builtin } else { - val = env->expandedValueForKey(env->key(vit)); + val = env->expandedValueForKey(res->key); } } for (int i = 0; i < val.length(); i++) { diff --git a/src/libs/utils/environment.cpp b/src/libs/utils/environment.cpp index 6d8785c9d7..eb49db208d 100644 --- a/src/libs/utils/environment.cpp +++ b/src/libs/utils/environment.cpp @@ -24,6 +24,14 @@ NameValueItems Environment::diff(const Environment &other, bool checkAppendPrepe return m_dict.diff(other.m_dict, checkAppendPrepend); } +Environment::FindResult Environment::find(const QString &name) const +{ + const auto it = m_dict.constFind(name); + if (it == m_dict.constEnd()) + return {}; + return Entry{it.key().name, it.value().first, it.value().second}; +} + void Environment::forEachEntry(const std::function &callBack) const { for (auto it = m_dict.m_values.constBegin(); it != m_dict.m_values.constEnd(); ++it) @@ -356,28 +364,30 @@ QString Environment::expandVariables(const QString &input) const } } else if (state == BRACEDVARIABLE) { if (c == '}') { - const_iterator it = constFind(result.mid(vStart, i - 1 - vStart)); - if (it != constEnd()) { - result.replace(vStart - 2, i - vStart + 2, it->first); - i = vStart - 2 + it->first.length(); + const QString key = result.mid(vStart, i - 1 - vStart); + const Environment::FindResult res = find(key); + if (res) { + result.replace(vStart - 2, i - vStart + 2, res->value); + i = vStart - 2 + res->value.length(); } state = BASE; } } else if (state == VARIABLE) { if (!c.isLetterOrNumber() && c != '_') { - const_iterator it = constFind(result.mid(vStart, i - vStart - 1)); - if (it != constEnd()) { - result.replace(vStart - 1, i - vStart, it->first); - i = vStart - 1 + it->first.length(); + const QString key = result.mid(vStart, i - vStart - 1); + const Environment::FindResult res = find(key); + if (res) { + result.replace(vStart - 1, i - vStart, res->value); + i = vStart - 1 + res->value.length(); } state = BASE; } } } if (state == VARIABLE) { - const_iterator it = constFind(result.mid(vStart)); - if (it != constEnd()) - result.replace(vStart - 1, result.length() - vStart + 1, it->first); + const Environment::FindResult res = find(result.mid(vStart)); + if (res) + result.replace(vStart - 1, result.length() - vStart + 1, res->value); } } return result; diff --git a/src/libs/utils/environment.h b/src/libs/utils/environment.h index 7d670a4ae7..52ac1db628 100644 --- a/src/libs/utils/environment.h +++ b/src/libs/utils/environment.h @@ -91,7 +91,10 @@ public: const_iterator constBegin() const { return m_dict.constBegin(); } // FIXME: avoid const_iterator constEnd() const { return m_dict.constEnd(); } // FIXME: avoid - const_iterator constFind(const QString &name) const { return m_dict.constFind(name); } // FIXME: avoid + + struct Entry { QString key; QString value; bool enabled; }; + using FindResult = std::optional; + FindResult find(const QString &name) const; // Note res->key may differ in case from name. void forEachEntry(const std::function &callBack) const; -- cgit v1.2.3 From 13a202564bfc7b01fb932d8c1b2c00bf7b0a9f03 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Thu, 2 Mar 2023 19:58:52 +0100 Subject: TerminalProcess: Add Pty into switch statement Amends 1da18a4b62c9fcb5d499b098b0e13049f0c34193 Change-Id: I1cef188a28f19eea3885a17e983b4cf7a4815498 Reviewed-by: Marcus Tillmanns --- src/libs/utils/terminalprocess.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/terminalprocess.cpp b/src/libs/utils/terminalprocess.cpp index bd0333c9d7..91423539a3 100644 --- a/src/libs/utils/terminalprocess.cpp +++ b/src/libs/utils/terminalprocess.cpp @@ -44,13 +44,15 @@ namespace Internal { static QString modeOption(TerminalMode m) { switch (m) { - case TerminalMode::Run: - return QLatin1String("run"); - case TerminalMode::Debug: - return QLatin1String("debug"); - case TerminalMode::Suspend: - return QLatin1String("suspend"); - case TerminalMode::Off: + case TerminalMode::Pty: + return "pty"; + case TerminalMode::Run: + return "run"; + case TerminalMode::Debug: + return "debug"; + case TerminalMode::Suspend: + return "suspend"; + case TerminalMode::Off: QTC_CHECK(false); break; } -- cgit v1.2.3 From b26e3a45012788370083ab67b86897043abb0e3c Mon Sep 17 00:00:00 2001 From: hjk Date: Thu, 2 Mar 2023 14:21:23 +0100 Subject: Utils: Remove unused Environment functions Change-Id: Idea6b1a126b1d4fe82b597e96ce447da9f546396 Reviewed-by: Christian Kandeler Reviewed-by: Qt CI Bot Reviewed-by: --- src/libs/utils/environment.h | 2 -- 1 file changed, 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/environment.h b/src/libs/utils/environment.h index 52ac1db628..1485dff2a1 100644 --- a/src/libs/utils/environment.h +++ b/src/libs/utils/environment.h @@ -41,7 +41,6 @@ public: void modify(const NameValueItems &items) { m_dict.modify(items); } bool hasChanges() const; - void clear() { return m_dict.clear(); } QStringList toStringList() const { return m_dict.toStringList(); } QProcessEnvironment toProcessEnvironment() const; @@ -76,7 +75,6 @@ public: QStringList expandVariables(const QStringList &input) const; OsType osType() const { return m_dict.osType(); } - QString userName() const; using const_iterator = NameValueMap::const_iterator; // FIXME: avoid NameValueDictionary toDictionary() const { return m_dict; } // FIXME: avoid -- cgit v1.2.3 From 6d70a2796564dae8d8cec591dfe0d7b2f58d0469 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Fri, 3 Mar 2023 09:00:11 +0100 Subject: UnixPtyProcess: Fix read loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously a value of "-1" from ::read would result in an endless loop. This could easily be reproduced by "cat /dev/random | base64" The buffer usage was also much more complicated than needed. A static readBuffer now keeps the amount of allocations lower. Change-Id: I5bb1a3c84b107ff8c2d3801bca8c6ae9a709cdb3 Reviewed-by: Cristian Adam --- src/libs/3rdparty/libptyqt/unixptyprocess.cpp | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) (limited to 'src/libs') diff --git a/src/libs/3rdparty/libptyqt/unixptyprocess.cpp b/src/libs/3rdparty/libptyqt/unixptyprocess.cpp index e049a2abb9..8c018daf8c 100644 --- a/src/libs/3rdparty/libptyqt/unixptyprocess.cpp +++ b/src/libs/3rdparty/libptyqt/unixptyprocess.cpp @@ -164,19 +164,14 @@ bool UnixPtyProcess::startProcess(const QString &shellPath, QObject::connect(m_readMasterNotify, &QSocketNotifier::activated, [this](int socket) { Q_UNUSED(socket) - QByteArray buffer; - int size = 1025; - int readSize = 1024; - QByteArray data; - do { - char nativeBuffer[size]; - int len = ::read(m_shellProcess.m_handleMaster, nativeBuffer, readSize); - data = QByteArray(nativeBuffer, len); - buffer.append(data); - } while (data.size() == readSize); //last data block always < readSize - - m_shellReadBuffer.append(buffer); - m_shellProcess.emitReadyRead(); + const size_t maxRead = 16 * 1024; + static std::array buffer; + + int len = ::read(m_shellProcess.m_handleMaster, buffer.data(), buffer.size()); + if (len > 0) { + m_shellReadBuffer.append(buffer.data(), len); + m_shellProcess.emitReadyRead(); + } }); QObject::connect(&m_shellProcess, &QProcess::finished, &m_shellProcess, [this](int exitCode) { -- cgit v1.2.3 From 22b9826e22566e82f106abae6ba14a2c7e3d2f94 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Fri, 24 Feb 2023 09:37:33 +0100 Subject: Utils: Introduce FileStreamer The class is responsible for asynchronous read / write of file contents. The file may be local or remote. It's also able to do an asynchronous copy of files between different devices. Change-Id: I65e4325b6b7f98bfc17286c9a72b0018db472a16 Reviewed-by: Qt CI Bot Reviewed-by: hjk --- src/libs/utils/CMakeLists.txt | 1 + src/libs/utils/asynctask.h | 8 +- src/libs/utils/filestreamer.cpp | 489 ++++++++++++++++++++++++++++++++++++++++ src/libs/utils/filestreamer.h | 62 +++++ src/libs/utils/utils.qbs | 2 + 5 files changed, 561 insertions(+), 1 deletion(-) create mode 100644 src/libs/utils/filestreamer.cpp create mode 100644 src/libs/utils/filestreamer.h (limited to 'src/libs') diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index d6b6efb4f2..e8adad413e 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -55,6 +55,7 @@ add_qtc_library(Utils filepath.cpp filepath.h filepathinfo.h filesearch.cpp filesearch.h + filestreamer.cpp filestreamer.h filesystemmodel.cpp filesystemmodel.h filesystemwatcher.cpp filesystemwatcher.h fileutils.cpp fileutils.h diff --git a/src/libs/utils/asynctask.h b/src/libs/utils/asynctask.h index 84cf1ee838..5beaf400ea 100644 --- a/src/libs/utils/asynctask.h +++ b/src/libs/utils/asynctask.h @@ -24,13 +24,18 @@ class QTCREATOR_UTILS_EXPORT AsyncTaskBase : public QObject signals: void started(); void done(); + void resultReadyAt(int index); }; template class AsyncTask : public AsyncTaskBase { public: - AsyncTask() { connect(&m_watcher, &QFutureWatcherBase::finished, this, &AsyncTaskBase::done); } + AsyncTask() { + connect(&m_watcher, &QFutureWatcherBase::finished, this, &AsyncTaskBase::done); + connect(&m_watcher, &QFutureWatcherBase::resultReadyAt, + this, &AsyncTaskBase::resultReadyAt); + } ~AsyncTask() { if (isDone()) @@ -72,6 +77,7 @@ public: QFuture future() const { return m_watcher.future(); } ResultType result() const { return m_watcher.result(); } + ResultType resultAt(int index) const { return m_watcher.resultAt(index); } QList results() const { return future().results(); } bool isResultAvailable() const { return future().resultCount(); } diff --git a/src/libs/utils/filestreamer.cpp b/src/libs/utils/filestreamer.cpp new file mode 100644 index 0000000000..222488a510 --- /dev/null +++ b/src/libs/utils/filestreamer.cpp @@ -0,0 +1,489 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "filestreamer.h" + +#include "asynctask.h" +#include "qtcprocess.h" + +#include +#include +#include +#include + +namespace Utils { + +using namespace Tasking; + +// TODO: Adjust according to time spent on single buffer read so that it's not more than ~50 ms +// in case of local read / write. Should it be adjusted dynamically / automatically? +static const qint64 s_bufferSize = 0x1 << 20; // 1048576 + +class FileStreamBase : public QObject +{ + Q_OBJECT + +public: + void setFilePath(const FilePath &filePath) { m_filePath = filePath; } + void start() { + QTC_ASSERT(!m_taskTree, return); + + const TaskItem task = m_filePath.needsDevice() ? remoteTask() : localTask(); + m_taskTree.reset(new TaskTree({task})); + const auto finalize = [this](bool success) { + m_taskTree.release()->deleteLater(); + emit done(success); + }; + connect(m_taskTree.get(), &TaskTree::done, this, [=] { finalize(true); }); + connect(m_taskTree.get(), &TaskTree::errorOccurred, this, [=] { finalize(false); }); + m_taskTree->start(); + } + +signals: + void done(bool success); + +protected: + FilePath m_filePath; + std::unique_ptr m_taskTree; + +private: + virtual TaskItem remoteTask() = 0; + virtual TaskItem localTask() = 0; +}; + +static void localRead(QPromise &promise, const FilePath &filePath) +{ + if (promise.isCanceled()) + return; + + QFile file(filePath.path()); + if (!file.exists()) { + promise.future().cancel(); + return; + } + + if (!file.open(QFile::ReadOnly)) { + promise.future().cancel(); + return; + } + + while (int chunkSize = qMin(s_bufferSize, file.bytesAvailable())) { + if (promise.isCanceled()) + return; + promise.addResult(file.read(chunkSize)); + } +} + +class FileStreamReader : public FileStreamBase +{ + Q_OBJECT + +signals: + void readyRead(const QByteArray &newData); + +private: + TaskItem remoteTask() final { + const auto setup = [this](QtcProcess &process) { + const QStringList args = {"if=" + m_filePath.path()}; + const FilePath dd = m_filePath.withNewPath("dd"); + process.setCommand({dd, args, OsType::OsTypeLinux}); + QtcProcess *processPtr = &process; + connect(processPtr, &QtcProcess::readyReadStandardOutput, this, [this, processPtr] { + emit readyRead(processPtr->readAllRawStandardOutput()); + }); + }; + return Process(setup); + } + TaskItem localTask() final { + const auto setup = [this](AsyncTask &async) { + async.setConcurrentCallData(localRead, m_filePath); + AsyncTask *asyncPtr = &async; + connect(asyncPtr, &AsyncTaskBase::resultReadyAt, this, [=](int index) { + emit readyRead(asyncPtr->resultAt(index)); + }); + }; + return Async(setup); + } +}; + +class WriteBuffer : public QObject +{ + Q_OBJECT + +public: + WriteBuffer(bool isConcurrent, QObject *parent) + : QObject(parent) + , m_isConcurrent(isConcurrent) {} + struct Data { + QByteArray m_writeData; + bool m_closeWriteChannel = false; + bool m_canceled = false; + bool hasNewData() const { return m_closeWriteChannel || !m_writeData.isEmpty(); } + }; + + void write(const QByteArray &newData) { + if (m_isConcurrent) { + QMutexLocker locker(&m_mutex); + QTC_ASSERT(!m_data.m_closeWriteChannel, return); + QTC_ASSERT(!m_data.m_canceled, return); + m_data.m_writeData += newData; + m_waitCondition.wakeOne(); + return; + } + emit writeRequested(newData); + } + void closeWriteChannel() { + if (m_isConcurrent) { + QMutexLocker locker(&m_mutex); + QTC_ASSERT(!m_data.m_canceled, return); + m_data.m_closeWriteChannel = true; + m_waitCondition.wakeOne(); + return; + } + emit closeWriteChannelRequested(); + } + void cancel() { + if (m_isConcurrent) { + QMutexLocker locker(&m_mutex); + m_data.m_canceled = true; + m_waitCondition.wakeOne(); + return; + } + emit closeWriteChannelRequested(); + } + Data waitForData() { + QTC_ASSERT(m_isConcurrent, return {}); + QMutexLocker locker(&m_mutex); + if (!m_data.hasNewData()) + m_waitCondition.wait(&m_mutex); + return std::exchange(m_data, {}); + } + +signals: + void writeRequested(const QByteArray &newData); + void closeWriteChannelRequested(); + +private: + QMutex m_mutex; + QWaitCondition m_waitCondition; + Data m_data; + bool m_isConcurrent = false; // Depends on whether FileStreamWriter::m_writeData is empty or not +}; + +static void localWrite(QPromise &promise, const FilePath &filePath, + const QByteArray &initialData, WriteBuffer *buffer) +{ + if (promise.isCanceled()) + return; + + QFile file(filePath.path()); + + if (!file.open(QFile::WriteOnly | QFile::Truncate)) { + promise.future().cancel(); + return; + } + + if (!initialData.isEmpty()) { + const qint64 res = file.write(initialData); + if (res != initialData.size()) + promise.future().cancel(); + return; + } + + while (true) { + if (promise.isCanceled()) { + promise.future().cancel(); + return; + } + const WriteBuffer::Data data = buffer->waitForData(); + if (data.m_canceled || promise.isCanceled()) { + promise.future().cancel(); + return; + } + if (!data.m_writeData.isEmpty()) { + // TODO: Write in chunks of s_bufferSize and check for promise.isCanceled() + const qint64 res = file.write(data.m_writeData); + if (res != data.m_writeData.size()) { + promise.future().cancel(); + return; + } + } + if (data.m_closeWriteChannel) + return; + } +} + +class FileStreamWriter : public FileStreamBase +{ + Q_OBJECT + +public: + ~FileStreamWriter() { // TODO: should d'tor remove unfinished file write leftovers? + if (m_writeBuffer && isBuffered()) + m_writeBuffer->cancel(); + } + + void setWriteData(const QByteArray &writeData) { + QTC_ASSERT(!m_taskTree, return); + m_writeData = writeData; + } + void write(const QByteArray &newData) { + QTC_ASSERT(m_taskTree, return); + QTC_ASSERT(m_writeData.isEmpty(), return); + QTC_ASSERT(m_writeBuffer, return); + m_writeBuffer->write(newData); + } + void closeWriteChannel() { + QTC_ASSERT(m_taskTree, return); + QTC_ASSERT(m_writeData.isEmpty(), return); + QTC_ASSERT(m_writeBuffer, return); + m_writeBuffer->closeWriteChannel(); + } + +signals: + void started(); + +private: + TaskItem remoteTask() final { + const auto setup = [this](QtcProcess &process) { + m_writeBuffer = new WriteBuffer(false, &process); + connect(m_writeBuffer, &WriteBuffer::writeRequested, &process, &QtcProcess::writeRaw); + connect(m_writeBuffer, &WriteBuffer::closeWriteChannelRequested, + &process, &QtcProcess::closeWriteChannel); + const QStringList args = {"of=" + m_filePath.path()}; + const FilePath dd = m_filePath.withNewPath("dd"); + process.setCommand({dd, args, OsType::OsTypeLinux}); + if (isBuffered()) + process.setProcessMode(ProcessMode::Writer); + else + process.setWriteData(m_writeData); + connect(&process, &QtcProcess::started, this, [this] { emit started(); }); + }; + const auto finalize = [this](const QtcProcess &) { + delete m_writeBuffer; + m_writeBuffer = nullptr; + }; + return Process(setup, finalize, finalize); + } + TaskItem localTask() final { + const auto setup = [this](AsyncTask &async) { + m_writeBuffer = new WriteBuffer(isBuffered(), &async); + async.setConcurrentCallData(localWrite, m_filePath, m_writeData, m_writeBuffer); + emit started(); + }; + const auto finalize = [this](const AsyncTask &) { + delete m_writeBuffer; + m_writeBuffer = nullptr; + }; + return Async(setup, finalize, finalize); + } + + bool isBuffered() const { return m_writeData.isEmpty(); } + QByteArray m_writeData; + WriteBuffer *m_writeBuffer = nullptr; +}; + +class FileStreamReaderAdapter : public Utils::Tasking::TaskAdapter +{ +public: + FileStreamReaderAdapter() { connect(task(), &FileStreamBase::done, this, &TaskInterface::done); } + void start() override { task()->start(); } +}; + +class FileStreamWriterAdapter : public Utils::Tasking::TaskAdapter +{ +public: + FileStreamWriterAdapter() { connect(task(), &FileStreamBase::done, this, &TaskInterface::done); } + void start() override { task()->start(); } +}; + +} // namespace Utils + +QTC_DECLARE_CUSTOM_TASK(Reader, Utils::FileStreamReaderAdapter); +QTC_DECLARE_CUSTOM_TASK(Writer, Utils::FileStreamWriterAdapter); + +namespace Utils { + +static Group interDeviceTransfer(const FilePath &source, const FilePath &destination) +{ + struct TransferStorage { QPointer writer; }; + Condition condition; + TreeStorage storage; + + const auto setupReader = [=](FileStreamReader &reader) { + reader.setFilePath(source); + QTC_CHECK(storage->writer != nullptr); + QObject::connect(&reader, &FileStreamReader::readyRead, + storage->writer, &FileStreamWriter::write); + }; + const auto finalizeReader = [=](const FileStreamReader &) { + QTC_CHECK(storage->writer != nullptr); + storage->writer->closeWriteChannel(); + }; + const auto setupWriter = [=](FileStreamWriter &writer) { + writer.setFilePath(destination); + ConditionActivator *activator = condition.activator(); + QObject::connect(&writer, &FileStreamWriter::started, + &writer, [activator] { activator->activate(); }); + QTC_CHECK(storage->writer == nullptr); + storage->writer = &writer; + }; + + const Group root { + parallel, + Storage(storage), + Writer(setupWriter), + Group { + WaitFor(condition), + Reader(setupReader, finalizeReader, finalizeReader) + } + }; + + return root; +} + +static void transfer(QPromise &promise, const FilePath &source, const FilePath &destination) +{ + if (promise.isCanceled()) + return; + + std::unique_ptr taskTree(new TaskTree(interDeviceTransfer(source, destination))); + + QEventLoop eventLoop; + bool finalized = false; + const auto finalize = [loop = &eventLoop, &taskTree, &finalized](int exitCode) { + if (finalized) // finalize only once + return; + finalized = true; + // Give the tree a chance to delete later all tasks that have finished and caused + // emission of tree's done or errorOccurred signal. + // TODO: maybe these signals should be sent queued already? + QMetaObject::invokeMethod(loop, [loop, &taskTree, exitCode] { + taskTree.reset(); + loop->exit(exitCode); + }, Qt::QueuedConnection); + }; + QTimer timer; + timer.setInterval(50); + QObject::connect(&timer, &QTimer::timeout, [&promise, finalize] { + if (promise.isCanceled()) + finalize(2); + }); + QObject::connect(taskTree.get(), &TaskTree::done, &eventLoop, [=] { finalize(0); }); + QObject::connect(taskTree.get(), &TaskTree::errorOccurred, &eventLoop, [=] { finalize(1); }); + taskTree->start(); + timer.start(); + if (eventLoop.exec()) + promise.future().cancel(); +} + +class FileStreamerPrivate : public QObject +{ +public: + StreamMode m_streamerMode = StreamMode::Transfer; + FilePath m_source; + FilePath m_destination; + QByteArray m_readBuffer; + QByteArray m_writeBuffer; + StreamResult m_streamResult = StreamResult::FinishedWithError; + std::unique_ptr m_taskTree; + + TaskItem task() { + if (m_streamerMode == StreamMode::Reader) + return readerTask(); + if (m_streamerMode == StreamMode::Writer) + return writerTask(); + return transferTask(); + } + +private: + TaskItem readerTask() { + const auto setup = [this](FileStreamReader &reader) { + m_readBuffer.clear(); + reader.setFilePath(m_source); + connect(&reader, &FileStreamReader::readyRead, this, [this](const QByteArray &data) { + m_readBuffer += data; + }); + }; + return Reader(setup); + } + TaskItem writerTask() { + const auto setup = [this](FileStreamWriter &writer) { + writer.setFilePath(m_destination); + writer.setWriteData(m_writeBuffer); + }; + return Writer(setup); + } + TaskItem transferTask() { + const auto setup = [this](AsyncTask &async) { + async.setConcurrentCallData(transfer, m_source, m_destination); + }; + return Async(setup); + } +}; + +FileStreamer::FileStreamer(QObject *parent) + : QObject(parent) + , d(new FileStreamerPrivate) +{ +} + +FileStreamer::~FileStreamer() +{ + delete d; +} + +void FileStreamer::setSource(const FilePath &source) +{ + d->m_source = source; +} + +void FileStreamer::setDestination(const FilePath &destination) +{ + d->m_destination = destination; +} + +void FileStreamer::setStreamMode(StreamMode mode) +{ + d->m_streamerMode = mode; +} + +QByteArray FileStreamer::readData() const +{ + return d->m_readBuffer; +} + +void FileStreamer::setWriteData(const QByteArray &writeData) +{ + d->m_writeBuffer = writeData; +} + +StreamResult FileStreamer::result() const +{ + return d->m_streamResult; +} + +void FileStreamer::start() +{ + // TODO: Preliminary check if local source exists? + QTC_ASSERT(!d->m_taskTree, return); + d->m_taskTree.reset(new TaskTree({d->task()})); + const auto finalize = [this](bool success) { + d->m_streamResult = success ? StreamResult::FinishedWithSuccess + : StreamResult::FinishedWithError; + d->m_taskTree.release()->deleteLater(); + emit done(); + }; + connect(d->m_taskTree.get(), &TaskTree::done, this, [=] { finalize(true); }); + connect(d->m_taskTree.get(), &TaskTree::errorOccurred, this, [=] { finalize(false); }); + d->m_taskTree->start(); +} + +void FileStreamer::stop() +{ + d->m_taskTree.reset(); +} + +} // namespace Utils + +#include "filestreamer.moc" diff --git a/src/libs/utils/filestreamer.h b/src/libs/utils/filestreamer.h new file mode 100644 index 0000000000..b572e910a2 --- /dev/null +++ b/src/libs/utils/filestreamer.h @@ -0,0 +1,62 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "utils_global.h" + +#include "filepath.h" +#include "tasktree.h" + +#include + +QT_BEGIN_NAMESPACE +class QByteArray; +QT_END_NAMESPACE + +namespace Utils { + +enum class StreamMode { Reader, Writer, Transfer }; + +enum class StreamResult { FinishedWithSuccess, FinishedWithError }; + +class QTCREATOR_UTILS_EXPORT FileStreamer final : public QObject +{ + Q_OBJECT + +public: + FileStreamer(QObject *parent = nullptr); + ~FileStreamer(); + + void setSource(const FilePath &source); + void setDestination(const FilePath &destination); + void setStreamMode(StreamMode mode); // Transfer by default + + // Only for Reader mode + QByteArray readData() const; + // Only for Writer mode + void setWriteData(const QByteArray &writeData); + + StreamResult result() const; + + void start(); + void stop(); + +signals: + void done(); + +private: + class FileStreamerPrivate *d = nullptr; +}; + +class FileStreamerAdapter : public Utils::Tasking::TaskAdapter +{ +public: + FileStreamerAdapter() { connect(task(), &FileStreamer::done, this, + [this] { emit done(task()->result() == StreamResult::FinishedWithSuccess); }); } + void start() override { task()->start(); } +}; + +} // namespace Utils + +QTC_DECLARE_CUSTOM_TASK(Streamer, Utils::FileStreamerAdapter); diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index cf952ae62d..8c0312ed03 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -127,6 +127,8 @@ Project { "filepath.h", "filesearch.cpp", "filesearch.h", + "filestreamer.cpp", + "filestreamer.h", "filesystemmodel.cpp", "filesystemmodel.h", "filesystemwatcher.cpp", -- cgit v1.2.3 From c1b1842c48632970cd0ddce79a80801afcae2043 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Mon, 27 Feb 2023 15:54:14 +0100 Subject: Utils: Introduce FileStreamerManager To be used for FilePath::async[Copy/Read/Write]() methods. Change-Id: Ie34e600f8d65eae10b41893e15685afe19ce2a46 Reviewed-by: hjk --- src/libs/utils/CMakeLists.txt | 1 + src/libs/utils/filestreamermanager.cpp | 200 +++++++++++++++++++++++++++++++++ src/libs/utils/filestreamermanager.h | 46 ++++++++ src/libs/utils/utils.qbs | 2 + 4 files changed, 249 insertions(+) create mode 100644 src/libs/utils/filestreamermanager.cpp create mode 100644 src/libs/utils/filestreamermanager.h (limited to 'src/libs') diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index e8adad413e..e296f2857a 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -56,6 +56,7 @@ add_qtc_library(Utils filepathinfo.h filesearch.cpp filesearch.h filestreamer.cpp filestreamer.h + filestreamermanager.cpp filestreamermanager.h filesystemmodel.cpp filesystemmodel.h filesystemwatcher.cpp filesystemwatcher.h fileutils.cpp fileutils.h diff --git a/src/libs/utils/filestreamermanager.cpp b/src/libs/utils/filestreamermanager.cpp new file mode 100644 index 0000000000..5a1ec9847e --- /dev/null +++ b/src/libs/utils/filestreamermanager.cpp @@ -0,0 +1,200 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "filestreamermanager.h" + +#include "filestreamer.h" +#include "threadutils.h" +#include "utilstr.h" + +#include +#include +#include +#include + +#include + +namespace Utils { + +// TODO: destruct the instance before destructing ProjectExplorer::DeviceManager (?) + +static FileStreamHandle generateUniqueHandle() +{ + static std::atomic_int handleCounter = 1; + return FileStreamHandle(handleCounter.fetch_add(1)); +} + +static QMutex s_mutex = {}; +static QWaitCondition s_waitCondition = {}; +static std::unordered_map s_fileStreamers = {}; + +static void addStreamer(FileStreamHandle handle, FileStreamer *streamer) +{ + QMutexLocker locker(&s_mutex); + const bool added = s_fileStreamers.try_emplace(handle, streamer).second; + QTC_CHECK(added); +} + +static void removeStreamer(FileStreamHandle handle) +{ + QMutexLocker locker(&s_mutex); + auto it = s_fileStreamers.find(handle); + QTC_ASSERT(it != s_fileStreamers.end(), return); + QTC_ASSERT(QThread::currentThread() == it->second->thread(), return); + s_fileStreamers.erase(it); + s_waitCondition.wakeAll(); +} + +static void deleteStreamer(FileStreamHandle handle) +{ + QMutexLocker locker(&s_mutex); + auto it = s_fileStreamers.find(handle); + if (it != s_fileStreamers.end()) + return; + if (QThread::currentThread() == it->second->thread()) { + delete it->second; + s_fileStreamers.erase(it); + s_waitCondition.wakeAll(); + } else { + QMetaObject::invokeMethod(it->second, [handle] { + deleteStreamer(handle); + }); + s_waitCondition.wait(&s_mutex); + QTC_CHECK(s_fileStreamers.find(handle) == s_fileStreamers.end()); + } +} + +static void deleteAllStreamers() +{ + QMutexLocker locker(&s_mutex); + QTC_ASSERT(Utils::isMainThread(), return); + while (s_fileStreamers.size()) { + auto it = s_fileStreamers.begin(); + if (QThread::currentThread() == it->second->thread()) { + delete it->second; + s_fileStreamers.erase(it); + s_waitCondition.wakeAll(); + } else { + const FileStreamHandle handle = it->first; + QMetaObject::invokeMethod(it->second, [handle] { + deleteStreamer(handle); + }); + s_waitCondition.wait(&s_mutex); + QTC_CHECK(s_fileStreamers.find(handle) == s_fileStreamers.end()); + } + } +} + +static FileStreamHandle checkHandle(FileStreamHandle handle) +{ + QMutexLocker locker(&s_mutex); + return s_fileStreamers.find(handle) != s_fileStreamers.end() ? handle : FileStreamHandle(0); +} + +FileStreamHandle execute(const std::function &onSetup, + const std::function &onDone, + QObject *context) +{ + FileStreamer *streamer = new FileStreamer; + onSetup(streamer); + const FileStreamHandle handle = generateUniqueHandle(); + QTC_CHECK(context == nullptr || context->thread() == QThread::currentThread()); + QObject *finalContext = context ? context : streamer; + QObject::connect(streamer, &FileStreamer::done, finalContext, [=] { + if (onDone) + onDone(streamer); + removeStreamer(handle); + streamer->deleteLater(); + }); + addStreamer(handle, streamer); + streamer->start(); + return checkHandle(handle); // The handle could have been already removed +} + +FileStreamHandle FileStreamerManager::copy(const FilePath &source, const FilePath &destination, + const CopyContinuation &cont) +{ + return copy(source, destination, nullptr, cont); +} + +FileStreamHandle FileStreamerManager::copy(const FilePath &source, const FilePath &destination, + QObject *context, const CopyContinuation &cont) +{ + const auto onSetup = [=](FileStreamer *streamer) { + streamer->setSource(source); + streamer->setDestination(destination); + }; + if (!cont) + return execute(onSetup, {}, context); + + const auto onDone = [=](FileStreamer *streamer) { + if (streamer->result() == StreamResult::FinishedWithSuccess) + cont({}); + else + cont(make_unexpected(Tr::tr("Failed copying file"))); + }; + return execute(onSetup, onDone, context); +} + +FileStreamHandle FileStreamerManager::read(const FilePath &source, const ReadContinuation &cont) +{ + return read(source, nullptr, cont); +} + +FileStreamHandle FileStreamerManager::read(const FilePath &source, QObject *context, + const ReadContinuation &cont) +{ + const auto onSetup = [=](FileStreamer *streamer) { + streamer->setStreamMode(StreamMode::Reader); + streamer->setSource(source); + }; + if (!cont) + return execute(onSetup, {}, context); + + const auto onDone = [=](FileStreamer *streamer) { + if (streamer->result() == StreamResult::FinishedWithSuccess) + cont(streamer->readData()); + else + cont(make_unexpected(Tr::tr("Failed reading file"))); + }; + return execute(onSetup, onDone, context); +} + +FileStreamHandle FileStreamerManager::write(const FilePath &destination, const QByteArray &data, + const WriteContinuation &cont) +{ + return write(destination, data, nullptr, cont); +} + +FileStreamHandle FileStreamerManager::write(const FilePath &destination, const QByteArray &data, + QObject *context, const WriteContinuation &cont) +{ + const auto onSetup = [=](FileStreamer *streamer) { + streamer->setStreamMode(StreamMode::Writer); + streamer->setDestination(destination); + streamer->setWriteData(data); + }; + if (!cont) + return execute(onSetup, {}, context); + + const auto onDone = [=](FileStreamer *streamer) { + if (streamer->result() == StreamResult::FinishedWithSuccess) + cont(0); // TODO: return write count? + else + cont(make_unexpected(Tr::tr("Failed writing file"))); + }; + return execute(onSetup, onDone, context); +} + +void FileStreamerManager::stop(FileStreamHandle handle) +{ + deleteStreamer(handle); +} + +void FileStreamerManager::stopAll() +{ + deleteAllStreamers(); +} + +} // namespace Utils + diff --git a/src/libs/utils/filestreamermanager.h b/src/libs/utils/filestreamermanager.h new file mode 100644 index 0000000000..261d58ba29 --- /dev/null +++ b/src/libs/utils/filestreamermanager.h @@ -0,0 +1,46 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "utils_global.h" + +#include "filepath.h" + +#include + +QT_BEGIN_NAMESPACE +class QByteArray; +QT_END_NAMESPACE + +namespace Utils { + +enum FileStreamHandle : int {}; + +class QTCREATOR_UTILS_EXPORT FileStreamerManager +{ +public: + using CopyContinuation = Continuation &>; + using ReadContinuation = Continuation &>; + using WriteContinuation = Continuation &>; + + static FileStreamHandle copy(const FilePath &source, const FilePath &destination, + const CopyContinuation &cont); + static FileStreamHandle copy(const FilePath &source, const FilePath &destination, + QObject *context, const CopyContinuation &cont); + + static FileStreamHandle read(const FilePath &source, const ReadContinuation &cont = {}); + static FileStreamHandle read(const FilePath &source, QObject *context, + const ReadContinuation &cont = {}); + + static FileStreamHandle write(const FilePath &destination, const QByteArray &data, + const WriteContinuation &cont = {}); + static FileStreamHandle write(const FilePath &destination, const QByteArray &data, + QObject *context, const WriteContinuation &cont = {}); + + // If called from the same thread that started the task, no continuation is going to be called. + static void stop(FileStreamHandle handle); + static void stopAll(); +}; + +} // namespace Utils diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index 8c0312ed03..9e3d1eaeaa 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -129,6 +129,8 @@ Project { "filesearch.h", "filestreamer.cpp", "filestreamer.h", + "filestreamermanager.cpp", + "filestreamermanager.h", "filesystemmodel.cpp", "filesystemmodel.h", "filesystemwatcher.cpp", -- cgit v1.2.3 From 30760747a321485e5e69a057235aae42830d3130 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Thu, 2 Mar 2023 23:44:36 +0100 Subject: FileStreamer: Optimize transfer on the same device Run just "cp" on device instead of transferring the content of the source into local and sending it back again to remote. Change-Id: I703ad1181d77d470ae145691979c34fc75b59a97 Reviewed-by: Qt CI Bot Reviewed-by: hjk --- src/libs/utils/filestreamer.cpp | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/filestreamer.cpp b/src/libs/utils/filestreamer.cpp index 222488a510..47cbffd3c2 100644 --- a/src/libs/utils/filestreamer.cpp +++ b/src/libs/utils/filestreamer.cpp @@ -304,7 +304,21 @@ QTC_DECLARE_CUSTOM_TASK(Writer, Utils::FileStreamWriterAdapter); namespace Utils { -static Group interDeviceTransfer(const FilePath &source, const FilePath &destination) +static Group sameRemoteDeviceTransferTask(const FilePath &source, const FilePath &destination) +{ + QTC_CHECK(source.needsDevice()); + QTC_CHECK(destination.needsDevice()); + QTC_CHECK(source.isSameDevice(destination)); + + const auto setup = [source, destination](QtcProcess &process) { + const QStringList args = {source.path(), destination.path()}; + const FilePath cp = source.withNewPath("cp"); + process.setCommand({cp, args, OsType::OsTypeLinux}); + }; + return {Process(setup)}; +} + +static Group interDeviceTransferTask(const FilePath &source, const FilePath &destination) { struct TransferStorage { QPointer writer; }; Condition condition; @@ -342,12 +356,19 @@ static Group interDeviceTransfer(const FilePath &source, const FilePath &destina return root; } +static Group transferTask(const FilePath &source, const FilePath &destination) +{ + if (source.needsDevice() && destination.needsDevice() && source.isSameDevice(destination)) + return sameRemoteDeviceTransferTask(source, destination); + return interDeviceTransferTask(source, destination); +} + static void transfer(QPromise &promise, const FilePath &source, const FilePath &destination) { if (promise.isCanceled()) return; - std::unique_ptr taskTree(new TaskTree(interDeviceTransfer(source, destination))); + std::unique_ptr taskTree(new TaskTree(transferTask(source, destination))); QEventLoop eventLoop; bool finalized = false; -- cgit v1.2.3 From 47d375bbb419accc1b5af8c588a6fa86a52f1f52 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Tue, 28 Feb 2023 11:09:50 +0100 Subject: CPlusPlus: Support requires clause in parser Change-Id: Ice6a7a287453516a1cfc296e2c9f16160b3ea130 Reviewed-by: Qt CI Bot Reviewed-by: Reviewed-by: Christian Stenger --- src/libs/3rdparty/cplusplus/AST.cpp | 2 ++ src/libs/3rdparty/cplusplus/AST.h | 22 ++++++++++++++++++++++ src/libs/3rdparty/cplusplus/ASTClone.cpp | 11 +++++++++++ src/libs/3rdparty/cplusplus/ASTMatch0.cpp | 7 +++++++ src/libs/3rdparty/cplusplus/ASTMatcher.cpp | 15 +++++++++++++++ src/libs/3rdparty/cplusplus/ASTMatcher.h | 1 + src/libs/3rdparty/cplusplus/ASTVisit.cpp | 8 ++++++++ src/libs/3rdparty/cplusplus/ASTVisitor.h | 2 ++ src/libs/3rdparty/cplusplus/ASTfwd.h | 1 + src/libs/3rdparty/cplusplus/Parser.cpp | 18 ++++++++++++++++++ src/libs/3rdparty/cplusplus/Parser.h | 1 + 11 files changed, 88 insertions(+) (limited to 'src/libs') diff --git a/src/libs/3rdparty/cplusplus/AST.cpp b/src/libs/3rdparty/cplusplus/AST.cpp index 260b358271..2f351ff7be 100644 --- a/src/libs/3rdparty/cplusplus/AST.cpp +++ b/src/libs/3rdparty/cplusplus/AST.cpp @@ -911,6 +911,8 @@ int DeclaratorAST::lastToken() const return candidate; if (equal_token) return equal_token + 1; + if (requiresClause) + return requiresClause->lastToken(); if (post_attribute_list) if (int candidate = post_attribute_list->lastToken()) return candidate; diff --git a/src/libs/3rdparty/cplusplus/AST.h b/src/libs/3rdparty/cplusplus/AST.h index 9288725edd..ff5759ffb2 100644 --- a/src/libs/3rdparty/cplusplus/AST.h +++ b/src/libs/3rdparty/cplusplus/AST.h @@ -340,6 +340,7 @@ public: virtual TypeofSpecifierAST *asTypeofSpecifier() { return nullptr; } virtual UnaryExpressionAST *asUnaryExpression() { return nullptr; } virtual RequiresExpressionAST *asRequiresExpression() { return nullptr; } + virtual RequiresClauseAST *asRequiresClause() { return nullptr; } virtual UsingAST *asUsing() { return nullptr; } virtual UsingDirectiveAST *asUsingDirective() { return nullptr; } virtual WhileStatementAST *asWhileStatement() { return nullptr; } @@ -693,6 +694,7 @@ public: SpecifierListAST *post_attribute_list = nullptr; int equal_token = 0; ExpressionAST *initializer = nullptr; + RequiresClauseAST *requiresClause = nullptr; public: DeclaratorAST *asDeclarator() override { return this; } @@ -2763,6 +2765,7 @@ public: int less_token = 0; DeclarationListAST *template_parameter_list = nullptr; int greater_token = 0; + RequiresClauseAST *requiresClause = nullptr; DeclarationAST *declaration = nullptr; public: // annotations @@ -3020,6 +3023,25 @@ protected: bool match0(AST *, ASTMatcher *) override; }; +class CPLUSPLUS_EXPORT RequiresClauseAST: public AST +{ +public: + int requires_token = 0; + ExpressionAST *constraint = nullptr; + +public: + RequiresClauseAST *asRequiresClause() override { return this; } + + int firstToken() const override { return requires_token; } + int lastToken() const override { return constraint->lastToken(); } + + RequiresClauseAST *clone(MemoryPool *pool) const override; + +protected: + void accept0(ASTVisitor *visitor) override; + bool match0(AST *, ASTMatcher *) override; +}; + class CPLUSPLUS_EXPORT UsingAST: public DeclarationAST { public: diff --git a/src/libs/3rdparty/cplusplus/ASTClone.cpp b/src/libs/3rdparty/cplusplus/ASTClone.cpp index 4e4cda40f9..f4a2f626da 100644 --- a/src/libs/3rdparty/cplusplus/ASTClone.cpp +++ b/src/libs/3rdparty/cplusplus/ASTClone.cpp @@ -1305,6 +1305,8 @@ TemplateDeclarationAST *TemplateDeclarationAST::clone(MemoryPool *pool) const iter; iter = iter->next, ast_iter = &(*ast_iter)->next) *ast_iter = new (pool) DeclarationListAST((iter->value) ? iter->value->clone(pool) : nullptr); ast->greater_token = greater_token; + if (requiresClause) + ast->requiresClause = requiresClause->clone(pool); if (declaration) ast->declaration = declaration->clone(pool); return ast; @@ -1338,6 +1340,15 @@ RequiresExpressionAST *RequiresExpressionAST::clone(MemoryPool *pool) const return ast; } +RequiresClauseAST *RequiresClauseAST::clone(MemoryPool *pool) const +{ + const auto ast = new (pool) RequiresClauseAST; + ast->requires_token = requires_token; + if (constraint) + ast->constraint = constraint->clone(pool); + return ast; +} + ThrowExpressionAST *ThrowExpressionAST::clone(MemoryPool *pool) const { ThrowExpressionAST *ast = new (pool) ThrowExpressionAST; diff --git a/src/libs/3rdparty/cplusplus/ASTMatch0.cpp b/src/libs/3rdparty/cplusplus/ASTMatch0.cpp index afe94aba06..a4bd9935f0 100644 --- a/src/libs/3rdparty/cplusplus/ASTMatch0.cpp +++ b/src/libs/3rdparty/cplusplus/ASTMatch0.cpp @@ -926,6 +926,13 @@ bool RequiresExpressionAST::match0(AST *pattern, ASTMatcher *matcher) return false; } +bool RequiresClauseAST::match0(AST *pattern, ASTMatcher *matcher) +{ + if (const auto other = pattern->asRequiresClause()) + return matcher->match(this, other); + return false; +} + bool ThrowExpressionAST::match0(AST *pattern, ASTMatcher *matcher) { if (ThrowExpressionAST *_other = pattern->asThrowExpression()) diff --git a/src/libs/3rdparty/cplusplus/ASTMatcher.cpp b/src/libs/3rdparty/cplusplus/ASTMatcher.cpp index ccf41dbae2..d39d0717c5 100644 --- a/src/libs/3rdparty/cplusplus/ASTMatcher.cpp +++ b/src/libs/3rdparty/cplusplus/ASTMatcher.cpp @@ -2205,6 +2205,11 @@ bool ASTMatcher::match(TemplateDeclarationAST *node, TemplateDeclarationAST *pat pattern->greater_token = node->greater_token; + if (! pattern->requiresClause) + pattern->requiresClause = node->requiresClause; + else if (! AST::match(node->requiresClause, pattern->requiresClause, this)) + return false; + if (! pattern->declaration) pattern->declaration = node->declaration; else if (! AST::match(node->declaration, pattern->declaration, this)) @@ -2247,6 +2252,16 @@ bool ASTMatcher::match(RequiresExpressionAST *node, RequiresExpressionAST *patte return true; } +bool ASTMatcher::match(RequiresClauseAST *node, RequiresClauseAST *pattern) +{ + pattern->requires_token = node->requires_token; + if (!pattern->constraint) + pattern->constraint = node->constraint; + else if (!AST::match(node->constraint, pattern->constraint, this)) + return false; + return true; +} + bool ASTMatcher::match(ThrowExpressionAST *node, ThrowExpressionAST *pattern) { (void) node; diff --git a/src/libs/3rdparty/cplusplus/ASTMatcher.h b/src/libs/3rdparty/cplusplus/ASTMatcher.h index 214c37a283..c7d762c9e1 100644 --- a/src/libs/3rdparty/cplusplus/ASTMatcher.h +++ b/src/libs/3rdparty/cplusplus/ASTMatcher.h @@ -158,6 +158,7 @@ public: virtual bool match(QualifiedNameAST *node, QualifiedNameAST *pattern); virtual bool match(RangeBasedForStatementAST *node, RangeBasedForStatementAST *pattern); virtual bool match(ReferenceAST *node, ReferenceAST *pattern); + virtual bool match(RequiresClauseAST *node, RequiresClauseAST *pattern); virtual bool match(RequiresExpressionAST *node, RequiresExpressionAST *pattern); virtual bool match(ReturnStatementAST *node, ReturnStatementAST *pattern); virtual bool match(SimpleDeclarationAST *node, SimpleDeclarationAST *pattern); diff --git a/src/libs/3rdparty/cplusplus/ASTVisit.cpp b/src/libs/3rdparty/cplusplus/ASTVisit.cpp index cb05dbde62..cde7842280 100644 --- a/src/libs/3rdparty/cplusplus/ASTVisit.cpp +++ b/src/libs/3rdparty/cplusplus/ASTVisit.cpp @@ -958,6 +958,7 @@ void TemplateDeclarationAST::accept0(ASTVisitor *visitor) { if (visitor->visit(this)) { accept(template_parameter_list, visitor); + accept(requiresClause, visitor); accept(declaration, visitor); } visitor->endVisit(this); @@ -980,6 +981,13 @@ void RequiresExpressionAST::accept0(ASTVisitor *visitor) visitor->endVisit(this); } +void RequiresClauseAST::accept0(ASTVisitor *visitor) +{ + if (visitor->visit(this)) + accept(constraint, visitor); + visitor->endVisit(this); +} + void ThrowExpressionAST::accept0(ASTVisitor *visitor) { if (visitor->visit(this)) { diff --git a/src/libs/3rdparty/cplusplus/ASTVisitor.h b/src/libs/3rdparty/cplusplus/ASTVisitor.h index 455936f984..9f27783693 100644 --- a/src/libs/3rdparty/cplusplus/ASTVisitor.h +++ b/src/libs/3rdparty/cplusplus/ASTVisitor.h @@ -201,6 +201,7 @@ public: virtual bool visit(RangeBasedForStatementAST *) { return true; } virtual bool visit(ReferenceAST *) { return true; } virtual bool visit(RequiresExpressionAST *) { return true; } + virtual bool visit(RequiresClauseAST *) { return true; } virtual bool visit(ReturnStatementAST *) { return true; } virtual bool visit(SimpleDeclarationAST *) { return true; } virtual bool visit(SimpleNameAST *) { return true; } @@ -359,6 +360,7 @@ public: virtual void endVisit(RangeBasedForStatementAST *) {} virtual void endVisit(ReferenceAST *) {} virtual void endVisit(RequiresExpressionAST *) {} + virtual void endVisit(RequiresClauseAST *) {} virtual void endVisit(ReturnStatementAST *) {} virtual void endVisit(SimpleDeclarationAST *) {} virtual void endVisit(SimpleNameAST *) {} diff --git a/src/libs/3rdparty/cplusplus/ASTfwd.h b/src/libs/3rdparty/cplusplus/ASTfwd.h index f0899b2e1c..b5222e2bbe 100644 --- a/src/libs/3rdparty/cplusplus/ASTfwd.h +++ b/src/libs/3rdparty/cplusplus/ASTfwd.h @@ -167,6 +167,7 @@ class QtPropertyDeclarationItemAST; class QualifiedNameAST; class RangeBasedForStatementAST; class ReferenceAST; +class RequiresClauseAST; class RequiresExpressionAST; class ReturnStatementAST; class SimpleDeclarationAST; diff --git a/src/libs/3rdparty/cplusplus/Parser.cpp b/src/libs/3rdparty/cplusplus/Parser.cpp index ed023b15f0..004bee40e7 100644 --- a/src/libs/3rdparty/cplusplus/Parser.cpp +++ b/src/libs/3rdparty/cplusplus/Parser.cpp @@ -1247,6 +1247,8 @@ bool Parser::parseTemplateDeclaration(DeclarationAST *&node) ast->less_token = consumeToken(); if (maybeSplitGreaterGreaterToken() || LA() == T_GREATER || parseTemplateParameterList(ast->template_parameter_list)) match(T_GREATER, &ast->greater_token); + if (!parseRequiresClauseOpt(ast->requiresClause)) + return false; } while (LA()) { @@ -1402,6 +1404,20 @@ bool Parser::parseRequirement() return true; } +bool Parser::parseRequiresClauseOpt(RequiresClauseAST *&node) +{ + if (!_languageFeatures.cxx20Enabled) + return true; + if (LA() != T_REQUIRES) + return true; + const auto ast = new (_pool) RequiresClauseAST; + ast->requires_token = consumeToken(); + if (!parseLogicalOrExpression(ast->constraint)) + return false; + node = ast; + return true; +} + bool Parser::parseRequiresExpression(ExpressionAST *&node) { if (!_languageFeatures.cxx20Enabled) @@ -2999,6 +3015,8 @@ bool Parser::parseInitDeclarator(DeclaratorAST *&node, SpecifierListAST *decl_sp } else if (node->core_declarator && node->core_declarator->asDecompositionDeclarator()) { error(cursor(), "structured binding needs initializer"); return false; + } else if (!parseRequiresClauseOpt(node->requiresClause)) { + return false; } return true; } diff --git a/src/libs/3rdparty/cplusplus/Parser.h b/src/libs/3rdparty/cplusplus/Parser.h index dbbb563097..a50706b5f3 100644 --- a/src/libs/3rdparty/cplusplus/Parser.h +++ b/src/libs/3rdparty/cplusplus/Parser.h @@ -147,6 +147,7 @@ public: bool parsePlaceholderTypeSpecifier(PlaceholderTypeSpecifierAST *&node); bool parseTypeConstraint(TypeConstraintAST *&node); bool parseRequirement(); + bool parseRequiresClauseOpt(RequiresClauseAST *&node); bool parseRequiresExpression(ExpressionAST *&node); bool parseTemplateParameter(DeclarationAST *&node); bool parseTemplateParameterList(DeclarationListAST *&node); -- cgit v1.2.3 From 215f79f5800e6de08c8a5efce54eb4a4b64b9384 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 22 Feb 2023 19:51:22 +0100 Subject: LocatorFilter classes: Use more linkForEditor Limit the usage of ambiguous internalData. Change-Id: Ice67884b9fb2ff303939cd5998c6e80453e82530 Reviewed-by: Reviewed-by: Eike Ziller --- src/libs/utils/link.h | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/link.h b/src/libs/utils/link.h index 6f01b48434..655957e9ac 100644 --- a/src/libs/utils/link.h +++ b/src/libs/utils/link.h @@ -17,7 +17,8 @@ namespace Utils { class QTCREATOR_UTILS_EXPORT Link { public: - Link(const FilePath &filePath = FilePath(), int line = 0, int column = 0) + Link() = default; + Link(const FilePath &filePath, int line = 0, int column = 0) : targetFilePath(filePath) , targetLine(line) , targetColumn(column) @@ -48,8 +49,8 @@ public: int linkTextEnd = -1; FilePath targetFilePath; - int targetLine; - int targetColumn; + int targetLine = 0; + int targetColumn = 0; }; using LinkHandler = std::function; @@ -58,3 +59,12 @@ using Links = QList; } // namespace Utils Q_DECLARE_METATYPE(Utils::Link) + +namespace std { + +template<> struct hash +{ + size_t operator()(const Utils::Link &fn) const { return qHash(fn); } +}; + +} // std -- cgit v1.2.3 From 1d6c1611241e5224aeb7df8cb96343d5b2d09568 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Fri, 3 Mar 2023 14:43:50 +0100 Subject: Utils: Fix missing modeBase Change-Id: I8ce9393ef97b83b9db8cde12cc8653e9072dad65 Reviewed-by: Eike Ziller --- src/libs/utils/devicefileaccess.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/devicefileaccess.cpp b/src/libs/utils/devicefileaccess.cpp index c50f3b145c..cf14faefc4 100644 --- a/src/libs/utils/devicefileaccess.cpp +++ b/src/libs/utils/devicefileaccess.cpp @@ -1170,7 +1170,7 @@ FilePathInfo UnixDeviceFileAccess::filePathInfo(const FilePath &filePath) const const RunResult stat = runInShell({"stat", args, OsType::OsTypeLinux}); return FileUtils::filePathInfoFromTriple(QString::fromLatin1(stat.stdOut), - osType() != OsTypeMac); + osType() == OsTypeMac ? 8 : 16); } // returns whether 'find' could be used. -- cgit v1.2.3 From ae86a6a4bc6ccd93e704d49490791096c1a39085 Mon Sep 17 00:00:00 2001 From: hjk Date: Thu, 2 Mar 2023 13:54:49 +0100 Subject: Utils: Remove some iterator bases accessed to Environment Now unused. Change-Id: I21bce9218662d9cb8acc18e5c2ede6dfbb8962bb Reviewed-by: Christian Kandeler --- src/libs/utils/environment.cpp | 2 +- src/libs/utils/environment.h | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/environment.cpp b/src/libs/utils/environment.cpp index eb49db208d..462eeab31d 100644 --- a/src/libs/utils/environment.cpp +++ b/src/libs/utils/environment.cpp @@ -48,7 +48,7 @@ QProcessEnvironment Environment::toProcessEnvironment() const QProcessEnvironment result; for (auto it = m_dict.m_values.constBegin(); it != m_dict.m_values.constEnd(); ++it) { if (it.value().second) - result.insert(it.key().name, expandedValueForKey(key(it))); + result.insert(it.key().name, expandedValueForKey(m_dict.key(it))); } return result; } diff --git a/src/libs/utils/environment.h b/src/libs/utils/environment.h index 1485dff2a1..541a730965 100644 --- a/src/libs/utils/environment.h +++ b/src/libs/utils/environment.h @@ -76,20 +76,12 @@ public: OsType osType() const { return m_dict.osType(); } - using const_iterator = NameValueMap::const_iterator; // FIXME: avoid NameValueDictionary toDictionary() const { return m_dict; } // FIXME: avoid NameValueItems diff(const Environment &other, bool checkAppendPrepend = false) const; // FIXME: avoid - QString key(const_iterator it) const { return m_dict.key(it); } // FIXME: avoid - QString value(const_iterator it) const { return m_dict.value(it); } // FIXME: avoid - bool isEnabled(const_iterator it) const { return m_dict.isEnabled(it); } // FIXME: avoid - void setCombineWithDeviceEnvironment(bool combine) { m_combineWithDeviceEnvironment = combine; } bool combineWithDeviceEnvironment() const { return m_combineWithDeviceEnvironment; } - const_iterator constBegin() const { return m_dict.constBegin(); } // FIXME: avoid - const_iterator constEnd() const { return m_dict.constEnd(); } // FIXME: avoid - struct Entry { QString key; QString value; bool enabled; }; using FindResult = std::optional; FindResult find(const QString &name) const; // Note res->key may differ in case from name. -- cgit v1.2.3 From cabba52a89d7dab3ea58e8e509aaa40e40425e90 Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Mon, 6 Mar 2023 08:51:18 +0200 Subject: Utils: Fix bad condition on iterator end Detected by Coverity. Amends c1b1842c486. Change-Id: Ie0233aab33317e286722dec7066d11dfc2a11a06 Reviewed-by: Jarek Kobus --- src/libs/utils/filestreamermanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/filestreamermanager.cpp b/src/libs/utils/filestreamermanager.cpp index 5a1ec9847e..051f114578 100644 --- a/src/libs/utils/filestreamermanager.cpp +++ b/src/libs/utils/filestreamermanager.cpp @@ -49,7 +49,7 @@ static void deleteStreamer(FileStreamHandle handle) { QMutexLocker locker(&s_mutex); auto it = s_fileStreamers.find(handle); - if (it != s_fileStreamers.end()) + if (it == s_fileStreamers.end()) return; if (QThread::currentThread() == it->second->thread()) { delete it->second; -- cgit v1.2.3 From 913513ff622fdf423ea97a5f9bf6144e80f57501 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Fri, 3 Mar 2023 15:25:20 +0100 Subject: qbs build: Turn off warnings for vterm library As in the cmake build. Change-Id: I2cbc5d3fcae89053f310675dc0ea55c52f72f646 Reviewed-by: Reviewed-by: Christian Stenger --- src/libs/3rdparty/libvterm/vterm.qbs | 1 + 1 file changed, 1 insertion(+) (limited to 'src/libs') diff --git a/src/libs/3rdparty/libvterm/vterm.qbs b/src/libs/3rdparty/libvterm/vterm.qbs index 274e3a46d6..18ccb638aa 100644 --- a/src/libs/3rdparty/libvterm/vterm.qbs +++ b/src/libs/3rdparty/libvterm/vterm.qbs @@ -5,6 +5,7 @@ Project { Depends { name: "cpp" } cpp.includePaths: base.concat("include") + cpp.warningLevel: "none" Group { prefix: "src/" -- cgit v1.2.3 From 755d9769d81a5dc4e0b837650f9fab27a3f094ab Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Fri, 3 Mar 2023 15:00:21 +0100 Subject: CPlusPlus: Add support for coroutines Also fix some concept-related bugs uncovered by the test case. Change-Id: Ia67c971026bcd85d9cc252f46cd4f56c2865d432 Reviewed-by: Qt CI Bot Reviewed-by: Reviewed-by: Christian Stenger --- src/libs/3rdparty/cplusplus/AST.cpp | 4 ++ src/libs/3rdparty/cplusplus/AST.h | 41 ++++++++++++++++ src/libs/3rdparty/cplusplus/ASTClone.cpp | 22 +++++++++ src/libs/3rdparty/cplusplus/ASTMatch0.cpp | 14 ++++++ src/libs/3rdparty/cplusplus/ASTMatcher.cpp | 25 ++++++++++ src/libs/3rdparty/cplusplus/ASTMatcher.h | 2 + src/libs/3rdparty/cplusplus/ASTVisit.cpp | 15 ++++++ src/libs/3rdparty/cplusplus/ASTVisitor.h | 4 ++ src/libs/3rdparty/cplusplus/ASTfwd.h | 2 + src/libs/3rdparty/cplusplus/Bind.cpp | 5 ++ src/libs/3rdparty/cplusplus/Bind.h | 1 + src/libs/3rdparty/cplusplus/Parser.cpp | 77 +++++++++++++++++++++++------- src/libs/3rdparty/cplusplus/Parser.h | 2 + 13 files changed, 196 insertions(+), 18 deletions(-) (limited to 'src/libs') diff --git a/src/libs/3rdparty/cplusplus/AST.cpp b/src/libs/3rdparty/cplusplus/AST.cpp index 2f351ff7be..53c84a95c6 100644 --- a/src/libs/3rdparty/cplusplus/AST.cpp +++ b/src/libs/3rdparty/cplusplus/AST.cpp @@ -3763,6 +3763,8 @@ int TemplateTypeParameterAST::firstToken() const { if (template_token) return template_token; + if (typeConstraint) + return typeConstraint->firstToken(); if (less_token) return less_token; if (template_parameter_list) @@ -3807,6 +3809,8 @@ int TemplateTypeParameterAST::lastToken() const return candidate; if (less_token) return less_token + 1; + if (typeConstraint) + return typeConstraint->lastToken(); if (template_token) return template_token + 1; return 1; diff --git a/src/libs/3rdparty/cplusplus/AST.h b/src/libs/3rdparty/cplusplus/AST.h index ff5759ffb2..0d7e337fd8 100644 --- a/src/libs/3rdparty/cplusplus/AST.h +++ b/src/libs/3rdparty/cplusplus/AST.h @@ -184,6 +184,7 @@ public: virtual ArrayInitializerAST *asArrayInitializer() { return nullptr; } virtual AsmDefinitionAST *asAsmDefinition() { return nullptr; } virtual AttributeSpecifierAST *asAttributeSpecifier() { return nullptr; } + virtual AwaitExpressionAST *asAwaitExpression() { return nullptr; } virtual BaseSpecifierAST *asBaseSpecifier() { return nullptr; } virtual BinaryExpressionAST *asBinaryExpression() { return nullptr; } virtual BoolLiteralAST *asBoolLiteral() { return nullptr; } @@ -344,6 +345,7 @@ public: virtual UsingAST *asUsing() { return nullptr; } virtual UsingDirectiveAST *asUsingDirective() { return nullptr; } virtual WhileStatementAST *asWhileStatement() { return nullptr; } + virtual YieldExpressionAST *asYieldExpression() { return nullptr; } protected: virtual void accept0(ASTVisitor *visitor) = 0; @@ -2826,6 +2828,44 @@ protected: bool match0(AST *, ASTMatcher *) override; }; +class CPLUSPLUS_EXPORT YieldExpressionAST: public ExpressionAST +{ +public: + int yield_token = 0; + ExpressionAST *expression = nullptr; + +public: + YieldExpressionAST *asYieldExpression() override { return this; } + + int firstToken() const override { return yield_token; } + int lastToken() const override { return expression->lastToken(); } + + YieldExpressionAST *clone(MemoryPool *pool) const override; + +protected: + void accept0(ASTVisitor *visitor) override; + bool match0(AST *, ASTMatcher *) override; +}; + +class CPLUSPLUS_EXPORT AwaitExpressionAST: public ExpressionAST +{ +public: + int await_token = 0; + ExpressionAST *castExpression = nullptr; + +public: + AwaitExpressionAST *asAwaitExpression() override { return this; } + + int firstToken() const override { return await_token; } + int lastToken() const override { return castExpression->lastToken(); } + + AwaitExpressionAST *clone(MemoryPool *pool) const override; + +protected: + void accept0(ASTVisitor *visitor) override; + bool match0(AST *, ASTMatcher *) override; +}; + class CPLUSPLUS_EXPORT NoExceptOperatorExpressionAST: public ExpressionAST { public: @@ -2956,6 +2996,7 @@ class CPLUSPLUS_EXPORT TemplateTypeParameterAST: public DeclarationAST { public: int template_token = 0; + TypeConstraintAST *typeConstraint = nullptr; int less_token = 0; DeclarationListAST *template_parameter_list = nullptr; int greater_token = 0; diff --git a/src/libs/3rdparty/cplusplus/ASTClone.cpp b/src/libs/3rdparty/cplusplus/ASTClone.cpp index f4a2f626da..e1e014afcc 100644 --- a/src/libs/3rdparty/cplusplus/ASTClone.cpp +++ b/src/libs/3rdparty/cplusplus/ASTClone.cpp @@ -147,6 +147,8 @@ TypeConstraintAST *TypeConstraintAST::clone(MemoryPool *pool) const for (NestedNameSpecifierListAST *iter = nestedName, **ast_iter = &ast->nestedName; iter; iter = iter->next, ast_iter = &(*ast_iter)->next) *ast_iter = new (pool) NestedNameSpecifierListAST((iter->value) ? iter->value->clone(pool) : nullptr); + if (conceptName) + ast->conceptName = conceptName->clone(pool); ast->lessToken = lessToken; for (ExpressionListAST *iter = templateArgs, **ast_iter = &ast->templateArgs; iter; iter = iter->next, ast_iter = &(*ast_iter)->next) @@ -1358,6 +1360,24 @@ ThrowExpressionAST *ThrowExpressionAST::clone(MemoryPool *pool) const return ast; } +YieldExpressionAST *YieldExpressionAST::clone(MemoryPool *pool) const +{ + const auto ast = new (pool) YieldExpressionAST; + ast->yield_token = yield_token; + if (expression) + ast->expression = expression->clone(pool); + return ast; +} + +AwaitExpressionAST *AwaitExpressionAST::clone(MemoryPool *pool) const +{ + const auto ast = new (pool) AwaitExpressionAST; + ast->await_token = await_token; + if (castExpression) + ast->castExpression = castExpression->clone(pool); + return ast; +} + NoExceptOperatorExpressionAST *NoExceptOperatorExpressionAST::clone(MemoryPool *pool) const { NoExceptOperatorExpressionAST *ast = new (pool) NoExceptOperatorExpressionAST; @@ -1429,6 +1449,8 @@ TemplateTypeParameterAST *TemplateTypeParameterAST::clone(MemoryPool *pool) cons { TemplateTypeParameterAST *ast = new (pool) TemplateTypeParameterAST; ast->template_token = template_token; + if (typeConstraint) + ast->typeConstraint = typeConstraint->clone(pool); ast->less_token = less_token; for (DeclarationListAST *iter = template_parameter_list, **ast_iter = &ast->template_parameter_list; iter; iter = iter->next, ast_iter = &(*ast_iter)->next) diff --git a/src/libs/3rdparty/cplusplus/ASTMatch0.cpp b/src/libs/3rdparty/cplusplus/ASTMatch0.cpp index a4bd9935f0..b58bd59efe 100644 --- a/src/libs/3rdparty/cplusplus/ASTMatch0.cpp +++ b/src/libs/3rdparty/cplusplus/ASTMatch0.cpp @@ -941,6 +941,20 @@ bool ThrowExpressionAST::match0(AST *pattern, ASTMatcher *matcher) return false; } +bool YieldExpressionAST::match0(AST *pattern, ASTMatcher *matcher) +{ + if (const auto other = pattern->asYieldExpression()) + return matcher->match(this, other); + return false; +} + +bool AwaitExpressionAST::match0(AST *pattern, ASTMatcher *matcher) +{ + if (const auto other = pattern->asAwaitExpression()) + return matcher->match(this, other); + return false; +} + bool NoExceptOperatorExpressionAST::match0(AST *pattern, ASTMatcher *matcher) { if (NoExceptOperatorExpressionAST *_other = pattern->asNoExceptOperatorExpression()) diff --git a/src/libs/3rdparty/cplusplus/ASTMatcher.cpp b/src/libs/3rdparty/cplusplus/ASTMatcher.cpp index d39d0717c5..4567cd68a2 100644 --- a/src/libs/3rdparty/cplusplus/ASTMatcher.cpp +++ b/src/libs/3rdparty/cplusplus/ASTMatcher.cpp @@ -2277,6 +2277,26 @@ bool ASTMatcher::match(ThrowExpressionAST *node, ThrowExpressionAST *pattern) return true; } +bool ASTMatcher::match(YieldExpressionAST *node, YieldExpressionAST *pattern) +{ + pattern->yield_token = node->yield_token; + if (!pattern->expression) + pattern->expression = node->expression; + else if (!AST::match(node->expression, pattern->expression, this)) + return false; + return true; +} + +bool ASTMatcher::match(AwaitExpressionAST *node, AwaitExpressionAST *pattern) +{ + pattern->await_token = node->await_token; + if (!pattern->castExpression) + pattern->castExpression = node->castExpression; + else if (!AST::match(node->castExpression, pattern->castExpression, this)) + return false; + return true; +} + bool ASTMatcher::match(NoExceptOperatorExpressionAST *node, NoExceptOperatorExpressionAST *pattern) { (void) node; @@ -2398,6 +2418,11 @@ bool ASTMatcher::match(TemplateTypeParameterAST *node, TemplateTypeParameterAST pattern->template_token = node->template_token; + if (!pattern->typeConstraint) + pattern->typeConstraint = node->typeConstraint; + else if (!AST::match(node->typeConstraint, pattern->typeConstraint, this)) + return false; + pattern->less_token = node->less_token; if (! pattern->template_parameter_list) diff --git a/src/libs/3rdparty/cplusplus/ASTMatcher.h b/src/libs/3rdparty/cplusplus/ASTMatcher.h index c7d762c9e1..fb6109a07d 100644 --- a/src/libs/3rdparty/cplusplus/ASTMatcher.h +++ b/src/libs/3rdparty/cplusplus/ASTMatcher.h @@ -38,6 +38,7 @@ public: virtual bool match(ArrayAccessAST *node, ArrayAccessAST *pattern); virtual bool match(ArrayDeclaratorAST *node, ArrayDeclaratorAST *pattern); virtual bool match(ArrayInitializerAST *node, ArrayInitializerAST *pattern); + virtual bool match(AwaitExpressionAST *node, AwaitExpressionAST *pattern); virtual bool match(AsmDefinitionAST *node, AsmDefinitionAST *pattern); virtual bool match(BaseSpecifierAST *node, BaseSpecifierAST *pattern); virtual bool match(BinaryExpressionAST *node, BinaryExpressionAST *pattern); @@ -188,6 +189,7 @@ public: virtual bool match(UsingAST *node, UsingAST *pattern); virtual bool match(UsingDirectiveAST *node, UsingDirectiveAST *pattern); virtual bool match(WhileStatementAST *node, WhileStatementAST *pattern); + virtual bool match(YieldExpressionAST *node, YieldExpressionAST *pattern); }; } // namespace CPlusPlus diff --git a/src/libs/3rdparty/cplusplus/ASTVisit.cpp b/src/libs/3rdparty/cplusplus/ASTVisit.cpp index cde7842280..d83684b966 100644 --- a/src/libs/3rdparty/cplusplus/ASTVisit.cpp +++ b/src/libs/3rdparty/cplusplus/ASTVisit.cpp @@ -996,6 +996,20 @@ void ThrowExpressionAST::accept0(ASTVisitor *visitor) visitor->endVisit(this); } +void YieldExpressionAST::accept0(ASTVisitor *visitor) +{ + if (visitor->visit(this)) + accept(expression, visitor); + visitor->endVisit(this); +} + +void AwaitExpressionAST::accept0(ASTVisitor *visitor) +{ + if (visitor->visit(this)) + accept(castExpression, visitor); + visitor->endVisit(this); +} + void NoExceptOperatorExpressionAST::accept0(ASTVisitor *visitor) { if (visitor->visit(this)) { @@ -1051,6 +1065,7 @@ void TypenameTypeParameterAST::accept0(ASTVisitor *visitor) void TemplateTypeParameterAST::accept0(ASTVisitor *visitor) { if (visitor->visit(this)) { + accept(typeConstraint, visitor); accept(template_parameter_list, visitor); accept(name, visitor); accept(type_id, visitor); diff --git a/src/libs/3rdparty/cplusplus/ASTVisitor.h b/src/libs/3rdparty/cplusplus/ASTVisitor.h index 9f27783693..9d9fec75b1 100644 --- a/src/libs/3rdparty/cplusplus/ASTVisitor.h +++ b/src/libs/3rdparty/cplusplus/ASTVisitor.h @@ -81,6 +81,7 @@ public: virtual bool visit(ArrayDeclaratorAST *) { return true; } virtual bool visit(ArrayInitializerAST *) { return true; } virtual bool visit(AsmDefinitionAST *) { return true; } + virtual bool visit(AwaitExpressionAST *) { return true; } virtual bool visit(BaseSpecifierAST *) { return true; } virtual bool visit(BinaryExpressionAST *) { return true; } virtual bool visit(BoolLiteralAST *) { return true; } @@ -230,6 +231,7 @@ public: virtual bool visit(UsingAST *) { return true; } virtual bool visit(UsingDirectiveAST *) { return true; } virtual bool visit(WhileStatementAST *) { return true; } + virtual bool visit(YieldExpressionAST *) { return true; } virtual void endVisit(AccessDeclarationAST *) {} virtual void endVisit(AliasDeclarationAST *) {} @@ -240,6 +242,7 @@ public: virtual void endVisit(ArrayDeclaratorAST *) {} virtual void endVisit(ArrayInitializerAST *) {} virtual void endVisit(AsmDefinitionAST *) {} + virtual void endVisit(AwaitExpressionAST *) {} virtual void endVisit(BaseSpecifierAST *) {} virtual void endVisit(BinaryExpressionAST *) {} virtual void endVisit(BoolLiteralAST *) {} @@ -389,6 +392,7 @@ public: virtual void endVisit(UsingAST *) {} virtual void endVisit(UsingDirectiveAST *) {} virtual void endVisit(WhileStatementAST *) {} + virtual void endVisit(YieldExpressionAST *) {} private: TranslationUnit *_translationUnit; diff --git a/src/libs/3rdparty/cplusplus/ASTfwd.h b/src/libs/3rdparty/cplusplus/ASTfwd.h index b5222e2bbe..102fda75fb 100644 --- a/src/libs/3rdparty/cplusplus/ASTfwd.h +++ b/src/libs/3rdparty/cplusplus/ASTfwd.h @@ -40,6 +40,7 @@ class ArrayDeclaratorAST; class ArrayInitializerAST; class AsmDefinitionAST; class AttributeSpecifierAST; +class AwaitExpressionAST; class BaseSpecifierAST; class BinaryExpressionAST; class BoolLiteralAST; @@ -200,6 +201,7 @@ class UnaryExpressionAST; class UsingAST; class UsingDirectiveAST; class WhileStatementAST; +class YieldExpressionAST; typedef List ExpressionListAST; typedef List DeclarationListAST; diff --git a/src/libs/3rdparty/cplusplus/Bind.cpp b/src/libs/3rdparty/cplusplus/Bind.cpp index 6293d732e0..202c36a51f 100644 --- a/src/libs/3rdparty/cplusplus/Bind.cpp +++ b/src/libs/3rdparty/cplusplus/Bind.cpp @@ -2532,6 +2532,11 @@ bool Bind::visit(TemplateTypeParameterAST *ast) return false; } +bool Bind::visit(TypeConstraintAST *) +{ + return false; +} + bool Bind::visit(UsingAST *ast) { int sourceLocation = location(ast->name, ast->firstToken()); diff --git a/src/libs/3rdparty/cplusplus/Bind.h b/src/libs/3rdparty/cplusplus/Bind.h index 4d12beee86..867ed7baaa 100644 --- a/src/libs/3rdparty/cplusplus/Bind.h +++ b/src/libs/3rdparty/cplusplus/Bind.h @@ -230,6 +230,7 @@ protected: bool visit(TemplateDeclarationAST *ast) override; bool visit(TypenameTypeParameterAST *ast) override; bool visit(TemplateTypeParameterAST *ast) override; + bool visit(TypeConstraintAST *ast) override; bool visit(UsingAST *ast) override; bool visit(UsingDirectiveAST *ast) override; bool visit(ObjCClassForwardDeclarationAST *ast) override; diff --git a/src/libs/3rdparty/cplusplus/Parser.cpp b/src/libs/3rdparty/cplusplus/Parser.cpp index 004bee40e7..45c2493fff 100644 --- a/src/libs/3rdparty/cplusplus/Parser.cpp +++ b/src/libs/3rdparty/cplusplus/Parser.cpp @@ -413,6 +413,7 @@ bool Parser::skipUntilStatement() case T_BREAK: case T_CONTINUE: case T_RETURN: + case T_CO_RETURN: case T_GOTO: case T_TRY: case T_CATCH: @@ -1329,16 +1330,20 @@ bool Parser::parsePlaceholderTypeSpecifier(PlaceholderTypeSpecifierAST *&node) bool Parser::parseTypeConstraint(TypeConstraintAST *&node) { + if (!_languageFeatures.cxx20Enabled) + return false; NestedNameSpecifierListAST *nestedName = nullptr; parseNestedNameSpecifierOpt(nestedName, true); NameAST *conceptName = nullptr; - if (!parseUnqualifiedName(conceptName, true)) + if (!parseUnqualifiedName(conceptName, false)) return false; const auto typeConstraint = new (_pool) TypeConstraintAST; typeConstraint->nestedName = nestedName; typeConstraint->conceptName = conceptName; - if (LA() != T_LESS) + if (LA() != T_LESS) { + node = typeConstraint; return true; + } typeConstraint->lessToken = consumeToken(); if (LA() != T_GREATER) { if (!parseTemplateArgumentList(typeConstraint->templateArgs)) @@ -2208,8 +2213,8 @@ bool Parser::parseTypenameTypeParameter(DeclarationAST *&node) bool Parser::parseTemplateTypeParameter(DeclarationAST *&node) { DEBUG_THIS_RULE(); + TemplateTypeParameterAST *ast = new (_pool) TemplateTypeParameterAST; if (LA() == T_TEMPLATE) { - TemplateTypeParameterAST *ast = new (_pool) TemplateTypeParameterAST; ast->template_token = consumeToken(); if (LA() == T_LESS) ast->less_token = consumeToken(); @@ -2218,20 +2223,21 @@ bool Parser::parseTemplateTypeParameter(DeclarationAST *&node) ast->greater_token = consumeToken(); if (LA() == T_CLASS) ast->class_token = consumeToken(); - if (_languageFeatures.cxx11Enabled && LA() == T_DOT_DOT_DOT) - ast->dot_dot_dot_token = consumeToken(); + } else if (!parseTypeConstraint(ast->typeConstraint)) { + return false; + } + if (_languageFeatures.cxx11Enabled && LA() == T_DOT_DOT_DOT) + ast->dot_dot_dot_token = consumeToken(); - // parse optional name - parseName(ast->name); + // parse optional name + parseName(ast->name); - if (LA() == T_EQUAL) { - ast->equal_token = consumeToken(); - parseTypeId(ast->type_id); - } - node = ast; - return true; + if (LA() == T_EQUAL) { + ast->equal_token = consumeToken(); + parseTypeId(ast->type_id); } - return false; + node = ast; + return true; } bool Parser::lookAtTypeParameter() @@ -2267,10 +2273,9 @@ bool Parser::parseTypeParameter(DeclarationAST *&node) if (lookAtTypeParameter()) return parseTypenameTypeParameter(node); - else if (LA() == T_TEMPLATE) + if (LA() == T_TEMPLATE) return parseTemplateTypeParameter(node); - else - return false; + return parseTemplateTypeParameter(node); } bool Parser::parseTypeId(ExpressionAST *&node) @@ -3556,6 +3561,7 @@ bool Parser::parseStatement(StatementAST *&node, bool blockLabeledStatement) return parseGotoStatement(node); case T_RETURN: + case T_CO_RETURN: return parseReturnStatement(node); case T_LBRACE: @@ -3666,7 +3672,7 @@ bool Parser::parseGotoStatement(StatementAST *&node) bool Parser::parseReturnStatement(StatementAST *&node) { DEBUG_THIS_RULE(); - if (LA() == T_RETURN) { + if (LA() == T_RETURN || LA() == T_CO_RETURN) { ReturnStatementAST *ast = new (_pool) ReturnStatementAST; ast->return_token = consumeToken(); if (_languageFeatures.cxx11Enabled && LA() == T_LBRACE) @@ -5637,6 +5643,11 @@ bool Parser::parseUnaryExpression(ExpressionAST *&node) return parseNoExceptOperatorExpression(node); } + case T_CO_AWAIT: + if (!_languageFeatures.cxx20Enabled) + break; + return parseAwaitExpression(node); + default: break; } // switch @@ -5895,6 +5906,8 @@ bool Parser::parseAssignmentExpression(ExpressionAST *&node) DEBUG_THIS_RULE(); if (LA() == T_THROW) return parseThrowExpression(node); + else if (LA() == T_CO_YIELD) + return parseYieldExpression(node); else PARSE_EXPRESSION_WITH_OPERATOR_PRECEDENCE(node, Prec::Assignment) } @@ -6023,6 +6036,34 @@ bool Parser::parseThrowExpression(ExpressionAST *&node) return false; } +bool Parser::parseYieldExpression(ExpressionAST *&node) +{ + DEBUG_THIS_RULE(); + if (LA() != T_CO_YIELD) + return false; + const auto ast = new (_pool) YieldExpressionAST; + ast->yield_token = consumeToken(); + if (parseBracedInitList0x(ast->expression) || parseAssignmentExpression(ast->expression)) { + node = ast; + return true; + } + return false; +} + +bool Parser::parseAwaitExpression(ExpressionAST *&node) +{ + DEBUG_THIS_RULE(); + if (LA() != T_CO_AWAIT) + return false; + const auto ast = new (_pool) AwaitExpressionAST; + ast->await_token = consumeToken(); + if (parseCastExpression(ast->castExpression)) { + node = ast; + return true; + } + return false; +} + bool Parser::parseNoExceptOperatorExpression(ExpressionAST *&node) { DEBUG_THIS_RULE(); diff --git a/src/libs/3rdparty/cplusplus/Parser.h b/src/libs/3rdparty/cplusplus/Parser.h index a50706b5f3..e34a4ff522 100644 --- a/src/libs/3rdparty/cplusplus/Parser.h +++ b/src/libs/3rdparty/cplusplus/Parser.h @@ -152,6 +152,8 @@ public: bool parseTemplateParameter(DeclarationAST *&node); bool parseTemplateParameterList(DeclarationListAST *&node); bool parseThrowExpression(ExpressionAST *&node); + bool parseYieldExpression(ExpressionAST *&node); + bool parseAwaitExpression(ExpressionAST *&node); bool parseNoExceptOperatorExpression(ExpressionAST *&node); bool parseTryBlockStatement(StatementAST *&node, CtorInitializerAST **placeholder); bool parseCatchClause(CatchClauseListAST *&node); -- cgit v1.2.3 From 098c76832afb098c844b77101bc1613c4e4b6ef4 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Fri, 3 Mar 2023 17:18:56 +0100 Subject: Terminal: Rewrite rendering The rendering has been rewritten to use cached GlyphRuns instead of text layouts. The VTerm specific code was moved into TerminalSurface. Change-Id: I10caa3db4ee932414987c9ddae2dcb777dc1f6e7 Reviewed-by: Cristian Adam Reviewed-by: --- src/libs/3rdparty/libvterm/include/vterm.h | 4 +++- src/libs/3rdparty/libvterm/src/pen.c | 6 ++++++ src/libs/3rdparty/libvterm/src/screen.c | 2 +- src/libs/3rdparty/libvterm/src/vterm_internal.h | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) (limited to 'src/libs') diff --git a/src/libs/3rdparty/libvterm/include/vterm.h b/src/libs/3rdparty/libvterm/include/vterm.h index c0f008776b..cb16ff2a04 100644 --- a/src/libs/3rdparty/libvterm/include/vterm.h +++ b/src/libs/3rdparty/libvterm/include/vterm.h @@ -496,7 +496,7 @@ void vterm_state_send_selection(VTermState *state, VTermSelectionMask mask, VTer typedef struct { unsigned int bold : 1; - unsigned int underline : 2; + unsigned int underline : 3; unsigned int italic : 1; unsigned int blink : 1; unsigned int reverse : 1; @@ -514,6 +514,8 @@ enum { VTERM_UNDERLINE_SINGLE, VTERM_UNDERLINE_DOUBLE, VTERM_UNDERLINE_CURLY, + VTERM_UNDERLINE_DOTTED, + VTERM_UNDERLINE_DASHED }; enum { diff --git a/src/libs/3rdparty/libvterm/src/pen.c b/src/libs/3rdparty/libvterm/src/pen.c index 2227a6fcd3..891a45cec7 100644 --- a/src/libs/3rdparty/libvterm/src/pen.c +++ b/src/libs/3rdparty/libvterm/src/pen.c @@ -323,6 +323,12 @@ INTERNAL void vterm_state_setpen(VTermState *state, const long args[], int argco case 3: state->pen.underline = VTERM_UNDERLINE_CURLY; break; + case 4: + state->pen.underline = VTERM_UNDERLINE_DOTTED; + break; + case 5: + state->pen.underline = VTERM_UNDERLINE_DASHED; + break; } } setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); diff --git a/src/libs/3rdparty/libvterm/src/screen.c b/src/libs/3rdparty/libvterm/src/screen.c index 51c7f99e74..9d1028e67a 100644 --- a/src/libs/3rdparty/libvterm/src/screen.c +++ b/src/libs/3rdparty/libvterm/src/screen.c @@ -18,7 +18,7 @@ typedef struct VTermColor fg, bg; unsigned int bold : 1; - unsigned int underline : 2; + unsigned int underline : 3; unsigned int italic : 1; unsigned int blink : 1; unsigned int reverse : 1; diff --git a/src/libs/3rdparty/libvterm/src/vterm_internal.h b/src/libs/3rdparty/libvterm/src/vterm_internal.h index df9495c678..ad61bff8b0 100644 --- a/src/libs/3rdparty/libvterm/src/vterm_internal.h +++ b/src/libs/3rdparty/libvterm/src/vterm_internal.h @@ -41,7 +41,7 @@ struct VTermPen VTermColor fg; VTermColor bg; unsigned int bold:1; - unsigned int underline:2; + unsigned int underline:3; unsigned int italic:1; unsigned int blink:1; unsigned int reverse:1; -- cgit v1.2.3 From 69fa8f3f3ccf5da4a02c44ae1e0a3109e2de422a Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Fri, 3 Mar 2023 01:04:45 +0100 Subject: FilePath: Integrate FileStreamerManager for async i-face Change-Id: I3371471e3c23b86a62b5e4056b343941ab46e191 Reviewed-by: Qt CI Bot Reviewed-by: hjk --- src/libs/utils/devicefileaccess.cpp | 23 ------------------- src/libs/utils/devicefileaccess.h | 14 ------------ src/libs/utils/filepath.cpp | 44 +++++++++++------------------------- src/libs/utils/filepath.h | 15 ++++++------ src/libs/utils/filestreamermanager.h | 6 +---- 5 files changed, 21 insertions(+), 81 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/devicefileaccess.cpp b/src/libs/utils/devicefileaccess.cpp index cf14faefc4..1dd035d916 100644 --- a/src/libs/utils/devicefileaccess.cpp +++ b/src/libs/utils/devicefileaccess.cpp @@ -380,29 +380,6 @@ std::optional DeviceFileAccess::refersToExecutableFile( return {}; } -void DeviceFileAccess::asyncFileContents(const FilePath &filePath, - const Continuation> &cont, - qint64 limit, - qint64 offset) const -{ - cont(fileContents(filePath, limit, offset)); -} - -void DeviceFileAccess::asyncWriteFileContents(const FilePath &filePath, - const Continuation> &cont, - const QByteArray &data, - qint64 offset) const -{ - cont(writeFileContents(filePath, data, offset)); -} - -void DeviceFileAccess::asyncCopyFile(const FilePath &filePath, - const Continuation> &cont, - const FilePath &target) const -{ - cont(copyFile(filePath, target)); -} - expected_str DeviceFileAccess::createTempFile(const FilePath &filePath) { Q_UNUSED(filePath) diff --git a/src/libs/utils/devicefileaccess.h b/src/libs/utils/devicefileaccess.h index 238bab5e77..e3a2acdb8f 100644 --- a/src/libs/utils/devicefileaccess.h +++ b/src/libs/utils/devicefileaccess.h @@ -72,20 +72,6 @@ protected: const QByteArray &data, qint64 offset) const; - virtual void asyncFileContents(const FilePath &filePath, - const Continuation> &cont, - qint64 limit, - qint64 offset) const; - - virtual void asyncWriteFileContents(const FilePath &filePath, - const Continuation> &cont, - const QByteArray &data, - qint64 offset) const; - - virtual void asyncCopyFile(const FilePath &filePath, - const Continuation> &cont, - const FilePath &target) const; - virtual expected_str createTempFile(const FilePath &filePath); }; diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 29ee8de6f7..12796afec9 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -6,6 +6,7 @@ #include "algorithm.h" #include "devicefileaccess.h" #include "environment.h" +#include "filestreamermanager.h" #include "fileutils.h" #include "hostosinfo.h" #include "qtcassert.h" @@ -626,13 +627,6 @@ bool FilePath::ensureReachable(const FilePath &other) const return false; } -void FilePath::asyncFileContents(const Continuation &> &cont, - qint64 maxSize, - qint64 offset) const -{ - return fileAccess()->asyncFileContents(*this, cont, maxSize, offset); -} - expected_str FilePath::writeFileContents(const QByteArray &data, qint64 offset) const { return fileAccess()->writeFileContents(*this, data, offset); @@ -643,11 +637,19 @@ FilePathInfo FilePath::filePathInfo() const return fileAccess()->filePathInfo(*this); } -void FilePath::asyncWriteFileContents(const Continuation &> &cont, - const QByteArray &data, - qint64 offset) const +FileStreamHandle FilePath::asyncCopy(const FilePath &target, const CopyContinuation &cont) const +{ + return FileStreamerManager::copy(*this, target, cont); +} + +FileStreamHandle FilePath::asyncRead(const ReadContinuation &cont) const +{ + return FileStreamerManager::read(*this, cont); +} + +FileStreamHandle FilePath::asyncWrite(const QByteArray &data, const WriteContinuation &cont) const { - return fileAccess()->asyncWriteFileContents(*this, cont, data, offset); + return FileStreamerManager::write(*this, data, cont); } bool FilePath::needsDevice() const @@ -1614,26 +1616,6 @@ expected_str FilePath::copyFile(const FilePath &target) const return fileAccess()->copyFile(*this, target); } -void FilePath::asyncCopyFile(const Continuation &> &cont, - const FilePath &target) const -{ - if (host() != target.host()) { - asyncFileContents([cont, target](const expected_str &contents) { - if (contents) - target.asyncWriteFileContents( - [cont](const expected_str &result) { - if (result) - cont({}); - else - cont(make_unexpected(result.error())); - }, - *contents); - }); - return; - } - return fileAccess()->asyncCopyFile(*this, cont, target); -} - bool FilePath::renameFile(const FilePath &target) const { return fileAccess()->renameFile(*this, target); diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h index 77a3b84382..11fd8e10e2 100644 --- a/src/libs/utils/filepath.h +++ b/src/libs/utils/filepath.h @@ -33,8 +33,12 @@ namespace Utils { class DeviceFileAccess; class Environment; class EnvironmentChange; +enum class FileStreamHandle; template using Continuation = std::function; +using CopyContinuation = Continuation &>; +using ReadContinuation = Continuation &>; +using WriteContinuation = Continuation &>; class QTCREATOR_UTILS_EXPORT FileFilter { @@ -206,14 +210,9 @@ public: static void sort(FilePaths &files); // Asynchronous interface - void asyncCopyFile(const Continuation &> &cont, - const FilePath &target) const; - void asyncFileContents(const Continuation &> &cont, - qint64 maxSize = -1, - qint64 offset = 0) const; - void asyncWriteFileContents(const Continuation &> &cont, - const QByteArray &data, - qint64 offset = 0) const; + FileStreamHandle asyncCopy(const FilePath &target, const CopyContinuation &cont = {}) const; + FileStreamHandle asyncRead(const ReadContinuation &cont = {}) const; + FileStreamHandle asyncWrite(const QByteArray &data, const WriteContinuation &cont = {}) const; // Prefer not to use // Using needsDevice() in "user" code is likely to result in code that diff --git a/src/libs/utils/filestreamermanager.h b/src/libs/utils/filestreamermanager.h index 261d58ba29..f86a3db480 100644 --- a/src/libs/utils/filestreamermanager.h +++ b/src/libs/utils/filestreamermanager.h @@ -15,15 +15,11 @@ QT_END_NAMESPACE namespace Utils { -enum FileStreamHandle : int {}; +enum class FileStreamHandle : int {}; class QTCREATOR_UTILS_EXPORT FileStreamerManager { public: - using CopyContinuation = Continuation &>; - using ReadContinuation = Continuation &>; - using WriteContinuation = Continuation &>; - static FileStreamHandle copy(const FilePath &source, const FilePath &destination, const CopyContinuation &cont); static FileStreamHandle copy(const FilePath &source, const FilePath &destination, -- cgit v1.2.3 From d0fae7fa592795468a2fe6e86197a7899fa1b5a0 Mon Sep 17 00:00:00 2001 From: Cristian Adam Date: Tue, 7 Mar 2023 12:17:21 +0100 Subject: Terminal: Fix conpty process creation When the Project's source directory would be used as a working directory for the terminal, conpty would fail to start the shell. Change-Id: I1050ec11c2bb46e17187431114a1319c86dd449c Reviewed-by: Reviewed-by: David Schulz --- src/libs/3rdparty/libptyqt/conptyprocess.cpp | 80 ++++++++++++---------------- 1 file changed, 34 insertions(+), 46 deletions(-) (limited to 'src/libs') diff --git a/src/libs/3rdparty/libptyqt/conptyprocess.cpp b/src/libs/3rdparty/libptyqt/conptyprocess.cpp index c788e74c92..687d3116f5 100644 --- a/src/libs/3rdparty/libptyqt/conptyprocess.cpp +++ b/src/libs/3rdparty/libptyqt/conptyprocess.cpp @@ -105,8 +105,7 @@ bool ConPtyProcess::startProcess(const QString &executable, qint16 cols, qint16 rows) { - if (!isAvailable()) - { + if (!isAvailable()) { m_lastError = m_winContext.lastError(); return false; } @@ -127,54 +126,45 @@ bool ConPtyProcess::startProcess(const QString &executable, m_size = QPair(cols, rows); //env - std::wstringstream envBlock; - for (const QString &line: std::as_const(environment)) - { - envBlock << line.toStdWString() << L'\0'; - } - envBlock << L'\0'; - std::wstring env = envBlock.str(); - LPWSTR envArg = env.empty() ? nullptr : env.data(); - LPCWSTR workingDirPointer = workingDir.isEmpty() ? nullptr : workingDir.toStdWString().c_str(); + const QString env = environment.join(QChar(QChar::Null)) + QChar(QChar::Null); + LPVOID envPtr = env.isEmpty() ? nullptr : (LPVOID) env.utf16(); + + LPCWSTR workingDirPtr = workingDir.isEmpty() ? nullptr : (LPCWSTR) workingDir.utf16(); QStringList exeAndArgs = arguments; exeAndArgs.prepend(m_shellPath); + std::wstring cmdArg{(LPCWSTR) (exeAndArgs.join(QLatin1String(" ")).utf16())}; - std::wstring cmdArg = exeAndArgs.join(" ").toStdWString(); - - HRESULT hr{ E_UNEXPECTED }; + HRESULT hr{E_UNEXPECTED}; // Create the Pseudo Console and pipes to it hr = createPseudoConsoleAndPipes(&m_ptyHandler, &m_hPipeIn, &m_hPipeOut, cols, rows); - if (S_OK != hr) - { + if (S_OK != hr) { m_lastError = QString("ConPty Error: CreatePseudoConsoleAndPipes fail"); return false; } // Initialize the necessary startup info struct - if (S_OK != initializeStartupInfoAttachedToPseudoConsole(&m_shellStartupInfo, m_ptyHandler)) - { + if (S_OK != initializeStartupInfoAttachedToPseudoConsole(&m_shellStartupInfo, m_ptyHandler)) { m_lastError = QString("ConPty Error: InitializeStartupInfoAttachedToPseudoConsole fail"); return false; } - hr = CreateProcess(NULL, // No module name - use Command Line - cmdArg.data(), // Command Line - NULL, // Process handle not inheritable - NULL, // Thread handle not inheritable - FALSE, // Inherit handles - EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT, // Creation flags - envArg, // Environment block - workingDirPointer, // Use parent's starting directory - &m_shellStartupInfo.StartupInfo, // Pointer to STARTUPINFO - &m_shellProcessInformation) // Pointer to PROCESS_INFORMATION + hr = CreateProcessW(nullptr, // No module name - use Command Line + cmdArg.data(), // Command Line + nullptr, // Process handle not inheritable + nullptr, // Thread handle not inheritable + FALSE, // Inherit handles + EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT, // Creation flags + envPtr, // Environment block + workingDirPtr, // Use parent's starting directory + &m_shellStartupInfo.StartupInfo, // Pointer to STARTUPINFO + &m_shellProcessInformation) // Pointer to PROCESS_INFORMATION ? S_OK : GetLastError(); - if (S_OK != hr) - { + if (S_OK != hr) { m_lastError = QString("ConPty Error: Cannot create process -> %1").arg(hr); return false; } @@ -184,27 +174,25 @@ bool ConPtyProcess::startProcess(const QString &executable, // Notify when the shell process has been terminated m_shellCloseWaitNotifier = new QWinEventNotifier(m_shellProcessInformation.hProcess, notifier()); QObject::connect(m_shellCloseWaitNotifier, - &QWinEventNotifier::activated, - notifier(), - [this](HANDLE hEvent) { - DWORD exitCode = 0; - GetExitCodeProcess(hEvent, &exitCode); - m_exitCode = exitCode; - // Do not respawn if the object is about to be destructed - if (!m_aboutToDestruct) - emit notifier()->aboutToClose(); - m_shellCloseWaitNotifier->setEnabled(false); - }); + &QWinEventNotifier::activated, + notifier(), + [this](HANDLE hEvent) { + DWORD exitCode = 0; + GetExitCodeProcess(hEvent, &exitCode); + m_exitCode = exitCode; + // Do not respawn if the object is about to be destructed + if (!m_aboutToDestruct) + emit notifier()->aboutToClose(); + m_shellCloseWaitNotifier->setEnabled(false); + }); //this code runned in separate thread - m_readThread = QThread::create([this]() - { + m_readThread = QThread::create([this]() { //buffers - const DWORD BUFF_SIZE{ 1024 }; + const DWORD BUFF_SIZE{1024}; char szBuffer[BUFF_SIZE]{}; - forever - { + forever { DWORD dwBytesRead{}; // Read from the pipe -- cgit v1.2.3 From 5d21da74f952600482afe300dbc67aa63ab2eaf5 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 7 Feb 2023 16:46:47 +0100 Subject: Utils: More explicit host os use to make it stand out Quite a few of the uses are actually wrong, but are better visible now and therefore more likely to be fixed. Change-Id: Ia51f7d6eb1b2d3a9c9f73d67dabacfd227c44b15 Reviewed-by: Christian Kandeler Reviewed-by: Qt CI Bot Reviewed-by: --- src/libs/utils/commandline.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/commandline.h b/src/libs/utils/commandline.h index 23585adb8a..c3aa17c7d8 100644 --- a/src/libs/utils/commandline.h +++ b/src/libs/utils/commandline.h @@ -55,7 +55,7 @@ public: //! Append already quoted arguments to a shell command static void addArgs(QString *args, const QString &inArgs); //! Split a shell command into separate arguments. - static QStringList splitArgs(const QString &cmd, OsType osType = HostOsInfo::hostOs(), + static QStringList splitArgs(const QString &cmd, OsType osType, bool abortOnMeta = false, SplitError *err = nullptr, const Environment *env = nullptr, const QString *pwd = nullptr); //! Safely replace the expandos in a shell command -- cgit v1.2.3 From cca7c2a12bef29f9b693d7494c85c7fecd5c5d6f Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Fri, 3 Mar 2023 20:00:20 +0100 Subject: AsyncTask: add asyncRun() wrappers around QtConcurrent::run() These wrappers accept the additional QThread::Priority argument. Change-Id: I05a692d13bb539622146e3267f8a918431af06ac Reviewed-by: Reviewed-by: Eike Ziller --- src/libs/utils/asynctask.cpp | 26 +++++++++++++++++---- src/libs/utils/asynctask.h | 54 +++++++++++++++++++++++++++----------------- 2 files changed, 55 insertions(+), 25 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/asynctask.cpp b/src/libs/utils/asynctask.cpp index 73217318a9..2f5aa4d27a 100644 --- a/src/libs/utils/asynctask.cpp +++ b/src/libs/utils/asynctask.cpp @@ -12,17 +12,35 @@ static int s_maxThreadCount = INT_MAX; class AsyncThreadPool : public QThreadPool { public: - AsyncThreadPool() { + AsyncThreadPool(QThread::Priority priority) { + setThreadPriority(priority); setMaxThreadCount(s_maxThreadCount); moveToThread(qApp->thread()); } }; -Q_GLOBAL_STATIC(AsyncThreadPool, s_asyncThreadPool); +Q_GLOBAL_STATIC(AsyncThreadPool, s_idle, QThread::IdlePriority); +Q_GLOBAL_STATIC(AsyncThreadPool, s_lowest, QThread::LowestPriority); +Q_GLOBAL_STATIC(AsyncThreadPool, s_low, QThread::LowPriority); +Q_GLOBAL_STATIC(AsyncThreadPool, s_normal, QThread::NormalPriority); +Q_GLOBAL_STATIC(AsyncThreadPool, s_high, QThread::HighPriority); +Q_GLOBAL_STATIC(AsyncThreadPool, s_highest, QThread::HighestPriority); +Q_GLOBAL_STATIC(AsyncThreadPool, s_timeCritical, QThread::TimeCriticalPriority); +Q_GLOBAL_STATIC(AsyncThreadPool, s_inherit, QThread::InheritPriority); -QThreadPool *asyncThreadPool() +QThreadPool *asyncThreadPool(QThread::Priority priority) { - return s_asyncThreadPool; + switch (priority) { + case QThread::IdlePriority : return s_idle; + case QThread::LowestPriority : return s_lowest; + case QThread::LowPriority : return s_low; + case QThread::NormalPriority : return s_normal; + case QThread::HighPriority : return s_high; + case QThread::HighestPriority : return s_highest; + case QThread::TimeCriticalPriority : return s_timeCritical; + case QThread::InheritPriority : return s_inherit; + } + return nullptr; } } // namespace Utils diff --git a/src/libs/utils/asynctask.h b/src/libs/utils/asynctask.h index 5beaf400ea..1c2f9e9a80 100644 --- a/src/libs/utils/asynctask.h +++ b/src/libs/utils/asynctask.h @@ -15,7 +15,36 @@ namespace Utils { -QTCREATOR_UTILS_EXPORT QThreadPool *asyncThreadPool(); +QTCREATOR_UTILS_EXPORT QThreadPool *asyncThreadPool(QThread::Priority priority); + +template +auto asyncRun(QThreadPool *threadPool, QThread::Priority priority, + Function &&function, Args &&...args) +{ + QThreadPool *pool = threadPool ? threadPool : asyncThreadPool(priority); + return QtConcurrent::run(pool, std::forward(function), std::forward(args)...); +} + +template +auto asyncRun(QThread::Priority priority, Function &&function, Args &&...args) +{ + return asyncRun(nullptr, priority, + std::forward(function), std::forward(args)...); +} + +template +auto asyncRun(QThreadPool *threadPool, Function &&function, Args &&...args) +{ + return asyncRun(threadPool, QThread::InheritPriority, + std::forward(function), std::forward(args)...); +} + +template +auto asyncRun(Function &&function, Args &&...args) +{ + return asyncRun(nullptr, QThread::InheritPriority, + std::forward(function), std::forward(args)...); +} class QTCREATOR_UTILS_EXPORT AsyncTaskBase : public QObject { @@ -86,7 +115,7 @@ private: void wrapConcurrent(Function &&function, Args &&...args) { m_startHandler = [=] { - return callConcurrent(function, args...); + return asyncRun(m_threadPool, m_priority, function, args...); }; } @@ -94,28 +123,11 @@ private: void wrapConcurrent(std::reference_wrapper &&wrapper, Args &&...args) { m_startHandler = [=] { - return callConcurrent(std::forward(wrapper.get()), args...); + return asyncRun(m_threadPool, m_priority, std::forward(wrapper.get()), + args...); }; } - template - auto callConcurrent(Function &&function, Args &&...args) - { - // Notice: we can't just call: - // - // return QtConcurrent::run(function, args...); - // - // since there is no way of passing m_priority there. - // There is an overload with thread pool, however, there is no overload with priority. - // - // Below implementation copied from QtConcurrent::run(): - QThreadPool *threadPool = m_threadPool ? m_threadPool : asyncThreadPool(); - QtConcurrent::DecayedTuple - tuple{std::forward(function), std::forward(args)...}; - return QtConcurrent::TaskResolver, std::decay_t...> - ::run(std::move(tuple), QtConcurrent::TaskStartParameters{threadPool, m_priority}); - } - using StartHandler = std::function()>; StartHandler m_startHandler; FutureSynchronizer *m_synchronizer = nullptr; -- cgit v1.2.3 From e3ce7cf2f97d971e542357bac81ce8418a41f10a Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Thu, 9 Mar 2023 10:01:43 +0100 Subject: Utils: Fix build with Qt6.2 Change-Id: I00a0e562bf4c290e4bdc441abd422b2771ba0a53 Reviewed-by: Jarek Kobus Reviewed-by: Eike Ziller --- src/libs/utils/asynctask.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/asynctask.cpp b/src/libs/utils/asynctask.cpp index 2f5aa4d27a..c2c42431d1 100644 --- a/src/libs/utils/asynctask.cpp +++ b/src/libs/utils/asynctask.cpp @@ -19,6 +19,16 @@ public: } }; +#if QT_VERSION < QT_VERSION_CHECK(6, 3, 0) +Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_idle, (QThread::IdlePriority)); +Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_lowest, (QThread::LowestPriority)); +Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_low, (QThread::LowPriority)); +Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_normal, (QThread::NormalPriority)); +Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_high, (QThread::HighPriority)); +Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_highest, (QThread::HighestPriority)); +Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_timeCritical, (QThread::TimeCriticalPriority)); +Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_inherit, (QThread::InheritPriority)); +#else Q_GLOBAL_STATIC(AsyncThreadPool, s_idle, QThread::IdlePriority); Q_GLOBAL_STATIC(AsyncThreadPool, s_lowest, QThread::LowestPriority); Q_GLOBAL_STATIC(AsyncThreadPool, s_low, QThread::LowPriority); @@ -27,6 +37,7 @@ Q_GLOBAL_STATIC(AsyncThreadPool, s_high, QThread::HighPriority); Q_GLOBAL_STATIC(AsyncThreadPool, s_highest, QThread::HighestPriority); Q_GLOBAL_STATIC(AsyncThreadPool, s_timeCritical, QThread::TimeCriticalPriority); Q_GLOBAL_STATIC(AsyncThreadPool, s_inherit, QThread::InheritPriority); +#endif QThreadPool *asyncThreadPool(QThread::Priority priority) { -- cgit v1.2.3 From d80b02de56406aeff9534960097b55f2286418da Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Fri, 3 Mar 2023 23:57:21 +0100 Subject: StringTable: Use QtConcurrent invocation for async run Change-Id: I33da94ee9d564104edf4b93f1ae40d97adc407fd Reviewed-by: Christian Stenger Reviewed-by: Qt CI Bot --- src/libs/utils/stringtable.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/stringtable.cpp b/src/libs/utils/stringtable.cpp index 6345b80c32..e501203a10 100644 --- a/src/libs/utils/stringtable.cpp +++ b/src/libs/utils/stringtable.cpp @@ -3,7 +3,7 @@ #include "stringtable.h" -#include "runextensions.h" +#include "asynctask.h" #include #include @@ -34,7 +34,7 @@ public: void cancelAndWait(); QString insert(const QString &string); void startGC(); - void GC(QFutureInterface &futureInterface); + void GC(QPromise &promise); QFuture m_future; QMutex m_lock; @@ -90,7 +90,7 @@ void StringTablePrivate::startGC() { QMutexLocker locker(&m_lock); cancelAndWait(); - m_future = Utils::runAsync(&StringTablePrivate::GC, this); + m_future = Utils::asyncRun(&StringTablePrivate::GC, this); } QTCREATOR_UTILS_EXPORT void scheduleGC() @@ -113,7 +113,7 @@ static inline bool isQStringInUse(const QString &string) return data_ptr->isShared() || !data_ptr->isMutable() /* QStringLiteral ? */; } -void StringTablePrivate::GC(QFutureInterface &futureInterface) +void StringTablePrivate::GC(QPromise &promise) { int initialSize = 0; bytesSaved = 0; @@ -125,7 +125,7 @@ void StringTablePrivate::GC(QFutureInterface &futureInterface) // Collect all QStrings which have refcount 1. (One reference in m_strings and nowhere else.) for (QSet::iterator i = m_strings.begin(); i != m_strings.end();) { - if (futureInterface.isCanceled()) + if (promise.isCanceled()) return; if (!isQStringInUse(*i)) -- cgit v1.2.3 From 5ff073df19b872b8db601f31e1124c6048a89a3c Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Sat, 11 Feb 2023 23:21:58 +0100 Subject: DiffEditor: Use QtConcurrent invocation for async tasks Change-Id: I06640837ffee830e60e8dd2a566f9388f8444010 Reviewed-by: Orgad Shaneh --- src/libs/utils/differ.cpp | 25 ++++++++----------------- src/libs/utils/differ.h | 6 +++--- 2 files changed, 11 insertions(+), 20 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/differ.cpp b/src/libs/utils/differ.cpp index 538aa6657a..b1c163bb5b 100644 --- a/src/libs/utils/differ.cpp +++ b/src/libs/utils/differ.cpp @@ -14,11 +14,10 @@ publication by Neil Fraser: http://neil.fraser.name/writing/diff/ #include "utilstr.h" #include -#include -#include #include #include -#include +#include +#include namespace Utils { @@ -937,10 +936,9 @@ QString Diff::toString() const /////////////// -Differ::Differ(QFutureInterfaceBase *jobController) - : m_jobController(jobController) +Differ::Differ(const QFuture &future) + : m_future(future) { - } QList Differ::diff(const QString &text1, const QString &text2) @@ -1075,7 +1073,7 @@ QList Differ::diffMyers(const QString &text1, const QString &text2) int kMinReverse = -D; int kMaxReverse = D; for (int d = 0; d <= D; d++) { - if (m_jobController && m_jobController->isCanceled()) { + if (m_future.isCanceled()) { delete [] forwardV; delete [] reverseV; return QList(); @@ -1193,17 +1191,10 @@ QList Differ::diffNonCharMode(const QString &text1, const QString &text2) QString lastDelete; QString lastInsert; QList newDiffList; - if (m_jobController) { - m_jobController->setProgressRange(0, diffList.count()); - m_jobController->setProgressValue(0); - } for (int i = 0; i <= diffList.count(); i++) { - if (m_jobController) { - if (m_jobController->isCanceled()) { - m_currentDiffMode = diffMode; - return QList(); - } - m_jobController->setProgressValue(i + 1); + if (m_future.isCanceled()) { + m_currentDiffMode = diffMode; + return {}; } const Diff diffItem = i < diffList.count() ? diffList.at(i) diff --git a/src/libs/utils/differ.h b/src/libs/utils/differ.h index 62b8bc7ec4..09b7965222 100644 --- a/src/libs/utils/differ.h +++ b/src/libs/utils/differ.h @@ -5,12 +5,12 @@ #include "utils_global.h" +#include #include QT_BEGIN_NAMESPACE template class QMap; -class QFutureInterfaceBase; QT_END_NAMESPACE namespace Utils { @@ -42,7 +42,7 @@ public: WordMode, LineMode }; - Differ(QFutureInterfaceBase *jobController = nullptr); + Differ(const QFuture &future = {}); QList diff(const QString &text1, const QString &text2); QList unifiedDiff(const QString &text1, const QString &text2); void setDiffMode(DiffMode mode); @@ -90,7 +90,7 @@ private: int subTextStart); DiffMode m_diffMode = Differ::LineMode; DiffMode m_currentDiffMode = Differ::LineMode; - QFutureInterfaceBase *m_jobController = nullptr; + QFuture m_future; }; } // namespace Utils -- cgit v1.2.3 From 665ae04605cfa4033201b42194580b32739c9df9 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Mon, 6 Mar 2023 12:25:26 +0100 Subject: Terminal: Open Link with line/column info Change-Id: I3e70a7c33a935b7bd3e12fb903148bcd60ff55aa Reviewed-by: hjk Reviewed-by: Cristian Adam --- src/libs/utils/link.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/link.h b/src/libs/utils/link.h index 655957e9ac..00194654c9 100644 --- a/src/libs/utils/link.h +++ b/src/libs/utils/link.h @@ -27,7 +27,11 @@ public: static Link fromString(const QString &filePathWithNumbers, bool canContainLineNumber = false); bool hasValidTarget() const - { return !targetFilePath.isEmpty(); } + { + if (!targetFilePath.isEmpty()) + return true; + return !targetFilePath.scheme().isEmpty() || !targetFilePath.host().isEmpty(); + } bool hasValidLinkText() const { return linkTextStart != linkTextEnd; } -- cgit v1.2.3 From 4fc891563a053d4e225cc28144c6897118fe978e Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Thu, 9 Mar 2023 20:08:29 +0100 Subject: DiffEditor: Fix DiffEditor tests Amends 5ff073df19b872b8db601f31e1124c6048a89a3c Change-Id: I4597453b057dfce41b73b4973205cba33d8e4a58 Reviewed-by: Qt CI Bot Reviewed-by: hjk Reviewed-by: --- src/libs/utils/differ.cpp | 6 +++--- src/libs/utils/differ.h | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/differ.cpp b/src/libs/utils/differ.cpp index b1c163bb5b..9dc2ce8454 100644 --- a/src/libs/utils/differ.cpp +++ b/src/libs/utils/differ.cpp @@ -936,7 +936,7 @@ QString Diff::toString() const /////////////// -Differ::Differ(const QFuture &future) +Differ::Differ(const std::optional> &future) : m_future(future) { } @@ -1073,7 +1073,7 @@ QList Differ::diffMyers(const QString &text1, const QString &text2) int kMinReverse = -D; int kMaxReverse = D; for (int d = 0; d <= D; d++) { - if (m_future.isCanceled()) { + if (m_future && m_future->isCanceled()) { delete [] forwardV; delete [] reverseV; return QList(); @@ -1192,7 +1192,7 @@ QList Differ::diffNonCharMode(const QString &text1, const QString &text2) QString lastInsert; QList newDiffList; for (int i = 0; i <= diffList.count(); i++) { - if (m_future.isCanceled()) { + if (m_future && m_future->isCanceled()) { m_currentDiffMode = diffMode; return {}; } diff --git a/src/libs/utils/differ.h b/src/libs/utils/differ.h index 09b7965222..dc6d74f30d 100644 --- a/src/libs/utils/differ.h +++ b/src/libs/utils/differ.h @@ -8,6 +8,8 @@ #include #include +#include + QT_BEGIN_NAMESPACE template class QMap; @@ -42,7 +44,7 @@ public: WordMode, LineMode }; - Differ(const QFuture &future = {}); + Differ(const std::optional> &future = {}); QList diff(const QString &text1, const QString &text2); QList unifiedDiff(const QString &text1, const QString &text2); void setDiffMode(DiffMode mode); @@ -90,7 +92,7 @@ private: int subTextStart); DiffMode m_diffMode = Differ::LineMode; DiffMode m_currentDiffMode = Differ::LineMode; - QFuture m_future; + std::optional> m_future; }; } // namespace Utils -- cgit v1.2.3 From 811e54145ff7d5d39433fc6ff53ac5eb5ba661ec Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Sun, 12 Feb 2023 03:02:52 +0100 Subject: AsyncTask: Get rid of setAsyncCallData() Replaced by setConcurrentCallData(), potentially with QPromise. Change-Id: I7eddb407d7df161d440c92cdce6be59dce3609da Reviewed-by: Eike Ziller Reviewed-by: Qt CI Bot Reviewed-by: --- src/libs/utils/asynctask.h | 8 -------- 1 file changed, 8 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/asynctask.h b/src/libs/utils/asynctask.h index 1c2f9e9a80..a600a14b24 100644 --- a/src/libs/utils/asynctask.h +++ b/src/libs/utils/asynctask.h @@ -7,7 +7,6 @@ #include "futuresynchronizer.h" #include "qtcassert.h" -#include "runextensions.h" #include "tasktree.h" #include @@ -81,13 +80,6 @@ public: return wrapConcurrent(std::forward(function), std::forward(args)...); } - template - void setAsyncCallData(const Function &function, const Args &...args) - { - m_startHandler = [=] { - return Utils::runAsync(m_threadPool, m_priority, function, args...); - }; - } void setFutureSynchronizer(FutureSynchronizer *synchorizer) { m_synchronizer = synchorizer; } void setThreadPool(QThreadPool *pool) { m_threadPool = pool; } void setPriority(QThread::Priority priority) { m_priority = priority; } -- cgit v1.2.3 From d558741985f97f0928eec957f0f1118fdceedec4 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Fri, 10 Mar 2023 15:57:40 +0100 Subject: Tracing: Use QtConcurrent invocation for async run Change-Id: I40dea7276ed9d54c7ce898f0463df05929576648 Reviewed-by: Ulf Hermann Reviewed-by: Reviewed-by: Qt CI Bot --- src/libs/tracing/timelinetracemanager.cpp | 36 ++++++++++++++++++------------- 1 file changed, 21 insertions(+), 15 deletions(-) (limited to 'src/libs') diff --git a/src/libs/tracing/timelinetracemanager.cpp b/src/libs/tracing/timelinetracemanager.cpp index 96eb147519..b252a02cee 100644 --- a/src/libs/tracing/timelinetracemanager.cpp +++ b/src/libs/tracing/timelinetracemanager.cpp @@ -6,12 +6,10 @@ #include "timelinetracemanager.h" #include "tracingtr.h" +#include #include -#include -#include #include -#include #include @@ -223,8 +221,11 @@ QFuture TimelineTraceManager::save(const QString &filename) connect(writer, &QObject::destroyed, this, &TimelineTraceManager::saveFinished); connect(writer, &TimelineTraceFile::error, this, &TimelineTraceManager::error); - return Utils::runAsync([filename, writer] (QFutureInterface &future) { - writer->setFuture(future); + QFutureInterface fi; + fi.reportStarted(); + writer->setFuture(fi); + + Utils::asyncRun([filename, writer, fi] { QFile file(filename); if (file.open(QIODevice::WriteOnly)) @@ -232,10 +233,13 @@ QFuture TimelineTraceManager::save(const QString &filename) else writer->fail(Tr::tr("Could not open %1 for writing.").arg(filename)); - if (future.isCanceled()) + if (fi.isCanceled()) file.remove(); writer->deleteLater(); + QFutureInterface fiCopy = fi; + fiCopy.reportFinished(); }); + return fi.future(); } QFuture TimelineTraceManager::load(const QString &filename) @@ -249,8 +253,10 @@ QFuture TimelineTraceManager::load(const QString &filename) connect(reader, &QObject::destroyed, this, &TimelineTraceManager::loadFinished); connect(reader, &TimelineTraceFile::error, this, &TimelineTraceManager::error); - QFuture future = Utils::runAsync([filename, reader] (QFutureInterface &future) { - reader->setFuture(future); + QFutureInterface fi; + fi.reportStarted(); + reader->setFuture(fi); + Utils::asyncRun([filename, reader, fi] { QFile file(filename); if (file.open(QIODevice::ReadOnly)) @@ -259,11 +265,13 @@ QFuture TimelineTraceManager::load(const QString &filename) reader->fail(Tr::tr("Could not open %1 for reading.").arg(filename)); reader->deleteLater(); + QFutureInterface fiCopy = fi; + fiCopy.reportFinished(); }); QFutureWatcher *watcher = new QFutureWatcher(reader); connect(watcher, &QFutureWatcherBase::canceled, this, &TimelineTraceManager::clearAll); - connect(watcher, &QFutureWatcherBase::finished, this, [this, reader]() { + connect(watcher, &QFutureWatcherBase::finished, this, [this, reader] { if (!reader->isCanceled()) { if (reader->traceStart() >= 0) decreaseTraceStart(reader->traceStart()); @@ -272,9 +280,8 @@ QFuture TimelineTraceManager::load(const QString &filename) finalize(); } }); - watcher->setFuture(future); - - return future; + watcher->setFuture(fi.future()); + return fi.future(); } qint64 TimelineTraceManager::traceStart() const @@ -366,10 +373,9 @@ void TimelineTraceManager::restrictByFilter(TraceEventFilter filter) QFutureInterface future; replayEvents(filter(std::bind(&TimelineTraceManagerPrivate::dispatch, d, - std::placeholders::_1, std::placeholders::_2)), - [this]() { + std::placeholders::_1, std::placeholders::_2)), [this] { initialize(); - }, [this]() { + }, [this] { if (d->notesModel) d->notesModel->restore(); finalize(); -- cgit v1.2.3 From 81dcfd907b961f4f3aab438d648a3fc3a50fc4ee Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Tue, 7 Mar 2023 10:04:14 +0100 Subject: CPlusPlus: Add parser support for generic lambdas Change-Id: Id17975a4296925c10b1b43f963412eea61ccfa5d Reviewed-by: Reviewed-by: Christian Stenger --- src/libs/3rdparty/cplusplus/AST.cpp | 24 ++++++++++++++++++++++++ src/libs/3rdparty/cplusplus/AST.h | 4 ++++ src/libs/3rdparty/cplusplus/ASTClone.cpp | 10 ++++++++++ src/libs/3rdparty/cplusplus/ASTMatcher.cpp | 20 ++++++++++++++++++++ src/libs/3rdparty/cplusplus/ASTVisit.cpp | 4 ++++ src/libs/3rdparty/cplusplus/Parser.cpp | 30 ++++++++++++++++++++++++++++-- 6 files changed, 90 insertions(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/3rdparty/cplusplus/AST.cpp b/src/libs/3rdparty/cplusplus/AST.cpp index 53c84a95c6..5c59bb683c 100644 --- a/src/libs/3rdparty/cplusplus/AST.cpp +++ b/src/libs/3rdparty/cplusplus/AST.cpp @@ -1659,12 +1659,18 @@ int LambdaDeclaratorAST::firstToken() const if (trailing_return_type) if (int candidate = trailing_return_type->firstToken()) return candidate; + if (requiresClause) + if (int candidate = requiresClause->firstToken()) + return candidate; return 0; } /** \generated */ int LambdaDeclaratorAST::lastToken() const { + if (requiresClause) + if (int candidate = requiresClause->firstToken()) + return candidate; if (trailing_return_type) if (int candidate = trailing_return_type->lastToken()) return candidate; @@ -1692,6 +1698,15 @@ int LambdaExpressionAST::firstToken() const if (lambda_introducer) if (int candidate = lambda_introducer->firstToken()) return candidate; + if (templateParameters) + if (int candidate = templateParameters->firstToken()) + return candidate; + if (requiresClause) + if (int candidate = requiresClause->firstToken()) + return candidate; + if (attributes) + if (int candidate = attributes->firstToken()) + return candidate; if (lambda_declarator) if (int candidate = lambda_declarator->firstToken()) return candidate; @@ -1710,6 +1725,15 @@ int LambdaExpressionAST::lastToken() const if (lambda_declarator) if (int candidate = lambda_declarator->lastToken()) return candidate; + if (attributes) + if (int candidate = attributes->firstToken()) + return candidate; + if (requiresClause) + if (int candidate = requiresClause->firstToken()) + return candidate; + if (templateParameters) + if (int candidate = templateParameters->firstToken()) + return candidate; if (lambda_introducer) if (int candidate = lambda_introducer->lastToken()) return candidate; diff --git a/src/libs/3rdparty/cplusplus/AST.h b/src/libs/3rdparty/cplusplus/AST.h index 0d7e337fd8..0f40424464 100644 --- a/src/libs/3rdparty/cplusplus/AST.h +++ b/src/libs/3rdparty/cplusplus/AST.h @@ -3678,6 +3678,9 @@ class LambdaExpressionAST: public ExpressionAST { public: LambdaIntroducerAST *lambda_introducer = nullptr; + DeclarationListAST *templateParameters = nullptr; + RequiresClauseAST *requiresClause = nullptr; + SpecifierListAST *attributes = nullptr; LambdaDeclaratorAST *lambda_declarator = nullptr; StatementAST *statement = nullptr; @@ -3758,6 +3761,7 @@ public: int mutable_token = 0; ExceptionSpecificationAST *exception_specification = nullptr; TrailingReturnTypeAST *trailing_return_type = nullptr; + RequiresClauseAST *requiresClause = nullptr; public: // annotations Function *symbol = nullptr; diff --git a/src/libs/3rdparty/cplusplus/ASTClone.cpp b/src/libs/3rdparty/cplusplus/ASTClone.cpp index e1e014afcc..e494ad71ac 100644 --- a/src/libs/3rdparty/cplusplus/ASTClone.cpp +++ b/src/libs/3rdparty/cplusplus/ASTClone.cpp @@ -1816,6 +1816,14 @@ LambdaExpressionAST *LambdaExpressionAST::clone(MemoryPool *pool) const LambdaExpressionAST *ast = new (pool) LambdaExpressionAST; if (lambda_introducer) ast->lambda_introducer = lambda_introducer->clone(pool); + for (DeclarationListAST *iter = templateParameters, **ast_iter = &ast->templateParameters; + iter; iter = iter->next, ast_iter = &(*ast_iter)->next) + *ast_iter = new (pool) DeclarationListAST((iter->value) ? iter->value->clone(pool) : nullptr); + if (requiresClause) + ast->requiresClause = requiresClause->clone(pool); + for (SpecifierListAST *iter = attributes, **ast_iter = &ast->attributes; + iter; iter = iter->next, ast_iter = &(*ast_iter)->next) + *ast_iter = new (pool) SpecifierListAST((iter->value) ? iter->value->clone(pool) : nullptr); if (lambda_declarator) ast->lambda_declarator = lambda_declarator->clone(pool); if (statement) @@ -1867,6 +1875,8 @@ LambdaDeclaratorAST *LambdaDeclaratorAST::clone(MemoryPool *pool) const ast->exception_specification = exception_specification->clone(pool); if (trailing_return_type) ast->trailing_return_type = trailing_return_type->clone(pool); + if (requiresClause) + ast->requiresClause = requiresClause->clone(pool); return ast; } diff --git a/src/libs/3rdparty/cplusplus/ASTMatcher.cpp b/src/libs/3rdparty/cplusplus/ASTMatcher.cpp index 4567cd68a2..de6f6fc87c 100644 --- a/src/libs/3rdparty/cplusplus/ASTMatcher.cpp +++ b/src/libs/3rdparty/cplusplus/ASTMatcher.cpp @@ -3056,6 +3056,21 @@ bool ASTMatcher::match(LambdaExpressionAST *node, LambdaExpressionAST *pattern) else if (! AST::match(node->lambda_introducer, pattern->lambda_introducer, this)) return false; + if (! pattern->templateParameters) + pattern->templateParameters = node->templateParameters; + else if (! AST::match(node->templateParameters, pattern->templateParameters, this)) + return false; + + if (! pattern->requiresClause) + pattern->requiresClause = node->requiresClause; + else if (! AST::match(node->requiresClause, pattern->requiresClause, this)) + return false; + + if (! pattern->attributes) + pattern->attributes = node->attributes; + else if (! AST::match(node->attributes, pattern->attributes, this)) + return false; + if (! pattern->lambda_declarator) pattern->lambda_declarator = node->lambda_declarator; else if (! AST::match(node->lambda_declarator, pattern->lambda_declarator, this)) @@ -3147,6 +3162,11 @@ bool ASTMatcher::match(LambdaDeclaratorAST *node, LambdaDeclaratorAST *pattern) else if (! AST::match(node->trailing_return_type, pattern->trailing_return_type, this)) return false; + if (! pattern->requiresClause) + pattern->requiresClause = node->requiresClause; + else if (! AST::match(node->requiresClause, pattern->requiresClause, this)) + return false; + return true; } diff --git a/src/libs/3rdparty/cplusplus/ASTVisit.cpp b/src/libs/3rdparty/cplusplus/ASTVisit.cpp index d83684b966..17941d8122 100644 --- a/src/libs/3rdparty/cplusplus/ASTVisit.cpp +++ b/src/libs/3rdparty/cplusplus/ASTVisit.cpp @@ -1317,6 +1317,9 @@ void LambdaExpressionAST::accept0(ASTVisitor *visitor) { if (visitor->visit(this)) { accept(lambda_introducer, visitor); + accept(templateParameters, visitor); + accept(requiresClause, visitor); + accept(attributes, visitor); accept(lambda_declarator, visitor); accept(statement, visitor); } @@ -1354,6 +1357,7 @@ void LambdaDeclaratorAST::accept0(ASTVisitor *visitor) accept(attributes, visitor); accept(exception_specification, visitor); accept(trailing_return_type, visitor); + accept(requiresClause, visitor); } visitor->endVisit(this); } diff --git a/src/libs/3rdparty/cplusplus/Parser.cpp b/src/libs/3rdparty/cplusplus/Parser.cpp index 45c2493fff..f8c737c097 100644 --- a/src/libs/3rdparty/cplusplus/Parser.cpp +++ b/src/libs/3rdparty/cplusplus/Parser.cpp @@ -1417,8 +1417,22 @@ bool Parser::parseRequiresClauseOpt(RequiresClauseAST *&node) return true; const auto ast = new (_pool) RequiresClauseAST; ast->requires_token = consumeToken(); - if (!parseLogicalOrExpression(ast->constraint)) + if (!parsePrimaryExpression(ast->constraint)) return false; + while (true) { + if (LA() != T_PIPE_PIPE && LA() != T_AMPER_AMPER) + break; + ExpressionAST *next = nullptr; + if (!parsePrimaryExpression(next)) + return false; + + // This won't yield the right precedence, but I don't care. + BinaryExpressionAST *expr = new (_pool) BinaryExpressionAST; + expr->left_expression = ast->constraint; + expr->binary_op_token = consumeToken(); + expr->right_expression = next; + ast->constraint = expr; + } node = ast; return true; } @@ -6999,6 +7013,15 @@ bool Parser::parseLambdaExpression(ExpressionAST *&node) if (parseLambdaIntroducer(lambda_introducer)) { LambdaExpressionAST *ast = new (_pool) LambdaExpressionAST; ast->lambda_introducer = lambda_introducer; + if (_languageFeatures.cxx20Enabled && LA() == T_LESS) { + consumeToken(); + parseTemplateParameterList(ast->templateParameters); + if (LA() != T_GREATER) + return false; + consumeToken(); + parseRequiresClauseOpt(ast->requiresClause); + } + parseOptionalAttributeSpecifierSequence(ast->attributes); parseLambdaDeclarator(ast->lambda_declarator); parseCompoundStatement(ast->statement); node = ast; @@ -7023,7 +7046,9 @@ bool Parser::parseLambdaIntroducer(LambdaIntroducerAST *&node) if (LA() == T_RBRACKET) { ast->rbracket_token = consumeToken(); - if (LA() == T_LPAREN || LA() == T_LBRACE) { + // FIXME: Attributes are also allowed ... + if (LA() == T_LPAREN || LA() == T_LBRACE + || (_languageFeatures.cxx20Enabled && LA() == T_LESS)) { node = ast; return true; } @@ -7139,6 +7164,7 @@ bool Parser::parseLambdaDeclarator(LambdaDeclaratorAST *&node) parseExceptionSpecification(ast->exception_specification); parseTrailingReturnType(ast->trailing_return_type); + parseRequiresClauseOpt(ast->requiresClause); node = ast; return true; -- cgit v1.2.3 From 0c4dff7c10962ed438bc27c73942b2d1a81cb3fb Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Mon, 13 Mar 2023 10:43:54 +0100 Subject: FileListIterator: Minor fixes Add a reference to c'tor's encodings arg. Make c'tor's args default. Make m_items field const. Change-Id: I74bb1829f3ba0ea8a61106bddadeb935b6405f77 Reviewed-by: Eike Ziller --- src/libs/utils/filesearch.cpp | 19 ++++++++++--------- src/libs/utils/filesearch.h | 7 ++++--- 2 files changed, 14 insertions(+), 12 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/filesearch.cpp b/src/libs/utils/filesearch.cpp index d0fa1c71c3..02f6409de0 100644 --- a/src/libs/utils/filesearch.cpp +++ b/src/libs/utils/filesearch.cpp @@ -598,19 +598,20 @@ FileIterator::const_iterator FileIterator::end() const // #pragma mark -- FileListIterator -QTextCodec *encodingAt(const QList &encodings, int index) +QList constructItems(const FilePaths &fileList, + const QList &encodings) { - if (index >= 0 && index < encodings.size()) - return encodings.at(index); - return QTextCodec::codecForLocale(); + QList items; + items.reserve(fileList.size()); + QTextCodec *defaultEncoding = QTextCodec::codecForLocale(); + for (int i = 0; i < fileList.size(); ++i) + items.append(FileIterator::Item(fileList.at(i), encodings.value(i, defaultEncoding))); + return items; } -FileListIterator::FileListIterator(const FilePaths &fileList, const QList encodings) - : m_maxIndex(-1) +FileListIterator::FileListIterator(const FilePaths &fileList, const QList &encodings) + : m_items(constructItems(fileList, encodings)) { - m_items.reserve(fileList.size()); - for (int i = 0; i < fileList.size(); ++i) - m_items.append(Item(fileList.at(i), encodingAt(encodings, i))); } void FileListIterator::update(int requestedIndex) diff --git a/src/libs/utils/filesearch.h b/src/libs/utils/filesearch.h index 9d427f5946..ecb6c574af 100644 --- a/src/libs/utils/filesearch.h +++ b/src/libs/utils/filesearch.h @@ -107,7 +107,8 @@ protected: class QTCREATOR_UTILS_EXPORT FileListIterator : public FileIterator { public: - explicit FileListIterator(const FilePaths &fileList, const QList encodings); + explicit FileListIterator(const FilePaths &fileList = {}, + const QList &encodings = {}); int maxProgress() const override; int currentProgress() const override; @@ -118,8 +119,8 @@ protected: const Item &itemAt(int index) const override; private: - QVector m_items; - int m_maxIndex; + const QList m_items; + int m_maxIndex = -1; }; class QTCREATOR_UTILS_EXPORT SubDirFileIterator : public FileIterator -- cgit v1.2.3 From 9f1afb0318a0cdf63603dcb9bfcf3685a34a7ed6 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Sun, 12 Mar 2023 20:26:25 +0100 Subject: Utils: Add stringutils splitAtFirst Change-Id: I221d6c6086f53ec5f94c1157ea533d862db38d52 Reviewed-by: hjk --- src/libs/utils/stringutils.cpp | 19 +++++++++++++++++++ src/libs/utils/stringutils.h | 4 ++++ 2 files changed, 23 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/stringutils.cpp b/src/libs/utils/stringutils.cpp index 3a1afe1fa1..7315a69da2 100644 --- a/src/libs/utils/stringutils.cpp +++ b/src/libs/utils/stringutils.cpp @@ -527,4 +527,23 @@ QTCREATOR_UTILS_EXPORT FilePath appendHelper(const FilePath &base, int n) return base.stringAppended(QString::number(n)); } +QTCREATOR_UTILS_EXPORT QPair splitAtFirst(const QStringView &stringView, + QChar ch) +{ + int splitIdx = stringView.indexOf(ch); + if (splitIdx == -1) + return {stringView, {}}; + + QStringView left = stringView.mid(0, splitIdx); + QStringView right = stringView.mid(splitIdx + 1); + + return {left, right}; +} + +QTCREATOR_UTILS_EXPORT QPair splitAtFirst(const QString &string, QChar ch) +{ + QStringView view = string; + return splitAtFirst(view, ch); +} + } // namespace Utils diff --git a/src/libs/utils/stringutils.h b/src/libs/utils/stringutils.h index 5cf2978c07..865a1dea12 100644 --- a/src/libs/utils/stringutils.h +++ b/src/libs/utils/stringutils.h @@ -115,4 +115,8 @@ QTCREATOR_UTILS_EXPORT QString trimFront(const QString &string, QChar ch); QTCREATOR_UTILS_EXPORT QString trimBack(const QString &string, QChar ch); QTCREATOR_UTILS_EXPORT QString trim(const QString &string, QChar ch); +QTCREATOR_UTILS_EXPORT QPair splitAtFirst(const QString &string, QChar ch); +QTCREATOR_UTILS_EXPORT QPair splitAtFirst(const QStringView &stringView, + QChar ch); + } // namespace Utils -- cgit v1.2.3 From 169b4110400e3b20fb2e87a5795d5b3964ff3887 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Thu, 9 Mar 2023 14:06:33 +0100 Subject: CppElementEvaluator: Use QtConcurrent invocation for async run Change-Id: Idc67ecd4e9e95c5893a04ca1a9ee7b30662ec664 Reviewed-by: Christian Kandeler Reviewed-by: Qt CI Bot Reviewed-by: --- src/libs/cplusplus/CppDocument.cpp | 10 ++-------- src/libs/cplusplus/CppDocument.h | 8 ++------ src/libs/cplusplus/DependencyTable.cpp | 18 +++++++++--------- src/libs/cplusplus/DependencyTable.h | 5 +++-- 4 files changed, 16 insertions(+), 25 deletions(-) (limited to 'src/libs') diff --git a/src/libs/cplusplus/CppDocument.cpp b/src/libs/cplusplus/CppDocument.cpp index 6e241fb29b..4261649897 100644 --- a/src/libs/cplusplus/CppDocument.cpp +++ b/src/libs/cplusplus/CppDocument.cpp @@ -821,16 +821,10 @@ FilePaths Snapshot::filesDependingOn(const FilePath &filePath) const return m_deps.filesDependingOn(filePath); } -void Snapshot::updateDependencyTable() const -{ - QFutureInterfaceBase futureInterface; - updateDependencyTable(futureInterface); -} - -void Snapshot::updateDependencyTable(QFutureInterfaceBase &futureInterface) const +void Snapshot::updateDependencyTable(const std::optional> &future) const { if (m_deps.files.isEmpty()) - m_deps.build(futureInterface, *this); + m_deps.build(future, *this); } bool Snapshot::operator==(const Snapshot &other) const diff --git a/src/libs/cplusplus/CppDocument.h b/src/libs/cplusplus/CppDocument.h index cfbad3be1e..679663f2d9 100644 --- a/src/libs/cplusplus/CppDocument.h +++ b/src/libs/cplusplus/CppDocument.h @@ -15,12 +15,9 @@ #include #include #include +#include #include -QT_BEGIN_NAMESPACE -class QFutureInterfaceBase; -QT_END_NAMESPACE - namespace CPlusPlus { class Macro; @@ -406,8 +403,7 @@ public: Utils::FilePaths filesDependingOn(const Utils::FilePath &filePath) const; - void updateDependencyTable() const; - void updateDependencyTable(QFutureInterfaceBase &futureInterface) const; + void updateDependencyTable(const std::optional> &future = {}) const; bool operator==(const Snapshot &other) const; diff --git a/src/libs/cplusplus/DependencyTable.cpp b/src/libs/cplusplus/DependencyTable.cpp index 00aca7dc65..5960957330 100644 --- a/src/libs/cplusplus/DependencyTable.cpp +++ b/src/libs/cplusplus/DependencyTable.cpp @@ -4,7 +4,7 @@ #include "CppDocument.h" #include -#include +#include using namespace Utils; @@ -28,14 +28,14 @@ Utils::FilePaths DependencyTable::filesDependingOn(const Utils::FilePath &fileNa return deps; } -void DependencyTable::build(QFutureInterfaceBase &futureInterface, const Snapshot &snapshot) +void DependencyTable::build(const std::optional> &future, const Snapshot &snapshot) { files.clear(); fileIndex.clear(); includes.clear(); includeMap.clear(); - if (futureInterface.isCanceled()) + if (future && future->isCanceled()) return; const int documentCount = snapshot.size(); @@ -49,7 +49,7 @@ void DependencyTable::build(QFutureInterfaceBase &futureInterface, const Snapsho fileIndex[it.key()] = i; } - if (futureInterface.isCanceled()) + if (future && future->isCanceled()) return; for (int i = 0; i < files.size(); ++i) { @@ -68,13 +68,13 @@ void DependencyTable::build(QFutureInterfaceBase &futureInterface, const Snapsho directIncludes.append(index); bitmap.setBit(index, true); - if (futureInterface.isCanceled()) + if (future && future->isCanceled()) return; } includeMap[i] = bitmap; includes[i] = directIncludes; - if (futureInterface.isCanceled()) + if (future && future->isCanceled()) return; } } @@ -91,7 +91,7 @@ void DependencyTable::build(QFutureInterfaceBase &futureInterface, const Snapsho const QList includedFileIndexes = includes.value(i); for (const int includedFileIndex : includedFileIndexes) { bitmap |= includeMap.value(includedFileIndex); - if (futureInterface.isCanceled()) + if (future && future->isCanceled()) return; } @@ -99,10 +99,10 @@ void DependencyTable::build(QFutureInterfaceBase &futureInterface, const Snapsho includeMap[i] = bitmap; changed = true; } - if (futureInterface.isCanceled()) + if (future && future->isCanceled()) return; } - if (futureInterface.isCanceled()) + if (future && future->isCanceled()) return; } while (changed); } diff --git a/src/libs/cplusplus/DependencyTable.h b/src/libs/cplusplus/DependencyTable.h index d905784433..d460ebeb7f 100644 --- a/src/libs/cplusplus/DependencyTable.h +++ b/src/libs/cplusplus/DependencyTable.h @@ -14,7 +14,8 @@ #include QT_BEGIN_NAMESPACE -class QFutureInterfaceBase; +template +class QFuture; QT_END_NAMESPACE namespace CPlusPlus { @@ -25,7 +26,7 @@ class CPLUSPLUS_EXPORT DependencyTable { private: friend class Snapshot; - void build(QFutureInterfaceBase &futureInterface, const Snapshot &snapshot); + void build(const std::optional> &future, const Snapshot &snapshot); Utils::FilePaths filesDependingOn(const Utils::FilePath &fileName) const; QVector files; -- cgit v1.2.3 From 0bec769b69c660abec9ed0cc333522e4d4b0fc55 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Fri, 3 Mar 2023 20:53:58 +0100 Subject: QmlJS: Use QtConcurrent invocation for async run Add ModelManagerInterface::importScan() overload to avoid instantiating dummy QPromise arg on caller side. Change-Id: Idf836d30b2167d8840cc4e7ac6f95377c9d5622a Reviewed-by: hjk Reviewed-by: Qt CI Bot Reviewed-by: --- src/libs/qmljs/qmljsmodelmanagerinterface.cpp | 95 ++++++++++++++------------- src/libs/qmljs/qmljsmodelmanagerinterface.h | 19 +++--- src/libs/qmljs/qmljsplugindumper.cpp | 22 +++---- 3 files changed, 68 insertions(+), 68 deletions(-) (limited to 'src/libs') diff --git a/src/libs/qmljs/qmljsmodelmanagerinterface.cpp b/src/libs/qmljs/qmljsmodelmanagerinterface.cpp index 3d2fc32120..f6c84fb88f 100644 --- a/src/libs/qmljs/qmljsmodelmanagerinterface.cpp +++ b/src/libs/qmljs/qmljsmodelmanagerinterface.cpp @@ -15,8 +15,8 @@ #include #include +#include #include -#include #include #ifdef WITH_TESTS @@ -337,11 +337,9 @@ QFuture ModelManagerInterface::refreshSourceFiles(const QList(); - QFuture result = Utils::runAsync(&m_threadPool, - &ModelManagerInterface::parse, - workingCopyInternal(), sourceFiles, - this, Dialect(Dialect::Qml), - emitDocumentOnDiskChanged); + QFuture result = Utils::asyncRun(&m_threadPool, &ModelManagerInterface::parse, + workingCopyInternal(), sourceFiles, this, + Dialect(Dialect::Qml), emitDocumentOnDiskChanged); addFuture(result); if (sourceFiles.count() > 1) @@ -365,13 +363,8 @@ QFuture ModelManagerInterface::refreshSourceFiles(const QList &files) @@ -1044,24 +1037,24 @@ void ModelManagerInterface::parseLoop(QSet &scannedPaths, class FutureReporter { public: - FutureReporter(QFutureInterface &future, int multiplier, int base) - : future(future), multiplier(multiplier), base(base) + FutureReporter(QPromise &promise, int multiplier, int base) + : m_promise(promise), m_multiplier(multiplier), m_base(base) {} bool operator()(qreal val) { - if (future.isCanceled()) + if (m_promise.isCanceled()) return false; - future.setProgressValue(int(base + multiplier * val)); + m_promise.setProgressValue(int(m_base + m_multiplier * val)); return true; } private: - QFutureInterface &future; - int multiplier; - int base; + QPromise &m_promise; + int m_multiplier; + int m_base; }; -void ModelManagerInterface::parse(QFutureInterface &future, +void ModelManagerInterface::parse(QPromise &promise, const WorkingCopy &workingCopy, QList files, ModelManagerInterface *modelManager, @@ -1069,8 +1062,8 @@ void ModelManagerInterface::parse(QFutureInterface &future, bool emitDocChangedOnDisk) { const int progressMax = 100; - FutureReporter reporter(future, progressMax, 0); - future.setProgressRange(0, progressMax); + FutureReporter reporter(promise, progressMax, 0); + promise.setProgressRange(0, progressMax); // paths we have scanned for files and added to the files list QSet scannedPaths; @@ -1078,7 +1071,7 @@ void ModelManagerInterface::parse(QFutureInterface &future, QSet newLibraries; parseLoop(scannedPaths, newLibraries, workingCopy, std::move(files), modelManager, mainLanguage, emitDocChangedOnDisk, reporter); - future.setProgressValue(progressMax); + promise.setProgressValue(progressMax); } struct ScanItem { @@ -1087,11 +1080,20 @@ struct ScanItem { Dialect language = Dialect::AnyLanguage; }; -void ModelManagerInterface::importScan(QFutureInterface &future, - const ModelManagerInterface::WorkingCopy &workingCopy, +void ModelManagerInterface::importScan(const WorkingCopy &workingCopy, const PathsAndLanguages &paths, ModelManagerInterface *modelManager, - bool emitDocChangedOnDisk, bool libOnly, bool forceRescan) + bool emitDocChanged, bool libOnly, bool forceRescan) +{ + QPromise promise; + promise.start(); + importScanAsync(promise, workingCopy, paths, modelManager, emitDocChanged, libOnly, forceRescan); +} + +void ModelManagerInterface::importScanAsync(QPromise &promise, const WorkingCopy &workingCopy, + const PathsAndLanguages &paths, + ModelManagerInterface *modelManager, + bool emitDocChanged, bool libOnly, bool forceRescan) { // paths we have scanned for files and added to the files list QSet scannedPaths; @@ -1118,9 +1120,9 @@ void ModelManagerInterface::importScan(QFutureInterface &future, int progressRange = pathsToScan.size() * (1 << (2 + maxScanDepth)); int totalWork = progressRange; int workDone = 0; - future.setProgressRange(0, progressRange); // update max length while iterating? + promise.setProgressRange(0, progressRange); // update max length while iterating? const Snapshot snapshot = modelManager->snapshot(); - bool isCanceled = future.isCanceled(); + bool isCanceled = promise.isCanceled(); while (!pathsToScan.isEmpty() && !isCanceled) { ScanItem toScan = pathsToScan.last(); pathsToScan.pop_back(); @@ -1135,16 +1137,16 @@ void ModelManagerInterface::importScan(QFutureInterface &future, toScan.language.companionLanguages()); } workDone += 1; - future.setProgressValue(progressRange * workDone / totalWork); + promise.setProgressValue(progressRange * workDone / totalWork); if (!importedFiles.isEmpty()) { - FutureReporter reporter(future, progressRange * pathBudget / (4 * totalWork), + FutureReporter reporter(promise, progressRange * pathBudget / (4 * totalWork), progressRange * workDone / totalWork); parseLoop(scannedPaths, newLibraries, workingCopy, importedFiles, modelManager, - toScan.language, emitDocChangedOnDisk, reporter); // run in parallel?? + toScan.language, emitDocChanged, reporter); // run in parallel?? importedFiles.clear(); } workDone += pathBudget / 4 - 1; - future.setProgressValue(progressRange * workDone / totalWork); + promise.setProgressValue(progressRange * workDone / totalWork); } else { workDone += pathBudget / 4; } @@ -1159,10 +1161,10 @@ void ModelManagerInterface::importScan(QFutureInterface &future, } else { workDone += pathBudget * 3 / 4; } - future.setProgressValue(progressRange * workDone / totalWork); - isCanceled = future.isCanceled(); + promise.setProgressValue(progressRange * workDone / totalWork); + isCanceled = promise.isCanceled(); } - future.setProgressValue(progressRange); + promise.setProgressValue(progressRange); if (isCanceled) { // assume no work has been done QMutexLocker l(&modelManager->m_mutex); @@ -1206,8 +1208,8 @@ void ModelManagerInterface::maybeScan(const PathsAndLanguages &importPaths) } if (pathToScan.length() >= 1) { - QFuture result = Utils::runAsync(&m_threadPool, - &ModelManagerInterface::importScan, + QFuture result = Utils::asyncRun(&m_threadPool, + &ModelManagerInterface::importScanAsync, workingCopyInternal(), pathToScan, this, true, true, false); addFuture(result); @@ -1373,8 +1375,8 @@ void ModelManagerInterface::startCppQmlTypeUpdate() if (!cppModelManager) return; - m_cppQmlTypesUpdater = Utils::runAsync(&ModelManagerInterface::updateCppQmlTypes, - this, cppModelManager->snapshot(), m_queuedCppDocuments); + m_cppQmlTypesUpdater = Utils::asyncRun(&ModelManagerInterface::updateCppQmlTypes, this, + cppModelManager->snapshot(), m_queuedCppDocuments); m_queuedCppDocuments.clear(); } @@ -1415,13 +1417,12 @@ bool rescanExports(const QString &fileName, FindExportedCppTypes &finder, return hasNewInfo; } -void ModelManagerInterface::updateCppQmlTypes( - QFutureInterface &futureInterface, ModelManagerInterface *qmlModelManager, - const CPlusPlus::Snapshot &snapshot, +void ModelManagerInterface::updateCppQmlTypes(QPromise &promise, + ModelManagerInterface *qmlModelManager, const CPlusPlus::Snapshot &snapshot, const QHash> &documents) { - futureInterface.setProgressRange(0, documents.size()); - futureInterface.setProgressValue(0); + promise.setProgressRange(0, documents.size()); + promise.setProgressValue(0); CppDataHash newData; QHash> newDeclarations; @@ -1436,9 +1437,9 @@ void ModelManagerInterface::updateCppQmlTypes( bool hasNewInfo = false; using DocScanPair = QPair; for (const DocScanPair &pair : documents) { - if (futureInterface.isCanceled()) + if (promise.isCanceled()) return; - futureInterface.setProgressValue(futureInterface.progressValue() + 1); + promise.setProgressValue(promise.future().progressValue() + 1); CPlusPlus::Document::Ptr doc = pair.first; const bool scan = pair.second; diff --git a/src/libs/qmljs/qmljsmodelmanagerinterface.h b/src/libs/qmljs/qmljsmodelmanagerinterface.h index b7b88c24ff..e702b91afb 100644 --- a/src/libs/qmljs/qmljsmodelmanagerinterface.h +++ b/src/libs/qmljs/qmljsmodelmanagerinterface.h @@ -179,10 +179,12 @@ public: void addFuture(const QFuture &future); QmlJS::Document::Ptr ensuredGetDocumentForPath(const Utils::FilePath &filePath); - static void importScan(QFutureInterface &future, const WorkingCopy& workingCopyInternal, - const PathsAndLanguages& paths, ModelManagerInterface *modelManager, - bool emitDocChangedOnDisk, bool libOnly = true, - bool forceRescan = false); + static void importScan(const WorkingCopy &workingCopy, const PathsAndLanguages &paths, + ModelManagerInterface *modelManager, bool emitDocChanged, + bool libOnly = true, bool forceRescan = false); + static void importScanAsync(QPromise &promise, const WorkingCopy& workingCopyInternal, + const PathsAndLanguages& paths, ModelManagerInterface *modelManager, + bool emitDocChanged, bool libOnly = true, bool forceRescan = false); virtual void resetCodeModel(); void removeProjectInfo(ProjectExplorer::Project *project); @@ -218,16 +220,15 @@ protected: QmlJS::Dialect mainLanguage, bool emitDocChangedOnDisk, const std::function &reportProgress); - static void parse(QFutureInterface &future, + static void parse(QPromise &promise, const WorkingCopy &workingCopyInternal, QList files, ModelManagerInterface *modelManager, QmlJS::Dialect mainLanguage, bool emitDocChangedOnDisk); - static void updateCppQmlTypes( - QFutureInterface &futureInterface, ModelManagerInterface *qmlModelManager, - const CPlusPlus::Snapshot &snapshot, - const QHash> &documents); + static void updateCppQmlTypes(QPromise &promise, ModelManagerInterface *qmlModelManager, + const CPlusPlus::Snapshot &snapshot, + const QHash> &documents); void maybeScan(const PathsAndLanguages &importPaths); void updateImportPaths(); diff --git a/src/libs/qmljs/qmljsplugindumper.cpp b/src/libs/qmljs/qmljsplugindumper.cpp index a7bafdde94..20736803d2 100644 --- a/src/libs/qmljs/qmljsplugindumper.cpp +++ b/src/libs/qmljs/qmljsplugindumper.cpp @@ -7,9 +7,9 @@ #include "qmljsmodelmanagerinterface.h" #include "qmljstr.h" #include "qmljsutils.h" -#include "qmljsviewercontext.h" #include +#include #include #include #include @@ -273,14 +273,13 @@ void PluginDumper::qmlPluginTypeDumpDone(QtcProcess *process) QStringList dependencies; }; - auto future = Utils::runAsync(m_modelManager->threadPool(), - [output, libraryPath](QFutureInterface& future) - { + auto future = Utils::asyncRun(m_modelManager->threadPool(), + [output, libraryPath](QPromise &promise) { CppQmlTypesInfo infos; - CppQmlTypesLoader::parseQmlTypeDescriptions(output, &infos.objectsList, &infos.moduleApis, &infos.dependencies, - &infos.error, &infos.warning, - "'); - future.reportFinished(&infos); + CppQmlTypesLoader::parseQmlTypeDescriptions(output, &infos.objectsList, + &infos.moduleApis, &infos.dependencies, &infos.error, &infos.warning, + "'); + promise.addResult(infos); }); m_modelManager->addFuture(future); @@ -327,8 +326,8 @@ void PluginDumper::pluginChanged(const QString &pluginLibrary) QFuture PluginDumper::loadQmlTypeDescription(const FilePaths &paths) const { - auto future = Utils::runAsync(m_modelManager->threadPool(), [=](QFutureInterface &future) - { + auto future = Utils::asyncRun(m_modelManager->threadPool(), + [=](QPromise &promise) { PluginDumper::QmlTypeDescription result; for (const FilePath &p: paths) { @@ -355,8 +354,7 @@ QFuture PluginDumper::loadQmlTypeDescription(c if (!warning.isEmpty()) result.warnings += warning; } - - future.reportFinished(&result); + promise.addResult(result); }); m_modelManager->addFuture(future); -- cgit v1.2.3 From 8778eaaaa06c669b35377ca8b61d509444acdb86 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Tue, 14 Mar 2023 14:39:24 +0100 Subject: FileIconProvider: Improve performance of icon(FilePath) By re-using the caching QFileInfo variant for icon(FilePath). QFileInfo/FSEngine caches the result of isDir etc for a while, which improves performance for slower devices in case that is called for the same file paths over and over again, like it is the case for locator. The cache might be out of date for some time when things change on disk, but for a cosmetic property like an icon that is not a big deal. (And it would be a file that transforms into a directory or vice versa, which is not very common either.) Change-Id: I663ac7e81f8f2996b87591dad17c5483e3f4ad3c Reviewed-by: Marcus Tillmanns --- src/libs/utils/fsengine/fileiconprovider.cpp | 41 +--------------------------- 1 file changed, 1 insertion(+), 40 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/fsengine/fileiconprovider.cpp b/src/libs/utils/fsengine/fileiconprovider.cpp index e5ed4a1281..07ea4b3f5a 100644 --- a/src/libs/utils/fsengine/fileiconprovider.cpp +++ b/src/libs/utils/fsengine/fileiconprovider.cpp @@ -216,46 +216,7 @@ QIcon FileIconProviderImplementation::icon(const FilePath &filePath) const { qCDebug(fileIconProvider) << "FileIconProvider::icon" << filePath.absoluteFilePath(); - if (filePath.isEmpty()) - return unknownFileIcon(); - - // Check if its one of the virtual devices directories - if (filePath.path().startsWith(FilePath::specialRootPath())) { - // If the filepath does not need a device, it is a virtual device directory - if (!filePath.needsDevice()) - return dirIcon(); - } - - bool isDir = filePath.isDir(); - - // Check for cached overlay icons by file suffix. - const QString filename = !isDir ? filePath.fileName() : QString(); - if (!filename.isEmpty()) { - const std::optional icon = getIcon(m_filenameCache, filename); - if (icon) - return *icon; - } - - const QString suffix = !isDir ? filePath.suffix() : QString(); - if (!suffix.isEmpty()) { - const std::optional icon = getIcon(m_suffixCache, suffix); - if (icon) - return *icon; - } - - if (filePath.needsDevice()) - return isDir ? dirIcon() : unknownFileIcon(); - - // Get icon from OS (and cache it based on suffix!) - QIcon icon; - if (HostOsInfo::isWindowsHost() || HostOsInfo::isMacHost()) - icon = QFileIconProvider::icon(filePath.toFileInfo()); - else // File icons are unknown on linux systems. - icon = isDir ? QFileIconProvider::icon(filePath.toFileInfo()) : unknownFileIcon(); - - if (!isDir && !suffix.isEmpty()) - m_suffixCache.insert(suffix, icon); - return icon; + return icon(QFileInfo(filePath.toFSPathString())); } /*! -- cgit v1.2.3 From 2d15be91bf8bab5ce5307b6e16ca1c13ecbdcf8f Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Fri, 10 Mar 2023 16:29:41 +0100 Subject: ProjectExplorer: Consolidate ProcessList * Combined local and ssh process list retrieval into LocalProcessList * Combined QnxProcessList into LocalProcessList * Renamed LocalProcessList to ProcessList Change-Id: I230c575375e306c638e4ca3034fa2d7ed243a44c Reviewed-by: David Schulz Reviewed-by: hjk --- src/libs/utils/processinfo.cpp | 175 ++++++++++++++++++++++++----------------- src/libs/utils/processinfo.h | 4 +- 2 files changed, 107 insertions(+), 72 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/processinfo.cpp b/src/libs/utils/processinfo.cpp index 366800d316..d6ecd4d234 100644 --- a/src/libs/utils/processinfo.cpp +++ b/src/libs/utils/processinfo.cpp @@ -3,14 +3,13 @@ #include "processinfo.h" +#include "algorithm.h" #include "qtcprocess.h" -#if defined(Q_OS_UNIX) #include -#include -#include -#include -#include +#include + +#if defined(Q_OS_UNIX) #elif defined(Q_OS_WIN) #include "winutils.h" #ifdef QTCREATOR_PCH_H @@ -32,82 +31,64 @@ bool ProcessInfo::operator<(const ProcessInfo &other) const return commandLine < other.commandLine; } -#if defined(Q_OS_UNIX) +// Determine UNIX processes by reading "/proc". Default to ps if +// it does not exist -static bool isUnixProcessId(const QString &procname) +static QList getLocalProcessesUsingProc(const FilePath &procDir) { - for (int i = 0; i != procname.size(); ++i) - if (!procname.at(i).isDigit()) - return false; - return true; -} + static const QString execs = "-exec test -f {}/exe \\; " + "-exec test -f {}/cmdline \\; " + "-exec echo -en 'p{}\\ne' \\; " + "-exec readlink {}/exe \\; " + "-exec echo -n c \\; " + "-exec head -n 1 {}/cmdline \\; " + "-exec echo \\; " + "-exec echo __SKIP_ME__ \\;"; -// Determine UNIX processes by reading "/proc". Default to ps if -// it does not exist + CommandLine cmd{procDir.withNewPath("find"), + {procDir.nativePath(), "-maxdepth", "1", "-type", "d", "-name", "[0-9]*"}}; -static const char procDirC[] = "/proc/"; + cmd.addArgs(execs, CommandLine::Raw); + + QtcProcess procProcess; + procProcess.setCommand(cmd); + procProcess.runBlocking(); -static QList getLocalProcessesUsingProc() -{ QList processes; - const QString procDirPath = QLatin1String(procDirC); - const QDir procDir = QDir(QLatin1String(procDirC)); - const QStringList procIds = procDir.entryList(); - for (const QString &procId : procIds) { - if (!isUnixProcessId(procId)) - continue; - ProcessInfo proc; - proc.processId = procId.toInt(); - const QString root = procDirPath + procId; - - const QFile exeFile(root + QLatin1String("/exe")); - proc.executable = exeFile.symLinkTarget(); - - QFile cmdLineFile(root + QLatin1String("/cmdline")); - if (cmdLineFile.open(QIODevice::ReadOnly)) { // process may have exited - const QList tokens = cmdLineFile.readAll().split('\0'); - if (!tokens.isEmpty()) { - if (proc.executable.isEmpty()) - proc.executable = QString::fromLocal8Bit(tokens.front()); - for (const QByteArray &t : tokens) { - if (!proc.commandLine.isEmpty()) - proc.commandLine.append(QLatin1Char(' ')); - proc.commandLine.append(QString::fromLocal8Bit(t)); - } - } - } - if (proc.executable.isEmpty()) { - QFile statFile(root + QLatin1String("/stat")); - if (statFile.open(QIODevice::ReadOnly)) { - const QStringList data = QString::fromLocal8Bit(statFile.readAll()).split(QLatin1Char(' ')); - if (data.size() < 2) - continue; - proc.executable = data.at(1); - proc.commandLine = data.at(1); // PPID is element 3 - if (proc.executable.startsWith(QLatin1Char('(')) && proc.executable.endsWith(QLatin1Char(')'))) { - proc.executable.truncate(proc.executable.size() - 1); - proc.executable.remove(0, 1); - } - } + const auto lines = procProcess.readAllStandardOutput().split('\n'); + for (auto it = lines.begin(); it != lines.end(); ++it) { + if (it->startsWith('p')) { + ProcessInfo proc; + bool ok; + proc.processId = FilePath::fromUserInput(it->mid(1).trimmed()).fileName().toInt(&ok); + QTC_ASSERT(ok, continue); + ++it; + + QTC_ASSERT(it->startsWith('e'), continue); + proc.executable = it->mid(1).trimmed(); + ++it; + + QTC_ASSERT(it->startsWith('c'), continue); + proc.commandLine = it->mid(1).trimmed().replace('\0', ' '); + if (!proc.commandLine.contains("__SKIP_ME__")) + processes.append(proc); } - if (!proc.executable.isEmpty()) - processes.push_back(proc); } + return processes; } // Determine UNIX processes by running ps -static QMap getLocalProcessDataUsingPs(const QString &column) +static QMap getLocalProcessDataUsingPs(const FilePath &deviceRoot, + const QString &column) { QtcProcess process; - process.setCommand({"ps", {"-e", "-o", "pid," + column}}); - process.start(); - if (!process.waitForFinished()) - return {}; + process.setCommand({deviceRoot.withNewPath("ps"), {"-e", "-o", "pid," + column}}); + process.runBlocking(); // Split "457 /Users/foo.app arg1 arg2" - const QStringList lines = process.stdOut().split(QLatin1Char('\n')); + const QStringList lines = process.readAllStandardOutput().split(QLatin1Char('\n')); QMap result; for (int i = 1; i < lines.size(); ++i) { // Skip header const QString line = lines.at(i).trimmed(); @@ -118,14 +99,14 @@ static QMap getLocalProcessDataUsingPs(const QString &column) return result; } -static QList getLocalProcessesUsingPs() +static QList getLocalProcessesUsingPs(const FilePath &deviceRoot) { QList processes; // cmdLines are full command lines, usually with absolute path, // exeNames only the file part of the executable's path. - const QMap exeNames = getLocalProcessDataUsingPs("comm"); - const QMap cmdLines = getLocalProcessDataUsingPs("args"); + const QMap exeNames = getLocalProcessDataUsingPs(deviceRoot, "comm"); + const QMap cmdLines = getLocalProcessDataUsingPs(deviceRoot, "args"); for (auto it = exeNames.begin(), end = exeNames.end(); it != end; ++it) { const qint64 pid = it.key(); @@ -146,16 +127,68 @@ static QList getLocalProcessesUsingPs() return processes; } -QList ProcessInfo::processInfoList() +static QList getProcessesUsingPidin(const FilePath &pidin) { - const QDir procDir = QDir(QLatin1String(procDirC)); - return procDir.exists() ? getLocalProcessesUsingProc() : getLocalProcessesUsingPs(); + QtcProcess process; + process.setCommand({pidin, {"-F", "%a %A {/%n}"}}); + process.runBlocking(); + + QList processes; + QStringList lines = process.readAllStandardOutput().split(QLatin1Char('\n')); + if (lines.isEmpty()) + return processes; + + lines.pop_front(); // drop headers + const QRegularExpression re("\\s*(\\d+)\\s+(.*){(.*)}"); + + for (const QString &line : std::as_const(lines)) { + const QRegularExpressionMatch match = re.match(line); + if (match.hasMatch()) { + const QStringList captures = match.capturedTexts(); + if (captures.size() == 4) { + const int pid = captures[1].toInt(); + const QString args = captures[2]; + const QString exe = captures[3]; + ProcessInfo deviceProcess; + deviceProcess.processId = pid; + deviceProcess.executable = exe.trimmed(); + deviceProcess.commandLine = args.trimmed(); + processes.append(deviceProcess); + } + } + } + + return Utils::sorted(std::move(processes)); +} + +static QList processInfoListUnix(const FilePath &deviceRoot) +{ + const FilePath procDir = deviceRoot.withNewPath("/proc"); + const FilePath pidin = deviceRoot.withNewPath("pidin").searchInPath(); + + if (pidin.isExecutableFile()) + return getProcessesUsingPidin(pidin); + + if (procDir.isReadableDir()) + return getLocalProcessesUsingProc(procDir); + + return getLocalProcessesUsingPs(deviceRoot); +} + +#if defined(Q_OS_UNIX) + +QList ProcessInfo::processInfoList(const FilePath &deviceRoot) +{ + return processInfoListUnix(deviceRoot); } #elif defined(Q_OS_WIN) -QList ProcessInfo::processInfoList() +QList ProcessInfo::processInfoList(const FilePath &deviceRoot) { + if (deviceRoot.needsDevice()) + return processInfoListUnix(deviceRoot); + QList processes; PROCESSENTRY32 pe; diff --git a/src/libs/utils/processinfo.h b/src/libs/utils/processinfo.h index 47fd2d6d0d..90c1a97374 100644 --- a/src/libs/utils/processinfo.h +++ b/src/libs/utils/processinfo.h @@ -5,6 +5,8 @@ #include "utils_global.h" +#include "filepath.h" + #include #include @@ -19,7 +21,7 @@ public: bool operator<(const ProcessInfo &other) const; - static QList processInfoList(); + static QList processInfoList(const Utils::FilePath &deviceRoot = Utils::FilePath()); }; } // namespace Utils -- cgit v1.2.3 From 8f1cbd2e5212986faa8222c8097d9cab670b03eb Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Mon, 13 Mar 2023 13:56:50 +0100 Subject: Utils: Improve and test CommandLine::fromUserInput Change-Id: Ia18f5b01d91200e6ad65735496395215c6393533 Reviewed-by: Qt CI Bot Reviewed-by: Reviewed-by: hjk --- src/libs/utils/commandline.cpp | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/commandline.cpp b/src/libs/utils/commandline.cpp index 81285259ac..e19eafa4c1 100644 --- a/src/libs/utils/commandline.cpp +++ b/src/libs/utils/commandline.cpp @@ -1429,16 +1429,20 @@ CommandLine::CommandLine(const FilePath &exe, const QString &args, RawType) CommandLine CommandLine::fromUserInput(const QString &cmdline, MacroExpander *expander) { - CommandLine cmd; - const int pos = cmdline.indexOf(' '); - if (pos == -1) { - cmd.m_executable = FilePath::fromString(cmdline); - } else { - cmd.m_executable = FilePath::fromString(cmdline.left(pos)); - cmd.m_arguments = cmdline.right(cmdline.length() - pos - 1); - if (expander) - cmd.m_arguments = expander->expand(cmd.m_arguments); - } + if (cmdline.isEmpty()) + return {}; + + QString input = cmdline.trimmed(); + + QStringList result = ProcessArgs::splitArgs(cmdline, HostOsInfo::hostOs()); + + if (result.isEmpty()) + return {}; + + auto cmd = CommandLine(FilePath::fromUserInput(result.value(0)), result.mid(1)); + if (expander) + cmd.m_arguments = expander->expand(cmd.m_arguments); + return cmd; } -- cgit v1.2.3 From e093c39727b3a31ef9c89e9f571edcfe4c163e35 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Sun, 12 Mar 2023 20:26:00 +0100 Subject: Utils: Better error reporting for copyFile Change-Id: Id76280e83e9dae92bff2db5722f1e582867e1566 Reviewed-by: David Schulz Reviewed-by: --- src/libs/utils/devicefileaccess.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/devicefileaccess.cpp b/src/libs/utils/devicefileaccess.cpp index 1dd035d916..5cec46b9e2 100644 --- a/src/libs/utils/devicefileaccess.cpp +++ b/src/libs/utils/devicefileaccess.cpp @@ -565,10 +565,13 @@ bool DesktopDeviceFileAccess::removeRecursively(const FilePath &filePath, QStrin expected_str DesktopDeviceFileAccess::copyFile(const FilePath &filePath, const FilePath &target) const { - if (QFile::copy(filePath.path(), target.path())) + QFile srcFile(filePath.path()); + + if (srcFile.copy(target.path())) return {}; - return make_unexpected(Tr::tr("Failed to copy file \"%1\" to \"%2\".") - .arg(filePath.toUserOutput(), target.toUserOutput())); + return make_unexpected( + Tr::tr("Failed to copy file \"%1\" to \"%2\": %3") + .arg(filePath.toUserOutput(), target.toUserOutput(), srcFile.errorString())); } bool DesktopDeviceFileAccess::renameFile(const FilePath &filePath, const FilePath &target) const -- cgit v1.2.3 From 666f3258ba0e6ed0b8068da32cc748b98383eaf2 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Fri, 10 Mar 2023 14:49:55 +0100 Subject: Docker: Fix shell exit handling Previously if a docker container was removed outside of Qt Creator the shell would not be correctly reset by the dockerdevice and therefor cause a hang. Change-Id: I5e84f7c114e525c732f45b701277736d6acc7a11 Reviewed-by: hjk Reviewed-by: --- src/libs/utils/deviceshell.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src/libs') diff --git a/src/libs/utils/deviceshell.cpp b/src/libs/utils/deviceshell.cpp index 283e9789c0..e8675745c0 100644 --- a/src/libs/utils/deviceshell.cpp +++ b/src/libs/utils/deviceshell.cpp @@ -98,6 +98,7 @@ RunResult DeviceShell::run(const CommandLine &cmd, const QByteArray &stdInData) const RunResult errorResult{-1, {}, {}}; QTC_ASSERT(m_shellProcess, return errorResult); + QTC_ASSERT(m_shellProcess->isRunning(), return errorResult); QTC_ASSERT(m_shellScriptState == State::Succeeded, return errorResult); QMutexLocker lk(&m_commandMutex); -- cgit v1.2.3 From a43c20969cad7733268f5bb5d47a0eb3aad885e7 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Tue, 14 Mar 2023 12:10:59 +0100 Subject: QtcProcess: Remove TerminalMode::Pty enum value Make Pty::Data optional. When set, the PtyProcessImpl implementation is implied. Change-Id: I7990e9d9016223e6597d876a5d0c4ed177365874 Reviewed-by: Marcus Tillmanns --- src/libs/utils/processenums.h | 1 - src/libs/utils/processinterface.h | 2 +- src/libs/utils/qtcprocess.cpp | 21 +++++++++++---------- src/libs/utils/qtcprocess.h | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/processenums.h b/src/libs/utils/processenums.h index 1c16f22dcb..6ea37a2d37 100644 --- a/src/libs/utils/processenums.h +++ b/src/libs/utils/processenums.h @@ -24,7 +24,6 @@ enum class ProcessImpl { enum class TerminalMode { Off, - Pty, Run, Debug, Suspend, diff --git a/src/libs/utils/processinterface.h b/src/libs/utils/processinterface.h index 210df7d268..5f3a4378d4 100644 --- a/src/libs/utils/processinterface.h +++ b/src/libs/utils/processinterface.h @@ -50,7 +50,7 @@ public: ProcessMode m_processMode = ProcessMode::Reader; TerminalMode m_terminalMode = TerminalMode::Off; - Pty::Data m_ptyData; + std::optional m_ptyData; CommandLine m_commandLine; FilePath m_workingDirectory; Environment m_environment; diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/qtcprocess.cpp index 4808764ddd..a5de95d29d 100644 --- a/src/libs/utils/qtcprocess.cpp +++ b/src/libs/utils/qtcprocess.cpp @@ -310,7 +310,7 @@ private: class PtyProcessImpl final : public DefaultImpl { public: - ~PtyProcessImpl() { m_setup.m_ptyData.setResizeHandler({}); } + ~PtyProcessImpl() { QTC_CHECK(m_setup.m_ptyData); m_setup.m_ptyData->setResizeHandler({}); } qint64 write(const QByteArray &data) final { @@ -338,7 +338,8 @@ public: void doDefaultStart(const QString &program, const QStringList &arguments) final { - m_setup.m_ptyData.setResizeHandler([this](const QSize &size) { + QTC_CHECK(m_setup.m_ptyData); + m_setup.m_ptyData->setResizeHandler([this](const QSize &size) { if (m_ptyProcess) m_ptyProcess->resize(size.width(), size.height()); }); @@ -357,8 +358,8 @@ public: arguments, m_setup.m_workingDirectory.path(), m_setup.m_environment.toProcessEnvironment().toStringList(), - m_setup.m_ptyData.size().width(), - m_setup.m_ptyData.size().height()); + m_setup.m_ptyData->size().width(), + m_setup.m_ptyData->size().height()); if (!startResult) { const ProcessResultData result = {-1, @@ -725,16 +726,16 @@ public: ProcessInterface *createProcessInterface() { - if (m_setup.m_terminalMode == TerminalMode::Pty) - return new PtyProcessImpl(); + if (m_setup.m_ptyData) + return new PtyProcessImpl; if (m_setup.m_terminalMode != TerminalMode::Off) return Terminal::Hooks::instance().createTerminalProcessInterfaceHook()(); const ProcessImpl impl = m_setup.m_processImpl == ProcessImpl::Default ? defaultProcessImpl() : m_setup.m_processImpl; if (impl == ProcessImpl::QProcess) - return new QProcessImpl(); - return new ProcessLauncherImpl(); + return new QProcessImpl; + return new ProcessLauncherImpl; } void setProcessInterface(ProcessInterface *process) @@ -1123,12 +1124,12 @@ void QtcProcess::setProcessImpl(ProcessImpl processImpl) d->m_setup.m_processImpl = processImpl; } -void QtcProcess::setPtyData(const Pty::Data &data) +void QtcProcess::setPtyData(const std::optional &data) { d->m_setup.m_ptyData = data; } -Pty::Data QtcProcess::ptyData() const +std::optional QtcProcess::ptyData() const { return d->m_setup.m_ptyData; } diff --git a/src/libs/utils/qtcprocess.h b/src/libs/utils/qtcprocess.h index 7218105149..9d539f08e2 100644 --- a/src/libs/utils/qtcprocess.h +++ b/src/libs/utils/qtcprocess.h @@ -77,8 +77,8 @@ public: void setProcessImpl(ProcessImpl processImpl); - void setPtyData(const Pty::Data &data); - Pty::Data ptyData() const; + void setPtyData(const std::optional &data); + std::optional ptyData() const; void setTerminalMode(TerminalMode mode); TerminalMode terminalMode() const; -- cgit v1.2.3 From 958db5a1440284a4332ced14e2a29ab92bedfba3 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 15 Mar 2023 15:12:56 +0100 Subject: TerminalProcess: Fix merge conflict Amends a43c20969cad7733268f5bb5d47a0eb3aad885e7 Change-Id: Ib94845dfb3a7a17a86d5a07ad6501a3c3cb905b2 Reviewed-by: Marcus Tillmanns --- src/libs/utils/terminalprocess.cpp | 2 -- 1 file changed, 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/terminalprocess.cpp b/src/libs/utils/terminalprocess.cpp index 91423539a3..0070a4db2c 100644 --- a/src/libs/utils/terminalprocess.cpp +++ b/src/libs/utils/terminalprocess.cpp @@ -44,8 +44,6 @@ namespace Internal { static QString modeOption(TerminalMode m) { switch (m) { - case TerminalMode::Pty: - return "pty"; case TerminalMode::Run: return "run"; case TerminalMode::Debug: -- cgit v1.2.3 From bd52e53dbfa7641098313edd56d885df69916f9f Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Fri, 10 Mar 2023 13:55:17 +0100 Subject: Terminal: Add shell integration Change-Id: Ic1e226b56f0103e5a6e7764073ab7ab241b67baa Reviewed-by: Cristian Adam --- src/libs/utils/fileutils.cpp | 1 - src/libs/utils/fileutils.h | 1 - src/libs/utils/qtcprocess.cpp | 4 +++- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index c0a574f9c3..4bd0692e13 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -5,7 +5,6 @@ #include "savefile.h" #include "algorithm.h" -#include "hostosinfo.h" #include "qtcassert.h" #include "utilstr.h" diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index 19f17a6d71..a1a7ffef97 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -121,7 +121,6 @@ public: QString *selectedFilter = nullptr, QFileDialog::Options options = {}); #endif - }; // for actually finding out if e.g. directories are writable on Windows diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/qtcprocess.cpp index a5de95d29d..21b1684242 100644 --- a/src/libs/utils/qtcprocess.cpp +++ b/src/libs/utils/qtcprocess.cpp @@ -355,7 +355,9 @@ public: bool startResult = m_ptyProcess->startProcess(program, - arguments, + HostOsInfo::isWindowsHost() + ? QStringList{m_setup.m_nativeArguments} << arguments + : arguments, m_setup.m_workingDirectory.path(), m_setup.m_environment.toProcessEnvironment().toStringList(), m_setup.m_ptyData->size().width(), -- cgit v1.2.3 From 44655995d124a34db64b32d2d21f87f0fe387527 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Fri, 17 Mar 2023 11:12:26 +0100 Subject: FilePath: Provide overloads for async tasks taking context object Change-Id: I0bb2f2bfc0f54e8a81efb7d9279d539bcdfd9bc9 Reviewed-by: hjk --- src/libs/utils/filepath.cpp | 17 +++++++++++++++++ src/libs/utils/filepath.h | 5 +++++ 2 files changed, 22 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 12796afec9..6e6ffe16fb 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -642,16 +642,33 @@ FileStreamHandle FilePath::asyncCopy(const FilePath &target, const CopyContinuat return FileStreamerManager::copy(*this, target, cont); } +FileStreamHandle FilePath::asyncCopy(const FilePath &target, QObject *context, + const CopyContinuation &cont) const +{ + return FileStreamerManager::copy(*this, target, context, cont); +} + FileStreamHandle FilePath::asyncRead(const ReadContinuation &cont) const { return FileStreamerManager::read(*this, cont); } +FileStreamHandle FilePath::asyncRead(QObject *context, const ReadContinuation &cont) const +{ + return FileStreamerManager::read(*this, context, cont); +} + FileStreamHandle FilePath::asyncWrite(const QByteArray &data, const WriteContinuation &cont) const { return FileStreamerManager::write(*this, data, cont); } +FileStreamHandle FilePath::asyncWrite(const QByteArray &data, QObject *context, + const WriteContinuation &cont) const +{ + return FileStreamerManager::write(*this, data, context, cont); +} + bool FilePath::needsDevice() const { return m_schemeLen != 0; diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h index 11fd8e10e2..ee17361570 100644 --- a/src/libs/utils/filepath.h +++ b/src/libs/utils/filepath.h @@ -211,8 +211,13 @@ public: // Asynchronous interface FileStreamHandle asyncCopy(const FilePath &target, const CopyContinuation &cont = {}) const; + FileStreamHandle asyncCopy(const FilePath &target, QObject *context, + const CopyContinuation &cont = {}) const; FileStreamHandle asyncRead(const ReadContinuation &cont = {}) const; + FileStreamHandle asyncRead(QObject *context, const ReadContinuation &cont = {}) const; FileStreamHandle asyncWrite(const QByteArray &data, const WriteContinuation &cont = {}) const; + FileStreamHandle asyncWrite(const QByteArray &data, QObject *context, + const WriteContinuation &cont = {}) const; // Prefer not to use // Using needsDevice() in "user" code is likely to result in code that -- cgit v1.2.3 From 81bd8e3bd89712fad41077f7ee0a67326b0ba4f2 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Fri, 17 Mar 2023 11:17:51 +0100 Subject: FilePath: Remove overloads for async tasks that don't take context Passing mandatory context object clearly suggests a special care should be taken for assuring the passed function may still run when the task finishes in the future. Fix FileStreamManager so that it deletes the streamer even when context object was deleted in meantime. Fix 2 usages of asyncCopy so that we pass a context object now. Side note: passing nullptr as a context object is still possible and should work, but it's not recommended. Change-Id: I464438db42ed9292c2f89ecb9d5dde7c78f77640 Reviewed-by: Qt CI Bot Reviewed-by: hjk --- src/libs/utils/filepath.cpp | 15 --------------- src/libs/utils/filepath.h | 3 --- src/libs/utils/filestreamermanager.cpp | 9 +++++---- 3 files changed, 5 insertions(+), 22 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 6e6ffe16fb..f117f4e868 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -637,32 +637,17 @@ FilePathInfo FilePath::filePathInfo() const return fileAccess()->filePathInfo(*this); } -FileStreamHandle FilePath::asyncCopy(const FilePath &target, const CopyContinuation &cont) const -{ - return FileStreamerManager::copy(*this, target, cont); -} - FileStreamHandle FilePath::asyncCopy(const FilePath &target, QObject *context, const CopyContinuation &cont) const { return FileStreamerManager::copy(*this, target, context, cont); } -FileStreamHandle FilePath::asyncRead(const ReadContinuation &cont) const -{ - return FileStreamerManager::read(*this, cont); -} - FileStreamHandle FilePath::asyncRead(QObject *context, const ReadContinuation &cont) const { return FileStreamerManager::read(*this, context, cont); } -FileStreamHandle FilePath::asyncWrite(const QByteArray &data, const WriteContinuation &cont) const -{ - return FileStreamerManager::write(*this, data, cont); -} - FileStreamHandle FilePath::asyncWrite(const QByteArray &data, QObject *context, const WriteContinuation &cont) const { diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h index ee17361570..877e8beb8e 100644 --- a/src/libs/utils/filepath.h +++ b/src/libs/utils/filepath.h @@ -210,12 +210,9 @@ public: static void sort(FilePaths &files); // Asynchronous interface - FileStreamHandle asyncCopy(const FilePath &target, const CopyContinuation &cont = {}) const; FileStreamHandle asyncCopy(const FilePath &target, QObject *context, const CopyContinuation &cont = {}) const; - FileStreamHandle asyncRead(const ReadContinuation &cont = {}) const; FileStreamHandle asyncRead(QObject *context, const ReadContinuation &cont = {}) const; - FileStreamHandle asyncWrite(const QByteArray &data, const WriteContinuation &cont = {}) const; FileStreamHandle asyncWrite(const QByteArray &data, QObject *context, const WriteContinuation &cont = {}) const; diff --git a/src/libs/utils/filestreamermanager.cpp b/src/libs/utils/filestreamermanager.cpp index 051f114578..11d05faee5 100644 --- a/src/libs/utils/filestreamermanager.cpp +++ b/src/libs/utils/filestreamermanager.cpp @@ -99,10 +99,11 @@ FileStreamHandle execute(const std::function &onSetup, onSetup(streamer); const FileStreamHandle handle = generateUniqueHandle(); QTC_CHECK(context == nullptr || context->thread() == QThread::currentThread()); - QObject *finalContext = context ? context : streamer; - QObject::connect(streamer, &FileStreamer::done, finalContext, [=] { - if (onDone) - onDone(streamer); + if (onDone) { + QObject *finalContext = context ? context : streamer; + QObject::connect(streamer, &FileStreamer::done, finalContext, [=] { onDone(streamer); }); + } + QObject::connect(streamer, &FileStreamer::done, streamer, [=] { removeStreamer(handle); streamer->deleteLater(); }); -- cgit v1.2.3 From 0870f2583bbc659df00ff65bf51918b940221665 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Tue, 7 Mar 2023 17:55:38 +0100 Subject: Terminal: Enable TerminalProcessInterface Adds a new helper app "process_stub" that replaces the previous. "process_stub_unix/win". The purpose was and is to allow processes to be "injected" into other hosts apps like terminals while still being able to control and debug them. A new base class called "TerminalInterface" is used for both the new Terminal plugin and the legacy TerminalProcess implementation. Fixes: QTCREATORBUG-16364 Change-Id: If21273fe53ad545d1a768c17c83db4bf2fd85395 Reviewed-by: Christian Stenger Reviewed-by: Jarek Kobus Reviewed-by: hjk --- src/libs/3rdparty/libptyqt/.clang-format | 1 + src/libs/3rdparty/libptyqt/conptyprocess.h | 8 +- src/libs/libs.qbs | 1 - src/libs/utils/CMakeLists.txt | 20 +- src/libs/utils/process_stub.qbs | 21 - src/libs/utils/process_stub_unix.c | 340 -------------- src/libs/utils/process_stub_win.c | 204 -------- src/libs/utils/processenums.h | 1 - src/libs/utils/qtcprocess.cpp | 2 +- src/libs/utils/terminalhooks.cpp | 66 ++- src/libs/utils/terminalhooks.h | 2 + src/libs/utils/terminalinterface.cpp | 400 ++++++++++++++++ src/libs/utils/terminalinterface.h | 61 +++ src/libs/utils/terminalprocess.cpp | 721 ----------------------------- src/libs/utils/terminalprocess_p.h | 54 --- src/libs/utils/utils.qbs | 4 +- 16 files changed, 534 insertions(+), 1372 deletions(-) create mode 100644 src/libs/3rdparty/libptyqt/.clang-format delete mode 100644 src/libs/utils/process_stub.qbs delete mode 100644 src/libs/utils/process_stub_unix.c delete mode 100644 src/libs/utils/process_stub_win.c create mode 100644 src/libs/utils/terminalinterface.cpp create mode 100644 src/libs/utils/terminalinterface.h delete mode 100644 src/libs/utils/terminalprocess.cpp delete mode 100644 src/libs/utils/terminalprocess_p.h (limited to 'src/libs') diff --git a/src/libs/3rdparty/libptyqt/.clang-format b/src/libs/3rdparty/libptyqt/.clang-format new file mode 100644 index 0000000000..b861ff7a95 --- /dev/null +++ b/src/libs/3rdparty/libptyqt/.clang-format @@ -0,0 +1 @@ +{ "DisableFormat" : true } \ No newline at end of file diff --git a/src/libs/3rdparty/libptyqt/conptyprocess.h b/src/libs/3rdparty/libptyqt/conptyprocess.h index a22b6290c7..ac94048981 100644 --- a/src/libs/3rdparty/libptyqt/conptyprocess.h +++ b/src/libs/3rdparty/libptyqt/conptyprocess.h @@ -154,15 +154,15 @@ private: private: WindowsContext m_winContext; - HPCON m_ptyHandler; - HANDLE m_hPipeIn, m_hPipeOut; + HPCON m_ptyHandler{INVALID_HANDLE_VALUE}; + HANDLE m_hPipeIn{INVALID_HANDLE_VALUE}, m_hPipeOut{INVALID_HANDLE_VALUE}; - QThread *m_readThread; + QThread *m_readThread{nullptr}; QMutex m_bufferMutex; PtyBuffer m_buffer; bool m_aboutToDestruct{false}; PROCESS_INFORMATION m_shellProcessInformation{}; - QWinEventNotifier* m_shellCloseWaitNotifier; + QWinEventNotifier *m_shellCloseWaitNotifier{nullptr}; STARTUPINFOEX m_shellStartupInfo{}; }; diff --git a/src/libs/libs.qbs b/src/libs/libs.qbs index f9f58565ac..141e2a6547 100644 --- a/src/libs/libs.qbs +++ b/src/libs/libs.qbs @@ -21,7 +21,6 @@ Project { "qtcreatorcdbext/qtcreatorcdbext.qbs", "sqlite/sqlite.qbs", "tracing/tracing.qbs", - "utils/process_stub.qbs", "utils/process_ctrlc_stub.qbs", "utils/utils.qbs", "3rdparty/libptyqt/ptyqt.qbs", diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index e296f2857a..ab55a0deec 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -173,7 +173,7 @@ add_qtc_library(Utils temporaryfile.cpp temporaryfile.h terminalcommand.cpp terminalcommand.h terminalhooks.cpp terminalhooks.h - terminalprocess.cpp terminalprocess_p.h + terminalinterface.cpp terminalinterface.h textfieldcheckbox.cpp textfieldcheckbox.h textfieldcombobox.cpp textfieldcombobox.h textfileformat.cpp textfileformat.h @@ -271,21 +271,3 @@ extend_qtc_library(Utils fsengine/fsenginehandler.h fsengine/filepathinfocache.h ) - -if (WIN32) - add_qtc_executable(qtcreator_process_stub - SOURCES process_stub_win.c - DEPENDS shell32 - DEFINES _UNICODE UNICODE _CRT_SECURE_NO_WARNINGS - ) - - add_qtc_executable(qtcreator_ctrlc_stub - DEPENDS user32 shell32 - DEFINES _UNICODE UNICODE _CRT_SECURE_NO_WARNINGS - SOURCES - process_ctrlc_stub.cpp - ) -else() - add_qtc_executable(qtcreator_process_stub SOURCES process_stub_unix.c) -endif() - diff --git a/src/libs/utils/process_stub.qbs b/src/libs/utils/process_stub.qbs deleted file mode 100644 index 341fb57791..0000000000 --- a/src/libs/utils/process_stub.qbs +++ /dev/null @@ -1,21 +0,0 @@ -import qbs 1.0 - -QtcTool { - name: "qtcreator_process_stub" - consoleApplication: true - - - files: { - if (qbs.targetOS.contains("windows")) { - return [ "process_stub_win.c" ] - } else { - return [ "process_stub_unix.c" ] - } - } - - cpp.dynamicLibraries: { - if (qbs.targetOS.contains("windows")) { - return [ "shell32" ] - } - } -} diff --git a/src/libs/utils/process_stub_unix.c b/src/libs/utils/process_stub_unix.c deleted file mode 100644 index 716e88d101..0000000000 --- a/src/libs/utils/process_stub_unix.c +++ /dev/null @@ -1,340 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __linux__ -#include - -// Enable compilation with older header that doesn't contain this constant -// for running on newer libraries that do support it -#ifndef PR_SET_PTRACER -#define PR_SET_PTRACER 0x59616d61 -#endif -#endif - -/* For OpenBSD */ -#ifndef EPROTO -# define EPROTO EINVAL -#endif - -extern char **environ; - -static int qtcFd; -static char *sleepMsg; -static int chldPipe[2]; -static int blockingPipe[2]; -static int isDebug; -static volatile int isDetached; -static volatile int chldPid; - -static void __attribute__((noreturn)) doExit(int code) -{ - tcsetpgrp(0, getpid()); - puts(sleepMsg); - const char *rv = fgets(sleepMsg, 2, stdin); /* Minimal size to make it wait */ - (void)rv; // Q_UNUSED - exit(code); -} - -static void sendMsg(const char *msg, int num) -{ - int pidStrLen; - int ioRet; - char pidStr[64]; - - pidStrLen = sprintf(pidStr, msg, num); - if (!isDetached && (ioRet = write(qtcFd, pidStr, pidStrLen)) != pidStrLen) { - fprintf(stderr, "Cannot write to creator comm socket: %s\n", - (ioRet < 0) ? strerror(errno) : "short write"); - isDetached = 2; - } -} - -enum { - ArgCmd = 0, - ArgAction, - ArgSocket, - ArgMsg, - ArgDir, - ArgEnv, - ArgPid, - ArgExe -}; - -/* Handle sigchld */ -static void sigchldHandler(int sig) -{ - int chldStatus; - /* Currently we have only one child, so we exit in case of error. */ - int waitRes; - (void)sig; - for (;;) { - waitRes = waitpid(-1, &chldStatus, WNOHANG); - if (!waitRes) - break; - if (waitRes < 0) { - perror("Cannot obtain exit status of child process"); - doExit(3); - } - if (WIFSTOPPED(chldStatus)) { - /* The child stopped. This can be the result of the initial SIGSTOP handling. - * We won't need the notification pipe any more, as we know that - * the exec() succeeded. */ - close(chldPipe[0]); - close(chldPipe[1]); - chldPipe[0] = -1; - if (isDetached == 2 && isDebug) { - /* qtcreator was not informed and died while debugging, killing the child */ - kill(chldPid, SIGKILL); - } - } else if (WIFEXITED(chldStatus)) { - sendMsg("exit %d\n", WEXITSTATUS(chldStatus)); - doExit(0); - } else { - sendMsg("crash %d\n", WTERMSIG(chldStatus)); - doExit(0); - } - } -} - - -/* syntax: $0 {"run"|"debug"} */ -/* exit codes: 0 = ok, 1 = invocation error, 3 = internal error */ -int main(int argc, char *argv[]) -{ - int errNo, hadInvalidCommand = 0; - char **env = 0; - struct sockaddr_un sau; - struct sigaction act; - - memset(&act, 0, sizeof(act)); - - if (argc < ArgEnv) { - fprintf(stderr, "This is an internal helper of Qt Creator. Do not run it manually.\n"); - return 1; - } - sleepMsg = argv[ArgMsg]; - - /* Connect to the master, i.e. Creator. */ - if ((qtcFd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) { - perror("Cannot create creator comm socket"); - doExit(3); - } - memset(&sau, 0, sizeof(sau)); - sau.sun_family = AF_UNIX; - strncpy(sau.sun_path, argv[ArgSocket], sizeof(sau.sun_path) - 1); - if (connect(qtcFd, (struct sockaddr *)&sau, sizeof(sau))) { - fprintf(stderr, "Cannot connect creator comm socket %s: %s\n", sau.sun_path, strerror(errno)); - doExit(1); - } - - isDebug = !strcmp(argv[ArgAction], "debug"); - isDetached = 0; - - if (*argv[ArgDir] && chdir(argv[ArgDir])) { - /* Only expected error: no such file or direcotry */ - sendMsg("err:chdir %d\n", errno); - return 1; - } - - if (*argv[ArgEnv]) { - FILE *envFd; - char *envdata, *edp, *termEnv; - long size; - int count; - if (!(envFd = fopen(argv[ArgEnv], "r"))) { - fprintf(stderr, "Cannot read creator env file %s: %s\n", - argv[ArgEnv], strerror(errno)); - doExit(1); - } - fseek(envFd, 0, SEEK_END); - size = ftell(envFd); - if (size < 0) { - perror("Failed to get size of env file"); - doExit(1); - } - rewind(envFd); - envdata = malloc(size + 1); - if (fread(envdata, 1, size, envFd) != (size_t)size) { - perror("Failed to read env file"); - doExit(1); - } - envdata[size] = '\0'; - fclose(envFd); - assert(!size || !envdata[size - 1]); - for (count = 0, edp = envdata; edp < envdata + size; ++count) - edp += strlen(edp) + 1; - env = malloc((count + 2) * sizeof(char *)); - for (count = 0, edp = envdata; edp < envdata + size; ++count) { - env[count] = edp; - edp += strlen(edp) + 1; - } - if ((termEnv = getenv("TERM"))) - env[count++] = termEnv - 5; - env[count] = 0; - } - - /* send our pid after we read the environment file (creator will get rid of it) */ - sendMsg("spid %ld\n", (long)getpid()); - - /* - * set up the signal handlers - */ - { - /* Ignore SIGTTOU. Without this, calling tcsetpgrp() from a background - * process group (in which we will be, once as child and once as parent) - * generates the mentioned signal and stops the concerned process. */ - act.sa_handler = SIG_IGN; - if (sigaction(SIGTTOU, &act, 0)) { - perror("sigaction SIGTTOU"); - doExit(3); - } - - /* Handle SIGCHLD to keep track of what the child does without blocking */ - act.sa_handler = sigchldHandler; - if (sigaction(SIGCHLD, &act, 0)) { - perror("sigaction SIGCHLD"); - doExit(3); - } - } - - /* Create execution result notification pipe. */ - if (pipe(chldPipe)) { - perror("Cannot create status pipe"); - doExit(3); - } - - /* The debugged program is not supposed to inherit these handles. But we cannot - * close the writing end before calling exec(). Just handle both ends the same way ... */ - fcntl(chldPipe[0], F_SETFD, FD_CLOEXEC); - fcntl(chldPipe[1], F_SETFD, FD_CLOEXEC); - - if (isDebug) { - /* Create execution start notification pipe. The child waits on this until - the parent writes to it, triggered by an 'c' message from Creator */ - if (pipe(blockingPipe)) { - perror("Cannot create blocking pipe"); - doExit(3); - } - } - - switch ((chldPid = fork())) { - case -1: - perror("Cannot fork child process"); - doExit(3); - case 0: - close(qtcFd); - - /* Remove the SIGCHLD handler from the child */ - act.sa_handler = SIG_DFL; - sigaction(SIGCHLD, &act, 0); - - /* Put the process into an own process group and make it the foregroud - * group on this terminal, so it will receive ctrl-c events, etc. - * This is the main reason for *all* this stub magic in the first place. */ - /* If one of these calls fails, the world is about to end anyway, so - * don't bother checking the return values. */ - setpgid(0, 0); - tcsetpgrp(0, getpid()); - -#ifdef __linux__ - prctl(PR_SET_PTRACER, atoi(argv[ArgPid])); -#endif - /* Block to allow the debugger to attach */ - if (isDebug) { - char buf; - int res = read(blockingPipe[0], &buf, 1); - if (res < 0) - perror("Could not read from blocking pipe"); - close(blockingPipe[0]); - close(blockingPipe[1]); - } - - if (env) - environ = env; - - execvp(argv[ArgExe], argv + ArgExe); - /* Only expected error: no such file or direcotry, i.e. executable not found */ - errNo = errno; - /* Only realistic error case is SIGPIPE */ - if (write(chldPipe[1], &errNo, sizeof(errNo)) != sizeof(errNo)) - perror("Error passing errno to child"); - _exit(0); - default: - sendMsg("pid %d\n", chldPid); - for (;;) { - char buffer[100]; - int nbytes; - - nbytes = read(qtcFd, buffer, 100); - if (nbytes <= 0) { - if (nbytes < 0 && errno == EINTR) - continue; - if (!isDetached) { - isDetached = 2; - if (nbytes == 0) - fprintf(stderr, "Lost connection to QtCreator, detaching from it.\n"); - else - perror("Lost connection to QtCreator, detaching from it"); - } - break; - } else { - int i; - char c = 'i'; - for (i = 0; i < nbytes; ++i) { - switch (buffer[i]) { - case 'k': - if (chldPid > 0) { - kill(chldPid, SIGTERM); - sleep(1); - kill(chldPid, SIGKILL); - } - break; - case 'i': - if (chldPid > 0) { - int res = kill(chldPid, SIGINT); - if (res) - perror("Stub could not interrupt inferior"); - } - break; - case 'c': { - int res = write(blockingPipe[1], &c, 1); - if (res < 0) - perror("Could not write to blocking pipe"); - break; - } - case 'd': - isDetached = 1; - break; - case 's': - exit(0); - default: - if (!hadInvalidCommand) { - fprintf(stderr, "Ignoring invalid commands from QtCreator.\n"); - hadInvalidCommand = 1; - } - } - } - } - } - if (isDetached) { - for (;;) - pause(); /* will exit in the signal handler... */ - } - } - assert(0); - return 0; -} diff --git a/src/libs/utils/process_stub_win.c b/src/libs/utils/process_stub_win.c deleted file mode 100644 index 09f220a6c6..0000000000 --- a/src/libs/utils/process_stub_win.c +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#undef _WIN32_WINNT -#define _WIN32_WINNT 0x0501 /* WinXP, needed for DebugActiveProcessStop() */ - -#include -#include -#include -#include -#include -#include -#include - -static FILE *qtcFd; -static wchar_t *sleepMsg; - -enum RunMode { Run, Debug, Suspend }; - -/* Print some "press enter" message, wait for that, exit. */ -static void doExit(int code) -{ - char buf[2]; - _putws(sleepMsg); - fgets(buf, 2, stdin); /* Minimal size to make it wait */ - exit(code); -} - -/* Print an error message for unexpected Windows system errors, wait, exit. */ -static void systemError(const char *str) -{ - fprintf(stderr, str, GetLastError()); - doExit(3); -} - -/* Send a message to the master. */ -static void sendMsg(const char *msg, int num) -{ - int pidStrLen; - char pidStr[64]; - - pidStrLen = sprintf(pidStr, msg, num); - if (fwrite(pidStr, pidStrLen, 1, qtcFd) != 1 || fflush(qtcFd)) { - fprintf(stderr, "Cannot write to creator comm socket: %s\n", - strerror(errno)); - doExit(3); - } -} - -/* Ignore the first ctrl-c/break within a second. */ -static BOOL WINAPI ctrlHandler(DWORD dwCtrlType) -{ - static ULARGE_INTEGER lastTime; - ULARGE_INTEGER thisTime; - SYSTEMTIME sysTime; - FILETIME fileTime; - - if (dwCtrlType == CTRL_C_EVENT || dwCtrlType == CTRL_BREAK_EVENT) { - GetSystemTime(&sysTime); - SystemTimeToFileTime(&sysTime, &fileTime); - thisTime.LowPart = fileTime.dwLowDateTime; - thisTime.HighPart = fileTime.dwHighDateTime; - if (lastTime.QuadPart + 10000000 < thisTime.QuadPart) { - lastTime.QuadPart = thisTime.QuadPart; - return TRUE; - } - } - return FALSE; -} - -enum { - ArgCmd = 0, - ArgAction, - ArgSocket, - ArgDir, - ArgEnv, - ArgCmdLine, - ArgMsg, - ArgCount -}; - -/* syntax: $0 {"run"|"debug"} */ -/* exit codes: 0 = ok, 1 = invocation error, 3 = internal error */ -int main() -{ - int argc; - int creationFlags; - wchar_t **argv; - wchar_t *env = 0; - STARTUPINFOW si; - PROCESS_INFORMATION pi; - DEBUG_EVENT dbev; - enum RunMode mode = Run; - HANDLE image = NULL; - - argv = CommandLineToArgvW(GetCommandLine(), &argc); - - if (argc != ArgCount) { - fprintf(stderr, "This is an internal helper of Qt Creator. Do not run it manually.\n"); - return 1; - } - sleepMsg = argv[ArgMsg]; - - /* Connect to the master, i.e. Creator. */ - if (!(qtcFd = _wfopen(argv[ArgSocket], L"w"))) { - fprintf(stderr, "Cannot connect creator comm pipe %S: %s\n", - argv[ArgSocket], strerror(errno)); - doExit(1); - } - - if (*argv[ArgDir] && !SetCurrentDirectoryW(argv[ArgDir])) { - /* Only expected error: no such file or direcotry */ - sendMsg("err:chdir %d\n", GetLastError()); - return 1; - } - - if (*argv[ArgEnv]) { - FILE *envFd; - long size; - if (!(envFd = _wfopen(argv[ArgEnv], L"rb"))) { - fprintf(stderr, "Cannot read creator env file %S: %s\n", - argv[ArgEnv], strerror(errno)); - doExit(1); - } - fseek(envFd, 0, SEEK_END); - size = ftell(envFd); - rewind(envFd); - env = malloc(size); - if (fread(env, 1, size, envFd) != size) { - perror("Failed to read env file"); - doExit(1); - } - fclose(envFd); - } - - ZeroMemory(&pi, sizeof(pi)); - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - - creationFlags = CREATE_UNICODE_ENVIRONMENT; - if (!wcscmp(argv[ArgAction], L"debug")) { - mode = Debug; - } else if (!wcscmp(argv[ArgAction], L"suspend")) { - mode = Suspend; - } - - switch (mode) { - case Debug: - creationFlags |= DEBUG_ONLY_THIS_PROCESS; - break; - case Suspend: - creationFlags |= CREATE_SUSPENDED; - break; - default: - break; - } - if (!CreateProcessW(0, argv[ArgCmdLine], 0, 0, FALSE, creationFlags, env, 0, &si, &pi)) { - /* Only expected error: no such file or direcotry, i.e. executable not found */ - sendMsg("err:exec %d\n", GetLastError()); - doExit(1); - } - - /* This is somewhat convoluted. What we actually want is creating a - suspended process and letting gdb attach to it. Unfortunately, - the Windows kernel runs amok when we attempt this. - So instead we start a debugged process, eat all the initial - debug events, suspend the process and detach from it. If gdb - tries to attach *now*, everything goes smoothly. Yay. */ - if (mode == Debug) { - do { - if (!WaitForDebugEvent (&dbev, INFINITE)) - systemError("Cannot fetch debug event, error %d\n"); - if (dbev.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) - image = dbev.u.CreateProcessInfo.hFile; - if (dbev.dwDebugEventCode == EXCEPTION_DEBUG_EVENT) { - /* The first exception to be delivered is a trap - which indicates completion of startup. */ - if (SuspendThread(pi.hThread) == (DWORD)-1) - systemError("Cannot suspend debugee, error %d\n"); - } - if (!ContinueDebugEvent(dbev.dwProcessId, dbev.dwThreadId, DBG_CONTINUE)) - systemError("Cannot continue debug event, error %d\n"); - } while (dbev.dwDebugEventCode != EXCEPTION_DEBUG_EVENT); - if (!DebugActiveProcessStop(dbev.dwProcessId)) - systemError("Cannot detach from debugee, error %d\n"); - if (image) - CloseHandle(image); - } - - SetConsoleCtrlHandler(ctrlHandler, TRUE); - - sendMsg("thread %d\n", pi.dwThreadId); - sendMsg("pid %d\n", pi.dwProcessId); - - if (WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_FAILED) - systemError("Wait for debugee failed, error %d\n"); - - /* Don't close the process/thread handles, so that the kernel doesn't free - the resources before ConsoleProcess is able to obtain handles to them - - this would be a problem if the child process exits very quickly. */ - doExit(0); - - return 0; -} diff --git a/src/libs/utils/processenums.h b/src/libs/utils/processenums.h index 6ea37a2d37..5ce782cd94 100644 --- a/src/libs/utils/processenums.h +++ b/src/libs/utils/processenums.h @@ -26,7 +26,6 @@ enum class TerminalMode { Off, Run, Debug, - Suspend, On = Run // Default mode for terminal set to on }; diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/qtcprocess.cpp index 21b1684242..0ec90d393b 100644 --- a/src/libs/utils/qtcprocess.cpp +++ b/src/libs/utils/qtcprocess.cpp @@ -358,7 +358,7 @@ public: HostOsInfo::isWindowsHost() ? QStringList{m_setup.m_nativeArguments} << arguments : arguments, - m_setup.m_workingDirectory.path(), + m_setup.m_workingDirectory.nativePath(), m_setup.m_environment.toProcessEnvironment().toStringList(), m_setup.m_ptyData->size().width(), m_setup.m_ptyData->size().height()); diff --git a/src/libs/utils/terminalhooks.cpp b/src/libs/utils/terminalhooks.cpp index f4a5944dbd..8187229cbf 100644 --- a/src/libs/utils/terminalhooks.cpp +++ b/src/libs/utils/terminalhooks.cpp @@ -3,8 +3,12 @@ #include "terminalhooks.h" +#include "terminalinterface.h" #include "filepath.h" -#include "terminalprocess_p.h" +#include "qtcprocess.h" +#include "terminalcommand.h" + +#include namespace Utils::Terminal { @@ -26,6 +30,61 @@ FilePath defaultShellForDevice(const FilePath &deviceRoot) return shell.onDevice(deviceRoot); } +class ExternalTerminalProcessImpl final : public TerminalInterface +{ + class ProcessStubCreator : public StubCreator + { + public: + ProcessStubCreator(ExternalTerminalProcessImpl *interface) + : m_interface(interface) + {} + + void startStubProcess(const CommandLine &cmd, const ProcessSetupData &) override + { + if (HostOsInfo::isWindowsHost()) { + m_terminalProcess.setCommand(cmd); + QObject::connect(&m_terminalProcess, &QtcProcess::done, this, [this] { + m_interface->onStubExited(); + }); + m_terminalProcess.start(); + } else if (HostOsInfo::isMacHost()) { + QTemporaryFile f; + f.setAutoRemove(false); + f.open(); + f.setPermissions(QFile::ExeUser | QFile::ReadUser | QFile::WriteUser); + f.write("#!/bin/sh\n"); + f.write(QString("exec '%1' %2\n") + .arg(cmd.executable().nativePath()) + .arg(cmd.arguments()) + .toUtf8()); + f.close(); + + const QString path = f.fileName(); + const QString exe + = QString("tell app \"Terminal\" to do script \"'%1'; rm -f '%1'; exit\"") + .arg(path); + + m_terminalProcess.setCommand({"osascript", {"-e", exe}}); + m_terminalProcess.runBlocking(); + } else { + const TerminalCommand terminal = TerminalCommand::terminalEmulator(); + + CommandLine cmdLine = {terminal.command, {terminal.executeArgs}}; + cmdLine.addCommandLineAsArgs(cmd, CommandLine::Raw); + + m_terminalProcess.setCommand(cmdLine); + m_terminalProcess.start(); + } + } + + ExternalTerminalProcessImpl *m_interface; + QtcProcess m_terminalProcess; + }; + +public: + ExternalTerminalProcessImpl() { setStubCreator(new ProcessStubCreator(this)); } +}; + struct HooksPrivate { HooksPrivate() @@ -34,9 +93,8 @@ struct HooksPrivate FilePath{}), parameters.environment.value_or(Environment{})); }) - , m_createTerminalProcessInterfaceHook( - []() -> ProcessInterface * { return new Internal::TerminalImpl(); }) - , m_getTerminalCommandsForDevicesHook([]() -> QList { return {}; }) + , m_createTerminalProcessInterfaceHook([] { return new ExternalTerminalProcessImpl(); }) + , m_getTerminalCommandsForDevicesHook([] { return QList{}; }) {} Hooks::OpenTerminalHook m_openTerminalHook; diff --git a/src/libs/utils/terminalhooks.h b/src/libs/utils/terminalhooks.h index 3f3d848581..2da470d171 100644 --- a/src/libs/utils/terminalhooks.h +++ b/src/libs/utils/terminalhooks.h @@ -6,6 +6,7 @@ #include "commandline.h" #include "environment.h" #include "filepath.h" +#include "id.h" #include #include @@ -46,6 +47,7 @@ struct OpenTerminalParameters std::optional workingDirectory; std::optional environment; ExitBehavior m_exitBehavior{ExitBehavior::Close}; + std::optional identifier{std::nullopt}; }; struct NameAndCommandLine diff --git a/src/libs/utils/terminalinterface.cpp b/src/libs/utils/terminalinterface.cpp new file mode 100644 index 0000000000..04468ddab7 --- /dev/null +++ b/src/libs/utils/terminalinterface.cpp @@ -0,0 +1,400 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "terminalinterface.h" + +#include "utilstr.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +Q_LOGGING_CATEGORY(terminalInterfaceLog, "qtc.terminalinterface", QtWarningMsg) + +namespace Utils { + +static QString msgCommChannelFailed(const QString &error) +{ + return Tr::tr("Cannot set up communication channel: %1").arg(error); +} + +static QString msgCannotCreateTempFile(const QString &why) +{ + return Tr::tr("Cannot create temporary file: %1").arg(why); +} + +static QString msgCannotWriteTempFile() +{ + return Tr::tr("Cannot write temporary file. Disk full?"); +} + +static QString msgCannotCreateTempDir(const QString &dir, const QString &why) +{ + return Tr::tr("Cannot create temporary directory \"%1\": %2").arg(dir, why); +} + +static QString msgUnexpectedOutput(const QByteArray &what) +{ + return Tr::tr("Unexpected output from helper program (%1).").arg(QString::fromLatin1(what)); +} + +static QString msgCannotChangeToWorkDir(const FilePath &dir, const QString &why) +{ + return Tr::tr("Cannot change to working directory \"%1\": %2").arg(dir.toString(), why); +} + +static QString msgCannotExecute(const QString &p, const QString &why) +{ + return Tr::tr("Cannot execute \"%1\": %2").arg(p, why); +} + +class TerminalInterfacePrivate : public QObject +{ + Q_OBJECT +public: + TerminalInterfacePrivate(TerminalInterface *p) + : q(p) + { + connect(&stubServer, + &QLocalServer::newConnection, + q, + &TerminalInterface::onNewStubConnection); + } + +public: + QLocalServer stubServer; + QLocalSocket *stubSocket = nullptr; + + int stubProcessId = 0; + int inferiorProcessId = 0; + int inferiorThreadId = 0; + + std::unique_ptr envListFile; + QTemporaryDir tempDir; + + std::unique_ptr stubConnectTimeoutTimer; + + ProcessResultData processResultData; + TerminalInterface *q; + + StubCreator *stubCreator{nullptr}; +}; + +TerminalInterface::TerminalInterface() + : d(new TerminalInterfacePrivate(this)) +{} + +TerminalInterface::~TerminalInterface() +{ + if (d->stubSocket && d->stubSocket->state() == QLocalSocket::ConnectedState) { + if (d->inferiorProcessId) + killInferiorProcess(); + killStubProcess(); + } + if (d->stubCreator) + d->stubCreator->deleteLater(); + delete d; +} + +void TerminalInterface::setStubCreator(StubCreator *creator) +{ + d->stubCreator = creator; +} + +int TerminalInterface::inferiorProcessId() const +{ + return d->inferiorProcessId; +} + +int TerminalInterface::inferiorThreadId() const +{ + return d->inferiorThreadId; +} + +static QString errnoToString(int code) +{ + return QString::fromLocal8Bit(strerror(code)); +} + +void TerminalInterface::onNewStubConnection() +{ + d->stubConnectTimeoutTimer.reset(); + + d->stubSocket = d->stubServer.nextPendingConnection(); + if (!d->stubSocket) + return; + + connect(d->stubSocket, &QIODevice::readyRead, this, &TerminalInterface::onStubReadyRead); + + if (HostOsInfo::isAnyUnixHost()) + connect(d->stubSocket, &QLocalSocket::disconnected, this, &TerminalInterface::onStubExited); +} + +void TerminalInterface::onStubExited() +{ + // The stub exit might get noticed before we read the pid for the kill on Windows + // or the error status elsewhere. + if (d->stubSocket && d->stubSocket->state() == QLocalSocket::ConnectedState) + d->stubSocket->waitForDisconnected(); + + shutdownStubServer(); + d->envListFile.reset(); + + if (d->inferiorProcessId) + emitFinished(-1, QProcess::CrashExit); +} + +void TerminalInterface::onStubReadyRead() +{ + while (d->stubSocket && d->stubSocket->canReadLine()) { + QByteArray out = d->stubSocket->readLine(); + out.chop(1); // remove newline + if (out.startsWith("err:chdir ")) { + emitError(QProcess::FailedToStart, + msgCannotChangeToWorkDir(m_setup.m_workingDirectory, + errnoToString(out.mid(10).toInt()))); + } else if (out.startsWith("err:exec ")) { + emitError(QProcess::FailedToStart, + msgCannotExecute(m_setup.m_commandLine.executable().toString(), + errnoToString(out.mid(9).toInt()))); + } else if (out.startsWith("spid ")) { + d->envListFile.reset(); + d->envListFile = nullptr; + } else if (out.startsWith("pid ")) { + d->inferiorProcessId = out.mid(4).toInt(); + emit started(d->inferiorProcessId, d->inferiorThreadId); + } else if (out.startsWith("thread ")) { // Windows only + d->inferiorThreadId = out.mid(7).toLongLong(); + } else if (out.startsWith("exit ")) { + emitFinished(out.mid(5).toInt(), QProcess::NormalExit); + } else if (out.startsWith("crash ")) { + emitFinished(out.mid(6).toInt(), QProcess::CrashExit); + } else { + emitError(QProcess::UnknownError, msgUnexpectedOutput(out)); + break; + } + } +} + +expected_str TerminalInterface::startStubServer() +{ + if (HostOsInfo::isWindowsHost()) { + if (d->stubServer.listen(QString::fromLatin1("creator-%1-%2") + .arg(QCoreApplication::applicationPid()) + .arg(rand()))) + return {}; + return make_unexpected(d->stubServer.errorString()); + } + + // We need to put the socket in a private directory, as some systems simply do not + // check the file permissions of sockets. + if (!QDir(d->tempDir.path()) + .mkdir("socket")) { // QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner + return make_unexpected(msgCannotCreateTempDir(d->tempDir.filePath("socket"), + QString::fromLocal8Bit(strerror(errno)))); + } + + if (!QFile::setPermissions(d->tempDir.filePath("socket"), + QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner)) { + return make_unexpected(Tr::tr("Cannot set permissions on temporary directory \"%1\": %2") + .arg(d->tempDir.filePath("socket")) + .arg(QString::fromLocal8Bit(strerror(errno)))); + } + + const QString socketPath = d->tempDir.filePath("socket/stub-socket"); + if (!d->stubServer.listen(socketPath)) { + return make_unexpected( + Tr::tr("Cannot create socket \"%1\": %2").arg(socketPath, d->stubServer.errorString())); + } + return {}; +} + +void TerminalInterface::shutdownStubServer() +{ + if (d->stubSocket) { + // Read potentially remaining data + onStubReadyRead(); + // avoid getting queued readyRead signals + d->stubSocket->disconnect(); + // we might be called from the disconnected signal of stubSocket + d->stubSocket->deleteLater(); + } + d->stubSocket = nullptr; + if (d->stubServer.isListening()) + d->stubServer.close(); +} + +void TerminalInterface::emitError(QProcess::ProcessError error, const QString &errorString) +{ + d->processResultData.m_error = error; + d->processResultData.m_errorString = errorString; + if (error == QProcess::FailedToStart) + emit done(d->processResultData); +} + +void TerminalInterface::emitFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + d->inferiorProcessId = 0; + d->inferiorThreadId = 0; + d->processResultData.m_exitCode = exitCode; + d->processResultData.m_exitStatus = exitStatus; + emit done(d->processResultData); +} + +bool TerminalInterface::isRunning() const +{ + return d->stubSocket && d->stubSocket->isOpen(); +} + +void TerminalInterface::cleanupAfterStartFailure(const QString &errorMessage) +{ + shutdownStubServer(); + emitError(QProcess::FailedToStart, errorMessage); + d->envListFile.reset(); +} + +void TerminalInterface::sendCommand(char c) +{ + if (d->stubSocket && d->stubSocket->isWritable()) { + d->stubSocket->write(&c, 1); + d->stubSocket->flush(); + } +} + +void TerminalInterface::killInferiorProcess() +{ + sendCommand('k'); + if (d->stubSocket) + d->stubSocket->waitForReadyRead(); +} + +void TerminalInterface::killStubProcess() +{ + if (!isRunning()) + return; + + sendCommand('s'); + if (d->stubSocket) + d->stubSocket->waitForReadyRead(); + shutdownStubServer(); +} + +void TerminalInterface::start() +{ + if (isRunning()) + return; + + const expected_str result = startStubServer(); + if (!result) { + emitError(QProcess::FailedToStart, msgCommChannelFailed(result.error())); + return; + } + + m_setup.m_environment.unset(QLatin1String("TERM")); + + Environment finalEnv = m_setup.m_environment; + + if (HostOsInfo::isWindowsHost()) { + if (!finalEnv.hasKey("PATH")) { + const QString path = qtcEnvironmentVariable("PATH"); + if (!path.isEmpty()) + finalEnv.set("PATH", path); + } + if (!finalEnv.hasKey("SystemRoot")) { + const QString systemRoot = qtcEnvironmentVariable("SystemRoot"); + if (!systemRoot.isEmpty()) + finalEnv.set("SystemRoot", systemRoot); + } + } + + if (finalEnv.hasChanges()) { + d->envListFile = std::make_unique(this); + if (!d->envListFile->open()) { + cleanupAfterStartFailure(msgCannotCreateTempFile(d->envListFile->errorString())); + return; + } + QTextStream stream(d->envListFile.get()); + finalEnv.forEachEntry([&stream](const QString &key, const QString &value, bool) { + stream << key << '=' << value << '\0'; + }); + + if (d->envListFile->error() != QFile::NoError) { + cleanupAfterStartFailure(msgCannotWriteTempFile()); + return; + } + } + + const FilePath stubPath = FilePath::fromUserInput(QCoreApplication::applicationDirPath()) + .pathAppended(QLatin1String(RELATIVE_LIBEXEC_PATH)) + .pathAppended((HostOsInfo::isWindowsHost() + ? QLatin1String("qtcreator_process_stub.exe") + : QLatin1String("qtcreator_process_stub"))); + + CommandLine cmd{stubPath, {"-s", d->stubServer.fullServerName()}}; + + if (!m_setup.m_workingDirectory.isEmpty()) + cmd.addArgs({"-w", m_setup.m_workingDirectory.nativePath()}); + + if (m_setup.m_terminalMode == TerminalMode::Debug) + cmd.addArg("-d"); + + if (terminalInterfaceLog().isDebugEnabled()) + cmd.addArg("-v"); + + if (d->envListFile) + cmd.addArgs({"-e", d->envListFile->fileName()}); + + cmd.addArgs({"--", m_setup.m_commandLine.executable().nativePath()}); + cmd.addArgs(m_setup.m_commandLine.arguments(), CommandLine::Raw); + + QTC_ASSERT(d->stubCreator, return); + + QMetaObject::invokeMethod( + d->stubCreator, + [cmd, this] { d->stubCreator->startStubProcess(cmd, m_setup); }, + d->stubCreator->thread() == QThread::currentThread() ? Qt::DirectConnection + : Qt::BlockingQueuedConnection); + + d->stubConnectTimeoutTimer = std::make_unique(); + + connect(d->stubConnectTimeoutTimer.get(), &QTimer::timeout, this, [this] { + killInferiorProcess(); + killStubProcess(); + }); + d->stubConnectTimeoutTimer->setSingleShot(true); + d->stubConnectTimeoutTimer->start(10000); +} + +qint64 TerminalInterface::write(const QByteArray &data) +{ + Q_UNUSED(data); + QTC_CHECK(false); + return -1; +} +void TerminalInterface::sendControlSignal(ControlSignal controlSignal) +{ + switch (controlSignal) { + case ControlSignal::Terminate: + case ControlSignal::Kill: + killInferiorProcess(); + break; + case ControlSignal::Interrupt: + sendCommand('i'); + break; + case ControlSignal::KickOff: + sendCommand('c'); + break; + case ControlSignal::CloseWriteChannel: + QTC_CHECK(false); + break; + } +} + +} // namespace Utils + +#include "terminalinterface.moc" diff --git a/src/libs/utils/terminalinterface.h b/src/libs/utils/terminalinterface.h new file mode 100644 index 0000000000..135fb8257d --- /dev/null +++ b/src/libs/utils/terminalinterface.h @@ -0,0 +1,61 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "commandline.h" +#include "expected.h" +#include "processinterface.h" + +namespace Utils { + +class TerminalInterfacePrivate; + +class StubCreator : public QObject +{ +public: + virtual void startStubProcess(const CommandLine &cmd, const ProcessSetupData &setup) = 0; +}; + +class QTCREATOR_UTILS_EXPORT TerminalInterface : public ProcessInterface +{ + friend class TerminalInterfacePrivate; + friend class StubCreator; + +public: + TerminalInterface(); + ~TerminalInterface() override; + + int inferiorProcessId() const; + int inferiorThreadId() const; + + void setStubCreator(StubCreator *creator); + + void emitError(QProcess::ProcessError error, const QString &errorString); + void emitFinished(int exitCode, QProcess::ExitStatus exitStatus); + void onStubExited(); + +protected: + void onNewStubConnection(); + void onStubReadyRead(); + + void sendCommand(char c); + + void killInferiorProcess(); + void killStubProcess(); + + expected_str startStubServer(); + void shutdownStubServer(); + void cleanupAfterStartFailure(const QString &errorMessage); + + bool isRunning() const; + +private: + void start() override; + qint64 write(const QByteArray &data) override; + void sendControlSignal(ControlSignal controlSignal) override; + + TerminalInterfacePrivate *d{nullptr}; +}; + +} // namespace Utils diff --git a/src/libs/utils/terminalprocess.cpp b/src/libs/utils/terminalprocess.cpp deleted file mode 100644 index 0070a4db2c..0000000000 --- a/src/libs/utils/terminalprocess.cpp +++ /dev/null @@ -1,721 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "terminalprocess_p.h" - -#include "commandline.h" -#include "environment.h" -#include "hostosinfo.h" -#include "qtcassert.h" -#include "qtcprocess.h" -#include "terminalcommand.h" -#include "utilstr.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef Q_OS_WIN - -#include "winutils.h" - -#include -#include -#include - -#else - -#include -#include -#include -#include -#include - -#endif - -namespace Utils { -namespace Internal { - -static QString modeOption(TerminalMode m) -{ - switch (m) { - case TerminalMode::Run: - return "run"; - case TerminalMode::Debug: - return "debug"; - case TerminalMode::Suspend: - return "suspend"; - case TerminalMode::Off: - QTC_CHECK(false); - break; - } - return {}; -} - -static QString msgCommChannelFailed(const QString &error) -{ - return Tr::tr("Cannot set up communication channel: %1").arg(error); -} - -static QString msgPromptToClose() -{ - // Shown in a terminal which might have a different character set on Windows. - return Tr::tr("Press to close this window..."); -} - -static QString msgCannotCreateTempFile(const QString &why) -{ - return Tr::tr("Cannot create temporary file: %1").arg(why); -} - -static QString msgCannotWriteTempFile() -{ - return Tr::tr("Cannot write temporary file. Disk full?"); -} - -static QString msgCannotCreateTempDir(const QString & dir, const QString &why) -{ - return Tr::tr("Cannot create temporary directory \"%1\": %2").arg(dir, why); -} - -static QString msgUnexpectedOutput(const QByteArray &what) -{ - return Tr::tr("Unexpected output from helper program (%1).") - .arg(QString::fromLatin1(what)); -} - -static QString msgCannotChangeToWorkDir(const FilePath &dir, const QString &why) -{ - return Tr::tr("Cannot change to working directory \"%1\": %2").arg(dir.toString(), why); -} - -static QString msgCannotExecute(const QString & p, const QString &why) -{ - return Tr::tr("Cannot execute \"%1\": %2").arg(p, why); -} - -class TerminalProcessPrivate -{ -public: - TerminalProcessPrivate(QObject *parent) - : m_stubServer(parent) - , m_process(parent) {} - - qint64 m_processId = 0; - ProcessResultData m_result; - QLocalServer m_stubServer; - QLocalSocket *m_stubSocket = nullptr; - QTemporaryFile *m_tempFile = nullptr; - - // Used on Unix only - QtcProcess m_process; - QTimer *m_stubConnectTimer = nullptr; - QByteArray m_stubServerDir; - - // Used on Windows only - qint64 m_appMainThreadId = 0; - -#ifdef Q_OS_WIN - PROCESS_INFORMATION *m_pid = nullptr; - HANDLE m_hInferior = NULL; - QWinEventNotifier *inferiorFinishedNotifier = nullptr; - QWinEventNotifier *processFinishedNotifier = nullptr; -#endif -}; - -TerminalImpl::TerminalImpl() - : d(new TerminalProcessPrivate(this)) -{ - connect(&d->m_stubServer, &QLocalServer::newConnection, - this, &TerminalImpl::stubConnectionAvailable); - - d->m_process.setProcessChannelMode(QProcess::ForwardedChannels); -} - -TerminalImpl::~TerminalImpl() -{ - stopProcess(); - delete d; -} - -void TerminalImpl::start() -{ - if (isRunning()) - return; - - d->m_result = {}; - -#ifdef Q_OS_WIN - - QString pcmd; - QString pargs; - if (m_setup.m_terminalMode != TerminalMode::Run) { // The debugger engines already pre-process the arguments. - pcmd = m_setup.m_commandLine.executable().toString(); - pargs = m_setup.m_commandLine.arguments(); - } else { - ProcessArgs outArgs; - ProcessArgs::prepareCommand(m_setup.m_commandLine, &pcmd, &outArgs, - &m_setup.m_environment, &m_setup.m_workingDirectory); - pargs = outArgs.toWindowsArgs(); - } - - const QString err = stubServerListen(); - if (!err.isEmpty()) { - emitError(QProcess::FailedToStart, msgCommChannelFailed(err)); - return; - } - - QStringList env = m_setup.m_environment.toStringList(); - if (!env.isEmpty()) { - d->m_tempFile = new QTemporaryFile(); - if (!d->m_tempFile->open()) { - cleanupAfterStartFailure(msgCannotCreateTempFile(d->m_tempFile->errorString())); - return; - } - QString outString; - QTextStream out(&outString); - // Add PATH and SystemRoot environment variables in case they are missing - const QStringList fixedEnvironment = [env] { - QStringList envStrings = env; - // add PATH if necessary (for DLL loading) - if (envStrings.filter(QRegularExpression("^PATH=.*", QRegularExpression::CaseInsensitiveOption)).isEmpty()) { - const QString path = qtcEnvironmentVariable("PATH"); - if (!path.isEmpty()) - envStrings.prepend(QString::fromLatin1("PATH=%1").arg(path)); - } - // add systemroot if needed - if (envStrings.filter(QRegularExpression("^SystemRoot=.*", QRegularExpression::CaseInsensitiveOption)).isEmpty()) { - const QString systemRoot = qtcEnvironmentVariable("SystemRoot"); - if (!systemRoot.isEmpty()) - envStrings.prepend(QString::fromLatin1("SystemRoot=%1").arg(systemRoot)); - } - return envStrings; - }(); - - for (const QString &var : fixedEnvironment) - out << var << QChar(0); - out << QChar(0); - const QTextCodec *textCodec = QTextCodec::codecForName("UTF-16LE"); - QTC_CHECK(textCodec); - const QByteArray outBytes = textCodec ? textCodec->fromUnicode(outString) : QByteArray(); - if (!textCodec || d->m_tempFile->write(outBytes) < 0) { - cleanupAfterStartFailure(msgCannotWriteTempFile()); - return; - } - d->m_tempFile->flush(); - } - - STARTUPINFO si; - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - - d->m_pid = new PROCESS_INFORMATION; - ZeroMemory(d->m_pid, sizeof(PROCESS_INFORMATION)); - - QString workDir = m_setup.m_workingDirectory.toUserOutput(); - if (!workDir.isEmpty() && !workDir.endsWith(QLatin1Char('\\'))) - workDir.append(QLatin1Char('\\')); - - // Quote a Windows command line correctly for the "CreateProcess" API - static const auto quoteWinCommand = [](const QString &program) { - const QChar doubleQuote = QLatin1Char('"'); - - // add the program as the first arg ... it works better - QString programName = program; - programName.replace(QLatin1Char('/'), QLatin1Char('\\')); - if (!programName.startsWith(doubleQuote) && !programName.endsWith(doubleQuote) - && programName.contains(QLatin1Char(' '))) { - programName.prepend(doubleQuote); - programName.append(doubleQuote); - } - return programName; - }; - static const auto quoteWinArgument = [](const QString &arg) { - if (arg.isEmpty()) - return QString::fromLatin1("\"\""); - - QString ret(arg); - // Quotes are escaped and their preceding backslashes are doubled. - ret.replace(QRegularExpression("(\\\\*)\""), "\\1\\1\\\""); - if (ret.contains(QRegularExpression("\\s"))) { - // The argument must not end with a \ since this would be interpreted - // as escaping the quote -- rather put the \ behind the quote: e.g. - // rather use "foo"\ than "foo\" - int i = ret.length(); - while (i > 0 && ret.at(i - 1) == QLatin1Char('\\')) - --i; - ret.insert(i, QLatin1Char('"')); - ret.prepend(QLatin1Char('"')); - } - return ret; - }; - static const auto createWinCommandlineMultiArgs = [](const QString &program, const QStringList &args) { - QString programName = quoteWinCommand(program); - for (const QString &arg : args) { - programName += QLatin1Char(' '); - programName += quoteWinArgument(arg); - } - return programName; - }; - static const auto createWinCommandlineSingleArg = [](const QString &program, const QString &args) - { - QString programName = quoteWinCommand(program); - if (!args.isEmpty()) { - programName += QLatin1Char(' '); - programName += args; - } - return programName; - }; - - QStringList stubArgs; - stubArgs << modeOption(m_setup.m_terminalMode) - << d->m_stubServer.fullServerName() - << workDir - << (d->m_tempFile ? d->m_tempFile->fileName() : QString()) - << createWinCommandlineSingleArg(pcmd, pargs) - << msgPromptToClose(); - - const QString cmdLine = createWinCommandlineMultiArgs( - QCoreApplication::applicationDirPath() + QLatin1String("/qtcreator_process_stub.exe"), stubArgs); - - bool success = CreateProcessW(0, (WCHAR*)cmdLine.utf16(), - 0, 0, FALSE, CREATE_NEW_CONSOLE, - 0, 0, - &si, d->m_pid); - - if (!success) { - delete d->m_pid; - d->m_pid = nullptr; - const QString msg = Tr::tr("The process \"%1\" could not be started: %2") - .arg(cmdLine, winErrorMessage(GetLastError())); - cleanupAfterStartFailure(msg); - return; - } - - d->processFinishedNotifier = new QWinEventNotifier(d->m_pid->hProcess, this); - connect(d->processFinishedNotifier, &QWinEventNotifier::activated, - this, &TerminalImpl::stubExited); - -#else - - ProcessArgs::SplitError perr; - ProcessArgs pargs = ProcessArgs::prepareArgs(m_setup.m_commandLine.arguments(), - &perr, - HostOsInfo::hostOs(), - &m_setup.m_environment, - &m_setup.m_workingDirectory, - m_setup.m_abortOnMetaChars); - - QString pcmd; - if (perr == ProcessArgs::SplitOk) { - pcmd = m_setup.m_commandLine.executable().toString(); - } else { - if (perr != ProcessArgs::FoundMeta) { - emitError(QProcess::FailedToStart, Tr::tr("Quoting error in command.")); - return; - } - if (m_setup.m_terminalMode == TerminalMode::Debug) { - // FIXME: QTCREATORBUG-2809 - emitError(QProcess::FailedToStart, - Tr::tr("Debugging complex shell commands in a terminal" - " is currently not supported.")); - return; - } - pcmd = qtcEnvironmentVariable("SHELL", "/bin/sh"); - pargs = ProcessArgs::createUnixArgs( - {"-c", (ProcessArgs::quoteArg(m_setup.m_commandLine.executable().toString()) - + ' ' + m_setup.m_commandLine.arguments())}); - } - - ProcessArgs::SplitError qerr; - const TerminalCommand terminal = TerminalCommand::terminalEmulator(); - const ProcessArgs terminalArgs = ProcessArgs::prepareArgs(terminal.executeArgs, - &qerr, - HostOsInfo::hostOs(), - &m_setup.m_environment, - &m_setup.m_workingDirectory); - if (qerr != ProcessArgs::SplitOk) { - emitError(QProcess::FailedToStart, - qerr == ProcessArgs::BadQuoting - ? Tr::tr("Quoting error in terminal command.") - : Tr::tr("Terminal command may not be a shell command.")); - return; - } - - const QString err = stubServerListen(); - if (!err.isEmpty()) { - emitError(QProcess::FailedToStart, msgCommChannelFailed(err)); - return; - } - - m_setup.m_environment.unset(QLatin1String("TERM")); - - const QStringList env = m_setup.m_environment.toStringList(); - if (!env.isEmpty()) { - d->m_tempFile = new QTemporaryFile(this); - if (!d->m_tempFile->open()) { - cleanupAfterStartFailure(msgCannotCreateTempFile(d->m_tempFile->errorString())); - return; - } - QByteArray contents; - for (const QString &var : env) { - const QByteArray l8b = var.toLocal8Bit(); - contents.append(l8b.constData(), l8b.size() + 1); - } - if (d->m_tempFile->write(contents) != contents.size() || !d->m_tempFile->flush()) { - cleanupAfterStartFailure(msgCannotWriteTempFile()); - return; - } - } - - const QString stubPath = QCoreApplication::applicationDirPath() - + QLatin1String("/" RELATIVE_LIBEXEC_PATH "/qtcreator_process_stub"); - - QStringList allArgs = terminalArgs.toUnixArgs(); - - allArgs << stubPath - << modeOption(m_setup.m_terminalMode) - << d->m_stubServer.fullServerName() - << msgPromptToClose() - << m_setup.m_workingDirectory.path() - << (d->m_tempFile ? d->m_tempFile->fileName() : QString()) - << QString::number(getpid()) - << pcmd - << pargs.toUnixArgs(); - - if (terminal.needsQuotes) - allArgs = QStringList { ProcessArgs::joinArgs(allArgs) }; - - d->m_process.setEnvironment(m_setup.m_environment); - d->m_process.setCommand({terminal.command, allArgs}); - d->m_process.setProcessImpl(m_setup.m_processImpl); - d->m_process.setReaperTimeout(m_setup.m_reaperTimeout); - - d->m_process.start(); - if (!d->m_process.waitForStarted()) { - const QString msg = Tr::tr("Cannot start the terminal emulator \"%1\", change the " - "setting in the Environment preferences. (%2)") - .arg(terminal.command.toUserOutput(), d->m_process.errorString()); - cleanupAfterStartFailure(msg); - return; - } - d->m_stubConnectTimer = new QTimer(this); - connect(d->m_stubConnectTimer, &QTimer::timeout, this, &TerminalImpl::stopProcess); - d->m_stubConnectTimer->setSingleShot(true); - d->m_stubConnectTimer->start(10000); - -#endif -} - -void TerminalImpl::cleanupAfterStartFailure(const QString &errorMessage) -{ - stubServerShutdown(); - emitError(QProcess::FailedToStart, errorMessage); - delete d->m_tempFile; - d->m_tempFile = nullptr; -} - -void TerminalImpl::sendControlSignal(ControlSignal controlSignal) -{ - switch (controlSignal) { - case ControlSignal::Terminate: - case ControlSignal::Kill: - killProcess(); - if (HostOsInfo::isWindowsHost()) - killStub(); - break; - case ControlSignal::Interrupt: - sendCommand('i'); - break; - case ControlSignal::KickOff: - sendCommand('c'); - break; - case ControlSignal::CloseWriteChannel: - QTC_CHECK(false); - break; - } -} - -void TerminalImpl::sendCommand(char c) -{ -#ifdef Q_OS_WIN - Q_UNUSED(c) -#else - if (d->m_stubSocket && d->m_stubSocket->isWritable()) { - d->m_stubSocket->write(&c, 1); - d->m_stubSocket->flush(); - } -#endif -} - -void TerminalImpl::killProcess() -{ -#ifdef Q_OS_WIN - if (d->m_hInferior != NULL) { - TerminateProcess(d->m_hInferior, (unsigned)-1); - cleanupInferior(); - } -#else - sendCommand('k'); -#endif - d->m_processId = 0; -} - -void TerminalImpl::killStub() -{ - if (!isRunning()) - return; - -#ifdef Q_OS_WIN - TerminateProcess(d->m_pid->hProcess, (unsigned)-1); - WaitForSingleObject(d->m_pid->hProcess, INFINITE); - cleanupStub(); - emitFinished(-1, QProcess::CrashExit); -#else - sendCommand('s'); - stubServerShutdown(); - d->m_process.stop(); - d->m_process.waitForFinished(); -#endif -} - -void TerminalImpl::stopProcess() -{ - killProcess(); - killStub(); -} - -bool TerminalImpl::isRunning() const -{ -#ifdef Q_OS_WIN - return d->m_pid != nullptr; -#else - return d->m_process.state() != QProcess::NotRunning - || (d->m_stubSocket && d->m_stubSocket->isOpen()); -#endif -} - -QString TerminalImpl::stubServerListen() -{ -#ifdef Q_OS_WIN - if (d->m_stubServer.listen(QString::fromLatin1("creator-%1-%2") - .arg(QCoreApplication::applicationPid()) - .arg(rand()))) - return QString(); - return d->m_stubServer.errorString(); -#else - // We need to put the socket in a private directory, as some systems simply do not - // check the file permissions of sockets. - QString stubFifoDir; - while (true) { - { - QTemporaryFile tf; - if (!tf.open()) - return msgCannotCreateTempFile(tf.errorString()); - stubFifoDir = tf.fileName(); - } - // By now the temp file was deleted again - d->m_stubServerDir = QFile::encodeName(stubFifoDir); - if (!::mkdir(d->m_stubServerDir.constData(), 0700)) - break; - if (errno != EEXIST) - return msgCannotCreateTempDir(stubFifoDir, QString::fromLocal8Bit(strerror(errno))); - } - const QString stubServer = stubFifoDir + QLatin1String("/stub-socket"); - if (!d->m_stubServer.listen(stubServer)) { - ::rmdir(d->m_stubServerDir.constData()); - return Tr::tr("Cannot create socket \"%1\": %2") - .arg(stubServer, d->m_stubServer.errorString()); - } - return {}; -#endif -} - -void TerminalImpl::stubServerShutdown() -{ -#ifdef Q_OS_WIN - delete d->m_stubSocket; - d->m_stubSocket = nullptr; - if (d->m_stubServer.isListening()) - d->m_stubServer.close(); -#else - if (d->m_stubSocket) { - readStubOutput(); // we could get the shutdown signal before emptying the buffer - d->m_stubSocket->disconnect(); // avoid getting queued readyRead signals - d->m_stubSocket->deleteLater(); // we might be called from the disconnected signal of m_stubSocket - } - d->m_stubSocket = nullptr; - if (d->m_stubServer.isListening()) { - d->m_stubServer.close(); - ::rmdir(d->m_stubServerDir.constData()); - } -#endif -} - -void TerminalImpl::stubConnectionAvailable() -{ - if (d->m_stubConnectTimer) { - delete d->m_stubConnectTimer; - d->m_stubConnectTimer = nullptr; - } - - d->m_stubSocket = d->m_stubServer.nextPendingConnection(); - connect(d->m_stubSocket, &QIODevice::readyRead, this, &TerminalImpl::readStubOutput); - - if (HostOsInfo::isAnyUnixHost()) - connect(d->m_stubSocket, &QLocalSocket::disconnected, this, &TerminalImpl::stubExited); -} - -static QString errorMsg(int code) -{ - return QString::fromLocal8Bit(strerror(code)); -} - -void TerminalImpl::readStubOutput() -{ - while (d->m_stubSocket->canReadLine()) { - QByteArray out = d->m_stubSocket->readLine(); -#ifdef Q_OS_WIN - out.chop(2); // \r\n - if (out.startsWith("err:chdir ")) { - emitError(QProcess::FailedToStart, - msgCannotChangeToWorkDir(m_setup.m_workingDirectory, winErrorMessage(out.mid(10).toInt()))); - } else if (out.startsWith("err:exec ")) { - emitError(QProcess::FailedToStart, - msgCannotExecute(m_setup.m_commandLine.executable().toUserOutput(), winErrorMessage(out.mid(9).toInt()))); - } else if (out.startsWith("thread ")) { // Windows only - // TODO: ensure that it comes before "pid " comes - d->m_appMainThreadId = out.mid(7).toLongLong(); - } else if (out.startsWith("pid ")) { - // Will not need it any more - delete d->m_tempFile; - d->m_tempFile = nullptr; - d->m_processId = out.mid(4).toLongLong(); - - d->m_hInferior = OpenProcess( - SYNCHRONIZE | PROCESS_QUERY_INFORMATION | PROCESS_TERMINATE, - FALSE, d->m_processId); - if (d->m_hInferior == NULL) { - emitError(QProcess::FailedToStart, - Tr::tr("Cannot obtain a handle to the inferior: %1") - .arg(winErrorMessage(GetLastError()))); - // Uhm, and now what? - continue; - } - d->inferiorFinishedNotifier = new QWinEventNotifier(d->m_hInferior, this); - connect(d->inferiorFinishedNotifier, &QWinEventNotifier::activated, this, [this] { - DWORD chldStatus; - - if (!GetExitCodeProcess(d->m_hInferior, &chldStatus)) - emitError(QProcess::UnknownError, - Tr::tr("Cannot obtain exit status from inferior: %1") - .arg(winErrorMessage(GetLastError()))); - cleanupInferior(); - emitFinished(chldStatus, QProcess::NormalExit); - }); - - emit started(d->m_processId, d->m_appMainThreadId); - } else { - emitError(QProcess::UnknownError, msgUnexpectedOutput(out)); - TerminateProcess(d->m_pid->hProcess, (unsigned)-1); - break; - } -#else - out.chop(1); // \n - if (out.startsWith("err:chdir ")) { - emitError(QProcess::FailedToStart, - msgCannotChangeToWorkDir(m_setup.m_workingDirectory, errorMsg(out.mid(10).toInt()))); - } else if (out.startsWith("err:exec ")) { - emitError(QProcess::FailedToStart, - msgCannotExecute(m_setup.m_commandLine.executable().toString(), errorMsg(out.mid(9).toInt()))); - } else if (out.startsWith("spid ")) { - delete d->m_tempFile; - d->m_tempFile = nullptr; - } else if (out.startsWith("pid ")) { - d->m_processId = out.mid(4).toInt(); - emit started(d->m_processId); - } else if (out.startsWith("exit ")) { - emitFinished(out.mid(5).toInt(), QProcess::NormalExit); - } else if (out.startsWith("crash ")) { - emitFinished(out.mid(6).toInt(), QProcess::CrashExit); - } else { - emitError(QProcess::UnknownError, msgUnexpectedOutput(out)); - d->m_process.terminate(); - break; - } -#endif - } // while -} - -void TerminalImpl::stubExited() -{ - // The stub exit might get noticed before we read the pid for the kill on Windows - // or the error status elsewhere. - if (d->m_stubSocket && d->m_stubSocket->state() == QLocalSocket::ConnectedState) - d->m_stubSocket->waitForDisconnected(); - -#ifdef Q_OS_WIN - cleanupStub(); - if (d->m_hInferior != NULL) { - TerminateProcess(d->m_hInferior, (unsigned)-1); - cleanupInferior(); - emitFinished(-1, QProcess::CrashExit); - } -#else - stubServerShutdown(); - delete d->m_tempFile; - d->m_tempFile = nullptr; - if (d->m_processId) - emitFinished(-1, QProcess::CrashExit); -#endif -} - -void TerminalImpl::cleanupInferior() -{ -#ifdef Q_OS_WIN - delete d->inferiorFinishedNotifier; - d->inferiorFinishedNotifier = nullptr; - CloseHandle(d->m_hInferior); - d->m_hInferior = NULL; -#endif -} - -void TerminalImpl::cleanupStub() -{ -#ifdef Q_OS_WIN - stubServerShutdown(); - delete d->processFinishedNotifier; - d->processFinishedNotifier = nullptr; - CloseHandle(d->m_pid->hThread); - CloseHandle(d->m_pid->hProcess); - delete d->m_pid; - d->m_pid = nullptr; - delete d->m_tempFile; - d->m_tempFile = nullptr; -#endif -} - -void TerminalImpl::emitError(QProcess::ProcessError error, const QString &errorString) -{ - d->m_result.m_error = error; - d->m_result.m_errorString = errorString; - if (error == QProcess::FailedToStart) - emit done(d->m_result); -} - -void TerminalImpl::emitFinished(int exitCode, QProcess::ExitStatus exitStatus) -{ - d->m_processId = 0; - d->m_result.m_exitCode = exitCode; - d->m_result.m_exitStatus = exitStatus; - emit done(d->m_result); -} - - -} // Internal -} // Utils diff --git a/src/libs/utils/terminalprocess_p.h b/src/libs/utils/terminalprocess_p.h deleted file mode 100644 index 27c99cee26..0000000000 --- a/src/libs/utils/terminalprocess_p.h +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "processenums.h" -#include "processinterface.h" -#include "qtcassert.h" - -#include - -namespace Utils { - -class CommandLine; -class Environment; -class FilePath; - -namespace Internal { - -class TerminalImpl final : public ProcessInterface -{ -public: - TerminalImpl(); - ~TerminalImpl() final; - -private: - void start() final; - qint64 write(const QByteArray &) final { QTC_CHECK(false); return -1; } - void sendControlSignal(ControlSignal controlSignal) final; - - // OK, however, impl looks a bit different (!= NotRunning vs == Running). - // Most probably changing it into (== Running) should be OK. - bool isRunning() const; - - void stopProcess(); - void stubConnectionAvailable(); - void readStubOutput(); - void stubExited(); - void cleanupAfterStartFailure(const QString &errorMessage); - void killProcess(); - void killStub(); - void emitError(QProcess::ProcessError error, const QString &errorString); - void emitFinished(int exitCode, QProcess::ExitStatus exitStatus); - QString stubServerListen(); - void stubServerShutdown(); - void cleanupStub(); - void cleanupInferior(); - void sendCommand(char c); - - class TerminalProcessPrivate *d; -}; - -} // Internal -} // Utils diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index 9e3d1eaeaa..35fe753d1c 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -314,8 +314,8 @@ Project { "terminalcommand.h", "terminalhooks.cpp", "terminalhooks.h", - "terminalprocess.cpp", - "terminalprocess_p.h", + "terminalinterface.cpp", + "terminalinterface.h", "textfieldcheckbox.cpp", "textfieldcheckbox.h", "textfieldcombobox.cpp", -- cgit v1.2.3 From 8e9b8933256c1483f1f72ade010ea879550d40d7 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Mon, 20 Mar 2023 16:29:51 +0100 Subject: LanguageClient: Introduce ClientRequestTask This class is going to be used inside TaskTree. Change-Id: Ia227a8f41e4557b45053cb018497a7eca8f8ac6a Reviewed-by: Jarek Kobus Reviewed-by: Reviewed-by: Qt CI Bot --- src/libs/languageserverprotocol/jsonrpcmessages.h | 1 + src/libs/languageserverprotocol/workspace.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/languageserverprotocol/jsonrpcmessages.h b/src/libs/languageserverprotocol/jsonrpcmessages.h index 0b018d7f54..680bff9578 100644 --- a/src/libs/languageserverprotocol/jsonrpcmessages.h +++ b/src/libs/languageserverprotocol/jsonrpcmessages.h @@ -141,6 +141,7 @@ public: void setMethod(const QString &method) { m_jsonObject.insert(methodKey, method); } + using Parameters = Params; std::optional params() const { const QJsonValue ¶ms = m_jsonObject.value(paramsKey); diff --git a/src/libs/languageserverprotocol/workspace.h b/src/libs/languageserverprotocol/workspace.h index efe77a68ec..42cfcf451d 100644 --- a/src/libs/languageserverprotocol/workspace.h +++ b/src/libs/languageserverprotocol/workspace.h @@ -175,7 +175,7 @@ class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceSymbolRequest : public Request< LanguageClientArray, std::nullptr_t, WorkspaceSymbolParams> { public: - WorkspaceSymbolRequest(const WorkspaceSymbolParams ¶ms); + explicit WorkspaceSymbolRequest(const WorkspaceSymbolParams ¶ms); using Request::Request; constexpr static const char methodName[] = "workspace/symbol"; }; -- cgit v1.2.3 From 80fa3339e06236bebeb1894deeb4cf7e648c2888 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Tue, 21 Mar 2023 10:15:54 +0100 Subject: Utils: Re-add "press enter to continue" to stub To keep the terminal from closing immediately, ask the user to press enter after the inferior exited. Make it configurable as the terminal plugin does not need this. Change-Id: I1949895f022a54539a6139be9f92fdc698f6534e Reviewed-by: Reviewed-by: Eike Ziller --- src/libs/utils/terminalinterface.cpp | 17 ++++++++++++++--- src/libs/utils/terminalinterface.h | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/terminalinterface.cpp b/src/libs/utils/terminalinterface.cpp index 04468ddab7..7ec7c755fd 100644 --- a/src/libs/utils/terminalinterface.cpp +++ b/src/libs/utils/terminalinterface.cpp @@ -53,12 +53,19 @@ static QString msgCannotExecute(const QString &p, const QString &why) return Tr::tr("Cannot execute \"%1\": %2").arg(p, why); } +static QString msgPromptToClose() +{ + // Shown in a terminal which might have a different character set on Windows. + return Tr::tr("Press to close this window..."); +} + class TerminalInterfacePrivate : public QObject { Q_OBJECT public: - TerminalInterfacePrivate(TerminalInterface *p) + TerminalInterfacePrivate(TerminalInterface *p, bool waitOnExit) : q(p) + , waitOnExit(waitOnExit) { connect(&stubServer, &QLocalServer::newConnection, @@ -83,10 +90,12 @@ public: TerminalInterface *q; StubCreator *stubCreator{nullptr}; + + const bool waitOnExit; }; -TerminalInterface::TerminalInterface() - : d(new TerminalInterfacePrivate(this)) +TerminalInterface::TerminalInterface(bool waitOnExit) + : d(new TerminalInterfacePrivate(this, waitOnExit)) {} TerminalInterface::~TerminalInterface() @@ -349,6 +358,8 @@ void TerminalInterface::start() if (d->envListFile) cmd.addArgs({"-e", d->envListFile->fileName()}); + cmd.addArgs({"--wait", d->waitOnExit ? msgPromptToClose() : ""}); + cmd.addArgs({"--", m_setup.m_commandLine.executable().nativePath()}); cmd.addArgs(m_setup.m_commandLine.arguments(), CommandLine::Raw); diff --git a/src/libs/utils/terminalinterface.h b/src/libs/utils/terminalinterface.h index 135fb8257d..feb19875ba 100644 --- a/src/libs/utils/terminalinterface.h +++ b/src/libs/utils/terminalinterface.h @@ -23,7 +23,7 @@ class QTCREATOR_UTILS_EXPORT TerminalInterface : public ProcessInterface friend class StubCreator; public: - TerminalInterface(); + TerminalInterface(bool waitOnExit = true); ~TerminalInterface() override; int inferiorProcessId() const; -- cgit v1.2.3 From 44074accc7eb2fe0659ccd5166975d54dfb5cc68 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Tue, 14 Mar 2023 09:09:55 +0100 Subject: Terminal: Use QtcProcess to start terminal window Previously DesktopDevice::openTerminal used custom code to open a terminal window. This patch changes it to use QtcProcess with TerminalMode::On. This also removes the need for "openTerminal.py" on macOS. Change-Id: Iec978bdd19487ff8e59dcd88c35c2d01b0681022 Reviewed-by: Cristian Adam --- src/libs/utils/launcherpackets.cpp | 6 ++++-- src/libs/utils/launcherpackets.h | 1 + src/libs/utils/launchersocket.cpp | 1 + src/libs/utils/processinterface.h | 1 + src/libs/utils/processutils.cpp | 33 +++++++++++++++++++++++---------- src/libs/utils/processutils.h | 2 +- src/libs/utils/qtcprocess.cpp | 15 +++++++++++++-- src/libs/utils/qtcprocess.h | 3 +++ src/libs/utils/terminalcommand.cpp | 8 +------- src/libs/utils/terminalhooks.cpp | 19 +++++++++++-------- src/libs/utils/terminalinterface.cpp | 4 ++-- 11 files changed, 61 insertions(+), 32 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/launcherpackets.cpp b/src/libs/utils/launcherpackets.cpp index 13c3a1560d..37261224ef 100644 --- a/src/libs/utils/launcherpackets.cpp +++ b/src/libs/utils/launcherpackets.cpp @@ -48,7 +48,8 @@ void StartProcessPacket::doSerialize(QDataStream &stream) const << lowPriority << unixTerminalDisabled << useCtrlCStub - << reaperTimeout; + << reaperTimeout + << createConsoleOnWindows; } void StartProcessPacket::doDeserialize(QDataStream &stream) @@ -68,7 +69,8 @@ void StartProcessPacket::doDeserialize(QDataStream &stream) >> lowPriority >> unixTerminalDisabled >> useCtrlCStub - >> reaperTimeout; + >> reaperTimeout + >> createConsoleOnWindows; processMode = Utils::ProcessMode(processModeInt); processChannelMode = QProcess::ProcessChannelMode(processChannelModeInt); } diff --git a/src/libs/utils/launcherpackets.h b/src/libs/utils/launcherpackets.h index 2f0bae2915..27e98a74e5 100644 --- a/src/libs/utils/launcherpackets.h +++ b/src/libs/utils/launcherpackets.h @@ -98,6 +98,7 @@ public: bool unixTerminalDisabled = false; bool useCtrlCStub = false; int reaperTimeout = 500; + bool createConsoleOnWindows = false; private: void doSerialize(QDataStream &stream) const override; diff --git a/src/libs/utils/launchersocket.cpp b/src/libs/utils/launchersocket.cpp index 07ebc84df9..f4feda595a 100644 --- a/src/libs/utils/launchersocket.cpp +++ b/src/libs/utils/launchersocket.cpp @@ -257,6 +257,7 @@ void CallerHandle::start(const QString &program, const QStringList &arguments) p.unixTerminalDisabled = m_setup->m_unixTerminalDisabled; p.useCtrlCStub = m_setup->m_useCtrlCStub; p.reaperTimeout = m_setup->m_reaperTimeout; + p.createConsoleOnWindows = m_setup->m_createConsoleOnWindows; sendPacket(p); } diff --git a/src/libs/utils/processinterface.h b/src/libs/utils/processinterface.h index 5f3a4378d4..bd02428ef9 100644 --- a/src/libs/utils/processinterface.h +++ b/src/libs/utils/processinterface.h @@ -68,6 +68,7 @@ public: bool m_unixTerminalDisabled = false; bool m_useCtrlCStub = false; bool m_belowNormalPriority = false; // internal, dependent on other fields and specific code path + bool m_createConsoleOnWindows = false; }; class QTCREATOR_UTILS_EXPORT ProcessResultData diff --git a/src/libs/utils/processutils.cpp b/src/libs/utils/processutils.cpp index c8394e37dd..b534d6f8bb 100644 --- a/src/libs/utils/processutils.cpp +++ b/src/libs/utils/processutils.cpp @@ -45,16 +45,6 @@ void ProcessStartHandler::handleProcessStarted() } } -void ProcessStartHandler::setBelowNormalPriority() -{ -#ifdef Q_OS_WIN - m_process->setCreateProcessArgumentsModifier( - [](QProcess::CreateProcessArguments *args) { - args->flags |= BELOW_NORMAL_PRIORITY_CLASS; - }); -#endif // Q_OS_WIN -} - void ProcessStartHandler::setNativeArguments(const QString &arguments) { #ifdef Q_OS_WIN @@ -65,6 +55,29 @@ void ProcessStartHandler::setNativeArguments(const QString &arguments) #endif // Q_OS_WIN } +void ProcessStartHandler::setWindowsSpecificStartupFlags(bool belowNormalPriority, + bool createConsoleWindow) +{ +#ifdef Q_OS_WIN + if (!belowNormalPriority && !createConsoleWindow) + return; + + m_process->setCreateProcessArgumentsModifier( + [belowNormalPriority, createConsoleWindow](QProcess::CreateProcessArguments *args) { + if (createConsoleWindow) { + args->flags |= CREATE_NEW_CONSOLE; + args->startupInfo->dwFlags &= ~STARTF_USESTDHANDLES; + } + + if (belowNormalPriority) + args->flags |= BELOW_NORMAL_PRIORITY_CLASS; + }); +#else // Q_OS_WIN + Q_UNUSED(belowNormalPriority) + Q_UNUSED(createConsoleWindow) +#endif +} + #ifdef Q_OS_WIN static BOOL sendMessage(UINT message, HWND hwnd, LPARAM lParam) { diff --git a/src/libs/utils/processutils.h b/src/libs/utils/processutils.h index 60161f8398..fa915e2ac4 100644 --- a/src/libs/utils/processutils.h +++ b/src/libs/utils/processutils.h @@ -20,8 +20,8 @@ public: QIODevice::OpenMode openMode() const; void handleProcessStart(); void handleProcessStarted(); - void setBelowNormalPriority(); void setNativeArguments(const QString &arguments); + void setWindowsSpecificStartupFlags(bool belowNormalPriority, bool createConsoleWindow); private: ProcessMode m_processMode = ProcessMode::Reader; diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/qtcprocess.cpp index 0ec90d393b..1583316832 100644 --- a/src/libs/utils/qtcprocess.cpp +++ b/src/libs/utils/qtcprocess.cpp @@ -454,9 +454,10 @@ private: ProcessStartHandler *handler = m_process->processStartHandler(); handler->setProcessMode(m_setup.m_processMode); handler->setWriteData(m_setup.m_writeData); - if (m_setup.m_belowNormalPriority) - handler->setBelowNormalPriority(); handler->setNativeArguments(m_setup.m_nativeArguments); + handler->setWindowsSpecificStartupFlags(m_setup.m_belowNormalPriority, + m_setup.m_createConsoleOnWindows); + m_process->setProcessEnvironment(m_setup.m_environment.toProcessEnvironment()); m_process->setWorkingDirectory(m_setup.m_workingDirectory.path()); m_process->setStandardInputFile(m_setup.m_standardInputFile); @@ -1313,6 +1314,16 @@ QString QtcProcess::toStandaloneCommandLine() const return parts.join(" "); } +void QtcProcess::setCreateConsoleOnWindows(bool create) +{ + d->m_setup.m_createConsoleOnWindows = create; +} + +bool QtcProcess::createConsoleOnWindows() const +{ + return d->m_setup.m_createConsoleOnWindows; +} + void QtcProcess::setExtraData(const QString &key, const QVariant &value) { d->m_setup.m_extraData.insert(key, value); diff --git a/src/libs/utils/qtcprocess.h b/src/libs/utils/qtcprocess.h index 9d539f08e2..e016b50d61 100644 --- a/src/libs/utils/qtcprocess.h +++ b/src/libs/utils/qtcprocess.h @@ -181,6 +181,9 @@ public: QString toStandaloneCommandLine() const; + void setCreateConsoleOnWindows(bool create); + bool createConsoleOnWindows() const; + signals: void starting(); // On NotRunning -> Starting state transition void started(); // On Starting -> Running state transition diff --git a/src/libs/utils/terminalcommand.cpp b/src/libs/utils/terminalcommand.cpp index c25b379f32..102fc42e05 100644 --- a/src/libs/utils/terminalcommand.cpp +++ b/src/libs/utils/terminalcommand.cpp @@ -65,13 +65,7 @@ TerminalCommand TerminalCommand::defaultTerminalEmulator() if (defaultTerm.command.isEmpty()) { if (HostOsInfo::isMacHost()) { - const FilePath termCmd = FilePath::fromString(QCoreApplication::applicationDirPath()) - / "../Resources/scripts/openTerminal.py"; - if (termCmd.exists()) - defaultTerm = {termCmd, "", ""}; - else - defaultTerm = {"/usr/X11/bin/xterm", "", "-e"}; - + return {"Terminal.app", "", ""}; } else if (HostOsInfo::isAnyUnixHost()) { defaultTerm = {"xterm", "", "-e"}; const Environment env = Environment::systemEnvironment(); diff --git a/src/libs/utils/terminalhooks.cpp b/src/libs/utils/terminalhooks.cpp index 8187229cbf..6ee46b3361 100644 --- a/src/libs/utils/terminalhooks.cpp +++ b/src/libs/utils/terminalhooks.cpp @@ -3,10 +3,10 @@ #include "terminalhooks.h" -#include "terminalinterface.h" #include "filepath.h" #include "qtcprocess.h" #include "terminalcommand.h" +#include "terminalinterface.h" #include @@ -14,10 +14,9 @@ namespace Utils::Terminal { FilePath defaultShellForDevice(const FilePath &deviceRoot) { - if (!deviceRoot.needsDevice()) - return {}; + if (deviceRoot.osType() == OsTypeWindows) + return deviceRoot.withNewPath("cmd.exe").searchInPath(); - // TODO: Windows ? const Environment env = deviceRoot.deviceEnvironment(); FilePath shell = FilePath::fromUserInput(env.value_or("SHELL", "/bin/sh")); @@ -41,18 +40,23 @@ class ExternalTerminalProcessImpl final : public TerminalInterface void startStubProcess(const CommandLine &cmd, const ProcessSetupData &) override { + const TerminalCommand terminal = TerminalCommand::terminalEmulator(); + if (HostOsInfo::isWindowsHost()) { m_terminalProcess.setCommand(cmd); QObject::connect(&m_terminalProcess, &QtcProcess::done, this, [this] { m_interface->onStubExited(); }); + m_terminalProcess.setCreateConsoleOnWindows(true); + m_terminalProcess.setProcessMode(ProcessMode::Writer); m_terminalProcess.start(); - } else if (HostOsInfo::isMacHost()) { + } else if (HostOsInfo::isMacHost() && terminal.command == "Terminal.app") { QTemporaryFile f; f.setAutoRemove(false); f.open(); f.setPermissions(QFile::ExeUser | QFile::ReadUser | QFile::WriteUser); f.write("#!/bin/sh\n"); + f.write("clear\n"); f.write(QString("exec '%1' %2\n") .arg(cmd.executable().nativePath()) .arg(cmd.arguments()) @@ -64,11 +68,10 @@ class ExternalTerminalProcessImpl final : public TerminalInterface = QString("tell app \"Terminal\" to do script \"'%1'; rm -f '%1'; exit\"") .arg(path); - m_terminalProcess.setCommand({"osascript", {"-e", exe}}); + m_terminalProcess.setCommand( + {"osascript", {"-e", "tell app \"Terminal\" to activate", "-e", exe}}); m_terminalProcess.runBlocking(); } else { - const TerminalCommand terminal = TerminalCommand::terminalEmulator(); - CommandLine cmdLine = {terminal.command, {terminal.executeArgs}}; cmdLine.addCommandLineAsArgs(cmd, CommandLine::Raw); diff --git a/src/libs/utils/terminalinterface.cpp b/src/libs/utils/terminalinterface.cpp index 7ec7c755fd..58d48cb910 100644 --- a/src/libs/utils/terminalinterface.cpp +++ b/src/libs/utils/terminalinterface.cpp @@ -304,8 +304,6 @@ void TerminalInterface::start() return; } - m_setup.m_environment.unset(QLatin1String("TERM")); - Environment finalEnv = m_setup.m_environment; if (HostOsInfo::isWindowsHost()) { @@ -319,6 +317,8 @@ void TerminalInterface::start() if (!systemRoot.isEmpty()) finalEnv.set("SystemRoot", systemRoot); } + } else if (HostOsInfo::isMacHost()) { + finalEnv.set("TERM", "xterm-256color"); } if (finalEnv.hasChanges()) { -- cgit v1.2.3 From c0676540eea543f2dc73f8f229dfb9f177ed58f7 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Thu, 23 Mar 2023 09:35:45 +0100 Subject: Utils: Add header to be installed Referenced in filepath.h Change-Id: Ie7c8a270c64e2d2a5f979bbf8ad5990fb6ff1a8c Reviewed-by: Marcus Tillmanns --- src/libs/utils/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) (limited to 'src/libs') diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index ab55a0deec..7277b7c8cf 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -193,6 +193,7 @@ add_qtc_library(Utils utils_global.h utilstr.h utilsicons.cpp utilsicons.h + utiltypes.h variablechooser.cpp variablechooser.h winutils.cpp winutils.h wizard.cpp wizard.h -- cgit v1.2.3 From 1d9c96a9f456833c027e4220c19b7b9622b9d22d Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Wed, 22 Mar 2023 16:54:53 +0100 Subject: Terminal: Add enable setting Change-Id: I13cca8b2d4c55df7db29807c1252718e2819ea0b Reviewed-by: Cristian Adam --- src/libs/utils/qtcprocess.cpp | 2 +- src/libs/utils/terminalhooks.cpp | 79 ++++++++++++++++++++++++++++++++++------ src/libs/utils/terminalhooks.h | 21 ++++++++--- 3 files changed, 84 insertions(+), 18 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/qtcprocess.cpp index 1583316832..04276a610b 100644 --- a/src/libs/utils/qtcprocess.cpp +++ b/src/libs/utils/qtcprocess.cpp @@ -732,7 +732,7 @@ public: if (m_setup.m_ptyData) return new PtyProcessImpl; if (m_setup.m_terminalMode != TerminalMode::Off) - return Terminal::Hooks::instance().createTerminalProcessInterfaceHook()(); + return Terminal::Hooks::instance().createTerminalProcessInterface(); const ProcessImpl impl = m_setup.m_processImpl == ProcessImpl::Default ? defaultProcessImpl() : m_setup.m_processImpl; diff --git a/src/libs/utils/terminalhooks.cpp b/src/libs/utils/terminalhooks.cpp index 6ee46b3361..6922f5c430 100644 --- a/src/libs/utils/terminalhooks.cpp +++ b/src/libs/utils/terminalhooks.cpp @@ -8,6 +8,7 @@ #include "terminalcommand.h" #include "terminalinterface.h" +#include #include namespace Utils::Terminal { @@ -88,21 +89,65 @@ public: ExternalTerminalProcessImpl() { setStubCreator(new ProcessStubCreator(this)); } }; -struct HooksPrivate +class HooksPrivate { +public: HooksPrivate() - : m_openTerminalHook([](const OpenTerminalParameters ¶meters) { + : m_getTerminalCommandsForDevicesHook([] { return QList{}; }) + { + auto openTerminal = [](const OpenTerminalParameters ¶meters) { DeviceFileHooks::instance().openTerminal(parameters.workingDirectory.value_or( FilePath{}), parameters.environment.value_or(Environment{})); - }) - , m_createTerminalProcessInterfaceHook([] { return new ExternalTerminalProcessImpl(); }) - , m_getTerminalCommandsForDevicesHook([] { return QList{}; }) - {} + }; + auto createProcessInterface = []() { return new ExternalTerminalProcessImpl(); }; + + addCallbackSet("External", {openTerminal, createProcessInterface}); + } + + void addCallbackSet(const QString &name, const Hooks::CallbackSet &callbackSet) + { + QMutexLocker lk(&m_mutex); + m_callbackSets.push_back(qMakePair(name, callbackSet)); + + m_createTerminalProcessInterface + = m_callbackSets.back().second.createTerminalProcessInterface; + m_openTerminal = m_callbackSets.back().second.openTerminal; + } + + void removeCallbackSet(const QString &name) + { + if (name == "External") + return; + + QMutexLocker lk(&m_mutex); + m_callbackSets.removeIf([name](const auto &pair) { return pair.first == name; }); + + m_createTerminalProcessInterface + = m_callbackSets.back().second.createTerminalProcessInterface; + m_openTerminal = m_callbackSets.back().second.openTerminal; + } + + Hooks::CreateTerminalProcessInterface createTerminalProcessInterface() + { + QMutexLocker lk(&m_mutex); + return m_createTerminalProcessInterface; + } + + Hooks::OpenTerminal openTerminal() + { + QMutexLocker lk(&m_mutex); + return m_openTerminal; + } - Hooks::OpenTerminalHook m_openTerminalHook; - Hooks::CreateTerminalProcessInterfaceHook m_createTerminalProcessInterfaceHook; Hooks::GetTerminalCommandsForDevicesHook m_getTerminalCommandsForDevicesHook; + +private: + Hooks::OpenTerminal m_openTerminal; + Hooks::CreateTerminalProcessInterface m_createTerminalProcessInterface; + + QMutex m_mutex; + QList> m_callbackSets; }; Hooks &Hooks::instance() @@ -117,14 +162,14 @@ Hooks::Hooks() Hooks::~Hooks() = default; -Hooks::OpenTerminalHook &Hooks::openTerminalHook() +void Hooks::openTerminal(const OpenTerminalParameters ¶meters) const { - return d->m_openTerminalHook; + d->openTerminal()(parameters); } -Hooks::CreateTerminalProcessInterfaceHook &Hooks::createTerminalProcessInterfaceHook() +ProcessInterface *Hooks::createTerminalProcessInterface() const { - return d->m_createTerminalProcessInterfaceHook; + return d->createTerminalProcessInterface()(); } Hooks::GetTerminalCommandsForDevicesHook &Hooks::getTerminalCommandsForDevicesHook() @@ -132,4 +177,14 @@ Hooks::GetTerminalCommandsForDevicesHook &Hooks::getTerminalCommandsForDevicesHo return d->m_getTerminalCommandsForDevicesHook; } +void Hooks::addCallbackSet(const QString &name, const CallbackSet &callbackSet) +{ + d->addCallbackSet(name, callbackSet); +} + +void Hooks::removeCallbackSet(const QString &name) +{ + d->removeCallbackSet(name); +} + } // namespace Utils::Terminal diff --git a/src/libs/utils/terminalhooks.h b/src/libs/utils/terminalhooks.h index 2da470d171..5a614400f4 100644 --- a/src/libs/utils/terminalhooks.h +++ b/src/libs/utils/terminalhooks.h @@ -37,7 +37,7 @@ private: }; namespace Terminal { -struct HooksPrivate; +class HooksPrivate; enum class ExitBehavior { Close, Restart, Keep }; @@ -61,18 +61,29 @@ QTCREATOR_UTILS_EXPORT FilePath defaultShellForDevice(const FilePath &deviceRoot class QTCREATOR_UTILS_EXPORT Hooks { public: - using OpenTerminalHook = Hook; - using CreateTerminalProcessInterfaceHook = Hook; + using OpenTerminal = std::function; + using CreateTerminalProcessInterface = std::function; + + struct CallbackSet + { + OpenTerminal openTerminal; + CreateTerminalProcessInterface createTerminalProcessInterface; + }; + using GetTerminalCommandsForDevicesHook = Hook>; public: static Hooks &instance(); ~Hooks(); - OpenTerminalHook &openTerminalHook(); - CreateTerminalProcessInterfaceHook &createTerminalProcessInterfaceHook(); GetTerminalCommandsForDevicesHook &getTerminalCommandsForDevicesHook(); + void openTerminal(const OpenTerminalParameters ¶meters) const; + ProcessInterface *createTerminalProcessInterface() const; + + void addCallbackSet(const QString &name, const CallbackSet &callbackSet); + void removeCallbackSet(const QString &name); + private: Hooks(); std::unique_ptr d; -- cgit v1.2.3 From 29873068eb88b51fe16d06f3ae8672dbb00c9845 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Wed, 15 Mar 2023 09:30:14 +0100 Subject: Utils: Store Device::osType in settings Change-Id: I41348be752598b7a8d1f1979764e7111cccc95a9 Reviewed-by: hjk Reviewed-by: Eike Ziller --- src/libs/utils/devicefileaccess.cpp | 44 ++++--------------------------------- src/libs/utils/devicefileaccess.h | 5 ----- src/libs/utils/filepath.cpp | 6 ++++- src/libs/utils/filepath.h | 1 + src/libs/utils/osspecificaspects.h | 30 +++++++++++++++++++++++++ 5 files changed, 40 insertions(+), 46 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/devicefileaccess.cpp b/src/libs/utils/devicefileaccess.cpp index 5cec46b9e2..1e3c771ee7 100644 --- a/src/libs/utils/devicefileaccess.cpp +++ b/src/libs/utils/devicefileaccess.cpp @@ -269,12 +269,6 @@ bool DeviceFileAccess::renameFile(const FilePath &filePath, const FilePath &targ return false; } -OsType DeviceFileAccess::osType(const FilePath &filePath) const -{ - Q_UNUSED(filePath) - return OsTypeOther; -} - FilePath DeviceFileAccess::symLinkTarget(const FilePath &filePath) const { Q_UNUSED(filePath) @@ -799,12 +793,6 @@ QByteArray DesktopDeviceFileAccess::fileId(const FilePath &filePath) const return result; } -OsType DesktopDeviceFileAccess::osType(const FilePath &filePath) const -{ - Q_UNUSED(filePath) - return HostOsInfo::hostOs(); -} - // UnixDeviceAccess UnixDeviceFileAccess::~UnixDeviceFileAccess() = default; @@ -1035,30 +1023,6 @@ expected_str UnixDeviceFileAccess::createTempFile(const FilePath &file return newPath; } -OsType UnixDeviceFileAccess::osType() const -{ - if (m_osType) - return *m_osType; - - const RunResult result = runInShell({"uname", {"-s"}, OsType::OsTypeLinux}); - QTC_ASSERT(result.exitCode == 0, return OsTypeLinux); - const QString osName = QString::fromUtf8(result.stdOut).trimmed(); - if (osName == "Darwin") - m_osType = OsTypeMac; - else if (osName == "Linux") - m_osType = OsTypeLinux; - else - m_osType = OsTypeOtherUnix; - - return *m_osType; -} - -OsType UnixDeviceFileAccess::osType(const FilePath &filePath) const -{ - Q_UNUSED(filePath); - return osType(); -} - QDateTime UnixDeviceFileAccess::lastModified(const FilePath &filePath) const { const RunResult result = runInShell( @@ -1072,7 +1036,7 @@ QStringList UnixDeviceFileAccess::statArgs(const FilePath &filePath, const QString &linuxFormat, const QString &macFormat) const { - return (osType() == OsTypeMac ? QStringList{"-f", macFormat} : QStringList{"-c", linuxFormat}) + return (filePath.osType() == OsTypeMac ? QStringList{"-f", macFormat} : QStringList{"-c", linuxFormat}) << "-L" << filePath.path(); } @@ -1150,7 +1114,7 @@ FilePathInfo UnixDeviceFileAccess::filePathInfo(const FilePath &filePath) const const RunResult stat = runInShell({"stat", args, OsType::OsTypeLinux}); return FileUtils::filePathInfoFromTriple(QString::fromLatin1(stat.stdOut), - osType() == OsTypeMac ? 8 : 16); + filePath.osType() == OsTypeMac ? 8 : 16); } // returns whether 'find' could be used. @@ -1165,7 +1129,7 @@ bool UnixDeviceFileAccess::iterateWithFind(const FilePath &filePath, // TODO: Using stat -L will always return the link target, not the link itself. // We may wan't to add the information that it is a link at some point. - const QString statFormat = osType() == OsTypeMac + const QString statFormat = filePath.osType() == OsTypeMac ? QLatin1String("-f \"%p %m %z\"") : QLatin1String("-c \"%f %Y %s\""); if (callBack.index() == 1) @@ -1192,7 +1156,7 @@ bool UnixDeviceFileAccess::iterateWithFind(const FilePath &filePath, if (entries.isEmpty()) return true; - const int modeBase = osType() == OsTypeMac ? 8 : 16; + const int modeBase = filePath.osType() == OsTypeMac ? 8 : 16; const auto toFilePath = [&filePath, &callBack, modeBase](const QString &entry) { diff --git a/src/libs/utils/devicefileaccess.h b/src/libs/utils/devicefileaccess.h index e3a2acdb8f..d57da462d2 100644 --- a/src/libs/utils/devicefileaccess.h +++ b/src/libs/utils/devicefileaccess.h @@ -45,7 +45,6 @@ protected: const FilePath &target) const; virtual bool renameFile(const FilePath &filePath, const FilePath &target) const; - virtual OsType osType(const FilePath &filePath) const; virtual FilePath symLinkTarget(const FilePath &filePath) const; virtual FilePathInfo filePathInfo(const FilePath &filePath) const; virtual QDateTime lastModified(const FilePath &filePath) const; @@ -100,7 +99,6 @@ protected: expected_str copyFile(const FilePath &filePath, const FilePath &target) const override; bool renameFile(const FilePath &filePath, const FilePath &target) const override; - OsType osType(const FilePath &filePath) const override; FilePath symLinkTarget(const FilePath &filePath) const override; FilePathInfo filePathInfo(const FilePath &filePath) const override; QDateTime lastModified(const FilePath &filePath) const override; @@ -159,7 +157,6 @@ protected: bool renameFile(const FilePath &filePath, const FilePath &target) const override; FilePathInfo filePathInfo(const FilePath &filePath) const override; - OsType osType(const FilePath &filePath) const override; FilePath symLinkTarget(const FilePath &filePath) const override; QDateTime lastModified(const FilePath &filePath) const override; QFile::Permissions permissions(const FilePath &filePath) const override; @@ -194,14 +191,12 @@ private: const FileFilter &filter, QStringList *found) const; - Utils::OsType osType() const; QStringList statArgs(const FilePath &filePath, const QString &linuxFormat, const QString &macFormat) const; mutable bool m_tryUseFind = true; mutable std::optional m_hasMkTemp; - mutable std::optional m_osType; }; } // Utils diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index f117f4e868..2345066190 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -1566,7 +1566,11 @@ bool FilePath::setPermissions(QFile::Permissions permissions) const OsType FilePath::osType() const { - return fileAccess()->osType(*this); + if (!needsDevice()) + return HostOsInfo::hostOs(); + + QTC_ASSERT(s_deviceHooks.osType, return HostOsInfo::hostOs()); + return s_deviceHooks.osType(*this); } bool FilePath::removeFile() const diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h index 877e8beb8e..0ecdc09dab 100644 --- a/src/libs/utils/filepath.h +++ b/src/libs/utils/filepath.h @@ -291,6 +291,7 @@ public: std::function isSameDevice; std::function(const FilePath &)> localSource; std::function openTerminal; + std::function osType; }; // For testing diff --git a/src/libs/utils/osspecificaspects.h b/src/libs/utils/osspecificaspects.h index 0cc22efe5f..c735f313ab 100644 --- a/src/libs/utils/osspecificaspects.h +++ b/src/libs/utils/osspecificaspects.h @@ -14,6 +14,36 @@ namespace Utils { // Add more as needed. enum OsType { OsTypeWindows, OsTypeLinux, OsTypeMac, OsTypeOtherUnix, OsTypeOther }; +inline QString osTypeToString(OsType osType) +{ + switch (osType) { + case OsTypeWindows: + return "Windows"; + case OsTypeLinux: + return "Linux"; + case OsTypeMac: + return "Mac"; + case OsTypeOtherUnix: + return "Other Unix"; + case OsTypeOther: + default: + return "Other"; + } +} + +inline OsType osTypeFromString(const QString &string) +{ + if (string == "Windows") + return OsTypeWindows; + if (string == "Linux") + return OsTypeLinux; + if (string == "Mac") + return OsTypeMac; + if (string == "Other Unix") + return OsTypeOtherUnix; + return OsTypeOther; +} + namespace OsSpecificAspects { inline QString withExecutableSuffix(OsType osType, const QString &executable) -- cgit v1.2.3 From 10816ecbe016e23cdbd7fd8ebcb34ae5cd4563f7 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Thu, 23 Mar 2023 14:28:06 +0100 Subject: Terminal: Add default themes to Creator themes Fixes: QTCREATORBUG-28939 Change-Id: I51ded621cdd2e87743a93853686bba09aa9aa44d Reviewed-by: Cristian Adam --- src/libs/utils/theme/theme.h | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/theme/theme.h b/src/libs/utils/theme/theme.h index 1fbae4934e..d3ba34b3ee 100644 --- a/src/libs/utils/theme/theme.h +++ b/src/libs/utils/theme/theme.h @@ -420,6 +420,27 @@ public: DSstatePanelBackground, DSstateHighlight, + + TerminalForeground, + TerminalBackground, + TerminalSelection, + + TerminalAnsi0, + TerminalAnsi1, + TerminalAnsi2, + TerminalAnsi3, + TerminalAnsi4, + TerminalAnsi5, + TerminalAnsi6, + TerminalAnsi7, + TerminalAnsi8, + TerminalAnsi9, + TerminalAnsi10, + TerminalAnsi11, + TerminalAnsi12, + TerminalAnsi13, + TerminalAnsi14, + TerminalAnsi15, }; enum ImageFile { -- cgit v1.2.3 From ae4e1782214b3284cd8cf8b842909ffa6e2d3aaa Mon Sep 17 00:00:00 2001 From: hjk Date: Thu, 23 Mar 2023 12:40:20 +0100 Subject: Utils: Move the CommandLine destructor out-of-line Not really cheap and bloats the user code. Change-Id: I2039794f0608838d97b404bb7d92b489d22f2cbe Reviewed-by: Christian Stenger Reviewed-by: --- src/libs/utils/commandline.cpp | 2 ++ src/libs/utils/commandline.h | 2 ++ 2 files changed, 4 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/commandline.cpp b/src/libs/utils/commandline.cpp index e19eafa4c1..9f6fdec35d 100644 --- a/src/libs/utils/commandline.cpp +++ b/src/libs/utils/commandline.cpp @@ -1409,6 +1409,8 @@ CommandLine::CommandLine(const FilePath &executable) : m_executable(executable) {} +CommandLine::~CommandLine() = default; + CommandLine::CommandLine(const FilePath &exe, const QStringList &args) : m_executable(exe) { diff --git a/src/libs/utils/commandline.h b/src/libs/utils/commandline.h index c3aa17c7d8..d7fc0a066b 100644 --- a/src/libs/utils/commandline.h +++ b/src/libs/utils/commandline.h @@ -119,6 +119,8 @@ public: enum RawType { Raw }; CommandLine(); + ~CommandLine(); + explicit CommandLine(const FilePath &executable); CommandLine(const FilePath &exe, const QStringList &args); CommandLine(const FilePath &exe, const QStringList &args, OsType osType); -- cgit v1.2.3 From 2766b4004b69377480f9e3a701fd20eb51b578bd Mon Sep 17 00:00:00 2001 From: hjk Date: Thu, 26 Jan 2023 17:48:36 +0100 Subject: Utils: Continue Environment/EnvironmentChange consolidation Make Environment a stack of changes that gets "expanded" to a full environment before things are actively accessed. Later this expansion should be done lazily if possible. Task-number: QTCREATORBUG-28357 Change-Id: If1c7bfdb9f58b81e71c51ed87ee75d6964a47019 Reviewed-by: Marcus Tillmanns Reviewed-by: Qt CI Bot --- src/libs/utils/aspects.cpp | 15 +-- src/libs/utils/aspects.h | 1 - src/libs/utils/environment.cpp | 255 ++++++++++++++++++++++++++++---------- src/libs/utils/environment.h | 98 ++++++--------- src/libs/utils/filepath.h | 1 - src/libs/utils/launchersocket.cpp | 2 + src/libs/utils/pathchooser.cpp | 14 +-- src/libs/utils/pathchooser.h | 2 - src/libs/utils/qtcprocess.cpp | 29 ++--- 9 files changed, 249 insertions(+), 168 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index 51be951a8f..aed8c851d1 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -632,7 +632,7 @@ public: QString m_placeHolderText; QString m_historyCompleterKey; PathChooser::Kind m_expectedKind = PathChooser::File; - EnvironmentChange m_environmentChange; + Environment m_environment; QPointer m_labelDisplay; QPointer m_lineEditDisplay; QPointer m_pathChooserDisplay; @@ -975,16 +975,11 @@ void StringAspect::setExpectedKind(const PathChooser::Kind expectedKind) d->m_pathChooserDisplay->setExpectedKind(expectedKind); } -void StringAspect::setEnvironmentChange(const EnvironmentChange &change) -{ - d->m_environmentChange = change; - if (d->m_pathChooserDisplay) - d->m_pathChooserDisplay->setEnvironmentChange(change); -} - void StringAspect::setEnvironment(const Environment &env) { - setEnvironmentChange(EnvironmentChange::fromDictionary(env.toDictionary())); + d->m_environment = env; + if (d->m_pathChooserDisplay) + d->m_pathChooserDisplay->setEnvironment(env); } void StringAspect::setBaseFileName(const FilePath &baseFileName) @@ -1082,7 +1077,7 @@ void StringAspect::addToLayout(Layouting::LayoutBuilder &builder) d->m_pathChooserDisplay->setHistoryCompleter(d->m_historyCompleterKey); if (d->m_validator) d->m_pathChooserDisplay->setValidationFunction(d->m_validator); - d->m_pathChooserDisplay->setEnvironmentChange(d->m_environmentChange); + d->m_pathChooserDisplay->setEnvironment(d->m_environment); d->m_pathChooserDisplay->setBaseDirectory(d->m_baseFileName); d->m_pathChooserDisplay->setOpenTerminalHandler(d->m_openTerminal); if (defaultValue() == value()) diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index 1aaaca3f29..0b9a560659 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -383,7 +383,6 @@ public: void setPlaceHolderText(const QString &placeHolderText); void setHistoryCompleter(const QString &historyCompleterKey); void setExpectedKind(const PathChooser::Kind expectedKind); - void setEnvironmentChange(const EnvironmentChange &change); void setEnvironment(const Environment &env); void setBaseFileName(const FilePath &baseFileName); void setUndoRedoEnabled(bool readOnly); diff --git a/src/libs/utils/environment.cpp b/src/libs/utils/environment.cpp index 462eeab31d..e64db0e2a3 100644 --- a/src/libs/utils/environment.cpp +++ b/src/libs/utils/environment.cpp @@ -19,84 +19,137 @@ Q_GLOBAL_STATIC_WITH_ARGS(Environment, staticSystemEnvironment, (QProcessEnvironment::systemEnvironment().toStringList())) Q_GLOBAL_STATIC(QVector, environmentProviders) +Environment::Environment() + : m_dict(HostOsInfo::hostOs()) +{} + +Environment::Environment(OsType osType) + : m_dict(osType) +{} + +Environment::Environment(const QStringList &env, OsType osType) + : m_dict(osType) +{ + m_changeItems.append(NameValueDictionary(env, osType)); +} + +Environment::Environment(const NameValuePairs &nameValues) +{ + m_changeItems.append(NameValueDictionary(nameValues)); +} + +Environment::Environment(const NameValueDictionary &dict) +{ + m_changeItems.append(dict); +} + NameValueItems Environment::diff(const Environment &other, bool checkAppendPrepend) const { - return m_dict.diff(other.m_dict, checkAppendPrepend); + const NameValueDictionary &dict = resolved(); + const NameValueDictionary &otherDict = other.resolved(); + return dict.diff(otherDict, checkAppendPrepend); } Environment::FindResult Environment::find(const QString &name) const { - const auto it = m_dict.constFind(name); - if (it == m_dict.constEnd()) + const NameValueDictionary &dict = resolved(); + const auto it = dict.constFind(name); + if (it == dict.constEnd()) return {}; return Entry{it.key().name, it.value().first, it.value().second}; } void Environment::forEachEntry(const std::function &callBack) const { - for (auto it = m_dict.m_values.constBegin(); it != m_dict.m_values.constEnd(); ++it) + const NameValueDictionary &dict = resolved(); + for (auto it = dict.m_values.constBegin(); it != dict.m_values.constEnd(); ++it) callBack(it.key().name, it.value().first, it.value().second); } +bool Environment::operator==(const Environment &other) const +{ + const NameValueDictionary &dict = resolved(); + const NameValueDictionary &otherDict = other.resolved(); + return dict == otherDict; +} + +bool Environment::operator!=(const Environment &other) const +{ + const NameValueDictionary &dict = resolved(); + const NameValueDictionary &otherDict = other.resolved(); + return dict != otherDict; +} + +QString Environment::value(const QString &key) const +{ + const NameValueDictionary &dict = resolved(); + return dict.value(key); +} + +QString Environment::value_or(const QString &key, const QString &defaultValue) const +{ + const NameValueDictionary &dict = resolved(); + return dict.hasKey(key) ? dict.value(key) : defaultValue; +} + +bool Environment::hasKey(const QString &key) const +{ + const NameValueDictionary &dict = resolved(); + return dict.hasKey(key); +} + bool Environment::hasChanges() const { - return m_dict.size() != 0; + const NameValueDictionary &dict = resolved(); + return dict.size() != 0; +} + +OsType Environment::osType() const +{ + return m_dict.m_osType; +} + +QStringList Environment::toStringList() const +{ + const NameValueDictionary &dict = resolved(); + return dict.toStringList(); } QProcessEnvironment Environment::toProcessEnvironment() const { + const NameValueDictionary &dict = resolved(); QProcessEnvironment result; - for (auto it = m_dict.m_values.constBegin(); it != m_dict.m_values.constEnd(); ++it) { + for (auto it = dict.m_values.constBegin(); it != dict.m_values.constEnd(); ++it) { if (it.value().second) - result.insert(it.key().name, expandedValueForKey(m_dict.key(it))); + result.insert(it.key().name, expandedValueForKey(dict.key(it))); } return result; } void Environment::appendOrSetPath(const FilePath &value) { - QTC_CHECK(value.osType() == osType()); + QTC_CHECK(value.osType() == m_dict.m_osType); if (value.isEmpty()) return; - appendOrSet("PATH", value.nativePath(), - QString(OsSpecificAspects::pathListSeparator(osType()))); + appendOrSet("PATH", value.nativePath(), OsSpecificAspects::pathListSeparator(osType())); } void Environment::prependOrSetPath(const FilePath &value) { - QTC_CHECK(value.osType() == osType()); + QTC_CHECK(value.osType() == m_dict.m_osType); if (value.isEmpty()) return; - prependOrSet("PATH", value.nativePath(), - QString(OsSpecificAspects::pathListSeparator(osType()))); + prependOrSet("PATH", value.nativePath(), OsSpecificAspects::pathListSeparator(osType())); } void Environment::appendOrSet(const QString &key, const QString &value, const QString &sep) { - QTC_ASSERT(!key.contains('='), return ); - const auto it = m_dict.findKey(key); - if (it == m_dict.m_values.end()) { - m_dict.m_values.insert(DictKey(key, m_dict.nameCaseSensitivity()), {value, true}); - } else { - // Append unless it is already there - const QString toAppend = sep + value; - if (!it.value().first.endsWith(toAppend)) - it.value().first.append(toAppend); - } + addItem(Item{std::in_place_index_t(), key, value, sep}); } void Environment::prependOrSet(const QString &key, const QString &value, const QString &sep) { - QTC_ASSERT(!key.contains('='), return ); - const auto it = m_dict.findKey(key); - if (it == m_dict.m_values.end()) { - m_dict.m_values.insert(DictKey(key, m_dict.nameCaseSensitivity()), {value, true}); - } else { - // Prepend unless it is already there - const QString toPrepend = value + sep; - if (!it.value().first.startsWith(toPrepend)) - it.value().first.prepend(toPrepend); - } + addItem(Item{std::in_place_index_t(), key, value, sep}); } void Environment::prependOrSetLibrarySearchPath(const FilePath &value) @@ -105,11 +158,11 @@ void Environment::prependOrSetLibrarySearchPath(const FilePath &value) switch (osType()) { case OsTypeWindows: { const QChar sep = ';'; - prependOrSet("PATH", value.nativePath(), QString(sep)); + prependOrSet("PATH", value.nativePath(), sep); break; } case OsTypeMac: { - const QString sep = ":"; + const QChar sep = ':'; const QString nativeValue = value.nativePath(); prependOrSet("DYLD_LIBRARY_PATH", nativeValue, sep); prependOrSet("DYLD_FRAMEWORK_PATH", nativeValue, sep); @@ -118,7 +171,7 @@ void Environment::prependOrSetLibrarySearchPath(const FilePath &value) case OsTypeLinux: case OsTypeOtherUnix: { const QChar sep = ':'; - prependOrSet("LD_LIBRARY_PATH", value.nativePath(), QString(sep)); + prependOrSet("LD_LIBRARY_PATH", value.nativePath(), sep); break; } default: @@ -141,8 +194,7 @@ Environment Environment::systemEnvironment() void Environment::setupEnglishOutput() { - m_dict.set("LC_MESSAGES", "en_US.utf8"); - m_dict.set("LANGUAGE", "en_US:en"); + addItem(Item{std::in_place_index_t()}); } using SearchResultCallback = std::function; @@ -190,7 +242,8 @@ static FilePaths appendExeExtensions(const Environment &env, const FilePath &exe QString Environment::expandedValueForKey(const QString &key) const { - return expandVariables(m_dict.value(key)); + const NameValueDictionary &dict = resolved(); + return expandVariables(dict.value(key)); } static void searchInDirectoriesHelper(const SearchResultCallback &resultCallback, @@ -324,14 +377,16 @@ void Environment::setSystemEnvironment(const Environment &environment) */ QString Environment::expandVariables(const QString &input) const { + const NameValueDictionary &dict = resolved(); + QString result = input; if (osType() == OsTypeWindows) { for (int vStart = -1, i = 0; i < result.length(); ) { if (result.at(i++) == '%') { if (vStart > 0) { - const auto it = m_dict.findKey(result.mid(vStart, i - vStart - 1)); - if (it != m_dict.m_values.constEnd()) { + const auto it = dict.findKey(result.mid(vStart, i - vStart - 1)); + if (it != dict.m_values.constEnd()) { result.replace(vStart - 1, i - vStart + 1, it->first); i = vStart - 1 + it->first.length(); vStart = -1; @@ -403,6 +458,12 @@ QStringList Environment::expandVariables(const QStringList &variables) const return transform(variables, [this](const QString &i) { return expandVariables(i); }); } +NameValueDictionary Environment::toDictionary() const +{ + const NameValueDictionary &dict = resolved(); + return dict; +} + void EnvironmentProvider::addProvider(EnvironmentProvider &&provider) { environmentProviders->append(std::move(provider)); @@ -421,63 +482,125 @@ std::optional EnvironmentProvider::provider(const QByteArra return std::nullopt; } -void EnvironmentChange::addSetValue(const QString &key, const QString &value) +void Environment::addItem(const Item &item) +{ + m_dict.clear(); + m_changeItems.append(item); +} + +void Environment::set(const QString &key, const QString &value, bool enabled) { - m_changeItems.append(Item{std::in_place_index_t(), QPair{key, value}}); + addItem(Item{std::in_place_index_t(), + std::tuple{key, value, enabled}}); } -void EnvironmentChange::addUnsetValue(const QString &key) +void Environment::unset(const QString &key) { - m_changeItems.append(Item{std::in_place_index_t(), key}); + addItem(Item{std::in_place_index_t(), key}); } -void EnvironmentChange::addPrependToPath(const FilePaths &values) +void Environment::modify(const NameValueItems &items) { + addItem(Item{std::in_place_index_t(), items}); +} + +void Environment::prependToPath(const FilePaths &values) +{ + m_dict.clear(); for (int i = values.size(); --i >= 0; ) { const FilePath value = values.at(i); - m_changeItems.append(Item{std::in_place_index_t(), value}); + m_changeItems.append(Item{ + std::in_place_index_t(), + QString("PATH"), + value.nativePath(), + value.pathListSeparator() + }); } } -void EnvironmentChange::addAppendToPath(const FilePaths &values) +void Environment::appendToPath(const FilePaths &values) { - for (const FilePath &value : values) - m_changeItems.append(Item{std::in_place_index_t(), value}); + m_dict.clear(); + for (const FilePath &value : values) { + m_changeItems.append(Item{ + std::in_place_index_t(), + QString("PATH"), + value.nativePath(), + value.pathListSeparator() + }); + } } -EnvironmentChange EnvironmentChange::fromDictionary(const NameValueDictionary &dict) +const NameValueDictionary &Environment::resolved() const { - EnvironmentChange change; - change.m_changeItems.append(Item{std::in_place_index_t(), dict}); - return change; -} + if (m_dict.size() != 0) + return m_dict; -void EnvironmentChange::applyToEnvironment(Environment &env) const -{ for (const Item &item : m_changeItems) { switch (item.index()) { case SetSystemEnvironment: - env = Environment::systemEnvironment(); + m_dict = Environment::systemEnvironment().toDictionary(); break; case SetFixedDictionary: - env = Environment(std::get(item)); + m_dict = std::get(item); break; case SetValue: { - const QPair data = std::get(item); - env.set(data.first, data.second); + auto [key, value, enabled] = std::get(item); + m_dict.set(key, value, enabled); break; } case UnsetValue: - env.unset(std::get(item)); + m_dict.unset(std::get(item)); break; - case PrependToPath: - env.prependOrSetPath(std::get(item)); + case PrependOrSet: { + auto [key, value, sep] = std::get(item); + QTC_ASSERT(!key.contains('='), return m_dict); + const auto it = m_dict.findKey(key); + if (it == m_dict.m_values.end()) { + m_dict.m_values.insert(DictKey(key, m_dict.nameCaseSensitivity()), {value, true}); + } else { + // Prepend unless it is already there + const QString toPrepend = value + sep; + if (!it.value().first.startsWith(toPrepend)) + it.value().first.prepend(toPrepend); + } break; - case AppendToPath: - env.appendOrSetPath(std::get(item)); + } + case AppendOrSet: { + auto [key, value, sep] = std::get(item); + QTC_ASSERT(!key.contains('='), return m_dict); + const auto it = m_dict.findKey(key); + if (it == m_dict.m_values.end()) { + m_dict.m_values.insert(DictKey(key, m_dict.nameCaseSensitivity()), {value, true}); + } else { + // Prepend unless it is already there + const QString toAppend = sep + value; + if (!it.value().first.endsWith(toAppend)) + it.value().first.append(toAppend); + } + break; + } + case Modify: { + NameValueItems items = std::get(item); + m_dict.modify(items); + break; + } + case SetupEnglishOutput: + m_dict.set("LC_MESSAGES", "en_US.utf8"); + m_dict.set("LANGUAGE", "en_US:en"); break; } } + + return m_dict; +} + +Environment Environment::appliedToEnvironment(const Environment &base) const +{ + Environment res = base; + res.m_dict.clear(); + res.m_changeItems.append(m_changeItems); + return res; } /*! diff --git a/src/libs/utils/environment.h b/src/libs/utils/environment.h index 541a730965..8424eb1d24 100644 --- a/src/libs/utils/environment.h +++ b/src/libs/utils/environment.h @@ -22,27 +22,24 @@ namespace Utils { class QTCREATOR_UTILS_EXPORT Environment final { public: - Environment() : m_dict(HostOsInfo::hostOs()) {} - explicit Environment(OsType osType) : m_dict(osType) {} - explicit Environment(const QStringList &env, OsType osType = HostOsInfo::hostOs()) - : m_dict(env, osType) {} - explicit Environment(const NameValuePairs &nameValues) : m_dict(nameValues) {} - explicit Environment(const NameValueDictionary &dict) : m_dict(dict) {} - - QString value(const QString &key) const { return m_dict.value(key); } - QString value_or(const QString &key, const QString &defaultValue) const - { - return m_dict.hasKey(key) ? m_dict.value(key) : defaultValue; - } - bool hasKey(const QString &key) const { return m_dict.hasKey(key); } - - void set(const QString &key, const QString &value, bool enabled = true) { m_dict.set(key, value, enabled); } - void unset(const QString &key) { m_dict.unset(key); } - void modify(const NameValueItems &items) { m_dict.modify(items); } + Environment(); + explicit Environment(OsType osType); + explicit Environment(const QStringList &env, OsType osType = HostOsInfo::hostOs()); + explicit Environment(const NameValuePairs &nameValues); + explicit Environment(const NameValueDictionary &dict); + + QString value(const QString &key) const; + QString value_or(const QString &key, const QString &defaultValue) const; + bool hasKey(const QString &key) const; + + void set(const QString &key, const QString &value, bool enabled = true); + void unset(const QString &key); + void modify(const NameValueItems &items); bool hasChanges() const; - QStringList toStringList() const { return m_dict.toStringList(); } + OsType osType() const; + QStringList toStringList() const; QProcessEnvironment toProcessEnvironment() const; void appendOrSet(const QString &key, const QString &value, const QString &sep = QString()); @@ -54,6 +51,9 @@ public: void prependOrSetLibrarySearchPath(const FilePath &value); void prependOrSetLibrarySearchPaths(const FilePaths &values); + void prependToPath(const FilePaths &values); + void appendToPath(const FilePaths &values); + void setupEnglishOutput(); FilePath searchInPath(const QString &executable, @@ -74,76 +74,58 @@ public: FilePath expandVariables(const FilePath &input) const; QStringList expandVariables(const QStringList &input) const; - OsType osType() const { return m_dict.osType(); } - - NameValueDictionary toDictionary() const { return m_dict; } // FIXME: avoid + NameValueDictionary toDictionary() const; // FIXME: avoid NameValueItems diff(const Environment &other, bool checkAppendPrepend = false) const; // FIXME: avoid - void setCombineWithDeviceEnvironment(bool combine) { m_combineWithDeviceEnvironment = combine; } - bool combineWithDeviceEnvironment() const { return m_combineWithDeviceEnvironment; } - struct Entry { QString key; QString value; bool enabled; }; using FindResult = std::optional; FindResult find(const QString &name) const; // Note res->key may differ in case from name. void forEachEntry(const std::function &callBack) const; - friend bool operator!=(const Environment &first, const Environment &second) - { - return first.m_dict != second.m_dict; - } - - friend bool operator==(const Environment &first, const Environment &second) - { - return first.m_dict == second.m_dict; - } + bool operator!=(const Environment &other) const; + bool operator==(const Environment &other) const; static Environment systemEnvironment(); static void modifySystemEnvironment(const EnvironmentItems &list); // use with care!!! static void setSystemEnvironment(const Environment &environment); // don't use at all!!! -private: - NameValueDictionary m_dict; - bool m_combineWithDeviceEnvironment = true; -}; - -class QTCREATOR_UTILS_EXPORT EnvironmentChange final -{ -public: - EnvironmentChange() = default; - enum Type { SetSystemEnvironment, SetFixedDictionary, SetValue, UnsetValue, - PrependToPath, - AppendToPath, + PrependOrSet, + AppendOrSet, + Modify, + SetupEnglishOutput, }; using Item = std::variant< - std::monostate, // SetSystemEnvironment dummy - NameValueDictionary, // SetFixedDictionary - QPair, // SetValue - QString, // UnsetValue - FilePath, // PrependToPath - FilePath // AppendToPath + std::monostate, // SetSystemEnvironment dummy + NameValueDictionary, // SetFixedDictionary + std::tuple, // SetValue (key, value, enabled) + QString, // UnsetValue (key) + std::tuple, // PrependOrSet (key, value, separator) + std::tuple, // AppendOrSet (key, value, separator) + NameValueItems, // Modify + std::monostate // SetupEnglishOutput >; - static EnvironmentChange fromDictionary(const NameValueDictionary &dict); + void addItem(const Item &item); - void applyToEnvironment(Environment &) const; + Environment appliedToEnvironment(const Environment &base) const; - void addSetValue(const QString &key, const QString &value); - void addUnsetValue(const QString &key); - void addPrependToPath(const FilePaths &values); - void addAppendToPath(const FilePaths &values); + const NameValueDictionary &resolved() const; private: - QList m_changeItems; + mutable QList m_changeItems; + mutable NameValueDictionary m_dict; // Latest resolved. }; +using EnviromentChange = Environment; + class QTCREATOR_UTILS_EXPORT EnvironmentProvider { public: diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h index 0ecdc09dab..220c6de5da 100644 --- a/src/libs/utils/filepath.h +++ b/src/libs/utils/filepath.h @@ -32,7 +32,6 @@ namespace Utils { class DeviceFileAccess; class Environment; -class EnvironmentChange; enum class FileStreamHandle; template using Continuation = std::function; diff --git a/src/libs/utils/launchersocket.cpp b/src/libs/utils/launchersocket.cpp index f4feda595a..ccf2ee7b2f 100644 --- a/src/libs/utils/launchersocket.cpp +++ b/src/libs/utils/launchersocket.cpp @@ -246,6 +246,8 @@ void CallerHandle::start(const QString &program, const QStringList &arguments) p.command = m_command; p.arguments = m_arguments; p.env = m_setup->m_environment.toStringList(); + if (p.env.isEmpty()) + p.env = Environment::systemEnvironment().toStringList(); p.workingDir = m_setup->m_workingDirectory.path(); p.processMode = m_setup->m_processMode; p.writeData = m_setup->m_writeData; diff --git a/src/libs/utils/pathchooser.cpp b/src/libs/utils/pathchooser.cpp index 53825931b9..0f0737f54c 100644 --- a/src/libs/utils/pathchooser.cpp +++ b/src/libs/utils/pathchooser.cpp @@ -171,7 +171,7 @@ public: FilePath m_initialBrowsePathOverride; QString m_defaultValue; FilePath m_baseDirectory; - EnvironmentChange m_environmentChange; + Environment m_environment; BinaryVersionToolTipEventFilter *m_binaryVersionToolTipEventFilter = nullptr; QList m_buttons; const MacroExpander *m_macroExpander = globalMacroExpander(); @@ -196,8 +196,7 @@ FilePath PathChooserPrivate::expandedPath(const FilePath &input) const FilePath path = input; - Environment env = path.deviceEnvironment(); - m_environmentChange.applyToEnvironment(env); + Environment env = m_environment.appliedToEnvironment(path.deviceEnvironment()); path = env.expandVariables(path); if (m_macroExpander) @@ -324,20 +323,15 @@ void PathChooser::setBaseDirectory(const FilePath &base) triggerChanged(); } -void PathChooser::setEnvironment(const Environment &env) -{ - setEnvironmentChange(EnvironmentChange::fromDictionary(env.toDictionary())); -} - FilePath PathChooser::baseDirectory() const { return d->m_baseDirectory; } -void PathChooser::setEnvironmentChange(const EnvironmentChange &env) +void PathChooser::setEnvironment(const Environment &env) { QString oldExpand = filePath().toString(); - d->m_environmentChange = env; + d->m_environment = env; if (filePath().toString() != oldExpand) { triggerChanged(); emit rawPathChanged(); diff --git a/src/libs/utils/pathchooser.h b/src/libs/utils/pathchooser.h index 92b5973b2d..62a370185d 100644 --- a/src/libs/utils/pathchooser.h +++ b/src/libs/utils/pathchooser.h @@ -20,7 +20,6 @@ namespace Utils { class CommandLine; class MacroExpander; class Environment; -class EnvironmentChange; class PathChooserPrivate; class QTCREATOR_UTILS_EXPORT PathChooser : public QWidget @@ -77,7 +76,6 @@ public: void setBaseDirectory(const FilePath &base); void setEnvironment(const Environment &env); - void setEnvironmentChange(const EnvironmentChange &change); /** Returns the suggested label title when used in a form layout. */ static QString label(); diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/qtcprocess.cpp index 04276a610b..5220e8b5d5 100644 --- a/src/libs/utils/qtcprocess.cpp +++ b/src/libs/utils/qtcprocess.cpp @@ -353,13 +353,18 @@ public: return; } + QProcessEnvironment penv = m_setup.m_environment.toProcessEnvironment(); + if (penv.isEmpty()) + penv = Environment::systemEnvironment().toProcessEnvironment(); + const QStringList senv = penv.toStringList(); + bool startResult = m_ptyProcess->startProcess(program, HostOsInfo::isWindowsHost() ? QStringList{m_setup.m_nativeArguments} << arguments : arguments, m_setup.m_workingDirectory.nativePath(), - m_setup.m_environment.toProcessEnvironment().toStringList(), + senv, m_setup.m_ptyData->size().width(), m_setup.m_ptyData->size().height()); @@ -458,7 +463,9 @@ private: handler->setWindowsSpecificStartupFlags(m_setup.m_belowNormalPriority, m_setup.m_createConsoleOnWindows); - m_process->setProcessEnvironment(m_setup.m_environment.toProcessEnvironment()); + const QProcessEnvironment penv = m_setup.m_environment.toProcessEnvironment(); + if (!penv.isEmpty()) + m_process->setProcessEnvironment(penv); m_process->setWorkingDirectory(m_setup.m_workingDirectory.path()); m_process->setStandardInputFile(m_setup.m_standardInputFile); m_process->setProcessChannelMode(m_setup.m_processChannelMode); @@ -715,7 +722,6 @@ public: , q(parent) , m_killTimer(this) { - m_setup.m_controlEnvironment = Environment::systemEnvironment(); m_killTimer.setSingleShot(true); connect(&m_killTimer, &QTimer::timeout, this, [this] { m_killTimer.stop(); @@ -769,22 +775,6 @@ public: return rootCommand; } - Environment fullEnvironment() const - { - Environment env = m_setup.m_environment; - if (!env.hasChanges() && env.combineWithDeviceEnvironment()) { - // FIXME: Either switch to using EnvironmentChange instead of full Environments, or - // feed the full environment into the QtcProcess instead of fixing it up here. - // qWarning("QtcProcess::start: Empty environment set when running '%s'.", - // qPrintable(m_setup.m_commandLine.executable().toString())); - env = m_setup.m_commandLine.executable().deviceEnvironment(); - } - // TODO: needs SshSettings - // if (m_runAsRoot) - // RunControl::provideAskPassEntry(env); - return env; - } - QtcProcess *q; std::unique_ptr m_blockingInterface; std::unique_ptr m_process; @@ -1227,7 +1217,6 @@ void QtcProcess::start() d->m_state = QProcess::Starting; d->m_process->m_setup = d->m_setup; d->m_process->m_setup.m_commandLine = d->fullCommandLine(); - d->m_process->m_setup.m_environment = d->fullEnvironment(); d->emitGuardedSignal(&QtcProcess::starting); d->m_process->start(); } -- cgit v1.2.3 From 83d40683d903000e484303bd93c08ea81a7cca47 Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Thu, 23 Mar 2023 15:07:44 +0100 Subject: Core: Fix error text color in SearchResultWidget Remove the CanceledSearchTextColor theme role and use TextColorError instead. Change-Id: I3dde538d107ecfdd9c9a3f2406f6cd26ad28c902 Reviewed-by: David Schulz Reviewed-by: --- src/libs/utils/theme/theme.h | 1 - 1 file changed, 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/theme/theme.h b/src/libs/utils/theme/theme.h index d3ba34b3ee..2652c73b13 100644 --- a/src/libs/utils/theme/theme.h +++ b/src/libs/utils/theme/theme.h @@ -36,7 +36,6 @@ public: BadgeLabelBackgroundColorUnchecked, BadgeLabelTextColorChecked, BadgeLabelTextColorUnchecked, - CanceledSearchTextColor, ComboBoxArrowColor, ComboBoxArrowColorDisabled, ComboBoxTextColor, -- cgit v1.2.3 From 47fcb28c8f5c401788a2865d9425bf52b46ac494 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Fri, 24 Mar 2023 10:52:44 +0100 Subject: Utils: Restore ctrlc stub It was incorrectly removed in 0870f2583bbc659df00ff65bf51918b940221665 Change-Id: Id219981d459ab3bf9560945275ed874cc63efa64 Reviewed-by: Alessandro Portale --- src/libs/utils/CMakeLists.txt | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index 7277b7c8cf..6b2e835d19 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -272,3 +272,12 @@ extend_qtc_library(Utils fsengine/fsenginehandler.h fsengine/filepathinfocache.h ) + +if (WIN32) + add_qtc_executable(qtcreator_ctrlc_stub + DEPENDS user32 shell32 + DEFINES _UNICODE UNICODE _CRT_SECURE_NO_WARNINGS + SOURCES + process_ctrlc_stub.cpp + ) +endif() -- cgit v1.2.3 From 3526b6a7350704e0b89706a175bea2141a47d0e1 Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Thu, 23 Mar 2023 16:40:10 +0100 Subject: Terminal: Introduce Add/Close icons for the pane Required a new "close" overlay. Change-Id: I5268ec280992124ebcee78a73ab58b18e7c9309b Reviewed-by: Marcus Tillmanns Reviewed-by: Cristian Adam --- src/libs/utils/images/iconoverlay_close_small.png | Bin 0 -> 173 bytes src/libs/utils/images/iconoverlay_close_small@2x.png | Bin 0 -> 249 bytes src/libs/utils/utils.qrc | 2 ++ 3 files changed, 2 insertions(+) create mode 100644 src/libs/utils/images/iconoverlay_close_small.png create mode 100644 src/libs/utils/images/iconoverlay_close_small@2x.png (limited to 'src/libs') diff --git a/src/libs/utils/images/iconoverlay_close_small.png b/src/libs/utils/images/iconoverlay_close_small.png new file mode 100644 index 0000000000..e39b67cfbb Binary files /dev/null and b/src/libs/utils/images/iconoverlay_close_small.png differ diff --git a/src/libs/utils/images/iconoverlay_close_small@2x.png b/src/libs/utils/images/iconoverlay_close_small@2x.png new file mode 100644 index 0000000000..e8a02dff03 Binary files /dev/null and b/src/libs/utils/images/iconoverlay_close_small@2x.png differ diff --git a/src/libs/utils/utils.qrc b/src/libs/utils/utils.qrc index 1f2ef64898..20f6d7c788 100644 --- a/src/libs/utils/utils.qrc +++ b/src/libs/utils/utils.qrc @@ -173,6 +173,8 @@ images/iconoverlay_add@2x.png images/iconoverlay_add_background.png images/iconoverlay_add_background@2x.png + images/iconoverlay_close_small.png + images/iconoverlay_close_small@2x.png images/iconoverlay_error.png images/iconoverlay_error@2x.png images/iconoverlay_error_background.png -- cgit v1.2.3 From b4bc5f6e1b077d620add58f057003abdd7fe620a Mon Sep 17 00:00:00 2001 From: hjk Date: Wed, 22 Mar 2023 14:49:42 +0100 Subject: Utils: Add short documentation for two FilePath functions ensureWriteableDir() and createDir() Also change a few \returns to Returns. Change-Id: I8c80616a465a7e665ff56fab8279e43e5755fb4f Reviewed-by: Leena Miettinen --- src/libs/utils/filepath.cpp | 95 ++++++++++++++++++++++++++------------------- 1 file changed, 56 insertions(+), 39 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 2345066190..9b97fb6b02 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -163,7 +163,7 @@ FilePath FilePath::fromFileInfo(const QFileInfo &info) } /*! - \returns a QFileInfo + Returns a QFileInfo. */ QFileInfo FilePath::toFileInfo() const { @@ -215,7 +215,7 @@ QString decodeHost(QString host) } /*! - \returns a QString for passing through QString based APIs + Returns a QString for passing through QString based APIs. \note This is obsolete API and should be replaced by extended use of proper \c FilePath, or, in case this is not possible by \c toFSPathString(). @@ -243,7 +243,7 @@ QString FilePath::toString() const } /*! - \returns a QString for passing on to QString based APIs + Returns a QString for passing on to QString based APIs. This uses a /__qtc_devices__/host/path setup. @@ -296,7 +296,7 @@ QString FilePath::toUserOutput() const } /*! - \returns a QString to pass to target system native commands, without the device prefix. + Returns a QString to pass to target system native commands, without the device prefix. Converts the separators to the native format of the system this path belongs to. @@ -350,7 +350,7 @@ QString FilePath::fileNameWithPathComponents(int pathComponents) const } /*! - \returns the base name of the file without the path. + Returns the base name of the file without the path. The base name consists of all characters in the file up to (but not including) the first '.' character. @@ -362,7 +362,7 @@ QString FilePath::baseName() const } /*! - \returns the complete base name of the file without the path. + Returns the complete base name of the file without the path. The complete base name consists of all characters in the file up to (but not including) the last '.' character. In case of ".ui.qml" @@ -377,7 +377,7 @@ QString FilePath::completeBaseName() const } /*! - \returns the suffix (extension) of the file. + Returns the suffix (extension) of the file. The suffix consists of all characters in the file after (but not including) the last '.'. In case of ".ui.qml" it will @@ -400,7 +400,7 @@ QString FilePath::suffix() const } /*! - \returns the complete suffix (extension) of the file. + Returns the complete suffix (extension) of the file. The complete suffix consists of all characters in the file after (but not including) the first '.'. @@ -448,7 +448,7 @@ void FilePath::setParts(const QStringView scheme, const QStringView host, QStrin } /*! - \returns a bool indicating whether a file or directory with this FilePath exists. + Returns a bool indicating whether a file or directory with this FilePath exists. */ bool FilePath::exists() const { @@ -456,7 +456,7 @@ bool FilePath::exists() const } /*! - \returns a bool indicating whether this is a writable directory. + Returns a bool indicating whether this is a writable directory. */ bool FilePath::isWritableDir() const { @@ -464,13 +464,20 @@ bool FilePath::isWritableDir() const } /*! - \returns a bool indicating whether this is a writable file. + Returns a bool indicating whether this is a writable file. */ bool FilePath::isWritableFile() const { return fileAccess()->isWritableFile(*this); } +/*! + \brief Re-uses or creates a directory in this location. + + Returns true if the directory is writable afterwards. + + \sa createDir() +*/ bool FilePath::ensureWritableDir() const { return fileAccess()->ensureWritableDirectory(*this); @@ -487,7 +494,7 @@ bool FilePath::isExecutableFile() const } /*! - \returns a bool indicating on whether a process with this FilePath's + Returns a bool indicating on whether a process with this FilePath's .nativePath() is likely to start. This is equivalent to \c isExecutableFile() in general. @@ -557,6 +564,14 @@ bool FilePath::isSymLink() const return fileAccess()->isSymLink(*this); } +/*! + \brief Creates a directory in this location. + + Returns true if the directory could be created, false if not, + even if it existed before. + + \sa ensureWriteableDir() +*/ bool FilePath::createDir() const { return fileAccess()->createDirectory(*this); @@ -726,7 +741,7 @@ bool FilePath::isSameExecutable(const FilePath &other) const } /*! - \returns an empty FilePath if this is not a symbolic linl + Returns an empty FilePath if this is not a symbolic link. */ FilePath FilePath::symLinkTarget() const { @@ -930,7 +945,7 @@ QString doCleanPath(const QString &input_) Returns an empty FilePath if the current directory is already a root level directory. - \returns \a FilePath with the last segment removed. + Returns \a FilePath with the last segment removed. */ FilePath FilePath::parentDir() const { @@ -1225,7 +1240,7 @@ QVariant FilePath::toVariant() const } /*! - \returns whether FilePath is a child of \a s + Returns whether FilePath is a child of \a s. */ bool FilePath::isChildOf(const FilePath &s) const { @@ -1247,7 +1262,7 @@ bool FilePath::isChildOf(const FilePath &s) const } /*! - \returns whether \c path() starts with \a s. + Returns whether \c path() starts with \a s. */ bool FilePath::startsWith(const QString &s) const { @@ -1255,7 +1270,7 @@ bool FilePath::startsWith(const QString &s) const } /*! - \returns whether \c path() ends with \a s. + Returns whether \c path() ends with \a s. */ bool FilePath::endsWith(const QString &s) const { @@ -1263,7 +1278,7 @@ bool FilePath::endsWith(const QString &s) const } /*! - \returns whether \c path() contains \a s. + Returns whether \c path() contains \a s. */ bool FilePath::contains(const QString &s) const { @@ -1274,7 +1289,8 @@ bool FilePath::contains(const QString &s) const \brief Checks whether the FilePath starts with a drive letter. Defaults to \c false if it is a non-Windows host or represents a path on device - \returns whether FilePath starts with a drive letter + + Returns whether FilePath starts with a drive letter */ bool FilePath::startsWithDriveLetter() const { @@ -1288,7 +1304,8 @@ bool FilePath::startsWithDriveLetter() const Returns a empty FilePath if this is not a child of \p parent. That is, this never returns a path starting with "../" \param parent The Parent to calculate the relative path to. - \returns The relative path of this to \p parent if this is a child of \p parent. + + Returns The relative path of this to \p parent if this is a child of \p parent. */ FilePath FilePath::relativeChildPath(const FilePath &parent) const { @@ -1303,7 +1320,7 @@ FilePath FilePath::relativeChildPath(const FilePath &parent) const } /*! - \returns the relativePath of FilePath from a given \a anchor. + Returns the relativePath of FilePath from a given \a anchor. Both, FilePath and anchor may be files or directories. Example usage: @@ -1348,8 +1365,10 @@ FilePath FilePath::relativePathFrom(const FilePath &anchor) const } /*! - \returns the relativePath of \a absolutePath to given \a absoluteAnchorPath. - Both paths must be an absolute path to a directory. Example usage: + Returns the relativePath of \a absolutePath to given \a absoluteAnchorPath. + Both paths must be an absolute path to a directory. + + Example usage: \code qDebug() << FilePath::calcRelativePath("/foo/b/ar", "/foo/c"); @@ -1397,8 +1416,7 @@ QString FilePath::calcRelativePath(const QString &absolutePath, const QString &a } /*! - \brief Returns a path corresponding to the current object on the - + Returns a path corresponding to the current object on the same device as \a deviceTemplate. The FilePath needs to be local. Example usage: @@ -1410,8 +1428,6 @@ QString FilePath::calcRelativePath(const QString &absolutePath, const QString &a \endcode \param deviceTemplate A path from which the host and scheme is taken. - - \returns A path on the same device as \a deviceTemplate. */ FilePath FilePath::onDevice(const FilePath &deviceTemplate) const { @@ -1583,7 +1599,7 @@ bool FilePath::removeFile() const \note The \a error parameter is optional. - \returns A bool indicating whether the operation succeeded. + Returns a Bool indicating whether the operation succeeded. */ bool FilePath::removeRecursively(QString *error) const { @@ -1641,7 +1657,7 @@ qint64 FilePath::bytesAvailable() const \brief Checks if this is newer than \p timeStamp \param timeStamp The time stamp to compare with - \returns true if this is newer than \p timeStamp. + Returns true if this is newer than \p timeStamp. If this is a directory, the function will recursively check all files and return true if one of them is newer than \a timeStamp. If this is a single file, true will be returned if the file is newer than \a timeStamp. @@ -1666,7 +1682,6 @@ bool FilePath::isNewerThan(const QDateTime &timeStamp) const /*! \brief Returns the caseSensitivity of the path. - \returns The caseSensitivity of the path. This is currently only based on the Host OS. For device paths, \c Qt::CaseSensitive is always returned. */ @@ -1685,7 +1700,7 @@ Qt::CaseSensitivity FilePath::caseSensitivity() const /*! \brief Returns the separator of path components for this path. - \returns The path separator of the path. + Returns the path separator of the path. */ QChar FilePath::pathComponentSeparator() const { @@ -1695,7 +1710,7 @@ QChar FilePath::pathComponentSeparator() const /*! \brief Returns the path list separator for the device this path belongs to. - \returns The path list separator of the device for this path + Returns the path list separator of the device for this path. */ QChar FilePath::pathListSeparator() const { @@ -1711,7 +1726,7 @@ QChar FilePath::pathListSeparator() const \note Maximum recursion depth == 16. - \returns the symlink target file path. + Returns the symlink target file path. */ FilePath FilePath::resolveSymlinks() const { @@ -1732,7 +1747,7 @@ FilePath FilePath::resolveSymlinks() const * Unlike QFileInfo::canonicalFilePath(), this function will not return an empty * string if path doesn't exist. * -* \returns the canonical path. +* Returns the canonical path. */ FilePath FilePath::canonicalPath() const { @@ -1789,7 +1804,7 @@ void FilePath::clear() /*! \brief Checks if the path() is empty. - \returns true if the path() is empty. + Returns true if the path() is empty. The Host and Scheme of the part are ignored. */ bool FilePath::isEmpty() const @@ -1803,7 +1818,7 @@ bool FilePath::isEmpty() const Like QDir::toNativeSeparators(), but use prefix '~' instead of $HOME on unix systems when an absolute path is given. - \returns the possibly shortened path with native separators. + Returns the possibly shortened path with native separators. */ QString FilePath::shortNativePath() const { @@ -1820,7 +1835,7 @@ QString FilePath::shortNativePath() const /*! \brief Checks whether the path is relative - \returns true if the path is relative. + Returns true if the path is relative. */ bool FilePath::isRelativePath() const { @@ -1838,7 +1853,8 @@ bool FilePath::isRelativePath() const \brief Appends the tail to this, if the tail is a relative path. \param tail The tail to append. - \returns Returns tail if tail is absolute, otherwise this + tail. + + Returns tail if tail is absolute, otherwise this + tail. */ FilePath FilePath::resolvePath(const FilePath &tail) const { @@ -1853,7 +1869,8 @@ FilePath FilePath::resolvePath(const FilePath &tail) const \brief Appends the tail to this, if the tail is a relative path. \param tail The tail to append. - \returns Returns tail if tail is absolute, otherwise this + tail. + + Returns tail if tail is absolute, otherwise this + tail. */ FilePath FilePath::resolvePath(const QString &tail) const { -- cgit v1.2.3 From 6e276b0a00843e48e1695bf27f3530a32dafe441 Mon Sep 17 00:00:00 2001 From: hjk Date: Fri, 24 Mar 2023 10:59:30 +0100 Subject: Utils: Add a way to provide fall-back entries in an environment These are used if a (presumably fully-expanded) environment does not have a value for a certain key. Currently this works only for fully-known environments, but this can at least be delayed until the environment is needed, not when it is set up. Change-Id: I9baaa2d23002ddd574101741a91d5f872e6b0314 Reviewed-by: Marcus Tillmanns Reviewed-by: Qt CI Bot --- src/libs/utils/environment.cpp | 20 ++++++++++++++++++++ src/libs/utils/environment.h | 10 ++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/environment.cpp b/src/libs/utils/environment.cpp index e64db0e2a3..cacd60fcd6 100644 --- a/src/libs/utils/environment.cpp +++ b/src/libs/utils/environment.cpp @@ -494,6 +494,12 @@ void Environment::set(const QString &key, const QString &value, bool enabled) std::tuple{key, value, enabled}}); } +void Environment::setFallback(const QString &key, const QString &value) +{ + addItem(Item{std::in_place_index_t(), + std::tuple{key, value}}); +} + void Environment::unset(const QString &key) { addItem(Item{std::in_place_index_t(), key}); @@ -536,19 +542,33 @@ const NameValueDictionary &Environment::resolved() const if (m_dict.size() != 0) return m_dict; + m_fullDict = false; for (const Item &item : m_changeItems) { switch (item.index()) { case SetSystemEnvironment: m_dict = Environment::systemEnvironment().toDictionary(); + m_fullDict = true; break; case SetFixedDictionary: m_dict = std::get(item); + m_fullDict = true; break; case SetValue: { auto [key, value, enabled] = std::get(item); m_dict.set(key, value, enabled); break; } + case SetFallbackValue: { + auto [key, value] = std::get(item); + if (m_fullDict) { + if (m_dict.value(key).isEmpty()) + m_dict.set(key, value, true); + } else { + QTC_ASSERT(false, qDebug() << "operating on partial dictionary"); + m_dict.set(key, value, true); + } + break; + } case UnsetValue: m_dict.unset(std::get(item)); break; diff --git a/src/libs/utils/environment.h b/src/libs/utils/environment.h index 8424eb1d24..7afa48ba0b 100644 --- a/src/libs/utils/environment.h +++ b/src/libs/utils/environment.h @@ -33,6 +33,7 @@ public: bool hasKey(const QString &key) const; void set(const QString &key, const QString &value, bool enabled = true); + void setFallback(const QString &key, const QString &value); void unset(const QString &key); void modify(const NameValueItems &items); @@ -55,6 +56,7 @@ public: void appendToPath(const FilePaths &values); void setupEnglishOutput(); + void setupSudoAskPass(const FilePath &askPass); FilePath searchInPath(const QString &executable, const FilePaths &additionalDirs = FilePaths(), @@ -95,22 +97,25 @@ public: SetSystemEnvironment, SetFixedDictionary, SetValue, + SetFallbackValue, UnsetValue, PrependOrSet, AppendOrSet, Modify, - SetupEnglishOutput, + SetupEnglishOutput }; using Item = std::variant< std::monostate, // SetSystemEnvironment dummy NameValueDictionary, // SetFixedDictionary std::tuple, // SetValue (key, value, enabled) + std::tuple, // SetFallbackValue (key, value) QString, // UnsetValue (key) std::tuple, // PrependOrSet (key, value, separator) std::tuple, // AppendOrSet (key, value, separator) NameValueItems, // Modify - std::monostate // SetupEnglishOutput + std::monostate, // SetupEnglishOutput + FilePath // SetupSudoAskPass (file path of qtc-askpass or ssh-askpass) >; void addItem(const Item &item); @@ -122,6 +127,7 @@ public: private: mutable QList m_changeItems; mutable NameValueDictionary m_dict; // Latest resolved. + mutable bool m_fullDict = false; }; using EnviromentChange = Environment; -- cgit v1.2.3 From b5af4501df0d72b9bb6d977a4abe9e87355b6468 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Tue, 21 Mar 2023 19:20:55 +0100 Subject: Fix include style Change-Id: I64cb77f8d39dac35821fe96d735bc5dda35738e7 Reviewed-by: Christian Stenger --- src/libs/utils/qrcparser.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/qrcparser.h b/src/libs/utils/qrcparser.h index 42cd9b6666..14352e26b8 100644 --- a/src/libs/utils/qrcparser.h +++ b/src/libs/utils/qrcparser.h @@ -4,7 +4,8 @@ #pragma once #include "utils_global.h" -#include "utils/filepath.h" + +#include "filepath.h" #include #include -- cgit v1.2.3 From 884a1d6f944b1334fb1350f426944993eaaf72af Mon Sep 17 00:00:00 2001 From: hjk Date: Fri, 24 Mar 2023 12:07:30 +0100 Subject: Replace a few \returns by Returns Change-Id: I09c633e610421f5cc8257b15de60ffa98d890ee0 Reviewed-by: Leena Miettinen --- src/libs/3rdparty/cplusplus/Symbol.h | 4 ++-- src/libs/qmljs/qmljsutils.cpp | 5 +++-- src/libs/utils/multitextcursor.h | 18 +++++++++--------- 3 files changed, 14 insertions(+), 13 deletions(-) (limited to 'src/libs') diff --git a/src/libs/3rdparty/cplusplus/Symbol.h b/src/libs/3rdparty/cplusplus/Symbol.h index 497226dcc1..dc07ced907 100644 --- a/src/libs/3rdparty/cplusplus/Symbol.h +++ b/src/libs/3rdparty/cplusplus/Symbol.h @@ -66,10 +66,10 @@ public: /// Returns this Symbol's source location. int sourceLocation() const { return _sourceLocation; } - /// \returns this Symbol's line number. The line number is 1-based. + /// Returns this Symbol's line number. The line number is 1-based. int line() const { return _line; } - /// \returns this Symbol's column number. The column number is 1-based. + /// Returns this Symbol's column number. The column number is 1-based. int column() const { return _column; } /// Returns this Symbol's file name. diff --git a/src/libs/qmljs/qmljsutils.cpp b/src/libs/qmljs/qmljsutils.cpp index 1609e49c68..e33a6b3e80 100644 --- a/src/libs/qmljs/qmljsutils.cpp +++ b/src/libs/qmljs/qmljsutils.cpp @@ -115,8 +115,9 @@ SourceLocation QmlJS::fullLocationForQualifiedId(AST::UiQualifiedId *qualifiedId } /*! - \returns the value of the 'id:' binding in \a object - \param idBinding optional out parameter to get the UiScriptBinding for the id binding + Returns the value of the 'id:' binding in \a object. + + \a idBinding is optional out parameter to get the UiScriptBinding for the id binding. */ QString QmlJS::idOfObject(Node *object, UiScriptBinding **idBinding) { diff --git a/src/libs/utils/multitextcursor.h b/src/libs/utils/multitextcursor.h index edb4f5f1fd..b2565f931f 100644 --- a/src/libs/utils/multitextcursor.h +++ b/src/libs/utils/multitextcursor.h @@ -28,15 +28,15 @@ public: ~MultiTextCursor(); - /// replace all cursors with \param cursors and the last one will be the new main cursors + /// Replaces all cursors with \param cursors and the last one will be the new main cursors. void setCursors(const QList &cursors); const QList cursors() const; - /// \returns whether this multi cursor contains any cursor + /// Returns whether this multi cursor contains any cursor. bool isNull() const; - /// \returns whether this multi cursor contains more than one cursor + /// Returns whether this multi cursor contains more than one cursor. bool hasMultipleCursors() const; - /// \returns the number of cursors handled by this cursor + /// Returns the number of cursors handled by this cursor. int cursorCount() const; /// the \param cursor that is appended by added by \brief addCursor @@ -46,9 +46,9 @@ public: /// convenience function that removes the old main cursor and appends /// \param cursor as the new main cursor void replaceMainCursor(const QTextCursor &cursor); - /// \returns the main cursor + /// Returns the main cursor. QTextCursor mainCursor() const; - /// \returns the main cursor and removes it from this multi cursor + /// Returns the main cursor and removes it from this multi cursor. QTextCursor takeMainCursor(); void beginEditBlock(); @@ -62,10 +62,10 @@ public: /// with the move \param mode void movePosition(QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode, int n = 1); - /// \returns whether any cursor has a selection + /// Returns whether any cursor has a selection. bool hasSelection() const; - /// \returns the selected text of all cursors that have a selection separated by - /// a newline character + /// Returns the selected text of all cursors that have a selection separated by + /// a newline character. QString selectedText() const; /// removes the selected text of all cursors that have a selection from the document void removeSelectedText(); -- cgit v1.2.3 From 9417f8b99eb7e10f0cf40deb8eceee1106c4d647 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Fri, 24 Mar 2023 12:53:21 +0100 Subject: Terminal: Add search * Adds new search widget to terminal * Adds new theme color "TerminalFindMatch" * Fixes ESC key handling in terminal Fixes: QTCREATORBUG-28946 Change-Id: I7b6057d13902a94a6bcd41dde6cc8ba8418cd585 Reviewed-by: Cristian Adam --- src/libs/utils/theme/theme.h | 1 + 1 file changed, 1 insertion(+) (limited to 'src/libs') diff --git a/src/libs/utils/theme/theme.h b/src/libs/utils/theme/theme.h index 2652c73b13..762f4490f9 100644 --- a/src/libs/utils/theme/theme.h +++ b/src/libs/utils/theme/theme.h @@ -423,6 +423,7 @@ public: TerminalForeground, TerminalBackground, TerminalSelection, + TerminalFindMatch, TerminalAnsi0, TerminalAnsi1, -- cgit v1.2.3 From 83759e80834ea33f8f67c68123a4fe9e13df193f Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Fri, 24 Mar 2023 18:09:04 +0100 Subject: Utils: Improve QtColorButton By making it more theme aware, using a contrast color to draw the outline; using the whole button area for the color (had to implement a custom focus rect for that); Tidying up the checkerboard code a bit. Change-Id: I9855c07668f920caf371a03fef7be2795feb2a08 Reviewed-by: Marcus Tillmanns Reviewed-by: Eike Ziller Reviewed-by: Qt CI Bot --- src/libs/utils/qtcolorbutton.cpp | 74 ++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 44 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/qtcolorbutton.cpp b/src/libs/utils/qtcolorbutton.cpp index ca0aa631ba..e6735baae2 100644 --- a/src/libs/utils/qtcolorbutton.cpp +++ b/src/libs/utils/qtcolorbutton.cpp @@ -3,12 +3,13 @@ #include "qtcolorbutton.h" -#include #include #include +#include #include +#include #include -#include +#include namespace Utils { @@ -157,51 +158,36 @@ bool QtColorButton::isDialogOpen() const void QtColorButton::paintEvent(QPaintEvent *event) { - QToolButton::paintEvent(event); - if (!isEnabled()) - return; - - const int pixSize = 10; - QBrush br(d_ptr->shownColor()); - if (d_ptr->m_backgroundCheckered) { - QPixmap pm(2 * pixSize, 2 * pixSize); - QPainter pmp(&pm); - pmp.fillRect(0, 0, pixSize, pixSize, Qt::white); - pmp.fillRect(pixSize, pixSize, pixSize, pixSize, Qt::white); - pmp.fillRect(0, pixSize, pixSize, pixSize, Qt::black); - pmp.fillRect(pixSize, 0, pixSize, pixSize, Qt::black); - pmp.fillRect(0, 0, 2 * pixSize, 2 * pixSize, d_ptr->shownColor()); - br = QBrush(pm); - } + Q_UNUSED(event) QPainter p(this); - const int corr = 5; - QRect r = rect().adjusted(corr, corr, -corr, -corr); - p.setBrushOrigin((r.width() % pixSize + pixSize) / 2 + corr, (r.height() % pixSize + pixSize) / 2 + corr); - p.fillRect(r, br); + if (isEnabled()) { + QBrush br(d_ptr->shownColor()); + if (d_ptr->m_backgroundCheckered) { + const int pixSize = 10; + QPixmap pm(2 * pixSize, 2 * pixSize); + pm.fill(Qt::white); + QPainter pmp(&pm); + pmp.fillRect(0, pixSize, pixSize, pixSize, Qt::black); + pmp.fillRect(pixSize, 0, pixSize, pixSize, Qt::black); + pmp.fillRect(pm.rect(), d_ptr->shownColor()); + br = QBrush(pm); + p.setBrushOrigin((width() - pixSize) / 2, (height() - pixSize) / 2); + } + p.fillRect(rect(), br); + } - //const int adjX = qRound(r.width() / 4.0); - //const int adjY = qRound(r.height() / 4.0); - //p.fillRect(r.adjusted(adjX, adjY, -adjX, -adjY), - // QColor(d_ptr->shownColor().rgb())); - /* - p.fillRect(r.adjusted(0, r.height() * 3 / 4, 0, 0), - QColor(d_ptr->shownColor().rgb())); - p.fillRect(r.adjusted(0, 0, 0, -r.height() * 3 / 4), - QColor(d_ptr->shownColor().rgb())); - */ - /* - const QColor frameColor0(0, 0, 0, qRound(0.2 * (0xFF - d_ptr->shownColor().alpha()))); - p.setPen(frameColor0); - p.drawRect(r.adjusted(adjX, adjY, -adjX - 1, -adjY - 1)); - */ - - const QColor frameColor1(0, 0, 0, 26); - p.setPen(frameColor1); - p.drawRect(r.adjusted(1, 1, -2, -2)); - const QColor frameColor2(0, 0, 0, 51); - p.setPen(frameColor2); - p.drawRect(r.adjusted(0, 0, -1, -1)); + if (hasFocus()) { + QPen pen; + pen.setBrush(Qt::white); + pen.setStyle(Qt::DotLine); + p.setPen(pen); + p.setCompositionMode(QPainter::CompositionMode_Difference); + } else { + p.setPen(palette().text().color()); + p.setOpacity(0.25); + } + p.drawRect(rect().adjusted(0, 0, -1, -1)); } void QtColorButton::mousePressEvent(QMouseEvent *event) -- cgit v1.2.3 From 3da30a8f2c93160cd1ba6b4291c7414249b9717e Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Mon, 27 Mar 2023 22:47:04 +0200 Subject: MinGW build: Fix a few compilation warnings Change-Id: Ib4f85d2ef15a5c06c6d2b175823c196b8588f5d2 Reviewed-by: Cristian Adam --- src/libs/utils/process_ctrlc_stub.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/process_ctrlc_stub.cpp b/src/libs/utils/process_ctrlc_stub.cpp index bbfdf7ff14..b924e74e49 100644 --- a/src/libs/utils/process_ctrlc_stub.cpp +++ b/src/libs/utils/process_ctrlc_stub.cpp @@ -44,7 +44,7 @@ public: fwprintf(stderr, L"qtcreator_ctrlc_stub: CreateJobObject failed: 0x%x.\n", GetLastError()); return; } - JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {0}; + JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {}; jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; if (!SetInformationJobObject(m_job, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli))) { fwprintf(stderr, L"qtcreator_ctrlc_stub: SetInformationJobObject failed: 0x%x.\n", GetLastError()); @@ -85,7 +85,7 @@ int main(int argc, char **) uiShutDownWindowMessage = RegisterWindowMessage(L"qtcctrlcstub_shutdown"); uiInterruptMessage = RegisterWindowMessage(L"qtcctrlcstub_interrupt"); - WNDCLASSEX wcex = {0}; + WNDCLASSEX wcex = {}; wcex.cbSize = sizeof(wcex); wcex.lpfnWndProc = WndProc; wcex.hInstance = GetModuleHandle(nullptr); @@ -188,14 +188,14 @@ DWORD WINAPI processWatcherThread(LPVOID lpParameter) bool startProcess(wchar_t *pCommandLine, bool lowerPriority, const JobKillOnClose& job) { - SECURITY_ATTRIBUTES sa = {0}; + SECURITY_ATTRIBUTES sa = {}; sa.nLength = sizeof(sa); sa.bInheritHandle = TRUE; - STARTUPINFO si = {0}; + STARTUPINFO si = {}; si.cb = sizeof(si); - PROCESS_INFORMATION pi; + PROCESS_INFORMATION pi = {}; DWORD dwCreationFlags = lowerPriority ? BELOW_NORMAL_PRIORITY_CLASS : 0; BOOL bSuccess = CreateProcess(NULL, pCommandLine, &sa, &sa, TRUE, dwCreationFlags, NULL, NULL, &si, &pi); if (!bSuccess) { -- cgit v1.2.3 From 7e4c52959c10aa0092cb0393b515691a7c4d174d Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Tue, 28 Mar 2023 07:49:05 +0200 Subject: Utils: Don't try to start clangd if path is empty Change-Id: I30d3279be81b66c16ee8081b09828aaa6bcd53e0 Reviewed-by: David Schulz --- src/libs/utils/clangutils.cpp | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/clangutils.cpp b/src/libs/utils/clangutils.cpp index 3f2fb69909..ee4868f94b 100644 --- a/src/libs/utils/clangutils.cpp +++ b/src/libs/utils/clangutils.cpp @@ -45,6 +45,11 @@ QVersionNumber clangdVersion(const FilePath &clangd) bool checkClangdVersion(const FilePath &clangd, QString *error) { + if (clangd.isEmpty()) { + *error = Tr::tr("No clangd executable specified."); + return false; + } + const QVersionNumber version = clangdVersion(clangd); if (version >= minimumClangdVersion()) return true; -- cgit v1.2.3 From de733a4294b8b411d3f11743babadec7ca43800b Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Tue, 28 Mar 2023 18:09:00 +0200 Subject: Utils: Add "pinned small" icon To be used it in pinnable floating dialogs. Change-Id: I379a0422af5b0f5dd4045fcaa8086c642832d641 Reviewed-by: Eike Ziller --- src/libs/utils/images/pinned_small.png | Bin 0 -> 145 bytes src/libs/utils/images/pinned_small@2x.png | Bin 0 -> 233 bytes src/libs/utils/utils.qrc | 2 ++ 3 files changed, 2 insertions(+) create mode 100644 src/libs/utils/images/pinned_small.png create mode 100644 src/libs/utils/images/pinned_small@2x.png (limited to 'src/libs') diff --git a/src/libs/utils/images/pinned_small.png b/src/libs/utils/images/pinned_small.png new file mode 100644 index 0000000000..10f96f3095 Binary files /dev/null and b/src/libs/utils/images/pinned_small.png differ diff --git a/src/libs/utils/images/pinned_small@2x.png b/src/libs/utils/images/pinned_small@2x.png new file mode 100644 index 0000000000..672af736fa Binary files /dev/null and b/src/libs/utils/images/pinned_small@2x.png differ diff --git a/src/libs/utils/utils.qrc b/src/libs/utils/utils.qrc index 20f6d7c788..c0f2d2559a 100644 --- a/src/libs/utils/utils.qrc +++ b/src/libs/utils/utils.qrc @@ -36,6 +36,8 @@ images/unlocked@2x.png images/pinned.png images/pinned@2x.png + images/pinned_small.png + images/pinned_small@2x.png images/broken.png images/broken@2x.png images/notloaded.png -- cgit v1.2.3 From 8a1e34f084f24f45d61e0011a2b6486aadc34218 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Mon, 27 Mar 2023 07:28:48 +0200 Subject: TextEditor: introduce text suggestion interface And also a copilot suggestion implementing that interface that allows reverting all changes done to a suggestion as well as applying it. Change-Id: I236c1fc5e5844d19ac606672af54e273e9c42e1c Reviewed-by: Marcus Tillmanns --- src/libs/languageserverprotocol/lsptypes.cpp | 10 ++++++++++ src/libs/languageserverprotocol/lsptypes.h | 2 ++ src/libs/languageserverprotocol/lsputils.h | 7 +++++++ 3 files changed, 19 insertions(+) (limited to 'src/libs') diff --git a/src/libs/languageserverprotocol/lsptypes.cpp b/src/libs/languageserverprotocol/lsptypes.cpp index 4bf3f3746e..bd7798dcef 100644 --- a/src/libs/languageserverprotocol/lsptypes.cpp +++ b/src/libs/languageserverprotocol/lsptypes.cpp @@ -311,6 +311,16 @@ bool Range::overlaps(const Range &range) const return !isLeftOf(range) && !range.isLeftOf(*this); } +QTextCursor Range::toSelection(QTextDocument *doc) const +{ + QTC_ASSERT(doc, return {}); + if (!isValid()) + return {}; + QTextCursor cursor = start().toTextCursor(doc); + cursor.setPosition(end().toPositionInDocument(doc), QTextCursor::KeepAnchor); + return cursor; +} + QString expressionForGlob(QString globPattern) { const QString anySubDir("qtc_anysubdir_id"); diff --git a/src/libs/languageserverprotocol/lsptypes.h b/src/libs/languageserverprotocol/lsptypes.h index 4adc35aa10..3a9adb1b0d 100644 --- a/src/libs/languageserverprotocol/lsptypes.h +++ b/src/libs/languageserverprotocol/lsptypes.h @@ -117,6 +117,8 @@ public: bool isLeftOf(const Range &other) const { return isEmpty() || other.isEmpty() ? end() < other.start() : end() <= other.start(); } + QTextCursor toSelection(QTextDocument *doc) const; + bool isValid() const override { return JsonObject::contains(startKey) && JsonObject::contains(endKey); } }; diff --git a/src/libs/languageserverprotocol/lsputils.h b/src/libs/languageserverprotocol/lsputils.h index 5515b51576..0c815bafd8 100644 --- a/src/libs/languageserverprotocol/lsputils.h +++ b/src/libs/languageserverprotocol/lsputils.h @@ -87,6 +87,13 @@ public: return QJsonValue(); } + QList toListOrEmpty() const + { + if (std::holds_alternative>(*this)) + return std::get>(*this); + return {}; + } + QList toList() const { QTC_ASSERT(std::holds_alternative>(*this), return {}); -- cgit v1.2.3 From cb3fd6eb0dd564b7ff9d58473fe00d83bd24011c Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Wed, 29 Mar 2023 15:58:56 +0200 Subject: Utils: Introduce Utils::Icons::PINNED_SMALL Replaces two duplicated pin.xpm in the pinnable debugger tooltip and the qmleditorwidgets with the new icon. Change-Id: I57b7adc5c0b92ffdf01da12dd832482d739cb86e Reviewed-by: Eike Ziller Reviewed-by: Qt CI Bot --- src/libs/qmleditorwidgets/contextpanewidget.cpp | 29 +++---------------------- src/libs/utils/utilsicons.cpp | 2 ++ src/libs/utils/utilsicons.h | 1 + 3 files changed, 6 insertions(+), 26 deletions(-) (limited to 'src/libs') diff --git a/src/libs/qmleditorwidgets/contextpanewidget.cpp b/src/libs/qmleditorwidgets/contextpanewidget.cpp index 688c65ad7b..8d675bf95b 100644 --- a/src/libs/qmleditorwidgets/contextpanewidget.cpp +++ b/src/libs/qmleditorwidgets/contextpanewidget.cpp @@ -6,6 +6,7 @@ #include "qmleditorwidgetstr.h" #include +#include #include #include @@ -27,26 +28,6 @@ using namespace Utils; namespace QmlEditorWidgets { -/* XPM */ -static const char * pin_xpm[] = { -"12 9 7 1", -" c None", -". c #000000", -"+ c #515151", -"@ c #A8A8A8", -"# c #A9A9A9", -"$ c #999999", -"% c #696969", -" . ", -" ......+", -" .@@@@@.", -" .#####.", -"+.....$$$$$.", -" .%%%%%.", -" .......", -" ......+", -" . "}; - DragWidget::DragWidget(QWidget *parent) : QFrame(parent) { setFrameStyle(QFrame::NoFrame); @@ -143,7 +124,7 @@ ContextPaneWidget::ContextPaneWidget(QWidget *parent) : DragWidget(parent), m_cu m_toolButton->setIcon(style()->standardIcon(QStyle::SP_DockWidgetCloseButton)); m_toolButton->setToolButtonStyle(Qt::ToolButtonIconOnly); - m_toolButton->setFixedSize(16, 16); + m_toolButton->setFixedSize(20, 20); m_toolButton->setToolTip(Tr::tr("Hides this toolbar.")); connect(m_toolButton, &QToolButton::clicked, this, &ContextPaneWidget::onTogglePane); @@ -464,9 +445,7 @@ void ContextPaneWidget::setPinButton() m_toolButton->setAutoRaise(true); m_pinned = true; - m_toolButton->setIcon(QPixmap::fromImage(QImage(pin_xpm))); - m_toolButton->setToolButtonStyle(Qt::ToolButtonIconOnly); - m_toolButton->setFixedSize(20, 20); + m_toolButton->setIcon(Utils::Icons::PINNED_SMALL.icon()); m_toolButton->setToolTip(Tr::tr("Unpins the toolbar and moves it to the default position.")); emit pinnedChanged(true); @@ -481,8 +460,6 @@ void ContextPaneWidget::setLineButton() m_pinned = false; m_toolButton->setAutoRaise(true); m_toolButton->setIcon(style()->standardIcon(QStyle::SP_DockWidgetCloseButton)); - m_toolButton->setToolButtonStyle(Qt::ToolButtonIconOnly); - m_toolButton->setFixedSize(20, 20); m_toolButton->setToolTip(Tr::tr("Hides this toolbar. This toolbar can be" " permanently disabled in the options page or in the context menu.")); diff --git a/src/libs/utils/utilsicons.cpp b/src/libs/utils/utilsicons.cpp index 7263b28594..cbc2ddf64f 100644 --- a/src/libs/utils/utilsicons.cpp +++ b/src/libs/utils/utilsicons.cpp @@ -24,6 +24,8 @@ const Icon UNLOCKED({ {":/utils/images/unlocked.png", Theme::PanelTextColorDark}}, Icon::Tint); const Icon PINNED({ {":/utils/images/pinned.png", Theme::PanelTextColorDark}}, Icon::Tint); +const Icon PINNED_SMALL({ + {":/utils/images/pinned_small.png", Theme::PanelTextColorDark}}, Icon::Tint); const Icon NEXT({ {":/utils/images/next.png", Theme::IconsWarningColor}}, Icon::MenuTintedStyle); const Icon NEXT_TOOLBAR({ diff --git a/src/libs/utils/utilsicons.h b/src/libs/utils/utilsicons.h index b8fda0c53f..3e5009c94c 100644 --- a/src/libs/utils/utilsicons.h +++ b/src/libs/utils/utilsicons.h @@ -19,6 +19,7 @@ QTCREATOR_UTILS_EXPORT extern const Icon LOCKED; QTCREATOR_UTILS_EXPORT extern const Icon UNLOCKED_TOOLBAR; QTCREATOR_UTILS_EXPORT extern const Icon UNLOCKED; QTCREATOR_UTILS_EXPORT extern const Icon PINNED; +QTCREATOR_UTILS_EXPORT extern const Icon PINNED_SMALL; QTCREATOR_UTILS_EXPORT extern const Icon NEXT; QTCREATOR_UTILS_EXPORT extern const Icon NEXT_TOOLBAR; QTCREATOR_UTILS_EXPORT extern const Icon PREV; -- cgit v1.2.3 From 3aa0291cd17b0133e94a5efbda60a0a64123bbfe Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Tue, 28 Mar 2023 14:28:59 +0200 Subject: Utils: Handle invalid colors in QtColorButton Paint a sublte placeholder. Change-Id: I4fc0a51744a98621fb8289ce7cddfc2ccd43784f Reviewed-by: Eike Ziller --- src/libs/utils/qtcolorbutton.cpp | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/qtcolorbutton.cpp b/src/libs/utils/qtcolorbutton.cpp index e6735baae2..6a8537bd5f 100644 --- a/src/libs/utils/qtcolorbutton.cpp +++ b/src/libs/utils/qtcolorbutton.cpp @@ -3,6 +3,8 @@ #include "qtcolorbutton.h" +#include "theme/theme.h" + #include #include #include @@ -160,9 +162,25 @@ void QtColorButton::paintEvent(QPaintEvent *event) { Q_UNUSED(event) + constexpr Theme::Color overlayColor = Theme::TextColorNormal; + constexpr qreal overlayOpacity = 0.25; + QPainter p(this); - if (isEnabled()) { - QBrush br(d_ptr->shownColor()); + const QColor color = d_ptr->shownColor(); + if (!color.isValid()) { + constexpr int size = 11; + const qreal horPadding = (width() - size) / 2.0; + const qreal verPadding = (height() - size) / 2.0; + const QPen pen(creatorTheme()->color(overlayColor), 2); + + p.save(); + p.setOpacity(overlayOpacity); + p.setPen(pen); + p.setRenderHint(QPainter::Antialiasing); + p.drawLine(QLineF(horPadding, height() - verPadding, width() - horPadding, verPadding)); + p.restore(); + } else if (isEnabled()) { + QBrush br(color); if (d_ptr->m_backgroundCheckered) { const int pixSize = 10; QPixmap pm(2 * pixSize, 2 * pixSize); @@ -170,7 +188,7 @@ void QtColorButton::paintEvent(QPaintEvent *event) QPainter pmp(&pm); pmp.fillRect(0, pixSize, pixSize, pixSize, Qt::black); pmp.fillRect(pixSize, 0, pixSize, pixSize, Qt::black); - pmp.fillRect(pm.rect(), d_ptr->shownColor()); + pmp.fillRect(pm.rect(), color); br = QBrush(pm); p.setBrushOrigin((width() - pixSize) / 2, (height() - pixSize) / 2); } @@ -184,8 +202,8 @@ void QtColorButton::paintEvent(QPaintEvent *event) p.setPen(pen); p.setCompositionMode(QPainter::CompositionMode_Difference); } else { - p.setPen(palette().text().color()); - p.setOpacity(0.25); + p.setPen(creatorTheme()->color(overlayColor)); + p.setOpacity(overlayOpacity); } p.drawRect(rect().adjusted(0, 0, -1, -1)); } -- cgit v1.2.3 From e0694c52d9f83db3a48d043c78e6573f729dba71 Mon Sep 17 00:00:00 2001 From: hjk Date: Fri, 31 Mar 2023 14:07:57 +0200 Subject: RemoteLinux: Allow parsing used ports from cat output We used to run {filePath("sed"), {"-e 's/.*: [[:xdigit:]]*:\\([[:xdigit:]]\\{4\\}\\).*/\\1/g' /proc/net/tcp*"} on the device side but doesn't pass quoting on double-remote setups. Chicken out by using a simpler for now command. Change-Id: I7794f803d185bd4b6b717d85c01cc250cc66f1eb Reviewed-by: Christian Stenger Reviewed-by: --- src/libs/utils/port.cpp | 32 ++++++++++++++++++++++++++++++++ src/libs/utils/port.h | 1 + 2 files changed, 33 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/port.cpp b/src/libs/utils/port.cpp index 3f9166a1a9..ceab772b62 100644 --- a/src/libs/utils/port.cpp +++ b/src/libs/utils/port.cpp @@ -6,6 +6,8 @@ #include "qtcassert.h" #include "stringutils.h" +#include + #include /*! \class Utils::Port @@ -51,6 +53,36 @@ QList Port::parseFromSedOutput(const QByteArray &output) return ports; } +QList Port::parseFromCatOutput(const QByteArray &output) +{ + // Parse something like + // sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode + // : 00000000:2717 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1001 0 3995881 1 0000000000000000 100 0 0 10 0 + // : 00000000:2716 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1001 0 3594482 1 0000000000000000 100 0 0 10 0 + const QRegularExpression re(".*: [[:xdigit:]]*:([[:xdigit:]]{4}).*"); + + QList ports; + const QStringList lines = QString::fromLocal8Bit(output).split('\n'); + for (const QString &line : lines) { + const QRegularExpressionMatch match = re.match(line); + if (!match.hasMatch()) + continue; + const QString portString = match.captured(1); + if (portString.size() != 4) + continue; + bool ok; + const Port port(portString.toInt(&ok, 16)); + if (ok) { + if (!ports.contains(port)) + ports << port; + } else { + qWarning("%s: Unexpected string '%s' is not a port.", + Q_FUNC_INFO, qPrintable(portString)); + } + } + return ports; +} + QList Port::parseFromNetstatOutput(const QByteArray &output) { QList ports; diff --git a/src/libs/utils/port.h b/src/libs/utils/port.h index 851b41ceaf..3202ac5f18 100644 --- a/src/libs/utils/port.h +++ b/src/libs/utils/port.h @@ -25,6 +25,7 @@ public: QString toString() const { return QString::number(m_port); } static QList parseFromSedOutput(const QByteArray &output); + static QList parseFromCatOutput(const QByteArray &output); static QList parseFromNetstatOutput(const QByteArray &output); friend bool operator<(const Port &p1, const Port &p2) { return p1.number() < p2.number(); } -- cgit v1.2.3 From f1f5a7412a124ec011c5563a1120faf8343d3e8e Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Fri, 24 Mar 2023 09:26:42 +0100 Subject: Utils: Send __qtc marker from stub Docker and ssh devices need the real process id on the remote device. The process stub now send this if it receives it as the first line of output. Change-Id: I5d3af39651958fc88d21c3854a0fa1d7f51547a6 Reviewed-by: Reviewed-by: David Schulz --- src/libs/utils/processinterface.cpp | 6 ++++-- src/libs/utils/terminalinterface.cpp | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/processinterface.cpp b/src/libs/utils/processinterface.cpp index a5e60c97a3..a2dc746905 100644 --- a/src/libs/utils/processinterface.cpp +++ b/src/libs/utils/processinterface.cpp @@ -29,8 +29,10 @@ int ProcessInterface::controlSignalToInt(ControlSignal controlSignal) case ControlSignal::Terminate: return 15; case ControlSignal::Kill: return 9; case ControlSignal::Interrupt: return 2; - case ControlSignal::KickOff: QTC_CHECK(false); return 0; - case ControlSignal::CloseWriteChannel: QTC_CHECK(false); return 0; + case ControlSignal::KickOff: return 19; + case ControlSignal::CloseWriteChannel: + QTC_CHECK(false); + return 0; } return 0; } diff --git a/src/libs/utils/terminalinterface.cpp b/src/libs/utils/terminalinterface.cpp index 58d48cb910..7483172329 100644 --- a/src/libs/utils/terminalinterface.cpp +++ b/src/libs/utils/terminalinterface.cpp @@ -183,6 +183,8 @@ void TerminalInterface::onStubReadyRead() emitFinished(out.mid(5).toInt(), QProcess::NormalExit); } else if (out.startsWith("crash ")) { emitFinished(out.mid(6).toInt(), QProcess::CrashExit); + } else if (out.startsWith("qtc: ")) { + emit readyRead(out.mid(5) + "\n", {}); } else { emitError(QProcess::UnknownError, msgUnexpectedOutput(out)); break; -- cgit v1.2.3 From 9956740905ee16eff22f75b5d6c6a85c266fba0a Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Mon, 3 Apr 2023 23:20:22 +0300 Subject: FilePath: Optimize string compare in setParts Change-Id: Ibc390ee943ed41dfef30fbbd07e2e681d82379ba Reviewed-by: hjk --- src/libs/utils/filepath.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 3af026d9ba..e397ffe9b7 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -438,7 +438,7 @@ void FilePath::setParts(const QStringView scheme, const QStringView host, QStrin { QTC_CHECK(!scheme.contains('/')); - if (path.startsWith(u"/./")) + if (path.length() >= 3 && path[0] == '/' && path[1] == '.' && path[2] == '/') path = path.mid(3); m_data = path.toString() + scheme.toString() + host.toString(); -- cgit v1.2.3 From 305ccfe259d44a5828b6a770ea90d7c9e189120a Mon Sep 17 00:00:00 2001 From: hjk Date: Wed, 29 Mar 2023 13:45:42 +0200 Subject: Utils: Replace FilePath::onDevice() by new FilePath::withMappedPath() Basically a.onDevice(b) == b.withNewMappedPath(a), matching the order of b.withNewPath(a). Whether the (curretly docker-specific) path mapping is useful /there/, and whether some of the calls are needed at all is dubious. I added some FIXME and changed a few cases directly. Change-Id: I7514736ce922f632f1f737bc496f6783389a42b6 Reviewed-by: Marcus Tillmanns Reviewed-by: Qt CI Bot Reviewed-by: --- src/libs/utils/devicefileaccess.cpp | 20 +++++++++---------- src/libs/utils/filepath.cpp | 39 +++++++++++++++++++------------------ src/libs/utils/filepath.h | 2 +- src/libs/utils/terminalhooks.cpp | 2 +- 4 files changed, 31 insertions(+), 32 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/devicefileaccess.cpp b/src/libs/utils/devicefileaccess.cpp index 1e3c771ee7..016310744b 100644 --- a/src/libs/utils/devicefileaccess.cpp +++ b/src/libs/utils/devicefileaccess.cpp @@ -204,11 +204,8 @@ expected_str DeviceFileAccess::copyRecursively(const FilePath &src, #ifdef UTILS_STATIC_LIBRARY return copyRecursively_fallback(src, target); #else - const FilePath tar = FilePath::fromString("tar").onDevice(target); - const FilePath targetTar = tar.searchInPath(); - - const FilePath sTar = FilePath::fromString("tar").onDevice(src); - const FilePath sourceTar = sTar.searchInPath(); + const FilePath targetTar = target.withNewPath("tar").searchInPath(); + const FilePath sourceTar = src.withNewPath("tar").searchInPath(); const bool isSrcOrTargetQrc = src.toFSPathString().startsWith(":/") || target.toFSPathString().startsWith(":/"); @@ -683,12 +680,13 @@ expected_str DesktopDeviceFileAccess::createTempFile(const FilePath &f { QTemporaryFile file(filePath.path()); file.setAutoRemove(false); - if (!file.open()) - return make_unexpected(Tr::tr("Could not create temporary file in \"%1\" (%2)").arg(filePath.toUserOutput()).arg(file.errorString())); - return FilePath::fromString(file.fileName()).onDevice(filePath); + if (!file.open()) { + return make_unexpected(Tr::tr("Could not create temporary file in \"%1\" (%2)") + .arg(filePath.toUserOutput()).arg(file.errorString())); + } + return filePath.withNewPath(file.fileName()); } - QDateTime DesktopDeviceFileAccess::lastModified(const FilePath &filePath) const { return QFileInfo(filePath.path()).lastModified(); @@ -984,7 +982,7 @@ expected_str UnixDeviceFileAccess::createTempFile(const FilePath &file .arg(filePath.toUserOutput(), QString::fromUtf8(result.stdErr))); } - return FilePath::fromString(QString::fromUtf8(result.stdOut.trimmed())).onDevice(filePath); + return filePath.withNewPath(QString::fromUtf8(result.stdOut.trimmed())); } // Manually create a temporary/unique file. @@ -1008,7 +1006,7 @@ expected_str UnixDeviceFileAccess::createTempFile(const FilePath &file for (QChar *it = firstX.base(); it != std::end(tmplate); ++it) { *it = chars[dist(*QRandomGenerator::global())]; } - newPath = FilePath::fromString(tmplate).onDevice(filePath); + newPath = filePath.withNewPath(tmplate); if (--maxTries == 0) { return make_unexpected(Tr::tr("Failed creating temporary file \"%1\" (too many tries)") .arg(filePath.toUserOutput())); diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index e397ffe9b7..1781831712 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -510,14 +510,14 @@ expected_str FilePath::tmpDir() const if (needsDevice()) { const Environment env = deviceEnvironment(); if (env.hasKey("TMPDIR")) - return FilePath::fromUserInput(env.value("TMPDIR")).onDevice(*this); + return withNewPath(env.value("TMPDIR")).cleanPath(); if (env.hasKey("TEMP")) - return FilePath::fromUserInput(env.value("TEMP")).onDevice(*this); + return withNewPath(env.value("TEMP")).cleanPath(); if (env.hasKey("TMP")) - return FilePath::fromUserInput(env.value("TMP")).onDevice(*this); + return withNewPath(env.value("TMP")).cleanPath(); if (osType() != OsTypeWindows) - return FilePath("/tmp").onDevice(*this); + return withNewPath("/tmp"); return make_unexpected(QString("Could not find temporary directory on device %1") .arg(displayName())); } @@ -1416,30 +1416,31 @@ QString FilePath::calcRelativePath(const QString &absolutePath, const QString &a } /*! - Returns a path corresponding to the current object on the - same device as \a deviceTemplate. The FilePath needs to be local. + Returns a path corresponding to \a newPath object on the + same device as the current object. + + This may involve device-specific translations like converting + windows style paths to unix style paths with suitable file + system case or handling of drive letters: C:/dev/src -> /c/dev/src Example usage: \code localDir = FilePath("/tmp/workingdir"); executable = FilePath::fromUrl("docker://123/bin/ls") - realDir = localDir.onDevice(executable) + realDir = executable.withNewMappedPath(localDir) assert(realDir == FilePath::fromUrl("docker://123/tmp/workingdir")) \endcode - - \param deviceTemplate A path from which the host and scheme is taken. */ -FilePath FilePath::onDevice(const FilePath &deviceTemplate) const +FilePath FilePath::withNewMappedPath(const FilePath &newPath) const { - isSameDevice(deviceTemplate); - const bool sameDevice = scheme() == deviceTemplate.scheme() && host() == deviceTemplate.host(); + const bool sameDevice = newPath.scheme() == scheme() && newPath.host() == host(); if (sameDevice) - return *this; + return newPath; // TODO: converting paths between different non local devices is still unsupported - QTC_CHECK(!needsDevice() || !deviceTemplate.needsDevice()); - return fromParts(deviceTemplate.scheme(), - deviceTemplate.host(), - deviceTemplate.fileAccess()->mapToDevicePath(path())); + QTC_CHECK(!newPath.needsDevice() || !needsDevice()); + FilePath res; + res.setParts(scheme(), host(), fileAccess()->mapToDevicePath(newPath.path())); + return res; } /*! @@ -1486,8 +1487,8 @@ FilePath FilePath::searchInPath(const FilePaths &additionalDirs, return *this; FilePaths directories = deviceEnvironment().path(); if (needsDevice()) { - directories = Utils::transform(directories, [this](const FilePath &path) { - return path.onDevice(*this); + directories = Utils::transform(directories, [this](const FilePath &filePath) { + return withNewPath(filePath.path()); }); } if (!additionalDirs.isEmpty()) { diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h index 220c6de5da..213eeb1e7b 100644 --- a/src/libs/utils/filepath.h +++ b/src/libs/utils/filepath.h @@ -163,8 +163,8 @@ public: [[nodiscard]] FilePath searchInDirectories(const FilePaths &dirs, const FilePathPredicate &filter = {}) const; [[nodiscard]] Environment deviceEnvironment() const; - [[nodiscard]] FilePath onDevice(const FilePath &deviceTemplate) const; [[nodiscard]] FilePath withNewPath(const QString &newPath) const; + [[nodiscard]] FilePath withNewMappedPath(const FilePath &newPath) const; using IterateDirCallback = std::variant< diff --git a/src/libs/utils/terminalhooks.cpp b/src/libs/utils/terminalhooks.cpp index 6922f5c430..d8e53010c3 100644 --- a/src/libs/utils/terminalhooks.cpp +++ b/src/libs/utils/terminalhooks.cpp @@ -27,7 +27,7 @@ FilePath defaultShellForDevice(const FilePath &deviceRoot) if (shell.isEmpty()) return shell; - return shell.onDevice(deviceRoot); + return deviceRoot.withNewMappedPath(shell); } class ExternalTerminalProcessImpl final : public TerminalInterface -- cgit v1.2.3 From a374dd8bfe8627c4d713a24ff06850506d081123 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Tue, 4 Apr 2023 09:50:02 +0200 Subject: Utils: Fix PathChooser validation Change-Id: I5b0ad8a1011f51c5db732fe454c409812b406c17 Reviewed-by: hjk --- src/libs/utils/pathchooser.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/pathchooser.cpp b/src/libs/utils/pathchooser.cpp index 0f0737f54c..124fdc4cef 100644 --- a/src/libs/utils/pathchooser.cpp +++ b/src/libs/utils/pathchooser.cpp @@ -613,8 +613,8 @@ bool PathChooser::validatePath(FancyLineEdit *edit, QString *errorMessage) const *errorMessage = Tr::tr("The path \"%1\" is not a directory.").arg(filePath.toUserOutput()); return false; } - if (HostOsInfo::isWindowsHost() && !filePath.startsWithDriveLetter() - && !filePath.startsWith("\\\\") && !filePath.startsWith("//")) { + if (filePath.osType() == OsTypeWindows && !filePath.startsWithDriveLetter() + && !filePath.startsWith("\\\\") && !filePath.startsWith("//")) { if (errorMessage) *errorMessage = Tr::tr("Invalid path \"%1\".").arg(filePath.toUserOutput()); return false; -- cgit v1.2.3 From 4fbc56d453ef2fa73e494f371bd44285e3ac0d50 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Tue, 4 Apr 2023 09:49:37 +0200 Subject: Utils: Change macro expander to use FilePath Change-Id: Ib7787d1b7f72f6b4728893636f6844e4297fcecd Reviewed-by: hjk --- src/libs/utils/macroexpander.cpp | 74 +++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 35 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/macroexpander.cpp b/src/libs/utils/macroexpander.cpp index 4e24247833..0bb91689e7 100644 --- a/src/libs/utils/macroexpander.cpp +++ b/src/libs/utils/macroexpander.cpp @@ -377,41 +377,45 @@ void MacroExpander::registerIntVariable(const QByteArray &variable, void MacroExpander::registerFileVariables(const QByteArray &prefix, const QString &heading, const FileFunction &base, bool visibleInChooser) { - registerVariable(prefix + kFilePathPostfix, - Tr::tr("%1: Full path including file name.").arg(heading), - [base]() -> QString { QString tmp = base().toString(); return tmp.isEmpty() ? QString() : QFileInfo(tmp).filePath(); }, - visibleInChooser); - - registerVariable(prefix + kPathPostfix, - Tr::tr("%1: Full path excluding file name.").arg(heading), - [base]() -> QString { QString tmp = base().toString(); return tmp.isEmpty() ? QString() : QFileInfo(tmp).path(); }, - visibleInChooser); - - registerVariable(prefix + kNativeFilePathPostfix, - Tr::tr("%1: Full path including file name, with native path separator (backslash on Windows).").arg(heading), - [base]() -> QString { - QString tmp = base().toString(); - return tmp.isEmpty() ? QString() : QDir::toNativeSeparators(QFileInfo(tmp).filePath()); - }, - visibleInChooser); - - registerVariable(prefix + kNativePathPostfix, - Tr::tr("%1: Full path excluding file name, with native path separator (backslash on Windows).").arg(heading), - [base]() -> QString { - QString tmp = base().toString(); - return tmp.isEmpty() ? QString() : QDir::toNativeSeparators(QFileInfo(tmp).path()); - }, - visibleInChooser); - - registerVariable(prefix + kFileNamePostfix, - Tr::tr("%1: File name without path.").arg(heading), - [base]() -> QString { QString tmp = base().toString(); return tmp.isEmpty() ? QString() : FilePath::fromString(tmp).fileName(); }, - visibleInChooser); - - registerVariable(prefix + kFileBaseNamePostfix, - Tr::tr("%1: File base name without path and suffix.").arg(heading), - [base]() -> QString { QString tmp = base().toString(); return tmp.isEmpty() ? QString() : QFileInfo(tmp).baseName(); }, - visibleInChooser); + registerVariable( + prefix + kFilePathPostfix, + Tr::tr("%1: Full path including file name.").arg(heading), + [base]() -> QString { return base().toUserOutput(); }, + visibleInChooser); + + registerVariable( + prefix + kPathPostfix, + Tr::tr("%1: Full path excluding file name.").arg(heading), + [base]() -> QString { return base().parentDir().toUserOutput(); }, + visibleInChooser); + + registerVariable( + prefix + kNativeFilePathPostfix, + Tr::tr( + "%1: Full path including file name, with native path separator (backslash on Windows).") + .arg(heading), + [base]() -> QString { return base().nativePath(); }, + visibleInChooser); + + registerVariable( + prefix + kNativePathPostfix, + Tr::tr( + "%1: Full path excluding file name, with native path separator (backslash on Windows).") + .arg(heading), + [base]() -> QString { return base().parentDir().nativePath(); }, + visibleInChooser); + + registerVariable( + prefix + kFileNamePostfix, + Tr::tr("%1: File name without path.").arg(heading), + [base]() -> QString { return base().fileName(); }, + visibleInChooser); + + registerVariable( + prefix + kFileBaseNamePostfix, + Tr::tr("%1: File base name without path and suffix.").arg(heading), + [base]() -> QString { return base().baseName(); }, + visibleInChooser); } void MacroExpander::registerExtraResolver(const MacroExpander::ResolverFunction &value) -- cgit v1.2.3 From 53e81443300f068bf27497100bacbf1de636551f Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Tue, 4 Apr 2023 11:20:40 +0200 Subject: Utils: Remove unnecessary code Change-Id: I3844d71d3f77d49f7e84c4feae910679391fa91a Reviewed-by: Jarek Kobus --- src/libs/utils/macroexpander.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/macroexpander.cpp b/src/libs/utils/macroexpander.cpp index 0bb91689e7..2fe340efc6 100644 --- a/src/libs/utils/macroexpander.cpp +++ b/src/libs/utils/macroexpander.cpp @@ -140,7 +140,7 @@ using namespace Internal; MacroExpander::registerVariable( "MyVariable", Tr::tr("The current value of whatever I want.")); - []() -> QString { + [] { QString value; // do whatever is necessary to retrieve the value [...] @@ -380,13 +380,13 @@ void MacroExpander::registerFileVariables(const QByteArray &prefix, registerVariable( prefix + kFilePathPostfix, Tr::tr("%1: Full path including file name.").arg(heading), - [base]() -> QString { return base().toUserOutput(); }, + [base] { return base().toUserOutput(); }, visibleInChooser); registerVariable( prefix + kPathPostfix, Tr::tr("%1: Full path excluding file name.").arg(heading), - [base]() -> QString { return base().parentDir().toUserOutput(); }, + [base] { return base().parentDir().toUserOutput(); }, visibleInChooser); registerVariable( @@ -394,7 +394,7 @@ void MacroExpander::registerFileVariables(const QByteArray &prefix, Tr::tr( "%1: Full path including file name, with native path separator (backslash on Windows).") .arg(heading), - [base]() -> QString { return base().nativePath(); }, + [base] { return base().nativePath(); }, visibleInChooser); registerVariable( @@ -402,19 +402,19 @@ void MacroExpander::registerFileVariables(const QByteArray &prefix, Tr::tr( "%1: Full path excluding file name, with native path separator (backslash on Windows).") .arg(heading), - [base]() -> QString { return base().parentDir().nativePath(); }, + [base] { return base().parentDir().nativePath(); }, visibleInChooser); registerVariable( prefix + kFileNamePostfix, Tr::tr("%1: File name without path.").arg(heading), - [base]() -> QString { return base().fileName(); }, + [base] { return base().fileName(); }, visibleInChooser); registerVariable( prefix + kFileBaseNamePostfix, Tr::tr("%1: File base name without path and suffix.").arg(heading), - [base]() -> QString { return base().baseName(); }, + [base] { return base().baseName(); }, visibleInChooser); } -- cgit v1.2.3 From e3565d41b88faec35e9229370362f0c57ae3a632 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 4 Apr 2023 16:03:53 +0200 Subject: Utils: Code cosmetics Change-Id: I34b566371fc4d6666439ed14c8ba95417584f0f5 Reviewed-by: Marcus Tillmanns Reviewed-by: --- src/libs/utils/fileutils.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index 4bd0692e13..f31bd822bb 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -803,11 +803,11 @@ FilePath FileUtils::homePath() return FilePath::fromUserInput(QDir::homePath()); } -FilePaths FileUtils::toFilePathList(const QStringList &paths) { - return transform(paths, [](const QString &path) { return FilePath::fromString(path); }); +FilePaths FileUtils::toFilePathList(const QStringList &paths) +{ + return transform(paths, &FilePath::fromString); } - qint64 FileUtils::bytesAvailableFromDFOutput(const QByteArray &dfOutput) { const auto lines = filtered(dfOutput.split('\n'), -- cgit v1.2.3 From 663bbbfb0eead89f133b23e9748a5df1d38cf5af Mon Sep 17 00:00:00 2001 From: hjk Date: Wed, 5 Apr 2023 10:46:13 +0200 Subject: Utils: Also list dot files when using the ls fallback Change-Id: I6763280134e8cb040b6bc627b4f67d595dc2fb5e Reviewed-by: Marcus Tillmanns --- src/libs/utils/devicefileaccess.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/devicefileaccess.cpp b/src/libs/utils/devicefileaccess.cpp index 016310744b..9345b62e88 100644 --- a/src/libs/utils/devicefileaccess.cpp +++ b/src/libs/utils/devicefileaccess.cpp @@ -1192,7 +1192,7 @@ void UnixDeviceFileAccess::findUsingLs(const QString ¤t, const FileFilter &filter, QStringList *found) const { - const RunResult result = runInShell({"ls", {"-1", "-p", "--", current}, OsType::OsTypeLinux}); + const RunResult result = runInShell({"ls", {"-1", "-a", "-p", "--", current}, OsType::OsTypeLinux}); const QStringList entries = QString::fromUtf8(result.stdOut).split('\n', Qt::SkipEmptyParts); for (QString entry : entries) { const QChar last = entry.back(); @@ -1254,8 +1254,8 @@ void UnixDeviceFileAccess::iterateDirectory(const FilePath &filePath, if (m_tryUseFind) { if (iterateWithFind(filePath, filter, callBack)) return; - m_tryUseFind - = false; // remember the failure for the next time and use the 'ls' fallback below. + // Remember the failure for the next time and use the 'ls' fallback below. + m_tryUseFind = false; } // if we do not have find - use ls as fallback -- cgit v1.2.3 From 522de9bfd7afc30ae32affa728b7e929b46ccc60 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Wed, 5 Apr 2023 11:15:02 +0200 Subject: Devices: Unify Port Gathering method All devices that support it use the same mechanism to gather ports so this patch removes the individual implementations in favor of a single one in IDevice.cpp. This patch also removes: * canAutodetectPorts() as it was not used. * Port::parseFrom...Output as they are not used anymore. Change-Id: I8ecedec2d71e60985402387982c64311c5a651e6 Reviewed-by: hjk Reviewed-by: --- src/libs/utils/port.cpp | 50 ------------------------------------------------- src/libs/utils/port.h | 2 -- 2 files changed, 52 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/port.cpp b/src/libs/utils/port.cpp index ceab772b62..cf56047f3a 100644 --- a/src/libs/utils/port.cpp +++ b/src/libs/utils/port.cpp @@ -33,56 +33,6 @@ quint16 Port::number() const QTC_ASSERT(isValid(), return -1); return quint16(m_port); } -QList Port::parseFromSedOutput(const QByteArray &output) -{ - QList ports; - const QList portStrings = output.split('\n'); - for (const QByteArray &portString : portStrings) { - if (portString.size() != 4) - continue; - bool ok; - const Port port(portString.toInt(&ok, 16)); - if (ok) { - if (!ports.contains(port)) - ports << port; - } else { - qWarning("%s: Unexpected string '%s' is not a port.", - Q_FUNC_INFO, portString.data()); - } - } - return ports; -} - -QList Port::parseFromCatOutput(const QByteArray &output) -{ - // Parse something like - // sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode - // : 00000000:2717 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1001 0 3995881 1 0000000000000000 100 0 0 10 0 - // : 00000000:2716 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1001 0 3594482 1 0000000000000000 100 0 0 10 0 - const QRegularExpression re(".*: [[:xdigit:]]*:([[:xdigit:]]{4}).*"); - - QList ports; - const QStringList lines = QString::fromLocal8Bit(output).split('\n'); - for (const QString &line : lines) { - const QRegularExpressionMatch match = re.match(line); - if (!match.hasMatch()) - continue; - const QString portString = match.captured(1); - if (portString.size() != 4) - continue; - bool ok; - const Port port(portString.toInt(&ok, 16)); - if (ok) { - if (!ports.contains(port)) - ports << port; - } else { - qWarning("%s: Unexpected string '%s' is not a port.", - Q_FUNC_INFO, qPrintable(portString)); - } - } - return ports; -} - QList Port::parseFromNetstatOutput(const QByteArray &output) { QList ports; diff --git a/src/libs/utils/port.h b/src/libs/utils/port.h index 3202ac5f18..a3543f72a3 100644 --- a/src/libs/utils/port.h +++ b/src/libs/utils/port.h @@ -24,8 +24,6 @@ public: QString toString() const { return QString::number(m_port); } - static QList parseFromSedOutput(const QByteArray &output); - static QList parseFromCatOutput(const QByteArray &output); static QList parseFromNetstatOutput(const QByteArray &output); friend bool operator<(const Port &p1, const Port &p2) { return p1.number() < p2.number(); } -- cgit v1.2.3 From f65206f9906a0543b733ff6ca0b9a737157ec8f7 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Thu, 6 Apr 2023 09:01:01 +0200 Subject: Utils: Rename Port::parseFrom function It can parse both netstat and cat /proc/net/tcp output Change-Id: Iafe37be7ace6a1eda068340b1f07e24a71724db1 Reviewed-by: hjk --- src/libs/utils/port.cpp | 2 +- src/libs/utils/port.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/port.cpp b/src/libs/utils/port.cpp index cf56047f3a..c41a65335d 100644 --- a/src/libs/utils/port.cpp +++ b/src/libs/utils/port.cpp @@ -33,7 +33,7 @@ quint16 Port::number() const QTC_ASSERT(isValid(), return -1); return quint16(m_port); } -QList Port::parseFromNetstatOutput(const QByteArray &output) +QList Port::parseFromCommandOutput(const QByteArray &output) { QList ports; const QList lines = output.split('\n'); diff --git a/src/libs/utils/port.h b/src/libs/utils/port.h index a3543f72a3..c4b46631de 100644 --- a/src/libs/utils/port.h +++ b/src/libs/utils/port.h @@ -24,7 +24,8 @@ public: QString toString() const { return QString::number(m_port); } - static QList parseFromNetstatOutput(const QByteArray &output); + // Parses the output of "netstat -an" and "cat /proc/net/tcp" + static QList parseFromCommandOutput(const QByteArray &output); friend bool operator<(const Port &p1, const Port &p2) { return p1.number() < p2.number(); } friend bool operator<=(const Port &p1, const Port &p2) { return p1.number() <= p2.number(); } -- cgit v1.2.3 From 45c2e3fe58fd9b5a85450ff18e0a40e701ebf8d6 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Thu, 6 Apr 2023 08:24:22 +0200 Subject: Copilot: Add insert next word action Fixes: QTCREATORBUG-28959 Change-Id: Ied53ad5676133e2eb71988ecfcce90c5ad77e3c3 Reviewed-by: David Schulz --- src/libs/utils/stringutils.cpp | 22 ++++++++++++++++++++++ src/libs/utils/stringutils.h | 2 ++ 2 files changed, 24 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/stringutils.cpp b/src/libs/utils/stringutils.cpp index 7315a69da2..37f9336d37 100644 --- a/src/libs/utils/stringutils.cpp +++ b/src/libs/utils/stringutils.cpp @@ -546,4 +546,26 @@ QTCREATOR_UTILS_EXPORT QPair splitAtFirst(const QStrin return splitAtFirst(view, ch); } +QTCREATOR_UTILS_EXPORT int endOfNextWord(const QString &string, int position) +{ + QTC_ASSERT(string.size() > position, return -1); + + static const QString wordSeparators = QStringLiteral(" \t\n\r()[]{}<>"); + + const auto predicate = [](const QChar &c) { return wordSeparators.contains(c); }; + + auto it = string.begin() + position; + if (predicate(*it)) + it = std::find_if_not(it, string.end(), predicate); + + if (it == string.end()) + return -1; + + it = std::find_if(it, string.end(), predicate); + if (it == string.end()) + return -1; + + return std::distance(string.begin(), it); +} + } // namespace Utils diff --git a/src/libs/utils/stringutils.h b/src/libs/utils/stringutils.h index 865a1dea12..d1a94330b7 100644 --- a/src/libs/utils/stringutils.h +++ b/src/libs/utils/stringutils.h @@ -119,4 +119,6 @@ QTCREATOR_UTILS_EXPORT QPair splitAtFirst(const QStrin QTCREATOR_UTILS_EXPORT QPair splitAtFirst(const QStringView &stringView, QChar ch); +QTCREATOR_UTILS_EXPORT int endOfNextWord(const QString &string, int position = 0); + } // namespace Utils -- cgit v1.2.3 From 3ba769fb466d1a96149e05727adc975d252a8264 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Tue, 28 Mar 2023 16:45:16 +0200 Subject: Fix saving of hardlinked files Our atomic write involves writing a temp file and renaming that (which is the only way to achieve something atomic). This creates a new inode, and disconnects any hardlinks. Note that the existing implementation for file paths with needsDevice already keeps hardlinks intact, because even though it first writes into a local temporary file it then writes the content directly into the target with dd. Check the number of hard links via system API and fallback to unsafe writing if there are any, for desktop paths. Fixes: QTCREATORBUG-19651 Change-Id: I3ce1ee81f339f241f0a2c9aa6f2259cb118ebef6 Reviewed-by: Christian Kandeler Reviewed-by: --- src/libs/utils/devicefileaccess.cpp | 29 +++++++++++++++++++++++++++++ src/libs/utils/devicefileaccess.h | 3 +++ src/libs/utils/filepath.cpp | 5 +++++ src/libs/utils/filepath.h | 1 + src/libs/utils/fileutils.cpp | 12 +++++++----- 5 files changed, 45 insertions(+), 5 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/devicefileaccess.cpp b/src/libs/utils/devicefileaccess.cpp index 9345b62e88..76f90efb69 100644 --- a/src/libs/utils/devicefileaccess.cpp +++ b/src/libs/utils/devicefileaccess.cpp @@ -106,6 +106,13 @@ bool DeviceFileAccess::isSymLink(const FilePath &filePath) const return false; } +bool DeviceFileAccess::hasHardLinks(const FilePath &filePath) const +{ + Q_UNUSED(filePath) + QTC_CHECK(false); + return false; +} + bool DeviceFileAccess::ensureWritableDirectory(const FilePath &filePath) const { if (isWritableDirectory(filePath)) @@ -475,6 +482,21 @@ bool DesktopDeviceFileAccess::isSymLink(const FilePath &filePath) const return fi.isSymLink(); } +bool DesktopDeviceFileAccess::hasHardLinks(const FilePath &filePath) const +{ +#ifdef Q_OS_UNIX + struct stat s + {}; + const int r = stat(filePath.absoluteFilePath().toString().toLocal8Bit().constData(), &s); + if (r == 0) { + // check for hardlinks because these would break without the atomic write implementation + if (s.st_nlink > 1) + return true; + } +#endif + return false; +} + bool DesktopDeviceFileAccess::ensureWritableDirectory(const FilePath &filePath) const { const QFileInfo fi(filePath.path()); @@ -849,6 +871,13 @@ bool UnixDeviceFileAccess::isSymLink(const FilePath &filePath) const return runInShellSuccess({"test", {"-h", path}, OsType::OsTypeLinux}); } +bool UnixDeviceFileAccess::hasHardLinks(const FilePath &filePath) const +{ + const QStringList args = statArgs(filePath, "%h", "%l"); + const RunResult result = runInShell({"stat", args, OsType::OsTypeLinux}); + return result.stdOut.toLongLong() > 1; +} + bool UnixDeviceFileAccess::ensureExistingFile(const FilePath &filePath) const { const QString path = filePath.path(); diff --git a/src/libs/utils/devicefileaccess.h b/src/libs/utils/devicefileaccess.h index d57da462d2..0f8282477f 100644 --- a/src/libs/utils/devicefileaccess.h +++ b/src/libs/utils/devicefileaccess.h @@ -34,6 +34,7 @@ protected: virtual bool isFile(const FilePath &filePath) const; virtual bool isDirectory(const FilePath &filePath) const; virtual bool isSymLink(const FilePath &filePath) const; + virtual bool hasHardLinks(const FilePath &filePath) const; virtual bool ensureWritableDirectory(const FilePath &filePath) const; virtual bool ensureExistingFile(const FilePath &filePath) const; virtual bool createDirectory(const FilePath &filePath) const; @@ -90,6 +91,7 @@ protected: bool isFile(const FilePath &filePath) const override; bool isDirectory(const FilePath &filePath) const override; bool isSymLink(const FilePath &filePath) const override; + bool hasHardLinks(const FilePath &filePath) const override; bool ensureWritableDirectory(const FilePath &filePath) const override; bool ensureExistingFile(const FilePath &filePath) const override; bool createDirectory(const FilePath &filePath) const override; @@ -148,6 +150,7 @@ protected: bool isFile(const FilePath &filePath) const override; bool isDirectory(const FilePath &filePath) const override; bool isSymLink(const FilePath &filePath) const override; + bool hasHardLinks(const FilePath &filePath) const override; bool ensureExistingFile(const FilePath &filePath) const override; bool createDirectory(const FilePath &filePath) const override; bool exists(const FilePath &filePath) const override; diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 1781831712..0fce0a9e4c 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -564,6 +564,11 @@ bool FilePath::isSymLink() const return fileAccess()->isSymLink(*this); } +bool FilePath::hasHardLinks() const +{ + return fileAccess()->hasHardLinks(*this); +} + /*! \brief Creates a directory in this location. diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h index 213eeb1e7b..6465169154 100644 --- a/src/libs/utils/filepath.h +++ b/src/libs/utils/filepath.h @@ -118,6 +118,7 @@ public: bool isFile() const; bool isDir() const; bool isSymLink() const; + bool hasHardLinks() const; bool isRootPath() const; bool isNewerThan(const QDateTime &timeStamp) const; QDateTime lastModified() const; diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index f31bd822bb..22d855fea3 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -5,6 +5,7 @@ #include "savefile.h" #include "algorithm.h" +#include "devicefileaccess.h" #include "qtcassert.h" #include "utilstr.h" @@ -189,12 +190,13 @@ FileSaver::FileSaver(const FilePath &filePath, QIODevice::OpenMode mode) auto tf = new QTemporaryFile(QDir::tempPath() + "/remotefilesaver-XXXXXX"); tf->setAutoRemove(false); m_file.reset(tf); - } else if (mode & (QIODevice::ReadOnly | QIODevice::Append)) { - m_file.reset(new QFile{filePath.path()}); - m_isSafe = false; } else { - m_file.reset(new SaveFile(filePath)); - m_isSafe = true; + const bool readOnlyOrAppend = mode & (QIODevice::ReadOnly | QIODevice::Append); + m_isSafe = !readOnlyOrAppend && !filePath.hasHardLinks(); + if (m_isSafe) + m_file.reset(new SaveFile(filePath)); + else + m_file.reset(new QFile{filePath.path()}); } if (!m_file->open(QIODevice::WriteOnly | mode)) { QString err = filePath.exists() ? -- cgit v1.2.3 From 8175d5abdaf97c487313a4d18a177b22e390a12d Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Thu, 6 Apr 2023 18:27:10 +0200 Subject: onResultReady: Provide a context object for all usages Remove overloads for onResultReady() and onFinished() that don't take context object. Change-Id: Iaec538bcccd29e22791ec65cc95b4b87640708c3 Reviewed-by: Eike Ziller Reviewed-by: Reviewed-by: Qt CI Bot --- src/libs/utils/runextensions.h | 34 ---------------------------------- 1 file changed, 34 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/runextensions.h b/src/libs/utils/runextensions.h index f81eb56699..3e0b7afb95 100644 --- a/src/libs/utils/runextensions.h +++ b/src/libs/utils/runextensions.h @@ -513,23 +513,6 @@ const QFuture &onResultReady(const QFuture &future, QObject *guard, Functi return future; } -/*! - Adds a handler for when a result is ready. - This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions - or create a QFutureWatcher already for other reasons. -*/ -template -const QFuture &onResultReady(const QFuture &future, Function f) -{ - auto watcher = new QFutureWatcher(); - QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); - QObject::connect(watcher, &QFutureWatcherBase::resultReadyAt, [f, watcher](int index) { - f(watcher->future().resultAt(index)); - }); - watcher->setFuture(future); - return future; -} - /*! Adds a handler for when the future is finished. This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions @@ -568,21 +551,4 @@ const QFuture &onFinished(const QFuture &future, QObject *guard, Function return future; } -/*! - Adds a handler for when the future is finished. - This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions - or create a QFutureWatcher already for other reasons. -*/ -template -const QFuture &onFinished(const QFuture &future, Function f) -{ - auto watcher = new QFutureWatcher(); - QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); - QObject::connect(watcher, &QFutureWatcherBase::finished, [f, watcher] { - f(watcher->future()); - }); - watcher->setFuture(future); - return future; -} - } // namespace Utils -- cgit v1.2.3 From 78cf563142efe442796ff74d332c1d96024d1fde Mon Sep 17 00:00:00 2001 From: Semih Yavuz Date: Tue, 11 Apr 2023 11:11:08 +0200 Subject: reformatter: fix formatting of js directives Fix the miscalculation of the start / end of the js directives lines. Also small optimizations on usage of increment operators. Add a unit test. Fixes: QTCREATORBUG-29001 Change-Id: I38923f137dca5c0b89d474cd747a64ec11e62fd9 Reviewed-by: Reviewed-by: Fabian Kosmale --- src/libs/qmljs/qmljsreformatter.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'src/libs') diff --git a/src/libs/qmljs/qmljsreformatter.cpp b/src/libs/qmljs/qmljsreformatter.cpp index 461c944afa..097119eb27 100644 --- a/src/libs/qmljs/qmljsreformatter.cpp +++ b/src/libs/qmljs/qmljsreformatter.cpp @@ -109,14 +109,15 @@ public: } const QList &directives = _doc->jsDirectives(); for (const auto &d: directives) { - quint32 line = 1; - int i = 0; - while (line++ < d.startLine && i++ >= 0) - i = _doc->source().indexOf(QChar('\n'), i); + quint32 line = 0; + const QString &source = _doc->source(); + int i = -1; + while (++line < d.startLine) + i = source.indexOf(QChar('\n'), i + 1); quint32 offset = static_cast(i) + d.startColumn; - int endline = _doc->source().indexOf('\n', static_cast(offset) + 1); - int end = endline == -1 ? _doc->source().length() : endline; - quint32 length = static_cast(end) - offset; + int endline = source.indexOf('\n', static_cast(offset) + 1); + int end = endline == -1 ? source.length() : endline; + quint32 length = static_cast(end) - offset + 1; out(SourceLocation(offset, length, d.startLine, d.startColumn)); } if (!directives.isEmpty()) -- cgit v1.2.3 From b795b42980d24b8c3febb7d9c7c1fd6b6ff1ba9a Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Wed, 12 Apr 2023 12:15:12 +0200 Subject: CppEditor: More special rendering for string literals Display prefixes and suffixes different from the actual string, like we already did for raw string literals. This uncovered some minor bugs in both lexer and highlighter: - Wrong length for a setFormat() call in highlightRawStringLiteral() - Missing check for user-defined literal in raw string literals - Missing check for user-defined literal in multi-line strings Fixes: QTCREATORBUG-28869 Change-Id: I018717c50ddc1d09c609556161c85dfb0cc29fab Reviewed-by: David Schulz --- src/libs/3rdparty/cplusplus/Lexer.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/3rdparty/cplusplus/Lexer.cpp b/src/libs/3rdparty/cplusplus/Lexer.cpp index e2f1b4e0e6..fc1b72cb7d 100644 --- a/src/libs/3rdparty/cplusplus/Lexer.cpp +++ b/src/libs/3rdparty/cplusplus/Lexer.cpp @@ -217,14 +217,17 @@ void Lexer::scan_helper(Token *tok) tok->f.kind = s._tokenKind; const bool found = _expectedRawStringSuffix.isEmpty() ? scanUntilRawStringLiteralEndSimple() : scanUntilRawStringLiteralEndPrecise(); - if (found) + if (found) { + scanOptionalUserDefinedLiteral(tok); _state = 0; + } return; } else { // non-raw strings tok->f.joined = true; tok->f.kind = s._tokenKind; _state = 0; scanUntilQuote(tok, '"'); + scanOptionalUserDefinedLiteral(tok); return; } @@ -829,6 +832,8 @@ void Lexer::scanRawStringLiteral(Token *tok, unsigned char hint) _expectedRawStringSuffix.prepend(')'); _expectedRawStringSuffix.append('"'); } + if (closed) + scanOptionalUserDefinedLiteral(tok); } bool Lexer::scanUntilRawStringLiteralEndPrecise() -- cgit v1.2.3 From 15a06b77c01c3bcbbc69ca0e80035e0c3824d62c Mon Sep 17 00:00:00 2001 From: Semih Yavuz Date: Fri, 14 Apr 2023 11:38:25 +0200 Subject: Fix tst_qml_reformatter auto test Add source location to pragma library writer so that the comments before .pragma line are also written. Fix tst_qml_reformatter by skipping the blankline comparisons. Line by line comparison with the non-formatted code is not a good idea to test reformatting. Ideally test should have compared the formatted file with the original one character wise, yet it is not worth changing into that at this point. Amends 0ce57fcf5e90f8bf8cfbe681f2954a0c1ef0e945 Change-Id: I39bcee2c881e1a0928c17ebb45aa1c85e6cf3b99 Reviewed-by: Qt CI Bot Reviewed-by: Jarek Kobus Reviewed-by: Eike Ziller --- src/libs/qmljs/qmljsreformatter.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/libs') diff --git a/src/libs/qmljs/qmljsreformatter.cpp b/src/libs/qmljs/qmljsreformatter.cpp index 097119eb27..bad58e414c 100644 --- a/src/libs/qmljs/qmljsreformatter.cpp +++ b/src/libs/qmljs/qmljsreformatter.cpp @@ -101,16 +101,16 @@ public: _hadEmptyLine = false; _binaryExpDepth = 0; - + const QString &source = _doc->source(); // emit directives if (_doc->bind()->isJsLibrary()) { - out(QLatin1String(".pragma library")); + const QLatin1String pragmaLine(".pragma library"); + out(pragmaLine, SourceLocation(source.indexOf(".pragma"), pragmaLine.length())); newLine(); } const QList &directives = _doc->jsDirectives(); for (const auto &d: directives) { quint32 line = 0; - const QString &source = _doc->source(); int i = -1; while (++line < d.startLine) i = source.indexOf(QChar('\n'), i + 1); -- cgit v1.2.3 From 7ab0fd56aea0ee43fc0ad36205b0b6f6823605c3 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Thu, 6 Apr 2023 17:59:27 +0200 Subject: RunExtensions: Move onResultReady and onFinished into asynctask.h Change-Id: I96dbf5b0253251224ae678172cd5fca12b34326a Reviewed-by: Eike Ziller Reviewed-by: Reviewed-by: Qt CI Bot --- src/libs/qmljs/qmljsplugindumper.cpp | 1 - src/libs/utils/asynctask.h | 70 +++++++++++++++++++++++++++++++++ src/libs/utils/runextensions.h | 75 ------------------------------------ 3 files changed, 70 insertions(+), 76 deletions(-) (limited to 'src/libs') diff --git a/src/libs/qmljs/qmljsplugindumper.cpp b/src/libs/qmljs/qmljsplugindumper.cpp index 20736803d2..9e2261014f 100644 --- a/src/libs/qmljs/qmljsplugindumper.cpp +++ b/src/libs/qmljs/qmljsplugindumper.cpp @@ -14,7 +14,6 @@ #include #include #include -#include #include #include diff --git a/src/libs/utils/asynctask.h b/src/libs/utils/asynctask.h index a600a14b24..d7c51a66fa 100644 --- a/src/libs/utils/asynctask.h +++ b/src/libs/utils/asynctask.h @@ -45,6 +45,76 @@ auto asyncRun(Function &&function, Args &&...args) std::forward(function), std::forward(args)...); } +/*! + Adds a handler for when a result is ready. + This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions + or create a QFutureWatcher already for other reasons. +*/ +template +const QFuture &onResultReady(const QFuture &future, R *receiver, void(R::*member)(const T &)) +{ + auto watcher = new QFutureWatcher(); + QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); + QObject::connect(watcher, &QFutureWatcherBase::resultReadyAt, receiver, [=](int index) { + (receiver->*member)(watcher->future().resultAt(index)); + }); + watcher->setFuture(future); + return future; +} + +/*! + Adds a handler for when a result is ready. The guard object determines the lifetime of + the connection. + This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions + or create a QFutureWatcher already for other reasons. +*/ +template +const QFuture &onResultReady(const QFuture &future, QObject *guard, Function f) +{ + auto watcher = new QFutureWatcher(); + QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); + QObject::connect(watcher, &QFutureWatcherBase::resultReadyAt, guard, [f, watcher](int index) { + f(watcher->future().resultAt(index)); + }); + watcher->setFuture(future); + return future; +} + +/*! + Adds a handler for when the future is finished. + This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions + or create a QFutureWatcher already for other reasons. +*/ +template +const QFuture &onFinished(const QFuture &future, + R *receiver, void (R::*member)(const QFuture &)) +{ + auto watcher = new QFutureWatcher(); + QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); + QObject::connect(watcher, &QFutureWatcherBase::finished, receiver, + [=] { (receiver->*member)(watcher->future()); }); + watcher->setFuture(future); + return future; +} + +/*! + Adds a handler for when the future is finished. The guard object determines the lifetime of + the connection. + This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions + or create a QFutureWatcher already for other reasons. +*/ +template +const QFuture &onFinished(const QFuture &future, QObject *guard, Function f) +{ + auto watcher = new QFutureWatcher(); + QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); + QObject::connect(watcher, &QFutureWatcherBase::finished, guard, [f, watcher] { + f(watcher->future()); + }); + watcher->setFuture(future); + return future; +} + class QTCREATOR_UTILS_EXPORT AsyncTaskBase : public QObject { Q_OBJECT diff --git a/src/libs/utils/runextensions.h b/src/libs/utils/runextensions.h index 3e0b7afb95..6505b12a9c 100644 --- a/src/libs/utils/runextensions.h +++ b/src/libs/utils/runextensions.h @@ -476,79 +476,4 @@ runAsync(QThreadPool *pool, Function &&function, Args&&... args) std::forward(args)...); } - -/*! - Adds a handler for when a result is ready. - This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions - or create a QFutureWatcher already for other reasons. -*/ -template -const QFuture &onResultReady(const QFuture &future, R *receiver, void(R::*member)(const T &)) -{ - auto watcher = new QFutureWatcher(); - QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); - QObject::connect(watcher, &QFutureWatcherBase::resultReadyAt, receiver, - [receiver, member, watcher](int index) { - (receiver->*member)(watcher->future().resultAt(index)); - }); - watcher->setFuture(future); - return future; -} - -/*! - Adds a handler for when a result is ready. The guard object determines the lifetime of - the connection. - This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions - or create a QFutureWatcher already for other reasons. -*/ -template -const QFuture &onResultReady(const QFuture &future, QObject *guard, Function f) -{ - auto watcher = new QFutureWatcher(); - QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); - QObject::connect(watcher, &QFutureWatcherBase::resultReadyAt, guard, [f, watcher](int index) { - f(watcher->future().resultAt(index)); - }); - watcher->setFuture(future); - return future; -} - -/*! - Adds a handler for when the future is finished. - This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions - or create a QFutureWatcher already for other reasons. -*/ -template -const QFuture &onFinished(const QFuture &future, - R *receiver, - void (R::*member)(const QFuture &)) -{ - auto watcher = new QFutureWatcher(); - QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); - QObject::connect(watcher, - &QFutureWatcherBase::finished, - receiver, - [receiver, member, watcher] { (receiver->*member)(watcher->future()); }); - watcher->setFuture(future); - return future; -} - -/*! - Adds a handler for when the future is finished. The guard object determines the lifetime of - the connection. - This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions - or create a QFutureWatcher already for other reasons. -*/ -template -const QFuture &onFinished(const QFuture &future, QObject *guard, Function f) -{ - auto watcher = new QFutureWatcher(); - QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); - QObject::connect(watcher, &QFutureWatcherBase::finished, guard, [f, watcher] { - f(watcher->future()); - }); - watcher->setFuture(future); - return future; -} - } // namespace Utils -- cgit v1.2.3 From 8db29850b168d9abc3c3d14af94878a819545514 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Thu, 16 Mar 2023 18:25:25 -0700 Subject: Clean up legacy content from Qt 5's QProcess::setupChildProcess() We needed a derived class because in Qt 5 we needed to override the setupChildProcess() virtual. Now have setChildProcessModifier(). The actual subclassing was removed in a prior commit; this merely cleans stuff up. Drive-by fix the arguments to setpgid: processId() always returns 0 in the child process. Change-Id: Icfe44ecf285a480fafe4fffd174d1073c0e1ddc3 Reviewed-by: Jarek Kobus --- src/libs/utils/launcherinterface.cpp | 24 ++++++++---------------- src/libs/utils/processutils.cpp | 29 ++++++++++++----------------- src/libs/utils/processutils.h | 1 - 3 files changed, 20 insertions(+), 34 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/launcherinterface.cpp b/src/libs/utils/launcherinterface.cpp index e71b09b0ed..218286521d 100644 --- a/src/libs/utils/launcherinterface.cpp +++ b/src/libs/utils/launcherinterface.cpp @@ -21,20 +21,6 @@ namespace Utils { namespace Internal { -class LauncherProcess : public QProcess -{ -public: - LauncherProcess(QObject *parent) : QProcess(parent) - { -#ifdef Q_OS_UNIX - setChildProcessModifier([this] { - const auto pid = static_cast(processId()); - setpgid(pid, pid); - }); -#endif - } -}; - static QString launcherSocketName() { return TemporaryDirectory::masterDirectoryPath() @@ -64,7 +50,7 @@ signals: private: QLocalServer * const m_server; Internal::LauncherSocket *const m_socket; - Internal::LauncherProcess *m_process = nullptr; + QProcess *m_process = nullptr; QString m_pathToLauncher; }; @@ -89,12 +75,18 @@ void LauncherInterfacePrivate::doStart() emit errorOccurred(m_server->errorString()); return; } - m_process = new LauncherProcess(this); + m_process = new QProcess(this); connect(m_process, &QProcess::errorOccurred, this, &LauncherInterfacePrivate::handleProcessError); connect(m_process, &QProcess::finished, this, &LauncherInterfacePrivate::handleProcessFinished); connect(m_process, &QProcess::readyReadStandardError, this, &LauncherInterfacePrivate::handleProcessStderr); +#ifdef Q_OS_UNIX + m_process->setChildProcessModifier([] { + setpgid(0, 0); + }); +#endif + m_process->start(launcherFilePath(), QStringList(m_server->fullServerName())); } diff --git a/src/libs/utils/processutils.cpp b/src/libs/utils/processutils.cpp index b534d6f8bb..62ea514b65 100644 --- a/src/libs/utils/processutils.cpp +++ b/src/libs/utils/processutils.cpp @@ -107,7 +107,18 @@ ProcessHelper::ProcessHelper(QObject *parent) : QProcess(parent), m_processStartHandler(this) { #if defined(Q_OS_UNIX) - setChildProcessModifier([this] { setupChildProcess_impl(); }); + setChildProcessModifier([this] { + // nice value range is -20 to +19 where -20 is highest, 0 default and +19 is lowest + if (m_lowPriority) { + errno = 0; + if (::nice(5) == -1 && errno != 0) + perror("Failed to set nice value"); + } + + // Disable terminal by becoming a session leader. + if (m_unixTerminalDisabled) + setsid(); + }); #endif } @@ -153,20 +164,4 @@ void ProcessHelper::interruptProcess(QProcess *process) ProcessHelper::interruptPid(process->processId()); } -void ProcessHelper::setupChildProcess_impl() -{ -#if defined Q_OS_UNIX - // nice value range is -20 to +19 where -20 is highest, 0 default and +19 is lowest - if (m_lowPriority) { - errno = 0; - if (::nice(5) == -1 && errno != 0) - perror("Failed to set nice value"); - } - - // Disable terminal by becoming a session leader. - if (m_unixTerminalDisabled) - setsid(); -#endif -} - } // namespace Utils diff --git a/src/libs/utils/processutils.h b/src/libs/utils/processutils.h index fa915e2ac4..89202c0daf 100644 --- a/src/libs/utils/processutils.h +++ b/src/libs/utils/processutils.h @@ -50,7 +50,6 @@ public: private: void terminateProcess(); - void setupChildProcess_impl(); bool m_lowPriority = false; bool m_unixTerminalDisabled = false; -- cgit v1.2.3 From ebfcb8af65c01083412eb63c86f7a8d85a177120 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Mon, 17 Apr 2023 06:45:30 +0200 Subject: QmlJS: Fix compile with Qt6.2 Amends 15a06b77c01c3bcbbc69ca0e80035e0c3824d62c. Change-Id: Iefac198e1d566fcbda77e22bcab289dda15fa1ba Reviewed-by: Eike Ziller --- src/libs/qmljs/qmljsreformatter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/qmljs/qmljsreformatter.cpp b/src/libs/qmljs/qmljsreformatter.cpp index bad58e414c..03a95a668c 100644 --- a/src/libs/qmljs/qmljsreformatter.cpp +++ b/src/libs/qmljs/qmljsreformatter.cpp @@ -104,7 +104,7 @@ public: const QString &source = _doc->source(); // emit directives if (_doc->bind()->isJsLibrary()) { - const QLatin1String pragmaLine(".pragma library"); + const QString pragmaLine(".pragma library"); out(pragmaLine, SourceLocation(source.indexOf(".pragma"), pragmaLine.length())); newLine(); } -- cgit v1.2.3 From a353e9fde19ae0862ed95aeb71654da57f24fcac Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Wed, 19 Apr 2023 11:03:58 +0200 Subject: Markdown: Reuse Markdown highlighter from change log viewer Change-Id: Ief1b0c135a34bfd5e9b5220e9fbf93f281d8e95a Reviewed-by: David Schulz Reviewed-by: Qt CI Bot --- src/libs/utils/stringutils.cpp | 63 ++++++++++++++++++++++++++++++++++++++++-- src/libs/utils/stringutils.h | 12 ++++++++ 2 files changed, 72 insertions(+), 3 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/stringutils.cpp b/src/libs/utils/stringutils.cpp index 37f9336d37..c296881cb8 100644 --- a/src/libs/utils/stringutils.cpp +++ b/src/libs/utils/stringutils.cpp @@ -3,10 +3,9 @@ #include "stringutils.h" -#include "algorithm.h" -#include "hostosinfo.h" -#include "qtcassert.h" #include "filepath.h" +#include "qtcassert.h" +#include "theme/theme.h" #include "utilstr.h" #ifdef QT_WIDGETS_LIB @@ -15,11 +14,13 @@ #endif #include +#include #include #include #include #include #include +#include #include #include @@ -568,4 +569,60 @@ QTCREATOR_UTILS_EXPORT int endOfNextWord(const QString &string, int position) return std::distance(string.begin(), it); } +MarkdownHighlighter::MarkdownHighlighter(QTextDocument *parent) + : QSyntaxHighlighter(parent) + , h2Brush(Qt::NoBrush) +{ + parent->setIndentWidth(30); // default value is 40 +} + +void MarkdownHighlighter::highlightBlock(const QString &text) +{ + if (text.isEmpty()) + return; + + QTextBlockFormat fmt = currentBlock().blockFormat(); + QTextCursor cur(currentBlock()); + if (fmt.hasProperty(QTextFormat::HeadingLevel)) { + fmt.setTopMargin(10); + fmt.setBottomMargin(10); + + // Draw an underline for Heading 2, by creating a texture brush + // with the last pixel visible + if (fmt.property(QTextFormat::HeadingLevel) == 2) { + QTextCharFormat charFmt = currentBlock().charFormat(); + charFmt.setBaselineOffset(15); + setFormat(0, text.length(), charFmt); + + if (h2Brush.style() == Qt::NoBrush) { + const int height = QFontMetrics(charFmt.font()).height(); + QImage image(1, height, QImage::Format_ARGB32); + + image.fill(QColor(0, 0, 0, 0).rgba()); + image.setPixel(0, + height - 1, + Utils::creatorTheme()->color(Theme::TextColorDisabled).rgba()); + + h2Brush = QBrush(image); + } + fmt.setBackground(h2Brush); + } + cur.setBlockFormat(fmt); + } else if (fmt.hasProperty(QTextFormat::BlockCodeLanguage) && fmt.indent() == 0) { + // set identation for code blocks + fmt.setIndent(1); + cur.setBlockFormat(fmt); + } + + // Show the bulet points as filled circles + QTextList *list = cur.currentList(); + if (list) { + QTextListFormat listFmt = list->format(); + if (listFmt.indent() == 1 && listFmt.style() == QTextListFormat::ListCircle) { + listFmt.setStyle(QTextListFormat::ListDisc); + list->setFormat(listFmt); + } + } +} + } // namespace Utils diff --git a/src/libs/utils/stringutils.h b/src/libs/utils/stringutils.h index d1a94330b7..3bab6110cf 100644 --- a/src/libs/utils/stringutils.h +++ b/src/libs/utils/stringutils.h @@ -5,8 +5,10 @@ #include "utils_global.h" +#include #include #include +#include #include @@ -121,4 +123,14 @@ QTCREATOR_UTILS_EXPORT QPair splitAtFirst(const QStrin QTCREATOR_UTILS_EXPORT int endOfNextWord(const QString &string, int position = 0); +class QTCREATOR_UTILS_EXPORT MarkdownHighlighter : public QSyntaxHighlighter +{ +public: + MarkdownHighlighter(QTextDocument *parent); + void highlightBlock(const QString &text); + +private: + QBrush h2Brush; +}; + } // namespace Utils -- cgit v1.2.3 From dd5eed0b35cadfa746e448191962d92b67f04e65 Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Tue, 18 Apr 2023 17:49:52 +0200 Subject: Utils: Fix painting artifacts of FancyLineEdit on StyledBar Don't enforce a premature polishing by the style. Fixes: QTCREATORBUG-27510 Change-Id: I1598a75741c5990567a33ad8376144432894597a Reviewed-by: Cristian Adam --- src/libs/utils/fancylineedit.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/fancylineedit.cpp b/src/libs/utils/fancylineedit.cpp index a0d1fd9b8a..03e33c67b6 100644 --- a/src/libs/utils/fancylineedit.cpp +++ b/src/libs/utils/fancylineedit.cpp @@ -172,7 +172,6 @@ FancyLineEdit::FancyLineEdit(QWidget *parent) : CompletingLineEdit(parent), d(new FancyLineEditPrivate(this)) { - ensurePolished(); updateMargins(); connect(d->m_iconbutton[Left], &QAbstractButton::clicked, this, [this] { -- cgit v1.2.3 From b6d1cbc5f6d65cdcc4a9eb8bafc78f0926bd1a19 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 19 Apr 2023 21:32:58 +0200 Subject: StringUtils: Fix missing include Amends a353e9fde19ae0862ed95aeb71654da57f24fcac Change-Id: Iee878da1f907ec0131dd3fd16fe4f84752355b38 Reviewed-by: Jarek Kobus --- src/libs/utils/stringutils.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src/libs') diff --git a/src/libs/utils/stringutils.cpp b/src/libs/utils/stringutils.cpp index c296881cb8..f860216961 100644 --- a/src/libs/utils/stringutils.cpp +++ b/src/libs/utils/stringutils.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include -- cgit v1.2.3 From f5cccab95b6b6f7c4b34b25ab31ce703bd15199f Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Tue, 18 Apr 2023 18:01:27 +0300 Subject: MacroExpander: Fix Path and FilePath on Windows They should have / separator. Amends commit 4fbc56d453ef2fa73e494f371bd44285e3ac0d50. Change-Id: I7218c345b271360f24c03aea5ee62be05342afe0 Reviewed-by: Eike Ziller --- src/libs/utils/macroexpander.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/macroexpander.cpp b/src/libs/utils/macroexpander.cpp index 2fe340efc6..b1ce40d25a 100644 --- a/src/libs/utils/macroexpander.cpp +++ b/src/libs/utils/macroexpander.cpp @@ -380,13 +380,13 @@ void MacroExpander::registerFileVariables(const QByteArray &prefix, registerVariable( prefix + kFilePathPostfix, Tr::tr("%1: Full path including file name.").arg(heading), - [base] { return base().toUserOutput(); }, + [base] { return base().path(); }, visibleInChooser); registerVariable( prefix + kPathPostfix, Tr::tr("%1: Full path excluding file name.").arg(heading), - [base] { return base().parentDir().toUserOutput(); }, + [base] { return base().parentDir().path(); }, visibleInChooser); registerVariable( -- cgit v1.2.3 From 21ca06fc7cbb2be37c3c8face1d43cf2cbda3b4f Mon Sep 17 00:00:00 2001 From: Semih Yavuz Date: Thu, 20 Apr 2023 11:04:06 +0200 Subject: Codeformatter: Support indenting of type annotated function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: QTCREATORBUG-29046 Change-Id: Ie4a4d85b7aa00ddf4dd3ea4bade6ffa57af7b4e0 Reviewed-by: Mikołaj Boc Reviewed-by: Ulf Hermann Reviewed-by: Christian Kandeler Reviewed-by: Qt CI Bot Reviewed-by: Fabian Kosmale --- src/libs/qmljs/qmljscodeformatter.cpp | 7 +++++++ src/libs/qmljs/qmljscodeformatter.h | 1 + 2 files changed, 8 insertions(+) (limited to 'src/libs') diff --git a/src/libs/qmljs/qmljscodeformatter.cpp b/src/libs/qmljs/qmljscodeformatter.cpp index 55ed2e6190..a2f944700f 100644 --- a/src/libs/qmljs/qmljscodeformatter.cpp +++ b/src/libs/qmljs/qmljscodeformatter.cpp @@ -253,9 +253,16 @@ void CodeFormatter::recalculateStateAfter(const QTextBlock &block) case function_arglist_closed: switch (kind) { case LeftBrace: turnInto(jsblock_open); break; + case Colon: turnInto(function_type_annotated_return); break; default: leave(true); continue; // error recovery } break; + case function_type_annotated_return: + switch (kind) { + case LeftBrace: turnInto(jsblock_open); break; + default: break; + } break; + case expression_or_objectdefinition: switch (kind) { case Dot: diff --git a/src/libs/qmljs/qmljscodeformatter.h b/src/libs/qmljs/qmljscodeformatter.h index 4800fccfdf..abef85a782 100644 --- a/src/libs/qmljs/qmljscodeformatter.h +++ b/src/libs/qmljs/qmljscodeformatter.h @@ -99,6 +99,7 @@ public: // must be public to make Q_GADGET introspection work function_start, // after 'function' function_arglist_open, // after '(' starting function argument list function_arglist_closed, // after ')' in argument list, expecting '{' + function_type_annotated_return, // after ':' expecting a type binding_or_objectdefinition, // after an identifier -- cgit v1.2.3 From bb9ba4349af79519a72f36148d68fd65761162f2 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Sun, 23 Apr 2023 10:02:04 +0200 Subject: Utils: Use Q_DISABLE_COPY / Q_DISABLE_COPY_MOVE Change-Id: If9ea6220700769cd99ede3ebaacc4d75cb673e89 Reviewed-by: Reviewed-by: hjk Reviewed-by: Qt CI Bot --- src/libs/utils/delegates.h | 8 ++------ src/libs/utils/layoutbuilder.h | 4 ++-- src/libs/utils/terminalhooks.h | 6 ++---- 3 files changed, 6 insertions(+), 12 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/delegates.h b/src/libs/utils/delegates.h index 97f1c774c3..ff012152f5 100644 --- a/src/libs/utils/delegates.h +++ b/src/libs/utils/delegates.h @@ -62,18 +62,14 @@ private: class QTCREATOR_UTILS_EXPORT CompleterDelegate : public QStyledItemDelegate { + Q_DISABLE_COPY_MOVE(CompleterDelegate) + public: CompleterDelegate(const QStringList &candidates, QObject *parent = nullptr); CompleterDelegate(QAbstractItemModel *model, QObject *parent = nullptr); CompleterDelegate(QCompleter *completer, QObject *parent = nullptr); ~CompleterDelegate() override; - CompleterDelegate(const CompleterDelegate &other) = delete; - CompleterDelegate(CompleterDelegate &&other) = delete; - - CompleterDelegate &operator=(const CompleterDelegate &other) = delete; - CompleterDelegate &operator=(CompleterDelegate &&other) = delete; - QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void setEditorData(QWidget *editor, const QModelIndex &index) const override; diff --git a/src/libs/utils/layoutbuilder.h b/src/libs/utils/layoutbuilder.h index 2f534cb488..d34a8f6cf4 100644 --- a/src/libs/utils/layoutbuilder.h +++ b/src/libs/utils/layoutbuilder.h @@ -162,6 +162,8 @@ QTCREATOR_UTILS_EXPORT QWidget *createHr(QWidget *parent = nullptr); class QTCREATOR_UTILS_EXPORT LayoutBuilder { + Q_DISABLE_COPY(LayoutBuilder) + public: enum LayoutType { HBoxLayout, @@ -175,9 +177,7 @@ public: explicit LayoutBuilder(LayoutType layoutType, const LayoutItems &items = {}); - LayoutBuilder(const LayoutBuilder &) = delete; LayoutBuilder(LayoutBuilder &&) = default; - LayoutBuilder &operator=(const LayoutBuilder &) = delete; LayoutBuilder &operator=(LayoutBuilder &&) = default; ~LayoutBuilder(); diff --git a/src/libs/utils/terminalhooks.h b/src/libs/utils/terminalhooks.h index 5a614400f4..b37c51517c 100644 --- a/src/libs/utils/terminalhooks.h +++ b/src/libs/utils/terminalhooks.h @@ -17,15 +17,13 @@ class ProcessInterface; template class Hook { + Q_DISABLE_COPY_MOVE(Hook) + public: using Callback = std::function; public: Hook() = delete; - Hook(const Hook &other) = delete; - Hook(Hook &&other) = delete; - Hook &operator=(const Hook &other) = delete; - Hook &operator=(Hook &&other) = delete; explicit Hook(Callback defaultCallback) { set(defaultCallback); } -- cgit v1.2.3 From 5bc60ac8de01372505c870469063723b3aaa3955 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Fri, 21 Apr 2023 08:14:54 +0200 Subject: QmlJS: Stop suggesting versions for imports if possible Fixes: QTCREATORBUG-28649 Change-Id: I918b229855c18519800a54a73b56eaffa40524e5 Reviewed-by: Fabian Kosmale Reviewed-by: Ulf Hermann Reviewed-by: --- src/libs/qmljs/qmljsbundle.cpp | 19 ++++++++++++++----- src/libs/qmljs/qmljsbundle.h | 5 +++-- 2 files changed, 17 insertions(+), 7 deletions(-) (limited to 'src/libs') diff --git a/src/libs/qmljs/qmljsbundle.cpp b/src/libs/qmljs/qmljsbundle.cpp index c55edcdf5e..e4d536a3da 100644 --- a/src/libs/qmljs/qmljsbundle.cpp +++ b/src/libs/qmljs/qmljsbundle.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -186,8 +187,10 @@ QString QmlBundle::toString(const QString &indent) } QStringList QmlBundle::maybeReadTrie(Trie &trie, Utils::JsonObjectValue *config, - const QString &path, const QString &propertyName, bool required) + const QString &path, const QString &propertyName, + bool required, bool stripVersions) { + static const QRegularExpression versionNumberAtEnd("^(.+)( \\d+\\.\\d+)$"); QStringList res; if (!config->hasMember(propertyName)) { if (required) @@ -202,7 +205,13 @@ QStringList QmlBundle::maybeReadTrie(Trie &trie, Utils::JsonObjectValue *config, for (Utils::JsonValue *v : elements) { Utils::JsonStringValue *impStr = ((v != nullptr) ? v->toString() : nullptr); if (impStr != nullptr) { - trie.insert(impStr->value()); + QString value = impStr->value(); + if (stripVersions) { + const QRegularExpressionMatch match = versionNumberAtEnd.match(value); + if (match.hasMatch()) + value = match.captured(1); + } + trie.insert(value); } else { res.append(QString::fromLatin1("Expected all elements of array in property \"%1\" " "to be strings in QmlBundle at %2.") @@ -217,7 +226,7 @@ QStringList QmlBundle::maybeReadTrie(Trie &trie, Utils::JsonObjectValue *config, return res; } -bool QmlBundle::readFrom(QString path, QStringList *errors) +bool QmlBundle::readFrom(QString path, bool stripVersions, QStringList *errors) { Utils::JsonMemoryPool pool; @@ -249,8 +258,8 @@ bool QmlBundle::readFrom(QString path, QStringList *errors) } errs << maybeReadTrie(m_searchPaths, config, path, QLatin1String("searchPaths")); errs << maybeReadTrie(m_installPaths, config, path, QLatin1String("installPaths")); - errs << maybeReadTrie(m_supportedImports, config, path, QLatin1String("supportedImports") - , true); + errs << maybeReadTrie(m_supportedImports, config, path, QLatin1String("supportedImports"), + true, stripVersions); errs << maybeReadTrie(m_implicitImports, config, path, QLatin1String("implicitImports")); if (errors) (*errors) << errs; diff --git a/src/libs/qmljs/qmljsbundle.h b/src/libs/qmljs/qmljsbundle.h index 8cf3145671..5d2058eef4 100644 --- a/src/libs/qmljs/qmljsbundle.h +++ b/src/libs/qmljs/qmljsbundle.h @@ -53,14 +53,15 @@ public: bool writeTo(const QString &path) const; bool writeTo(QTextStream &stream, const QString &indent = QString()) const; QString toString(const QString &indent = QString()); - bool readFrom(QString path, QStringList *errors); + bool readFrom(QString path, bool stripVersions, QStringList *errors); bool operator==(const QmlBundle &o) const; bool operator!=(const QmlBundle &o) const; private: static void printEscaped(QTextStream &s, const QString &str); static void writeTrie(QTextStream &stream, const Trie &t, const QString &indent); QStringList maybeReadTrie(Trie &trie, Utils::JsonObjectValue *config, const QString &path, - const QString &propertyName, bool required = false); + const QString &propertyName, bool required = false, + bool stripVersions = false); QString m_name; Trie m_searchPaths; -- cgit v1.2.3 From b7ca84c5eef0eeb50cd9e86dad7482ab6f19cdd8 Mon Sep 17 00:00:00 2001 From: hjk Date: Mon, 24 Apr 2023 17:06:32 +0200 Subject: Utils: Remove one LayoutBuilder::addRow() overload The flexibility here is getting in the way later when trying to remove the dependency on aspects. Change-Id: I7221e80f2067292c7c80aead8f6d739fb7878f7e Reviewed-by: Reviewed-by: Qt CI Bot Reviewed-by: Eike Ziller --- src/libs/utils/layoutbuilder.cpp | 11 ----------- src/libs/utils/layoutbuilder.h | 1 - 2 files changed, 12 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index e7136c5a90..f4b1dda3ee 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -341,17 +341,6 @@ LayoutBuilder &LayoutBuilder::finishRow() return *this; } -/*! - This starts a new row containing the \a item. The row can be further extended by - other items using \c addItem() or \c addItems(). - - \sa finishRow(), addItem(), addItems() - */ -LayoutBuilder &LayoutBuilder::addRow(const LayoutItem &item) -{ - return finishRow().addItem(item); -} - /*! This starts a new row containing \a items. The row can be further extended by other items using \c addItem() or \c addItems(). diff --git a/src/libs/utils/layoutbuilder.h b/src/libs/utils/layoutbuilder.h index d34a8f6cf4..b32668e22d 100644 --- a/src/libs/utils/layoutbuilder.h +++ b/src/libs/utils/layoutbuilder.h @@ -188,7 +188,6 @@ public: LayoutBuilder &addItems(const LayoutItems &items); LayoutBuilder &finishRow(); - LayoutBuilder &addRow(const LayoutItem &item); LayoutBuilder &addRow(const LayoutItems &items); LayoutType layoutType() const { return m_layoutType; } -- cgit v1.2.3 From 72d72251d84c0393eef8a30dab7839a66009dd1a Mon Sep 17 00:00:00 2001 From: hjk Date: Mon, 24 Apr 2023 16:40:01 +0200 Subject: Utils: Start decoupling LayoutBuilder from Aspects Makes it easier reusable elsewhere. Change-Id: I86ff9f40229a33690f854f5fda692bc06d6976ef Reviewed-by: Eike Ziller --- src/libs/utils/aspects.cpp | 9 ++- src/libs/utils/aspects.h | 8 ++- src/libs/utils/layoutbuilder.cpp | 77 +++++-------------------- src/libs/utils/layoutbuilder.h | 119 +++++++++++++++++++++++++-------------- 4 files changed, 106 insertions(+), 107 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index aed8c851d1..28de4dd4ca 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -421,10 +421,17 @@ QAction *BaseAspect::action() Adds the visual representation of this aspect to a layout using a layout builder. */ -void BaseAspect::addToLayout(Layouting::LayoutBuilder &) +void BaseAspect::addToLayout(LayoutBuilder &) { } +void doLayout(const BaseAspect &aspect, LayoutBuilder &builder) +{ + const_cast(aspect).addToLayout(builder); + if (builder.layoutType() == LayoutBuilder::FormLayout || builder.layoutType() == LayoutBuilder::VBoxLayout) + builder.finishRow(); +} + /*! Updates this aspect's value from user-initiated changes in the widget. diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index 0b9a560659..1e58c73773 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -25,7 +25,11 @@ namespace Utils { class AspectContainer; class BoolAspect; -namespace Layouting { class LayoutBuilder; } + +namespace Layouting { +class LayoutBuilder; +class LayoutItem; +} // Layouting namespace Internal { class AspectContainerPrivate; @@ -206,6 +210,8 @@ private: std::unique_ptr d; }; +QTCREATOR_UTILS_EXPORT void doLayout(const BaseAspect &aspect, Layouting::LayoutBuilder &builder); + class QTCREATOR_UTILS_EXPORT BoolAspect : public BaseAspect { Q_OBJECT diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index f4b1dda3ee..99b726ec4c 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -9,12 +9,12 @@ #include #include #include +#include #include #include #include #include #include -#include namespace Utils::Layouting { @@ -51,42 +51,23 @@ LayoutItem::LayoutItem() /*! - Constructs a layout item proxy for \a layout. - */ -LayoutItem::LayoutItem(QLayout *layout) - : layout(layout) -{} - -/*! - Constructs a layout item proxy for \a widget. - */ -LayoutItem::LayoutItem(QWidget *widget) - : widget(widget) -{} - -/*! - Constructs a layout item representing a \c BaseAspect. + \fn template LayoutItem(const T &t) - This ultimately uses the \a aspect's \c addToLayout(LayoutBuilder &) function, - which in turn can add one or more layout items to the target layout. + Constructs a layout item proxy for \a t. - \sa BaseAspect::addToLayout() + T could be + \list + \li \c {QString} + \li \c {QWidget *} + \li \c {QLayout *} + \endlist */ -LayoutItem::LayoutItem(BaseAspect &aspect) - : aspect(&aspect) -{} -LayoutItem::LayoutItem(BaseAspect *aspect) - : aspect(aspect) -{} /*! - Constructs a layout item containing some static \a text. + Constructs a layout item representing something that knows how to add it + to a layout by itself. */ -LayoutItem::LayoutItem(const QString &text) - : text(text) -{} - QLayout *LayoutBuilder::createLayout() const { QLayout *layout = nullptr; @@ -276,7 +257,7 @@ static void doLayoutHelper(QLayout *layout, /*! Constructs a layout item from the contents of another LayoutBuilder */ -LayoutItem::LayoutItem(const LayoutBuilder &builder) +void LayoutItem::setBuilder(const LayoutBuilder &builder) { layout = builder.createLayout(); doLayoutHelper(layout, builder.m_items, Layouting::WithoutMargins); @@ -357,10 +338,8 @@ LayoutBuilder &LayoutBuilder::addRow(const LayoutItems &items) */ LayoutBuilder &LayoutBuilder::addItem(const LayoutItem &item) { - if (item.aspect) { - item.aspect->addToLayout(*this); - if (m_layoutType == FormLayout || m_layoutType == VBoxLayout) - finishRow(); + if (item.onAdd) { + item.onAdd(*this); } else { m_items.push_back(item); } @@ -427,29 +406,6 @@ LayoutExtender::~LayoutExtender() // Special items -Break::Break() -{ - specialType = SpecialType::Break; -} - -Stretch::Stretch(int stretch) -{ - specialType = SpecialType::Stretch; - specialValue = stretch; -} - -Space::Space(int space) -{ - specialType = SpecialType::Space; - specialValue = space; -} - -Span::Span(int span_, const LayoutItem &item) -{ - LayoutItem::operator=(item); - span = span_; -} - Tab::Tab(const QString &tabName, const LayoutBuilder &item) { text = tabName; @@ -457,11 +413,6 @@ Tab::Tab(const QString &tabName, const LayoutBuilder &item) item.attachTo(widget); } -HorizontalRule::HorizontalRule() -{ - specialType = SpecialType::HorizontalRule; -} - // "Widgets" static void applyItems(QWidget *widget, const QList &items) diff --git a/src/libs/utils/layoutbuilder.h b/src/libs/utils/layoutbuilder.h index b32668e22d..76106ffc76 100644 --- a/src/libs/utils/layoutbuilder.h +++ b/src/libs/utils/layoutbuilder.h @@ -18,10 +18,7 @@ class QTabWidget; class QWidget; QT_END_NAMESPACE -namespace Utils { -class BaseAspect; -class BoolAspect; -} // Utils +namespace Utils { class BoolAspect; } namespace Utils::Layouting { @@ -32,6 +29,43 @@ enum AttachType { }; class LayoutBuilder; +class LayoutItem; + +// Special items + +class QTCREATOR_UTILS_EXPORT Space +{ +public: + explicit Space(int space) : space(space) {} + const int space; +}; + +class QTCREATOR_UTILS_EXPORT Stretch +{ +public: + explicit Stretch(int stretch = 1) : stretch(stretch) {} + const int stretch; +}; + +class QTCREATOR_UTILS_EXPORT Break +{ +public: + Break() {} +}; + +class QTCREATOR_UTILS_EXPORT Span +{ +public: + Span(int span, const LayoutItem &item) : span(span), item(item) {} + const int span; + const LayoutItem &item; +}; + +class QTCREATOR_UTILS_EXPORT HorizontalRule +{ +public: + HorizontalRule() {} +}; // LayoutItem @@ -52,18 +86,49 @@ public: }; using Setter = std::function; + using OnAdder = std::function; + LayoutItem(); - LayoutItem(QLayout *layout); - LayoutItem(QWidget *widget); - LayoutItem(BaseAspect *aspect); // Remove - LayoutItem(BaseAspect &aspect); - LayoutItem(const QString &text); - LayoutItem(const LayoutBuilder &builder); - LayoutItem(const Setter &setter) { this->setter = setter; } + + template LayoutItem(const T &t) + { + if constexpr (std::is_same_v) { + text = t; + } else if constexpr (std::is_same_v) { + specialType = LayoutItem::SpecialType::Space; + specialValue = t.space; + } else if constexpr (std::is_same_v) { + specialType = LayoutItem::SpecialType::Stretch; + specialValue = t.stretch; + } else if constexpr (std::is_same_v) { + specialType = LayoutItem::SpecialType::Break; + } else if constexpr (std::is_same_v) { + LayoutItem::operator=(t.item); + span = t.span; + } else if constexpr (std::is_same_v) { + specialType = SpecialType::HorizontalRule; + } else if constexpr (std::is_base_of_v) { + setBuilder(t); + } else if constexpr (std::is_base_of_v) { + LayoutItem::operator=(t); + } else if constexpr (std::is_base_of_v) { + setter = t; + } else if constexpr (std::is_base_of_v>) { + layout = t; + } else if constexpr (std::is_base_of_v>) { + widget = t; + } else if constexpr (std::is_pointer_v) { + onAdd = [t](LayoutBuilder &builder) { doLayout(*t, builder); }; + } else { + onAdd = [&t](LayoutBuilder &builder) { doLayout(t, builder); }; + } + } + + void setBuilder(const LayoutBuilder &builder); QLayout *layout = nullptr; QWidget *widget = nullptr; - BaseAspect *aspect = nullptr; + OnAdder onAdd; QString text; // FIXME: Use specialValue for that int span = 1; @@ -73,42 +138,12 @@ public: QVariant specialValue; }; -class QTCREATOR_UTILS_EXPORT Space : public LayoutItem -{ -public: - explicit Space(int space); -}; - -class QTCREATOR_UTILS_EXPORT Span : public LayoutItem -{ -public: - Span(int span, const LayoutItem &item); -}; - -class QTCREATOR_UTILS_EXPORT Stretch : public LayoutItem -{ -public: - explicit Stretch(int stretch = 1); -}; - class QTCREATOR_UTILS_EXPORT Tab : public LayoutItem { public: Tab(const QString &tabName, const LayoutBuilder &item); }; -class QTCREATOR_UTILS_EXPORT Break : public LayoutItem -{ -public: - Break(); -}; - -class QTCREATOR_UTILS_EXPORT HorizontalRule : public LayoutItem -{ -public: - HorizontalRule(); -}; - class QTCREATOR_UTILS_EXPORT Group : public LayoutItem { public: -- cgit v1.2.3 From 72bddf9f51fedd064f551bcb4ced5feeb46fdfc1 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Tue, 25 Apr 2023 14:07:03 +0200 Subject: PluginManager: Introduce global future synchronizer The global synchronizer will be destructed after the asynchronous shutdown phase and prior to deleting all the plugins (i.e. synchronously). It's assumed that between deleting the synchronizer and the point when all plugin destructors are finished no new futures are added to the global future synchronizer. Change-Id: Ibc839b04f2c2bbd35980b8baed51b29c2c4f7c76 Reviewed-by: Eike Ziller --- src/libs/extensionsystem/pluginmanager.cpp | 9 +++++++++ src/libs/extensionsystem/pluginmanager.h | 4 ++++ src/libs/extensionsystem/pluginmanager_p.h | 2 ++ 3 files changed, 15 insertions(+) (limited to 'src/libs') diff --git a/src/libs/extensionsystem/pluginmanager.cpp b/src/libs/extensionsystem/pluginmanager.cpp index aec053b6a8..9d478453ea 100644 --- a/src/libs/extensionsystem/pluginmanager.cpp +++ b/src/libs/extensionsystem/pluginmanager.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -422,6 +423,11 @@ QString PluginManager::systemInformation() return result; } +static FutureSynchronizer *futureSynchronizer() +{ + return d->m_futureSynchronizer.get(); +} + /*! The list of paths were the plugin manager searches for plugins. @@ -976,6 +982,8 @@ void PluginManagerPrivate::nextDelayedInitialize() PluginManagerPrivate::PluginManagerPrivate(PluginManager *pluginManager) : q(pluginManager) { + m_futureSynchronizer.reset(new FutureSynchronizer); + m_futureSynchronizer->setCancelOnWait(true); } @@ -1043,6 +1051,7 @@ void PluginManagerPrivate::stopAll() */ void PluginManagerPrivate::deleteAll() { + m_futureSynchronizer.reset(); // Synchronize all futures from all plugins Utils::reverseForeach(loadQueue(), [this](PluginSpec *spec) { loadPlugin(spec, PluginSpec::Deleted); }); diff --git a/src/libs/extensionsystem/pluginmanager.h b/src/libs/extensionsystem/pluginmanager.h index 8dac9544cc..a56cb7ba9d 100644 --- a/src/libs/extensionsystem/pluginmanager.h +++ b/src/libs/extensionsystem/pluginmanager.h @@ -15,6 +15,8 @@ QT_BEGIN_NAMESPACE class QTextStream; QT_END_NAMESPACE +namespace Utils { class FutureSynchronizer; } + namespace ExtensionSystem { class IPlugin; class PluginSpec; @@ -133,6 +135,8 @@ public: static QString systemInformation(); + static Utils::FutureSynchronizer *futureSynchronizer(); + signals: void objectAdded(QObject *obj); void aboutToRemoveObject(QObject *obj); diff --git a/src/libs/extensionsystem/pluginmanager_p.h b/src/libs/extensionsystem/pluginmanager_p.h index decd627177..86c3a6c362 100644 --- a/src/libs/extensionsystem/pluginmanager_p.h +++ b/src/libs/extensionsystem/pluginmanager_p.h @@ -26,6 +26,7 @@ class QEventLoop; QT_END_NAMESPACE namespace Utils { +class FutureSynchronizer; class QtcSettings; } @@ -133,6 +134,7 @@ public: QWaitCondition m_scenarioWaitCondition; PluginManager::ProcessData m_creatorProcessData; + std::unique_ptr m_futureSynchronizer; private: PluginManager *q; -- cgit v1.2.3 From 1fe09a00d7bdefc2e7a675f265797739a4d5986a Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Mon, 24 Apr 2023 14:24:48 +0200 Subject: FSEngine: Fix thread safety Change-Id: I5223cef1a70ffcb92e886733af2b1d8061c4dbf0 Reviewed-by: Reviewed-by: Qt CI Bot Reviewed-by: Eike Ziller --- src/libs/utils/fsengine/fsengine.cpp | 51 ++++++++++++++++++++--------- src/libs/utils/fsengine/fsengine.h | 4 --- src/libs/utils/fsengine/fsenginehandler.cpp | 2 +- 3 files changed, 37 insertions(+), 20 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/fsengine/fsengine.cpp b/src/libs/utils/fsengine/fsengine.cpp index cc622b8786..fd343aaad1 100644 --- a/src/libs/utils/fsengine/fsengine.cpp +++ b/src/libs/utils/fsengine/fsengine.cpp @@ -10,6 +10,8 @@ class Utils::Internal::FSEngineHandler {}; #endif +#include + #include namespace Utils { @@ -29,46 +31,65 @@ bool FSEngine::isAvailable() #endif } -FilePaths FSEngine::registeredDeviceRoots() +template +class Locked +{ +public: + Locked(QMutex *mutex, T &object) + : m_object(object) + , m_locker(mutex) + {} + + T *operator->() const noexcept { return &m_object; } + const T operator*() const noexcept { return m_object; } + +private: + T &m_object; + QMutexLocker m_locker; +}; + +static Locked deviceRoots() { - return FSEngine::deviceRoots(); + static FilePaths g_deviceRoots; + static QMutex mutex; + return {&mutex, g_deviceRoots}; } -void FSEngine::addDevice(const FilePath &deviceRoot) +static Locked deviceSchemes() { - deviceRoots().append(deviceRoot); + static QStringList g_deviceSchemes{"device"}; + static QMutex mutex; + return {&mutex, g_deviceSchemes}; } -void FSEngine::removeDevice(const FilePath &deviceRoot) +FilePaths FSEngine::registeredDeviceRoots() { - deviceRoots().removeAll(deviceRoot); + return *deviceRoots(); } -FilePaths &FSEngine::deviceRoots() +void FSEngine::addDevice(const FilePath &deviceRoot) { - static FilePaths g_deviceRoots; - return g_deviceRoots; + deviceRoots()->append(deviceRoot); } -QStringList &FSEngine::deviceSchemes() +void FSEngine::removeDevice(const FilePath &deviceRoot) { - static QStringList g_deviceSchemes{"device"}; - return g_deviceSchemes; + deviceRoots()->removeAll(deviceRoot); } void FSEngine::registerDeviceScheme(const QStringView scheme) { - deviceSchemes().append(scheme.toString()); + deviceSchemes()->append(scheme.toString()); } void FSEngine::unregisterDeviceScheme(const QStringView scheme) { - deviceSchemes().removeAll(scheme.toString()); + deviceSchemes()->removeAll(scheme.toString()); } QStringList FSEngine::registeredDeviceSchemes() { - return FSEngine::deviceSchemes(); + return *deviceSchemes(); } } // namespace Utils diff --git a/src/libs/utils/fsengine/fsengine.h b/src/libs/utils/fsengine/fsengine.h index 52c03e9cee..4c486ead6e 100644 --- a/src/libs/utils/fsengine/fsengine.h +++ b/src/libs/utils/fsengine/fsengine.h @@ -33,10 +33,6 @@ public: static void unregisterDeviceScheme(const QStringView scheme); static QStringList registeredDeviceSchemes(); -private: - static Utils::FilePaths &deviceRoots(); - static QStringList &deviceSchemes(); - private: std::unique_ptr m_engineHandler; }; diff --git a/src/libs/utils/fsengine/fsenginehandler.cpp b/src/libs/utils/fsengine/fsenginehandler.cpp index c5fa71e786..e020eebdda 100644 --- a/src/libs/utils/fsengine/fsenginehandler.cpp +++ b/src/libs/utils/fsengine/fsenginehandler.cpp @@ -56,7 +56,7 @@ QAbstractFileEngine *FSEngineHandler::create(const QString &fileName) const const QStringList deviceSchemes = FSEngine::registeredDeviceSchemes(); for (const QString &scheme : deviceSchemes) { if (fixedFileName == rootFilePath.pathAppended(scheme).toString()) { - const FilePaths filteredRoots = Utils::filtered(FSEngine::deviceRoots(), + const FilePaths filteredRoots = Utils::filtered(FSEngine::registeredDeviceRoots(), [scheme](const FilePath &root) { return root.scheme() == scheme; }); -- cgit v1.2.3 From 8cf500c5bc2d2171f80f392d325ddcf5107e184c Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 25 Apr 2023 10:15:07 +0200 Subject: Utils: Make Layouting a top level namespace The whole machinery is now almost only layoutbuilder.{h,cpp}, mostly independent of the rest of Utils. Idea is to finish the separation to make it stand-alone usable also outside creator. Change-Id: I958aa667d17ae26b21209f22412309c5307a579c Reviewed-by: Eike Ziller Reviewed-by: Alessandro Portale --- src/libs/advanceddockingsystem/workspacedialog.cpp | 4 ++-- src/libs/extensionsystem/plugindetailsview.cpp | 2 +- src/libs/extensionsystem/pluginerroroverview.cpp | 2 +- src/libs/extensionsystem/pluginerrorview.cpp | 2 +- src/libs/qmleditorwidgets/contextpanetextwidget.cpp | 2 +- src/libs/qmleditorwidgets/contextpanewidgetimage.cpp | 4 ++-- src/libs/qmleditorwidgets/contextpanewidgetrectangle.cpp | 2 +- .../qmleditorwidgets/easingpane/easingcontextpane.cpp | 2 +- src/libs/utils/aspects.cpp | 2 +- src/libs/utils/aspects.h | 10 +++++----- src/libs/utils/layoutbuilder.cpp | 6 +++--- src/libs/utils/layoutbuilder.h | 15 ++++++++++----- 12 files changed, 29 insertions(+), 24 deletions(-) (limited to 'src/libs') diff --git a/src/libs/advanceddockingsystem/workspacedialog.cpp b/src/libs/advanceddockingsystem/workspacedialog.cpp index e4f92d92ba..e99ec6946a 100644 --- a/src/libs/advanceddockingsystem/workspacedialog.cpp +++ b/src/libs/advanceddockingsystem/workspacedialog.cpp @@ -79,7 +79,7 @@ WorkspaceNameInputDialog::WorkspaceNameInputDialog(DockManager *manager, QWidget connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); - using namespace Utils::Layouting; + using namespace Layouting; Column { label, @@ -137,7 +137,7 @@ WorkspaceDialog::WorkspaceDialog(DockManager *manager, QWidget *parent) connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); - using namespace Utils::Layouting; + using namespace Layouting; Column { Row { diff --git a/src/libs/extensionsystem/plugindetailsview.cpp b/src/libs/extensionsystem/plugindetailsview.cpp index 8242d8dac6..8fd9e7b5ef 100644 --- a/src/libs/extensionsystem/plugindetailsview.cpp +++ b/src/libs/extensionsystem/plugindetailsview.cpp @@ -53,7 +53,7 @@ public: , license(createTextEdit()) , dependencies(new QListWidget(q)) { - using namespace Utils::Layouting; + using namespace Layouting; // clang-format off Form { diff --git a/src/libs/extensionsystem/pluginerroroverview.cpp b/src/libs/extensionsystem/pluginerroroverview.cpp index 20e6f5ac90..69562e504d 100644 --- a/src/libs/extensionsystem/pluginerroroverview.cpp +++ b/src/libs/extensionsystem/pluginerroroverview.cpp @@ -42,7 +42,7 @@ PluginErrorOverview::PluginErrorOverview(QWidget *parent) QObject::connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); QObject::connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - using namespace Utils::Layouting; + using namespace Layouting; auto createLabel = [this](const QString &text) { QLabel *label = new QLabel(text, this); diff --git a/src/libs/extensionsystem/pluginerrorview.cpp b/src/libs/extensionsystem/pluginerrorview.cpp index fac7fd9a42..42374b0376 100644 --- a/src/libs/extensionsystem/pluginerrorview.cpp +++ b/src/libs/extensionsystem/pluginerrorview.cpp @@ -41,7 +41,7 @@ public: errorString->setTabChangesFocus(true); errorString->setReadOnly(true); - using namespace Utils::Layouting; + using namespace Layouting; Form { Tr::tr("State:"), state, br, diff --git a/src/libs/qmleditorwidgets/contextpanetextwidget.cpp b/src/libs/qmleditorwidgets/contextpanetextwidget.cpp index 1a7d527294..a7f9e31f02 100644 --- a/src/libs/qmleditorwidgets/contextpanetextwidget.cpp +++ b/src/libs/qmleditorwidgets/contextpanetextwidget.cpp @@ -80,7 +80,7 @@ ContextPaneTextWidget::ContextPaneTextWidget(QWidget *parent) : vAlignButtons->addButton(m_centerVAlignmentButton); vAlignButtons->addButton(m_bottomAlignmentButton); - using namespace Utils::Layouting; + using namespace Layouting; Column { Row { m_fontComboBox, m_colorButton, m_fontSizeSpinBox, }, Row { diff --git a/src/libs/qmleditorwidgets/contextpanewidgetimage.cpp b/src/libs/qmleditorwidgets/contextpanewidgetimage.cpp index f4ba3a6a5c..12ec4da3d5 100644 --- a/src/libs/qmleditorwidgets/contextpanewidgetimage.cpp +++ b/src/libs/qmleditorwidgets/contextpanewidgetimage.cpp @@ -97,7 +97,7 @@ ContextPaneWidgetImage::ContextPaneWidgetImage(QWidget *parent, bool borderImage vRadioButtons->addButton(m_borderImage.verticalStretchRadioButton); vRadioButtons->addButton(m_borderImage.verticalTileRadioButtonNoCrop); - using namespace Utils::Layouting; + using namespace Layouting; Row { Column { m_previewLabel, m_sizeLabel, }, Column { @@ -146,7 +146,7 @@ ContextPaneWidgetImage::ContextPaneWidgetImage(QWidget *parent, bool borderImage m_image.cropAspectFitRadioButton = radioButton("aspect-crop-icon", Tr::tr("The image is scaled uniformly to fill, cropping if necessary.")); - using namespace Utils::Layouting; + using namespace Layouting; Row { Column { m_previewLabel, m_sizeLabel, }, Column { diff --git a/src/libs/qmleditorwidgets/contextpanewidgetrectangle.cpp b/src/libs/qmleditorwidgets/contextpanewidgetrectangle.cpp index 52afeff187..0a8c1242e5 100644 --- a/src/libs/qmleditorwidgets/contextpanewidgetrectangle.cpp +++ b/src/libs/qmleditorwidgets/contextpanewidgetrectangle.cpp @@ -56,7 +56,7 @@ ContextPaneWidgetRectangle::ContextPaneWidgetRectangle(QWidget *parent) borderButtons->addButton(m_borderSolid); borderButtons->addButton(m_borderNone); - using namespace Utils::Layouting; + using namespace Layouting; Grid { m_gradientLabel, m_gradientLine, br, Tr::tr("Color"), Row { m_colorColorButton, m_colorSolid, m_colorGradient, m_colorNone, st, }, br, diff --git a/src/libs/qmleditorwidgets/easingpane/easingcontextpane.cpp b/src/libs/qmleditorwidgets/easingpane/easingcontextpane.cpp index e61f3c2592..c35f6d792c 100644 --- a/src/libs/qmleditorwidgets/easingpane/easingcontextpane.cpp +++ b/src/libs/qmleditorwidgets/easingpane/easingcontextpane.cpp @@ -145,7 +145,7 @@ EasingContextPane::EasingContextPane(QWidget *parent) spinBox->setMaximum(999999.9); } - using namespace Utils::Layouting; + using namespace Layouting; Column { Row { m_graphicsView, m_playButton, }, Row { diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index 28de4dd4ca..ea4a2f1848 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -30,7 +30,7 @@ #include #include -using namespace Utils::Layouting; +using namespace Layouting; namespace Utils { namespace Internal { diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index 1e58c73773..81a67eb5d5 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -21,16 +21,16 @@ class QGroupBox; class QSettings; QT_END_NAMESPACE -namespace Utils { - -class AspectContainer; -class BoolAspect; - namespace Layouting { class LayoutBuilder; class LayoutItem; } // Layouting +namespace Utils { + +class AspectContainer; +class BoolAspect; + namespace Internal { class AspectContainerPrivate; class BaseAspectPrivate; diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index 99b726ec4c..12eb4003b9 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -16,7 +16,7 @@ #include #include -namespace Utils::Layouting { +namespace Layouting { /*! \enum Utils::LayoutBuilder::LayoutType @@ -464,7 +464,7 @@ TabWidget::TabWidget(QTabWidget *tabWidget, std::initializer_list tabs) // "Properties" -LayoutItem::Setter title(const QString &title, BoolAspect *checker) +LayoutItem::Setter title(const QString &title, Utils::BoolAspect *checker) { return [title, checker](QObject *target) { if (auto groupBox = qobject_cast(target)) { @@ -528,4 +528,4 @@ Stretch st; Space empty(0); HorizontalRule hr; -} // Utils::Layouting +} // Layouting diff --git a/src/libs/utils/layoutbuilder.h b/src/libs/utils/layoutbuilder.h index 76106ffc76..74b4ff3e0a 100644 --- a/src/libs/utils/layoutbuilder.h +++ b/src/libs/utils/layoutbuilder.h @@ -3,14 +3,19 @@ #pragma once -#include "utils_global.h" - #include #include #include +#include #include +#if defined(UTILS_LIBRARY) +# define QTCREATOR_UTILS_EXPORT Q_DECL_EXPORT +#else +# define QTCREATOR_UTILS_EXPORT Q_DECL_IMPORT +#endif + QT_BEGIN_NAMESPACE class QLayout; class QSplitter; @@ -20,7 +25,7 @@ QT_END_NAMESPACE namespace Utils { class BoolAspect; } -namespace Utils::Layouting { +namespace Layouting { enum AttachType { WithMargins, @@ -180,7 +185,7 @@ QTCREATOR_UTILS_EXPORT extern HorizontalRule hr; // "Properties" QTCREATOR_UTILS_EXPORT LayoutItem::Setter title(const QString &title, - BoolAspect *checker = nullptr); + Utils::BoolAspect *checker = nullptr); QTCREATOR_UTILS_EXPORT LayoutItem::Setter text(const QString &text); QTCREATOR_UTILS_EXPORT LayoutItem::Setter tooltip(const QString &toolTip); @@ -289,4 +294,4 @@ public: Stack(std::initializer_list items) : LayoutBuilder(StackLayout, items) {} }; -} // Utils::Layouting +} // Layouting -- cgit v1.2.3 From 65d704854e97c7dbec1ab4df20c2c65acbf40d5d Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Tue, 25 Apr 2023 16:02:35 +0200 Subject: PluginManager: Fix an obvious linkage error Amends 72bddf9f51fedd064f551bcb4ced5feeb46fdfc1 Change-Id: I238bea72821252f37ebba02903ca68de8633500e Reviewed-by: Eike Ziller --- src/libs/extensionsystem/pluginmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/extensionsystem/pluginmanager.cpp b/src/libs/extensionsystem/pluginmanager.cpp index 9d478453ea..cb0323798b 100644 --- a/src/libs/extensionsystem/pluginmanager.cpp +++ b/src/libs/extensionsystem/pluginmanager.cpp @@ -423,7 +423,7 @@ QString PluginManager::systemInformation() return result; } -static FutureSynchronizer *futureSynchronizer() +static FutureSynchronizer *PluginManager::futureSynchronizer() { return d->m_futureSynchronizer.get(); } -- cgit v1.2.3 From fe3887e2bf41efb4d62d62b2e94e81471323b95b Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 25 Apr 2023 16:09:12 +0200 Subject: ExtensionsSystem: Future #3 Change-Id: Ie1e8230ac044b6c79e1125fb39bab0e68d0d6e6d Reviewed-by: Jarek Kobus --- src/libs/extensionsystem/pluginmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/extensionsystem/pluginmanager.cpp b/src/libs/extensionsystem/pluginmanager.cpp index cb0323798b..588afe661d 100644 --- a/src/libs/extensionsystem/pluginmanager.cpp +++ b/src/libs/extensionsystem/pluginmanager.cpp @@ -423,7 +423,7 @@ QString PluginManager::systemInformation() return result; } -static FutureSynchronizer *PluginManager::futureSynchronizer() +FutureSynchronizer *PluginManager::futureSynchronizer() { return d->m_futureSynchronizer.get(); } -- cgit v1.2.3 From b4a6af026e5bce2340a900a92250e61d9205e511 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 18 Apr 2023 13:36:45 +0200 Subject: Utils: Remove displayName from SettingsAccessors It was not used. Change-Id: I7c0927698bf31548ec076c39881561d72e8495e6 Reviewed-by: Christian Stenger --- src/libs/utils/settingsaccessor.cpp | 24 +++++++----------------- src/libs/utils/settingsaccessor.h | 16 ++++++---------- 2 files changed, 13 insertions(+), 27 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/settingsaccessor.cpp b/src/libs/utils/settingsaccessor.cpp index dc3cd0711e..24f922bf98 100644 --- a/src/libs/utils/settingsaccessor.cpp +++ b/src/libs/utils/settingsaccessor.cpp @@ -39,15 +39,10 @@ QMessageBox::StandardButtons SettingsAccessor::Issue::allButtons() const /*! * The SettingsAccessor can be used to read/write settings in XML format. */ -SettingsAccessor::SettingsAccessor(const QString &docType, - const QString &displayName, - const QString &applicationDisplayName) : -docType(docType), -displayName(displayName), -applicationDisplayName(applicationDisplayName) +SettingsAccessor::SettingsAccessor(const QString &docType, const QString &applicationDisplayName) + : docType(docType), applicationDisplayName(applicationDisplayName) { QTC_CHECK(!docType.isEmpty()); - QTC_CHECK(!displayName.isEmpty()); QTC_CHECK(!applicationDisplayName.isEmpty()); } @@ -227,16 +222,14 @@ std::optional BackUpStrategy::backupName(const QVariantMap &oldData, } BackingUpSettingsAccessor::BackingUpSettingsAccessor(const QString &docType, - const QString &displayName, const QString &applicationDisplayName) : - BackingUpSettingsAccessor(std::make_unique(), docType, displayName, applicationDisplayName) + BackingUpSettingsAccessor(std::make_unique(), docType, applicationDisplayName) { } BackingUpSettingsAccessor::BackingUpSettingsAccessor(std::unique_ptr &&strategy, const QString &docType, - const QString &displayName, const QString &applicationDisplayName) : - SettingsAccessor(docType, displayName, applicationDisplayName), + SettingsAccessor(docType, applicationDisplayName), m_strategy(std::move(strategy)) { } @@ -411,17 +404,15 @@ QVariantMap VersionUpgrader::renameKeys(const QList &changes, QVariantMa * upgrade the settings on load to the latest supported version (if possible). */ UpgradingSettingsAccessor::UpgradingSettingsAccessor(const QString &docType, - const QString &displayName, const QString &applicationDisplayName) : UpgradingSettingsAccessor(std::make_unique(this), docType, - displayName, applicationDisplayName) + applicationDisplayName) { } UpgradingSettingsAccessor::UpgradingSettingsAccessor(std::unique_ptr &&strategy, const QString &docType, - const QString &displayName, const QString &applicationDisplayName) : - BackingUpSettingsAccessor(std::move(strategy), docType, displayName, applicationDisplayName) + BackingUpSettingsAccessor(std::move(strategy), docType, applicationDisplayName) { } int UpgradingSettingsAccessor::currentVersion() const @@ -579,9 +570,8 @@ UpgradingSettingsAccessor::validateVersionRange(const RestoreData &data) const */ MergingSettingsAccessor::MergingSettingsAccessor(std::unique_ptr &&strategy, const QString &docType, - const QString &displayName, const QString &applicationDisplayName) : - UpgradingSettingsAccessor(std::move(strategy), docType, displayName, applicationDisplayName) + UpgradingSettingsAccessor(std::move(strategy), docType, applicationDisplayName) { } SettingsAccessor::RestoreData MergingSettingsAccessor::readData(const FilePath &path, diff --git a/src/libs/utils/settingsaccessor.h b/src/libs/utils/settingsaccessor.h index f1bbb3594e..0c0af48500 100644 --- a/src/libs/utils/settingsaccessor.h +++ b/src/libs/utils/settingsaccessor.h @@ -51,8 +51,7 @@ using SettingsMergeResult = std::optional>; class QTCREATOR_UTILS_EXPORT SettingsAccessor { public: - SettingsAccessor(const QString &docType, const QString &displayName, - const QString &applicationDisplayName); + SettingsAccessor(const QString &docType, const QString &applicationDisplayName); virtual ~SettingsAccessor(); enum ProceedInfo { Continue, DiscardAndContinue }; @@ -96,7 +95,6 @@ public: bool saveSettings(const QVariantMap &data, QWidget *parent) const; const QString docType; - const QString displayName; const QString applicationDisplayName; void setBaseFilePath(const FilePath &baseFilePath) { m_baseFilePath = baseFilePath; } @@ -148,10 +146,9 @@ public: class QTCREATOR_UTILS_EXPORT BackingUpSettingsAccessor : public SettingsAccessor { public: - BackingUpSettingsAccessor(const QString &docType, const QString &displayName, - const QString &applicationDisplayName); + BackingUpSettingsAccessor(const QString &docType, const QString &applicationDisplayName); BackingUpSettingsAccessor(std::unique_ptr &&strategy, const QString &docType, - const QString &displayName, const QString &applicationDisplayName); + const QString &applicationDisplayName); RestoreData readData(const FilePath &path, QWidget *parent) const override; std::optional writeData(const FilePath &path, @@ -220,10 +217,9 @@ class MergingSettingsAccessor; class QTCREATOR_UTILS_EXPORT UpgradingSettingsAccessor : public BackingUpSettingsAccessor { public: - UpgradingSettingsAccessor(const QString &docType, - const QString &displayName, const QString &applicationDisplayName); + UpgradingSettingsAccessor(const QString &docType, const QString &applicationDisplayName); UpgradingSettingsAccessor(std::unique_ptr &&strategy, const QString &docType, - const QString &displayName, const QString &appDisplayName); + const QString &appDisplayName); int currentVersion() const; int firstSupportedVersion() const; @@ -264,7 +260,7 @@ public: }; MergingSettingsAccessor(std::unique_ptr &&strategy, - const QString &docType, const QString &displayName, + const QString &docType, const QString &applicationDisplayName); RestoreData readData(const FilePath &path, QWidget *parent) const final; -- cgit v1.2.3 From aeac83af7813f6ca5c67281c6cd91fc77351830e Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 18 Apr 2023 14:14:39 +0200 Subject: Utils: Collapse the two SettingsAccessor constructor hierarchies Change-Id: I7b659c9e3c66700cfc92f9cfbd9df0535a1ca655 Reviewed-by: Reviewed-by: Christian Stenger --- src/libs/utils/settingsaccessor.cpp | 31 ++++++++++++------------------- src/libs/utils/settingsaccessor.h | 9 ++------- 2 files changed, 14 insertions(+), 26 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/settingsaccessor.cpp b/src/libs/utils/settingsaccessor.cpp index 24f922bf98..0562f5cf98 100644 --- a/src/libs/utils/settingsaccessor.cpp +++ b/src/libs/utils/settingsaccessor.cpp @@ -223,14 +223,8 @@ std::optional BackUpStrategy::backupName(const QVariantMap &oldData, BackingUpSettingsAccessor::BackingUpSettingsAccessor(const QString &docType, const QString &applicationDisplayName) : - BackingUpSettingsAccessor(std::make_unique(), docType, applicationDisplayName) -{ } - -BackingUpSettingsAccessor::BackingUpSettingsAccessor(std::unique_ptr &&strategy, - const QString &docType, - const QString &applicationDisplayName) : SettingsAccessor(docType, applicationDisplayName), - m_strategy(std::move(strategy)) + m_strategy(std::make_unique()) { } SettingsAccessor::RestoreData @@ -272,6 +266,11 @@ std::optional BackingUpSettingsAccessor::writeData(cons return SettingsAccessor::writeData(path, data, parent); } +void BackingUpSettingsAccessor::setStrategy(std::unique_ptr &&strategy) +{ + m_strategy = std::move(strategy); +} + FilePaths BackingUpSettingsAccessor::readFileCandidates(const FilePath &path) const { FilePaths result = filteredUnique(m_strategy->readFileCandidates(path)); @@ -405,15 +404,10 @@ QVariantMap VersionUpgrader::renameKeys(const QList &changes, QVariantMa */ UpgradingSettingsAccessor::UpgradingSettingsAccessor(const QString &docType, const QString &applicationDisplayName) : - UpgradingSettingsAccessor(std::make_unique(this), docType, - applicationDisplayName) -{ } - -UpgradingSettingsAccessor::UpgradingSettingsAccessor(std::unique_ptr &&strategy, - const QString &docType, - const QString &applicationDisplayName) : - BackingUpSettingsAccessor(std::move(strategy), docType, applicationDisplayName) -{ } + BackingUpSettingsAccessor(docType, applicationDisplayName) +{ + setStrategy(std::make_unique(this)); +} int UpgradingSettingsAccessor::currentVersion() const { @@ -568,10 +562,9 @@ UpgradingSettingsAccessor::validateVersionRange(const RestoreData &data) const * MergingSettingsAccessor allows to merge secondary settings into the main settings. * This is useful to e.g. handle .shared files together with .user files. */ -MergingSettingsAccessor::MergingSettingsAccessor(std::unique_ptr &&strategy, - const QString &docType, +MergingSettingsAccessor::MergingSettingsAccessor(const QString &docType, const QString &applicationDisplayName) : - UpgradingSettingsAccessor(std::move(strategy), docType, applicationDisplayName) + UpgradingSettingsAccessor(docType, applicationDisplayName) { } SettingsAccessor::RestoreData MergingSettingsAccessor::readData(const FilePath &path, diff --git a/src/libs/utils/settingsaccessor.h b/src/libs/utils/settingsaccessor.h index 0c0af48500..5ad37bd59c 100644 --- a/src/libs/utils/settingsaccessor.h +++ b/src/libs/utils/settingsaccessor.h @@ -147,8 +147,6 @@ class QTCREATOR_UTILS_EXPORT BackingUpSettingsAccessor : public SettingsAccessor { public: BackingUpSettingsAccessor(const QString &docType, const QString &applicationDisplayName); - BackingUpSettingsAccessor(std::unique_ptr &&strategy, const QString &docType, - const QString &applicationDisplayName); RestoreData readData(const FilePath &path, QWidget *parent) const override; std::optional writeData(const FilePath &path, @@ -156,6 +154,7 @@ public: QWidget *parent) const override; BackUpStrategy *strategy() const { return m_strategy.get(); } + void setStrategy(std::unique_ptr &&strategy); private: FilePaths readFileCandidates(const FilePath &path) const; @@ -218,8 +217,6 @@ class QTCREATOR_UTILS_EXPORT UpgradingSettingsAccessor : public BackingUpSetting { public: UpgradingSettingsAccessor(const QString &docType, const QString &applicationDisplayName); - UpgradingSettingsAccessor(std::unique_ptr &&strategy, const QString &docType, - const QString &appDisplayName); int currentVersion() const; int firstSupportedVersion() const; @@ -259,9 +256,7 @@ public: QString key; }; - MergingSettingsAccessor(std::unique_ptr &&strategy, - const QString &docType, - const QString &applicationDisplayName); + MergingSettingsAccessor(const QString &docType, const QString &applicationDisplayName); RestoreData readData(const FilePath &path, QWidget *parent) const final; -- cgit v1.2.3 From 3edc491b2ab5b2177503cf58e7d04c6ef7f61080 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 18 Apr 2023 14:36:46 +0200 Subject: Utils: Simplify SettingsAccessor constructors Use setters instead. Change-Id: I912234fa832eeb80519740ca011921f1e71d70b4 Reviewed-by: Christian Stenger --- src/libs/utils/settingsaccessor.cpp | 45 ++++++++++++++++--------------------- src/libs/utils/settingsaccessor.h | 17 ++++++++------ 2 files changed, 29 insertions(+), 33 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/settingsaccessor.cpp b/src/libs/utils/settingsaccessor.cpp index 0562f5cf98..275290674f 100644 --- a/src/libs/utils/settingsaccessor.cpp +++ b/src/libs/utils/settingsaccessor.cpp @@ -39,12 +39,7 @@ QMessageBox::StandardButtons SettingsAccessor::Issue::allButtons() const /*! * The SettingsAccessor can be used to read/write settings in XML format. */ -SettingsAccessor::SettingsAccessor(const QString &docType, const QString &applicationDisplayName) - : docType(docType), applicationDisplayName(applicationDisplayName) -{ - QTC_CHECK(!docType.isEmpty()); - QTC_CHECK(!applicationDisplayName.isEmpty()); -} +SettingsAccessor::SettingsAccessor() = default; SettingsAccessor::~SettingsAccessor() = default; @@ -63,6 +58,9 @@ QVariantMap SettingsAccessor::restoreSettings(QWidget *parent) const */ bool SettingsAccessor::saveSettings(const QVariantMap &data, QWidget *parent) const { + QTC_CHECK(!m_docType.isEmpty()); + QTC_CHECK(!m_applicationDisplayName.isEmpty()); + const std::optional result = writeData(m_baseFilePath, data, parent); const ProceedInfo pi = result ? reportIssues(result.value(), m_baseFilePath, parent) : ProceedInfo::Continue; @@ -94,6 +92,9 @@ std::optional SettingsAccessor::writeData(const FilePat QVariantMap SettingsAccessor::restoreSettings(const FilePath &settingsPath, QWidget *parent) const { + QTC_CHECK(!m_docType.isEmpty()); + QTC_CHECK(!m_applicationDisplayName.isEmpty()); + const RestoreData result = readData(settingsPath, parent); const ProceedInfo pi = result.hasIssue() ? reportIssues(result.issue.value(), result.path, parent) @@ -118,7 +119,7 @@ SettingsAccessor::RestoreData SettingsAccessor::readFile(const FilePath &path) c const QVariantMap data = reader.restoreValues(); if (!m_readOnly && path == m_baseFilePath) { if (!m_writer) - m_writer = std::make_unique(m_baseFilePath, docType); + m_writer = std::make_unique(m_baseFilePath, m_docType); m_writer->setContents(data); } @@ -141,7 +142,7 @@ std::optional SettingsAccessor::writeFile(const FilePat QString errorMessage; if (!m_readOnly && (!m_writer || m_writer->fileName() != path)) - m_writer = std::make_unique(path, docType); + m_writer = std::make_unique(path, m_docType); if (!m_writer->save(data, &errorMessage)) { return Issue(Tr::tr("Failed to Write File"), @@ -151,8 +152,7 @@ std::optional SettingsAccessor::writeFile(const FilePat } SettingsAccessor::ProceedInfo -SettingsAccessor::reportIssues(const SettingsAccessor::Issue &issue, const FilePath &path, - QWidget *parent) +SettingsAccessor::reportIssues(const Issue &issue, const FilePath &path, QWidget *parent) { if (!path.exists()) return Continue; @@ -221,10 +221,8 @@ std::optional BackUpStrategy::backupName(const QVariantMap &oldData, return path.stringAppended(".bak"); } -BackingUpSettingsAccessor::BackingUpSettingsAccessor(const QString &docType, - const QString &applicationDisplayName) : - SettingsAccessor(docType, applicationDisplayName), - m_strategy(std::make_unique()) +BackingUpSettingsAccessor::BackingUpSettingsAccessor() + : m_strategy(std::make_unique()) { } SettingsAccessor::RestoreData @@ -246,7 +244,7 @@ BackingUpSettingsAccessor::readData(const FilePath &path, QWidget *parent) const "for instance because they were written by an incompatible " "version of %2, or because a different settings path " "was used.

") - .arg(path.toUserOutput(), applicationDisplayName), Issue::Type::ERROR); + .arg(path.toUserOutput(), m_applicationDisplayName), Issue::Type::ERROR); i.buttons.insert(QMessageBox::Ok, DiscardAndContinue); result.issue = i; } @@ -402,9 +400,7 @@ QVariantMap VersionUpgrader::renameKeys(const QList &changes, QVariantMa * The UpgradingSettingsAccessor keeps version information in the settings file and will * upgrade the settings on load to the latest supported version (if possible). */ -UpgradingSettingsAccessor::UpgradingSettingsAccessor(const QString &docType, - const QString &applicationDisplayName) : - BackingUpSettingsAccessor(docType, applicationDisplayName) +UpgradingSettingsAccessor::UpgradingSettingsAccessor() { setStrategy(std::make_unique(this)); } @@ -526,7 +522,7 @@ UpgradingSettingsAccessor::validateVersionRange(const RestoreData &data) const "version of %2 was used are ignored, and " "changes made now will not be propagated to " "the newer version.

") - .arg(result.path.toUserOutput(), applicationDisplayName), Issue::Type::WARNING); + .arg(result.path.toUserOutput(), m_applicationDisplayName), Issue::Type::WARNING); i.buttons.insert(QMessageBox::Ok, Continue); result.issue = i; return result; @@ -535,13 +531,13 @@ UpgradingSettingsAccessor::validateVersionRange(const RestoreData &data) const const QByteArray readId = settingsIdFromMap(result.data); if (!settingsId().isEmpty() && !readId.isEmpty() && readId != settingsId()) { Issue i(Tr::tr("Settings File for \"%1\" from a Different Environment?") - .arg(applicationDisplayName), + .arg(m_applicationDisplayName), Tr::tr("

No settings file created by this instance " "of %1 was found.

" "

Did you work with this project on another machine or " "using a different settings path before?

" "

Do you still want to load the settings file \"%2\"?

") - .arg(applicationDisplayName, result.path.toUserOutput()), Issue::Type::WARNING); + .arg(m_applicationDisplayName, result.path.toUserOutput()), Issue::Type::WARNING); i.defaultButton = QMessageBox::No; i.escapeButton = QMessageBox::No; i.buttons.clear(); @@ -562,10 +558,7 @@ UpgradingSettingsAccessor::validateVersionRange(const RestoreData &data) const * MergingSettingsAccessor allows to merge secondary settings into the main settings. * This is useful to e.g. handle .shared files together with .user files. */ -MergingSettingsAccessor::MergingSettingsAccessor(const QString &docType, - const QString &applicationDisplayName) : - UpgradingSettingsAccessor(docType, applicationDisplayName) -{ } +MergingSettingsAccessor::MergingSettingsAccessor() = default; SettingsAccessor::RestoreData MergingSettingsAccessor::readData(const FilePath &path, QWidget *parent) const @@ -597,7 +590,7 @@ SettingsAccessor::RestoreData MergingSettingsAccessor::readData(const FilePath & secondaryData.issue = Issue(Tr::tr("Unsupported Merge Settings File"), Tr::tr("\"%1\" is not supported by %2. " "Do you want to try loading it anyway?") - .arg(secondaryData.path.toUserOutput(), applicationDisplayName), + .arg(secondaryData.path.toUserOutput(), m_applicationDisplayName), Issue::Type::WARNING); secondaryData.issue->buttons.clear(); secondaryData.issue->buttons.insert(QMessageBox::Yes, Continue); diff --git a/src/libs/utils/settingsaccessor.h b/src/libs/utils/settingsaccessor.h index 5ad37bd59c..683743ac62 100644 --- a/src/libs/utils/settingsaccessor.h +++ b/src/libs/utils/settingsaccessor.h @@ -51,7 +51,7 @@ using SettingsMergeResult = std::optional>; class QTCREATOR_UTILS_EXPORT SettingsAccessor { public: - SettingsAccessor(const QString &docType, const QString &applicationDisplayName); + SettingsAccessor(); virtual ~SettingsAccessor(); enum ProceedInfo { Continue, DiscardAndContinue }; @@ -94,9 +94,6 @@ public: QVariantMap restoreSettings(QWidget *parent) const; bool saveSettings(const QVariantMap &data, QWidget *parent) const; - const QString docType; - const QString applicationDisplayName; - void setBaseFilePath(const FilePath &baseFilePath) { m_baseFilePath = baseFilePath; } void setReadOnly() { m_readOnly = true; } FilePath baseFilePath() const { return m_baseFilePath; } @@ -106,6 +103,9 @@ public: const QVariantMap &data, QWidget *parent) const; + void setDocType(const QString &docType) { m_docType = docType; } + void setApplicationDisplayName(const QString &name) { m_applicationDisplayName = name; } + protected: // Report errors: QVariantMap restoreSettings(const FilePath &settingsPath, QWidget *parent) const; @@ -117,6 +117,9 @@ protected: virtual RestoreData readFile(const FilePath &path) const; virtual std::optional writeFile(const FilePath &path, const QVariantMap &data) const; + QString m_docType; + QString m_applicationDisplayName; + private: FilePath m_baseFilePath; mutable std::unique_ptr m_writer; @@ -146,7 +149,7 @@ public: class QTCREATOR_UTILS_EXPORT BackingUpSettingsAccessor : public SettingsAccessor { public: - BackingUpSettingsAccessor(const QString &docType, const QString &applicationDisplayName); + BackingUpSettingsAccessor(); RestoreData readData(const FilePath &path, QWidget *parent) const override; std::optional writeData(const FilePath &path, @@ -216,7 +219,7 @@ class MergingSettingsAccessor; class QTCREATOR_UTILS_EXPORT UpgradingSettingsAccessor : public BackingUpSettingsAccessor { public: - UpgradingSettingsAccessor(const QString &docType, const QString &applicationDisplayName); + UpgradingSettingsAccessor(); int currentVersion() const; int firstSupportedVersion() const; @@ -256,7 +259,7 @@ public: QString key; }; - MergingSettingsAccessor(const QString &docType, const QString &applicationDisplayName); + MergingSettingsAccessor(); RestoreData readData(const FilePath &path, QWidget *parent) const final; -- cgit v1.2.3 From 070bfe9387503688e154451c28fe278841012ac4 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Tue, 25 Apr 2023 17:48:43 +0200 Subject: FutureSynchronizer: Change the default value of cancelOnWait to true The "false" default wasn't really useful. This changes the default value to the following usages: 1. AndroidDeployQtStep Introduced in 91f136ef3ab75471cabcaed9dc16dad9f504add8 The synchronizer was used to cancel the running tasks inside the doCancel(), so the similar behavior should be expected when destructing the AndroidDeployQtStep. 2. GitClient Introduced in f3106ebafe9a02904e822e9698c8b4cbb6c7e0f5 Is used only inside the last line of GitSubmitEditor::updateFileModel(). The running function (CommitDataFetchResult::fetch) doesn't take QPromise<>, so it can't detect if the future was canceled or not. In this case this change is no-op. 3. ExtraCompiler Introduced in c99ce1f455189864de9a2043730f704d7b024abf The intention was to make it cancellable and finish early on cancel. 4. PluginManager global future synchronizer Introduced in 72bddf9f51fedd064f551bcb4ced5feeb46fdfc1 The intention was to make it cancellable and finish early on cancel. The relevant places in code are marked explicitly for points: 1, 2 and 3. Change-Id: I1a52deb8d1f81d355950c8772bbaa6d0a202fd7e Reviewed-by: Eike Ziller --- src/libs/extensionsystem/pluginmanager.cpp | 1 - src/libs/utils/futuresynchronizer.h | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src/libs') diff --git a/src/libs/extensionsystem/pluginmanager.cpp b/src/libs/extensionsystem/pluginmanager.cpp index 588afe661d..6ca6ef114c 100644 --- a/src/libs/extensionsystem/pluginmanager.cpp +++ b/src/libs/extensionsystem/pluginmanager.cpp @@ -983,7 +983,6 @@ PluginManagerPrivate::PluginManagerPrivate(PluginManager *pluginManager) : q(pluginManager) { m_futureSynchronizer.reset(new FutureSynchronizer); - m_futureSynchronizer->setCancelOnWait(true); } diff --git a/src/libs/utils/futuresynchronizer.h b/src/libs/utils/futuresynchronizer.h index 6a8192f0eb..29b1f5e456 100644 --- a/src/libs/utils/futuresynchronizer.h +++ b/src/libs/utils/futuresynchronizer.h @@ -31,14 +31,15 @@ public: void clearFutures(); void setCancelOnWait(bool enabled); - bool isCancelOnWait() const; // TODO: The original contained cancelOnWait, what suggests action, not a getter + // Note: The QFutureSynchronizer contains cancelOnWait(), what suggests action, not a getter. + bool isCancelOnWait() const; void flushFinishedFutures(); private: - QList> m_futures; - bool m_cancelOnWait = false; // TODO: True default makes more sense... + // Note: This default value is different than QFutureSynchronizer's one. True makes more sense. + bool m_cancelOnWait = true; }; } // namespace Utils -- cgit v1.2.3 From 382168364785582763d68afc64c832858e87b833 Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Fri, 21 Apr 2023 22:10:24 +0200 Subject: StyleHelper: Add setter for "panelwidget[_singlerow]" 20 string duplications warrant a centralized setter. A couple more of them will come with the upcoming toolbar changes. Change-Id: Ide8c680da21d5be09f968bcc0a774e062c6f0260 Reviewed-by: Eike Ziller --- src/libs/utils/styledbar.cpp | 8 +++++--- src/libs/utils/stylehelper.cpp | 10 ++++++++++ src/libs/utils/stylehelper.h | 2 ++ 3 files changed, 17 insertions(+), 3 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/styledbar.cpp b/src/libs/utils/styledbar.cpp index a20aba6e1a..7b00c4b495 100644 --- a/src/libs/utils/styledbar.cpp +++ b/src/libs/utils/styledbar.cpp @@ -3,6 +3,8 @@ #include "styledbar.h" +#include "stylehelper.h" + #include #include @@ -11,14 +13,14 @@ using namespace Utils; StyledBar::StyledBar(QWidget *parent) : QWidget(parent) { - setProperty("panelwidget", true); - setProperty("panelwidget_singlerow", true); + StyleHelper::setPanelWidget(this); + StyleHelper::setPanelWidgetSingleRow(this); setProperty("lightColored", false); } void StyledBar::setSingleRow(bool singleRow) { - setProperty("panelwidget_singlerow", singleRow); + StyleHelper::setPanelWidgetSingleRow(this, singleRow); } bool StyledBar::isSingleRow() const diff --git a/src/libs/utils/stylehelper.cpp b/src/libs/utils/stylehelper.cpp index 9f849b9a61..f27fb2f8fc 100644 --- a/src/libs/utils/stylehelper.cpp +++ b/src/libs/utils/stylehelper.cpp @@ -634,6 +634,16 @@ QLinearGradient StyleHelper::statusBarGradient(const QRect &statusBarRect) return grad; } +void StyleHelper::setPanelWidget(QWidget *widget, bool value) +{ + widget->setProperty("panelwidget", value); +} + +void StyleHelper::setPanelWidgetSingleRow(QWidget *widget, bool value) +{ + widget->setProperty("panelwidget_singlerow", value); +} + bool StyleHelper::isQDSTheme() { return creatorTheme() ? creatorTheme()->flag(Theme::QDSTheme) : false; diff --git a/src/libs/utils/stylehelper.h b/src/libs/utils/stylehelper.h index da93bb3c3a..c7766b0ffe 100644 --- a/src/libs/utils/stylehelper.h +++ b/src/libs/utils/stylehelper.h @@ -72,6 +72,8 @@ public: static void tintImage(QImage &img, const QColor &tintColor); static QLinearGradient statusBarGradient(const QRect &statusBarRect); + static void setPanelWidget(QWidget *widget, bool value = true); + static void setPanelWidgetSingleRow(QWidget *widget, bool value = true); static bool isQDSTheme(); -- cgit v1.2.3 From e18c1dceb236d5aed2ddf70e651da1435bb7530e Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 25 Apr 2023 16:58:21 +0200 Subject: Layouting: Add QTextEdit Change-Id: I3a74dce8ee7874b73cb11acab52092c4053722b8 Reviewed-by: Christian Stenger Reviewed-by: --- src/libs/utils/layoutbuilder.cpp | 9 +++++++++ src/libs/utils/layoutbuilder.h | 7 +++++++ 2 files changed, 16 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index 12eb4003b9..9308ba96ce 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -15,6 +15,7 @@ #include #include #include +#include namespace Layouting { @@ -442,6 +443,12 @@ PushButton::PushButton(std::initializer_list items) applyItems(widget, items); } +TextEdit::TextEdit(std::initializer_list items) +{ + widget = new QTextEdit; + applyItems(widget, items); +} + Splitter::Splitter(std::initializer_list items) : Splitter(new QSplitter(Qt::Vertical), items) {} @@ -497,6 +504,8 @@ LayoutItem::Setter text(const QString &text) return [text](QObject *target) { if (auto button = qobject_cast(target)) { button->setText(text); + } else if (auto textEdit = qobject_cast(target)) { + textEdit->setText(text); } else { QTC_CHECK(false); } diff --git a/src/libs/utils/layoutbuilder.h b/src/libs/utils/layoutbuilder.h index 74b4ff3e0a..06f0ae525d 100644 --- a/src/libs/utils/layoutbuilder.h +++ b/src/libs/utils/layoutbuilder.h @@ -20,6 +20,7 @@ QT_BEGIN_NAMESPACE class QLayout; class QSplitter; class QTabWidget; +class QTextEdit; class QWidget; QT_END_NAMESPACE @@ -155,6 +156,12 @@ public: Group(std::initializer_list items); }; +class QTCREATOR_UTILS_EXPORT TextEdit : public LayoutItem +{ +public: + TextEdit(std::initializer_list items); +}; + class QTCREATOR_UTILS_EXPORT PushButton : public LayoutItem { public: -- cgit v1.2.3 From df5e3c587a045fbd3d50233ecdde0e06dccf35a3 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 26 Apr 2023 12:33:11 +0200 Subject: TaskTree: Enhance Sync's function Make it possible to pass a void returning function to the Sync constructor. In this case it's assumed that function returns true by default and finishes successfully. Add some helpful error messages when requirements for the passed function are not met. Change-Id: I8be75acd277d06e87db3c87a6eb96173aa9cd890 Reviewed-by: Qt CI Bot Reviewed-by: Marcus Tillmanns --- src/libs/utils/tasktree.h | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/tasktree.h b/src/libs/utils/tasktree.h index 8e9c0a6c17..0c1b5e7a33 100644 --- a/src/libs/utils/tasktree.h +++ b/src/libs/utils/tasktree.h @@ -306,11 +306,28 @@ public: // Synchronous invocation. Similarly to Group - isn't counted as a task inside taskCount() class QTCREATOR_UTILS_EXPORT Sync : public Group { + public: - using SynchronousMethod = std::function; - Sync(const SynchronousMethod &sync) - : Group({OnGroupSetup([sync] { return sync() ? TaskAction::StopWithDone - : TaskAction::StopWithError; })}) {} + template + Sync(Function &&function) : Group(init(std::forward(function))) {} + +private: + template + static QList init(Function &&function) { + constexpr bool isInvocable = std::is_invocable_v>; + static_assert(isInvocable, + "Sync element: The synchronous function can't take any arguments."); + constexpr bool isBool = std::is_same_v>>; + constexpr bool isVoid = std::is_same_v>>; + static_assert(isBool || isVoid, + "Sync element: The synchronous function has to return void or bool."); + if constexpr (isBool) { + return {OnGroupSetup([function] { return function() ? TaskAction::StopWithDone + : TaskAction::StopWithError; })}; + } + return {OnGroupSetup([function] { function(); return TaskAction::StopWithDone; })}; + }; + }; QTCREATOR_UTILS_EXPORT extern ParallelLimit sequential; -- cgit v1.2.3 From 56b0d77c8276c8c3aa122409643deaaddb075bfb Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Sun, 30 Apr 2023 12:18:00 +0200 Subject: TaskTree: Add an useful warning message It's a common mistake to forget to insert the Storage element into the tree, but reuse it from inside running handlers. This message should help in quick fixing the issue. Change-Id: I771e89b06943667b56188d0655ec3da1b48f8a34 Reviewed-by: Marcus Tillmanns --- src/libs/utils/tasktree.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/tasktree.cpp b/src/libs/utils/tasktree.cpp index 8ea5136932..97a4dbae66 100644 --- a/src/libs/utils/tasktree.cpp +++ b/src/libs/utils/tasktree.cpp @@ -33,7 +33,12 @@ TreeStorageBase::StorageData::~StorageData() void *TreeStorageBase::activeStorageVoid() const { - QTC_ASSERT(m_storageData->m_activeStorage, return nullptr); + QTC_ASSERT(m_storageData->m_activeStorage, qWarning( + "The referenced storage is not reachable in the running tree. " + "A nullptr will be returned which might lead to a crash in the calling code. " + "It is possible that no storage was added to the tree, " + "or the storage is not reachable from where it is referenced."); + return nullptr); const auto it = m_storageData->m_storageHash.constFind(m_storageData->m_activeStorage); QTC_ASSERT(it != m_storageData->m_storageHash.constEnd(), return nullptr); return it.value(); -- cgit v1.2.3 From e46a4eba8dbe757903c10fd043bf03142836994a Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Sat, 29 Apr 2023 19:53:40 +0200 Subject: Utils: Introduce Barrier primitive This primitive is going to replace the TaskTree's built-in mechanism consisting of Wait, Condition and ConditionActivator elements. When combining 2 barriers, one placed in a custom storage, and the other in a tree, it's possible to fully substitute the Wait, Condition and ConditionActivator with the comparable amount of code. However, the Barrier is much more versatile, since it makes it possible to: 1. distribute the decision about the ultimate barrier pass on the whole tree. In order to utilize it, increase the limit of the shared barrier with setLimit() to the expected number of places that participate in the decision about the ultimate barrier pass and use advance() from multiple places in the tree. When the number of calls to advance() reaches the limit(), the shared barrier passes automatically. Whenever some participant failed, so that the shared barrier can not be passed, it may call stopWithResult(false). Whenever some other participant decided that all the needed data are already collected, so that the barrier may pass early, it may call stopWithResult(true), making the remaining calls to advance no-op. 2. wait for the same barrier from multiple places. Before, only one WaitFor was possible for a single Condition. 3. insert multiple Barriers into one Group element. Before, only one WaitFor could be placed in a single Group. Provide ready-made SingleCondition and waitFor() helpers. With the new approach, the following equivalents are provided: - SingleBarrier (substitutes old Condition) - WaitForBarrier() (substitutes old WaitFor) - Barrier (substitutes old ConditionActivator) This change replaces the mechanism introduced in 29f634a8caf69a374edb00df7a19146352a14d6f. This change conforms to the naming scheme proposed in QTCREATORBUG-29102. Task-number: QTCREATORBUG-29102 Change-Id: I48b3e2ee723c3b9fe73a59a25eb7facc72940c3b Reviewed-by: Marcus Tillmanns --- src/libs/utils/CMakeLists.txt | 1 + src/libs/utils/barrier.cpp | 47 +++++++++++++++++++++ src/libs/utils/barrier.h | 97 +++++++++++++++++++++++++++++++++++++++++++ src/libs/utils/utils.qbs | 2 + 4 files changed, 147 insertions(+) create mode 100644 src/libs/utils/barrier.cpp create mode 100644 src/libs/utils/barrier.h (limited to 'src/libs') diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index 69fa8769a9..8660e65b84 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -13,6 +13,7 @@ add_qtc_library(Utils archive.cpp archive.h aspects.cpp aspects.h asynctask.cpp asynctask.h + barrier.cpp barrier.h basetreeview.cpp basetreeview.h benchmarker.cpp benchmarker.h buildablehelperlibrary.cpp buildablehelperlibrary.h diff --git a/src/libs/utils/barrier.cpp b/src/libs/utils/barrier.cpp new file mode 100644 index 0000000000..76cc351088 --- /dev/null +++ b/src/libs/utils/barrier.cpp @@ -0,0 +1,47 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "barrier.h" + +#include "qtcassert.h" + +namespace Utils { + +void Barrier::setLimit(int value) +{ + QTC_ASSERT(!isRunning(), return); + QTC_ASSERT(value > 0, return); + + m_limit = value; +} + +void Barrier::start() +{ + QTC_ASSERT(!isRunning(), return); + m_current = 0; + m_result = {}; +} + +void Barrier::advance() +{ + // Calling advance on finished is OK + QTC_ASSERT(isRunning() || m_result, return); + if (!isRunning()) // no-op + return; + ++m_current; + if (m_current == m_limit) + stopWithResult(true); +} + +void Barrier::stopWithResult(bool success) +{ + // Calling stopWithResult on finished is OK when the same success is passed + QTC_ASSERT(isRunning() || (m_result && *m_result == success), return); + if (!isRunning()) // no-op + return; + m_current = -1; + m_result = success; + emit done(success); +} + +} // namespace Utils diff --git a/src/libs/utils/barrier.h b/src/libs/utils/barrier.h new file mode 100644 index 0000000000..f61b2ef2b8 --- /dev/null +++ b/src/libs/utils/barrier.h @@ -0,0 +1,97 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "utils_global.h" + +#include "tasktree.h" + +namespace Utils { + +class QTCREATOR_UTILS_EXPORT Barrier final : public QObject +{ + Q_OBJECT + +public: + void setLimit(int value); + int limit() const { return m_limit; } + + void start(); + void advance(); // If limit reached, stops with true + void stopWithResult(bool success); // Ignores limit + + bool isRunning() const { return m_current >= 0; } + int current() const { return m_current; } + std::optional result() const { return m_result; } + +signals: + void done(bool success); + +private: + std::optional m_result = {}; + int m_limit = 1; + int m_current = -1; +}; + +class QTCREATOR_UTILS_EXPORT BarrierAdapter : public Tasking::TaskAdapter +{ +public: + BarrierAdapter() { connect(task(), &Barrier::done, this, &TaskInterface::done); } + void start() final { task()->start(); } +}; + +} // namespace Utils + +QTC_DECLARE_CUSTOM_TASK(BarrierTask, Utils::BarrierAdapter); + +namespace Utils::Tasking { + +template +class SharedBarrier +{ +public: + static_assert(Limit > 0, "SharedBarrier's limit should be 1 or more."); + SharedBarrier() : m_barrier(new Barrier) { + m_barrier->setLimit(Limit); + m_barrier->start(); + } + Barrier *barrier() const { return m_barrier.get(); } + +private: + std::shared_ptr m_barrier; +}; + +template +using MultiBarrier = TreeStorage>; + +// Can't write: "MultiBarrier barrier;". Only "MultiBarrier<> barrier;" would work. +// Can't have one alias with default type in C++17, getting the following error: +// alias template deduction only available with C++20. +using SingleBarrier = MultiBarrier<1>; + +class WaitForBarrier : public BarrierTask +{ +public: + template + WaitForBarrier(const MultiBarrier &sharedBarrier) + : BarrierTask([sharedBarrier](Barrier &barrier) { + SharedBarrier *activeBarrier = sharedBarrier.activeStorage(); + if (!activeBarrier) { + qWarning("The barrier referenced from WaitForBarrier element " + "is not reachable in the running tree. " + "It is possible that no barrier was added to the tree, " + "or the storage is not reachable from where it is referenced. " + "The WaitForBarrier task will finish with error. "); + return TaskAction::StopWithError; + } + Barrier *activeSharedBarrier = activeBarrier->barrier(); + const std::optional result = activeSharedBarrier->result(); + if (result.has_value()) + return result.value() ? TaskAction::StopWithDone : TaskAction::StopWithError; + QObject::connect(activeSharedBarrier, &Barrier::done, &barrier, &Barrier::stopWithResult); + return TaskAction::Continue; + }) {} +}; + +} // namespace Utils::Tasking diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index 210a623031..eb8c2a9f56 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -51,6 +51,8 @@ Project { "aspects.h", "asynctask.cpp", "asynctask.h", + "barrier.cpp", + "barrier.h", "basetreeview.cpp", "basetreeview.h", "benchmarker.cpp", -- cgit v1.2.3 From 8f345bbc35e3d1d7e92fdee25d4f42b1194e9793 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 25 Apr 2023 10:51:14 +0200 Subject: Layouting: Drop compile dependency on BoolAspect Change-Id: I4068048f470db126a2583d6b1b90245205cfd601 Reviewed-by: Qt CI Bot Reviewed-by: Reviewed-by: Christian Stenger --- src/libs/utils/aspects.cpp | 16 ++++++++++------ src/libs/utils/aspects.h | 10 ++-------- src/libs/utils/layoutbuilder.cpp | 10 ++-------- src/libs/utils/layoutbuilder.h | 6 +----- 4 files changed, 15 insertions(+), 27 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index ea4a2f1848..baf80e3fe7 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -1423,6 +1423,16 @@ void BoolAspect::addToLayout(Layouting::LayoutBuilder &builder) this, &BoolAspect::volatileValueChanged); } +std::function BoolAspect::groupChecker() +{ + return [this](QObject *target) { + auto groupBox = qobject_cast(target); + QTC_ASSERT(groupBox, return); + registerSubWidget(groupBox); + d->m_groupBox = d->m_groupBox; + }; +} + QAction *BoolAspect::action() { if (hasAction()) @@ -1515,12 +1525,6 @@ void BoolAspect::setLabelPlacement(BoolAspect::LabelPlacement labelPlacement) d->m_labelPlacement = labelPlacement; } -void BoolAspect::setHandlesGroup(QGroupBox *box) -{ - registerSubWidget(box); - d->m_groupBox = box; -} - /*! \class Utils::SelectionAspect \inmodule QtCreator diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index 81a67eb5d5..9950fbef61 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -9,22 +9,16 @@ #include "macroexpander.h" #include "pathchooser.h" -#include - #include #include #include QT_BEGIN_NAMESPACE class QAction; -class QGroupBox; class QSettings; QT_END_NAMESPACE -namespace Layouting { -class LayoutBuilder; -class LayoutItem; -} // Layouting +namespace Layouting { class LayoutBuilder; } namespace Utils { @@ -226,6 +220,7 @@ public: }; void addToLayout(Layouting::LayoutBuilder &builder) override; + std::function groupChecker(); QAction *action() override; @@ -242,7 +237,6 @@ public: void setLabel(const QString &labelText, LabelPlacement labelPlacement = LabelPlacement::InExtraLabel); void setLabelPlacement(LabelPlacement labelPlacement); - void setHandlesGroup(QGroupBox *box); signals: void valueChanged(bool newValue); diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index 9308ba96ce..c47be9778d 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -3,7 +3,6 @@ #include "layoutbuilder.h" -#include "aspects.h" #include "qtcassert.h" #include @@ -471,17 +470,12 @@ TabWidget::TabWidget(QTabWidget *tabWidget, std::initializer_list tabs) // "Properties" -LayoutItem::Setter title(const QString &title, Utils::BoolAspect *checker) +LayoutItem::Setter title(const QString &title) { - return [title, checker](QObject *target) { + return [title](QObject *target) { if (auto groupBox = qobject_cast(target)) { groupBox->setTitle(title); groupBox->setObjectName(title); - if (checker) { - groupBox->setCheckable(true); - groupBox->setChecked(checker->value()); - checker->setHandlesGroup(groupBox); - } } else { QTC_CHECK(false); } diff --git a/src/libs/utils/layoutbuilder.h b/src/libs/utils/layoutbuilder.h index 06f0ae525d..0e24d1d2ec 100644 --- a/src/libs/utils/layoutbuilder.h +++ b/src/libs/utils/layoutbuilder.h @@ -24,8 +24,6 @@ class QTextEdit; class QWidget; QT_END_NAMESPACE -namespace Utils { class BoolAspect; } - namespace Layouting { enum AttachType { @@ -191,9 +189,7 @@ QTCREATOR_UTILS_EXPORT extern HorizontalRule hr; // "Properties" -QTCREATOR_UTILS_EXPORT LayoutItem::Setter title(const QString &title, - Utils::BoolAspect *checker = nullptr); - +QTCREATOR_UTILS_EXPORT LayoutItem::Setter title(const QString &title); QTCREATOR_UTILS_EXPORT LayoutItem::Setter text(const QString &text); QTCREATOR_UTILS_EXPORT LayoutItem::Setter tooltip(const QString &toolTip); QTCREATOR_UTILS_EXPORT LayoutItem::Setter onClicked(const std::function &func, -- cgit v1.2.3 From 36dad70ab0f3e07043a4b09516ae2de85687f13c Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Sun, 30 Apr 2023 10:08:12 +0200 Subject: TaskTree: Replace the usages of old WaitFor with new Barrier Adapt the TaskTree tests and the usage in FileStreamer. The FileStreamer may be tested by running the FileSystemAccessTest. Change-Id: I1d8086dd359c458b7bdd3d4d47cf249184b04c65 Reviewed-by: Marcus Tillmanns --- src/libs/utils/filestreamer.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/filestreamer.cpp b/src/libs/utils/filestreamer.cpp index 47cbffd3c2..c47eafaf7a 100644 --- a/src/libs/utils/filestreamer.cpp +++ b/src/libs/utils/filestreamer.cpp @@ -4,6 +4,7 @@ #include "filestreamer.h" #include "asynctask.h" +#include "barrier.h" #include "qtcprocess.h" #include @@ -321,7 +322,7 @@ static Group sameRemoteDeviceTransferTask(const FilePath &source, const FilePath static Group interDeviceTransferTask(const FilePath &source, const FilePath &destination) { struct TransferStorage { QPointer writer; }; - Condition condition; + SingleBarrier writerReadyBarrier; TreeStorage storage; const auto setupReader = [=](FileStreamReader &reader) { @@ -336,19 +337,19 @@ static Group interDeviceTransferTask(const FilePath &source, const FilePath &des }; const auto setupWriter = [=](FileStreamWriter &writer) { writer.setFilePath(destination); - ConditionActivator *activator = condition.activator(); QObject::connect(&writer, &FileStreamWriter::started, - &writer, [activator] { activator->activate(); }); + writerReadyBarrier->barrier(), &Barrier::advance); QTC_CHECK(storage->writer == nullptr); storage->writer = &writer; }; const Group root { + Storage(writerReadyBarrier), parallel, Storage(storage), Writer(setupWriter), Group { - WaitFor(condition), + WaitForBarrier(writerReadyBarrier), Reader(setupReader, finalizeReader, finalizeReader) } }; -- cgit v1.2.3 From 68d05b05d9568c224ded23311754bc805455fd9c Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Sun, 30 Apr 2023 11:25:22 +0200 Subject: TaskTree: Remove the old WaitFor, Condition, ConditionActivator Remove it from internals of TaskTree. It's replaced with the new mechanism consisting of Barrier, implemented outside of TaskTree. The change mostly reverts 29f634a8caf69a374edb00df7a19146352a14d6f. Change-Id: I1f2f4100e7c992389a19c3cc9132c3f2980b9bf8 Reviewed-by: Reviewed-by: Marcus Tillmanns Reviewed-by: Qt CI Bot --- src/libs/utils/tasktree.cpp | 179 +------------------------------------------- src/libs/utils/tasktree.h | 56 -------------- 2 files changed, 1 insertion(+), 234 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/tasktree.cpp b/src/libs/utils/tasktree.cpp index 97a4dbae66..5e87466c66 100644 --- a/src/libs/utils/tasktree.cpp +++ b/src/libs/utils/tasktree.cpp @@ -79,54 +79,6 @@ void TreeStorageBase::activateStorage(int id) const m_storageData->m_activeStorage = id; } -Condition::Condition() - : m_conditionData(new ConditionData()) {} - -Condition::ConditionData::~ConditionData() -{ - QTC_CHECK(m_activatorHash.isEmpty()); - qDeleteAll(m_activatorHash); -} - -ConditionActivator *Condition::activator() const -{ - QTC_ASSERT(m_conditionData->m_activeActivator, return nullptr); - const auto it = m_conditionData->m_activatorHash.constFind(m_conditionData->m_activeActivator); - QTC_ASSERT(it != m_conditionData->m_activatorHash.constEnd(), return nullptr); - return it.value(); -} - -int Condition::createActivator(TaskNode *node) const -{ - QTC_ASSERT(m_conditionData->m_activeActivator == 0, return 0); // TODO: should be allowed? - const int newId = ++m_conditionData->m_activatorCounter; - m_conditionData->m_activatorHash.insert(newId, new ConditionActivator(node)); - return newId; -} - -void Condition::deleteActivator(int id) const -{ - QTC_ASSERT(m_conditionData->m_activeActivator == 0, return); // TODO: should be allowed? - const auto it = m_conditionData->m_activatorHash.constFind(id); - QTC_ASSERT(it != m_conditionData->m_activatorHash.constEnd(), return); - delete it.value(); - m_conditionData->m_activatorHash.erase(it); -} - -// passing 0 deactivates currently active condition -void Condition::activateActivator(int id) const -{ - if (id == 0) { - QTC_ASSERT(m_conditionData->m_activeActivator, return); - m_conditionData->m_activeActivator = 0; - return; - } - QTC_ASSERT(m_conditionData->m_activeActivator == 0, return); - const auto it = m_conditionData->m_activatorHash.find(id); - QTC_ASSERT(it != m_conditionData->m_activatorHash.end(), return); - m_conditionData->m_activeActivator = id; -} - ParallelLimit sequential(1); ParallelLimit parallel(0); Workflow stopOnError(WorkflowPolicy::StopOnError); @@ -180,12 +132,6 @@ void TaskItem::addChildren(const QList &children) if (child.m_groupHandler.m_errorHandler) m_groupHandler.m_errorHandler = child.m_groupHandler.m_errorHandler; break; - case Type::Condition: - QTC_ASSERT(m_type == Type::Group, qWarning("WaitFor may only be a child of a Group, " - "skipping..."); break); - QTC_ASSERT(!m_condition, qWarning("WaitFor redefinition, overriding...")); - m_condition = child.m_condition; - break; case Type::Storage: m_storageList.append(child.m_storageList); break; @@ -213,11 +159,6 @@ public: void emitProgress(); void emitDone(); void emitError(); - bool addCondition(const TaskItem &task, TaskContainer *container); - void createConditionActivators(); - void deleteConditionActivators(); - void activateConditions(); - void deactivateConditions(); QList addStorages(const QList &storages); void callSetupHandler(TreeStorageBase storage, int storageId) { callStorageHandler(storage, storageId, &StorageHandler::m_setupHandler); @@ -246,7 +187,6 @@ public: TaskTree *q = nullptr; Guard m_guard; int m_progressValue = 0; - QHash m_conditions; QSet m_storages; QHash m_storageHandlers; std::unique_ptr m_root = nullptr; // Keep me last in order to destruct first @@ -257,14 +197,11 @@ class TaskContainer public: TaskContainer(TaskTreePrivate *taskTreePrivate, const TaskItem &task, TaskNode *parentNode, TaskContainer *parentContainer) - : m_constData(taskTreePrivate, task, parentNode, parentContainer, this) - , m_conditionData(taskTreePrivate->addCondition(task, this) - ? ConditionData() : std::optional()) {} + : m_constData(taskTreePrivate, task, parentNode, parentContainer, this) {} TaskAction start(); TaskAction continueStart(TaskAction startAction, int nextChild); TaskAction startChildren(int nextChild); TaskAction childDone(bool success); - void activateCondition(); void stop(); void invokeEndHandler(); bool isRunning() const { return m_runtimeData.has_value(); } @@ -286,11 +223,6 @@ public: const int m_taskCount = 0; }; - struct ConditionData { - bool m_activated = false; - int m_conditionId = 0; - }; - struct RuntimeData { RuntimeData(const ConstData &constData); ~RuntimeData(); @@ -308,7 +240,6 @@ public: }; const ConstData m_constData; - std::optional m_conditionData; std::optional m_runtimeData; }; @@ -330,7 +261,6 @@ public: bool isTask() const { return m_taskHandler.m_createHandler && m_taskHandler.m_setupHandler; } int taskCount() const { return isTask() ? 1 : m_container.m_constData.m_taskCount; } TaskContainer *parentContainer() const { return m_container.m_constData.m_parentContainer; } - void activateCondition(); private: const TaskItem::TaskHandler m_taskHandler; @@ -348,7 +278,6 @@ void TaskTreePrivate::start() QTC_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't " "exist in task tree. Its handlers will never be called.")); } - createConditionActivators(); m_root->start(); } @@ -391,7 +320,6 @@ void TaskTreePrivate::emitProgress() void TaskTreePrivate::emitDone() { - deleteConditionActivators(); QTC_CHECK(m_progressValue == m_root->taskCount()); GuardLocker locker(m_guard); emit q->done(); @@ -399,57 +327,11 @@ void TaskTreePrivate::emitDone() void TaskTreePrivate::emitError() { - deleteConditionActivators(); QTC_CHECK(m_progressValue == m_root->taskCount()); GuardLocker locker(m_guard); emit q->errorOccurred(); } -bool TaskTreePrivate::addCondition(const TaskItem &task, TaskContainer *container) -{ - if (!task.condition()) - return false; - QTC_ASSERT(!m_conditions.contains(*task.condition()), qWarning("Can't add the same condition " - "into one TaskTree twice, skipping..."); return false); - m_conditions.insert(*task.condition(), container); - return true; -} - -void TaskTreePrivate::createConditionActivators() -{ - for (auto it = m_conditions.cbegin(); it != m_conditions.cend(); ++it) { - Condition condition = it.key(); - TaskContainer *container = it.value(); - container->m_conditionData->m_conditionId - = condition.createActivator(container->m_constData.m_parentNode); - } -} - -void TaskTreePrivate::deleteConditionActivators() -{ - for (auto it = m_conditions.cbegin(); it != m_conditions.cend(); ++it) { - Condition condition = it.key(); - TaskContainer *container = it.value(); - condition.deleteActivator(container->m_conditionData->m_conditionId); - container->m_conditionData = TaskContainer::ConditionData(); - } -} - -void TaskTreePrivate::activateConditions() -{ - for (auto it = m_conditions.cbegin(); it != m_conditions.cend(); ++it) { - Condition condition = it.key(); - TaskContainer *container = it.value(); - condition.activateActivator(container->m_conditionData->m_conditionId); - } -} - -void TaskTreePrivate::deactivateConditions() -{ - for (auto it = m_conditions.cbegin(); it != m_conditions.cend(); ++it) - it.key().activateActivator(0); -} - QList TaskTreePrivate::addStorages(const QList &storages) { QList addedStorages; @@ -462,7 +344,6 @@ QList TaskTreePrivate::addStorages(const QList return addedStorages; } -// TODO: Activate/deactivate Conditions class ExecutionContextActivator { public: @@ -477,8 +358,6 @@ private: const TaskContainer::ConstData &constData = container->m_constData; if (constData.m_parentContainer) activateContext(constData.m_parentContainer); - else - constData.m_taskTreePrivate->activateConditions(); for (int i = 0; i < constData.m_storageList.size(); ++i) constData.m_storageList[i].activateStorage(container->m_runtimeData->m_storageIdList.value(i)); } @@ -490,8 +369,6 @@ private: constData.m_storageList[i].activateStorage(0); if (constData.m_parentContainer) deactivateContext(constData.m_parentContainer); - else - constData.m_taskTreePrivate->deactivateConditions(); } TaskContainer *m_container = nullptr; }; @@ -597,8 +474,6 @@ TaskAction TaskContainer::start() m_constData.m_taskTreePrivate->advanceProgress(m_constData.m_taskCount); } if (startAction == TaskAction::Continue) { - if (m_conditionData && !m_conditionData->m_activated) // Group has condition and it wasn't activated yet - return TaskAction::Continue; if (m_constData.m_children.isEmpty()) startAction = TaskAction::StopWithDone; } @@ -673,35 +548,6 @@ TaskAction TaskContainer::childDone(bool success) return continueStart(startAction, limit); } -void ConditionActivator::activate() -{ - m_node->activateCondition(); -} - -void TaskContainer::activateCondition() -{ - QTC_ASSERT(m_conditionData, return); - if (!m_constData.m_taskTreePrivate->m_root->isRunning()) - return; - - if (!isRunning()) - return; // Condition not run yet or group already skipped or stopped - - if (!m_conditionData->m_activated) - return; // May it happen that scheduled call is coming from previous TaskTree's start? - - if (m_runtimeData->m_doneCount != 0) - return; // In meantime the group was started - - for (TaskNode *child : m_constData.m_children) { - if (child->isRunning()) - return; // In meantime the group was started - } - const TaskAction startAction = m_constData.m_children.isEmpty() ? TaskAction::StopWithDone - : TaskAction::Continue; - continueStart(startAction, 0); -} - void TaskContainer::stop() { if (!isRunning()) @@ -786,26 +632,6 @@ void TaskNode::invokeEndHandler(bool success) m_container.m_constData.m_taskTreePrivate->advanceProgress(1); } -void TaskNode::activateCondition() -{ - QTC_ASSERT(m_container.m_conditionData, return); - QTC_ASSERT(m_container.m_constData.m_taskTreePrivate->m_root->isRunning(), return); - - if (m_container.m_conditionData->m_activated) - return; // Was already activated - - m_container.m_conditionData->m_activated = true; - if (!isRunning()) - return; // Condition not run yet or group already skipped or stopped - - QTC_CHECK(m_container.m_runtimeData->m_doneCount == 0); - for (TaskNode *child : m_container.m_constData.m_children) - QTC_CHECK(!child->isRunning()); - - QMetaObject::invokeMethod(this, [this] { m_container.activateCondition(); }, - Qt::QueuedConnection); -} - /*! \class Utils::TaskTree \inheaderfile utils/tasktree.h @@ -1533,8 +1359,6 @@ TaskTree::~TaskTree() { QTC_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting TaskTree instance directly from " "one of its handlers will lead to crash!")); - if (isRunning()) - d->deleteConditionActivators(); // TODO: delete storages explicitly here? delete d; } @@ -1544,7 +1368,6 @@ void TaskTree::setupRoot(const Tasking::Group &root) QTC_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return); QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The setupRoot() is called from one of the" "TaskTree handlers, ingoring..."); return); - d->m_conditions.clear(); d->m_storages.clear(); d->m_root.reset(new TaskNode(d, root, nullptr)); } diff --git a/src/libs/utils/tasktree.h b/src/libs/utils/tasktree.h index 0c1b5e7a33..9fdb1717b0 100644 --- a/src/libs/utils/tasktree.h +++ b/src/libs/utils/tasktree.h @@ -88,50 +88,6 @@ private: } }; -class QTCREATOR_UTILS_EXPORT ConditionActivator -{ -public: - void activate(); - -private: - ConditionActivator(TaskNode *container) : m_node(container) {} - TaskNode *m_node = nullptr; - friend class Condition; -}; - -class QTCREATOR_UTILS_EXPORT Condition -{ -public: - Condition(); - ConditionActivator &operator*() const noexcept { return *activator(); } - ConditionActivator *operator->() const noexcept { return activator(); } - ConditionActivator *activator() const; - -private: - int createActivator(TaskNode *node) const; - void deleteActivator(int id) const; - void activateActivator(int id) const; - - friend bool operator==(const Condition &first, const Condition &second) - { return first.m_conditionData == second.m_conditionData; } - - friend bool operator!=(const Condition &first, const Condition &second) - { return first.m_conditionData != second.m_conditionData; } - - friend size_t qHash(const Condition &storage, uint seed = 0) - { return size_t(storage.m_conditionData.get()) ^ seed; } - - struct ConditionData { - ~ConditionData(); - QHash m_activatorHash = {}; - int m_activeActivator = 0; // 0 means no active activator - int m_activatorCounter = 0; - }; - QSharedPointer m_conditionData; - friend TaskTreePrivate; - friend ExecutionContextActivator; -}; - // WorkflowPolicy: // 1. When all children finished with done -> report done, otherwise: // a) Report error on first error and stop executing other children (including their subtree) @@ -188,13 +144,11 @@ public: TaskHandler taskHandler() const { return m_taskHandler; } GroupHandler groupHandler() const { return m_groupHandler; } QList children() const { return m_children; } - std::optional condition() const { return m_condition; } QList storageList() const { return m_storageList; } protected: enum class Type { Group, - Condition, Storage, Limit, Policy, @@ -215,9 +169,6 @@ protected: TaskItem(const GroupHandler &handler) : m_type(Type::GroupHandler) , m_groupHandler(handler) {} - TaskItem(const Condition &condition) - : m_type(Type::Condition) - , m_condition{condition} {} TaskItem(const TreeStorageBase &storage) : m_type(Type::Storage) , m_storageList{storage} {} @@ -229,7 +180,6 @@ private: WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError; TaskHandler m_taskHandler; GroupHandler m_groupHandler; - std::optional m_condition; QList m_storageList; QList m_children; }; @@ -247,12 +197,6 @@ public: Storage(const TreeStorageBase &storage) : TaskItem(storage) { } }; -class QTCREATOR_UTILS_EXPORT WaitFor : public TaskItem -{ -public: - WaitFor(const Condition &condition) : TaskItem(condition) { } -}; - class QTCREATOR_UTILS_EXPORT ParallelLimit : public TaskItem { public: -- cgit v1.2.3 From fbe9366498a14bfb8daaa604037bb27e1d59db64 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 25 Apr 2023 11:32:55 +0200 Subject: LayoutBuilder: Cut remaining dependency to utils layoutbuilder.{cpp,h} can now be re-used outside Creator Change-Id: I306d2d8168d8a09658ea008f4606ca37a0dbbc01 Reviewed-by: Eike Ziller --- src/libs/utils/layoutbuilder.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index c47be9778d..3633c083e3 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -3,8 +3,7 @@ #include "layoutbuilder.h" -#include "qtcassert.h" - +#include #include #include #include @@ -18,6 +17,14 @@ namespace Layouting { +// That's cut down qtcassert.{c,h} to avoid the dependency. +#define QTC_STRINGIFY_HELPER(x) #x +#define QTC_STRINGIFY(x) QTC_STRINGIFY_HELPER(x) +#define QTC_STRING(cond) qDebug("SOFT ASSERT: \"%s\" in %s: %s", cond, __FILE__, QTC_STRINGIFY(__LINE__)) +#define QTC_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QTC_STRING(#cond); action; } do {} while (0) +#define QTC_CHECK(cond) if (cond) {} else { QTC_STRING(#cond); } do {} while (0) + + /*! \enum Utils::LayoutBuilder::LayoutType \inmodule QtCreator -- cgit v1.2.3 From 66c0c36bc1cb2417295f5b5c7d7e556f5329f309 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Fri, 28 Apr 2023 11:29:08 +0200 Subject: Fix compiler warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: I9b0b4a60d1152142f62bf3f76885cf8019714623 Reviewed-by: Qt CI Bot Reviewed-by: Sivert Krøvel Reviewed-by: Christian Stenger Reviewed-by: --- src/libs/3rdparty/syntax-highlighting/src/lib/syntaxhighlighter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/syntaxhighlighter.cpp b/src/libs/3rdparty/syntax-highlighting/src/lib/syntaxhighlighter.cpp index 41551e96da..4754da22c6 100644 --- a/src/libs/3rdparty/syntax-highlighting/src/lib/syntaxhighlighter.cpp +++ b/src/libs/3rdparty/syntax-highlighting/src/lib/syntaxhighlighter.cpp @@ -179,7 +179,7 @@ void SyntaxHighlighter::applyFolding(int offset, int length, FoldingRegion regio { Q_UNUSED(offset); Q_UNUSED(length); - Q_D(SyntaxHighlighter); + [[maybe_unused]] Q_D(SyntaxHighlighter); if (region.type() == FoldingRegion::Begin) { d->foldingRegions.push_back(region); -- cgit v1.2.3 From 1c2b29b31acc7f2cb27fc1a5c84fb9f0f176b38c Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 25 Apr 2023 17:21:39 +0200 Subject: Layouting: Introduce a 'bindTo' LayoutItem ... to 'export' the widget being operated on. The 'Tab' related changes are related, as they affect the order of execution. Change-Id: I7aa079f12e49a1dab7c6a49acfae9dc684cfb479 Reviewed-by: Qt CI Bot Reviewed-by: Alessandro Portale Reviewed-by: --- src/libs/utils/layoutbuilder.cpp | 81 ++++++++++++++++++++++++---------------- src/libs/utils/layoutbuilder.h | 16 ++++---- 2 files changed, 57 insertions(+), 40 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index 3633c083e3..e86e69a830 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -418,16 +418,22 @@ Tab::Tab(const QString &tabName, const LayoutBuilder &item) text = tabName; widget = new QWidget; item.attachTo(widget); + specialType = LayoutItem::SpecialType::Tab; } // "Widgets" -static void applyItems(QWidget *widget, const QList &items) +static void applyItems(LayoutItem *owner, QWidget *widget, const QList &items) { + owner->widget = widget; bool hadLayout = false; for (const LayoutItem &item : items) { if (item.setter) { item.setter(widget); + } else if (item.specialType == LayoutItem::SpecialType::Tab) { + auto tabWidget = qobject_cast(widget); + QTC_ASSERT(tabWidget, continue); + tabWidget->addTab(item.widget, item.text); } else if (item.layout && !hadLayout) { hadLayout = true; widget->setLayout(item.layout); @@ -439,70 +445,65 @@ static void applyItems(QWidget *widget, const QList &items) Group::Group(std::initializer_list items) { - widget = new QGroupBox; - applyItems(widget, items); + applyItems(this, new QGroupBox, items); } PushButton::PushButton(std::initializer_list items) { - widget = new QPushButton; - applyItems(widget, items); + applyItems(this, new QPushButton, items); } TextEdit::TextEdit(std::initializer_list items) { - widget = new QTextEdit; - applyItems(widget, items); + applyItems(this, new QTextEdit, items); } Splitter::Splitter(std::initializer_list items) - : Splitter(new QSplitter(Qt::Vertical), items) {} - -Splitter::Splitter(QSplitter *splitter, std::initializer_list items) { - widget = splitter; - for (const LayoutItem &item : items) - splitter->addWidget(item.widget); -} + applyItems(this, new QSplitter(Qt::Vertical), items); + } -TabWidget::TabWidget(std::initializer_list tabs) - : TabWidget(new QTabWidget, tabs) {} -TabWidget::TabWidget(QTabWidget *tabWidget, std::initializer_list tabs) -{ - widget = tabWidget; - for (const Tab &tab : tabs) - tabWidget->addTab(tab.widget, tab.text); +TabWidget::TabWidget(std::initializer_list items) + { + applyItems(this, new QTabWidget, items); } // "Properties" -LayoutItem::Setter title(const QString &title) +static LayoutItem setter(const LayoutItem::Setter &setter) +{ + LayoutItem item; + item.setter = setter; + return item; +} + +LayoutItem title(const QString &title) { - return [title](QObject *target) { + return setter([title](QObject *target) { if (auto groupBox = qobject_cast(target)) { groupBox->setTitle(title); groupBox->setObjectName(title); } else { QTC_CHECK(false); } - }; + }); } -LayoutItem::Setter onClicked(const std::function &func, QObject *guard) +LayoutItem onClicked(const std::function &func, QObject *guard) { - return [func, guard](QObject *target) { + return setter([func, guard](QObject *target) { if (auto button = qobject_cast(target)) { QObject::connect(button, &QAbstractButton::clicked, guard ? guard : target, func); } else { QTC_CHECK(false); } - }; + }); } -LayoutItem::Setter text(const QString &text) +LayoutItem text(const QString &text) { - return [text](QObject *target) { + return setter([text](QObject *target) { if (auto button = qobject_cast(target)) { button->setText(text); } else if (auto textEdit = qobject_cast(target)) { @@ -510,18 +511,32 @@ LayoutItem::Setter text(const QString &text) } else { QTC_CHECK(false); } - }; + }); } -LayoutItem::Setter tooltip(const QString &toolTip) +LayoutItem tooltip(const QString &toolTip) { - return [toolTip](QObject *target) { + return setter([toolTip](QObject *target) { if (auto widget = qobject_cast(target)) { widget->setToolTip(toolTip); } else { QTC_CHECK(false); } - }; + }); +} + +LayoutItem bindTo(QSplitter **out) +{ + return setter([out](QObject *target) { + *out = qobject_cast(target); + }); +} + +LayoutItem bindTo(QTabWidget **out) +{ + return setter([out](QObject *target) { + *out = qobject_cast(target); + }); } QWidget *createHr(QWidget *parent) diff --git a/src/libs/utils/layoutbuilder.h b/src/libs/utils/layoutbuilder.h index 0e24d1d2ec..aea4db876c 100644 --- a/src/libs/utils/layoutbuilder.h +++ b/src/libs/utils/layoutbuilder.h @@ -87,6 +87,7 @@ public: Stretch, Break, HorizontalRule, + Tab, }; using Setter = std::function; @@ -170,14 +171,12 @@ class QTCREATOR_UTILS_EXPORT Splitter : public LayoutItem { public: Splitter(std::initializer_list items); - Splitter(QSplitter *splitter, std::initializer_list items); }; class QTCREATOR_UTILS_EXPORT TabWidget : public LayoutItem { public: - TabWidget(std::initializer_list tabs); - TabWidget(QTabWidget *tabWidget, std::initializer_list tabs); + TabWidget(std::initializer_list items); }; // Singleton items. @@ -189,10 +188,13 @@ QTCREATOR_UTILS_EXPORT extern HorizontalRule hr; // "Properties" -QTCREATOR_UTILS_EXPORT LayoutItem::Setter title(const QString &title); -QTCREATOR_UTILS_EXPORT LayoutItem::Setter text(const QString &text); -QTCREATOR_UTILS_EXPORT LayoutItem::Setter tooltip(const QString &toolTip); -QTCREATOR_UTILS_EXPORT LayoutItem::Setter onClicked(const std::function &func, +QTCREATOR_UTILS_EXPORT LayoutItem bindTo(QTabWidget **); +QTCREATOR_UTILS_EXPORT LayoutItem bindTo(QSplitter **); + +QTCREATOR_UTILS_EXPORT LayoutItem title(const QString &title); +QTCREATOR_UTILS_EXPORT LayoutItem text(const QString &text); +QTCREATOR_UTILS_EXPORT LayoutItem tooltip(const QString &toolTip); +QTCREATOR_UTILS_EXPORT LayoutItem onClicked(const std::function &func, QObject *guard = nullptr); -- cgit v1.2.3 From 4b129df44f7a3bf77befc04895c1e5de00e5030a Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Tue, 2 May 2023 14:01:49 +0200 Subject: TaskTree: Enable the fluent interface for Tasks Make it possible to use the default Task c'tor and add selected handlers with onSetup/Done/Error() methods. Change-Id: I94f5806f347931faa07cff0ade620a3d30777cfe Reviewed-by: hjk --- src/libs/utils/tasktree.cpp | 42 +++++++++++++++++++++++++++++++++++++----- src/libs/utils/tasktree.h | 25 ++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 8 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/tasktree.cpp b/src/libs/utils/tasktree.cpp index 5e87466c66..ee0982072b 100644 --- a/src/libs/utils/tasktree.cpp +++ b/src/libs/utils/tasktree.cpp @@ -109,8 +109,6 @@ void TaskItem::addChildren(const QList &children) case Type::TaskHandler: QTC_ASSERT(child.m_taskHandler.m_createHandler, qWarning("Task Create Handler can't be null, skipping..."); return); - QTC_ASSERT(child.m_taskHandler.m_setupHandler, - qWarning("Task Setup Handler can't be null, skipping..."); return); m_children.append(child); break; case Type::GroupHandler: @@ -139,6 +137,39 @@ void TaskItem::addChildren(const QList &children) } } +void TaskItem::setTaskSetupHandler(const TaskSetupHandler &handler) +{ + if (!handler) { + qWarning("Setting empty Setup Handler is no-op, skipping..."); + return; + } + if (m_taskHandler.m_setupHandler) + qWarning("Setup Handler redefinition, overriding..."); + m_taskHandler.m_setupHandler = handler; +} + +void TaskItem::setTaskDoneHandler(const TaskEndHandler &handler) +{ + if (!handler) { + qWarning("Setting empty Done Handler is no-op, skipping..."); + return; + } + if (m_taskHandler.m_doneHandler) + qWarning("Done Handler redefinition, overriding..."); + m_taskHandler.m_doneHandler = handler; +} + +void TaskItem::setTaskErrorHandler(const TaskEndHandler &handler) +{ + if (!handler) { + qWarning("Setting empty Error Handler is no-op, skipping..."); + return; + } + if (m_taskHandler.m_errorHandler) + qWarning("Error Handler redefinition, overriding..."); + m_taskHandler.m_errorHandler = handler; +} + } // namespace Tasking using namespace Tasking; @@ -258,7 +289,7 @@ public: void stop(); void invokeEndHandler(bool success); bool isRunning() const { return m_task || m_container.isRunning(); } - bool isTask() const { return m_taskHandler.m_createHandler && m_taskHandler.m_setupHandler; } + bool isTask() const { return bool(m_taskHandler.m_createHandler); } int taskCount() const { return isTask() ? 1 : m_container.m_constData.m_taskCount; } TaskContainer *parentContainer() const { return m_container.m_constData.m_parentContainer; } @@ -582,8 +613,9 @@ TaskAction TaskNode::start() return m_container.start(); m_task.reset(m_taskHandler.m_createHandler()); - const TaskAction startAction = invokeHandler(parentContainer(), m_taskHandler.m_setupHandler, - *m_task.get()); + const TaskAction startAction = m_taskHandler.m_setupHandler + ? invokeHandler(parentContainer(), m_taskHandler.m_setupHandler, *m_task.get()) + : TaskAction::Continue; if (startAction != TaskAction::Continue) { m_container.m_constData.m_taskTreePrivate->advanceProgress(1); m_task.reset(); diff --git a/src/libs/utils/tasktree.h b/src/libs/utils/tasktree.h index 9fdb1717b0..418feb797e 100644 --- a/src/libs/utils/tasktree.h +++ b/src/libs/utils/tasktree.h @@ -128,9 +128,9 @@ public: struct TaskHandler { TaskCreateHandler m_createHandler; - TaskSetupHandler m_setupHandler; - TaskEndHandler m_doneHandler; - TaskEndHandler m_errorHandler; + TaskSetupHandler m_setupHandler = {}; + TaskEndHandler m_doneHandler = {}; + TaskEndHandler m_errorHandler = {}; }; struct GroupHandler { @@ -174,6 +174,10 @@ protected: , m_storageList{storage} {} void addChildren(const QList &children); + void setTaskSetupHandler(const TaskSetupHandler &handler); + void setTaskDoneHandler(const TaskEndHandler &handler); + void setTaskErrorHandler(const TaskEndHandler &handler); + private: Type m_type = Type::Group; int m_parallelLimit = 1; // 0 means unlimited @@ -301,11 +305,26 @@ public: using Task = typename Adapter::Type; using EndHandler = std::function; static Adapter *createAdapter() { return new Adapter; } + CustomTask() : TaskItem({&createAdapter}) {} template CustomTask(SetupFunction &&function, const EndHandler &done = {}, const EndHandler &error = {}) : TaskItem({&createAdapter, wrapSetup(std::forward(function)), wrapEnd(done), wrapEnd(error)}) {} + template + CustomTask &onSetup(SetupFunction &&function) { + setTaskSetupHandler(wrapSetup(std::forward(function))); + return *this; + } + CustomTask &onDone(const EndHandler &handler) { + setTaskDoneHandler(wrapEnd(handler)); + return *this; + } + CustomTask &onError(const EndHandler &handler) { + setTaskErrorHandler(wrapEnd(handler)); + return *this; + } + private: template static TaskItem::TaskSetupHandler wrapSetup(SetupFunction &&function) { -- cgit v1.2.3 From b5492953e2ce9def4f4bb876cbeff025efd75c01 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 3 May 2023 09:29:50 +0200 Subject: Barrier: Fix the missing export Amends e46a4eba8dbe757903c10fd043bf03142836994a Change-Id: I72d451151796b342fe97375abb30f9ef433c897e Reviewed-by: Alessandro Portale --- src/libs/utils/barrier.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/barrier.h b/src/libs/utils/barrier.h index f61b2ef2b8..52b44fd703 100644 --- a/src/libs/utils/barrier.h +++ b/src/libs/utils/barrier.h @@ -70,7 +70,7 @@ using MultiBarrier = TreeStorage>; // alias template deduction only available with C++20. using SingleBarrier = MultiBarrier<1>; -class WaitForBarrier : public BarrierTask +class QTCREATOR_UTILS_EXPORT WaitForBarrier : public BarrierTask { public: template -- cgit v1.2.3 From e15b38494433a6f3e608f561517966ea0d745ddf Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Tue, 25 Apr 2023 11:22:56 +0200 Subject: TextEditor: Use icons with better contrast in TextMark tooltip The "_TOOLBAR" variants do not have a good contrast in some themes. This also introduces the missing non-toolbar variation for the EYE_OPEN icon. Fixes: QTCREATORBUG-29087 Change-Id: I64c8c6b7f5696d640c7bea7a431982caacd70050 Reviewed-by: David Schulz Reviewed-by: --- src/libs/utils/utilsicons.cpp | 2 ++ src/libs/utils/utilsicons.h | 1 + 2 files changed, 3 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/utilsicons.cpp b/src/libs/utils/utilsicons.cpp index cbc2ddf64f..01b1f0c1db 100644 --- a/src/libs/utils/utilsicons.cpp +++ b/src/libs/utils/utilsicons.cpp @@ -227,6 +227,8 @@ const Icon INTERRUPT_SMALL_TOOLBAR({ {":/utils/images/interrupt_small.png", Theme::IconsInterruptToolBarColor}}); const Icon BOUNDING_RECT({ {":/utils/images/boundingrect.png", Theme::IconsBaseColor}}); +const Icon EYE_OPEN({ + {":/utils/images/eye_open.png", Theme::PanelTextColorMid}}, Icon::Tint); const Icon EYE_OPEN_TOOLBAR({ {":/utils/images/eye_open.png", Theme::IconsBaseColor}}); const Icon EYE_CLOSED_TOOLBAR({ diff --git a/src/libs/utils/utilsicons.h b/src/libs/utils/utilsicons.h index 3e5009c94c..5a75267a36 100644 --- a/src/libs/utils/utilsicons.h +++ b/src/libs/utils/utilsicons.h @@ -121,6 +121,7 @@ QTCREATOR_UTILS_EXPORT extern const Icon STOP_SMALL_TOOLBAR; QTCREATOR_UTILS_EXPORT extern const Icon INTERRUPT_SMALL; QTCREATOR_UTILS_EXPORT extern const Icon INTERRUPT_SMALL_TOOLBAR; QTCREATOR_UTILS_EXPORT extern const Icon BOUNDING_RECT; +QTCREATOR_UTILS_EXPORT extern const Icon EYE_OPEN; QTCREATOR_UTILS_EXPORT extern const Icon EYE_OPEN_TOOLBAR; QTCREATOR_UTILS_EXPORT extern const Icon EYE_CLOSED_TOOLBAR; QTCREATOR_UTILS_EXPORT extern const Icon REPLACE; -- cgit v1.2.3 From 1d69f943aae6abbd0ea94c5130c82fe112abf618 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 3 May 2023 14:06:15 +0200 Subject: Tasking::Process: Rename Process into ProcessTask Rename QtcProcessAdapter into ProcessTaskAdapter. Task-number: QTCREATORBUG-29102 Change-Id: I1902c7176da75db60d70125f505084a2ea5ba774 Reviewed-by: hjk --- src/libs/utils/filestreamer.cpp | 6 +++--- src/libs/utils/qtcprocess.cpp | 4 ++-- src/libs/utils/qtcprocess.h | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/filestreamer.cpp b/src/libs/utils/filestreamer.cpp index c47eafaf7a..a6a3e76ae6 100644 --- a/src/libs/utils/filestreamer.cpp +++ b/src/libs/utils/filestreamer.cpp @@ -93,7 +93,7 @@ private: emit readyRead(processPtr->readAllRawStandardOutput()); }); }; - return Process(setup); + return ProcessTask(setup); } TaskItem localTask() final { const auto setup = [this](AsyncTask &async) { @@ -264,7 +264,7 @@ private: delete m_writeBuffer; m_writeBuffer = nullptr; }; - return Process(setup, finalize, finalize); + return ProcessTask(setup, finalize, finalize); } TaskItem localTask() final { const auto setup = [this](AsyncTask &async) { @@ -316,7 +316,7 @@ static Group sameRemoteDeviceTransferTask(const FilePath &source, const FilePath const FilePath cp = source.withNewPath("cp"); process.setCommand({cp, args, OsType::OsTypeLinux}); }; - return {Process(setup)}; + return {ProcessTask(setup)}; } static Group interDeviceTransferTask(const FilePath &source, const FilePath &destination) diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/qtcprocess.cpp index 5220e8b5d5..593c445648 100644 --- a/src/libs/utils/qtcprocess.cpp +++ b/src/libs/utils/qtcprocess.cpp @@ -2146,14 +2146,14 @@ void QtcProcessPrivate::storeEventLoopDebugInfo(const QVariant &value) setProperty(QTC_PROCESS_BLOCKING_TYPE, value); } -QtcProcessAdapter::QtcProcessAdapter() +ProcessTaskAdapter::ProcessTaskAdapter() { connect(task(), &QtcProcess::done, this, [this] { emit done(task()->result() == ProcessResult::FinishedWithSuccess); }); } -void QtcProcessAdapter::start() +void ProcessTaskAdapter::start() { task()->start(); } diff --git a/src/libs/utils/qtcprocess.h b/src/libs/utils/qtcprocess.h index e016b50d61..5e39453e38 100644 --- a/src/libs/utils/qtcprocess.h +++ b/src/libs/utils/qtcprocess.h @@ -207,13 +207,13 @@ public: std::function systemEnvironmentForBinary; }; -class QTCREATOR_UTILS_EXPORT QtcProcessAdapter : public Tasking::TaskAdapter +class QTCREATOR_UTILS_EXPORT ProcessTaskAdapter : public Tasking::TaskAdapter { public: - QtcProcessAdapter(); + ProcessTaskAdapter(); void start() final; }; } // namespace Utils -QTC_DECLARE_CUSTOM_TASK(Process, Utils::QtcProcessAdapter); +QTC_DECLARE_CUSTOM_TASK(ProcessTask, Utils::ProcessTaskAdapter); -- cgit v1.2.3 From 187a7640def4f137762547d82638c7f169f2cfe4 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 3 May 2023 14:33:12 +0200 Subject: Tasking::Async: Rename Async into AsyncTask Rename Utils::AsyncTask into Utils::Async. Rename AsyncTaskBase into AsyncTask. Task-number: QTCREATORBUG-29102 Change-Id: I3aa24d84138c19922d4f61b1c9cf15bc8989f60e Reviewed-by: hjk --- src/libs/utils/asynctask.h | 19 +++++++++---------- src/libs/utils/filestreamer.cpp | 18 +++++++++--------- 2 files changed, 18 insertions(+), 19 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/asynctask.h b/src/libs/utils/asynctask.h index d7c51a66fa..07b3848749 100644 --- a/src/libs/utils/asynctask.h +++ b/src/libs/utils/asynctask.h @@ -115,7 +115,7 @@ const QFuture &onFinished(const QFuture &future, QObject *guard, Function return future; } -class QTCREATOR_UTILS_EXPORT AsyncTaskBase : public QObject +class QTCREATOR_UTILS_EXPORT AsyncBase : public QObject { Q_OBJECT @@ -126,15 +126,14 @@ signals: }; template -class AsyncTask : public AsyncTaskBase +class Async : public AsyncBase { public: - AsyncTask() { - connect(&m_watcher, &QFutureWatcherBase::finished, this, &AsyncTaskBase::done); - connect(&m_watcher, &QFutureWatcherBase::resultReadyAt, - this, &AsyncTaskBase::resultReadyAt); + Async() { + connect(&m_watcher, &QFutureWatcherBase::finished, this, &AsyncBase::done); + connect(&m_watcher, &QFutureWatcherBase::resultReadyAt, this, &AsyncBase::resultReadyAt); } - ~AsyncTask() + ~Async() { if (isDone()) return; @@ -199,11 +198,11 @@ private: }; template -class AsyncTaskAdapter : public Tasking::TaskAdapter> +class AsyncTaskAdapter : public Tasking::TaskAdapter> { public: AsyncTaskAdapter() { - this->connect(this->task(), &AsyncTaskBase::done, this, [this] { + this->connect(this->task(), &AsyncBase::done, this, [this] { emit this->done(!this->task()->isCanceled()); }); } @@ -212,4 +211,4 @@ public: } // namespace Utils -QTC_DECLARE_CUSTOM_TEMPLATE_TASK(Async, AsyncTaskAdapter); +QTC_DECLARE_CUSTOM_TEMPLATE_TASK(AsyncTask, AsyncTaskAdapter); diff --git a/src/libs/utils/filestreamer.cpp b/src/libs/utils/filestreamer.cpp index a6a3e76ae6..cdc192422a 100644 --- a/src/libs/utils/filestreamer.cpp +++ b/src/libs/utils/filestreamer.cpp @@ -96,14 +96,14 @@ private: return ProcessTask(setup); } TaskItem localTask() final { - const auto setup = [this](AsyncTask &async) { + const auto setup = [this](Async &async) { async.setConcurrentCallData(localRead, m_filePath); - AsyncTask *asyncPtr = &async; - connect(asyncPtr, &AsyncTaskBase::resultReadyAt, this, [=](int index) { + Async *asyncPtr = &async; + connect(asyncPtr, &AsyncBase::resultReadyAt, this, [=](int index) { emit readyRead(asyncPtr->resultAt(index)); }); }; - return Async(setup); + return AsyncTask(setup); } }; @@ -267,16 +267,16 @@ private: return ProcessTask(setup, finalize, finalize); } TaskItem localTask() final { - const auto setup = [this](AsyncTask &async) { + const auto setup = [this](Async &async) { m_writeBuffer = new WriteBuffer(isBuffered(), &async); async.setConcurrentCallData(localWrite, m_filePath, m_writeData, m_writeBuffer); emit started(); }; - const auto finalize = [this](const AsyncTask &) { + const auto finalize = [this](const Async &) { delete m_writeBuffer; m_writeBuffer = nullptr; }; - return Async(setup, finalize, finalize); + return AsyncTask(setup, finalize, finalize); } bool isBuffered() const { return m_writeData.isEmpty(); } @@ -437,10 +437,10 @@ private: return Writer(setup); } TaskItem transferTask() { - const auto setup = [this](AsyncTask &async) { + const auto setup = [this](Async &async) { async.setConcurrentCallData(transfer, m_source, m_destination); }; - return Async(setup); + return AsyncTask(setup); } }; -- cgit v1.2.3 From 5c254bb5bf8a55fe05db6901dbe4976028828e30 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 3 May 2023 14:38:53 +0200 Subject: Tasking::Streamer: Rename Streamer into FileStreamerTask Rename FileStreamerAdapter into FileStreamerTaskAdapter. Task-number: QTCREATORBUG-29102 Change-Id: I8e8b773116a4c7203531341074b7c8efcef4f5f8 Reviewed-by: hjk --- src/libs/utils/filestreamer.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/filestreamer.h b/src/libs/utils/filestreamer.h index b572e910a2..7d274b9444 100644 --- a/src/libs/utils/filestreamer.h +++ b/src/libs/utils/filestreamer.h @@ -49,14 +49,14 @@ private: class FileStreamerPrivate *d = nullptr; }; -class FileStreamerAdapter : public Utils::Tasking::TaskAdapter +class FileStreamerTaskAdapter : public Utils::Tasking::TaskAdapter { public: - FileStreamerAdapter() { connect(task(), &FileStreamer::done, this, + FileStreamerTaskAdapter() { connect(task(), &FileStreamer::done, this, [this] { emit done(task()->result() == StreamResult::FinishedWithSuccess); }); } void start() override { task()->start(); } }; } // namespace Utils -QTC_DECLARE_CUSTOM_TASK(Streamer, Utils::FileStreamerAdapter); +QTC_DECLARE_CUSTOM_TASK(FileStreamerTask, Utils::FileStreamerTaskAdapter); -- cgit v1.2.3 From 82bc4870b37455d87e32826a55d1fe280723d07c Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 3 May 2023 14:45:54 +0200 Subject: Tasking::WaitForBarrier: Rename it into WaitForBarrierTask Rename BarrierAdapter into BarrierTaskAdapter. Task-number: QTCREATORBUG-29102 Change-Id: I003b09fd71af1bde870f761d365a8cea1858862a Reviewed-by: hjk --- src/libs/utils/barrier.h | 10 +++++----- src/libs/utils/filestreamer.cpp | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/barrier.h b/src/libs/utils/barrier.h index 52b44fd703..2af08dfdd1 100644 --- a/src/libs/utils/barrier.h +++ b/src/libs/utils/barrier.h @@ -34,16 +34,16 @@ private: int m_current = -1; }; -class QTCREATOR_UTILS_EXPORT BarrierAdapter : public Tasking::TaskAdapter +class QTCREATOR_UTILS_EXPORT BarrierTaskAdapter : public Tasking::TaskAdapter { public: - BarrierAdapter() { connect(task(), &Barrier::done, this, &TaskInterface::done); } + BarrierTaskAdapter() { connect(task(), &Barrier::done, this, &TaskInterface::done); } void start() final { task()->start(); } }; } // namespace Utils -QTC_DECLARE_CUSTOM_TASK(BarrierTask, Utils::BarrierAdapter); +QTC_DECLARE_CUSTOM_TASK(BarrierTask, Utils::BarrierTaskAdapter); namespace Utils::Tasking { @@ -70,11 +70,11 @@ using MultiBarrier = TreeStorage>; // alias template deduction only available with C++20. using SingleBarrier = MultiBarrier<1>; -class QTCREATOR_UTILS_EXPORT WaitForBarrier : public BarrierTask +class QTCREATOR_UTILS_EXPORT WaitForBarrierTask : public BarrierTask { public: template - WaitForBarrier(const MultiBarrier &sharedBarrier) + WaitForBarrierTask(const MultiBarrier &sharedBarrier) : BarrierTask([sharedBarrier](Barrier &barrier) { SharedBarrier *activeBarrier = sharedBarrier.activeStorage(); if (!activeBarrier) { diff --git a/src/libs/utils/filestreamer.cpp b/src/libs/utils/filestreamer.cpp index cdc192422a..54e5d6e1b1 100644 --- a/src/libs/utils/filestreamer.cpp +++ b/src/libs/utils/filestreamer.cpp @@ -349,7 +349,7 @@ static Group interDeviceTransferTask(const FilePath &source, const FilePath &des Storage(storage), Writer(setupWriter), Group { - WaitForBarrier(writerReadyBarrier), + WaitForBarrierTask(writerReadyBarrier), Reader(setupReader, finalizeReader, finalizeReader) } }; -- cgit v1.2.3 From a3a5b8f806a4dc6515e2fe5cdf9be0f0dead7008 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 3 May 2023 15:05:47 +0200 Subject: Utils: Rename asynctask.{cpp,h} -> async.{cpp,h} Follows AsyncTask -> Async rename. Change-Id: I37f18368ab826c9960a24087b52f6691bb33f225 Reviewed-by: hjk --- src/libs/qmljs/qmljsmodelmanagerinterface.cpp | 2 +- src/libs/qmljs/qmljsplugindumper.cpp | 2 +- src/libs/tracing/timelinetracemanager.cpp | 2 +- src/libs/utils/CMakeLists.txt | 2 +- src/libs/utils/async.cpp | 57 +++++++ src/libs/utils/async.h | 214 ++++++++++++++++++++++++++ src/libs/utils/asynctask.cpp | 57 ------- src/libs/utils/asynctask.h | 214 -------------------------- src/libs/utils/filestreamer.cpp | 2 +- src/libs/utils/stringtable.cpp | 2 +- src/libs/utils/utils.qbs | 4 +- 11 files changed, 279 insertions(+), 279 deletions(-) create mode 100644 src/libs/utils/async.cpp create mode 100644 src/libs/utils/async.h delete mode 100644 src/libs/utils/asynctask.cpp delete mode 100644 src/libs/utils/asynctask.h (limited to 'src/libs') diff --git a/src/libs/qmljs/qmljsmodelmanagerinterface.cpp b/src/libs/qmljs/qmljsmodelmanagerinterface.cpp index f6c84fb88f..6bcfe7ee25 100644 --- a/src/libs/qmljs/qmljsmodelmanagerinterface.cpp +++ b/src/libs/qmljs/qmljsmodelmanagerinterface.cpp @@ -15,7 +15,7 @@ #include #include -#include +#include #include #include diff --git a/src/libs/qmljs/qmljsplugindumper.cpp b/src/libs/qmljs/qmljsplugindumper.cpp index 9e2261014f..3de270923b 100644 --- a/src/libs/qmljs/qmljsplugindumper.cpp +++ b/src/libs/qmljs/qmljsplugindumper.cpp @@ -9,7 +9,7 @@ #include "qmljsutils.h" #include -#include +#include #include #include #include diff --git a/src/libs/tracing/timelinetracemanager.cpp b/src/libs/tracing/timelinetracemanager.cpp index b252a02cee..98f333da91 100644 --- a/src/libs/tracing/timelinetracemanager.cpp +++ b/src/libs/tracing/timelinetracemanager.cpp @@ -6,7 +6,7 @@ #include "timelinetracemanager.h" #include "tracingtr.h" -#include +#include #include #include diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index 8660e65b84..f26c08c7b5 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -12,7 +12,7 @@ add_qtc_library(Utils appmainwindow.cpp appmainwindow.h archive.cpp archive.h aspects.cpp aspects.h - asynctask.cpp asynctask.h + async.cpp async.h barrier.cpp barrier.h basetreeview.cpp basetreeview.h benchmarker.cpp benchmarker.h diff --git a/src/libs/utils/async.cpp b/src/libs/utils/async.cpp new file mode 100644 index 0000000000..9295b50dc1 --- /dev/null +++ b/src/libs/utils/async.cpp @@ -0,0 +1,57 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "async.h" + +#include + +namespace Utils { + +static int s_maxThreadCount = INT_MAX; + +class AsyncThreadPool : public QThreadPool +{ +public: + AsyncThreadPool(QThread::Priority priority) { + setThreadPriority(priority); + setMaxThreadCount(s_maxThreadCount); + moveToThread(qApp->thread()); + } +}; + +#if QT_VERSION < QT_VERSION_CHECK(6, 3, 0) +Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_idle, (QThread::IdlePriority)); +Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_lowest, (QThread::LowestPriority)); +Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_low, (QThread::LowPriority)); +Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_normal, (QThread::NormalPriority)); +Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_high, (QThread::HighPriority)); +Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_highest, (QThread::HighestPriority)); +Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_timeCritical, (QThread::TimeCriticalPriority)); +Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_inherit, (QThread::InheritPriority)); +#else +Q_GLOBAL_STATIC(AsyncThreadPool, s_idle, QThread::IdlePriority); +Q_GLOBAL_STATIC(AsyncThreadPool, s_lowest, QThread::LowestPriority); +Q_GLOBAL_STATIC(AsyncThreadPool, s_low, QThread::LowPriority); +Q_GLOBAL_STATIC(AsyncThreadPool, s_normal, QThread::NormalPriority); +Q_GLOBAL_STATIC(AsyncThreadPool, s_high, QThread::HighPriority); +Q_GLOBAL_STATIC(AsyncThreadPool, s_highest, QThread::HighestPriority); +Q_GLOBAL_STATIC(AsyncThreadPool, s_timeCritical, QThread::TimeCriticalPriority); +Q_GLOBAL_STATIC(AsyncThreadPool, s_inherit, QThread::InheritPriority); +#endif + +QThreadPool *asyncThreadPool(QThread::Priority priority) +{ + switch (priority) { + case QThread::IdlePriority : return s_idle; + case QThread::LowestPriority : return s_lowest; + case QThread::LowPriority : return s_low; + case QThread::NormalPriority : return s_normal; + case QThread::HighPriority : return s_high; + case QThread::HighestPriority : return s_highest; + case QThread::TimeCriticalPriority : return s_timeCritical; + case QThread::InheritPriority : return s_inherit; + } + return nullptr; +} + +} // namespace Utils diff --git a/src/libs/utils/async.h b/src/libs/utils/async.h new file mode 100644 index 0000000000..07b3848749 --- /dev/null +++ b/src/libs/utils/async.h @@ -0,0 +1,214 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "utils_global.h" + +#include "futuresynchronizer.h" +#include "qtcassert.h" +#include "tasktree.h" + +#include +#include + +namespace Utils { + +QTCREATOR_UTILS_EXPORT QThreadPool *asyncThreadPool(QThread::Priority priority); + +template +auto asyncRun(QThreadPool *threadPool, QThread::Priority priority, + Function &&function, Args &&...args) +{ + QThreadPool *pool = threadPool ? threadPool : asyncThreadPool(priority); + return QtConcurrent::run(pool, std::forward(function), std::forward(args)...); +} + +template +auto asyncRun(QThread::Priority priority, Function &&function, Args &&...args) +{ + return asyncRun(nullptr, priority, + std::forward(function), std::forward(args)...); +} + +template +auto asyncRun(QThreadPool *threadPool, Function &&function, Args &&...args) +{ + return asyncRun(threadPool, QThread::InheritPriority, + std::forward(function), std::forward(args)...); +} + +template +auto asyncRun(Function &&function, Args &&...args) +{ + return asyncRun(nullptr, QThread::InheritPriority, + std::forward(function), std::forward(args)...); +} + +/*! + Adds a handler for when a result is ready. + This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions + or create a QFutureWatcher already for other reasons. +*/ +template +const QFuture &onResultReady(const QFuture &future, R *receiver, void(R::*member)(const T &)) +{ + auto watcher = new QFutureWatcher(); + QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); + QObject::connect(watcher, &QFutureWatcherBase::resultReadyAt, receiver, [=](int index) { + (receiver->*member)(watcher->future().resultAt(index)); + }); + watcher->setFuture(future); + return future; +} + +/*! + Adds a handler for when a result is ready. The guard object determines the lifetime of + the connection. + This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions + or create a QFutureWatcher already for other reasons. +*/ +template +const QFuture &onResultReady(const QFuture &future, QObject *guard, Function f) +{ + auto watcher = new QFutureWatcher(); + QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); + QObject::connect(watcher, &QFutureWatcherBase::resultReadyAt, guard, [f, watcher](int index) { + f(watcher->future().resultAt(index)); + }); + watcher->setFuture(future); + return future; +} + +/*! + Adds a handler for when the future is finished. + This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions + or create a QFutureWatcher already for other reasons. +*/ +template +const QFuture &onFinished(const QFuture &future, + R *receiver, void (R::*member)(const QFuture &)) +{ + auto watcher = new QFutureWatcher(); + QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); + QObject::connect(watcher, &QFutureWatcherBase::finished, receiver, + [=] { (receiver->*member)(watcher->future()); }); + watcher->setFuture(future); + return future; +} + +/*! + Adds a handler for when the future is finished. The guard object determines the lifetime of + the connection. + This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions + or create a QFutureWatcher already for other reasons. +*/ +template +const QFuture &onFinished(const QFuture &future, QObject *guard, Function f) +{ + auto watcher = new QFutureWatcher(); + QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); + QObject::connect(watcher, &QFutureWatcherBase::finished, guard, [f, watcher] { + f(watcher->future()); + }); + watcher->setFuture(future); + return future; +} + +class QTCREATOR_UTILS_EXPORT AsyncBase : public QObject +{ + Q_OBJECT + +signals: + void started(); + void done(); + void resultReadyAt(int index); +}; + +template +class Async : public AsyncBase +{ +public: + Async() { + connect(&m_watcher, &QFutureWatcherBase::finished, this, &AsyncBase::done); + connect(&m_watcher, &QFutureWatcherBase::resultReadyAt, this, &AsyncBase::resultReadyAt); + } + ~Async() + { + if (isDone()) + return; + + m_watcher.cancel(); + if (!m_synchronizer) + m_watcher.waitForFinished(); + } + + template + void setConcurrentCallData(Function &&function, Args &&...args) + { + return wrapConcurrent(std::forward(function), std::forward(args)...); + } + + void setFutureSynchronizer(FutureSynchronizer *synchorizer) { m_synchronizer = synchorizer; } + void setThreadPool(QThreadPool *pool) { m_threadPool = pool; } + void setPriority(QThread::Priority priority) { m_priority = priority; } + + void start() + { + QTC_ASSERT(m_startHandler, qWarning("No start handler specified."); return); + m_watcher.setFuture(m_startHandler()); + emit started(); + if (m_synchronizer) + m_synchronizer->addFuture(m_watcher.future()); + } + + bool isDone() const { return m_watcher.isFinished(); } + bool isCanceled() const { return m_watcher.isCanceled(); } + + QFuture future() const { return m_watcher.future(); } + ResultType result() const { return m_watcher.result(); } + ResultType resultAt(int index) const { return m_watcher.resultAt(index); } + QList results() const { return future().results(); } + bool isResultAvailable() const { return future().resultCount(); } + +private: + template + void wrapConcurrent(Function &&function, Args &&...args) + { + m_startHandler = [=] { + return asyncRun(m_threadPool, m_priority, function, args...); + }; + } + + template + void wrapConcurrent(std::reference_wrapper &&wrapper, Args &&...args) + { + m_startHandler = [=] { + return asyncRun(m_threadPool, m_priority, std::forward(wrapper.get()), + args...); + }; + } + + using StartHandler = std::function()>; + StartHandler m_startHandler; + FutureSynchronizer *m_synchronizer = nullptr; + QThreadPool *m_threadPool = nullptr; + QThread::Priority m_priority = QThread::InheritPriority; + QFutureWatcher m_watcher; +}; + +template +class AsyncTaskAdapter : public Tasking::TaskAdapter> +{ +public: + AsyncTaskAdapter() { + this->connect(this->task(), &AsyncBase::done, this, [this] { + emit this->done(!this->task()->isCanceled()); + }); + } + void start() final { this->task()->start(); } +}; + +} // namespace Utils + +QTC_DECLARE_CUSTOM_TEMPLATE_TASK(AsyncTask, AsyncTaskAdapter); diff --git a/src/libs/utils/asynctask.cpp b/src/libs/utils/asynctask.cpp deleted file mode 100644 index c2c42431d1..0000000000 --- a/src/libs/utils/asynctask.cpp +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "asynctask.h" - -#include - -namespace Utils { - -static int s_maxThreadCount = INT_MAX; - -class AsyncThreadPool : public QThreadPool -{ -public: - AsyncThreadPool(QThread::Priority priority) { - setThreadPriority(priority); - setMaxThreadCount(s_maxThreadCount); - moveToThread(qApp->thread()); - } -}; - -#if QT_VERSION < QT_VERSION_CHECK(6, 3, 0) -Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_idle, (QThread::IdlePriority)); -Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_lowest, (QThread::LowestPriority)); -Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_low, (QThread::LowPriority)); -Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_normal, (QThread::NormalPriority)); -Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_high, (QThread::HighPriority)); -Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_highest, (QThread::HighestPriority)); -Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_timeCritical, (QThread::TimeCriticalPriority)); -Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_inherit, (QThread::InheritPriority)); -#else -Q_GLOBAL_STATIC(AsyncThreadPool, s_idle, QThread::IdlePriority); -Q_GLOBAL_STATIC(AsyncThreadPool, s_lowest, QThread::LowestPriority); -Q_GLOBAL_STATIC(AsyncThreadPool, s_low, QThread::LowPriority); -Q_GLOBAL_STATIC(AsyncThreadPool, s_normal, QThread::NormalPriority); -Q_GLOBAL_STATIC(AsyncThreadPool, s_high, QThread::HighPriority); -Q_GLOBAL_STATIC(AsyncThreadPool, s_highest, QThread::HighestPriority); -Q_GLOBAL_STATIC(AsyncThreadPool, s_timeCritical, QThread::TimeCriticalPriority); -Q_GLOBAL_STATIC(AsyncThreadPool, s_inherit, QThread::InheritPriority); -#endif - -QThreadPool *asyncThreadPool(QThread::Priority priority) -{ - switch (priority) { - case QThread::IdlePriority : return s_idle; - case QThread::LowestPriority : return s_lowest; - case QThread::LowPriority : return s_low; - case QThread::NormalPriority : return s_normal; - case QThread::HighPriority : return s_high; - case QThread::HighestPriority : return s_highest; - case QThread::TimeCriticalPriority : return s_timeCritical; - case QThread::InheritPriority : return s_inherit; - } - return nullptr; -} - -} // namespace Utils diff --git a/src/libs/utils/asynctask.h b/src/libs/utils/asynctask.h deleted file mode 100644 index 07b3848749..0000000000 --- a/src/libs/utils/asynctask.h +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "utils_global.h" - -#include "futuresynchronizer.h" -#include "qtcassert.h" -#include "tasktree.h" - -#include -#include - -namespace Utils { - -QTCREATOR_UTILS_EXPORT QThreadPool *asyncThreadPool(QThread::Priority priority); - -template -auto asyncRun(QThreadPool *threadPool, QThread::Priority priority, - Function &&function, Args &&...args) -{ - QThreadPool *pool = threadPool ? threadPool : asyncThreadPool(priority); - return QtConcurrent::run(pool, std::forward(function), std::forward(args)...); -} - -template -auto asyncRun(QThread::Priority priority, Function &&function, Args &&...args) -{ - return asyncRun(nullptr, priority, - std::forward(function), std::forward(args)...); -} - -template -auto asyncRun(QThreadPool *threadPool, Function &&function, Args &&...args) -{ - return asyncRun(threadPool, QThread::InheritPriority, - std::forward(function), std::forward(args)...); -} - -template -auto asyncRun(Function &&function, Args &&...args) -{ - return asyncRun(nullptr, QThread::InheritPriority, - std::forward(function), std::forward(args)...); -} - -/*! - Adds a handler for when a result is ready. - This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions - or create a QFutureWatcher already for other reasons. -*/ -template -const QFuture &onResultReady(const QFuture &future, R *receiver, void(R::*member)(const T &)) -{ - auto watcher = new QFutureWatcher(); - QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); - QObject::connect(watcher, &QFutureWatcherBase::resultReadyAt, receiver, [=](int index) { - (receiver->*member)(watcher->future().resultAt(index)); - }); - watcher->setFuture(future); - return future; -} - -/*! - Adds a handler for when a result is ready. The guard object determines the lifetime of - the connection. - This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions - or create a QFutureWatcher already for other reasons. -*/ -template -const QFuture &onResultReady(const QFuture &future, QObject *guard, Function f) -{ - auto watcher = new QFutureWatcher(); - QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); - QObject::connect(watcher, &QFutureWatcherBase::resultReadyAt, guard, [f, watcher](int index) { - f(watcher->future().resultAt(index)); - }); - watcher->setFuture(future); - return future; -} - -/*! - Adds a handler for when the future is finished. - This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions - or create a QFutureWatcher already for other reasons. -*/ -template -const QFuture &onFinished(const QFuture &future, - R *receiver, void (R::*member)(const QFuture &)) -{ - auto watcher = new QFutureWatcher(); - QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); - QObject::connect(watcher, &QFutureWatcherBase::finished, receiver, - [=] { (receiver->*member)(watcher->future()); }); - watcher->setFuture(future); - return future; -} - -/*! - Adds a handler for when the future is finished. The guard object determines the lifetime of - the connection. - This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions - or create a QFutureWatcher already for other reasons. -*/ -template -const QFuture &onFinished(const QFuture &future, QObject *guard, Function f) -{ - auto watcher = new QFutureWatcher(); - QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); - QObject::connect(watcher, &QFutureWatcherBase::finished, guard, [f, watcher] { - f(watcher->future()); - }); - watcher->setFuture(future); - return future; -} - -class QTCREATOR_UTILS_EXPORT AsyncBase : public QObject -{ - Q_OBJECT - -signals: - void started(); - void done(); - void resultReadyAt(int index); -}; - -template -class Async : public AsyncBase -{ -public: - Async() { - connect(&m_watcher, &QFutureWatcherBase::finished, this, &AsyncBase::done); - connect(&m_watcher, &QFutureWatcherBase::resultReadyAt, this, &AsyncBase::resultReadyAt); - } - ~Async() - { - if (isDone()) - return; - - m_watcher.cancel(); - if (!m_synchronizer) - m_watcher.waitForFinished(); - } - - template - void setConcurrentCallData(Function &&function, Args &&...args) - { - return wrapConcurrent(std::forward(function), std::forward(args)...); - } - - void setFutureSynchronizer(FutureSynchronizer *synchorizer) { m_synchronizer = synchorizer; } - void setThreadPool(QThreadPool *pool) { m_threadPool = pool; } - void setPriority(QThread::Priority priority) { m_priority = priority; } - - void start() - { - QTC_ASSERT(m_startHandler, qWarning("No start handler specified."); return); - m_watcher.setFuture(m_startHandler()); - emit started(); - if (m_synchronizer) - m_synchronizer->addFuture(m_watcher.future()); - } - - bool isDone() const { return m_watcher.isFinished(); } - bool isCanceled() const { return m_watcher.isCanceled(); } - - QFuture future() const { return m_watcher.future(); } - ResultType result() const { return m_watcher.result(); } - ResultType resultAt(int index) const { return m_watcher.resultAt(index); } - QList results() const { return future().results(); } - bool isResultAvailable() const { return future().resultCount(); } - -private: - template - void wrapConcurrent(Function &&function, Args &&...args) - { - m_startHandler = [=] { - return asyncRun(m_threadPool, m_priority, function, args...); - }; - } - - template - void wrapConcurrent(std::reference_wrapper &&wrapper, Args &&...args) - { - m_startHandler = [=] { - return asyncRun(m_threadPool, m_priority, std::forward(wrapper.get()), - args...); - }; - } - - using StartHandler = std::function()>; - StartHandler m_startHandler; - FutureSynchronizer *m_synchronizer = nullptr; - QThreadPool *m_threadPool = nullptr; - QThread::Priority m_priority = QThread::InheritPriority; - QFutureWatcher m_watcher; -}; - -template -class AsyncTaskAdapter : public Tasking::TaskAdapter> -{ -public: - AsyncTaskAdapter() { - this->connect(this->task(), &AsyncBase::done, this, [this] { - emit this->done(!this->task()->isCanceled()); - }); - } - void start() final { this->task()->start(); } -}; - -} // namespace Utils - -QTC_DECLARE_CUSTOM_TEMPLATE_TASK(AsyncTask, AsyncTaskAdapter); diff --git a/src/libs/utils/filestreamer.cpp b/src/libs/utils/filestreamer.cpp index 54e5d6e1b1..744f682617 100644 --- a/src/libs/utils/filestreamer.cpp +++ b/src/libs/utils/filestreamer.cpp @@ -3,7 +3,7 @@ #include "filestreamer.h" -#include "asynctask.h" +#include "async.h" #include "barrier.h" #include "qtcprocess.h" diff --git a/src/libs/utils/stringtable.cpp b/src/libs/utils/stringtable.cpp index e501203a10..73cad02588 100644 --- a/src/libs/utils/stringtable.cpp +++ b/src/libs/utils/stringtable.cpp @@ -3,7 +3,7 @@ #include "stringtable.h" -#include "asynctask.h" +#include "async.h" #include #include diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index eb8c2a9f56..ba2bc175f8 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -49,8 +49,8 @@ Project { "archive.h", "aspects.cpp", "aspects.h", - "asynctask.cpp", - "asynctask.h", + "async.cpp", + "async.h", "barrier.cpp", "barrier.h", "basetreeview.cpp", -- cgit v1.2.3 From 70b02d23e1a32a1c4911644cb73b7247430a4416 Mon Sep 17 00:00:00 2001 From: hjk Date: Thu, 27 Apr 2023 08:24:43 +0200 Subject: LayoutBuilder: Rework Everying is a LayoutItem now, and everything is split into a proper setup and execution phase. Execution happens only via LayoutBuilder (directly or via convenience wrappers in LayoutItem). No direct access to the widget in creation, funnel out is via the new bindTo() facility. Change-Id: I7eb38fd736ae57a68f9a72a6add5c767da82b49f Reviewed-by: Qt CI Bot Reviewed-by: Alessandro Portale --- src/libs/utils/aspects.cpp | 32 +- src/libs/utils/aspects.h | 8 +- src/libs/utils/layoutbuilder.cpp | 711 ++++++++++++++++++++++++--------------- src/libs/utils/layoutbuilder.h | 265 ++++++--------- 4 files changed, 566 insertions(+), 450 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index baf80e3fe7..fd953d2493 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -216,9 +216,7 @@ void BaseAspect::addLabeledItem(Layouting::LayoutBuilder &builder, QWidget *widg if (QLabel *l = label()) { l->setBuddy(widget); builder.addItem(l); - LayoutItem item(widget); - item.span = std::max(d->m_spanX - 1, 1); - builder.addItem(item); + builder.addItem(Span(std::max(d->m_spanX - 1, 1), LayoutItem(widget))); } else { builder.addItem(LayoutItem(widget)); } @@ -425,13 +423,23 @@ void BaseAspect::addToLayout(LayoutBuilder &) { } -void doLayout(const BaseAspect &aspect, LayoutBuilder &builder) +void createItem(Layouting::LayoutItem *item, const BaseAspect &aspect) { - const_cast(aspect).addToLayout(builder); - if (builder.layoutType() == LayoutBuilder::FormLayout || builder.layoutType() == LayoutBuilder::VBoxLayout) - builder.finishRow(); + item->onAdd = [&aspect](LayoutBuilder &builder) { + const_cast(aspect).addToLayout(builder); + builder.addItem(br); + }; +} + +void createItem(Layouting::LayoutItem *item, const BaseAspect *aspect) +{ + item->onAdd = [aspect](LayoutBuilder &builder) { + const_cast(aspect)->addToLayout(builder); + builder.addItem(br); + }; } + /*! Updates this aspect's value from user-initiated changes in the widget. @@ -1063,7 +1071,7 @@ void StringAspect::addToLayout(Layouting::LayoutBuilder &builder) { if (d->m_checker && d->m_checkBoxPlacement == CheckBoxPlacement::Top) { d->m_checker->addToLayout(builder); - builder.finishRow(); + builder.addItem(br); } const auto useMacroExpander = [this](QWidget *w) { @@ -1404,8 +1412,7 @@ void BoolAspect::addToLayout(Layouting::LayoutBuilder &builder) break; case LabelPlacement::AtCheckBox: { d->m_checkBox->setText(labelText()); - Layouting::LayoutBuilder::LayoutType type = builder.layoutType(); - if (type == LayoutBuilder::FormLayout) + if (builder.isForm()) builder.addItem(createSubWidget()); builder.addItem(d->m_checkBox.data()); break; @@ -1566,7 +1573,8 @@ void SelectionAspect::addToLayout(Layouting::LayoutBuilder &builder) button->setChecked(i == value()); button->setEnabled(option.enabled); button->setToolTip(option.tooltip); - builder.addItems({Layouting::empty, button}); + builder.addItem(Layouting::empty); + builder.addItem(button); d->m_buttons.append(button); d->m_buttonGroup->addButton(button, i); if (isAutoApply()) { @@ -2284,7 +2292,7 @@ TextDisplay::~TextDisplay() = default; /*! \reimp */ -void TextDisplay::addToLayout(LayoutBuilder &builder) +void TextDisplay::addToLayout(Layouting::LayoutBuilder &builder) { if (!d->m_label) { d->m_label = createSubWidget(d->m_message, d->m_type); diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index 9950fbef61..3eae9ffaea 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -18,7 +18,10 @@ class QAction; class QSettings; QT_END_NAMESPACE -namespace Layouting { class LayoutBuilder; } +namespace Layouting { +class LayoutItem; +class LayoutBuilder; +} namespace Utils { @@ -204,7 +207,8 @@ private: std::unique_ptr d; }; -QTCREATOR_UTILS_EXPORT void doLayout(const BaseAspect &aspect, Layouting::LayoutBuilder &builder); +QTCREATOR_UTILS_EXPORT void createItem(Layouting::LayoutItem *item, const BaseAspect &aspect); +QTCREATOR_UTILS_EXPORT void createItem(Layouting::LayoutItem *item, const BaseAspect *aspect); class QTCREATOR_UTILS_EXPORT BoolAspect : public BaseAspect { diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index e86e69a830..c7ead009c7 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -10,10 +10,12 @@ #include #include #include +#include #include #include #include #include +#include namespace Layouting { @@ -26,20 +28,7 @@ namespace Layouting { /*! - \enum Utils::LayoutBuilder::LayoutType - \inmodule QtCreator - - The LayoutType enum describes the type of \c QLayout a layout builder - operates on. - - \value Form - \value Grid - \value HBox - \value VBox -*/ - -/*! - \class Utils::LayoutBuilder::LayoutItem + \class Layouting::LayoutItem \inmodule QtCreator \brief The LayoutItem class represents widgets, layouts, and aggregate @@ -53,8 +42,9 @@ namespace Layouting { /*! Constructs a layout item instance representing an empty cell. */ -LayoutItem::LayoutItem() -{} +LayoutItem::LayoutItem() = default; + +LayoutItem::~LayoutItem() = default; /*! @@ -70,47 +60,38 @@ LayoutItem::LayoutItem() \endlist */ +struct ResultItem +{ + ResultItem() = default; + explicit ResultItem(QLayout *l) : layout(l) {} + explicit ResultItem(QWidget *w) : widget(w) {} -/*! - Constructs a layout item representing something that knows how to add it - to a layout by itself. - */ -QLayout *LayoutBuilder::createLayout() const + QString text; + QLayout *layout = nullptr; + QWidget *widget = nullptr; + int space = -1; + int stretch = -1; + int span = 1; +}; + +struct LayoutBuilder::Slice { + Slice() = default; + Slice(QLayout *l) : layout(l) {} + Slice(QWidget *w) : widget(w) {} + Slice(QWidget *w, AttachType a) : widget(w), attachType(a) {} + QLayout *layout = nullptr; - switch (m_layoutType) { - case LayoutBuilder::FormLayout: { - auto formLayout = new QFormLayout; - formLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); - layout = formLayout; - break; - } - case LayoutBuilder::GridLayout: { - auto gridLayout = new QGridLayout; - layout = gridLayout; - break; - } - case LayoutBuilder::HBoxLayout: { - auto hboxLayout = new QHBoxLayout; - layout = hboxLayout; - break; - } - case LayoutBuilder::VBoxLayout: { - auto vboxLayout = new QVBoxLayout; - layout = vboxLayout; - break; - } - case LayoutBuilder::StackLayout: { - auto stackLayout = new QStackedLayout; - layout = stackLayout; - break; - } - } - QTC_ASSERT(layout, return nullptr); - if (m_spacing) - layout->setSpacing(*m_spacing); - return layout; -} + QWidget *widget = nullptr; + + void flush(); + + int currentGridColumn = 0; + int currentGridRow = 0; + + AttachType attachType = WithMargins; + QList pendingItems; +}; static QWidget *widgetForItem(QLayoutItem *item) { @@ -135,18 +116,16 @@ static QLabel *createLabel(const QString &text) return label; } -static void addItemToBoxLayout(QBoxLayout *layout, const LayoutItem &item) +static void addItemToBoxLayout(QBoxLayout *layout, const ResultItem &item) { if (QWidget *w = item.widget) { layout->addWidget(w); } else if (QLayout *l = item.layout) { layout->addLayout(l); - } else if (item.specialType == LayoutItem::SpecialType::Stretch) { - layout->addStretch(item.specialValue.toInt()); - } else if (item.specialType == LayoutItem::SpecialType::Space) { - layout->addSpacing(item.specialValue.toInt()); - } else if (item.specialType == LayoutItem::SpecialType::HorizontalRule) { - layout->addWidget(Layouting::createHr()); + } else if (item.stretch != -1) { + layout->addStretch(item.stretch); + } else if (item.space != -1) { + layout->addSpacing(item.space); } else if (!item.text.isEmpty()) { layout->addWidget(createLabel(item.text)); } else { @@ -154,138 +133,168 @@ static void addItemToBoxLayout(QBoxLayout *layout, const LayoutItem &item) } } -static void flushPendingFormItems(QFormLayout *formLayout, - LayoutBuilder::LayoutItems &pendingFormItems) +void LayoutBuilder::Slice::flush() { - QTC_ASSERT(formLayout, return); - - if (pendingFormItems.empty()) + if (pendingItems.empty()) return; - // If there are more than two items, we cram the last ones in one hbox. - if (pendingFormItems.size() > 2) { - auto hbox = new QHBoxLayout; - hbox->setContentsMargins(0, 0, 0, 0); - for (int i = 1; i < pendingFormItems.size(); ++i) - addItemToBoxLayout(hbox, pendingFormItems.at(i)); - while (pendingFormItems.size() >= 2) - pendingFormItems.pop_back(); - pendingFormItems.append(LayoutItem(hbox)); - } + if (auto formLayout = qobject_cast(layout)) { + + // If there are more than two items, we cram the last ones in one hbox. + if (pendingItems.size() > 2) { + auto hbox = new QHBoxLayout; + hbox->setContentsMargins(0, 0, 0, 0); + for (int i = 1; i < pendingItems.size(); ++i) + addItemToBoxLayout(hbox, pendingItems.at(i)); + while (pendingItems.size() > 1) + pendingItems.pop_back(); + pendingItems.append(ResultItem(hbox)); + } - if (pendingFormItems.size() == 1) { // One one item given, so this spans both columns. - if (auto layout = pendingFormItems.at(0).layout) - formLayout->addRow(layout); - else if (auto widget = pendingFormItems.at(0).widget) - formLayout->addRow(widget); - } else if (pendingFormItems.size() == 2) { // Normal case, both columns used. - if (auto label = pendingFormItems.at(0).widget) { - if (auto layout = pendingFormItems.at(1).layout) - formLayout->addRow(label, layout); - else if (auto widget = pendingFormItems.at(1).widget) - formLayout->addRow(label, widget); - } else { - if (auto layout = pendingFormItems.at(1).layout) - formLayout->addRow(pendingFormItems.at(0).text, layout); - else if (auto widget = pendingFormItems.at(1).widget) - formLayout->addRow(pendingFormItems.at(0).text, widget); + if (pendingItems.size() == 1) { // One one item given, so this spans both columns. + const ResultItem &f0 = pendingItems.at(0); + if (auto layout = f0.layout) + formLayout->addRow(layout); + else if (auto widget = f0.widget) + formLayout->addRow(widget); + } else if (pendingItems.size() == 2) { // Normal case, both columns used. + ResultItem &f1 = pendingItems[1]; + const ResultItem &f0 = pendingItems.at(0); + if (!f1.widget && !f1.layout && !f1.text.isEmpty()) + f1.widget = createLabel(f1.text); + + if (f0.widget) { + if (f1.layout) + formLayout->addRow(f0.widget, f1.layout); + else if (f1.widget) + formLayout->addRow(f0.widget, f1.widget); + } else { + if (f1.layout) + formLayout->addRow(f0.text, f1.layout); + else if (f1.widget) + formLayout->addRow(f0.text, f1.widget); + } + } else { + QTC_CHECK(false); } + + // Set up label as buddy if possible. + const int lastRow = formLayout->rowCount() - 1; + QLayoutItem *l = formLayout->itemAt(lastRow, QFormLayout::LabelRole); + QLayoutItem *f = formLayout->itemAt(lastRow, QFormLayout::FieldRole); + if (l && f) { + if (QLabel *label = qobject_cast(l->widget())) { + if (QWidget *widget = widgetForItem(f)) + label->setBuddy(widget); + } + } + + } else if (auto gridLayout = qobject_cast(layout)) { + + for (const ResultItem &item : std::as_const(pendingItems)) { + Qt::Alignment align = {}; + // if (attachType == Layouting::WithFormAlignment && currentGridColumn == 0) + // align = Qt::Alignment(m_widget->style()->styleHint(QStyle::SH_FormLayoutLabelAlignment)); + if (item.widget) + gridLayout->addWidget(item.widget, currentGridRow, currentGridColumn, 1, item.span, align); + else if (item.layout) + gridLayout->addLayout(item.layout, currentGridRow, currentGridColumn, 1, item.span, align); + else if (!item.text.isEmpty()) + gridLayout->addWidget(createLabel(item.text), currentGridRow, currentGridColumn, 1, 1, align); + currentGridColumn += item.span; + } + ++currentGridRow; + currentGridColumn = 0; + + } else if (auto boxLayout = qobject_cast(layout)) { + + for (const ResultItem &item : std::as_const(pendingItems)) + addItemToBoxLayout(boxLayout, item); + + } else if (auto stackLayout = qobject_cast(layout)) { + for (const ResultItem &item : std::as_const(pendingItems)) { + if (item.widget) + stackLayout->addWidget(item.widget); + else + QTC_CHECK(false); + } + } else { QTC_CHECK(false); } - // Set up label as buddy if possible. - const int lastRow = formLayout->rowCount() - 1; - QLayoutItem *l = formLayout->itemAt(lastRow, QFormLayout::LabelRole); - QLayoutItem *f = formLayout->itemAt(lastRow, QFormLayout::FieldRole); - if (l && f) { - if (QLabel *label = qobject_cast(l->widget())) { - if (QWidget *widget = widgetForItem(f)) - label->setBuddy(widget); - } + pendingItems.clear(); +} + +static void addItemHelper(LayoutBuilder &builder, const LayoutItem &item) +{ + if (item.onAdd) + item.onAdd(builder); + + if (item.setter) { + if (QWidget *widget = builder.stack.last().widget) + item.setter(widget); + else if (QLayout *layout = builder.stack.last().layout) + item.setter(layout); + else + QTC_CHECK(false); } - pendingFormItems.clear(); + for (const LayoutItem &subItem : item.subItems) + addItemHelper(builder, subItem); + + if (item.onExit) + item.onExit(builder); } -static void doLayoutHelper(QLayout *layout, - const LayoutBuilder::LayoutItems &items, - const Layouting::AttachType attachType, - int currentGridRow = 0) +void doAddText(LayoutBuilder &builder, const QString &text) { - int currentGridColumn = 0; - LayoutBuilder::LayoutItems pendingFormItems; - - auto formLayout = qobject_cast(layout); - auto gridLayout = qobject_cast(layout); - auto boxLayout = qobject_cast(layout); - auto stackLayout = qobject_cast(layout); - - for (const LayoutItem &item : items) { - if (item.specialType == LayoutItem::SpecialType::Break) { - if (formLayout) - flushPendingFormItems(formLayout, pendingFormItems); - else if (gridLayout) { - if (currentGridColumn != 0) { - ++currentGridRow; - currentGridColumn = 0; - } - } - continue; - } + ResultItem fi; + fi.text = text; + builder.stack.last().pendingItems.append(fi); +} - QWidget *widget = item.widget; - - if (gridLayout) { - Qt::Alignment align = {}; - if (attachType == Layouting::WithFormAlignment && currentGridColumn == 0) - align = Qt::Alignment(widget->style()->styleHint(QStyle::SH_FormLayoutLabelAlignment)); - if (widget) - gridLayout->addWidget(widget, currentGridRow, currentGridColumn, 1, item.span, align); - else if (item.layout) - gridLayout->addLayout(item.layout, currentGridRow, currentGridColumn, 1, item.span, align); - else if (!item.text.isEmpty()) - gridLayout->addWidget(createLabel(item.text), currentGridRow, currentGridColumn, 1, 1, align); - currentGridColumn += item.span; - } else if (boxLayout) { - addItemToBoxLayout(boxLayout, item); - } else if (stackLayout) { - stackLayout->addWidget(item.widget); - } else { - pendingFormItems.append(item); - } - } +void doAddSpace(LayoutBuilder &builder, const Space &space) +{ + ResultItem fi; + fi.space = space.space; + builder.stack.last().pendingItems.append(fi); +} - if (formLayout) - flushPendingFormItems(formLayout, pendingFormItems); +void doAddStretch(LayoutBuilder &builder, const Stretch &stretch) +{ + ResultItem fi; + fi.stretch = stretch.stretch; + builder.stack.last().pendingItems.append(fi); } +void doAddLayout(LayoutBuilder &builder, QLayout *layout) +{ + builder.stack.last().pendingItems.append(ResultItem(layout)); +} -/*! - Constructs a layout item from the contents of another LayoutBuilder - */ -void LayoutItem::setBuilder(const LayoutBuilder &builder) +void doAddWidget(LayoutBuilder &builder, QWidget *widget) { - layout = builder.createLayout(); - doLayoutHelper(layout, builder.m_items, Layouting::WithoutMargins); + builder.stack.last().pendingItems.append(ResultItem(widget)); } + /*! - \class Utils::LayoutBuilder::Space + \class Layouting::Space \inmodule QtCreator - \brief The LayoutBuilder::Space class represents some empty space in a layout. + \brief The Layouting::Space class represents some empty space in a layout. */ /*! - \class Utils::LayoutBuilder::Stretch + \class Layouting::Stretch \inmodule QtCreator - \brief The LayoutBuilder::Stretch class represents some stretch in a layout. + \brief The Layouting::Stretch class represents some stretch in a layout. */ /*! - \class Utils::LayoutBuilder + \class LayoutBuilder \inmodule QtCreator \brief The LayoutBuilder class provides a convenient way to fill \c QFormLayout @@ -298,20 +307,6 @@ void LayoutItem::setBuilder(const LayoutBuilder &builder) \sa addItem(), addItems(), addRow(), finishRow() */ -LayoutBuilder::LayoutBuilder(LayoutType layoutType, const LayoutItems &items) - : m_layoutType(layoutType) -{ - m_items.reserve(items.size() * 2); - for (const LayoutItem &item : items) - addItem(item); -} - -LayoutBuilder &LayoutBuilder::setSpacing(int spacing) -{ - m_spacing = spacing; - return *this; -} - LayoutBuilder::LayoutBuilder() = default; /*! @@ -319,14 +314,30 @@ LayoutBuilder::LayoutBuilder() = default; */ LayoutBuilder::~LayoutBuilder() = default; +void LayoutBuilder::addItem(const LayoutItem &item) +{ + addItemHelper(*this, item); +} + +void LayoutBuilder::addItems(const LayoutItems &items) +{ + for (const LayoutItem &item : items) + addItemHelper(*this, item); +} + +void LayoutBuilder::addRow(const LayoutItems &items) +{ + addItem(br); + addItems(items); +} + /*! Instructs a layout builder to finish the current row. This is implicitly called by LayoutBuilder's destructor. */ -LayoutBuilder &LayoutBuilder::finishRow() +void LayoutItem::finishRow() { - addItem(Break()); - return *this; + addItem(br); } /*! @@ -335,42 +346,26 @@ LayoutBuilder &LayoutBuilder::finishRow() \sa finishRow(), addItem(), addItems() */ -LayoutBuilder &LayoutBuilder::addRow(const LayoutItems &items) +void LayoutItem::addRow(const LayoutItems &items) { - return finishRow().addItems(items); + finishRow(); + addItems(items); } /*! - Adds the layout item \a item to the current row. + Adds the layout item \a item as sub items. */ -LayoutBuilder &LayoutBuilder::addItem(const LayoutItem &item) +void LayoutItem::addItem(const LayoutItem &item) { - if (item.onAdd) { - item.onAdd(*this); - } else { - m_items.push_back(item); - } - return *this; -} - -void LayoutBuilder::doLayout(QWidget *parent, Layouting::AttachType attachType) const -{ - QLayout *layout = createLayout(); - parent->setLayout(layout); - - doLayoutHelper(layout, m_items, attachType); - if (attachType == Layouting::WithoutMargins) - layout->setContentsMargins(0, 0, 0, 0); + subItems.append(item); } /*! - Adds the layout item \a items to the current row. + Adds the layout items \a items as sub items. */ -LayoutBuilder &LayoutBuilder::addItems(const LayoutItems &items) +void LayoutItem::addItems(const LayoutItems &items) { - for (const LayoutItem &item : items) - addItem(item); - return *this; + subItems.append(items); } /*! @@ -378,18 +373,106 @@ LayoutBuilder &LayoutBuilder::addItems(const LayoutItems &items) This operation can only be performed once per LayoutBuilder instance. */ -void LayoutBuilder::attachTo(QWidget *w, Layouting::AttachType attachType) const + +void LayoutItem::attachTo(QWidget *w, AttachType attachType) const { - doLayout(w, attachType); + LayoutBuilder builder; + + builder.stack.append({w, attachType}); + addItemHelper(builder, *this); } -QWidget *LayoutBuilder::emerge(Layouting::AttachType attachType) +QWidget *LayoutItem::emerge(Layouting::AttachType attachType) { auto w = new QWidget; - doLayout(w, attachType); + attachTo(w, attachType); return w; } +bool LayoutBuilder::isForm() const +{ + return qobject_cast(stack.last().layout); +} + +static void layoutExit(LayoutBuilder &builder) +{ + builder.stack.last().flush(); + QLayout *layout = builder.stack.last().layout; + if (builder.stack.back().attachType == WithoutMargins) + layout->setContentsMargins(0, 0, 0, 0); + builder.stack.pop_back(); + + if (QWidget *widget = builder.stack.last().widget) + widget->setLayout(layout); + else + builder.stack.last().pendingItems.append(ResultItem(layout)); +} + +static void widgetExit(LayoutBuilder &builder) +{ + QWidget *widget = builder.stack.last().widget; + if (builder.stack.back().attachType == WithoutMargins) + widget->setContentsMargins(0, 0, 0, 0); + builder.stack.pop_back(); + builder.stack.last().pendingItems.append(ResultItem(widget)); +} + +Column::Column(std::initializer_list items) +{ + subItems = items; + onAdd = [](LayoutBuilder &builder) { builder.stack.append(new QVBoxLayout); }; + onExit = layoutExit; +} + +Row::Row(std::initializer_list items) +{ + subItems = items; + onAdd = [](LayoutBuilder &builder) { builder.stack.append(new QHBoxLayout); }; + onExit = layoutExit; +} + +Grid::Grid(std::initializer_list items) +{ + subItems = items; + onAdd = [](LayoutBuilder &builder) { builder.stack.append(new QGridLayout); }; + onExit = layoutExit; +} + +static QFormLayout *newFormLayout() +{ + auto formLayout = new QFormLayout; + formLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); + return formLayout; +} + +Form::Form(std::initializer_list items) +{ + subItems = items; + onAdd = [](LayoutBuilder &builder) { builder.stack.append(newFormLayout()); }; + onExit = layoutExit; +} + +Stack::Stack(std::initializer_list items) +{ + subItems = items; + onAdd = [](LayoutBuilder &builder) { builder.stack.append(new QStackedLayout); }; + onExit = layoutExit; +} + +LayoutItem br() +{ + LayoutItem item; + item.onAdd = [](LayoutBuilder &builder) { + builder.stack.last().flush(); + }; + return item; +} + +LayoutItem empty() +{ + return {}; +} + /*! Constructs a layout extender to extend an existing \a layout. @@ -399,111 +482,123 @@ QWidget *LayoutBuilder::emerge(Layouting::AttachType attachType) */ LayoutExtender::LayoutExtender(QLayout *layout, Layouting::AttachType attachType) - : m_layout(layout), m_attachType(attachType) -{} - -LayoutExtender::~LayoutExtender() { - QTC_ASSERT(m_layout, return); - int currentGridRow = 0; - if (auto gridLayout = qobject_cast(m_layout)) - currentGridRow = gridLayout->rowCount(); - doLayoutHelper(m_layout, m_items, m_attachType, currentGridRow); + Slice slice; + slice.layout = layout; + if (auto gridLayout = qobject_cast(layout)) + slice.currentGridRow = gridLayout->rowCount(); + slice.attachType = attachType; + stack.append(slice); } -// Special items - -Tab::Tab(const QString &tabName, const LayoutBuilder &item) -{ - text = tabName; - widget = new QWidget; - item.attachTo(widget); - specialType = LayoutItem::SpecialType::Tab; -} +LayoutExtender::~LayoutExtender() = default; // "Widgets" -static void applyItems(LayoutItem *owner, QWidget *widget, const QList &items) +template +void setupWidget(LayoutItem *item) { - owner->widget = widget; - bool hadLayout = false; - for (const LayoutItem &item : items) { - if (item.setter) { - item.setter(widget); - } else if (item.specialType == LayoutItem::SpecialType::Tab) { - auto tabWidget = qobject_cast(widget); - QTC_ASSERT(tabWidget, continue); - tabWidget->addTab(item.widget, item.text); - } else if (item.layout && !hadLayout) { - hadLayout = true; - widget->setLayout(item.layout); - } else { - QTC_CHECK(false); - } - } -} + item->onAdd = [](LayoutBuilder &builder) { builder.stack.append(new T); }; + item->onExit = widgetExit; +}; Group::Group(std::initializer_list items) { - applyItems(this, new QGroupBox, items); + this->subItems = items; + setupWidget(this); } PushButton::PushButton(std::initializer_list items) { - applyItems(this, new QPushButton, items); + this->subItems = items; + setupWidget(this); } TextEdit::TextEdit(std::initializer_list items) { - applyItems(this, new QTextEdit, items); + this->subItems = items; + setupWidget(this); } Splitter::Splitter(std::initializer_list items) { - applyItems(this, new QSplitter(Qt::Vertical), items); - } - + this->subItems = items; + setupWidget(this); // FIXME: Default was Qt::Vertical) +} TabWidget::TabWidget(std::initializer_list items) - { - applyItems(this, new QTabWidget, items); +{ + this->subItems = items; + setupWidget(this); } -// "Properties" +// Special Tab -static LayoutItem setter(const LayoutItem::Setter &setter) +Tab::Tab(const QString &tabName, const LayoutItem &item) { - LayoutItem item; - item.setter = setter; - return item; + onAdd = [item](LayoutBuilder &builder) { + auto tab = new QWidget; + builder.stack.append(tab); + item.attachTo(tab); + }; + onExit = [tabName](LayoutBuilder &builder) { + QWidget *inner = builder.stack.last().widget; + builder.stack.pop_back(); + auto tabWidget = qobject_cast(builder.stack.last().widget); + QTC_ASSERT(tabWidget, return); + tabWidget->addTab(inner, tabName); + }; +} + +// Special Application + +Application::Application(std::initializer_list items) +{ + subItems = items; + setupWidget(this); + onExit = {}; // Hack: Don't dropp the last slice, we need the resulting widget. +} + +int Application::exec(int &argc, char *argv[]) +{ + auto app = new QApplication(argc, argv); + LayoutBuilder builder; + addItemHelper(builder, *this); + if (QWidget *widget = builder.stack.last().widget) + widget->show(); + return app->exec(); } +// "Properties" + LayoutItem title(const QString &title) { - return setter([title](QObject *target) { + return [title](QObject *target) { if (auto groupBox = qobject_cast(target)) { groupBox->setTitle(title); groupBox->setObjectName(title); + } else if (auto widget = qobject_cast(target)) { + widget->setWindowTitle(title); } else { QTC_CHECK(false); } - }); + }; } LayoutItem onClicked(const std::function &func, QObject *guard) { - return setter([func, guard](QObject *target) { + return [func, guard](QObject *target) { if (auto button = qobject_cast(target)) { QObject::connect(button, &QAbstractButton::clicked, guard ? guard : target, func); } else { QTC_CHECK(false); } - }); + }; } LayoutItem text(const QString &text) { - return setter([text](QObject *target) { + return [text](QObject *target) { if (auto button = qobject_cast(target)) { button->setText(text); } else if (auto textEdit = qobject_cast(target)) { @@ -511,32 +606,40 @@ LayoutItem text(const QString &text) } else { QTC_CHECK(false); } - }); + }; } LayoutItem tooltip(const QString &toolTip) { - return setter([toolTip](QObject *target) { + return [toolTip](QObject *target) { if (auto widget = qobject_cast(target)) { widget->setToolTip(toolTip); } else { QTC_CHECK(false); } - }); + }; } -LayoutItem bindTo(QSplitter **out) +LayoutItem spacing(int spacing) { - return setter([out](QObject *target) { - *out = qobject_cast(target); - }); + return [spacing](QObject *target) { + if (auto layout = qobject_cast(target)) { + layout->setSpacing(spacing); + } else { + QTC_CHECK(false); + } + }; } -LayoutItem bindTo(QTabWidget **out) +LayoutItem resize(int w, int h) { - return setter([out](QObject *target) { - *out = qobject_cast(target); - }); + return [w, h](QObject *target) { + if (auto widget = qobject_cast(target)) { + widget->resize(w, h); + } else { + QTC_CHECK(false); + } + }; } QWidget *createHr(QWidget *parent) @@ -548,9 +651,67 @@ QWidget *createHr(QWidget *parent) } // Singletons. -Break br; -Stretch st; -Space empty(0); -HorizontalRule hr; + +LayoutItem::LayoutItem(const LayoutItem &t) +{ + operator=(t); +} + +void createItem(LayoutItem *item, LayoutItem(*t)()) +{ + *item = t(); +} + +void createItem(LayoutItem *item, const std::function &t) +{ + item->setter = t; +} + +void createItem(LayoutItem *item, QWidget *t) +{ + item->onAdd = [t](LayoutBuilder &builder) { doAddWidget(builder, t); }; +} + +void createItem(LayoutItem *item, QLayout *t) +{ + item->onAdd = [t](LayoutBuilder &builder) { doAddLayout(builder, t); }; +} + +void createItem(LayoutItem *item, const QString &t) +{ + item->onAdd = [t](LayoutBuilder &builder) { doAddText(builder, t); }; +} + +void createItem(LayoutItem *item, const Space &t) +{ + item->onAdd = [t](LayoutBuilder &builder) { doAddSpace(builder, t); }; +} + +void createItem(LayoutItem *item, const Stretch &t) +{ + item->onAdd = [t](LayoutBuilder &builder) { doAddStretch(builder, t); }; +} + +void createItem(LayoutItem *item, const Span &t) +{ + item->onAdd = [t](LayoutBuilder &builder) { + addItemHelper(builder, t.item); + builder.stack.last().pendingItems.last().span = t.span; + }; +} + +LayoutItem hr() +{ + LayoutItem item; + item.onAdd = [](LayoutBuilder &builder) { doAddWidget(builder, createHr()); }; + return item; +} + +LayoutItem st() +{ + LayoutItem item; + item.onAdd = [](LayoutBuilder &builder) { doAddStretch(builder, Stretch(1)); }; + return item; +} } // Layouting diff --git a/src/libs/utils/layoutbuilder.h b/src/libs/utils/layoutbuilder.h index aea4db876c..1e9e6b9ad3 100644 --- a/src/libs/utils/layoutbuilder.h +++ b/src/libs/utils/layoutbuilder.h @@ -5,7 +5,6 @@ #include #include -#include #include #include @@ -18,10 +17,9 @@ QT_BEGIN_NAMESPACE class QLayout; -class QSplitter; -class QTabWidget; -class QTextEdit; +class QObject; class QWidget; +template T qobject_cast(QObject *object); QT_END_NAMESPACE namespace Layouting { @@ -34,6 +32,7 @@ enum AttachType { class LayoutBuilder; class LayoutItem; +class Span; // Special items @@ -51,27 +50,19 @@ public: const int stretch; }; -class QTCREATOR_UTILS_EXPORT Break -{ -public: - Break() {} -}; -class QTCREATOR_UTILS_EXPORT Span -{ -public: - Span(int span, const LayoutItem &item) : span(span), item(item) {} - const int span; - const LayoutItem &item; -}; +// LayoutItem -class QTCREATOR_UTILS_EXPORT HorizontalRule -{ -public: - HorizontalRule() {} -}; +using LayoutItems = QList; -// LayoutItem +void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const std::function &t); +void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, QWidget *t); +void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, QLayout *t); +void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, LayoutItem(*t)()); +void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const QString &t); +void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const Span &t); +void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const Space &t); +void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const Stretch &t); class QTCREATOR_UTILS_EXPORT LayoutItem { @@ -81,72 +72,81 @@ public: AlignAsFormLabel, }; - enum class SpecialType { - NotSpecial, - Space, - Stretch, - Break, - HorizontalRule, - Tab, - }; - using Setter = std::function; - using OnAdder = std::function; LayoutItem(); + ~LayoutItem(); + + LayoutItem(const LayoutItem &t); + LayoutItem &operator=(const LayoutItem &t) = default; template LayoutItem(const T &t) { - if constexpr (std::is_same_v) { - text = t; - } else if constexpr (std::is_same_v) { - specialType = LayoutItem::SpecialType::Space; - specialValue = t.space; - } else if constexpr (std::is_same_v) { - specialType = LayoutItem::SpecialType::Stretch; - specialValue = t.stretch; - } else if constexpr (std::is_same_v) { - specialType = LayoutItem::SpecialType::Break; - } else if constexpr (std::is_same_v) { - LayoutItem::operator=(t.item); - span = t.span; - } else if constexpr (std::is_same_v) { - specialType = SpecialType::HorizontalRule; - } else if constexpr (std::is_base_of_v) { - setBuilder(t); - } else if constexpr (std::is_base_of_v) { + if constexpr (std::is_base_of_v) LayoutItem::operator=(t); - } else if constexpr (std::is_base_of_v) { - setter = t; - } else if constexpr (std::is_base_of_v>) { - layout = t; - } else if constexpr (std::is_base_of_v>) { - widget = t; - } else if constexpr (std::is_pointer_v) { - onAdd = [t](LayoutBuilder &builder) { doLayout(*t, builder); }; - } else { - onAdd = [&t](LayoutBuilder &builder) { doLayout(t, builder); }; - } + else + createItem(this, t); } - void setBuilder(const LayoutBuilder &builder); + void attachTo(QWidget *w, AttachType attachType = WithMargins) const; + QWidget *emerge(AttachType attachType = WithMargins); + + void addItem(const LayoutItem &item); + void addItems(const LayoutItems &items); + void addRow(const LayoutItems &items); + void finishRow(); + + std::function onAdd; + std::function onExit; + std::function setter; + LayoutItems subItems; +}; + +class QTCREATOR_UTILS_EXPORT Span +{ +public: + Span(int span, const LayoutItem &item) : span(span), item(item) {} + const int span; + LayoutItem item; +}; - QLayout *layout = nullptr; - QWidget *widget = nullptr; - OnAdder onAdd; +class QTCREATOR_UTILS_EXPORT Column : public LayoutItem +{ +public: + Column(std::initializer_list items); +}; + +class QTCREATOR_UTILS_EXPORT Row : public LayoutItem +{ +public: + Row(std::initializer_list items); +}; + +class QTCREATOR_UTILS_EXPORT Grid : public LayoutItem +{ +public: + Grid() : Grid({}) {} + Grid(std::initializer_list items); +}; + +class QTCREATOR_UTILS_EXPORT Form : public LayoutItem +{ +public: + Form() : Form({}) {} + Form(std::initializer_list items); +}; - QString text; // FIXME: Use specialValue for that - int span = 1; - AlignmentType align = AlignmentType::DefaultAlignment; - Setter setter; - SpecialType specialType = SpecialType::NotSpecial; - QVariant specialValue; +class QTCREATOR_UTILS_EXPORT Stack : public LayoutItem +{ +public: + Stack() : Stack({}) {} + Stack(std::initializer_list items); }; class QTCREATOR_UTILS_EXPORT Tab : public LayoutItem { public: - Tab(const QString &tabName, const LayoutBuilder &item); + Tab(const QString &tabName, const LayoutItem &item); }; class QTCREATOR_UTILS_EXPORT Group : public LayoutItem @@ -179,124 +179,67 @@ public: TabWidget(std::initializer_list items); }; -// Singleton items. +class QTCREATOR_UTILS_EXPORT Application : public LayoutItem +{ +public: + Application(std::initializer_list items); -QTCREATOR_UTILS_EXPORT extern Break br; -QTCREATOR_UTILS_EXPORT extern Stretch st; -QTCREATOR_UTILS_EXPORT extern Space empty; -QTCREATOR_UTILS_EXPORT extern HorizontalRule hr; + int exec(int &argc, char *argv[]); +}; -// "Properties" +// "Singletons" -QTCREATOR_UTILS_EXPORT LayoutItem bindTo(QTabWidget **); -QTCREATOR_UTILS_EXPORT LayoutItem bindTo(QSplitter **); +QTCREATOR_UTILS_EXPORT LayoutItem br(); +QTCREATOR_UTILS_EXPORT LayoutItem st(); +QTCREATOR_UTILS_EXPORT LayoutItem empty(); +QTCREATOR_UTILS_EXPORT LayoutItem hr(); + +// "Properties" QTCREATOR_UTILS_EXPORT LayoutItem title(const QString &title); QTCREATOR_UTILS_EXPORT LayoutItem text(const QString &text); QTCREATOR_UTILS_EXPORT LayoutItem tooltip(const QString &toolTip); -QTCREATOR_UTILS_EXPORT LayoutItem onClicked(const std::function &func, - QObject *guard = nullptr); - +QTCREATOR_UTILS_EXPORT LayoutItem resize(int, int); +QTCREATOR_UTILS_EXPORT LayoutItem spacing(int); +QTCREATOR_UTILS_EXPORT LayoutItem windowTitle(const QString &windowTitle); +QTCREATOR_UTILS_EXPORT LayoutItem onClicked(const std::function &, + QObject *guard = nullptr); // Convenience QTCREATOR_UTILS_EXPORT QWidget *createHr(QWidget *parent = nullptr); +template +LayoutItem bindTo(T **out) +{ + return [out](QObject *target) { *out = qobject_cast(target); }; +} // LayoutBuilder class QTCREATOR_UTILS_EXPORT LayoutBuilder { - Q_DISABLE_COPY(LayoutBuilder) + Q_DISABLE_COPY_MOVE(LayoutBuilder) public: - enum LayoutType { - HBoxLayout, - VBoxLayout, - FormLayout, - GridLayout, - StackLayout, - }; - - using LayoutItems = QList; - - explicit LayoutBuilder(LayoutType layoutType, const LayoutItems &items = {}); - - LayoutBuilder(LayoutBuilder &&) = default; - LayoutBuilder &operator=(LayoutBuilder &&) = default; - + LayoutBuilder(); ~LayoutBuilder(); - LayoutBuilder &setSpacing(int spacing); - - LayoutBuilder &addItem(const LayoutItem &item); - LayoutBuilder &addItems(const LayoutItems &items); - - LayoutBuilder &finishRow(); - LayoutBuilder &addRow(const LayoutItems &items); - - LayoutType layoutType() const { return m_layoutType; } - - void attachTo(QWidget *w, Layouting::AttachType attachType = Layouting::WithMargins) const; - QWidget *emerge(Layouting::AttachType attachType = Layouting::WithMargins); - -protected: - friend class LayoutItem; - - explicit LayoutBuilder(); // Adds to existing layout. + void addItem(const LayoutItem &item); + void addItems(const LayoutItems &items); + void addRow(const LayoutItems &items); - QLayout *createLayout() const; - void doLayout(QWidget *parent, Layouting::AttachType attachType) const; + bool isForm() const; - LayoutItems m_items; - LayoutType m_layoutType; - std::optional m_spacing; + struct Slice; + QList stack; }; class QTCREATOR_UTILS_EXPORT LayoutExtender : public LayoutBuilder { public: - explicit LayoutExtender(QLayout *layout, Layouting::AttachType attachType); + explicit LayoutExtender(QLayout *layout, AttachType attachType); ~LayoutExtender(); - -private: - QLayout *m_layout = nullptr; - Layouting::AttachType m_attachType = {}; -}; - -class QTCREATOR_UTILS_EXPORT Column : public LayoutBuilder -{ -public: - Column() : LayoutBuilder(VBoxLayout) {} - Column(std::initializer_list items) : LayoutBuilder(VBoxLayout, items) {} -}; - -class QTCREATOR_UTILS_EXPORT Row : public LayoutBuilder -{ -public: - Row() : LayoutBuilder(HBoxLayout) {} - Row(std::initializer_list items) : LayoutBuilder(HBoxLayout, items) {} -}; - -class QTCREATOR_UTILS_EXPORT Grid : public LayoutBuilder -{ -public: - Grid() : LayoutBuilder(GridLayout) {} - Grid(std::initializer_list items) : LayoutBuilder(GridLayout, items) {} -}; - -class QTCREATOR_UTILS_EXPORT Form : public LayoutBuilder -{ -public: - Form() : LayoutBuilder(FormLayout) {} - Form(std::initializer_list items) : LayoutBuilder(FormLayout, items) {} -}; - -class QTCREATOR_UTILS_EXPORT Stack : public LayoutBuilder -{ -public: - Stack() : LayoutBuilder(StackLayout) {} - Stack(std::initializer_list items) : LayoutBuilder(StackLayout, items) {} }; } // Layouting -- cgit v1.2.3 From 50084f6b0ed78bfee6f5e1d2e56ade9b52b3f2f6 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 2 May 2023 12:51:03 +0200 Subject: Layouting: Handle attach types via setter Change-Id: I862f5cd109db3582b4f029787ec0cded2da39ce6 Reviewed-by: Qt CI Bot Reviewed-by: Alessandro Portale --- src/libs/extensionsystem/plugindetailsview.cpp | 5 +- src/libs/extensionsystem/pluginerrorview.cpp | 5 +- src/libs/utils/layoutbuilder.cpp | 92 +++++++++++++++++--------- src/libs/utils/layoutbuilder.h | 15 ++--- 4 files changed, 74 insertions(+), 43 deletions(-) (limited to 'src/libs') diff --git a/src/libs/extensionsystem/plugindetailsview.cpp b/src/libs/extensionsystem/plugindetailsview.cpp index 8fd9e7b5ef..49ba0815a3 100644 --- a/src/libs/extensionsystem/plugindetailsview.cpp +++ b/src/libs/extensionsystem/plugindetailsview.cpp @@ -68,8 +68,9 @@ public: Tr::tr("Description:"), description, br, Tr::tr("Copyright:"), copyright, br, Tr::tr("License:"), license, br, - Tr::tr("Dependencies:"), dependencies - }.attachTo(q, WithoutMargins); + Tr::tr("Dependencies:"), dependencies, + noMargin + }.attachTo(q); // clang-format on } diff --git a/src/libs/extensionsystem/pluginerrorview.cpp b/src/libs/extensionsystem/pluginerrorview.cpp index 42374b0376..31c4334b6a 100644 --- a/src/libs/extensionsystem/pluginerrorview.cpp +++ b/src/libs/extensionsystem/pluginerrorview.cpp @@ -45,8 +45,9 @@ public: Form { Tr::tr("State:"), state, br, - Tr::tr("Error message:"), errorString - }.attachTo(q, WithoutMargins); + Tr::tr("Error message:"), errorString, + noMargin, + }.attachTo(q); } PluginErrorView *q = nullptr; diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index c7ead009c7..48a75fc162 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -79,17 +79,19 @@ struct LayoutBuilder::Slice Slice() = default; Slice(QLayout *l) : layout(l) {} Slice(QWidget *w) : widget(w) {} - Slice(QWidget *w, AttachType a) : widget(w), attachType(a) {} QLayout *layout = nullptr; QWidget *widget = nullptr; void flush(); + // Grid-specific int currentGridColumn = 0; int currentGridRow = 0; + bool isFormAlignment = false; + Qt::Alignment align = {}; // Can be changed to - AttachType attachType = WithMargins; + // Grid or Form QList pendingItems; }; @@ -192,9 +194,6 @@ void LayoutBuilder::Slice::flush() } else if (auto gridLayout = qobject_cast(layout)) { for (const ResultItem &item : std::as_const(pendingItems)) { - Qt::Alignment align = {}; - // if (attachType == Layouting::WithFormAlignment && currentGridColumn == 0) - // align = Qt::Alignment(m_widget->style()->styleHint(QStyle::SH_FormLayoutLabelAlignment)); if (item.widget) gridLayout->addWidget(item.widget, currentGridRow, currentGridColumn, 1, item.span, align); else if (item.layout) @@ -374,18 +373,18 @@ void LayoutItem::addItems(const LayoutItems &items) This operation can only be performed once per LayoutBuilder instance. */ -void LayoutItem::attachTo(QWidget *w, AttachType attachType) const +void LayoutItem::attachTo(QWidget *w) const { LayoutBuilder builder; - builder.stack.append({w, attachType}); + builder.stack.append(w); addItemHelper(builder, *this); } -QWidget *LayoutItem::emerge(Layouting::AttachType attachType) +QWidget *LayoutItem::emerge() { auto w = new QWidget; - attachTo(w, attachType); + attachTo(w); return w; } @@ -398,8 +397,6 @@ static void layoutExit(LayoutBuilder &builder) { builder.stack.last().flush(); QLayout *layout = builder.stack.last().layout; - if (builder.stack.back().attachType == WithoutMargins) - layout->setContentsMargins(0, 0, 0, 0); builder.stack.pop_back(); if (QWidget *widget = builder.stack.last().widget) @@ -411,8 +408,6 @@ static void layoutExit(LayoutBuilder &builder) static void widgetExit(LayoutBuilder &builder) { QWidget *widget = builder.stack.last().widget; - if (builder.stack.back().attachType == WithoutMargins) - widget->setContentsMargins(0, 0, 0, 0); builder.stack.pop_back(); builder.stack.last().pendingItems.append(ResultItem(widget)); } @@ -464,7 +459,7 @@ LayoutItem br() LayoutItem item; item.onAdd = [](LayoutBuilder &builder) { builder.stack.last().flush(); - }; + }; return item; } @@ -473,6 +468,58 @@ LayoutItem empty() return {}; } +LayoutItem hr() +{ + LayoutItem item; + item.onAdd = [](LayoutBuilder &builder) { doAddWidget(builder, createHr()); }; + return item; +} + +LayoutItem st() +{ + LayoutItem item; + item.onAdd = [](LayoutBuilder &builder) { doAddStretch(builder, Stretch(1)); }; + return item; +} + +LayoutItem noMargin() +{ + LayoutItem item; + item.onAdd = [](LayoutBuilder &builder) { + if (auto layout = builder.stack.last().layout) + layout->setContentsMargins(0, 0, 0, 0); + else if (auto widget = builder.stack.last().widget) + widget->setContentsMargins(0, 0, 0, 0); + }; + return item; +} + +LayoutItem normalMargin() +{ + LayoutItem item; + item.onAdd = [](LayoutBuilder &builder) { + if (auto layout = builder.stack.last().layout) + layout->setContentsMargins(9, 9, 9, 9); + else if (auto widget = builder.stack.last().widget) + widget->setContentsMargins(9, 9, 9, 9); + }; + return item; +} + +LayoutItem withFormAlignment() +{ + LayoutItem item; + item.onAdd = [](LayoutBuilder &builder) { + if (builder.stack.size() >= 2) { + if (auto widget = builder.stack.at(builder.stack.size() - 2).widget) { + const Qt::Alignment align(widget->style()->styleHint(QStyle::SH_FormLayoutLabelAlignment)); + builder.stack.last().align = align; + } + } + }; + return item; +} + /*! Constructs a layout extender to extend an existing \a layout. @@ -481,13 +528,12 @@ LayoutItem empty() new items will be added below existing ones. */ -LayoutExtender::LayoutExtender(QLayout *layout, Layouting::AttachType attachType) +LayoutExtender::LayoutExtender(QLayout *layout) { Slice slice; slice.layout = layout; if (auto gridLayout = qobject_cast(layout)) slice.currentGridRow = gridLayout->rowCount(); - slice.attachType = attachType; stack.append(slice); } @@ -700,18 +746,4 @@ void createItem(LayoutItem *item, const Span &t) }; } -LayoutItem hr() -{ - LayoutItem item; - item.onAdd = [](LayoutBuilder &builder) { doAddWidget(builder, createHr()); }; - return item; -} - -LayoutItem st() -{ - LayoutItem item; - item.onAdd = [](LayoutBuilder &builder) { doAddStretch(builder, Stretch(1)); }; - return item; -} - } // Layouting diff --git a/src/libs/utils/layoutbuilder.h b/src/libs/utils/layoutbuilder.h index 1e9e6b9ad3..e63c359e7e 100644 --- a/src/libs/utils/layoutbuilder.h +++ b/src/libs/utils/layoutbuilder.h @@ -24,12 +24,6 @@ QT_END_NAMESPACE namespace Layouting { -enum AttachType { - WithMargins, - WithoutMargins, - WithFormAlignment, // Handle Grid similar to QFormLayout, i.e. use special alignment for the first column on Mac -}; - class LayoutBuilder; class LayoutItem; class Span; @@ -88,8 +82,8 @@ public: createItem(this, t); } - void attachTo(QWidget *w, AttachType attachType = WithMargins) const; - QWidget *emerge(AttachType attachType = WithMargins); + void attachTo(QWidget *w) const; + QWidget *emerge(); void addItem(const LayoutItem &item); void addItems(const LayoutItems &items); @@ -193,6 +187,9 @@ QTCREATOR_UTILS_EXPORT LayoutItem br(); QTCREATOR_UTILS_EXPORT LayoutItem st(); QTCREATOR_UTILS_EXPORT LayoutItem empty(); QTCREATOR_UTILS_EXPORT LayoutItem hr(); +QTCREATOR_UTILS_EXPORT LayoutItem noMargin(); +QTCREATOR_UTILS_EXPORT LayoutItem normalMargin(); +QTCREATOR_UTILS_EXPORT LayoutItem withFormAlignment(); // "Properties" @@ -238,7 +235,7 @@ public: class QTCREATOR_UTILS_EXPORT LayoutExtender : public LayoutBuilder { public: - explicit LayoutExtender(QLayout *layout, AttachType attachType); + explicit LayoutExtender(QLayout *layout); ~LayoutExtender(); }; -- cgit v1.2.3 From 99f767956417ed7dc91a5295a2af8326376d3b5e Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 2 May 2023 17:20:57 +0200 Subject: Layouting: Make aspects operate on parent items, not LayoutBuilder LayoutBuilder is meant to be an implementation detail nowadays. Change-Id: I777ab934d3d405873e819eeddd27428d8c652f9a Reviewed-by: Alessandro Portale --- src/libs/utils/aspects.cpp | 82 +++++++++++++++++++--------------------- src/libs/utils/aspects.h | 29 +++++++------- src/libs/utils/layoutbuilder.cpp | 69 +++++++++++++++++++-------------- src/libs/utils/layoutbuilder.h | 28 +------------- 4 files changed, 93 insertions(+), 115 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index fd953d2493..94aee75214 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -210,15 +210,15 @@ void BaseAspect::setupLabel() registerSubWidget(d->m_label); } -void BaseAspect::addLabeledItem(Layouting::LayoutBuilder &builder, QWidget *widget) +void BaseAspect::addLabeledItem(LayoutItem &parent, QWidget *widget) { setupLabel(); if (QLabel *l = label()) { l->setBuddy(widget); - builder.addItem(l); - builder.addItem(Span(std::max(d->m_spanX - 1, 1), LayoutItem(widget))); + parent.addItem(l); + parent.addItem(Span(std::max(d->m_spanX - 1, 1), LayoutItem(widget))); } else { - builder.addItem(LayoutItem(widget)); + parent.addItem(LayoutItem(widget)); } } @@ -419,24 +419,18 @@ QAction *BaseAspect::action() Adds the visual representation of this aspect to a layout using a layout builder. */ -void BaseAspect::addToLayout(LayoutBuilder &) +void BaseAspect::addToLayout(LayoutItem &) { } void createItem(Layouting::LayoutItem *item, const BaseAspect &aspect) { - item->onAdd = [&aspect](LayoutBuilder &builder) { - const_cast(aspect).addToLayout(builder); - builder.addItem(br); - }; + const_cast(aspect).addToLayout(*item); } void createItem(Layouting::LayoutItem *item, const BaseAspect *aspect) { - item->onAdd = [aspect](LayoutBuilder &builder) { - const_cast(aspect)->addToLayout(builder); - builder.addItem(br); - }; + const_cast(aspect)->addToLayout(*item); } @@ -1067,11 +1061,11 @@ void StringAspect::setUncheckedSemantics(StringAspect::UncheckedSemantics semant d->m_uncheckedSemantics = semantics; } -void StringAspect::addToLayout(Layouting::LayoutBuilder &builder) +void StringAspect::addToLayout(LayoutItem &parent) { if (d->m_checker && d->m_checkBoxPlacement == CheckBoxPlacement::Top) { - d->m_checker->addToLayout(builder); - builder.addItem(br); + d->m_checker->addToLayout(parent); + parent.addItem(br); } const auto useMacroExpander = [this](QWidget *w) { @@ -1103,7 +1097,7 @@ void StringAspect::addToLayout(Layouting::LayoutBuilder &builder) if (d->m_pathChooserDisplay->lineEdit()->placeholderText().isEmpty()) d->m_pathChooserDisplay->lineEdit()->setPlaceholderText(d->m_placeHolderText); d->updateWidgetFromCheckStatus(this, d->m_pathChooserDisplay.data()); - addLabeledItem(builder, d->m_pathChooserDisplay); + addLabeledItem(parent, d->m_pathChooserDisplay); useMacroExpander(d->m_pathChooserDisplay->lineEdit()); if (isAutoApply()) { if (d->m_autoApplyOnEditingFinished) { @@ -1134,7 +1128,7 @@ void StringAspect::addToLayout(Layouting::LayoutBuilder &builder) d->m_lineEditDisplay->setTextKeepingActiveCursor(displayedString); d->m_lineEditDisplay->setReadOnly(isReadOnly()); d->updateWidgetFromCheckStatus(this, d->m_lineEditDisplay.data()); - addLabeledItem(builder, d->m_lineEditDisplay); + addLabeledItem(parent, d->m_lineEditDisplay); useMacroExpander(d->m_lineEditDisplay); if (isAutoApply()) { if (d->m_autoApplyOnEditingFinished) { @@ -1164,7 +1158,7 @@ void StringAspect::addToLayout(Layouting::LayoutBuilder &builder) connect(d->m_lineEditDisplay, &QLineEdit::textChanged, this, [this, resetButton] { resetButton->setEnabled(d->m_lineEditDisplay->text() != defaultValue()); }); - builder.addItem(resetButton); + parent.addItem(resetButton); } break; case TextEditDisplay: @@ -1176,7 +1170,7 @@ void StringAspect::addToLayout(Layouting::LayoutBuilder &builder) d->m_textEditDisplay->setText(displayedString); d->m_textEditDisplay->setReadOnly(isReadOnly()); d->updateWidgetFromCheckStatus(this, d->m_textEditDisplay.data()); - addLabeledItem(builder, d->m_textEditDisplay); + addLabeledItem(parent, d->m_textEditDisplay); useMacroExpander(d->m_textEditDisplay); if (isAutoApply()) { connect(d->m_textEditDisplay, &QTextEdit::textChanged, this, [this] { @@ -1190,14 +1184,14 @@ void StringAspect::addToLayout(Layouting::LayoutBuilder &builder) d->m_labelDisplay->setTextInteractionFlags(Qt::TextSelectableByMouse); d->m_labelDisplay->setText(displayedString); d->m_labelDisplay->setToolTip(d->m_showToolTipOnLabel ? displayedString : toolTip()); - addLabeledItem(builder, d->m_labelDisplay); + addLabeledItem(parent, d->m_labelDisplay); break; } validateInput(); if (d->m_checker && d->m_checkBoxPlacement == CheckBoxPlacement::Right) - d->m_checker->addToLayout(builder); + d->m_checker->addToLayout(parent); } QVariant StringAspect::volatileValue() const @@ -1327,11 +1321,11 @@ ColorAspect::ColorAspect(const QString &settingsKey) ColorAspect::~ColorAspect() = default; -void ColorAspect::addToLayout(Layouting::LayoutBuilder &builder) +void ColorAspect::addToLayout(Layouting::LayoutItem &parent) { QTC_CHECK(!d->m_colorButton); d->m_colorButton = createSubWidget(); - builder.addItem(d->m_colorButton.data()); + parent.addItem(d->m_colorButton.data()); d->m_colorButton->setColor(value()); if (isAutoApply()) { connect(d->m_colorButton.data(), @@ -1401,24 +1395,24 @@ BoolAspect::~BoolAspect() = default; /*! \reimp */ -void BoolAspect::addToLayout(Layouting::LayoutBuilder &builder) +void BoolAspect::addToLayout(Layouting::LayoutItem &parent) { QTC_CHECK(!d->m_checkBox); d->m_checkBox = createSubWidget(); switch (d->m_labelPlacement) { case LabelPlacement::AtCheckBoxWithoutDummyLabel: d->m_checkBox->setText(labelText()); - builder.addItem(d->m_checkBox.data()); + parent.addItem(d->m_checkBox.data()); break; case LabelPlacement::AtCheckBox: { d->m_checkBox->setText(labelText()); - if (builder.isForm()) - builder.addItem(createSubWidget()); - builder.addItem(d->m_checkBox.data()); +// if (parent.isForm()) FIXME + parent.addItem(createSubWidget()); + parent.addItem(d->m_checkBox.data()); break; } case LabelPlacement::InExtraLabel: - addLabeledItem(builder, d->m_checkBox); + addLabeledItem(parent, d->m_checkBox); break; } d->m_checkBox->setChecked(value()); @@ -1557,7 +1551,7 @@ SelectionAspect::~SelectionAspect() = default; /*! \reimp */ -void SelectionAspect::addToLayout(Layouting::LayoutBuilder &builder) +void SelectionAspect::addToLayout(Layouting::LayoutItem &parent) { QTC_CHECK(d->m_buttonGroup == nullptr); QTC_CHECK(!d->m_comboBox); @@ -1573,8 +1567,8 @@ void SelectionAspect::addToLayout(Layouting::LayoutBuilder &builder) button->setChecked(i == value()); button->setEnabled(option.enabled); button->setToolTip(option.tooltip); - builder.addItem(Layouting::empty); - builder.addItem(button); + parent.addItem(Layouting::empty); + parent.addItem(button); d->m_buttons.append(button); d->m_buttonGroup->addButton(button, i); if (isAutoApply()) { @@ -1596,7 +1590,7 @@ void SelectionAspect::addToLayout(Layouting::LayoutBuilder &builder) connect(d->m_comboBox.data(), &QComboBox::currentIndexChanged, this, &SelectionAspect::volatileValueChanged); d->m_comboBox->setCurrentIndex(value()); - addLabeledItem(builder, d->m_comboBox); + addLabeledItem(parent, d->m_comboBox); break; } } @@ -1762,7 +1756,7 @@ MultiSelectionAspect::~MultiSelectionAspect() = default; /*! \reimp */ -void MultiSelectionAspect::addToLayout(Layouting::LayoutBuilder &builder) +void MultiSelectionAspect::addToLayout(LayoutItem &builder) { QTC_CHECK(d->m_listView == nullptr); if (d->m_allValues.isEmpty()) @@ -1871,7 +1865,7 @@ IntegerAspect::~IntegerAspect() = default; /*! \reimp */ -void IntegerAspect::addToLayout(Layouting::LayoutBuilder &builder) +void IntegerAspect::addToLayout(Layouting::LayoutItem &parent) { QTC_CHECK(!d->m_spinBox); d->m_spinBox = createSubWidget(); @@ -1884,7 +1878,7 @@ void IntegerAspect::addToLayout(Layouting::LayoutBuilder &builder) d->m_spinBox->setRange(int(d->m_minimumValue.value() / d->m_displayScaleFactor), int(d->m_maximumValue.value() / d->m_displayScaleFactor)); d->m_spinBox->setValue(int(value() / d->m_displayScaleFactor)); // Must happen after setRange() - addLabeledItem(builder, d->m_spinBox); + addLabeledItem(parent, d->m_spinBox); if (isAutoApply()) { connect(d->m_spinBox.data(), &QSpinBox::valueChanged, @@ -2005,7 +1999,7 @@ DoubleAspect::~DoubleAspect() = default; /*! \reimp */ -void DoubleAspect::addToLayout(Layouting::LayoutBuilder &builder) +void DoubleAspect::addToLayout(LayoutItem &builder) { QTC_CHECK(!d->m_spinBox); d->m_spinBox = createSubWidget(); @@ -2159,9 +2153,9 @@ StringListAspect::~StringListAspect() = default; /*! \reimp */ -void StringListAspect::addToLayout(Layouting::LayoutBuilder &builder) +void StringListAspect::addToLayout(LayoutItem &parent) { - Q_UNUSED(builder) + Q_UNUSED(parent) // TODO - when needed. } @@ -2229,9 +2223,9 @@ IntegersAspect::~IntegersAspect() = default; /*! \reimp */ -void IntegersAspect::addToLayout(Layouting::LayoutBuilder &builder) +void IntegersAspect::addToLayout(Layouting::LayoutItem &parent) { - Q_UNUSED(builder) + Q_UNUSED(parent) // TODO - when needed. } @@ -2292,7 +2286,7 @@ TextDisplay::~TextDisplay() = default; /*! \reimp */ -void TextDisplay::addToLayout(Layouting::LayoutBuilder &builder) +void TextDisplay::addToLayout(LayoutItem &parent) { if (!d->m_label) { d->m_label = createSubWidget(d->m_message, d->m_type); @@ -2304,7 +2298,7 @@ void TextDisplay::addToLayout(Layouting::LayoutBuilder &builder) if (!isVisible()) d->m_label->setVisible(false); } - builder.addItem(d->m_label.data()); + parent.addItem(d->m_label.data()); } /*! diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index 3eae9ffaea..8b75bf911d 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -18,10 +18,7 @@ class QAction; class QSettings; QT_END_NAMESPACE -namespace Layouting { -class LayoutItem; -class LayoutBuilder; -} +namespace Layouting { class LayoutItem; } namespace Utils { @@ -100,7 +97,7 @@ public: virtual void toMap(QVariantMap &map) const; virtual void toActiveMap(QVariantMap &map) const { toMap(map); } - virtual void addToLayout(Layouting::LayoutBuilder &builder); + virtual void addToLayout(Layouting::LayoutItem &parent); virtual QVariant volatileValue() const; virtual void setVolatileValue(const QVariant &val); @@ -171,7 +168,7 @@ signals: protected: QLabel *label() const; void setupLabel(); - void addLabeledItem(Layouting::LayoutBuilder &builder, QWidget *widget); + void addLabeledItem(Layouting::LayoutItem &parent, QWidget *widget); void setDataCreatorHelper(const DataCreator &creator) const; void setDataClonerHelper(const DataCloner &cloner) const; @@ -223,7 +220,7 @@ public: bool value; }; - void addToLayout(Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; std::function groupChecker(); QAction *action() override; @@ -263,7 +260,7 @@ public: QColor value; }; - void addToLayout(Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; QColor value() const; void setValue(const QColor &val); @@ -283,7 +280,7 @@ public: SelectionAspect(); ~SelectionAspect() override; - void addToLayout(Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; QVariant volatileValue() const override; void setVolatileValue(const QVariant &val) override; void finish() override; @@ -337,7 +334,7 @@ public: MultiSelectionAspect(); ~MultiSelectionAspect() override; - void addToLayout(Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; enum class DisplayStyle { ListView }; void setDisplayStyle(DisplayStyle style); @@ -366,7 +363,7 @@ public: FilePath filePath; }; - void addToLayout(Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; QVariant volatileValue() const override; void setVolatileValue(const QVariant &val) override; @@ -444,7 +441,7 @@ public: IntegerAspect(); ~IntegerAspect() override; - void addToLayout(Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; QVariant volatileValue() const override; void setVolatileValue(const QVariant &val) override; @@ -481,7 +478,7 @@ public: DoubleAspect(); ~DoubleAspect() override; - void addToLayout(Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; QVariant volatileValue() const override; void setVolatileValue(const QVariant &val) override; @@ -549,7 +546,7 @@ public: StringListAspect(); ~StringListAspect() override; - void addToLayout(Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; QStringList value() const; void setValue(const QStringList &val); @@ -571,7 +568,7 @@ public: IntegersAspect(); ~IntegersAspect() override; - void addToLayout(Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; void emitChangedValue() override; QList value() const; @@ -593,7 +590,7 @@ public: InfoLabel::InfoType type = InfoLabel::None); ~TextDisplay() override; - void addToLayout(Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; void setIconType(InfoLabel::InfoType t); void setText(const QString &message); diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index 48a75fc162..3324e9c4f6 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -74,7 +74,7 @@ struct ResultItem int span = 1; }; -struct LayoutBuilder::Slice +struct Slice { Slice() = default; Slice(QLayout *l) : layout(l) {} @@ -135,7 +135,7 @@ static void addItemToBoxLayout(QBoxLayout *layout, const ResultItem &item) } } -void LayoutBuilder::Slice::flush() +void Slice::flush() { if (pendingItems.empty()) return; @@ -194,13 +194,14 @@ void LayoutBuilder::Slice::flush() } else if (auto gridLayout = qobject_cast(layout)) { for (const ResultItem &item : std::as_const(pendingItems)) { - if (item.widget) - gridLayout->addWidget(item.widget, currentGridRow, currentGridColumn, 1, item.span, align); - else if (item.layout) - gridLayout->addLayout(item.layout, currentGridRow, currentGridColumn, 1, item.span, align); - else if (!item.text.isEmpty()) - gridLayout->addWidget(createLabel(item.text), currentGridRow, currentGridColumn, 1, 1, align); - currentGridColumn += item.span; + Qt::Alignment a = currentGridColumn == 0 ? align : Qt::Alignment(); + if (item.widget) + gridLayout->addWidget(item.widget, currentGridRow, currentGridColumn, 1, item.span, a); + else if (item.layout) + gridLayout->addLayout(item.layout, currentGridRow, currentGridColumn, 1, item.span, a); + else if (!item.text.isEmpty()) + gridLayout->addWidget(createLabel(item.text), currentGridRow, currentGridColumn, 1, 1, a); + currentGridColumn += item.span; } ++currentGridRow; currentGridColumn = 0; @@ -225,6 +226,25 @@ void LayoutBuilder::Slice::flush() pendingItems.clear(); } +// LayoutBuilder + +class LayoutBuilder +{ + Q_DISABLE_COPY_MOVE(LayoutBuilder) + +public: + LayoutBuilder(); + ~LayoutBuilder(); + + void addItem(const LayoutItem &item); + void addItems(const LayoutItems &items); + void addRow(const LayoutItems &items); + + bool isForm() const; + + QList stack; +}; + static void addItemHelper(LayoutBuilder &builder, const LayoutItem &item) { if (item.onAdd) @@ -306,6 +326,7 @@ void doAddWidget(LayoutBuilder &builder, QWidget *widget) \sa addItem(), addItems(), addRow(), finishRow() */ + LayoutBuilder::LayoutBuilder() = default; /*! @@ -520,25 +541,6 @@ LayoutItem withFormAlignment() return item; } -/*! - Constructs a layout extender to extend an existing \a layout. - - This constructor can be used to continue the work of previous layout building. - The type of the underlying layout and previous contents will be retained, - new items will be added below existing ones. - */ - -LayoutExtender::LayoutExtender(QLayout *layout) -{ - Slice slice; - slice.layout = layout; - if (auto gridLayout = qobject_cast(layout)) - slice.currentGridRow = gridLayout->rowCount(); - stack.append(slice); -} - -LayoutExtender::~LayoutExtender() = default; - // "Widgets" template @@ -688,6 +690,17 @@ LayoutItem resize(int w, int h) }; } +LayoutItem columnStretch(int column, int stretch) +{ + return [column, stretch](QObject *target) { + if (auto grid = qobject_cast(target)) { + grid->setColumnStretch(column, stretch); + } else { + QTC_CHECK(false); + } + }; +} + QWidget *createHr(QWidget *parent) { auto frame = new QFrame(parent); diff --git a/src/libs/utils/layoutbuilder.h b/src/libs/utils/layoutbuilder.h index e63c359e7e..1a9c3439bc 100644 --- a/src/libs/utils/layoutbuilder.h +++ b/src/libs/utils/layoutbuilder.h @@ -197,6 +197,7 @@ QTCREATOR_UTILS_EXPORT LayoutItem title(const QString &title); QTCREATOR_UTILS_EXPORT LayoutItem text(const QString &text); QTCREATOR_UTILS_EXPORT LayoutItem tooltip(const QString &toolTip); QTCREATOR_UTILS_EXPORT LayoutItem resize(int, int); +QTCREATOR_UTILS_EXPORT LayoutItem columnStretch(int column, int stretch); QTCREATOR_UTILS_EXPORT LayoutItem spacing(int); QTCREATOR_UTILS_EXPORT LayoutItem windowTitle(const QString &windowTitle); QTCREATOR_UTILS_EXPORT LayoutItem onClicked(const std::function &, @@ -212,31 +213,4 @@ LayoutItem bindTo(T **out) return [out](QObject *target) { *out = qobject_cast(target); }; } -// LayoutBuilder - -class QTCREATOR_UTILS_EXPORT LayoutBuilder -{ - Q_DISABLE_COPY_MOVE(LayoutBuilder) - -public: - LayoutBuilder(); - ~LayoutBuilder(); - - void addItem(const LayoutItem &item); - void addItems(const LayoutItems &items); - void addRow(const LayoutItems &items); - - bool isForm() const; - - struct Slice; - QList stack; -}; - -class QTCREATOR_UTILS_EXPORT LayoutExtender : public LayoutBuilder -{ -public: - explicit LayoutExtender(QLayout *layout); - ~LayoutExtender(); -}; - } // Layouting -- cgit v1.2.3 From 470c95c94be58905bc3202d3b58175add5f576fa Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 3 May 2023 16:00:22 +0200 Subject: Utils: Rename QtcProcess -> Process Task-number: QTCREATORBUG-29102 Change-Id: Ibc264f9db6a32206e4097766ee3f7d0b35225a5c Reviewed-by: Qt CI Bot Reviewed-by: Reviewed-by: hjk --- src/libs/extensionsystem/pluginmanager.cpp | 2 +- src/libs/qmljs/qmljsplugindumper.cpp | 8 +- src/libs/qmljs/qmljsplugindumper.h | 6 +- src/libs/utils/archive.cpp | 6 +- src/libs/utils/archive.h | 4 +- src/libs/utils/buildablehelperlibrary.cpp | 4 +- src/libs/utils/clangutils.cpp | 2 +- src/libs/utils/devicefileaccess.cpp | 8 +- src/libs/utils/deviceshell.cpp | 14 +- src/libs/utils/deviceshell.h | 6 +- src/libs/utils/filestreamer.cpp | 18 +-- src/libs/utils/pathchooser.cpp | 2 +- src/libs/utils/processinfo.cpp | 6 +- src/libs/utils/processinterface.h | 2 +- src/libs/utils/qtcprocess.cpp | 204 ++++++++++++++--------------- src/libs/utils/qtcprocess.h | 10 +- src/libs/utils/terminalhooks.cpp | 4 +- 17 files changed, 153 insertions(+), 153 deletions(-) (limited to 'src/libs') diff --git a/src/libs/extensionsystem/pluginmanager.cpp b/src/libs/extensionsystem/pluginmanager.cpp index 6ca6ef114c..c22cdfdca0 100644 --- a/src/libs/extensionsystem/pluginmanager.cpp +++ b/src/libs/extensionsystem/pluginmanager.cpp @@ -401,7 +401,7 @@ QString PluginManager::systemInformation() QString result; CommandLine qtDiag(FilePath::fromString(QLibraryInfo::location(QLibraryInfo::BinariesPath)) .pathAppended("qtdiag").withExecutableSuffix()); - QtcProcess qtDiagProc; + Process qtDiagProc; qtDiagProc.setCommand(qtDiag); qtDiagProc.runBlocking(); if (qtDiagProc.result() == ProcessResult::FinishedWithSuccess) diff --git a/src/libs/qmljs/qmljsplugindumper.cpp b/src/libs/qmljs/qmljsplugindumper.cpp index 3de270923b..b566905735 100644 --- a/src/libs/qmljs/qmljsplugindumper.cpp +++ b/src/libs/qmljs/qmljsplugindumper.cpp @@ -203,7 +203,7 @@ static void printParseWarnings(const FilePath &libraryPath, const QString &warni "%2").arg(libraryPath.toUserOutput(), warning)); } -static QString qmlPluginDumpErrorMessage(QtcProcess *process) +static QString qmlPluginDumpErrorMessage(Process *process) { QString errorMessage; const QString binary = process->commandLine().executable().toUserOutput(); @@ -237,7 +237,7 @@ static QString qmlPluginDumpErrorMessage(QtcProcess *process) return errorMessage; } -void PluginDumper::qmlPluginTypeDumpDone(QtcProcess *process) +void PluginDumper::qmlPluginTypeDumpDone(Process *process) { process->deleteLater(); @@ -597,11 +597,11 @@ void PluginDumper::loadQmltypesFile(const FilePaths &qmltypesFilePaths, void PluginDumper::runQmlDump(const ModelManagerInterface::ProjectInfo &info, const QStringList &arguments, const FilePath &importPath) { - auto process = new QtcProcess(this); + auto process = new Process(this); process->setEnvironment(info.qmlDumpEnvironment); process->setWorkingDirectory(importPath); process->setCommand({info.qmlDumpPath, arguments}); - connect(process, &QtcProcess::done, this, [this, process] { qmlPluginTypeDumpDone(process); }); + connect(process, &Process::done, this, [this, process] { qmlPluginTypeDumpDone(process); }); process->start(); m_runningQmldumps.insert(process, importPath); } diff --git a/src/libs/qmljs/qmljsplugindumper.h b/src/libs/qmljs/qmljsplugindumper.h index 1b672cefdc..e27bfeba96 100644 --- a/src/libs/qmljs/qmljsplugindumper.h +++ b/src/libs/qmljs/qmljsplugindumper.h @@ -14,7 +14,7 @@ QT_END_NAMESPACE namespace Utils { class FileSystemWatcher; -class QtcProcess; +class Process; } namespace QmlJS { @@ -41,7 +41,7 @@ private: const QString &importUri, const QString &importVersion); Q_INVOKABLE void dumpAllPlugins(); - void qmlPluginTypeDumpDone(Utils::QtcProcess *process); + void qmlPluginTypeDumpDone(Utils::Process *process); void pluginChanged(const QString &pluginLibrary); private: @@ -102,7 +102,7 @@ private: ModelManagerInterface *m_modelManager; Utils::FileSystemWatcher *m_pluginWatcher; - QHash m_runningQmldumps; + QHash m_runningQmldumps; QList m_plugins; QHash m_libraryToPluginIndex; QHash m_qtToInfo; diff --git a/src/libs/utils/archive.cpp b/src/libs/utils/archive.cpp index 408c75d7c5..db541293e3 100644 --- a/src/libs/utils/archive.cpp +++ b/src/libs/utils/archive.cpp @@ -160,12 +160,12 @@ void Archive::unarchive() m_workingDirectory.ensureWritableDir(); - m_process.reset(new QtcProcess); + m_process.reset(new Process); m_process->setProcessChannelMode(QProcess::MergedChannels); - QObject::connect(m_process.get(), &QtcProcess::readyReadStandardOutput, this, [this] { + QObject::connect(m_process.get(), &Process::readyReadStandardOutput, this, [this] { emit outputReceived(m_process->readAllStandardOutput()); }); - QObject::connect(m_process.get(), &QtcProcess::done, this, [this] { + QObject::connect(m_process.get(), &Process::done, this, [this] { const bool successfulFinish = m_process->result() == ProcessResult::FinishedWithSuccess; if (!successfulFinish) emit outputReceived(Tr::tr("Command failed.")); diff --git a/src/libs/utils/archive.h b/src/libs/utils/archive.h index 30f58585e3..ccf62d3885 100644 --- a/src/libs/utils/archive.h +++ b/src/libs/utils/archive.h @@ -12,7 +12,7 @@ namespace Utils { class FilePath; -class QtcProcess; +class Process; class QTCREATOR_UTILS_EXPORT Archive : public QObject { @@ -33,7 +33,7 @@ signals: private: CommandLine m_commandLine; FilePath m_workingDirectory; - std::unique_ptr m_process; + std::unique_ptr m_process; }; } // namespace Utils diff --git a/src/libs/utils/buildablehelperlibrary.cpp b/src/libs/utils/buildablehelperlibrary.cpp index eac24cfdba..f4cb50b454 100644 --- a/src/libs/utils/buildablehelperlibrary.cpp +++ b/src/libs/utils/buildablehelperlibrary.cpp @@ -21,7 +21,7 @@ bool BuildableHelperLibrary::isQtChooser(const FilePath &filePath) FilePath BuildableHelperLibrary::qtChooserToQmakePath(const FilePath &qtChooser) { const QString toolDir = QLatin1String("QTTOOLDIR=\""); - QtcProcess proc; + Process proc; proc.setTimeoutS(1); proc.setCommand({qtChooser, {"-print-env"}}); proc.runBlocking(); @@ -103,7 +103,7 @@ QString BuildableHelperLibrary::qtVersionForQMake(const FilePath &qmakePath) if (qmakePath.isEmpty()) return QString(); - QtcProcess qmake; + Process qmake; qmake.setTimeoutS(5); qmake.setCommand({qmakePath, {"--version"}}); qmake.runBlocking(); diff --git a/src/libs/utils/clangutils.cpp b/src/libs/utils/clangutils.cpp index ee4868f94b..4c8c7d801d 100644 --- a/src/libs/utils/clangutils.cpp +++ b/src/libs/utils/clangutils.cpp @@ -13,7 +13,7 @@ namespace Utils { static QVersionNumber getClangdVersion(const FilePath &clangdFilePath) { - QtcProcess clangdProc; + Process clangdProc; clangdProc.setCommand({clangdFilePath, {"--version"}}); clangdProc.runBlocking(); if (clangdProc.result() != ProcessResult::FinishedWithSuccess) diff --git a/src/libs/utils/devicefileaccess.cpp b/src/libs/utils/devicefileaccess.cpp index 76f90efb69..6b07fbdeb4 100644 --- a/src/libs/utils/devicefileaccess.cpp +++ b/src/libs/utils/devicefileaccess.cpp @@ -220,13 +220,13 @@ expected_str DeviceFileAccess::copyRecursively(const FilePath &src, if (isSrcOrTargetQrc || !targetTar.isExecutableFile() || !sourceTar.isExecutableFile()) return copyRecursively_fallback(src, target); - QtcProcess srcProcess; - QtcProcess targetProcess; + Process srcProcess; + Process targetProcess; targetProcess.setProcessMode(ProcessMode::Writer); QObject::connect(&srcProcess, - &QtcProcess::readyReadStandardOutput, + &Process::readyReadStandardOutput, &targetProcess, [&srcProcess, &targetProcess]() { targetProcess.writeRaw(srcProcess.readAllRawStandardOutput()); @@ -960,7 +960,7 @@ expected_str UnixDeviceFileAccess::fileContents(const FilePath &file #ifndef UTILS_STATIC_LIBRARY const FilePath dd = filePath.withNewPath("dd"); - QtcProcess p; + Process p; p.setCommand({dd, args, OsType::OsTypeLinux}); p.runBlocking(); if (p.exitCode() != 0) { diff --git a/src/libs/utils/deviceshell.cpp b/src/libs/utils/deviceshell.cpp index 35b2a9ad82..0641c10c38 100644 --- a/src/libs/utils/deviceshell.cpp +++ b/src/libs/utils/deviceshell.cpp @@ -81,7 +81,7 @@ RunResult DeviceShell::run(const CommandLine &cmd, const QByteArray &stdInData) useProcess |= stdInData.size() > (1024 * 100); if (useProcess) { - QtcProcess proc; + Process proc; const CommandLine fallbackCmd = createFallbackCommand(cmd); qCDebug(deviceShellLog) << "Running fallback:" << fallbackCmd; proc.setCommand(fallbackCmd); @@ -136,7 +136,7 @@ void DeviceShell::close() * Override this function to setup the shell process. * The default implementation just sets the command line to "bash" */ -void DeviceShell::setupShellProcess(QtcProcess *shellProcess) +void DeviceShell::setupShellProcess(Process *shellProcess) { shellProcess->setCommand(CommandLine{"bash"}); } @@ -171,8 +171,8 @@ void DeviceShell::startupFailed(const CommandLine &cmdLine) */ bool DeviceShell::start() { - m_shellProcess = std::make_unique(); - connect(m_shellProcess.get(), &QtcProcess::done, m_shellProcess.get(), + m_shellProcess = std::make_unique(); + connect(m_shellProcess.get(), &Process::done, m_shellProcess.get(), [this] { emit done(m_shellProcess->resultData()); }); connect(&m_thread, &QThread::finished, m_shellProcess.get(), [this] { closeShellProcess(); }, Qt::DirectConnection); @@ -199,11 +199,11 @@ bool DeviceShell::start() if (installShellScript()) { connect(m_shellProcess.get(), - &QtcProcess::readyReadStandardOutput, + &Process::readyReadStandardOutput, m_shellProcess.get(), [this] { onReadyRead(); }); connect(m_shellProcess.get(), - &QtcProcess::readyReadStandardError, + &Process::readyReadStandardError, m_shellProcess.get(), [this] { const QByteArray stdErr = m_shellProcess->readAllRawStandardError(); @@ -216,7 +216,7 @@ bool DeviceShell::start() return false; } - connect(m_shellProcess.get(), &QtcProcess::done, m_shellProcess.get(), [this] { + connect(m_shellProcess.get(), &Process::done, m_shellProcess.get(), [this] { if (m_shellProcess->resultData().m_exitCode != EXIT_SUCCESS || m_shellProcess->resultData().m_exitStatus != QProcess::NormalExit) { qCWarning(deviceShellLog) << "Shell exited with error code:" diff --git a/src/libs/utils/deviceshell.h b/src/libs/utils/deviceshell.h index 83af1db365..052aac1838 100644 --- a/src/libs/utils/deviceshell.h +++ b/src/libs/utils/deviceshell.h @@ -19,7 +19,7 @@ namespace Utils { class CommandLine; class ProcessResultData; -class QtcProcess; +class Process; class DeviceShellImpl; @@ -57,7 +57,7 @@ protected: void close(); private: - virtual void setupShellProcess(QtcProcess *shellProcess); + virtual void setupShellProcess(Process *shellProcess); virtual CommandLine createFallbackCommand(const CommandLine &cmdLine); bool installShellScript(); @@ -73,7 +73,7 @@ private: QWaitCondition *waiter; }; - std::unique_ptr m_shellProcess; + std::unique_ptr m_shellProcess; QThread m_thread; int m_currentId{0}; diff --git a/src/libs/utils/filestreamer.cpp b/src/libs/utils/filestreamer.cpp index 744f682617..e4dea9887d 100644 --- a/src/libs/utils/filestreamer.cpp +++ b/src/libs/utils/filestreamer.cpp @@ -84,12 +84,12 @@ signals: private: TaskItem remoteTask() final { - const auto setup = [this](QtcProcess &process) { + const auto setup = [this](Process &process) { const QStringList args = {"if=" + m_filePath.path()}; const FilePath dd = m_filePath.withNewPath("dd"); process.setCommand({dd, args, OsType::OsTypeLinux}); - QtcProcess *processPtr = &process; - connect(processPtr, &QtcProcess::readyReadStandardOutput, this, [this, processPtr] { + Process *processPtr = &process; + connect(processPtr, &Process::readyReadStandardOutput, this, [this, processPtr] { emit readyRead(processPtr->readAllRawStandardOutput()); }); }; @@ -246,11 +246,11 @@ signals: private: TaskItem remoteTask() final { - const auto setup = [this](QtcProcess &process) { + const auto setup = [this](Process &process) { m_writeBuffer = new WriteBuffer(false, &process); - connect(m_writeBuffer, &WriteBuffer::writeRequested, &process, &QtcProcess::writeRaw); + connect(m_writeBuffer, &WriteBuffer::writeRequested, &process, &Process::writeRaw); connect(m_writeBuffer, &WriteBuffer::closeWriteChannelRequested, - &process, &QtcProcess::closeWriteChannel); + &process, &Process::closeWriteChannel); const QStringList args = {"of=" + m_filePath.path()}; const FilePath dd = m_filePath.withNewPath("dd"); process.setCommand({dd, args, OsType::OsTypeLinux}); @@ -258,9 +258,9 @@ private: process.setProcessMode(ProcessMode::Writer); else process.setWriteData(m_writeData); - connect(&process, &QtcProcess::started, this, [this] { emit started(); }); + connect(&process, &Process::started, this, [this] { emit started(); }); }; - const auto finalize = [this](const QtcProcess &) { + const auto finalize = [this](const Process &) { delete m_writeBuffer; m_writeBuffer = nullptr; }; @@ -311,7 +311,7 @@ static Group sameRemoteDeviceTransferTask(const FilePath &source, const FilePath QTC_CHECK(destination.needsDevice()); QTC_CHECK(source.isSameDevice(destination)); - const auto setup = [source, destination](QtcProcess &process) { + const auto setup = [source, destination](Process &process) { const QStringList args = {source.path(), destination.path()}; const FilePath cp = source.withNewPath("cp"); process.setCommand({cp, args, OsType::OsTypeLinux}); diff --git a/src/libs/utils/pathchooser.cpp b/src/libs/utils/pathchooser.cpp index 124fdc4cef..0c43e8f527 100644 --- a/src/libs/utils/pathchooser.cpp +++ b/src/libs/utils/pathchooser.cpp @@ -130,7 +130,7 @@ QString BinaryVersionToolTipEventFilter::toolVersion(const CommandLine &cmd) { if (cmd.executable().isEmpty()) return QString(); - QtcProcess proc; + Process proc; proc.setTimeoutS(1); proc.setCommand(cmd); proc.runBlocking(); diff --git a/src/libs/utils/processinfo.cpp b/src/libs/utils/processinfo.cpp index d6ecd4d234..dc41a8f63e 100644 --- a/src/libs/utils/processinfo.cpp +++ b/src/libs/utils/processinfo.cpp @@ -50,7 +50,7 @@ static QList getLocalProcessesUsingProc(const FilePath &procDir) cmd.addArgs(execs, CommandLine::Raw); - QtcProcess procProcess; + Process procProcess; procProcess.setCommand(cmd); procProcess.runBlocking(); @@ -83,7 +83,7 @@ static QList getLocalProcessesUsingProc(const FilePath &procDir) static QMap getLocalProcessDataUsingPs(const FilePath &deviceRoot, const QString &column) { - QtcProcess process; + Process process; process.setCommand({deviceRoot.withNewPath("ps"), {"-e", "-o", "pid," + column}}); process.runBlocking(); @@ -129,7 +129,7 @@ static QList getLocalProcessesUsingPs(const FilePath &deviceRoot) static QList getProcessesUsingPidin(const FilePath &pidin) { - QtcProcess process; + Process process; process.setCommand({pidin, {"-F", "%a %A {/%n}"}}); process.runBlocking(); diff --git a/src/libs/utils/processinterface.h b/src/libs/utils/processinterface.h index bd02428ef9..e06076d60b 100644 --- a/src/libs/utils/processinterface.h +++ b/src/libs/utils/processinterface.h @@ -142,7 +142,7 @@ private: virtual ProcessBlockingInterface *processBlockingInterface() const { return nullptr; } - friend class QtcProcess; + friend class Process; friend class Internal::QtcProcessPrivate; }; diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/qtcprocess.cpp index 593c445648..1fe97b3d2f 100644 --- a/src/libs/utils/qtcprocess.cpp +++ b/src/libs/utils/qtcprocess.cpp @@ -717,7 +717,7 @@ private: class QtcProcessPrivate : public QObject { public: - explicit QtcProcessPrivate(QtcProcess *parent) + explicit QtcProcessPrivate(Process *parent) : QObject(parent) , q(parent) , m_killTimer(this) @@ -775,7 +775,7 @@ public: return rootCommand; } - QtcProcess *q; + Process *q; std::unique_ptr m_blockingInterface; std::unique_ptr m_process; ProcessSetupData m_setup; @@ -786,7 +786,7 @@ public: void handleDone(const ProcessResultData &data); void clearForRun(); - void emitGuardedSignal(void (QtcProcess::* signalName)()) { + void emitGuardedSignal(void (Process::* signalName)()) { GuardLocker locker(m_guard); emit (q->*signalName)(); } @@ -1092,7 +1092,7 @@ ProcessResult QtcProcessPrivate::interpretExitCode(int exitCode) \sa Utils::ProcessArgs */ -QtcProcess::QtcProcess(QObject *parent) +Process::Process(QObject *parent) : QObject(parent), d(new QtcProcessPrivate(this)) { @@ -1103,7 +1103,7 @@ QtcProcess::QtcProcess(QObject *parent) Q_UNUSED(qProcessProcessErrorMeta) } -QtcProcess::~QtcProcess() +Process::~Process() { QTC_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting QtcProcess instance directly from " "one of its signal handlers will lead to crash!")); @@ -1112,62 +1112,62 @@ QtcProcess::~QtcProcess() delete d; } -void QtcProcess::setProcessImpl(ProcessImpl processImpl) +void Process::setProcessImpl(ProcessImpl processImpl) { d->m_setup.m_processImpl = processImpl; } -void QtcProcess::setPtyData(const std::optional &data) +void Process::setPtyData(const std::optional &data) { d->m_setup.m_ptyData = data; } -std::optional QtcProcess::ptyData() const +std::optional Process::ptyData() const { return d->m_setup.m_ptyData; } -ProcessMode QtcProcess::processMode() const +ProcessMode Process::processMode() const { return d->m_setup.m_processMode; } -void QtcProcess::setTerminalMode(TerminalMode mode) +void Process::setTerminalMode(TerminalMode mode) { d->m_setup.m_terminalMode = mode; } -TerminalMode QtcProcess::terminalMode() const +TerminalMode Process::terminalMode() const { return d->m_setup.m_terminalMode; } -void QtcProcess::setProcessMode(ProcessMode processMode) +void Process::setProcessMode(ProcessMode processMode) { d->m_setup.m_processMode = processMode; } -void QtcProcess::setEnvironment(const Environment &env) +void Process::setEnvironment(const Environment &env) { d->m_setup.m_environment = env; } -const Environment &QtcProcess::environment() const +const Environment &Process::environment() const { return d->m_setup.m_environment; } -void QtcProcess::setControlEnvironment(const Environment &environment) +void Process::setControlEnvironment(const Environment &environment) { d->m_setup.m_controlEnvironment = environment; } -const Environment &QtcProcess::controlEnvironment() const +const Environment &Process::controlEnvironment() const { return d->m_setup.m_controlEnvironment; } -void QtcProcess::setCommand(const CommandLine &cmdLine) +void Process::setCommand(const CommandLine &cmdLine) { if (d->m_setup.m_workingDirectory.needsDevice() && cmdLine.executable().needsDevice()) { QTC_CHECK(d->m_setup.m_workingDirectory.host() == cmdLine.executable().host()); @@ -1175,17 +1175,17 @@ void QtcProcess::setCommand(const CommandLine &cmdLine) d->m_setup.m_commandLine = cmdLine; } -const CommandLine &QtcProcess::commandLine() const +const CommandLine &Process::commandLine() const { return d->m_setup.m_commandLine; } -FilePath QtcProcess::workingDirectory() const +FilePath Process::workingDirectory() const { return d->m_setup.m_workingDirectory; } -void QtcProcess::setWorkingDirectory(const FilePath &dir) +void Process::setWorkingDirectory(const FilePath &dir) { if (dir.needsDevice() && d->m_setup.m_commandLine.executable().needsDevice()) { QTC_CHECK(dir.host() == d->m_setup.m_commandLine.executable().host()); @@ -1193,12 +1193,12 @@ void QtcProcess::setWorkingDirectory(const FilePath &dir) d->m_setup.m_workingDirectory = dir; } -void QtcProcess::setUseCtrlCStub(bool enabled) +void Process::setUseCtrlCStub(bool enabled) { d->m_setup.m_useCtrlCStub = enabled; } -void QtcProcess::start() +void Process::start() { QTC_ASSERT(state() == QProcess::NotRunning, return); QTC_ASSERT(!(d->m_process && d->m_guard.isLocked()), @@ -1217,36 +1217,36 @@ void QtcProcess::start() d->m_state = QProcess::Starting; d->m_process->m_setup = d->m_setup; d->m_process->m_setup.m_commandLine = d->fullCommandLine(); - d->emitGuardedSignal(&QtcProcess::starting); + d->emitGuardedSignal(&Process::starting); d->m_process->start(); } -void QtcProcess::terminate() +void Process::terminate() { d->sendControlSignal(ControlSignal::Terminate); } -void QtcProcess::kill() +void Process::kill() { d->sendControlSignal(ControlSignal::Kill); } -void QtcProcess::interrupt() +void Process::interrupt() { d->sendControlSignal(ControlSignal::Interrupt); } -void QtcProcess::kickoffProcess() +void Process::kickoffProcess() { d->sendControlSignal(ControlSignal::KickOff); } -void QtcProcess::closeWriteChannel() +void Process::closeWriteChannel() { d->sendControlSignal(ControlSignal::CloseWriteChannel); } -bool QtcProcess::startDetached(const CommandLine &cmd, const FilePath &workingDirectory, qint64 *pid) +bool Process::startDetached(const CommandLine &cmd, const FilePath &workingDirectory, qint64 *pid) { return QProcess::startDetached(cmd.executable().toUserOutput(), cmd.splitArguments(), @@ -1254,37 +1254,37 @@ bool QtcProcess::startDetached(const CommandLine &cmd, const FilePath &workingDi pid); } -void QtcProcess::setLowPriority() +void Process::setLowPriority() { d->m_setup.m_lowPriority = true; } -void QtcProcess::setDisableUnixTerminal() +void Process::setDisableUnixTerminal() { d->m_setup.m_unixTerminalDisabled = true; } -void QtcProcess::setAbortOnMetaChars(bool abort) +void Process::setAbortOnMetaChars(bool abort) { d->m_setup.m_abortOnMetaChars = abort; } -void QtcProcess::setRunAsRoot(bool on) +void Process::setRunAsRoot(bool on) { d->m_setup.m_runAsRoot = on; } -bool QtcProcess::isRunAsRoot() const +bool Process::isRunAsRoot() const { return d->m_setup.m_runAsRoot; } -void QtcProcess::setStandardInputFile(const QString &inputFile) +void Process::setStandardInputFile(const QString &inputFile) { d->m_setup.m_standardInputFile = inputFile; } -QString QtcProcess::toStandaloneCommandLine() const +QString Process::toStandaloneCommandLine() const { QStringList parts; parts.append("/usr/bin/env"); @@ -1303,47 +1303,47 @@ QString QtcProcess::toStandaloneCommandLine() const return parts.join(" "); } -void QtcProcess::setCreateConsoleOnWindows(bool create) +void Process::setCreateConsoleOnWindows(bool create) { d->m_setup.m_createConsoleOnWindows = create; } -bool QtcProcess::createConsoleOnWindows() const +bool Process::createConsoleOnWindows() const { return d->m_setup.m_createConsoleOnWindows; } -void QtcProcess::setExtraData(const QString &key, const QVariant &value) +void Process::setExtraData(const QString &key, const QVariant &value) { d->m_setup.m_extraData.insert(key, value); } -QVariant QtcProcess::extraData(const QString &key) const +QVariant Process::extraData(const QString &key) const { return d->m_setup.m_extraData.value(key); } -void QtcProcess::setExtraData(const QVariantHash &extraData) +void Process::setExtraData(const QVariantHash &extraData) { d->m_setup.m_extraData = extraData; } -QVariantHash QtcProcess::extraData() const +QVariantHash Process::extraData() const { return d->m_setup.m_extraData; } -void QtcProcess::setReaperTimeout(int msecs) +void Process::setReaperTimeout(int msecs) { d->m_setup.m_reaperTimeout = msecs; } -int QtcProcess::reaperTimeout() const +int Process::reaperTimeout() const { return d->m_setup.m_reaperTimeout; } -void QtcProcess::setRemoteProcessHooks(const DeviceProcessHooks &hooks) +void Process::setRemoteProcessHooks(const DeviceProcessHooks &hooks) { s_deviceHooks = hooks; } @@ -1378,7 +1378,7 @@ static bool askToKill(const CommandLine &command) // occurs on stderr/stdout as opposed to waitForFinished()). Returns false if a timeout // occurs. Checking of the process' exit state/code still has to be done. -bool QtcProcess::readDataFromProcess(QByteArray *stdOut, QByteArray *stdErr, int timeoutS) +bool Process::readDataFromProcess(QByteArray *stdOut, QByteArray *stdErr, int timeoutS) { enum { syncDebug = 0 }; if (syncDebug) @@ -1418,39 +1418,39 @@ bool QtcProcess::readDataFromProcess(QByteArray *stdOut, QByteArray *stdErr, int return finished; } -ProcessResult QtcProcess::result() const +ProcessResult Process::result() const { return d->m_result; } -ProcessResultData QtcProcess::resultData() const +ProcessResultData Process::resultData() const { return d->m_resultData; } -int QtcProcess::exitCode() const +int Process::exitCode() const { return resultData().m_exitCode; } -QProcess::ExitStatus QtcProcess::exitStatus() const +QProcess::ExitStatus Process::exitStatus() const { return resultData().m_exitStatus; } -QProcess::ProcessError QtcProcess::error() const +QProcess::ProcessError Process::error() const { return resultData().m_error; } -QString QtcProcess::errorString() const +QString Process::errorString() const { return resultData().m_errorString; } // Path utilities -Environment QtcProcess::systemEnvironmentForBinary(const FilePath &filePath) +Environment Process::systemEnvironmentForBinary(const FilePath &filePath) { if (filePath.needsDevice()) { QTC_ASSERT(s_deviceHooks.systemEnvironmentForBinary, return {}); @@ -1460,37 +1460,37 @@ Environment QtcProcess::systemEnvironmentForBinary(const FilePath &filePath) return Environment::systemEnvironment(); } -qint64 QtcProcess::applicationMainThreadId() const +qint64 Process::applicationMainThreadId() const { return d->m_applicationMainThreadId; } -QProcess::ProcessChannelMode QtcProcess::processChannelMode() const +QProcess::ProcessChannelMode Process::processChannelMode() const { return d->m_setup.m_processChannelMode; } -void QtcProcess::setProcessChannelMode(QProcess::ProcessChannelMode mode) +void Process::setProcessChannelMode(QProcess::ProcessChannelMode mode) { d->m_setup.m_processChannelMode = mode; } -QProcess::ProcessState QtcProcess::state() const +QProcess::ProcessState Process::state() const { return d->m_state; } -bool QtcProcess::isRunning() const +bool Process::isRunning() const { return state() == QProcess::Running; } -qint64 QtcProcess::processId() const +qint64 Process::processId() const { return d->m_processId; } -bool QtcProcess::waitForStarted(int msecs) +bool Process::waitForStarted(int msecs) { QTC_ASSERT(d->m_process, return false); if (d->m_state == QProcess::Running) @@ -1501,7 +1501,7 @@ bool QtcProcess::waitForStarted(int msecs) ProcessSignalType::Started, msecs); } -bool QtcProcess::waitForReadyRead(int msecs) +bool Process::waitForReadyRead(int msecs) { QTC_ASSERT(d->m_process, return false); if (d->m_state == QProcess::NotRunning) @@ -1509,7 +1509,7 @@ bool QtcProcess::waitForReadyRead(int msecs) return d->waitForSignal(ProcessSignalType::ReadyRead, msecs); } -bool QtcProcess::waitForFinished(int msecs) +bool Process::waitForFinished(int msecs) { QTC_ASSERT(d->m_process, return false); if (d->m_state == QProcess::NotRunning) @@ -1517,17 +1517,17 @@ bool QtcProcess::waitForFinished(int msecs) return d->waitForSignal(ProcessSignalType::Done, msecs); } -QByteArray QtcProcess::readAllRawStandardOutput() +QByteArray Process::readAllRawStandardOutput() { return d->m_stdOut.readAllData(); } -QByteArray QtcProcess::readAllRawStandardError() +QByteArray Process::readAllRawStandardError() { return d->m_stdErr.readAllData(); } -qint64 QtcProcess::write(const QString &input) +qint64 Process::write(const QString &input) { // Non-windows is assumed to be UTF-8 if (commandLine().executable().osType() != OsTypeWindows) @@ -1542,7 +1542,7 @@ qint64 QtcProcess::write(const QString &input) return writeRaw(input.toUtf8()); } -qint64 QtcProcess::writeRaw(const QByteArray &input) +qint64 Process::writeRaw(const QByteArray &input) { QTC_ASSERT(processMode() == ProcessMode::Writer, return -1); QTC_ASSERT(d->m_process, return -1); @@ -1555,7 +1555,7 @@ qint64 QtcProcess::writeRaw(const QByteArray &input) return result; } -void QtcProcess::close() +void Process::close() { QTC_ASSERT(QThread::currentThread() == thread(), return); if (d->m_process) { @@ -1575,7 +1575,7 @@ void QtcProcess::close() Calls terminate() directly and after a delay of reaperTimeout() it calls kill() if the process is still running. */ -void QtcProcess::stop() +void Process::stop() { if (state() == QProcess::NotRunning) return; @@ -1584,12 +1584,12 @@ void QtcProcess::stop() d->m_killTimer.start(d->m_process->m_setup.m_reaperTimeout); } -QString QtcProcess::readAllStandardOutput() +QString Process::readAllStandardOutput() { return QString::fromUtf8(readAllRawStandardOutput()); } -QString QtcProcess::readAllStandardError() +QString Process::readAllStandardError() { return QString::fromUtf8(readAllRawStandardError()); } @@ -1624,7 +1624,7 @@ QString QtcProcess::readAllStandardError() as this will cause event loop problems. */ -QString QtcProcess::exitMessage() const +QString Process::exitMessage() const { const QString fullCmd = commandLine().toUserOutput(); switch (result()) { @@ -1644,7 +1644,7 @@ QString QtcProcess::exitMessage() const return {}; } -QByteArray QtcProcess::allRawOutput() const +QByteArray Process::allRawOutput() const { QTC_CHECK(d->m_stdOut.keepRawData); QTC_CHECK(d->m_stdErr.keepRawData); @@ -1658,7 +1658,7 @@ QByteArray QtcProcess::allRawOutput() const return !d->m_stdOut.rawData.isEmpty() ? d->m_stdOut.rawData : d->m_stdErr.rawData; } -QString QtcProcess::allOutput() const +QString Process::allOutput() const { QTC_CHECK(d->m_stdOut.keepRawData); QTC_CHECK(d->m_stdErr.keepRawData); @@ -1675,30 +1675,30 @@ QString QtcProcess::allOutput() const return !out.isEmpty() ? out : err; } -QByteArray QtcProcess::rawStdOut() const +QByteArray Process::rawStdOut() const { QTC_CHECK(d->m_stdOut.keepRawData); return d->m_stdOut.rawData; } -QString QtcProcess::stdOut() const +QString Process::stdOut() const { QTC_CHECK(d->m_stdOut.keepRawData); return d->m_codec->toUnicode(d->m_stdOut.rawData); } -QString QtcProcess::stdErr() const +QString Process::stdErr() const { QTC_CHECK(d->m_stdErr.keepRawData); return d->m_codec->toUnicode(d->m_stdErr.rawData); } -QString QtcProcess::cleanedStdOut() const +QString Process::cleanedStdOut() const { return Utils::normalizeNewlines(stdOut()); } -QString QtcProcess::cleanedStdErr() const +QString Process::cleanedStdErr() const { return Utils::normalizeNewlines(stdErr()); } @@ -1713,17 +1713,17 @@ static QStringList splitLines(const QString &text) return result; } -const QStringList QtcProcess::stdOutLines() const +const QStringList Process::stdOutLines() const { return splitLines(cleanedStdOut()); } -const QStringList QtcProcess::stdErrLines() const +const QStringList Process::stdErrLines() const { return splitLines(cleanedStdErr()); } -QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const QtcProcess &r) +QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const Process &r) { QDebug nsp = str.nospace(); nsp << "QtcProcess: result=" @@ -1802,7 +1802,7 @@ void ChannelBuffer::handleRest() } } -void QtcProcess::setTimeoutS(int timeoutS) +void Process::setTimeoutS(int timeoutS) { if (timeoutS > 0) d->m_maxHangTimerCount = qMax(2, timeoutS); @@ -1810,33 +1810,33 @@ void QtcProcess::setTimeoutS(int timeoutS) d->m_maxHangTimerCount = INT_MAX / 1000; } -int QtcProcess::timeoutS() const +int Process::timeoutS() const { return d->m_maxHangTimerCount; } -void QtcProcess::setCodec(QTextCodec *c) +void Process::setCodec(QTextCodec *c) { QTC_ASSERT(c, return); d->m_codec = c; } -void QtcProcess::setTimeOutMessageBoxEnabled(bool v) +void Process::setTimeOutMessageBoxEnabled(bool v) { d->m_timeOutMessageBoxEnabled = v; } -void QtcProcess::setExitCodeInterpreter(const ExitCodeInterpreter &interpreter) +void Process::setExitCodeInterpreter(const ExitCodeInterpreter &interpreter) { d->m_exitCodeInterpreter = interpreter; } -void QtcProcess::setWriteData(const QByteArray &writeData) +void Process::setWriteData(const QByteArray &writeData) { d->m_setup.m_writeData = writeData; } -void QtcProcess::runBlocking(EventLoopMode eventLoopMode) +void Process::runBlocking(EventLoopMode eventLoopMode) { // Attach a dynamic property with info about blocking type d->storeEventLoopDebugInfo(int(eventLoopMode)); @@ -1845,7 +1845,7 @@ void QtcProcess::runBlocking(EventLoopMode eventLoopMode) static const int blockingThresholdMs = qtcEnvironmentVariableIntValue("QTC_PROCESS_THRESHOLD"); if (blockingThresholdMs > 0 && isMainThread()) startTime = QDateTime::currentDateTime(); - QtcProcess::start(); + Process::start(); // Remove the dynamic property so that it's not reused in subseqent start() d->storeEventLoopDebugInfo({}); @@ -1897,33 +1897,33 @@ void QtcProcess::runBlocking(EventLoopMode eventLoopMode) } } -void QtcProcess::setStdOutCallback(const TextChannelCallback &callback) +void Process::setStdOutCallback(const TextChannelCallback &callback) { d->m_stdOut.outputCallback = callback; d->m_stdOut.emitSingleLines = false; } -void QtcProcess::setStdOutLineCallback(const TextChannelCallback &callback) +void Process::setStdOutLineCallback(const TextChannelCallback &callback) { d->m_stdOut.outputCallback = callback; d->m_stdOut.emitSingleLines = true; d->m_stdOut.keepRawData = false; } -void QtcProcess::setStdErrCallback(const TextChannelCallback &callback) +void Process::setStdErrCallback(const TextChannelCallback &callback) { d->m_stdErr.outputCallback = callback; d->m_stdErr.emitSingleLines = false; } -void QtcProcess::setStdErrLineCallback(const TextChannelCallback &callback) +void Process::setStdErrLineCallback(const TextChannelCallback &callback) { d->m_stdErr.outputCallback = callback; d->m_stdErr.emitSingleLines = true; d->m_stdErr.keepRawData = false; } -void QtcProcess::setTextChannelMode(Channel channel, TextChannelMode mode) +void Process::setTextChannelMode(Channel channel, TextChannelMode mode) { const TextChannelCallback outputCb = [this](const QString &text) { GuardLocker locker(d->m_guard); @@ -1959,7 +1959,7 @@ void QtcProcess::setTextChannelMode(Channel channel, TextChannelMode mode) } } -TextChannelMode QtcProcess::textChannelMode(Channel channel) const +TextChannelMode Process::textChannelMode(Channel channel) const { ChannelBuffer *buffer = channel == Channel::Output ? &d->m_stdOut : &d->m_stdErr; return buffer->m_textChannelMode; @@ -1993,7 +1993,7 @@ void QtcProcessPrivate::handleStarted(qint64 processId, qint64 applicationMainTh m_processId = processId; m_applicationMainThreadId = applicationMainThreadId; - emitGuardedSignal(&QtcProcess::started); + emitGuardedSignal(&Process::started); } void QtcProcessPrivate::handleReadyRead(const QByteArray &outputData, const QByteArray &errorData) @@ -2011,7 +2011,7 @@ void QtcProcessPrivate::handleReadyRead(const QByteArray &outputData, const QByt std::cout << outputData.constData() << std::flush; } else { m_stdOut.append(outputData); - emitGuardedSignal(&QtcProcess::readyReadStandardOutput); + emitGuardedSignal(&Process::readyReadStandardOutput); } } if (!errorData.isEmpty()) { @@ -2020,7 +2020,7 @@ void QtcProcessPrivate::handleReadyRead(const QByteArray &outputData, const QByt std::cerr << errorData.constData() << std::flush; } else { m_stdErr.append(errorData); - emitGuardedSignal(&QtcProcess::readyReadStandardError); + emitGuardedSignal(&Process::readyReadStandardError); } } } @@ -2077,7 +2077,7 @@ void QtcProcessPrivate::handleDone(const ProcessResultData &data) m_stdOut.handleRest(); m_stdErr.handleRest(); - emitGuardedSignal(&QtcProcess::done); + emitGuardedSignal(&Process::done); m_processId = 0; m_applicationMainThreadId = 0; } @@ -2101,7 +2101,7 @@ void QtcProcessPrivate::setupDebugLog() return duration_cast(system_clock::now().time_since_epoch()).count(); }; - connect(q, &QtcProcess::starting, this, [=] { + connect(q, &Process::starting, this, [=] { const quint64 msNow = now(); setProperty(QTC_PROCESS_STARTTIME, msNow); @@ -2114,7 +2114,7 @@ void QtcProcessPrivate::setupDebugLog() setProperty(QTC_PROCESS_NUMBER, currentNumber); }); - connect(q, &QtcProcess::done, this, [=] { + connect(q, &Process::done, this, [=] { if (!m_process.get()) return; const QVariant n = property(QTC_PROCESS_NUMBER); @@ -2148,7 +2148,7 @@ void QtcProcessPrivate::storeEventLoopDebugInfo(const QVariant &value) ProcessTaskAdapter::ProcessTaskAdapter() { - connect(task(), &QtcProcess::done, this, [this] { + connect(task(), &Process::done, this, [this] { emit done(task()->result() == ProcessResult::FinishedWithSuccess); }); } diff --git a/src/libs/utils/qtcprocess.h b/src/libs/utils/qtcprocess.h index 5e39453e38..f89b6f9b22 100644 --- a/src/libs/utils/qtcprocess.h +++ b/src/libs/utils/qtcprocess.h @@ -28,13 +28,13 @@ class DeviceProcessHooks; class ProcessInterface; class ProcessResultData; -class QTCREATOR_UTILS_EXPORT QtcProcess final : public QObject +class QTCREATOR_UTILS_EXPORT Process final : public QObject { Q_OBJECT public: - explicit QtcProcess(QObject *parent = nullptr); - ~QtcProcess(); + explicit Process(QObject *parent = nullptr); + ~Process(); // ProcessInterface related @@ -194,7 +194,7 @@ signals: void textOnStandardError(const QString &text); private: - friend QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const QtcProcess &r); + friend QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const Process &r); friend class Internal::QtcProcessPrivate; Internal::QtcProcessPrivate *d = nullptr; @@ -207,7 +207,7 @@ public: std::function systemEnvironmentForBinary; }; -class QTCREATOR_UTILS_EXPORT ProcessTaskAdapter : public Tasking::TaskAdapter +class QTCREATOR_UTILS_EXPORT ProcessTaskAdapter : public Tasking::TaskAdapter { public: ProcessTaskAdapter(); diff --git a/src/libs/utils/terminalhooks.cpp b/src/libs/utils/terminalhooks.cpp index d8e53010c3..c7cab04028 100644 --- a/src/libs/utils/terminalhooks.cpp +++ b/src/libs/utils/terminalhooks.cpp @@ -45,7 +45,7 @@ class ExternalTerminalProcessImpl final : public TerminalInterface if (HostOsInfo::isWindowsHost()) { m_terminalProcess.setCommand(cmd); - QObject::connect(&m_terminalProcess, &QtcProcess::done, this, [this] { + QObject::connect(&m_terminalProcess, &Process::done, this, [this] { m_interface->onStubExited(); }); m_terminalProcess.setCreateConsoleOnWindows(true); @@ -82,7 +82,7 @@ class ExternalTerminalProcessImpl final : public TerminalInterface } ExternalTerminalProcessImpl *m_interface; - QtcProcess m_terminalProcess; + Process m_terminalProcess; }; public: -- cgit v1.2.3 From a0f6e8dc04291138ec2305fe7c02a0a460f57fac Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 3 May 2023 17:05:35 +0200 Subject: Utils: Rename qtcprocess.{cpp,h} -> process.{cpp,h} Follows QtcProcess -> Process rename. Change-Id: I97235a9a40cb7fd52944515b7ab878d96528f919 Reviewed-by: hjk Reviewed-by: Qt CI Bot --- src/libs/extensionsystem/pluginmanager.cpp | 2 +- src/libs/qmljs/qmljsplugindumper.cpp | 2 +- src/libs/utils/CMakeLists.txt | 2 +- src/libs/utils/archive.cpp | 2 +- src/libs/utils/buildablehelperlibrary.cpp | 2 +- src/libs/utils/clangutils.cpp | 2 +- src/libs/utils/devicefileaccess.cpp | 2 +- src/libs/utils/deviceshell.cpp | 2 +- src/libs/utils/filestreamer.cpp | 2 +- src/libs/utils/pathchooser.cpp | 2 +- src/libs/utils/process.cpp | 2163 ++++++++++++++++++++++++++++ src/libs/utils/process.h | 219 +++ src/libs/utils/processinfo.cpp | 2 +- src/libs/utils/qtcprocess.cpp | 2163 ---------------------------- src/libs/utils/qtcprocess.h | 219 --- src/libs/utils/terminalhooks.cpp | 2 +- src/libs/utils/utils.qbs | 4 +- 17 files changed, 2396 insertions(+), 2396 deletions(-) create mode 100644 src/libs/utils/process.cpp create mode 100644 src/libs/utils/process.h delete mode 100644 src/libs/utils/qtcprocess.cpp delete mode 100644 src/libs/utils/qtcprocess.h (limited to 'src/libs') diff --git a/src/libs/extensionsystem/pluginmanager.cpp b/src/libs/extensionsystem/pluginmanager.cpp index c22cdfdca0..3ee9951055 100644 --- a/src/libs/extensionsystem/pluginmanager.cpp +++ b/src/libs/extensionsystem/pluginmanager.cpp @@ -35,8 +35,8 @@ #include #include #include +#include #include -#include #include #include diff --git a/src/libs/qmljs/qmljsplugindumper.cpp b/src/libs/qmljs/qmljsplugindumper.cpp index b566905735..761df90ddc 100644 --- a/src/libs/qmljs/qmljsplugindumper.cpp +++ b/src/libs/qmljs/qmljsplugindumper.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index f26c08c7b5..defbbf2794 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -127,6 +127,7 @@ add_qtc_library(Utils port.cpp port.h portlist.cpp portlist.h predicates.h + process.cpp process.h processenums.h processhandle.cpp processhandle.h processinfo.cpp processinfo.h @@ -139,7 +140,6 @@ add_qtc_library(Utils qrcparser.cpp qrcparser.h qtcassert.cpp qtcassert.h qtcolorbutton.cpp qtcolorbutton.h - qtcprocess.cpp qtcprocess.h qtcsettings.cpp qtcsettings.h ranges.h reloadpromptutils.cpp reloadpromptutils.h diff --git a/src/libs/utils/archive.cpp b/src/libs/utils/archive.cpp index db541293e3..5e62835a20 100644 --- a/src/libs/utils/archive.cpp +++ b/src/libs/utils/archive.cpp @@ -5,8 +5,8 @@ #include "algorithm.h" #include "mimeutils.h" +#include "process.h" #include "qtcassert.h" -#include "qtcprocess.h" #include "utilstr.h" #include diff --git a/src/libs/utils/buildablehelperlibrary.cpp b/src/libs/utils/buildablehelperlibrary.cpp index f4cb50b454..cafd5b0452 100644 --- a/src/libs/utils/buildablehelperlibrary.cpp +++ b/src/libs/utils/buildablehelperlibrary.cpp @@ -4,7 +4,7 @@ #include "buildablehelperlibrary.h" #include "environment.h" #include "hostosinfo.h" -#include "qtcprocess.h" +#include "process.h" #include #include diff --git a/src/libs/utils/clangutils.cpp b/src/libs/utils/clangutils.cpp index 4c8c7d801d..6cf265820e 100644 --- a/src/libs/utils/clangutils.cpp +++ b/src/libs/utils/clangutils.cpp @@ -4,7 +4,7 @@ #include "clangutils.h" #include "filepath.h" -#include "qtcprocess.h" +#include "process.h" #include "utilstr.h" #include diff --git a/src/libs/utils/devicefileaccess.cpp b/src/libs/utils/devicefileaccess.cpp index 6b07fbdeb4..bda098e0f6 100644 --- a/src/libs/utils/devicefileaccess.cpp +++ b/src/libs/utils/devicefileaccess.cpp @@ -13,7 +13,7 @@ #include "utilstr.h" #ifndef UTILS_STATIC_LIBRARY -#include "qtcprocess.h" +#include "process.h" #endif #include diff --git a/src/libs/utils/deviceshell.cpp b/src/libs/utils/deviceshell.cpp index 0641c10c38..627fee3d67 100644 --- a/src/libs/utils/deviceshell.cpp +++ b/src/libs/utils/deviceshell.cpp @@ -3,9 +3,9 @@ #include "deviceshell.h" +#include "process.h" #include "processinterface.h" #include "qtcassert.h" -#include "qtcprocess.h" #include #include diff --git a/src/libs/utils/filestreamer.cpp b/src/libs/utils/filestreamer.cpp index e4dea9887d..a42e632000 100644 --- a/src/libs/utils/filestreamer.cpp +++ b/src/libs/utils/filestreamer.cpp @@ -5,7 +5,7 @@ #include "async.h" #include "barrier.h" -#include "qtcprocess.h" +#include "process.h" #include #include diff --git a/src/libs/utils/pathchooser.cpp b/src/libs/utils/pathchooser.cpp index 0c43e8f527..1fe1e96256 100644 --- a/src/libs/utils/pathchooser.cpp +++ b/src/libs/utils/pathchooser.cpp @@ -10,8 +10,8 @@ #include "hostosinfo.h" #include "macroexpander.h" #include "optionpushbutton.h" +#include "process.h" #include "qtcassert.h" -#include "qtcprocess.h" #include "utilstr.h" #include diff --git a/src/libs/utils/process.cpp b/src/libs/utils/process.cpp new file mode 100644 index 0000000000..1b11bd8645 --- /dev/null +++ b/src/libs/utils/process.cpp @@ -0,0 +1,2163 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "process.h" + +#include "algorithm.h" +#include "environment.h" +#include "guard.h" +#include "hostosinfo.h" +#include "launcherinterface.h" +#include "launchersocket.h" +#include "processreaper.h" +#include "processutils.h" +#include "stringutils.h" +#include "terminalhooks.h" +#include "threadutils.h" +#include "utilstr.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef QT_GUI_LIB +// qmlpuppet does not use that. +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +using namespace Utils::Internal; + +namespace Utils { +namespace Internal { + +const char QTC_PROCESS_BLOCKING_TYPE[] = "__BLOCKING_TYPE__"; +const char QTC_PROCESS_NUMBER[] = "__NUMBER__"; +const char QTC_PROCESS_STARTTIME[] = "__STARTTIME__"; + +class MeasureAndRun +{ +public: + MeasureAndRun(const char *functionName) + : m_functionName(functionName) + , m_measureProcess(qtcEnvironmentVariableIsSet("QTC_MEASURE_PROCESS")) + {} + template + std::invoke_result_t measureAndRun(Function &&function, Args&&... args) + { + if (!m_measureProcess) + return std::invoke(std::forward(function), std::forward(args)...); + QElapsedTimer timer; + timer.start(); + auto cleanup = qScopeGuard([this, &timer] { + const qint64 currentNsecs = timer.nsecsElapsed(); + const bool mainThread = isMainThread(); + const int hitThisAll = m_hitThisAll.fetch_add(1) + 1; + const int hitAllAll = m_hitAllAll.fetch_add(1) + 1; + const int hitThisMain = mainThread + ? m_hitThisMain.fetch_add(1) + 1 + : m_hitThisMain.load(); + const int hitAllMain = mainThread + ? m_hitAllMain.fetch_add(1) + 1 + : m_hitAllMain.load(); + const qint64 totalThisAll = toMs(m_totalThisAll.fetch_add(currentNsecs) + currentNsecs); + const qint64 totalAllAll = toMs(m_totalAllAll.fetch_add(currentNsecs) + currentNsecs); + const qint64 totalThisMain = toMs(mainThread + ? m_totalThisMain.fetch_add(currentNsecs) + currentNsecs + : m_totalThisMain.load()); + const qint64 totalAllMain = toMs(mainThread + ? m_totalAllMain.fetch_add(currentNsecs) + currentNsecs + : m_totalAllMain.load()); + printMeasurement(QLatin1String(m_functionName), hitThisAll, toMs(currentNsecs), + totalThisAll, hitAllAll, totalAllAll, mainThread, + hitThisMain, totalThisMain, hitAllMain, totalAllMain); + }); + return std::invoke(std::forward(function), std::forward(args)...); + } +private: + static void printHeader() + { + // [function/thread]: function:(T)his|(A)ll, thread:(M)ain|(A)ll + qDebug() << "+----------------+-------+---------+----------+-------+----------+---------+-------+----------+-------+----------+"; + qDebug() << "| [Function/Thread] = [(T|A)/(M|A)], where: (T)his function, (A)ll functions / threads, (M)ain thread |"; + qDebug() << "+----------------+-------+---------+----------+-------+----------+---------+-------+----------+-------+----------+"; + qDebug() << "| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |"; + qDebug() << "| | [T/A] | [T/A] | [T/A] | [A/A] | [A/A] | | [T/M] | [T/M] | [A/M] | [A/M] |"; + qDebug() << "| Function | Hit | Current | Total | Hit | Total | Current | Hit | Total | Hit | Total |"; + qDebug() << "| Name | Count | Measu- | Measu- | Count | Measu- | is Main | Count | Measu- | Count | Measu- |"; + qDebug() << "| | | rement | rement | | rement | Thread | | rement | | rement |"; + qDebug() << "+----------------+-------+---------+----------+-------+----------+---------+-------+----------+-------+----------+"; + } + static void printMeasurement(const QString &functionName, int hitThisAll, int currentNsecs, + int totalThisAll, int hitAllAll, int totalAllAll, bool isMainThread, + int hitThisMain, int totalThisMain, int hitAllMain, int totalAllMain) + { + static const int repeatHeaderLineCount = 25; + if (s_lineCounter.fetch_add(1) % repeatHeaderLineCount == 0) + printHeader(); + + const QString &functionNameField = QString("%1").arg(functionName, 14); + const QString &hitThisAllField = formatField(hitThisAll, 5); + const QString ¤tNsecsField = formatField(currentNsecs, 7, " ms"); + const QString &totalThisAllField = formatField(totalThisAll, 8, " ms"); + const QString &hitAllAllField = formatField(hitAllAll, 5); + const QString &totalAllAllField = formatField(totalAllAll, 8, " ms"); + const QString &mainThreadField = isMainThread ? QString("%1").arg("yes", 7) + : QString("%1").arg("no", 7); + const QString &hitThisMainField = formatField(hitThisMain, 5); + const QString &totalThisMainField = formatField(totalThisMain, 8, " ms"); + const QString &hitAllMainField = formatField(hitAllMain, 5); + const QString &totalAllMainField = formatField(totalAllMain, 8, " ms"); + + const QString &totalString = QString("| %1 | %2 | %3 | %4 | %5 | %6 | %7 | %8 | %9 | %10 | %11 |") + .arg(functionNameField, hitThisAllField, currentNsecsField, + totalThisAllField, hitAllAllField, totalAllAllField, mainThreadField, + hitThisMainField, totalThisMainField, hitAllMainField, totalAllMainField); + qDebug("%s", qPrintable(totalString)); + } + static QString formatField(int number, int fieldWidth, const QString &suffix = {}) + { + return QString("%1%2").arg(number, fieldWidth - suffix.count()).arg(suffix); + } + + static int toMs(quint64 nsesc) // nanoseconds to miliseconds + { + static const int halfMillion = 500000; + static const int million = 2 * halfMillion; + return int((nsesc + halfMillion) / million); + } + + const char * const m_functionName; + const bool m_measureProcess; + std::atomic_int m_hitThisAll = 0; + std::atomic_int m_hitThisMain = 0; + std::atomic_int64_t m_totalThisAll = 0; + std::atomic_int64_t m_totalThisMain = 0; + static std::atomic_int m_hitAllAll; + static std::atomic_int m_hitAllMain; + static std::atomic_int64_t m_totalAllAll; + static std::atomic_int64_t m_totalAllMain; + static std::atomic_int s_lineCounter; +}; + +std::atomic_int MeasureAndRun::m_hitAllAll = 0; +std::atomic_int MeasureAndRun::m_hitAllMain = 0; +std::atomic_int64_t MeasureAndRun::m_totalAllAll = 0; +std::atomic_int64_t MeasureAndRun::m_totalAllMain = 0; +std::atomic_int MeasureAndRun::s_lineCounter = 0; + +static MeasureAndRun s_start = MeasureAndRun("start"); +static MeasureAndRun s_waitForStarted = MeasureAndRun("waitForStarted"); + +enum { debug = 0 }; +enum { syncDebug = 0 }; + +enum { defaultMaxHangTimerCount = 10 }; + +static Q_LOGGING_CATEGORY(processLog, "qtc.utils.qtcprocess", QtWarningMsg) +static Q_LOGGING_CATEGORY(processStdoutLog, "qtc.utils.qtcprocess.stdout", QtWarningMsg) +static Q_LOGGING_CATEGORY(processStderrLog, "qtc.utils.qtcprocess.stderr", QtWarningMsg) + +static DeviceProcessHooks s_deviceHooks; + +// Data for one channel buffer (stderr/stdout) +class ChannelBuffer +{ +public: + void clearForRun(); + + void handleRest(); + void append(const QByteArray &text); + + QByteArray readAllData() { return std::exchange(rawData, {}); } + + QByteArray rawData; + QString incompleteLineBuffer; // lines not yet signaled + QTextCodec *codec = nullptr; // Not owner + std::unique_ptr codecState; + std::function outputCallback; + TextChannelMode m_textChannelMode = TextChannelMode::Off; + + bool emitSingleLines = true; + bool keepRawData = true; +}; + +class DefaultImpl : public ProcessInterface +{ +private: + virtual void start() final; + virtual void doDefaultStart(const QString &program, const QStringList &arguments) = 0; + bool dissolveCommand(QString *program, QStringList *arguments); + bool ensureProgramExists(const QString &program); +}; + +void DefaultImpl::start() +{ + QString program; + QStringList arguments; + if (!dissolveCommand(&program, &arguments)) + return; + if (!ensureProgramExists(program)) + return; + s_start.measureAndRun(&DefaultImpl::doDefaultStart, this, program, arguments); +} + +bool DefaultImpl::dissolveCommand(QString *program, QStringList *arguments) +{ + const CommandLine &commandLine = m_setup.m_commandLine; + QString commandString; + ProcessArgs processArgs; + const bool success = ProcessArgs::prepareCommand(commandLine, &commandString, &processArgs, + &m_setup.m_environment, + &m_setup.m_workingDirectory); + + if (commandLine.executable().osType() == OsTypeWindows) { + QString args; + if (m_setup.m_useCtrlCStub) { + if (m_setup.m_lowPriority) + ProcessArgs::addArg(&args, "-nice"); + ProcessArgs::addArg(&args, QDir::toNativeSeparators(commandString)); + commandString = QCoreApplication::applicationDirPath() + + QLatin1String("/qtcreator_ctrlc_stub.exe"); + } else if (m_setup.m_lowPriority) { + m_setup.m_belowNormalPriority = true; + } + ProcessArgs::addArgs(&args, processArgs.toWindowsArgs()); + m_setup.m_nativeArguments = args; + // Note: Arguments set with setNativeArgs will be appended to the ones + // passed with start() below. + *arguments = {}; + } else { + if (!success) { + const ProcessResultData result = {0, + QProcess::NormalExit, + QProcess::FailedToStart, + Tr::tr("Error in command line.")}; + emit done(result); + return false; + } + *arguments = processArgs.toUnixArgs(); + } + *program = commandString; + return true; +} + +static FilePath resolve(const FilePath &workingDir, const FilePath &filePath) +{ + if (filePath.isAbsolutePath()) + return filePath; + + const FilePath fromWorkingDir = workingDir.resolvePath(filePath); + if (fromWorkingDir.exists() && fromWorkingDir.isExecutableFile()) + return fromWorkingDir; + return filePath.searchInPath(); +} + +bool DefaultImpl::ensureProgramExists(const QString &program) +{ + const FilePath programFilePath = resolve(m_setup.m_workingDirectory, + FilePath::fromString(program)); + if (programFilePath.exists() && programFilePath.isExecutableFile()) + return true; + + const QString errorString + = Tr::tr("The program \"%1\" does not exist or is not executable.").arg(program); + const ProcessResultData result = { 0, QProcess::NormalExit, QProcess::FailedToStart, + errorString }; + emit done(result); + return false; +} + +class QProcessBlockingImpl : public ProcessBlockingInterface +{ +public: + QProcessBlockingImpl(QProcess *process) : m_process(process) {} + +private: + bool waitForSignal(ProcessSignalType signalType, int msecs) final + { + switch (signalType) { + case ProcessSignalType::Started: + return m_process->waitForStarted(msecs); + case ProcessSignalType::ReadyRead: + return m_process->waitForReadyRead(msecs); + case ProcessSignalType::Done: + return m_process->waitForFinished(msecs); + } + return false; + } + + QProcess *m_process = nullptr; +}; + +class PtyProcessImpl final : public DefaultImpl +{ +public: + ~PtyProcessImpl() { QTC_CHECK(m_setup.m_ptyData); m_setup.m_ptyData->setResizeHandler({}); } + + qint64 write(const QByteArray &data) final + { + if (m_ptyProcess) + return m_ptyProcess->write(data); + return -1; + } + + void sendControlSignal(ControlSignal controlSignal) final + { + if (!m_ptyProcess) + return; + + switch (controlSignal) { + case ControlSignal::Terminate: + m_ptyProcess.reset(); + break; + case ControlSignal::Kill: + m_ptyProcess->kill(); + break; + default: + QTC_CHECK(false); + } + } + + void doDefaultStart(const QString &program, const QStringList &arguments) final + { + QTC_CHECK(m_setup.m_ptyData); + m_setup.m_ptyData->setResizeHandler([this](const QSize &size) { + if (m_ptyProcess) + m_ptyProcess->resize(size.width(), size.height()); + }); + m_ptyProcess.reset(PtyQt::createPtyProcess(IPtyProcess::AutoPty)); + if (!m_ptyProcess) { + const ProcessResultData result = {-1, + QProcess::CrashExit, + QProcess::FailedToStart, + "Failed to create pty process"}; + emit done(result); + return; + } + + QProcessEnvironment penv = m_setup.m_environment.toProcessEnvironment(); + if (penv.isEmpty()) + penv = Environment::systemEnvironment().toProcessEnvironment(); + const QStringList senv = penv.toStringList(); + + bool startResult + = m_ptyProcess->startProcess(program, + HostOsInfo::isWindowsHost() + ? QStringList{m_setup.m_nativeArguments} << arguments + : arguments, + m_setup.m_workingDirectory.nativePath(), + senv, + m_setup.m_ptyData->size().width(), + m_setup.m_ptyData->size().height()); + + if (!startResult) { + const ProcessResultData result = {-1, + QProcess::CrashExit, + QProcess::FailedToStart, + "Failed to start pty process: " + + m_ptyProcess->lastError()}; + emit done(result); + return; + } + + if (!m_ptyProcess->lastError().isEmpty()) { + const ProcessResultData result + = {-1, QProcess::CrashExit, QProcess::FailedToStart, m_ptyProcess->lastError()}; + emit done(result); + return; + } + + connect(m_ptyProcess->notifier(), &QIODevice::readyRead, this, [this] { + emit readyRead(m_ptyProcess->readAll(), {}); + }); + + connect(m_ptyProcess->notifier(), &QIODevice::aboutToClose, this, [this] { + if (m_ptyProcess) { + const ProcessResultData result + = {m_ptyProcess->exitCode(), QProcess::NormalExit, QProcess::UnknownError, {}}; + emit done(result); + return; + } + + const ProcessResultData result = {0, QProcess::NormalExit, QProcess::UnknownError, {}}; + emit done(result); + }); + + emit started(m_ptyProcess->pid()); + } + +private: + std::unique_ptr m_ptyProcess; +}; + +class QProcessImpl final : public DefaultImpl +{ +public: + QProcessImpl() + : m_process(new ProcessHelper(this)) + , m_blockingImpl(new QProcessBlockingImpl(m_process)) + { + connect(m_process, &QProcess::started, this, &QProcessImpl::handleStarted); + connect(m_process, &QProcess::finished, this, &QProcessImpl::handleFinished); + connect(m_process, &QProcess::errorOccurred, this, &QProcessImpl::handleError); + connect(m_process, &QProcess::readyReadStandardOutput, this, [this] { + emit readyRead(m_process->readAllStandardOutput(), {}); + }); + connect(m_process, &QProcess::readyReadStandardError, this, [this] { + emit readyRead({}, m_process->readAllStandardError()); + }); + } + ~QProcessImpl() final { ProcessReaper::reap(m_process, m_setup.m_reaperTimeout); } + +private: + qint64 write(const QByteArray &data) final { return m_process->write(data); } + void sendControlSignal(ControlSignal controlSignal) final { + switch (controlSignal) { + case ControlSignal::Terminate: + ProcessHelper::terminateProcess(m_process); + break; + case ControlSignal::Kill: + m_process->kill(); + break; + case ControlSignal::Interrupt: + ProcessHelper::interruptProcess(m_process); + break; + case ControlSignal::KickOff: + QTC_CHECK(false); + break; + case ControlSignal::CloseWriteChannel: + m_process->closeWriteChannel(); + break; + } + } + + virtual ProcessBlockingInterface *processBlockingInterface() const { return m_blockingImpl; } + + void doDefaultStart(const QString &program, const QStringList &arguments) final + { + QTC_ASSERT(QThread::currentThread()->eventDispatcher(), + qWarning("QtcProcess::start(): Starting a process in a non QThread thread " + "may cause infinite hang when destroying the running process.")); + ProcessStartHandler *handler = m_process->processStartHandler(); + handler->setProcessMode(m_setup.m_processMode); + handler->setWriteData(m_setup.m_writeData); + handler->setNativeArguments(m_setup.m_nativeArguments); + handler->setWindowsSpecificStartupFlags(m_setup.m_belowNormalPriority, + m_setup.m_createConsoleOnWindows); + + const QProcessEnvironment penv = m_setup.m_environment.toProcessEnvironment(); + if (!penv.isEmpty()) + m_process->setProcessEnvironment(penv); + m_process->setWorkingDirectory(m_setup.m_workingDirectory.path()); + m_process->setStandardInputFile(m_setup.m_standardInputFile); + m_process->setProcessChannelMode(m_setup.m_processChannelMode); + if (m_setup.m_lowPriority) + m_process->setLowPriority(); + if (m_setup.m_unixTerminalDisabled) + m_process->setUnixTerminalDisabled(); + m_process->setUseCtrlCStub(m_setup.m_useCtrlCStub); + m_process->start(program, arguments, handler->openMode()); + handler->handleProcessStart(); + } + + void handleStarted() + { + m_process->processStartHandler()->handleProcessStarted(); + emit started(m_process->processId()); + } + + void handleError(QProcess::ProcessError error) + { + if (error != QProcess::FailedToStart) + return; + const ProcessResultData result = { m_process->exitCode(), m_process->exitStatus(), + error, m_process->errorString() }; + emit done(result); + } + + void handleFinished(int exitCode, QProcess::ExitStatus exitStatus) + { + const ProcessResultData result = { exitCode, exitStatus, + m_process->error(), m_process->errorString() }; + emit done(result); + } + + ProcessHelper *m_process = nullptr; + QProcessBlockingImpl *m_blockingImpl = nullptr; +}; + +static uint uniqueToken() +{ + static std::atomic_uint globalUniqueToken = 0; + return ++globalUniqueToken; +} + +class ProcessLauncherBlockingImpl : public ProcessBlockingInterface +{ +public: + ProcessLauncherBlockingImpl(CallerHandle *caller) : m_caller(caller) {} + +private: + bool waitForSignal(ProcessSignalType signalType, int msecs) final + { + // TODO: Remove CallerHandle::SignalType + const CallerHandle::SignalType type = [signalType] { + switch (signalType) { + case ProcessSignalType::Started: + return CallerHandle::SignalType::Started; + case ProcessSignalType::ReadyRead: + return CallerHandle::SignalType::ReadyRead; + case ProcessSignalType::Done: + return CallerHandle::SignalType::Done; + } + QTC_CHECK(false); + return CallerHandle::SignalType::NoSignal; + }(); + return m_caller->waitForSignal(type, msecs); + } + + CallerHandle *m_caller = nullptr; +}; + +class ProcessLauncherImpl final : public DefaultImpl +{ + Q_OBJECT +public: + ProcessLauncherImpl() : m_token(uniqueToken()) + { + m_handle = LauncherInterface::registerHandle(this, token()); + m_handle->setProcessSetupData(&m_setup); + connect(m_handle, &CallerHandle::started, + this, &ProcessInterface::started); + connect(m_handle, &CallerHandle::readyRead, + this, &ProcessInterface::readyRead); + connect(m_handle, &CallerHandle::done, + this, &ProcessInterface::done); + m_blockingImpl = new ProcessLauncherBlockingImpl(m_handle); + } + ~ProcessLauncherImpl() final + { + m_handle->close(); + LauncherInterface::unregisterHandle(token()); + m_handle = nullptr; + } + +private: + qint64 write(const QByteArray &data) final { return m_handle->write(data); } + void sendControlSignal(ControlSignal controlSignal) final { + switch (controlSignal) { + case ControlSignal::Terminate: + m_handle->terminate(); + break; + case ControlSignal::Kill: + m_handle->kill(); + break; + case ControlSignal::Interrupt: + ProcessHelper::interruptPid(m_handle->processId()); + break; + case ControlSignal::KickOff: + QTC_CHECK(false); + break; + case ControlSignal::CloseWriteChannel: + m_handle->closeWriteChannel(); + break; + } + } + + virtual ProcessBlockingInterface *processBlockingInterface() const { return m_blockingImpl; } + + void doDefaultStart(const QString &program, const QStringList &arguments) final + { + m_handle->start(program, arguments); + } + + quintptr token() const { return m_token; } + + const uint m_token = 0; + // Lives in caller's thread. + CallerHandle *m_handle = nullptr; + ProcessLauncherBlockingImpl *m_blockingImpl = nullptr; +}; + +static ProcessImpl defaultProcessImpl() +{ + if (qtcEnvironmentVariableIsSet("QTC_USE_QPROCESS")) + return ProcessImpl::QProcess; + return ProcessImpl::ProcessLauncher; +} + +class ProcessInterfaceSignal +{ +public: + ProcessSignalType signalType() const { return m_signalType; } + virtual ~ProcessInterfaceSignal() = default; +protected: + ProcessInterfaceSignal(ProcessSignalType signalType) : m_signalType(signalType) {} +private: + const ProcessSignalType m_signalType; +}; + +class StartedSignal : public ProcessInterfaceSignal +{ +public: + StartedSignal(qint64 processId, qint64 applicationMainThreadId) + : ProcessInterfaceSignal(ProcessSignalType::Started) + , m_processId(processId) + , m_applicationMainThreadId(applicationMainThreadId) {} + qint64 processId() const { return m_processId; } + qint64 applicationMainThreadId() const { return m_applicationMainThreadId; } +private: + const qint64 m_processId; + const qint64 m_applicationMainThreadId; +}; + +class ReadyReadSignal : public ProcessInterfaceSignal +{ +public: + ReadyReadSignal(const QByteArray &stdOut, const QByteArray &stdErr) + : ProcessInterfaceSignal(ProcessSignalType::ReadyRead) + , m_stdOut(stdOut) + , m_stdErr(stdErr) {} + QByteArray stdOut() const { return m_stdOut; } + QByteArray stdErr() const { return m_stdErr; } +private: + const QByteArray m_stdOut; + const QByteArray m_stdErr; +}; + +class DoneSignal : public ProcessInterfaceSignal +{ +public: + DoneSignal(const ProcessResultData &resultData) + : ProcessInterfaceSignal(ProcessSignalType::Done) + , m_resultData(resultData) {} + ProcessResultData resultData() const { return m_resultData; } +private: + const ProcessResultData m_resultData; +}; + +class GeneralProcessBlockingImpl; + +class ProcessInterfaceHandler : public QObject +{ +public: + ProcessInterfaceHandler(GeneralProcessBlockingImpl *caller, ProcessInterface *process); + + // Called from caller's thread exclusively. + bool waitForSignal(ProcessSignalType newSignal, int msecs); + void moveToCallerThread(); + +private: + // Called from caller's thread exclusively. + bool doWaitForSignal(QDeadlineTimer deadline); + + // Called from caller's thread when not waiting for signal, + // otherwise called from temporary thread. + void handleStarted(qint64 processId, qint64 applicationMainThreadId); + void handleReadyRead(const QByteArray &outputData, const QByteArray &errorData); + void handleDone(const ProcessResultData &data); + void appendSignal(ProcessInterfaceSignal *newSignal); + + GeneralProcessBlockingImpl *m_caller = nullptr; + QMutex m_mutex; + QWaitCondition m_waitCondition; +}; + +class GeneralProcessBlockingImpl : public ProcessBlockingInterface +{ +public: + GeneralProcessBlockingImpl(QtcProcessPrivate *parent); + + void flush() { flushSignals(takeAllSignals()); } + bool flushFor(ProcessSignalType signalType) { + return flushSignals(takeSignalsFor(signalType), &signalType); + } + + bool shouldFlush() const { QMutexLocker locker(&m_mutex); return !m_signals.isEmpty(); } + // Called from ProcessInterfaceHandler thread exclusively. + void appendSignal(ProcessInterfaceSignal *launcherSignal); + +private: + // Called from caller's thread exclusively + bool waitForSignal(ProcessSignalType newSignal, int msecs) final; + + QList takeAllSignals(); + QList takeSignalsFor(ProcessSignalType signalType); + bool flushSignals(const QList &signalList, + ProcessSignalType *signalType = nullptr); + + void handleStartedSignal(const StartedSignal *launcherSignal); + void handleReadyReadSignal(const ReadyReadSignal *launcherSignal); + void handleDoneSignal(const DoneSignal *launcherSignal); + + QtcProcessPrivate *m_caller = nullptr; + std::unique_ptr m_processHandler; + mutable QMutex m_mutex; + QList m_signals; +}; + +class QtcProcessPrivate : public QObject +{ +public: + explicit QtcProcessPrivate(Process *parent) + : QObject(parent) + , q(parent) + , m_killTimer(this) + { + m_killTimer.setSingleShot(true); + connect(&m_killTimer, &QTimer::timeout, this, [this] { + m_killTimer.stop(); + sendControlSignal(ControlSignal::Kill); + }); + setupDebugLog(); + } + + void setupDebugLog(); + void storeEventLoopDebugInfo(const QVariant &value); + + ProcessInterface *createProcessInterface() + { + if (m_setup.m_ptyData) + return new PtyProcessImpl; + if (m_setup.m_terminalMode != TerminalMode::Off) + return Terminal::Hooks::instance().createTerminalProcessInterface(); + + const ProcessImpl impl = m_setup.m_processImpl == ProcessImpl::Default + ? defaultProcessImpl() : m_setup.m_processImpl; + if (impl == ProcessImpl::QProcess) + return new QProcessImpl; + return new ProcessLauncherImpl; + } + + void setProcessInterface(ProcessInterface *process) + { + if (m_process) + m_process->disconnect(); + m_process.reset(process); + m_process->setParent(this); + connect(m_process.get(), &ProcessInterface::started, + this, &QtcProcessPrivate::handleStarted); + connect(m_process.get(), &ProcessInterface::readyRead, + this, &QtcProcessPrivate::handleReadyRead); + connect(m_process.get(), &ProcessInterface::done, + this, &QtcProcessPrivate::handleDone); + + m_blockingInterface.reset(process->processBlockingInterface()); + if (!m_blockingInterface) + m_blockingInterface.reset(new GeneralProcessBlockingImpl(this)); + m_blockingInterface->setParent(this); + } + + CommandLine fullCommandLine() const + { + if (!m_setup.m_runAsRoot || HostOsInfo::isWindowsHost()) + return m_setup.m_commandLine; + CommandLine rootCommand("sudo", {"-A"}); + rootCommand.addCommandLineAsArgs(m_setup.m_commandLine); + return rootCommand; + } + + Process *q; + std::unique_ptr m_blockingInterface; + std::unique_ptr m_process; + ProcessSetupData m_setup; + + void slotTimeout(); + void handleStarted(qint64 processId, qint64 applicationMainThreadId); + void handleReadyRead(const QByteArray &outputData, const QByteArray &errorData); + void handleDone(const ProcessResultData &data); + void clearForRun(); + + void emitGuardedSignal(void (Process::* signalName)()) { + GuardLocker locker(m_guard); + emit (q->*signalName)(); + } + + ProcessResult interpretExitCode(int exitCode); + + bool waitForSignal(ProcessSignalType signalType, int msecs); + Qt::ConnectionType connectionType() const; + void sendControlSignal(ControlSignal controlSignal); + + QTimer m_killTimer; + QProcess::ProcessState m_state = QProcess::NotRunning; + qint64 m_processId = 0; + qint64 m_applicationMainThreadId = 0; + ProcessResultData m_resultData; + + QTextCodec *m_codec = QTextCodec::codecForLocale(); + QEventLoop *m_eventLoop = nullptr; + ProcessResult m_result = ProcessResult::StartFailed; + ChannelBuffer m_stdOut; + ChannelBuffer m_stdErr; + ExitCodeInterpreter m_exitCodeInterpreter; + + int m_hangTimerCount = 0; + int m_maxHangTimerCount = defaultMaxHangTimerCount; + bool m_timeOutMessageBoxEnabled = false; + bool m_waitingForUser = false; + + Guard m_guard; +}; + +ProcessInterfaceHandler::ProcessInterfaceHandler(GeneralProcessBlockingImpl *caller, + ProcessInterface *process) + : m_caller(caller) +{ + process->disconnect(); + connect(process, &ProcessInterface::started, + this, &ProcessInterfaceHandler::handleStarted); + connect(process, &ProcessInterface::readyRead, + this, &ProcessInterfaceHandler::handleReadyRead); + connect(process, &ProcessInterface::done, + this, &ProcessInterfaceHandler::handleDone); +} + +// Called from caller's thread exclusively. +bool ProcessInterfaceHandler::waitForSignal(ProcessSignalType newSignal, int msecs) +{ + QDeadlineTimer deadline(msecs); + while (true) { + if (deadline.hasExpired()) + break; + if (!doWaitForSignal(deadline)) + break; + // Matching (or Done) signal was flushed + if (m_caller->flushFor(newSignal)) + return true; + // Otherwise continue awaiting (e.g. when ReadyRead came while waitForFinished()) + } + return false; +} + +// Called from caller's thread exclusively. +void ProcessInterfaceHandler::moveToCallerThread() +{ + QMetaObject::invokeMethod(this, [this] { + moveToThread(m_caller->thread()); + }, Qt::BlockingQueuedConnection); +} + +// Called from caller's thread exclusively. +bool ProcessInterfaceHandler::doWaitForSignal(QDeadlineTimer deadline) +{ + QMutexLocker locker(&m_mutex); + + // Flush, if we have any stored signals. + // This must be called when holding laucher's mutex locked prior to the call to wait, + // so that it's done atomically. + if (m_caller->shouldFlush()) + return true; + + return m_waitCondition.wait(&m_mutex, deadline); +} + +// Called from ProcessInterfaceHandler thread exclusively +void ProcessInterfaceHandler::handleStarted(qint64 processId, qint64 applicationMainThreadId) +{ + appendSignal(new StartedSignal(processId, applicationMainThreadId)); +} + +// Called from ProcessInterfaceHandler thread exclusively +void ProcessInterfaceHandler::handleReadyRead(const QByteArray &outputData, const QByteArray &errorData) +{ + appendSignal(new ReadyReadSignal(outputData, errorData)); +} + +// Called from ProcessInterfaceHandler thread exclusively +void ProcessInterfaceHandler::handleDone(const ProcessResultData &data) +{ + appendSignal(new DoneSignal(data)); +} + +void ProcessInterfaceHandler::appendSignal(ProcessInterfaceSignal *newSignal) +{ + { + QMutexLocker locker(&m_mutex); + m_caller->appendSignal(newSignal); + } + m_waitCondition.wakeOne(); + // call in callers thread + QMetaObject::invokeMethod(m_caller, &GeneralProcessBlockingImpl::flush); +} + +GeneralProcessBlockingImpl::GeneralProcessBlockingImpl(QtcProcessPrivate *parent) + : m_caller(parent) + , m_processHandler(new ProcessInterfaceHandler(this, parent->m_process.get())) +{ + // In order to move the process interface into another thread together with handle + parent->m_process.get()->setParent(m_processHandler.get()); + m_processHandler->setParent(this); + // So the hierarchy looks like: + // QtcProcessPrivate + // | + // +- GeneralProcessBlockingImpl + // | + // +- ProcessInterfaceHandler + // | + // +- ProcessInterface +} + +bool GeneralProcessBlockingImpl::waitForSignal(ProcessSignalType newSignal, int msecs) +{ + m_processHandler->setParent(nullptr); + + QThread thread; + thread.start(); + // Note: the thread may have started before and it's appending new signals before + // waitForSignal() is called. However, in this case they won't be flushed since + // the caller here is blocked, so all signals should be buffered and we are going + // to flush them from inside waitForSignal(). + m_processHandler->moveToThread(&thread); + const bool result = m_processHandler->waitForSignal(newSignal, msecs); + m_processHandler->moveToCallerThread(); + m_processHandler->setParent(this); + thread.quit(); + thread.wait(); + return result; +} + +// Called from caller's thread exclusively +QList GeneralProcessBlockingImpl::takeAllSignals() +{ + QMutexLocker locker(&m_mutex); + return std::exchange(m_signals, {}); +} + +// Called from caller's thread exclusively +QList GeneralProcessBlockingImpl::takeSignalsFor(ProcessSignalType signalType) +{ + // If we are flushing for ReadyRead or Done - flush all. + if (signalType != ProcessSignalType::Started) + return takeAllSignals(); + + QMutexLocker locker(&m_mutex); + const QList storedSignals = transform(std::as_const(m_signals), + [](const ProcessInterfaceSignal *aSignal) { + return aSignal->signalType(); + }); + + // If we are flushing for Started: + // - if Started was buffered - flush Started only (even when Done was buffered) + // - otherwise if Done signal was buffered - flush all. + if (!storedSignals.contains(ProcessSignalType::Started) + && storedSignals.contains(ProcessSignalType::Done)) { + return std::exchange(m_signals, {}); // avoid takeAllSignals() because of mutex locked + } + + QList oldSignals; + const auto matchingIndex = storedSignals.lastIndexOf(signalType); + if (matchingIndex >= 0) { + oldSignals = m_signals.mid(0, matchingIndex + 1); + m_signals = m_signals.mid(matchingIndex + 1); + } + return oldSignals; +} + +// Called from caller's thread exclusively +bool GeneralProcessBlockingImpl::flushSignals(const QList &signalList, + ProcessSignalType *signalType) +{ + bool signalMatched = false; + for (const ProcessInterfaceSignal *storedSignal : std::as_const(signalList)) { + const ProcessSignalType storedSignalType = storedSignal->signalType(); + if (signalType && storedSignalType == *signalType) + signalMatched = true; + switch (storedSignalType) { + case ProcessSignalType::Started: + handleStartedSignal(static_cast(storedSignal)); + break; + case ProcessSignalType::ReadyRead: + handleReadyReadSignal(static_cast(storedSignal)); + break; + case ProcessSignalType::Done: + if (signalType) + signalMatched = true; + handleDoneSignal(static_cast(storedSignal)); + break; + } + delete storedSignal; + } + return signalMatched; +} + +void GeneralProcessBlockingImpl::handleStartedSignal(const StartedSignal *aSignal) +{ + m_caller->handleStarted(aSignal->processId(), aSignal->applicationMainThreadId()); +} + +void GeneralProcessBlockingImpl::handleReadyReadSignal(const ReadyReadSignal *aSignal) +{ + m_caller->handleReadyRead(aSignal->stdOut(), aSignal->stdErr()); +} + +void GeneralProcessBlockingImpl::handleDoneSignal(const DoneSignal *aSignal) +{ + m_caller->handleDone(aSignal->resultData()); +} + +// Called from ProcessInterfaceHandler thread exclusively. +void GeneralProcessBlockingImpl::appendSignal(ProcessInterfaceSignal *newSignal) +{ + QMutexLocker locker(&m_mutex); + m_signals.append(newSignal); +} + +bool QtcProcessPrivate::waitForSignal(ProcessSignalType newSignal, int msecs) +{ + const QDeadlineTimer timeout(msecs); + const QDeadlineTimer currentKillTimeout(m_killTimer.remainingTime()); + const bool needsSplit = m_killTimer.isActive() ? timeout > currentKillTimeout : false; + const QDeadlineTimer mainTimeout = needsSplit ? currentKillTimeout : timeout; + + bool result = m_blockingInterface->waitForSignal(newSignal, mainTimeout.remainingTime()); + if (!result && needsSplit) { + m_killTimer.stop(); + sendControlSignal(ControlSignal::Kill); + result = m_blockingInterface->waitForSignal(newSignal, timeout.remainingTime()); + } + return result; +} + +Qt::ConnectionType QtcProcessPrivate::connectionType() const +{ + return (m_process->thread() == thread()) ? Qt::DirectConnection + : Qt::BlockingQueuedConnection; +} + +void QtcProcessPrivate::sendControlSignal(ControlSignal controlSignal) +{ + QTC_ASSERT(QThread::currentThread() == thread(), return); + if (!m_process || (m_state == QProcess::NotRunning)) + return; + + if (controlSignal == ControlSignal::Terminate || controlSignal == ControlSignal::Kill) + m_resultData.m_canceledByUser = true; + + QMetaObject::invokeMethod(m_process.get(), [this, controlSignal] { + m_process->sendControlSignal(controlSignal); + }, connectionType()); +} + +void QtcProcessPrivate::clearForRun() +{ + m_hangTimerCount = 0; + m_stdOut.clearForRun(); + m_stdOut.codec = m_codec; + m_stdErr.clearForRun(); + m_stdErr.codec = m_codec; + m_result = ProcessResult::StartFailed; + + m_killTimer.stop(); + m_state = QProcess::NotRunning; + m_processId = 0; + m_applicationMainThreadId = 0; + m_resultData = {}; +} + +ProcessResult QtcProcessPrivate::interpretExitCode(int exitCode) +{ + if (m_exitCodeInterpreter) + return m_exitCodeInterpreter(exitCode); + + // default: + return exitCode ? ProcessResult::FinishedWithError : ProcessResult::FinishedWithSuccess; +} + +} // Internal + +/*! + \class Utils::QtcProcess + + \brief The QtcProcess class provides functionality for with processes. + + \sa Utils::ProcessArgs +*/ + +Process::Process(QObject *parent) + : QObject(parent), + d(new QtcProcessPrivate(this)) +{ + qRegisterMetaType("ProcessResultData"); + static int qProcessExitStatusMeta = qRegisterMetaType(); + static int qProcessProcessErrorMeta = qRegisterMetaType(); + Q_UNUSED(qProcessExitStatusMeta) + Q_UNUSED(qProcessProcessErrorMeta) +} + +Process::~Process() +{ + QTC_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting QtcProcess instance directly from " + "one of its signal handlers will lead to crash!")); + if (d->m_process) + d->m_process->disconnect(); + delete d; +} + +void Process::setProcessImpl(ProcessImpl processImpl) +{ + d->m_setup.m_processImpl = processImpl; +} + +void Process::setPtyData(const std::optional &data) +{ + d->m_setup.m_ptyData = data; +} + +std::optional Process::ptyData() const +{ + return d->m_setup.m_ptyData; +} + +ProcessMode Process::processMode() const +{ + return d->m_setup.m_processMode; +} + +void Process::setTerminalMode(TerminalMode mode) +{ + d->m_setup.m_terminalMode = mode; +} + +TerminalMode Process::terminalMode() const +{ + return d->m_setup.m_terminalMode; +} + +void Process::setProcessMode(ProcessMode processMode) +{ + d->m_setup.m_processMode = processMode; +} + +void Process::setEnvironment(const Environment &env) +{ + d->m_setup.m_environment = env; +} + +const Environment &Process::environment() const +{ + return d->m_setup.m_environment; +} + +void Process::setControlEnvironment(const Environment &environment) +{ + d->m_setup.m_controlEnvironment = environment; +} + +const Environment &Process::controlEnvironment() const +{ + return d->m_setup.m_controlEnvironment; +} + +void Process::setCommand(const CommandLine &cmdLine) +{ + if (d->m_setup.m_workingDirectory.needsDevice() && cmdLine.executable().needsDevice()) { + QTC_CHECK(d->m_setup.m_workingDirectory.host() == cmdLine.executable().host()); + } + d->m_setup.m_commandLine = cmdLine; +} + +const CommandLine &Process::commandLine() const +{ + return d->m_setup.m_commandLine; +} + +FilePath Process::workingDirectory() const +{ + return d->m_setup.m_workingDirectory; +} + +void Process::setWorkingDirectory(const FilePath &dir) +{ + if (dir.needsDevice() && d->m_setup.m_commandLine.executable().needsDevice()) { + QTC_CHECK(dir.host() == d->m_setup.m_commandLine.executable().host()); + } + d->m_setup.m_workingDirectory = dir; +} + +void Process::setUseCtrlCStub(bool enabled) +{ + d->m_setup.m_useCtrlCStub = enabled; +} + +void Process::start() +{ + QTC_ASSERT(state() == QProcess::NotRunning, return); + QTC_ASSERT(!(d->m_process && d->m_guard.isLocked()), + qWarning("Restarting the QtcProcess directly from one of its signal handlers will " + "lead to crash! Consider calling close() prior to direct restart.")); + d->clearForRun(); + ProcessInterface *processImpl = nullptr; + if (d->m_setup.m_commandLine.executable().needsDevice()) { + QTC_ASSERT(s_deviceHooks.processImplHook, return); + processImpl = s_deviceHooks.processImplHook(commandLine().executable()); + } else { + processImpl = d->createProcessInterface(); + } + QTC_ASSERT(processImpl, return); + d->setProcessInterface(processImpl); + d->m_state = QProcess::Starting; + d->m_process->m_setup = d->m_setup; + d->m_process->m_setup.m_commandLine = d->fullCommandLine(); + d->emitGuardedSignal(&Process::starting); + d->m_process->start(); +} + +void Process::terminate() +{ + d->sendControlSignal(ControlSignal::Terminate); +} + +void Process::kill() +{ + d->sendControlSignal(ControlSignal::Kill); +} + +void Process::interrupt() +{ + d->sendControlSignal(ControlSignal::Interrupt); +} + +void Process::kickoffProcess() +{ + d->sendControlSignal(ControlSignal::KickOff); +} + +void Process::closeWriteChannel() +{ + d->sendControlSignal(ControlSignal::CloseWriteChannel); +} + +bool Process::startDetached(const CommandLine &cmd, const FilePath &workingDirectory, qint64 *pid) +{ + return QProcess::startDetached(cmd.executable().toUserOutput(), + cmd.splitArguments(), + workingDirectory.toUserOutput(), + pid); +} + +void Process::setLowPriority() +{ + d->m_setup.m_lowPriority = true; +} + +void Process::setDisableUnixTerminal() +{ + d->m_setup.m_unixTerminalDisabled = true; +} + +void Process::setAbortOnMetaChars(bool abort) +{ + d->m_setup.m_abortOnMetaChars = abort; +} + +void Process::setRunAsRoot(bool on) +{ + d->m_setup.m_runAsRoot = on; +} + +bool Process::isRunAsRoot() const +{ + return d->m_setup.m_runAsRoot; +} + +void Process::setStandardInputFile(const QString &inputFile) +{ + d->m_setup.m_standardInputFile = inputFile; +} + +QString Process::toStandaloneCommandLine() const +{ + QStringList parts; + parts.append("/usr/bin/env"); + if (!d->m_setup.m_workingDirectory.isEmpty()) { + parts.append("-C"); + d->m_setup.m_workingDirectory.path(); + } + parts.append("-i"); + if (d->m_setup.m_environment.hasChanges()) { + const QStringList envVars = d->m_setup.m_environment.toStringList(); + std::transform(envVars.cbegin(), envVars.cend(), + std::back_inserter(parts), ProcessArgs::quoteArgUnix); + } + parts.append(d->m_setup.m_commandLine.executable().path()); + parts.append(d->m_setup.m_commandLine.splitArguments()); + return parts.join(" "); +} + +void Process::setCreateConsoleOnWindows(bool create) +{ + d->m_setup.m_createConsoleOnWindows = create; +} + +bool Process::createConsoleOnWindows() const +{ + return d->m_setup.m_createConsoleOnWindows; +} + +void Process::setExtraData(const QString &key, const QVariant &value) +{ + d->m_setup.m_extraData.insert(key, value); +} + +QVariant Process::extraData(const QString &key) const +{ + return d->m_setup.m_extraData.value(key); +} + +void Process::setExtraData(const QVariantHash &extraData) +{ + d->m_setup.m_extraData = extraData; +} + +QVariantHash Process::extraData() const +{ + return d->m_setup.m_extraData; +} + +void Process::setReaperTimeout(int msecs) +{ + d->m_setup.m_reaperTimeout = msecs; +} + +int Process::reaperTimeout() const +{ + return d->m_setup.m_reaperTimeout; +} + +void Process::setRemoteProcessHooks(const DeviceProcessHooks &hooks) +{ + s_deviceHooks = hooks; +} + +static bool askToKill(const CommandLine &command) +{ +#ifdef QT_GUI_LIB + if (!isMainThread()) + return true; + const QString title = Tr::tr("Process Not Responding"); + QString msg = command.isEmpty() ? Tr::tr("The process is not responding.") + : Tr::tr("The process \"%1\" is not responding.") + .arg(command.executable().toUserOutput()); + msg += ' '; + msg += Tr::tr("Terminate the process?"); + // Restore the cursor that is set to wait while running. + const bool hasOverrideCursor = QApplication::overrideCursor() != nullptr; + if (hasOverrideCursor) + QApplication::restoreOverrideCursor(); + QMessageBox::StandardButton answer = QMessageBox::question(nullptr, title, msg, QMessageBox::Yes|QMessageBox::No); + if (hasOverrideCursor) + QApplication::setOverrideCursor(Qt::WaitCursor); + return answer == QMessageBox::Yes; +#else + Q_UNUSED(command) + return true; +#endif +} + +// Helper for running a process synchronously in the foreground with timeout +// detection (taking effect after no more output +// occurs on stderr/stdout as opposed to waitForFinished()). Returns false if a timeout +// occurs. Checking of the process' exit state/code still has to be done. + +bool Process::readDataFromProcess(QByteArray *stdOut, QByteArray *stdErr, int timeoutS) +{ + enum { syncDebug = 0 }; + if (syncDebug) + qDebug() << ">readDataFromProcess" << timeoutS; + if (state() != QProcess::Running) { + qWarning("readDataFromProcess: Process in non-running state passed in."); + return false; + } + + // Keep the process running until it has no longer has data + bool finished = false; + bool hasData = false; + do { + finished = waitForFinished(timeoutS > 0 ? timeoutS * 1000 : -1) + || state() == QProcess::NotRunning; + // First check 'stdout' + const QByteArray newStdOut = readAllRawStandardOutput(); + if (!newStdOut.isEmpty()) { + hasData = true; + if (stdOut) + stdOut->append(newStdOut); + } + // Check 'stderr' separately. This is a special handling + // for 'git pull' and the like which prints its progress on stderr. + const QByteArray newStdErr = readAllRawStandardError(); + if (!newStdErr.isEmpty()) { + hasData = true; + if (stdErr) + stdErr->append(newStdErr); + } + // Prompt user, pretend we have data if says 'No'. + const bool hang = !hasData && !finished; + hasData = hang && !askToKill(d->m_setup.m_commandLine); + } while (hasData && !finished); + if (syncDebug) + qDebug() << "m_result; +} + +ProcessResultData Process::resultData() const +{ + return d->m_resultData; +} + +int Process::exitCode() const +{ + return resultData().m_exitCode; +} + +QProcess::ExitStatus Process::exitStatus() const +{ + return resultData().m_exitStatus; +} + +QProcess::ProcessError Process::error() const +{ + return resultData().m_error; +} + +QString Process::errorString() const +{ + return resultData().m_errorString; +} + +// Path utilities + +Environment Process::systemEnvironmentForBinary(const FilePath &filePath) +{ + if (filePath.needsDevice()) { + QTC_ASSERT(s_deviceHooks.systemEnvironmentForBinary, return {}); + return s_deviceHooks.systemEnvironmentForBinary(filePath); + } + + return Environment::systemEnvironment(); +} + +qint64 Process::applicationMainThreadId() const +{ + return d->m_applicationMainThreadId; +} + +QProcess::ProcessChannelMode Process::processChannelMode() const +{ + return d->m_setup.m_processChannelMode; +} + +void Process::setProcessChannelMode(QProcess::ProcessChannelMode mode) +{ + d->m_setup.m_processChannelMode = mode; +} + +QProcess::ProcessState Process::state() const +{ + return d->m_state; +} + +bool Process::isRunning() const +{ + return state() == QProcess::Running; +} + +qint64 Process::processId() const +{ + return d->m_processId; +} + +bool Process::waitForStarted(int msecs) +{ + QTC_ASSERT(d->m_process, return false); + if (d->m_state == QProcess::Running) + return true; + if (d->m_state == QProcess::NotRunning) + return false; + return s_waitForStarted.measureAndRun(&QtcProcessPrivate::waitForSignal, d, + ProcessSignalType::Started, msecs); +} + +bool Process::waitForReadyRead(int msecs) +{ + QTC_ASSERT(d->m_process, return false); + if (d->m_state == QProcess::NotRunning) + return false; + return d->waitForSignal(ProcessSignalType::ReadyRead, msecs); +} + +bool Process::waitForFinished(int msecs) +{ + QTC_ASSERT(d->m_process, return false); + if (d->m_state == QProcess::NotRunning) + return false; + return d->waitForSignal(ProcessSignalType::Done, msecs); +} + +QByteArray Process::readAllRawStandardOutput() +{ + return d->m_stdOut.readAllData(); +} + +QByteArray Process::readAllRawStandardError() +{ + return d->m_stdErr.readAllData(); +} + +qint64 Process::write(const QString &input) +{ + // Non-windows is assumed to be UTF-8 + if (commandLine().executable().osType() != OsTypeWindows) + return writeRaw(input.toUtf8()); + + if (HostOsInfo::hostOs() == OsTypeWindows) + return writeRaw(input.toLocal8Bit()); + + // "remote" Windows target on non-Windows host is unlikely, + // but the true encoding is not accessible. Use UTF8 as best guess. + QTC_CHECK(false); + return writeRaw(input.toUtf8()); +} + +qint64 Process::writeRaw(const QByteArray &input) +{ + QTC_ASSERT(processMode() == ProcessMode::Writer, return -1); + QTC_ASSERT(d->m_process, return -1); + QTC_ASSERT(state() == QProcess::Running, return -1); + QTC_ASSERT(QThread::currentThread() == thread(), return -1); + qint64 result = -1; + QMetaObject::invokeMethod(d->m_process.get(), [this, input] { + d->m_process->write(input); + }, d->connectionType(), &result); + return result; +} + +void Process::close() +{ + QTC_ASSERT(QThread::currentThread() == thread(), return); + if (d->m_process) { + // Note: the m_process may be inside ProcessInterfaceHandler's thread. + QTC_ASSERT(d->m_process->thread() == thread(), return); + d->m_process->disconnect(); + d->m_process.release()->deleteLater(); + } + if (d->m_blockingInterface) { + d->m_blockingInterface->disconnect(); + d->m_blockingInterface.release()->deleteLater(); + } + d->clearForRun(); +} + +/* + Calls terminate() directly and after a delay of reaperTimeout() it calls kill() + if the process is still running. +*/ +void Process::stop() +{ + if (state() == QProcess::NotRunning) + return; + + d->sendControlSignal(ControlSignal::Terminate); + d->m_killTimer.start(d->m_process->m_setup.m_reaperTimeout); +} + +QString Process::readAllStandardOutput() +{ + return QString::fromUtf8(readAllRawStandardOutput()); +} + +QString Process::readAllStandardError() +{ + return QString::fromUtf8(readAllRawStandardError()); +} + +/*! + \class Utils::SynchronousProcess + + \brief The SynchronousProcess class runs a synchronous process in its own + event loop that blocks only user input events. Thus, it allows for the GUI to + repaint and append output to log windows. + + The callbacks set with setStdOutCallback(), setStdErrCallback() are called + with complete lines based on the '\\n' marker. + They would typically be used for log windows. + + Alternatively you can used setStdOutLineCallback() and setStdErrLineCallback() + to process the output line by line. + + There is a timeout handling that takes effect after the last data have been + read from stdout/stdin (as opposed to waitForFinished(), which measures time + since it was invoked). It is thus also suitable for slow processes that + continuously output data (like version system operations). + + The property timeOutMessageBoxEnabled influences whether a message box is + shown asking the user if they want to kill the process on timeout (default: false). + + There are also static utility functions for dealing with fully synchronous + processes, like reading the output with correct timeout handling. + + Caution: This class should NOT be used if there is a chance that the process + triggers opening dialog boxes (for example, by file watchers triggering), + as this will cause event loop problems. +*/ + +QString Process::exitMessage() const +{ + const QString fullCmd = commandLine().toUserOutput(); + switch (result()) { + case ProcessResult::FinishedWithSuccess: + return Tr::tr("The command \"%1\" finished successfully.").arg(fullCmd); + case ProcessResult::FinishedWithError: + return Tr::tr("The command \"%1\" terminated with exit code %2.") + .arg(fullCmd).arg(exitCode()); + case ProcessResult::TerminatedAbnormally: + return Tr::tr("The command \"%1\" terminated abnormally.").arg(fullCmd); + case ProcessResult::StartFailed: + return Tr::tr("The command \"%1\" could not be started.").arg(fullCmd); + case ProcessResult::Hang: + return Tr::tr("The command \"%1\" did not respond within the timeout limit (%2 s).") + .arg(fullCmd).arg(d->m_maxHangTimerCount); + } + return {}; +} + +QByteArray Process::allRawOutput() const +{ + QTC_CHECK(d->m_stdOut.keepRawData); + QTC_CHECK(d->m_stdErr.keepRawData); + if (!d->m_stdOut.rawData.isEmpty() && !d->m_stdErr.rawData.isEmpty()) { + QByteArray result = d->m_stdOut.rawData; + if (!result.endsWith('\n')) + result += '\n'; + result += d->m_stdErr.rawData; + return result; + } + return !d->m_stdOut.rawData.isEmpty() ? d->m_stdOut.rawData : d->m_stdErr.rawData; +} + +QString Process::allOutput() const +{ + QTC_CHECK(d->m_stdOut.keepRawData); + QTC_CHECK(d->m_stdErr.keepRawData); + const QString out = cleanedStdOut(); + const QString err = cleanedStdErr(); + + if (!out.isEmpty() && !err.isEmpty()) { + QString result = out; + if (!result.endsWith('\n')) + result += '\n'; + result += err; + return result; + } + return !out.isEmpty() ? out : err; +} + +QByteArray Process::rawStdOut() const +{ + QTC_CHECK(d->m_stdOut.keepRawData); + return d->m_stdOut.rawData; +} + +QString Process::stdOut() const +{ + QTC_CHECK(d->m_stdOut.keepRawData); + return d->m_codec->toUnicode(d->m_stdOut.rawData); +} + +QString Process::stdErr() const +{ + QTC_CHECK(d->m_stdErr.keepRawData); + return d->m_codec->toUnicode(d->m_stdErr.rawData); +} + +QString Process::cleanedStdOut() const +{ + return Utils::normalizeNewlines(stdOut()); +} + +QString Process::cleanedStdErr() const +{ + return Utils::normalizeNewlines(stdErr()); +} + +static QStringList splitLines(const QString &text) +{ + QStringList result = text.split('\n'); + for (QString &line : result) { + if (line.endsWith('\r')) + line.chop(1); + } + return result; +} + +const QStringList Process::stdOutLines() const +{ + return splitLines(cleanedStdOut()); +} + +const QStringList Process::stdErrLines() const +{ + return splitLines(cleanedStdErr()); +} + +QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const Process &r) +{ + QDebug nsp = str.nospace(); + nsp << "QtcProcess: result=" + << int(r.d->m_result) << " ex=" << r.exitCode() << '\n' + << r.d->m_stdOut.rawData.size() << " bytes stdout, stderr=" << r.d->m_stdErr.rawData << '\n'; + return str; +} + +void ChannelBuffer::clearForRun() +{ + rawData.clear(); + codecState.reset(new QTextCodec::ConverterState); + incompleteLineBuffer.clear(); +} + +/* Check for complete lines read from the device and return them, moving the + * buffer position. */ +void ChannelBuffer::append(const QByteArray &text) +{ + if (text.isEmpty()) + return; + + if (keepRawData) + rawData += text; + + // Line-wise operation below: + if (!outputCallback) + return; + + // Convert and append the new input to the buffer of incomplete lines + incompleteLineBuffer.append(codec->toUnicode(text.constData(), text.size(), codecState.get())); + + do { + // Any completed lines in the incompleteLineBuffer? + int pos = -1; + if (emitSingleLines) { + const int posn = incompleteLineBuffer.indexOf('\n'); + const int posr = incompleteLineBuffer.indexOf('\r'); + if (posn != -1) { + if (posr != -1) { + if (posn == posr + 1) + pos = posn; // \r followed by \n -> line end, use the \n. + else + pos = qMin(posr, posn); // free floating \r and \n: Use the first one. + } else { + pos = posn; + } + } else { + pos = posr; // Make sure internal '\r' triggers a line output + } + } else { + pos = qMax(incompleteLineBuffer.lastIndexOf('\n'), + incompleteLineBuffer.lastIndexOf('\r')); + } + + if (pos == -1) + break; + + // Get completed lines and remove them from the incompleteLinesBuffer: + const QString line = Utils::normalizeNewlines(incompleteLineBuffer.left(pos + 1)); + incompleteLineBuffer = incompleteLineBuffer.mid(pos + 1); + + QTC_ASSERT(outputCallback, return); + outputCallback(line); + + if (!emitSingleLines) + break; + } while (true); +} + +void ChannelBuffer::handleRest() +{ + if (outputCallback && !incompleteLineBuffer.isEmpty()) { + outputCallback(incompleteLineBuffer); + incompleteLineBuffer.clear(); + } +} + +void Process::setTimeoutS(int timeoutS) +{ + if (timeoutS > 0) + d->m_maxHangTimerCount = qMax(2, timeoutS); + else + d->m_maxHangTimerCount = INT_MAX / 1000; +} + +int Process::timeoutS() const +{ + return d->m_maxHangTimerCount; +} + +void Process::setCodec(QTextCodec *c) +{ + QTC_ASSERT(c, return); + d->m_codec = c; +} + +void Process::setTimeOutMessageBoxEnabled(bool v) +{ + d->m_timeOutMessageBoxEnabled = v; +} + +void Process::setExitCodeInterpreter(const ExitCodeInterpreter &interpreter) +{ + d->m_exitCodeInterpreter = interpreter; +} + +void Process::setWriteData(const QByteArray &writeData) +{ + d->m_setup.m_writeData = writeData; +} + +void Process::runBlocking(EventLoopMode eventLoopMode) +{ + // Attach a dynamic property with info about blocking type + d->storeEventLoopDebugInfo(int(eventLoopMode)); + + QDateTime startTime; + static const int blockingThresholdMs = qtcEnvironmentVariableIntValue("QTC_PROCESS_THRESHOLD"); + if (blockingThresholdMs > 0 && isMainThread()) + startTime = QDateTime::currentDateTime(); + Process::start(); + + // Remove the dynamic property so that it's not reused in subseqent start() + d->storeEventLoopDebugInfo({}); + + if (eventLoopMode == EventLoopMode::On) { + // Start failure is triggered immediately if the executable cannot be found in the path. + // In this case the process is left in NotRunning state. + // Do not start the event loop in that case. + if (state() == QProcess::Starting) { + QTimer timer(this); + connect(&timer, &QTimer::timeout, d, &QtcProcessPrivate::slotTimeout); + timer.setInterval(1000); + timer.start(); +#ifdef QT_GUI_LIB + if (isMainThread()) + QApplication::setOverrideCursor(Qt::WaitCursor); +#endif + QEventLoop eventLoop(this); + QTC_ASSERT(!d->m_eventLoop, return); + d->m_eventLoop = &eventLoop; + eventLoop.exec(QEventLoop::ExcludeUserInputEvents); + d->m_eventLoop = nullptr; + timer.stop(); +#ifdef QT_GUI_LIB + if (isMainThread()) + QApplication::restoreOverrideCursor(); +#endif + } + } else { + if (!waitForStarted(d->m_maxHangTimerCount * 1000)) { + d->m_result = ProcessResult::StartFailed; + return; + } + if (!waitForFinished(d->m_maxHangTimerCount * 1000)) { + d->m_result = ProcessResult::Hang; + terminate(); + if (!waitForFinished(1000)) { + kill(); + waitForFinished(1000); + } + } + } + if (blockingThresholdMs > 0) { + const int timeDiff = startTime.msecsTo(QDateTime::currentDateTime()); + if (timeDiff > blockingThresholdMs && isMainThread()) { + qWarning() << "Blocking process " << d->m_setup.m_commandLine << "took" << timeDiff + << "ms, longer than threshold" << blockingThresholdMs; + } + } +} + +void Process::setStdOutCallback(const TextChannelCallback &callback) +{ + d->m_stdOut.outputCallback = callback; + d->m_stdOut.emitSingleLines = false; +} + +void Process::setStdOutLineCallback(const TextChannelCallback &callback) +{ + d->m_stdOut.outputCallback = callback; + d->m_stdOut.emitSingleLines = true; + d->m_stdOut.keepRawData = false; +} + +void Process::setStdErrCallback(const TextChannelCallback &callback) +{ + d->m_stdErr.outputCallback = callback; + d->m_stdErr.emitSingleLines = false; +} + +void Process::setStdErrLineCallback(const TextChannelCallback &callback) +{ + d->m_stdErr.outputCallback = callback; + d->m_stdErr.emitSingleLines = true; + d->m_stdErr.keepRawData = false; +} + +void Process::setTextChannelMode(Channel channel, TextChannelMode mode) +{ + const TextChannelCallback outputCb = [this](const QString &text) { + GuardLocker locker(d->m_guard); + emit textOnStandardOutput(text); + }; + const TextChannelCallback errorCb = [this](const QString &text) { + GuardLocker locker(d->m_guard); + emit textOnStandardError(text); + }; + const TextChannelCallback callback = (channel == Channel::Output) ? outputCb : errorCb; + ChannelBuffer *buffer = channel == Channel::Output ? &d->m_stdOut : &d->m_stdErr; + QTC_ASSERT(buffer->m_textChannelMode == TextChannelMode::Off, qWarning() + << "QtcProcess::setTextChannelMode(): Changing text channel mode for" + << (channel == Channel::Output ? "Output": "Error") + << "channel while it was previously set for this channel."); + buffer->m_textChannelMode = mode; + switch (mode) { + case TextChannelMode::Off: + buffer->outputCallback = {}; + buffer->emitSingleLines = true; + buffer->keepRawData = true; + break; + case TextChannelMode::SingleLine: + buffer->outputCallback = callback; + buffer->emitSingleLines = true; + buffer->keepRawData = false; + break; + case TextChannelMode::MultiLine: + buffer->outputCallback = callback; + buffer->emitSingleLines = false; + buffer->keepRawData = true; + break; + } +} + +TextChannelMode Process::textChannelMode(Channel channel) const +{ + ChannelBuffer *buffer = channel == Channel::Output ? &d->m_stdOut : &d->m_stdErr; + return buffer->m_textChannelMode; +} + +void QtcProcessPrivate::slotTimeout() +{ + if (!m_waitingForUser && (++m_hangTimerCount > m_maxHangTimerCount)) { + if (debug) + qDebug() << Q_FUNC_INFO << "HANG detected, killing"; + m_waitingForUser = true; + const bool terminate = !m_timeOutMessageBoxEnabled || askToKill(m_setup.m_commandLine); + m_waitingForUser = false; + if (terminate) { + q->stop(); + q->waitForFinished(); + m_result = ProcessResult::Hang; + } else { + m_hangTimerCount = 0; + } + } else { + if (debug) + qDebug() << Q_FUNC_INFO << m_hangTimerCount; + } +} + +void QtcProcessPrivate::handleStarted(qint64 processId, qint64 applicationMainThreadId) +{ + QTC_CHECK(m_state == QProcess::Starting); + m_state = QProcess::Running; + + m_processId = processId; + m_applicationMainThreadId = applicationMainThreadId; + emitGuardedSignal(&Process::started); +} + +void QtcProcessPrivate::handleReadyRead(const QByteArray &outputData, const QByteArray &errorData) +{ + QTC_CHECK(m_state == QProcess::Running); + + // TODO: check why we need this timer? + m_hangTimerCount = 0; + // TODO: store a copy of m_processChannelMode on start()? Currently we assert that state + // is NotRunning when setting the process channel mode. + + if (!outputData.isEmpty()) { + if (m_process->m_setup.m_processChannelMode == QProcess::ForwardedOutputChannel + || m_process->m_setup.m_processChannelMode == QProcess::ForwardedChannels) { + std::cout << outputData.constData() << std::flush; + } else { + m_stdOut.append(outputData); + emitGuardedSignal(&Process::readyReadStandardOutput); + } + } + if (!errorData.isEmpty()) { + if (m_process->m_setup.m_processChannelMode == QProcess::ForwardedErrorChannel + || m_process->m_setup.m_processChannelMode == QProcess::ForwardedChannels) { + std::cerr << errorData.constData() << std::flush; + } else { + m_stdErr.append(errorData); + emitGuardedSignal(&Process::readyReadStandardError); + } + } +} + +void QtcProcessPrivate::handleDone(const ProcessResultData &data) +{ + m_killTimer.stop(); + const bool wasCanceled = m_resultData.m_canceledByUser; + m_resultData = data; + m_resultData.m_canceledByUser = wasCanceled; + + switch (m_state) { + case QProcess::NotRunning: + QTC_CHECK(false); // Can't happen + break; + case QProcess::Starting: + QTC_CHECK(m_resultData.m_error == QProcess::FailedToStart); + break; + case QProcess::Running: + QTC_CHECK(m_resultData.m_error != QProcess::FailedToStart); + break; + } + m_state = QProcess::NotRunning; + + // This code (255) is being returned by QProcess when FailedToStart error occurred + if (m_resultData.m_error == QProcess::FailedToStart) + m_resultData.m_exitCode = 0xFF; + + // HACK: See QIODevice::errorString() implementation. + if (m_resultData.m_error == QProcess::UnknownError) + m_resultData.m_errorString.clear(); + else if (m_result != ProcessResult::Hang) + m_result = ProcessResult::StartFailed; + + if (debug) + qDebug() << Q_FUNC_INFO << m_resultData.m_exitCode << m_resultData.m_exitStatus; + m_hangTimerCount = 0; + + if (m_resultData.m_error != QProcess::FailedToStart) { + switch (m_resultData.m_exitStatus) { + case QProcess::NormalExit: + m_result = interpretExitCode(m_resultData.m_exitCode); + break; + case QProcess::CrashExit: + // Was hang detected before and killed? + if (m_result != ProcessResult::Hang) + m_result = ProcessResult::TerminatedAbnormally; + break; + } + } + if (m_eventLoop) + m_eventLoop->quit(); + + m_stdOut.handleRest(); + m_stdErr.handleRest(); + + emitGuardedSignal(&Process::done); + m_processId = 0; + m_applicationMainThreadId = 0; +} + +static QString blockingMessage(const QVariant &variant) +{ + if (!variant.isValid()) + return "non blocking"; + if (variant.toInt() == int(EventLoopMode::On)) + return "blocking with event loop"; + return "blocking without event loop"; +} + +void QtcProcessPrivate::setupDebugLog() +{ + if (!processLog().isDebugEnabled()) + return; + + auto now = [] { + using namespace std::chrono; + return duration_cast(system_clock::now().time_since_epoch()).count(); + }; + + connect(q, &Process::starting, this, [=] { + const quint64 msNow = now(); + setProperty(QTC_PROCESS_STARTTIME, msNow); + + static std::atomic_int startCounter = 0; + const int currentNumber = startCounter.fetch_add(1); + qCDebug(processLog).nospace().noquote() + << "Process " << currentNumber << " starting (" + << qPrintable(blockingMessage(property(QTC_PROCESS_BLOCKING_TYPE))) + << "): " << m_setup.m_commandLine.toUserOutput(); + setProperty(QTC_PROCESS_NUMBER, currentNumber); + }); + + connect(q, &Process::done, this, [=] { + if (!m_process.get()) + return; + const QVariant n = property(QTC_PROCESS_NUMBER); + if (!n.isValid()) + return; + const quint64 msNow = now(); + const quint64 msStarted = property(QTC_PROCESS_STARTTIME).toULongLong(); + const quint64 msElapsed = msNow - msStarted; + + const int number = n.toInt(); + const QString stdOut = q->cleanedStdOut(); + const QString stdErr = q->cleanedStdErr(); + qCDebug(processLog).nospace() + << "Process " << number << " finished: result=" << int(m_result) + << ", ex=" << m_resultData.m_exitCode + << ", " << stdOut.size() << " bytes stdout: " << stdOut.left(20) + << ", " << stdErr.size() << " bytes stderr: " << stdErr.left(1000) + << ", " << msElapsed << " ms elapsed"; + if (processStdoutLog().isDebugEnabled() && !stdOut.isEmpty()) + qCDebug(processStdoutLog).nospace() << "Process " << number << " sdout: " << stdOut; + if (processStderrLog().isDebugEnabled() && !stdErr.isEmpty()) + qCDebug(processStderrLog).nospace() << "Process " << number << " stderr: " << stdErr; + }); +} + +void QtcProcessPrivate::storeEventLoopDebugInfo(const QVariant &value) +{ + if (processLog().isDebugEnabled()) + setProperty(QTC_PROCESS_BLOCKING_TYPE, value); +} + +ProcessTaskAdapter::ProcessTaskAdapter() +{ + connect(task(), &Process::done, this, [this] { + emit done(task()->result() == ProcessResult::FinishedWithSuccess); + }); +} + +void ProcessTaskAdapter::start() +{ + task()->start(); +} + +} // namespace Utils + +#include "process.moc" diff --git a/src/libs/utils/process.h b/src/libs/utils/process.h new file mode 100644 index 0000000000..f89b6f9b22 --- /dev/null +++ b/src/libs/utils/process.h @@ -0,0 +1,219 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "utils_global.h" + +#include "commandline.h" +#include "processenums.h" +#include "tasktree.h" + +#include + +QT_BEGIN_NAMESPACE +class QDebug; +class QTextCodec; +QT_END_NAMESPACE + +class tst_QtcProcess; + +namespace Utils { + +namespace Internal { class QtcProcessPrivate; } +namespace Pty { class Data; } + +class Environment; +class DeviceProcessHooks; +class ProcessInterface; +class ProcessResultData; + +class QTCREATOR_UTILS_EXPORT Process final : public QObject +{ + Q_OBJECT + +public: + explicit Process(QObject *parent = nullptr); + ~Process(); + + // ProcessInterface related + + void start(); + + void terminate(); + void kill(); + void interrupt(); + void kickoffProcess(); + void closeWriteChannel(); + void close(); + void stop(); + + QString readAllStandardOutput(); + QString readAllStandardError(); + + QByteArray readAllRawStandardOutput(); + QByteArray readAllRawStandardError(); + + qint64 write(const QString &input); + qint64 writeRaw(const QByteArray &input); + + qint64 processId() const; + qint64 applicationMainThreadId() const; + + QProcess::ProcessState state() const; + ProcessResultData resultData() const; + + int exitCode() const; + QProcess::ExitStatus exitStatus() const; + + QProcess::ProcessError error() const; + QString errorString() const; + + bool waitForStarted(int msecs = 30000); + bool waitForReadyRead(int msecs = 30000); + bool waitForFinished(int msecs = 30000); + + // ProcessSetupData related + + void setProcessImpl(ProcessImpl processImpl); + + void setPtyData(const std::optional &data); + std::optional ptyData() const; + + void setTerminalMode(TerminalMode mode); + TerminalMode terminalMode() const; + bool usesTerminal() const { return terminalMode() != TerminalMode::Off; } + + void setProcessMode(ProcessMode processMode); + ProcessMode processMode() const; + + void setEnvironment(const Environment &env); // Main process + const Environment &environment() const; + + void setControlEnvironment(const Environment &env); // Possible helper process (ssh on host etc) + const Environment &controlEnvironment() const; + + void setCommand(const CommandLine &cmdLine); + const CommandLine &commandLine() const; + + void setWorkingDirectory(const FilePath &dir); + FilePath workingDirectory() const; + + void setWriteData(const QByteArray &writeData); + + void setUseCtrlCStub(bool enabled); // release only + void setLowPriority(); + void setDisableUnixTerminal(); + void setRunAsRoot(bool on); + bool isRunAsRoot() const; + void setAbortOnMetaChars(bool abort); + + QProcess::ProcessChannelMode processChannelMode() const; + void setProcessChannelMode(QProcess::ProcessChannelMode mode); + void setStandardInputFile(const QString &inputFile); + + void setExtraData(const QString &key, const QVariant &value); + QVariant extraData(const QString &key) const; + + void setExtraData(const QVariantHash &extraData); + QVariantHash extraData() const; + + void setReaperTimeout(int msecs); + int reaperTimeout() const; + + static void setRemoteProcessHooks(const DeviceProcessHooks &hooks); + + // TODO: Some usages of this method assume that Starting phase is also a running state + // i.e. if isRunning() returns false, they assume NotRunning state, what may be an error. + bool isRunning() const; // Short for state() == QProcess::Running. + + // Other enhancements. + // These (or some of them) may be potentially moved outside of the class. + // For some we may aggregate in another public utils class (or subclass of QtcProcess)? + + // TODO: Unused currently? Should it serve as a compartment for contrary of remoteEnvironment? + static Environment systemEnvironmentForBinary(const FilePath &filePath); + + static bool startDetached(const CommandLine &cmd, const FilePath &workingDirectory = {}, + qint64 *pid = nullptr); + + // Starts the command and waits for finish. + // User input processing is enabled when EventLoopMode::On was passed. + void runBlocking(EventLoopMode eventLoopMode = EventLoopMode::Off); + + /* Timeout for hanging processes (triggers after no more output + * occurs on stderr/stdout). */ + void setTimeoutS(int timeoutS); + int timeoutS() const; + + // TODO: We should specify the purpose of the codec, e.g. setCodecForStandardChannel() + void setCodec(QTextCodec *c); + void setTimeOutMessageBoxEnabled(bool); + void setExitCodeInterpreter(const ExitCodeInterpreter &interpreter); + + void setStdOutCallback(const TextChannelCallback &callback); + void setStdOutLineCallback(const TextChannelCallback &callback); + void setStdErrCallback(const TextChannelCallback &callback); + void setStdErrLineCallback(const TextChannelCallback &callback); + + void setTextChannelMode(Channel channel, TextChannelMode mode); + TextChannelMode textChannelMode(Channel channel) const; + + bool readDataFromProcess(QByteArray *stdOut, QByteArray *stdErr, int timeoutS = 30); + + ProcessResult result() const; + + QByteArray allRawOutput() const; + QString allOutput() const; + + QByteArray rawStdOut() const; + + QString stdOut() const; // possibly with CR + QString stdErr() const; // possibly with CR + + QString cleanedStdOut() const; // with sequences of CR squashed and CR LF replaced by LF + QString cleanedStdErr() const; // with sequences of CR squashed and CR LF replaced by LF + + const QStringList stdOutLines() const; // split, CR removed + const QStringList stdErrLines() const; // split, CR removed + + QString exitMessage() const; + + QString toStandaloneCommandLine() const; + + void setCreateConsoleOnWindows(bool create); + bool createConsoleOnWindows() const; + +signals: + void starting(); // On NotRunning -> Starting state transition + void started(); // On Starting -> Running state transition + void done(); // On Starting | Running -> NotRunning state transition + void readyReadStandardOutput(); + void readyReadStandardError(); + void textOnStandardOutput(const QString &text); + void textOnStandardError(const QString &text); + +private: + friend QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const Process &r); + + friend class Internal::QtcProcessPrivate; + Internal::QtcProcessPrivate *d = nullptr; +}; + +class DeviceProcessHooks +{ +public: + std::function processImplHook; + std::function systemEnvironmentForBinary; +}; + +class QTCREATOR_UTILS_EXPORT ProcessTaskAdapter : public Tasking::TaskAdapter +{ +public: + ProcessTaskAdapter(); + void start() final; +}; + +} // namespace Utils + +QTC_DECLARE_CUSTOM_TASK(ProcessTask, Utils::ProcessTaskAdapter); diff --git a/src/libs/utils/processinfo.cpp b/src/libs/utils/processinfo.cpp index dc41a8f63e..0d71e380fb 100644 --- a/src/libs/utils/processinfo.cpp +++ b/src/libs/utils/processinfo.cpp @@ -4,7 +4,7 @@ #include "processinfo.h" #include "algorithm.h" -#include "qtcprocess.h" +#include "process.h" #include #include diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/qtcprocess.cpp deleted file mode 100644 index 1fe97b3d2f..0000000000 --- a/src/libs/utils/qtcprocess.cpp +++ /dev/null @@ -1,2163 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "qtcprocess.h" - -#include "algorithm.h" -#include "environment.h" -#include "guard.h" -#include "hostosinfo.h" -#include "launcherinterface.h" -#include "launchersocket.h" -#include "processreaper.h" -#include "processutils.h" -#include "stringutils.h" -#include "terminalhooks.h" -#include "threadutils.h" -#include "utilstr.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef QT_GUI_LIB -// qmlpuppet does not use that. -#include -#include -#endif - -#include -#include -#include -#include -#include -#include -#include - -using namespace Utils::Internal; - -namespace Utils { -namespace Internal { - -const char QTC_PROCESS_BLOCKING_TYPE[] = "__BLOCKING_TYPE__"; -const char QTC_PROCESS_NUMBER[] = "__NUMBER__"; -const char QTC_PROCESS_STARTTIME[] = "__STARTTIME__"; - -class MeasureAndRun -{ -public: - MeasureAndRun(const char *functionName) - : m_functionName(functionName) - , m_measureProcess(qtcEnvironmentVariableIsSet("QTC_MEASURE_PROCESS")) - {} - template - std::invoke_result_t measureAndRun(Function &&function, Args&&... args) - { - if (!m_measureProcess) - return std::invoke(std::forward(function), std::forward(args)...); - QElapsedTimer timer; - timer.start(); - auto cleanup = qScopeGuard([this, &timer] { - const qint64 currentNsecs = timer.nsecsElapsed(); - const bool mainThread = isMainThread(); - const int hitThisAll = m_hitThisAll.fetch_add(1) + 1; - const int hitAllAll = m_hitAllAll.fetch_add(1) + 1; - const int hitThisMain = mainThread - ? m_hitThisMain.fetch_add(1) + 1 - : m_hitThisMain.load(); - const int hitAllMain = mainThread - ? m_hitAllMain.fetch_add(1) + 1 - : m_hitAllMain.load(); - const qint64 totalThisAll = toMs(m_totalThisAll.fetch_add(currentNsecs) + currentNsecs); - const qint64 totalAllAll = toMs(m_totalAllAll.fetch_add(currentNsecs) + currentNsecs); - const qint64 totalThisMain = toMs(mainThread - ? m_totalThisMain.fetch_add(currentNsecs) + currentNsecs - : m_totalThisMain.load()); - const qint64 totalAllMain = toMs(mainThread - ? m_totalAllMain.fetch_add(currentNsecs) + currentNsecs - : m_totalAllMain.load()); - printMeasurement(QLatin1String(m_functionName), hitThisAll, toMs(currentNsecs), - totalThisAll, hitAllAll, totalAllAll, mainThread, - hitThisMain, totalThisMain, hitAllMain, totalAllMain); - }); - return std::invoke(std::forward(function), std::forward(args)...); - } -private: - static void printHeader() - { - // [function/thread]: function:(T)his|(A)ll, thread:(M)ain|(A)ll - qDebug() << "+----------------+-------+---------+----------+-------+----------+---------+-------+----------+-------+----------+"; - qDebug() << "| [Function/Thread] = [(T|A)/(M|A)], where: (T)his function, (A)ll functions / threads, (M)ain thread |"; - qDebug() << "+----------------+-------+---------+----------+-------+----------+---------+-------+----------+-------+----------+"; - qDebug() << "| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |"; - qDebug() << "| | [T/A] | [T/A] | [T/A] | [A/A] | [A/A] | | [T/M] | [T/M] | [A/M] | [A/M] |"; - qDebug() << "| Function | Hit | Current | Total | Hit | Total | Current | Hit | Total | Hit | Total |"; - qDebug() << "| Name | Count | Measu- | Measu- | Count | Measu- | is Main | Count | Measu- | Count | Measu- |"; - qDebug() << "| | | rement | rement | | rement | Thread | | rement | | rement |"; - qDebug() << "+----------------+-------+---------+----------+-------+----------+---------+-------+----------+-------+----------+"; - } - static void printMeasurement(const QString &functionName, int hitThisAll, int currentNsecs, - int totalThisAll, int hitAllAll, int totalAllAll, bool isMainThread, - int hitThisMain, int totalThisMain, int hitAllMain, int totalAllMain) - { - static const int repeatHeaderLineCount = 25; - if (s_lineCounter.fetch_add(1) % repeatHeaderLineCount == 0) - printHeader(); - - const QString &functionNameField = QString("%1").arg(functionName, 14); - const QString &hitThisAllField = formatField(hitThisAll, 5); - const QString ¤tNsecsField = formatField(currentNsecs, 7, " ms"); - const QString &totalThisAllField = formatField(totalThisAll, 8, " ms"); - const QString &hitAllAllField = formatField(hitAllAll, 5); - const QString &totalAllAllField = formatField(totalAllAll, 8, " ms"); - const QString &mainThreadField = isMainThread ? QString("%1").arg("yes", 7) - : QString("%1").arg("no", 7); - const QString &hitThisMainField = formatField(hitThisMain, 5); - const QString &totalThisMainField = formatField(totalThisMain, 8, " ms"); - const QString &hitAllMainField = formatField(hitAllMain, 5); - const QString &totalAllMainField = formatField(totalAllMain, 8, " ms"); - - const QString &totalString = QString("| %1 | %2 | %3 | %4 | %5 | %6 | %7 | %8 | %9 | %10 | %11 |") - .arg(functionNameField, hitThisAllField, currentNsecsField, - totalThisAllField, hitAllAllField, totalAllAllField, mainThreadField, - hitThisMainField, totalThisMainField, hitAllMainField, totalAllMainField); - qDebug("%s", qPrintable(totalString)); - } - static QString formatField(int number, int fieldWidth, const QString &suffix = {}) - { - return QString("%1%2").arg(number, fieldWidth - suffix.count()).arg(suffix); - } - - static int toMs(quint64 nsesc) // nanoseconds to miliseconds - { - static const int halfMillion = 500000; - static const int million = 2 * halfMillion; - return int((nsesc + halfMillion) / million); - } - - const char * const m_functionName; - const bool m_measureProcess; - std::atomic_int m_hitThisAll = 0; - std::atomic_int m_hitThisMain = 0; - std::atomic_int64_t m_totalThisAll = 0; - std::atomic_int64_t m_totalThisMain = 0; - static std::atomic_int m_hitAllAll; - static std::atomic_int m_hitAllMain; - static std::atomic_int64_t m_totalAllAll; - static std::atomic_int64_t m_totalAllMain; - static std::atomic_int s_lineCounter; -}; - -std::atomic_int MeasureAndRun::m_hitAllAll = 0; -std::atomic_int MeasureAndRun::m_hitAllMain = 0; -std::atomic_int64_t MeasureAndRun::m_totalAllAll = 0; -std::atomic_int64_t MeasureAndRun::m_totalAllMain = 0; -std::atomic_int MeasureAndRun::s_lineCounter = 0; - -static MeasureAndRun s_start = MeasureAndRun("start"); -static MeasureAndRun s_waitForStarted = MeasureAndRun("waitForStarted"); - -enum { debug = 0 }; -enum { syncDebug = 0 }; - -enum { defaultMaxHangTimerCount = 10 }; - -static Q_LOGGING_CATEGORY(processLog, "qtc.utils.qtcprocess", QtWarningMsg) -static Q_LOGGING_CATEGORY(processStdoutLog, "qtc.utils.qtcprocess.stdout", QtWarningMsg) -static Q_LOGGING_CATEGORY(processStderrLog, "qtc.utils.qtcprocess.stderr", QtWarningMsg) - -static DeviceProcessHooks s_deviceHooks; - -// Data for one channel buffer (stderr/stdout) -class ChannelBuffer -{ -public: - void clearForRun(); - - void handleRest(); - void append(const QByteArray &text); - - QByteArray readAllData() { return std::exchange(rawData, {}); } - - QByteArray rawData; - QString incompleteLineBuffer; // lines not yet signaled - QTextCodec *codec = nullptr; // Not owner - std::unique_ptr codecState; - std::function outputCallback; - TextChannelMode m_textChannelMode = TextChannelMode::Off; - - bool emitSingleLines = true; - bool keepRawData = true; -}; - -class DefaultImpl : public ProcessInterface -{ -private: - virtual void start() final; - virtual void doDefaultStart(const QString &program, const QStringList &arguments) = 0; - bool dissolveCommand(QString *program, QStringList *arguments); - bool ensureProgramExists(const QString &program); -}; - -void DefaultImpl::start() -{ - QString program; - QStringList arguments; - if (!dissolveCommand(&program, &arguments)) - return; - if (!ensureProgramExists(program)) - return; - s_start.measureAndRun(&DefaultImpl::doDefaultStart, this, program, arguments); -} - -bool DefaultImpl::dissolveCommand(QString *program, QStringList *arguments) -{ - const CommandLine &commandLine = m_setup.m_commandLine; - QString commandString; - ProcessArgs processArgs; - const bool success = ProcessArgs::prepareCommand(commandLine, &commandString, &processArgs, - &m_setup.m_environment, - &m_setup.m_workingDirectory); - - if (commandLine.executable().osType() == OsTypeWindows) { - QString args; - if (m_setup.m_useCtrlCStub) { - if (m_setup.m_lowPriority) - ProcessArgs::addArg(&args, "-nice"); - ProcessArgs::addArg(&args, QDir::toNativeSeparators(commandString)); - commandString = QCoreApplication::applicationDirPath() - + QLatin1String("/qtcreator_ctrlc_stub.exe"); - } else if (m_setup.m_lowPriority) { - m_setup.m_belowNormalPriority = true; - } - ProcessArgs::addArgs(&args, processArgs.toWindowsArgs()); - m_setup.m_nativeArguments = args; - // Note: Arguments set with setNativeArgs will be appended to the ones - // passed with start() below. - *arguments = {}; - } else { - if (!success) { - const ProcessResultData result = {0, - QProcess::NormalExit, - QProcess::FailedToStart, - Tr::tr("Error in command line.")}; - emit done(result); - return false; - } - *arguments = processArgs.toUnixArgs(); - } - *program = commandString; - return true; -} - -static FilePath resolve(const FilePath &workingDir, const FilePath &filePath) -{ - if (filePath.isAbsolutePath()) - return filePath; - - const FilePath fromWorkingDir = workingDir.resolvePath(filePath); - if (fromWorkingDir.exists() && fromWorkingDir.isExecutableFile()) - return fromWorkingDir; - return filePath.searchInPath(); -} - -bool DefaultImpl::ensureProgramExists(const QString &program) -{ - const FilePath programFilePath = resolve(m_setup.m_workingDirectory, - FilePath::fromString(program)); - if (programFilePath.exists() && programFilePath.isExecutableFile()) - return true; - - const QString errorString - = Tr::tr("The program \"%1\" does not exist or is not executable.").arg(program); - const ProcessResultData result = { 0, QProcess::NormalExit, QProcess::FailedToStart, - errorString }; - emit done(result); - return false; -} - -class QProcessBlockingImpl : public ProcessBlockingInterface -{ -public: - QProcessBlockingImpl(QProcess *process) : m_process(process) {} - -private: - bool waitForSignal(ProcessSignalType signalType, int msecs) final - { - switch (signalType) { - case ProcessSignalType::Started: - return m_process->waitForStarted(msecs); - case ProcessSignalType::ReadyRead: - return m_process->waitForReadyRead(msecs); - case ProcessSignalType::Done: - return m_process->waitForFinished(msecs); - } - return false; - } - - QProcess *m_process = nullptr; -}; - -class PtyProcessImpl final : public DefaultImpl -{ -public: - ~PtyProcessImpl() { QTC_CHECK(m_setup.m_ptyData); m_setup.m_ptyData->setResizeHandler({}); } - - qint64 write(const QByteArray &data) final - { - if (m_ptyProcess) - return m_ptyProcess->write(data); - return -1; - } - - void sendControlSignal(ControlSignal controlSignal) final - { - if (!m_ptyProcess) - return; - - switch (controlSignal) { - case ControlSignal::Terminate: - m_ptyProcess.reset(); - break; - case ControlSignal::Kill: - m_ptyProcess->kill(); - break; - default: - QTC_CHECK(false); - } - } - - void doDefaultStart(const QString &program, const QStringList &arguments) final - { - QTC_CHECK(m_setup.m_ptyData); - m_setup.m_ptyData->setResizeHandler([this](const QSize &size) { - if (m_ptyProcess) - m_ptyProcess->resize(size.width(), size.height()); - }); - m_ptyProcess.reset(PtyQt::createPtyProcess(IPtyProcess::AutoPty)); - if (!m_ptyProcess) { - const ProcessResultData result = {-1, - QProcess::CrashExit, - QProcess::FailedToStart, - "Failed to create pty process"}; - emit done(result); - return; - } - - QProcessEnvironment penv = m_setup.m_environment.toProcessEnvironment(); - if (penv.isEmpty()) - penv = Environment::systemEnvironment().toProcessEnvironment(); - const QStringList senv = penv.toStringList(); - - bool startResult - = m_ptyProcess->startProcess(program, - HostOsInfo::isWindowsHost() - ? QStringList{m_setup.m_nativeArguments} << arguments - : arguments, - m_setup.m_workingDirectory.nativePath(), - senv, - m_setup.m_ptyData->size().width(), - m_setup.m_ptyData->size().height()); - - if (!startResult) { - const ProcessResultData result = {-1, - QProcess::CrashExit, - QProcess::FailedToStart, - "Failed to start pty process: " - + m_ptyProcess->lastError()}; - emit done(result); - return; - } - - if (!m_ptyProcess->lastError().isEmpty()) { - const ProcessResultData result - = {-1, QProcess::CrashExit, QProcess::FailedToStart, m_ptyProcess->lastError()}; - emit done(result); - return; - } - - connect(m_ptyProcess->notifier(), &QIODevice::readyRead, this, [this] { - emit readyRead(m_ptyProcess->readAll(), {}); - }); - - connect(m_ptyProcess->notifier(), &QIODevice::aboutToClose, this, [this] { - if (m_ptyProcess) { - const ProcessResultData result - = {m_ptyProcess->exitCode(), QProcess::NormalExit, QProcess::UnknownError, {}}; - emit done(result); - return; - } - - const ProcessResultData result = {0, QProcess::NormalExit, QProcess::UnknownError, {}}; - emit done(result); - }); - - emit started(m_ptyProcess->pid()); - } - -private: - std::unique_ptr m_ptyProcess; -}; - -class QProcessImpl final : public DefaultImpl -{ -public: - QProcessImpl() - : m_process(new ProcessHelper(this)) - , m_blockingImpl(new QProcessBlockingImpl(m_process)) - { - connect(m_process, &QProcess::started, this, &QProcessImpl::handleStarted); - connect(m_process, &QProcess::finished, this, &QProcessImpl::handleFinished); - connect(m_process, &QProcess::errorOccurred, this, &QProcessImpl::handleError); - connect(m_process, &QProcess::readyReadStandardOutput, this, [this] { - emit readyRead(m_process->readAllStandardOutput(), {}); - }); - connect(m_process, &QProcess::readyReadStandardError, this, [this] { - emit readyRead({}, m_process->readAllStandardError()); - }); - } - ~QProcessImpl() final { ProcessReaper::reap(m_process, m_setup.m_reaperTimeout); } - -private: - qint64 write(const QByteArray &data) final { return m_process->write(data); } - void sendControlSignal(ControlSignal controlSignal) final { - switch (controlSignal) { - case ControlSignal::Terminate: - ProcessHelper::terminateProcess(m_process); - break; - case ControlSignal::Kill: - m_process->kill(); - break; - case ControlSignal::Interrupt: - ProcessHelper::interruptProcess(m_process); - break; - case ControlSignal::KickOff: - QTC_CHECK(false); - break; - case ControlSignal::CloseWriteChannel: - m_process->closeWriteChannel(); - break; - } - } - - virtual ProcessBlockingInterface *processBlockingInterface() const { return m_blockingImpl; } - - void doDefaultStart(const QString &program, const QStringList &arguments) final - { - QTC_ASSERT(QThread::currentThread()->eventDispatcher(), - qWarning("QtcProcess::start(): Starting a process in a non QThread thread " - "may cause infinite hang when destroying the running process.")); - ProcessStartHandler *handler = m_process->processStartHandler(); - handler->setProcessMode(m_setup.m_processMode); - handler->setWriteData(m_setup.m_writeData); - handler->setNativeArguments(m_setup.m_nativeArguments); - handler->setWindowsSpecificStartupFlags(m_setup.m_belowNormalPriority, - m_setup.m_createConsoleOnWindows); - - const QProcessEnvironment penv = m_setup.m_environment.toProcessEnvironment(); - if (!penv.isEmpty()) - m_process->setProcessEnvironment(penv); - m_process->setWorkingDirectory(m_setup.m_workingDirectory.path()); - m_process->setStandardInputFile(m_setup.m_standardInputFile); - m_process->setProcessChannelMode(m_setup.m_processChannelMode); - if (m_setup.m_lowPriority) - m_process->setLowPriority(); - if (m_setup.m_unixTerminalDisabled) - m_process->setUnixTerminalDisabled(); - m_process->setUseCtrlCStub(m_setup.m_useCtrlCStub); - m_process->start(program, arguments, handler->openMode()); - handler->handleProcessStart(); - } - - void handleStarted() - { - m_process->processStartHandler()->handleProcessStarted(); - emit started(m_process->processId()); - } - - void handleError(QProcess::ProcessError error) - { - if (error != QProcess::FailedToStart) - return; - const ProcessResultData result = { m_process->exitCode(), m_process->exitStatus(), - error, m_process->errorString() }; - emit done(result); - } - - void handleFinished(int exitCode, QProcess::ExitStatus exitStatus) - { - const ProcessResultData result = { exitCode, exitStatus, - m_process->error(), m_process->errorString() }; - emit done(result); - } - - ProcessHelper *m_process = nullptr; - QProcessBlockingImpl *m_blockingImpl = nullptr; -}; - -static uint uniqueToken() -{ - static std::atomic_uint globalUniqueToken = 0; - return ++globalUniqueToken; -} - -class ProcessLauncherBlockingImpl : public ProcessBlockingInterface -{ -public: - ProcessLauncherBlockingImpl(CallerHandle *caller) : m_caller(caller) {} - -private: - bool waitForSignal(ProcessSignalType signalType, int msecs) final - { - // TODO: Remove CallerHandle::SignalType - const CallerHandle::SignalType type = [signalType] { - switch (signalType) { - case ProcessSignalType::Started: - return CallerHandle::SignalType::Started; - case ProcessSignalType::ReadyRead: - return CallerHandle::SignalType::ReadyRead; - case ProcessSignalType::Done: - return CallerHandle::SignalType::Done; - } - QTC_CHECK(false); - return CallerHandle::SignalType::NoSignal; - }(); - return m_caller->waitForSignal(type, msecs); - } - - CallerHandle *m_caller = nullptr; -}; - -class ProcessLauncherImpl final : public DefaultImpl -{ - Q_OBJECT -public: - ProcessLauncherImpl() : m_token(uniqueToken()) - { - m_handle = LauncherInterface::registerHandle(this, token()); - m_handle->setProcessSetupData(&m_setup); - connect(m_handle, &CallerHandle::started, - this, &ProcessInterface::started); - connect(m_handle, &CallerHandle::readyRead, - this, &ProcessInterface::readyRead); - connect(m_handle, &CallerHandle::done, - this, &ProcessInterface::done); - m_blockingImpl = new ProcessLauncherBlockingImpl(m_handle); - } - ~ProcessLauncherImpl() final - { - m_handle->close(); - LauncherInterface::unregisterHandle(token()); - m_handle = nullptr; - } - -private: - qint64 write(const QByteArray &data) final { return m_handle->write(data); } - void sendControlSignal(ControlSignal controlSignal) final { - switch (controlSignal) { - case ControlSignal::Terminate: - m_handle->terminate(); - break; - case ControlSignal::Kill: - m_handle->kill(); - break; - case ControlSignal::Interrupt: - ProcessHelper::interruptPid(m_handle->processId()); - break; - case ControlSignal::KickOff: - QTC_CHECK(false); - break; - case ControlSignal::CloseWriteChannel: - m_handle->closeWriteChannel(); - break; - } - } - - virtual ProcessBlockingInterface *processBlockingInterface() const { return m_blockingImpl; } - - void doDefaultStart(const QString &program, const QStringList &arguments) final - { - m_handle->start(program, arguments); - } - - quintptr token() const { return m_token; } - - const uint m_token = 0; - // Lives in caller's thread. - CallerHandle *m_handle = nullptr; - ProcessLauncherBlockingImpl *m_blockingImpl = nullptr; -}; - -static ProcessImpl defaultProcessImpl() -{ - if (qtcEnvironmentVariableIsSet("QTC_USE_QPROCESS")) - return ProcessImpl::QProcess; - return ProcessImpl::ProcessLauncher; -} - -class ProcessInterfaceSignal -{ -public: - ProcessSignalType signalType() const { return m_signalType; } - virtual ~ProcessInterfaceSignal() = default; -protected: - ProcessInterfaceSignal(ProcessSignalType signalType) : m_signalType(signalType) {} -private: - const ProcessSignalType m_signalType; -}; - -class StartedSignal : public ProcessInterfaceSignal -{ -public: - StartedSignal(qint64 processId, qint64 applicationMainThreadId) - : ProcessInterfaceSignal(ProcessSignalType::Started) - , m_processId(processId) - , m_applicationMainThreadId(applicationMainThreadId) {} - qint64 processId() const { return m_processId; } - qint64 applicationMainThreadId() const { return m_applicationMainThreadId; } -private: - const qint64 m_processId; - const qint64 m_applicationMainThreadId; -}; - -class ReadyReadSignal : public ProcessInterfaceSignal -{ -public: - ReadyReadSignal(const QByteArray &stdOut, const QByteArray &stdErr) - : ProcessInterfaceSignal(ProcessSignalType::ReadyRead) - , m_stdOut(stdOut) - , m_stdErr(stdErr) {} - QByteArray stdOut() const { return m_stdOut; } - QByteArray stdErr() const { return m_stdErr; } -private: - const QByteArray m_stdOut; - const QByteArray m_stdErr; -}; - -class DoneSignal : public ProcessInterfaceSignal -{ -public: - DoneSignal(const ProcessResultData &resultData) - : ProcessInterfaceSignal(ProcessSignalType::Done) - , m_resultData(resultData) {} - ProcessResultData resultData() const { return m_resultData; } -private: - const ProcessResultData m_resultData; -}; - -class GeneralProcessBlockingImpl; - -class ProcessInterfaceHandler : public QObject -{ -public: - ProcessInterfaceHandler(GeneralProcessBlockingImpl *caller, ProcessInterface *process); - - // Called from caller's thread exclusively. - bool waitForSignal(ProcessSignalType newSignal, int msecs); - void moveToCallerThread(); - -private: - // Called from caller's thread exclusively. - bool doWaitForSignal(QDeadlineTimer deadline); - - // Called from caller's thread when not waiting for signal, - // otherwise called from temporary thread. - void handleStarted(qint64 processId, qint64 applicationMainThreadId); - void handleReadyRead(const QByteArray &outputData, const QByteArray &errorData); - void handleDone(const ProcessResultData &data); - void appendSignal(ProcessInterfaceSignal *newSignal); - - GeneralProcessBlockingImpl *m_caller = nullptr; - QMutex m_mutex; - QWaitCondition m_waitCondition; -}; - -class GeneralProcessBlockingImpl : public ProcessBlockingInterface -{ -public: - GeneralProcessBlockingImpl(QtcProcessPrivate *parent); - - void flush() { flushSignals(takeAllSignals()); } - bool flushFor(ProcessSignalType signalType) { - return flushSignals(takeSignalsFor(signalType), &signalType); - } - - bool shouldFlush() const { QMutexLocker locker(&m_mutex); return !m_signals.isEmpty(); } - // Called from ProcessInterfaceHandler thread exclusively. - void appendSignal(ProcessInterfaceSignal *launcherSignal); - -private: - // Called from caller's thread exclusively - bool waitForSignal(ProcessSignalType newSignal, int msecs) final; - - QList takeAllSignals(); - QList takeSignalsFor(ProcessSignalType signalType); - bool flushSignals(const QList &signalList, - ProcessSignalType *signalType = nullptr); - - void handleStartedSignal(const StartedSignal *launcherSignal); - void handleReadyReadSignal(const ReadyReadSignal *launcherSignal); - void handleDoneSignal(const DoneSignal *launcherSignal); - - QtcProcessPrivate *m_caller = nullptr; - std::unique_ptr m_processHandler; - mutable QMutex m_mutex; - QList m_signals; -}; - -class QtcProcessPrivate : public QObject -{ -public: - explicit QtcProcessPrivate(Process *parent) - : QObject(parent) - , q(parent) - , m_killTimer(this) - { - m_killTimer.setSingleShot(true); - connect(&m_killTimer, &QTimer::timeout, this, [this] { - m_killTimer.stop(); - sendControlSignal(ControlSignal::Kill); - }); - setupDebugLog(); - } - - void setupDebugLog(); - void storeEventLoopDebugInfo(const QVariant &value); - - ProcessInterface *createProcessInterface() - { - if (m_setup.m_ptyData) - return new PtyProcessImpl; - if (m_setup.m_terminalMode != TerminalMode::Off) - return Terminal::Hooks::instance().createTerminalProcessInterface(); - - const ProcessImpl impl = m_setup.m_processImpl == ProcessImpl::Default - ? defaultProcessImpl() : m_setup.m_processImpl; - if (impl == ProcessImpl::QProcess) - return new QProcessImpl; - return new ProcessLauncherImpl; - } - - void setProcessInterface(ProcessInterface *process) - { - if (m_process) - m_process->disconnect(); - m_process.reset(process); - m_process->setParent(this); - connect(m_process.get(), &ProcessInterface::started, - this, &QtcProcessPrivate::handleStarted); - connect(m_process.get(), &ProcessInterface::readyRead, - this, &QtcProcessPrivate::handleReadyRead); - connect(m_process.get(), &ProcessInterface::done, - this, &QtcProcessPrivate::handleDone); - - m_blockingInterface.reset(process->processBlockingInterface()); - if (!m_blockingInterface) - m_blockingInterface.reset(new GeneralProcessBlockingImpl(this)); - m_blockingInterface->setParent(this); - } - - CommandLine fullCommandLine() const - { - if (!m_setup.m_runAsRoot || HostOsInfo::isWindowsHost()) - return m_setup.m_commandLine; - CommandLine rootCommand("sudo", {"-A"}); - rootCommand.addCommandLineAsArgs(m_setup.m_commandLine); - return rootCommand; - } - - Process *q; - std::unique_ptr m_blockingInterface; - std::unique_ptr m_process; - ProcessSetupData m_setup; - - void slotTimeout(); - void handleStarted(qint64 processId, qint64 applicationMainThreadId); - void handleReadyRead(const QByteArray &outputData, const QByteArray &errorData); - void handleDone(const ProcessResultData &data); - void clearForRun(); - - void emitGuardedSignal(void (Process::* signalName)()) { - GuardLocker locker(m_guard); - emit (q->*signalName)(); - } - - ProcessResult interpretExitCode(int exitCode); - - bool waitForSignal(ProcessSignalType signalType, int msecs); - Qt::ConnectionType connectionType() const; - void sendControlSignal(ControlSignal controlSignal); - - QTimer m_killTimer; - QProcess::ProcessState m_state = QProcess::NotRunning; - qint64 m_processId = 0; - qint64 m_applicationMainThreadId = 0; - ProcessResultData m_resultData; - - QTextCodec *m_codec = QTextCodec::codecForLocale(); - QEventLoop *m_eventLoop = nullptr; - ProcessResult m_result = ProcessResult::StartFailed; - ChannelBuffer m_stdOut; - ChannelBuffer m_stdErr; - ExitCodeInterpreter m_exitCodeInterpreter; - - int m_hangTimerCount = 0; - int m_maxHangTimerCount = defaultMaxHangTimerCount; - bool m_timeOutMessageBoxEnabled = false; - bool m_waitingForUser = false; - - Guard m_guard; -}; - -ProcessInterfaceHandler::ProcessInterfaceHandler(GeneralProcessBlockingImpl *caller, - ProcessInterface *process) - : m_caller(caller) -{ - process->disconnect(); - connect(process, &ProcessInterface::started, - this, &ProcessInterfaceHandler::handleStarted); - connect(process, &ProcessInterface::readyRead, - this, &ProcessInterfaceHandler::handleReadyRead); - connect(process, &ProcessInterface::done, - this, &ProcessInterfaceHandler::handleDone); -} - -// Called from caller's thread exclusively. -bool ProcessInterfaceHandler::waitForSignal(ProcessSignalType newSignal, int msecs) -{ - QDeadlineTimer deadline(msecs); - while (true) { - if (deadline.hasExpired()) - break; - if (!doWaitForSignal(deadline)) - break; - // Matching (or Done) signal was flushed - if (m_caller->flushFor(newSignal)) - return true; - // Otherwise continue awaiting (e.g. when ReadyRead came while waitForFinished()) - } - return false; -} - -// Called from caller's thread exclusively. -void ProcessInterfaceHandler::moveToCallerThread() -{ - QMetaObject::invokeMethod(this, [this] { - moveToThread(m_caller->thread()); - }, Qt::BlockingQueuedConnection); -} - -// Called from caller's thread exclusively. -bool ProcessInterfaceHandler::doWaitForSignal(QDeadlineTimer deadline) -{ - QMutexLocker locker(&m_mutex); - - // Flush, if we have any stored signals. - // This must be called when holding laucher's mutex locked prior to the call to wait, - // so that it's done atomically. - if (m_caller->shouldFlush()) - return true; - - return m_waitCondition.wait(&m_mutex, deadline); -} - -// Called from ProcessInterfaceHandler thread exclusively -void ProcessInterfaceHandler::handleStarted(qint64 processId, qint64 applicationMainThreadId) -{ - appendSignal(new StartedSignal(processId, applicationMainThreadId)); -} - -// Called from ProcessInterfaceHandler thread exclusively -void ProcessInterfaceHandler::handleReadyRead(const QByteArray &outputData, const QByteArray &errorData) -{ - appendSignal(new ReadyReadSignal(outputData, errorData)); -} - -// Called from ProcessInterfaceHandler thread exclusively -void ProcessInterfaceHandler::handleDone(const ProcessResultData &data) -{ - appendSignal(new DoneSignal(data)); -} - -void ProcessInterfaceHandler::appendSignal(ProcessInterfaceSignal *newSignal) -{ - { - QMutexLocker locker(&m_mutex); - m_caller->appendSignal(newSignal); - } - m_waitCondition.wakeOne(); - // call in callers thread - QMetaObject::invokeMethod(m_caller, &GeneralProcessBlockingImpl::flush); -} - -GeneralProcessBlockingImpl::GeneralProcessBlockingImpl(QtcProcessPrivate *parent) - : m_caller(parent) - , m_processHandler(new ProcessInterfaceHandler(this, parent->m_process.get())) -{ - // In order to move the process interface into another thread together with handle - parent->m_process.get()->setParent(m_processHandler.get()); - m_processHandler->setParent(this); - // So the hierarchy looks like: - // QtcProcessPrivate - // | - // +- GeneralProcessBlockingImpl - // | - // +- ProcessInterfaceHandler - // | - // +- ProcessInterface -} - -bool GeneralProcessBlockingImpl::waitForSignal(ProcessSignalType newSignal, int msecs) -{ - m_processHandler->setParent(nullptr); - - QThread thread; - thread.start(); - // Note: the thread may have started before and it's appending new signals before - // waitForSignal() is called. However, in this case they won't be flushed since - // the caller here is blocked, so all signals should be buffered and we are going - // to flush them from inside waitForSignal(). - m_processHandler->moveToThread(&thread); - const bool result = m_processHandler->waitForSignal(newSignal, msecs); - m_processHandler->moveToCallerThread(); - m_processHandler->setParent(this); - thread.quit(); - thread.wait(); - return result; -} - -// Called from caller's thread exclusively -QList GeneralProcessBlockingImpl::takeAllSignals() -{ - QMutexLocker locker(&m_mutex); - return std::exchange(m_signals, {}); -} - -// Called from caller's thread exclusively -QList GeneralProcessBlockingImpl::takeSignalsFor(ProcessSignalType signalType) -{ - // If we are flushing for ReadyRead or Done - flush all. - if (signalType != ProcessSignalType::Started) - return takeAllSignals(); - - QMutexLocker locker(&m_mutex); - const QList storedSignals = transform(std::as_const(m_signals), - [](const ProcessInterfaceSignal *aSignal) { - return aSignal->signalType(); - }); - - // If we are flushing for Started: - // - if Started was buffered - flush Started only (even when Done was buffered) - // - otherwise if Done signal was buffered - flush all. - if (!storedSignals.contains(ProcessSignalType::Started) - && storedSignals.contains(ProcessSignalType::Done)) { - return std::exchange(m_signals, {}); // avoid takeAllSignals() because of mutex locked - } - - QList oldSignals; - const auto matchingIndex = storedSignals.lastIndexOf(signalType); - if (matchingIndex >= 0) { - oldSignals = m_signals.mid(0, matchingIndex + 1); - m_signals = m_signals.mid(matchingIndex + 1); - } - return oldSignals; -} - -// Called from caller's thread exclusively -bool GeneralProcessBlockingImpl::flushSignals(const QList &signalList, - ProcessSignalType *signalType) -{ - bool signalMatched = false; - for (const ProcessInterfaceSignal *storedSignal : std::as_const(signalList)) { - const ProcessSignalType storedSignalType = storedSignal->signalType(); - if (signalType && storedSignalType == *signalType) - signalMatched = true; - switch (storedSignalType) { - case ProcessSignalType::Started: - handleStartedSignal(static_cast(storedSignal)); - break; - case ProcessSignalType::ReadyRead: - handleReadyReadSignal(static_cast(storedSignal)); - break; - case ProcessSignalType::Done: - if (signalType) - signalMatched = true; - handleDoneSignal(static_cast(storedSignal)); - break; - } - delete storedSignal; - } - return signalMatched; -} - -void GeneralProcessBlockingImpl::handleStartedSignal(const StartedSignal *aSignal) -{ - m_caller->handleStarted(aSignal->processId(), aSignal->applicationMainThreadId()); -} - -void GeneralProcessBlockingImpl::handleReadyReadSignal(const ReadyReadSignal *aSignal) -{ - m_caller->handleReadyRead(aSignal->stdOut(), aSignal->stdErr()); -} - -void GeneralProcessBlockingImpl::handleDoneSignal(const DoneSignal *aSignal) -{ - m_caller->handleDone(aSignal->resultData()); -} - -// Called from ProcessInterfaceHandler thread exclusively. -void GeneralProcessBlockingImpl::appendSignal(ProcessInterfaceSignal *newSignal) -{ - QMutexLocker locker(&m_mutex); - m_signals.append(newSignal); -} - -bool QtcProcessPrivate::waitForSignal(ProcessSignalType newSignal, int msecs) -{ - const QDeadlineTimer timeout(msecs); - const QDeadlineTimer currentKillTimeout(m_killTimer.remainingTime()); - const bool needsSplit = m_killTimer.isActive() ? timeout > currentKillTimeout : false; - const QDeadlineTimer mainTimeout = needsSplit ? currentKillTimeout : timeout; - - bool result = m_blockingInterface->waitForSignal(newSignal, mainTimeout.remainingTime()); - if (!result && needsSplit) { - m_killTimer.stop(); - sendControlSignal(ControlSignal::Kill); - result = m_blockingInterface->waitForSignal(newSignal, timeout.remainingTime()); - } - return result; -} - -Qt::ConnectionType QtcProcessPrivate::connectionType() const -{ - return (m_process->thread() == thread()) ? Qt::DirectConnection - : Qt::BlockingQueuedConnection; -} - -void QtcProcessPrivate::sendControlSignal(ControlSignal controlSignal) -{ - QTC_ASSERT(QThread::currentThread() == thread(), return); - if (!m_process || (m_state == QProcess::NotRunning)) - return; - - if (controlSignal == ControlSignal::Terminate || controlSignal == ControlSignal::Kill) - m_resultData.m_canceledByUser = true; - - QMetaObject::invokeMethod(m_process.get(), [this, controlSignal] { - m_process->sendControlSignal(controlSignal); - }, connectionType()); -} - -void QtcProcessPrivate::clearForRun() -{ - m_hangTimerCount = 0; - m_stdOut.clearForRun(); - m_stdOut.codec = m_codec; - m_stdErr.clearForRun(); - m_stdErr.codec = m_codec; - m_result = ProcessResult::StartFailed; - - m_killTimer.stop(); - m_state = QProcess::NotRunning; - m_processId = 0; - m_applicationMainThreadId = 0; - m_resultData = {}; -} - -ProcessResult QtcProcessPrivate::interpretExitCode(int exitCode) -{ - if (m_exitCodeInterpreter) - return m_exitCodeInterpreter(exitCode); - - // default: - return exitCode ? ProcessResult::FinishedWithError : ProcessResult::FinishedWithSuccess; -} - -} // Internal - -/*! - \class Utils::QtcProcess - - \brief The QtcProcess class provides functionality for with processes. - - \sa Utils::ProcessArgs -*/ - -Process::Process(QObject *parent) - : QObject(parent), - d(new QtcProcessPrivate(this)) -{ - qRegisterMetaType("ProcessResultData"); - static int qProcessExitStatusMeta = qRegisterMetaType(); - static int qProcessProcessErrorMeta = qRegisterMetaType(); - Q_UNUSED(qProcessExitStatusMeta) - Q_UNUSED(qProcessProcessErrorMeta) -} - -Process::~Process() -{ - QTC_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting QtcProcess instance directly from " - "one of its signal handlers will lead to crash!")); - if (d->m_process) - d->m_process->disconnect(); - delete d; -} - -void Process::setProcessImpl(ProcessImpl processImpl) -{ - d->m_setup.m_processImpl = processImpl; -} - -void Process::setPtyData(const std::optional &data) -{ - d->m_setup.m_ptyData = data; -} - -std::optional Process::ptyData() const -{ - return d->m_setup.m_ptyData; -} - -ProcessMode Process::processMode() const -{ - return d->m_setup.m_processMode; -} - -void Process::setTerminalMode(TerminalMode mode) -{ - d->m_setup.m_terminalMode = mode; -} - -TerminalMode Process::terminalMode() const -{ - return d->m_setup.m_terminalMode; -} - -void Process::setProcessMode(ProcessMode processMode) -{ - d->m_setup.m_processMode = processMode; -} - -void Process::setEnvironment(const Environment &env) -{ - d->m_setup.m_environment = env; -} - -const Environment &Process::environment() const -{ - return d->m_setup.m_environment; -} - -void Process::setControlEnvironment(const Environment &environment) -{ - d->m_setup.m_controlEnvironment = environment; -} - -const Environment &Process::controlEnvironment() const -{ - return d->m_setup.m_controlEnvironment; -} - -void Process::setCommand(const CommandLine &cmdLine) -{ - if (d->m_setup.m_workingDirectory.needsDevice() && cmdLine.executable().needsDevice()) { - QTC_CHECK(d->m_setup.m_workingDirectory.host() == cmdLine.executable().host()); - } - d->m_setup.m_commandLine = cmdLine; -} - -const CommandLine &Process::commandLine() const -{ - return d->m_setup.m_commandLine; -} - -FilePath Process::workingDirectory() const -{ - return d->m_setup.m_workingDirectory; -} - -void Process::setWorkingDirectory(const FilePath &dir) -{ - if (dir.needsDevice() && d->m_setup.m_commandLine.executable().needsDevice()) { - QTC_CHECK(dir.host() == d->m_setup.m_commandLine.executable().host()); - } - d->m_setup.m_workingDirectory = dir; -} - -void Process::setUseCtrlCStub(bool enabled) -{ - d->m_setup.m_useCtrlCStub = enabled; -} - -void Process::start() -{ - QTC_ASSERT(state() == QProcess::NotRunning, return); - QTC_ASSERT(!(d->m_process && d->m_guard.isLocked()), - qWarning("Restarting the QtcProcess directly from one of its signal handlers will " - "lead to crash! Consider calling close() prior to direct restart.")); - d->clearForRun(); - ProcessInterface *processImpl = nullptr; - if (d->m_setup.m_commandLine.executable().needsDevice()) { - QTC_ASSERT(s_deviceHooks.processImplHook, return); - processImpl = s_deviceHooks.processImplHook(commandLine().executable()); - } else { - processImpl = d->createProcessInterface(); - } - QTC_ASSERT(processImpl, return); - d->setProcessInterface(processImpl); - d->m_state = QProcess::Starting; - d->m_process->m_setup = d->m_setup; - d->m_process->m_setup.m_commandLine = d->fullCommandLine(); - d->emitGuardedSignal(&Process::starting); - d->m_process->start(); -} - -void Process::terminate() -{ - d->sendControlSignal(ControlSignal::Terminate); -} - -void Process::kill() -{ - d->sendControlSignal(ControlSignal::Kill); -} - -void Process::interrupt() -{ - d->sendControlSignal(ControlSignal::Interrupt); -} - -void Process::kickoffProcess() -{ - d->sendControlSignal(ControlSignal::KickOff); -} - -void Process::closeWriteChannel() -{ - d->sendControlSignal(ControlSignal::CloseWriteChannel); -} - -bool Process::startDetached(const CommandLine &cmd, const FilePath &workingDirectory, qint64 *pid) -{ - return QProcess::startDetached(cmd.executable().toUserOutput(), - cmd.splitArguments(), - workingDirectory.toUserOutput(), - pid); -} - -void Process::setLowPriority() -{ - d->m_setup.m_lowPriority = true; -} - -void Process::setDisableUnixTerminal() -{ - d->m_setup.m_unixTerminalDisabled = true; -} - -void Process::setAbortOnMetaChars(bool abort) -{ - d->m_setup.m_abortOnMetaChars = abort; -} - -void Process::setRunAsRoot(bool on) -{ - d->m_setup.m_runAsRoot = on; -} - -bool Process::isRunAsRoot() const -{ - return d->m_setup.m_runAsRoot; -} - -void Process::setStandardInputFile(const QString &inputFile) -{ - d->m_setup.m_standardInputFile = inputFile; -} - -QString Process::toStandaloneCommandLine() const -{ - QStringList parts; - parts.append("/usr/bin/env"); - if (!d->m_setup.m_workingDirectory.isEmpty()) { - parts.append("-C"); - d->m_setup.m_workingDirectory.path(); - } - parts.append("-i"); - if (d->m_setup.m_environment.hasChanges()) { - const QStringList envVars = d->m_setup.m_environment.toStringList(); - std::transform(envVars.cbegin(), envVars.cend(), - std::back_inserter(parts), ProcessArgs::quoteArgUnix); - } - parts.append(d->m_setup.m_commandLine.executable().path()); - parts.append(d->m_setup.m_commandLine.splitArguments()); - return parts.join(" "); -} - -void Process::setCreateConsoleOnWindows(bool create) -{ - d->m_setup.m_createConsoleOnWindows = create; -} - -bool Process::createConsoleOnWindows() const -{ - return d->m_setup.m_createConsoleOnWindows; -} - -void Process::setExtraData(const QString &key, const QVariant &value) -{ - d->m_setup.m_extraData.insert(key, value); -} - -QVariant Process::extraData(const QString &key) const -{ - return d->m_setup.m_extraData.value(key); -} - -void Process::setExtraData(const QVariantHash &extraData) -{ - d->m_setup.m_extraData = extraData; -} - -QVariantHash Process::extraData() const -{ - return d->m_setup.m_extraData; -} - -void Process::setReaperTimeout(int msecs) -{ - d->m_setup.m_reaperTimeout = msecs; -} - -int Process::reaperTimeout() const -{ - return d->m_setup.m_reaperTimeout; -} - -void Process::setRemoteProcessHooks(const DeviceProcessHooks &hooks) -{ - s_deviceHooks = hooks; -} - -static bool askToKill(const CommandLine &command) -{ -#ifdef QT_GUI_LIB - if (!isMainThread()) - return true; - const QString title = Tr::tr("Process Not Responding"); - QString msg = command.isEmpty() ? Tr::tr("The process is not responding.") - : Tr::tr("The process \"%1\" is not responding.") - .arg(command.executable().toUserOutput()); - msg += ' '; - msg += Tr::tr("Terminate the process?"); - // Restore the cursor that is set to wait while running. - const bool hasOverrideCursor = QApplication::overrideCursor() != nullptr; - if (hasOverrideCursor) - QApplication::restoreOverrideCursor(); - QMessageBox::StandardButton answer = QMessageBox::question(nullptr, title, msg, QMessageBox::Yes|QMessageBox::No); - if (hasOverrideCursor) - QApplication::setOverrideCursor(Qt::WaitCursor); - return answer == QMessageBox::Yes; -#else - Q_UNUSED(command) - return true; -#endif -} - -// Helper for running a process synchronously in the foreground with timeout -// detection (taking effect after no more output -// occurs on stderr/stdout as opposed to waitForFinished()). Returns false if a timeout -// occurs. Checking of the process' exit state/code still has to be done. - -bool Process::readDataFromProcess(QByteArray *stdOut, QByteArray *stdErr, int timeoutS) -{ - enum { syncDebug = 0 }; - if (syncDebug) - qDebug() << ">readDataFromProcess" << timeoutS; - if (state() != QProcess::Running) { - qWarning("readDataFromProcess: Process in non-running state passed in."); - return false; - } - - // Keep the process running until it has no longer has data - bool finished = false; - bool hasData = false; - do { - finished = waitForFinished(timeoutS > 0 ? timeoutS * 1000 : -1) - || state() == QProcess::NotRunning; - // First check 'stdout' - const QByteArray newStdOut = readAllRawStandardOutput(); - if (!newStdOut.isEmpty()) { - hasData = true; - if (stdOut) - stdOut->append(newStdOut); - } - // Check 'stderr' separately. This is a special handling - // for 'git pull' and the like which prints its progress on stderr. - const QByteArray newStdErr = readAllRawStandardError(); - if (!newStdErr.isEmpty()) { - hasData = true; - if (stdErr) - stdErr->append(newStdErr); - } - // Prompt user, pretend we have data if says 'No'. - const bool hang = !hasData && !finished; - hasData = hang && !askToKill(d->m_setup.m_commandLine); - } while (hasData && !finished); - if (syncDebug) - qDebug() << "m_result; -} - -ProcessResultData Process::resultData() const -{ - return d->m_resultData; -} - -int Process::exitCode() const -{ - return resultData().m_exitCode; -} - -QProcess::ExitStatus Process::exitStatus() const -{ - return resultData().m_exitStatus; -} - -QProcess::ProcessError Process::error() const -{ - return resultData().m_error; -} - -QString Process::errorString() const -{ - return resultData().m_errorString; -} - -// Path utilities - -Environment Process::systemEnvironmentForBinary(const FilePath &filePath) -{ - if (filePath.needsDevice()) { - QTC_ASSERT(s_deviceHooks.systemEnvironmentForBinary, return {}); - return s_deviceHooks.systemEnvironmentForBinary(filePath); - } - - return Environment::systemEnvironment(); -} - -qint64 Process::applicationMainThreadId() const -{ - return d->m_applicationMainThreadId; -} - -QProcess::ProcessChannelMode Process::processChannelMode() const -{ - return d->m_setup.m_processChannelMode; -} - -void Process::setProcessChannelMode(QProcess::ProcessChannelMode mode) -{ - d->m_setup.m_processChannelMode = mode; -} - -QProcess::ProcessState Process::state() const -{ - return d->m_state; -} - -bool Process::isRunning() const -{ - return state() == QProcess::Running; -} - -qint64 Process::processId() const -{ - return d->m_processId; -} - -bool Process::waitForStarted(int msecs) -{ - QTC_ASSERT(d->m_process, return false); - if (d->m_state == QProcess::Running) - return true; - if (d->m_state == QProcess::NotRunning) - return false; - return s_waitForStarted.measureAndRun(&QtcProcessPrivate::waitForSignal, d, - ProcessSignalType::Started, msecs); -} - -bool Process::waitForReadyRead(int msecs) -{ - QTC_ASSERT(d->m_process, return false); - if (d->m_state == QProcess::NotRunning) - return false; - return d->waitForSignal(ProcessSignalType::ReadyRead, msecs); -} - -bool Process::waitForFinished(int msecs) -{ - QTC_ASSERT(d->m_process, return false); - if (d->m_state == QProcess::NotRunning) - return false; - return d->waitForSignal(ProcessSignalType::Done, msecs); -} - -QByteArray Process::readAllRawStandardOutput() -{ - return d->m_stdOut.readAllData(); -} - -QByteArray Process::readAllRawStandardError() -{ - return d->m_stdErr.readAllData(); -} - -qint64 Process::write(const QString &input) -{ - // Non-windows is assumed to be UTF-8 - if (commandLine().executable().osType() != OsTypeWindows) - return writeRaw(input.toUtf8()); - - if (HostOsInfo::hostOs() == OsTypeWindows) - return writeRaw(input.toLocal8Bit()); - - // "remote" Windows target on non-Windows host is unlikely, - // but the true encoding is not accessible. Use UTF8 as best guess. - QTC_CHECK(false); - return writeRaw(input.toUtf8()); -} - -qint64 Process::writeRaw(const QByteArray &input) -{ - QTC_ASSERT(processMode() == ProcessMode::Writer, return -1); - QTC_ASSERT(d->m_process, return -1); - QTC_ASSERT(state() == QProcess::Running, return -1); - QTC_ASSERT(QThread::currentThread() == thread(), return -1); - qint64 result = -1; - QMetaObject::invokeMethod(d->m_process.get(), [this, input] { - d->m_process->write(input); - }, d->connectionType(), &result); - return result; -} - -void Process::close() -{ - QTC_ASSERT(QThread::currentThread() == thread(), return); - if (d->m_process) { - // Note: the m_process may be inside ProcessInterfaceHandler's thread. - QTC_ASSERT(d->m_process->thread() == thread(), return); - d->m_process->disconnect(); - d->m_process.release()->deleteLater(); - } - if (d->m_blockingInterface) { - d->m_blockingInterface->disconnect(); - d->m_blockingInterface.release()->deleteLater(); - } - d->clearForRun(); -} - -/* - Calls terminate() directly and after a delay of reaperTimeout() it calls kill() - if the process is still running. -*/ -void Process::stop() -{ - if (state() == QProcess::NotRunning) - return; - - d->sendControlSignal(ControlSignal::Terminate); - d->m_killTimer.start(d->m_process->m_setup.m_reaperTimeout); -} - -QString Process::readAllStandardOutput() -{ - return QString::fromUtf8(readAllRawStandardOutput()); -} - -QString Process::readAllStandardError() -{ - return QString::fromUtf8(readAllRawStandardError()); -} - -/*! - \class Utils::SynchronousProcess - - \brief The SynchronousProcess class runs a synchronous process in its own - event loop that blocks only user input events. Thus, it allows for the GUI to - repaint and append output to log windows. - - The callbacks set with setStdOutCallback(), setStdErrCallback() are called - with complete lines based on the '\\n' marker. - They would typically be used for log windows. - - Alternatively you can used setStdOutLineCallback() and setStdErrLineCallback() - to process the output line by line. - - There is a timeout handling that takes effect after the last data have been - read from stdout/stdin (as opposed to waitForFinished(), which measures time - since it was invoked). It is thus also suitable for slow processes that - continuously output data (like version system operations). - - The property timeOutMessageBoxEnabled influences whether a message box is - shown asking the user if they want to kill the process on timeout (default: false). - - There are also static utility functions for dealing with fully synchronous - processes, like reading the output with correct timeout handling. - - Caution: This class should NOT be used if there is a chance that the process - triggers opening dialog boxes (for example, by file watchers triggering), - as this will cause event loop problems. -*/ - -QString Process::exitMessage() const -{ - const QString fullCmd = commandLine().toUserOutput(); - switch (result()) { - case ProcessResult::FinishedWithSuccess: - return Tr::tr("The command \"%1\" finished successfully.").arg(fullCmd); - case ProcessResult::FinishedWithError: - return Tr::tr("The command \"%1\" terminated with exit code %2.") - .arg(fullCmd).arg(exitCode()); - case ProcessResult::TerminatedAbnormally: - return Tr::tr("The command \"%1\" terminated abnormally.").arg(fullCmd); - case ProcessResult::StartFailed: - return Tr::tr("The command \"%1\" could not be started.").arg(fullCmd); - case ProcessResult::Hang: - return Tr::tr("The command \"%1\" did not respond within the timeout limit (%2 s).") - .arg(fullCmd).arg(d->m_maxHangTimerCount); - } - return {}; -} - -QByteArray Process::allRawOutput() const -{ - QTC_CHECK(d->m_stdOut.keepRawData); - QTC_CHECK(d->m_stdErr.keepRawData); - if (!d->m_stdOut.rawData.isEmpty() && !d->m_stdErr.rawData.isEmpty()) { - QByteArray result = d->m_stdOut.rawData; - if (!result.endsWith('\n')) - result += '\n'; - result += d->m_stdErr.rawData; - return result; - } - return !d->m_stdOut.rawData.isEmpty() ? d->m_stdOut.rawData : d->m_stdErr.rawData; -} - -QString Process::allOutput() const -{ - QTC_CHECK(d->m_stdOut.keepRawData); - QTC_CHECK(d->m_stdErr.keepRawData); - const QString out = cleanedStdOut(); - const QString err = cleanedStdErr(); - - if (!out.isEmpty() && !err.isEmpty()) { - QString result = out; - if (!result.endsWith('\n')) - result += '\n'; - result += err; - return result; - } - return !out.isEmpty() ? out : err; -} - -QByteArray Process::rawStdOut() const -{ - QTC_CHECK(d->m_stdOut.keepRawData); - return d->m_stdOut.rawData; -} - -QString Process::stdOut() const -{ - QTC_CHECK(d->m_stdOut.keepRawData); - return d->m_codec->toUnicode(d->m_stdOut.rawData); -} - -QString Process::stdErr() const -{ - QTC_CHECK(d->m_stdErr.keepRawData); - return d->m_codec->toUnicode(d->m_stdErr.rawData); -} - -QString Process::cleanedStdOut() const -{ - return Utils::normalizeNewlines(stdOut()); -} - -QString Process::cleanedStdErr() const -{ - return Utils::normalizeNewlines(stdErr()); -} - -static QStringList splitLines(const QString &text) -{ - QStringList result = text.split('\n'); - for (QString &line : result) { - if (line.endsWith('\r')) - line.chop(1); - } - return result; -} - -const QStringList Process::stdOutLines() const -{ - return splitLines(cleanedStdOut()); -} - -const QStringList Process::stdErrLines() const -{ - return splitLines(cleanedStdErr()); -} - -QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const Process &r) -{ - QDebug nsp = str.nospace(); - nsp << "QtcProcess: result=" - << int(r.d->m_result) << " ex=" << r.exitCode() << '\n' - << r.d->m_stdOut.rawData.size() << " bytes stdout, stderr=" << r.d->m_stdErr.rawData << '\n'; - return str; -} - -void ChannelBuffer::clearForRun() -{ - rawData.clear(); - codecState.reset(new QTextCodec::ConverterState); - incompleteLineBuffer.clear(); -} - -/* Check for complete lines read from the device and return them, moving the - * buffer position. */ -void ChannelBuffer::append(const QByteArray &text) -{ - if (text.isEmpty()) - return; - - if (keepRawData) - rawData += text; - - // Line-wise operation below: - if (!outputCallback) - return; - - // Convert and append the new input to the buffer of incomplete lines - incompleteLineBuffer.append(codec->toUnicode(text.constData(), text.size(), codecState.get())); - - do { - // Any completed lines in the incompleteLineBuffer? - int pos = -1; - if (emitSingleLines) { - const int posn = incompleteLineBuffer.indexOf('\n'); - const int posr = incompleteLineBuffer.indexOf('\r'); - if (posn != -1) { - if (posr != -1) { - if (posn == posr + 1) - pos = posn; // \r followed by \n -> line end, use the \n. - else - pos = qMin(posr, posn); // free floating \r and \n: Use the first one. - } else { - pos = posn; - } - } else { - pos = posr; // Make sure internal '\r' triggers a line output - } - } else { - pos = qMax(incompleteLineBuffer.lastIndexOf('\n'), - incompleteLineBuffer.lastIndexOf('\r')); - } - - if (pos == -1) - break; - - // Get completed lines and remove them from the incompleteLinesBuffer: - const QString line = Utils::normalizeNewlines(incompleteLineBuffer.left(pos + 1)); - incompleteLineBuffer = incompleteLineBuffer.mid(pos + 1); - - QTC_ASSERT(outputCallback, return); - outputCallback(line); - - if (!emitSingleLines) - break; - } while (true); -} - -void ChannelBuffer::handleRest() -{ - if (outputCallback && !incompleteLineBuffer.isEmpty()) { - outputCallback(incompleteLineBuffer); - incompleteLineBuffer.clear(); - } -} - -void Process::setTimeoutS(int timeoutS) -{ - if (timeoutS > 0) - d->m_maxHangTimerCount = qMax(2, timeoutS); - else - d->m_maxHangTimerCount = INT_MAX / 1000; -} - -int Process::timeoutS() const -{ - return d->m_maxHangTimerCount; -} - -void Process::setCodec(QTextCodec *c) -{ - QTC_ASSERT(c, return); - d->m_codec = c; -} - -void Process::setTimeOutMessageBoxEnabled(bool v) -{ - d->m_timeOutMessageBoxEnabled = v; -} - -void Process::setExitCodeInterpreter(const ExitCodeInterpreter &interpreter) -{ - d->m_exitCodeInterpreter = interpreter; -} - -void Process::setWriteData(const QByteArray &writeData) -{ - d->m_setup.m_writeData = writeData; -} - -void Process::runBlocking(EventLoopMode eventLoopMode) -{ - // Attach a dynamic property with info about blocking type - d->storeEventLoopDebugInfo(int(eventLoopMode)); - - QDateTime startTime; - static const int blockingThresholdMs = qtcEnvironmentVariableIntValue("QTC_PROCESS_THRESHOLD"); - if (blockingThresholdMs > 0 && isMainThread()) - startTime = QDateTime::currentDateTime(); - Process::start(); - - // Remove the dynamic property so that it's not reused in subseqent start() - d->storeEventLoopDebugInfo({}); - - if (eventLoopMode == EventLoopMode::On) { - // Start failure is triggered immediately if the executable cannot be found in the path. - // In this case the process is left in NotRunning state. - // Do not start the event loop in that case. - if (state() == QProcess::Starting) { - QTimer timer(this); - connect(&timer, &QTimer::timeout, d, &QtcProcessPrivate::slotTimeout); - timer.setInterval(1000); - timer.start(); -#ifdef QT_GUI_LIB - if (isMainThread()) - QApplication::setOverrideCursor(Qt::WaitCursor); -#endif - QEventLoop eventLoop(this); - QTC_ASSERT(!d->m_eventLoop, return); - d->m_eventLoop = &eventLoop; - eventLoop.exec(QEventLoop::ExcludeUserInputEvents); - d->m_eventLoop = nullptr; - timer.stop(); -#ifdef QT_GUI_LIB - if (isMainThread()) - QApplication::restoreOverrideCursor(); -#endif - } - } else { - if (!waitForStarted(d->m_maxHangTimerCount * 1000)) { - d->m_result = ProcessResult::StartFailed; - return; - } - if (!waitForFinished(d->m_maxHangTimerCount * 1000)) { - d->m_result = ProcessResult::Hang; - terminate(); - if (!waitForFinished(1000)) { - kill(); - waitForFinished(1000); - } - } - } - if (blockingThresholdMs > 0) { - const int timeDiff = startTime.msecsTo(QDateTime::currentDateTime()); - if (timeDiff > blockingThresholdMs && isMainThread()) { - qWarning() << "Blocking process " << d->m_setup.m_commandLine << "took" << timeDiff - << "ms, longer than threshold" << blockingThresholdMs; - } - } -} - -void Process::setStdOutCallback(const TextChannelCallback &callback) -{ - d->m_stdOut.outputCallback = callback; - d->m_stdOut.emitSingleLines = false; -} - -void Process::setStdOutLineCallback(const TextChannelCallback &callback) -{ - d->m_stdOut.outputCallback = callback; - d->m_stdOut.emitSingleLines = true; - d->m_stdOut.keepRawData = false; -} - -void Process::setStdErrCallback(const TextChannelCallback &callback) -{ - d->m_stdErr.outputCallback = callback; - d->m_stdErr.emitSingleLines = false; -} - -void Process::setStdErrLineCallback(const TextChannelCallback &callback) -{ - d->m_stdErr.outputCallback = callback; - d->m_stdErr.emitSingleLines = true; - d->m_stdErr.keepRawData = false; -} - -void Process::setTextChannelMode(Channel channel, TextChannelMode mode) -{ - const TextChannelCallback outputCb = [this](const QString &text) { - GuardLocker locker(d->m_guard); - emit textOnStandardOutput(text); - }; - const TextChannelCallback errorCb = [this](const QString &text) { - GuardLocker locker(d->m_guard); - emit textOnStandardError(text); - }; - const TextChannelCallback callback = (channel == Channel::Output) ? outputCb : errorCb; - ChannelBuffer *buffer = channel == Channel::Output ? &d->m_stdOut : &d->m_stdErr; - QTC_ASSERT(buffer->m_textChannelMode == TextChannelMode::Off, qWarning() - << "QtcProcess::setTextChannelMode(): Changing text channel mode for" - << (channel == Channel::Output ? "Output": "Error") - << "channel while it was previously set for this channel."); - buffer->m_textChannelMode = mode; - switch (mode) { - case TextChannelMode::Off: - buffer->outputCallback = {}; - buffer->emitSingleLines = true; - buffer->keepRawData = true; - break; - case TextChannelMode::SingleLine: - buffer->outputCallback = callback; - buffer->emitSingleLines = true; - buffer->keepRawData = false; - break; - case TextChannelMode::MultiLine: - buffer->outputCallback = callback; - buffer->emitSingleLines = false; - buffer->keepRawData = true; - break; - } -} - -TextChannelMode Process::textChannelMode(Channel channel) const -{ - ChannelBuffer *buffer = channel == Channel::Output ? &d->m_stdOut : &d->m_stdErr; - return buffer->m_textChannelMode; -} - -void QtcProcessPrivate::slotTimeout() -{ - if (!m_waitingForUser && (++m_hangTimerCount > m_maxHangTimerCount)) { - if (debug) - qDebug() << Q_FUNC_INFO << "HANG detected, killing"; - m_waitingForUser = true; - const bool terminate = !m_timeOutMessageBoxEnabled || askToKill(m_setup.m_commandLine); - m_waitingForUser = false; - if (terminate) { - q->stop(); - q->waitForFinished(); - m_result = ProcessResult::Hang; - } else { - m_hangTimerCount = 0; - } - } else { - if (debug) - qDebug() << Q_FUNC_INFO << m_hangTimerCount; - } -} - -void QtcProcessPrivate::handleStarted(qint64 processId, qint64 applicationMainThreadId) -{ - QTC_CHECK(m_state == QProcess::Starting); - m_state = QProcess::Running; - - m_processId = processId; - m_applicationMainThreadId = applicationMainThreadId; - emitGuardedSignal(&Process::started); -} - -void QtcProcessPrivate::handleReadyRead(const QByteArray &outputData, const QByteArray &errorData) -{ - QTC_CHECK(m_state == QProcess::Running); - - // TODO: check why we need this timer? - m_hangTimerCount = 0; - // TODO: store a copy of m_processChannelMode on start()? Currently we assert that state - // is NotRunning when setting the process channel mode. - - if (!outputData.isEmpty()) { - if (m_process->m_setup.m_processChannelMode == QProcess::ForwardedOutputChannel - || m_process->m_setup.m_processChannelMode == QProcess::ForwardedChannels) { - std::cout << outputData.constData() << std::flush; - } else { - m_stdOut.append(outputData); - emitGuardedSignal(&Process::readyReadStandardOutput); - } - } - if (!errorData.isEmpty()) { - if (m_process->m_setup.m_processChannelMode == QProcess::ForwardedErrorChannel - || m_process->m_setup.m_processChannelMode == QProcess::ForwardedChannels) { - std::cerr << errorData.constData() << std::flush; - } else { - m_stdErr.append(errorData); - emitGuardedSignal(&Process::readyReadStandardError); - } - } -} - -void QtcProcessPrivate::handleDone(const ProcessResultData &data) -{ - m_killTimer.stop(); - const bool wasCanceled = m_resultData.m_canceledByUser; - m_resultData = data; - m_resultData.m_canceledByUser = wasCanceled; - - switch (m_state) { - case QProcess::NotRunning: - QTC_CHECK(false); // Can't happen - break; - case QProcess::Starting: - QTC_CHECK(m_resultData.m_error == QProcess::FailedToStart); - break; - case QProcess::Running: - QTC_CHECK(m_resultData.m_error != QProcess::FailedToStart); - break; - } - m_state = QProcess::NotRunning; - - // This code (255) is being returned by QProcess when FailedToStart error occurred - if (m_resultData.m_error == QProcess::FailedToStart) - m_resultData.m_exitCode = 0xFF; - - // HACK: See QIODevice::errorString() implementation. - if (m_resultData.m_error == QProcess::UnknownError) - m_resultData.m_errorString.clear(); - else if (m_result != ProcessResult::Hang) - m_result = ProcessResult::StartFailed; - - if (debug) - qDebug() << Q_FUNC_INFO << m_resultData.m_exitCode << m_resultData.m_exitStatus; - m_hangTimerCount = 0; - - if (m_resultData.m_error != QProcess::FailedToStart) { - switch (m_resultData.m_exitStatus) { - case QProcess::NormalExit: - m_result = interpretExitCode(m_resultData.m_exitCode); - break; - case QProcess::CrashExit: - // Was hang detected before and killed? - if (m_result != ProcessResult::Hang) - m_result = ProcessResult::TerminatedAbnormally; - break; - } - } - if (m_eventLoop) - m_eventLoop->quit(); - - m_stdOut.handleRest(); - m_stdErr.handleRest(); - - emitGuardedSignal(&Process::done); - m_processId = 0; - m_applicationMainThreadId = 0; -} - -static QString blockingMessage(const QVariant &variant) -{ - if (!variant.isValid()) - return "non blocking"; - if (variant.toInt() == int(EventLoopMode::On)) - return "blocking with event loop"; - return "blocking without event loop"; -} - -void QtcProcessPrivate::setupDebugLog() -{ - if (!processLog().isDebugEnabled()) - return; - - auto now = [] { - using namespace std::chrono; - return duration_cast(system_clock::now().time_since_epoch()).count(); - }; - - connect(q, &Process::starting, this, [=] { - const quint64 msNow = now(); - setProperty(QTC_PROCESS_STARTTIME, msNow); - - static std::atomic_int startCounter = 0; - const int currentNumber = startCounter.fetch_add(1); - qCDebug(processLog).nospace().noquote() - << "Process " << currentNumber << " starting (" - << qPrintable(blockingMessage(property(QTC_PROCESS_BLOCKING_TYPE))) - << "): " << m_setup.m_commandLine.toUserOutput(); - setProperty(QTC_PROCESS_NUMBER, currentNumber); - }); - - connect(q, &Process::done, this, [=] { - if (!m_process.get()) - return; - const QVariant n = property(QTC_PROCESS_NUMBER); - if (!n.isValid()) - return; - const quint64 msNow = now(); - const quint64 msStarted = property(QTC_PROCESS_STARTTIME).toULongLong(); - const quint64 msElapsed = msNow - msStarted; - - const int number = n.toInt(); - const QString stdOut = q->cleanedStdOut(); - const QString stdErr = q->cleanedStdErr(); - qCDebug(processLog).nospace() - << "Process " << number << " finished: result=" << int(m_result) - << ", ex=" << m_resultData.m_exitCode - << ", " << stdOut.size() << " bytes stdout: " << stdOut.left(20) - << ", " << stdErr.size() << " bytes stderr: " << stdErr.left(1000) - << ", " << msElapsed << " ms elapsed"; - if (processStdoutLog().isDebugEnabled() && !stdOut.isEmpty()) - qCDebug(processStdoutLog).nospace() << "Process " << number << " sdout: " << stdOut; - if (processStderrLog().isDebugEnabled() && !stdErr.isEmpty()) - qCDebug(processStderrLog).nospace() << "Process " << number << " stderr: " << stdErr; - }); -} - -void QtcProcessPrivate::storeEventLoopDebugInfo(const QVariant &value) -{ - if (processLog().isDebugEnabled()) - setProperty(QTC_PROCESS_BLOCKING_TYPE, value); -} - -ProcessTaskAdapter::ProcessTaskAdapter() -{ - connect(task(), &Process::done, this, [this] { - emit done(task()->result() == ProcessResult::FinishedWithSuccess); - }); -} - -void ProcessTaskAdapter::start() -{ - task()->start(); -} - -} // namespace Utils - -#include "qtcprocess.moc" diff --git a/src/libs/utils/qtcprocess.h b/src/libs/utils/qtcprocess.h deleted file mode 100644 index f89b6f9b22..0000000000 --- a/src/libs/utils/qtcprocess.h +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "utils_global.h" - -#include "commandline.h" -#include "processenums.h" -#include "tasktree.h" - -#include - -QT_BEGIN_NAMESPACE -class QDebug; -class QTextCodec; -QT_END_NAMESPACE - -class tst_QtcProcess; - -namespace Utils { - -namespace Internal { class QtcProcessPrivate; } -namespace Pty { class Data; } - -class Environment; -class DeviceProcessHooks; -class ProcessInterface; -class ProcessResultData; - -class QTCREATOR_UTILS_EXPORT Process final : public QObject -{ - Q_OBJECT - -public: - explicit Process(QObject *parent = nullptr); - ~Process(); - - // ProcessInterface related - - void start(); - - void terminate(); - void kill(); - void interrupt(); - void kickoffProcess(); - void closeWriteChannel(); - void close(); - void stop(); - - QString readAllStandardOutput(); - QString readAllStandardError(); - - QByteArray readAllRawStandardOutput(); - QByteArray readAllRawStandardError(); - - qint64 write(const QString &input); - qint64 writeRaw(const QByteArray &input); - - qint64 processId() const; - qint64 applicationMainThreadId() const; - - QProcess::ProcessState state() const; - ProcessResultData resultData() const; - - int exitCode() const; - QProcess::ExitStatus exitStatus() const; - - QProcess::ProcessError error() const; - QString errorString() const; - - bool waitForStarted(int msecs = 30000); - bool waitForReadyRead(int msecs = 30000); - bool waitForFinished(int msecs = 30000); - - // ProcessSetupData related - - void setProcessImpl(ProcessImpl processImpl); - - void setPtyData(const std::optional &data); - std::optional ptyData() const; - - void setTerminalMode(TerminalMode mode); - TerminalMode terminalMode() const; - bool usesTerminal() const { return terminalMode() != TerminalMode::Off; } - - void setProcessMode(ProcessMode processMode); - ProcessMode processMode() const; - - void setEnvironment(const Environment &env); // Main process - const Environment &environment() const; - - void setControlEnvironment(const Environment &env); // Possible helper process (ssh on host etc) - const Environment &controlEnvironment() const; - - void setCommand(const CommandLine &cmdLine); - const CommandLine &commandLine() const; - - void setWorkingDirectory(const FilePath &dir); - FilePath workingDirectory() const; - - void setWriteData(const QByteArray &writeData); - - void setUseCtrlCStub(bool enabled); // release only - void setLowPriority(); - void setDisableUnixTerminal(); - void setRunAsRoot(bool on); - bool isRunAsRoot() const; - void setAbortOnMetaChars(bool abort); - - QProcess::ProcessChannelMode processChannelMode() const; - void setProcessChannelMode(QProcess::ProcessChannelMode mode); - void setStandardInputFile(const QString &inputFile); - - void setExtraData(const QString &key, const QVariant &value); - QVariant extraData(const QString &key) const; - - void setExtraData(const QVariantHash &extraData); - QVariantHash extraData() const; - - void setReaperTimeout(int msecs); - int reaperTimeout() const; - - static void setRemoteProcessHooks(const DeviceProcessHooks &hooks); - - // TODO: Some usages of this method assume that Starting phase is also a running state - // i.e. if isRunning() returns false, they assume NotRunning state, what may be an error. - bool isRunning() const; // Short for state() == QProcess::Running. - - // Other enhancements. - // These (or some of them) may be potentially moved outside of the class. - // For some we may aggregate in another public utils class (or subclass of QtcProcess)? - - // TODO: Unused currently? Should it serve as a compartment for contrary of remoteEnvironment? - static Environment systemEnvironmentForBinary(const FilePath &filePath); - - static bool startDetached(const CommandLine &cmd, const FilePath &workingDirectory = {}, - qint64 *pid = nullptr); - - // Starts the command and waits for finish. - // User input processing is enabled when EventLoopMode::On was passed. - void runBlocking(EventLoopMode eventLoopMode = EventLoopMode::Off); - - /* Timeout for hanging processes (triggers after no more output - * occurs on stderr/stdout). */ - void setTimeoutS(int timeoutS); - int timeoutS() const; - - // TODO: We should specify the purpose of the codec, e.g. setCodecForStandardChannel() - void setCodec(QTextCodec *c); - void setTimeOutMessageBoxEnabled(bool); - void setExitCodeInterpreter(const ExitCodeInterpreter &interpreter); - - void setStdOutCallback(const TextChannelCallback &callback); - void setStdOutLineCallback(const TextChannelCallback &callback); - void setStdErrCallback(const TextChannelCallback &callback); - void setStdErrLineCallback(const TextChannelCallback &callback); - - void setTextChannelMode(Channel channel, TextChannelMode mode); - TextChannelMode textChannelMode(Channel channel) const; - - bool readDataFromProcess(QByteArray *stdOut, QByteArray *stdErr, int timeoutS = 30); - - ProcessResult result() const; - - QByteArray allRawOutput() const; - QString allOutput() const; - - QByteArray rawStdOut() const; - - QString stdOut() const; // possibly with CR - QString stdErr() const; // possibly with CR - - QString cleanedStdOut() const; // with sequences of CR squashed and CR LF replaced by LF - QString cleanedStdErr() const; // with sequences of CR squashed and CR LF replaced by LF - - const QStringList stdOutLines() const; // split, CR removed - const QStringList stdErrLines() const; // split, CR removed - - QString exitMessage() const; - - QString toStandaloneCommandLine() const; - - void setCreateConsoleOnWindows(bool create); - bool createConsoleOnWindows() const; - -signals: - void starting(); // On NotRunning -> Starting state transition - void started(); // On Starting -> Running state transition - void done(); // On Starting | Running -> NotRunning state transition - void readyReadStandardOutput(); - void readyReadStandardError(); - void textOnStandardOutput(const QString &text); - void textOnStandardError(const QString &text); - -private: - friend QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const Process &r); - - friend class Internal::QtcProcessPrivate; - Internal::QtcProcessPrivate *d = nullptr; -}; - -class DeviceProcessHooks -{ -public: - std::function processImplHook; - std::function systemEnvironmentForBinary; -}; - -class QTCREATOR_UTILS_EXPORT ProcessTaskAdapter : public Tasking::TaskAdapter -{ -public: - ProcessTaskAdapter(); - void start() final; -}; - -} // namespace Utils - -QTC_DECLARE_CUSTOM_TASK(ProcessTask, Utils::ProcessTaskAdapter); diff --git a/src/libs/utils/terminalhooks.cpp b/src/libs/utils/terminalhooks.cpp index c7cab04028..b6faae765d 100644 --- a/src/libs/utils/terminalhooks.cpp +++ b/src/libs/utils/terminalhooks.cpp @@ -4,7 +4,7 @@ #include "terminalhooks.h" #include "filepath.h" -#include "qtcprocess.h" +#include "process.h" #include "terminalcommand.h" #include "terminalinterface.h" diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index ba2bc175f8..4f628616a1 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -239,6 +239,8 @@ Project { "port.h", "portlist.cpp", "portlist.h", + "process.cpp", + "process.h", "processenums.h", "processhandle.cpp", "processhandle.h", @@ -262,8 +264,6 @@ Project { "qtcassert.h", "qtcolorbutton.cpp", "qtcolorbutton.h", - "qtcprocess.cpp", - "qtcprocess.h", "qtcsettings.cpp", "qtcsettings.h", "reloadpromptutils.cpp", -- cgit v1.2.3 From 867b10a06b74545f83f42ab805cf1ca24abd8b67 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 3 May 2023 23:49:37 +0200 Subject: Remove unused includes of QFutureInterface Change-Id: I70f5e842801b628c7f9ad4d433334ce04d4e648e Reviewed-by: Eike Ziller Reviewed-by: Qt CI Bot --- src/libs/cplusplus/CppDocument.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/cplusplus/CppDocument.cpp b/src/libs/cplusplus/CppDocument.cpp index 4261649897..d28fdd50ba 100644 --- a/src/libs/cplusplus/CppDocument.cpp +++ b/src/libs/cplusplus/CppDocument.cpp @@ -26,7 +26,6 @@ #include #include -#include #include /*! -- cgit v1.2.3 From e6081aaa0a845a3cb9af1e4ff9cc8278aca2c276 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Thu, 4 May 2023 08:09:40 +0200 Subject: Utils: Add TerminalMode::Detached Change-Id: Ic36845d3469719e17f24602ce80f3e6cfc984fbf Reviewed-by: Christian Stenger --- src/libs/utils/processenums.h | 6 ++-- src/libs/utils/terminalhooks.cpp | 68 +++++++++++++++++++++++++++--------- src/libs/utils/terminalinterface.cpp | 21 +++++++++++ src/libs/utils/terminalinterface.h | 4 ++- 4 files changed, 79 insertions(+), 20 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/processenums.h b/src/libs/utils/processenums.h index 5ce782cd94..84019bae53 100644 --- a/src/libs/utils/processenums.h +++ b/src/libs/utils/processenums.h @@ -24,9 +24,9 @@ enum class ProcessImpl { enum class TerminalMode { Off, - Run, - Debug, - On = Run // Default mode for terminal set to on + Run, // Start with process stub enabled + Debug, // Start with process stub enabled and wait for debugger to attach + Detached, // Start in a terminal, without process stub. }; // Miscellaneous, not process core diff --git a/src/libs/utils/terminalhooks.cpp b/src/libs/utils/terminalhooks.cpp index b6faae765d..355aefe2c2 100644 --- a/src/libs/utils/terminalhooks.cpp +++ b/src/libs/utils/terminalhooks.cpp @@ -7,6 +7,7 @@ #include "process.h" #include "terminalcommand.h" #include "terminalinterface.h" +#include "utilstr.h" #include #include @@ -39,24 +40,20 @@ class ExternalTerminalProcessImpl final : public TerminalInterface : m_interface(interface) {} - void startStubProcess(const CommandLine &cmd, const ProcessSetupData &) override + ~ProcessStubCreator() override = default; + + expected_str startStubProcess(const CommandLine &cmd, + const ProcessSetupData &setupData) override { const TerminalCommand terminal = TerminalCommand::terminalEmulator(); - if (HostOsInfo::isWindowsHost()) { - m_terminalProcess.setCommand(cmd); - QObject::connect(&m_terminalProcess, &Process::done, this, [this] { - m_interface->onStubExited(); - }); - m_terminalProcess.setCreateConsoleOnWindows(true); - m_terminalProcess.setProcessMode(ProcessMode::Writer); - m_terminalProcess.start(); - } else if (HostOsInfo::isMacHost() && terminal.command == "Terminal.app") { + if (HostOsInfo::isMacHost() && terminal.command == "Terminal.app") { QTemporaryFile f; f.setAutoRemove(false); f.open(); f.setPermissions(QFile::ExeUser | QFile::ReadUser | QFile::WriteUser); f.write("#!/bin/sh\n"); + f.write(QString("cd %1\n").arg(setupData.m_workingDirectory.nativePath()).toUtf8()); f.write("clear\n"); f.write(QString("exec '%1' %2\n") .arg(cmd.executable().nativePath()) @@ -69,20 +66,59 @@ class ExternalTerminalProcessImpl final : public TerminalInterface = QString("tell app \"Terminal\" to do script \"'%1'; rm -f '%1'; exit\"") .arg(path); - m_terminalProcess.setCommand( + Process process; + + process.setCommand( {"osascript", {"-e", "tell app \"Terminal\" to activate", "-e", exe}}); - m_terminalProcess.runBlocking(); + process.runBlocking(); + + if (process.exitCode() != 0) { + return make_unexpected(Tr::tr("Failed to start terminal process: \"%1\"") + .arg(process.errorString())); + } + + return 0; + } + + bool detached = setupData.m_terminalMode == TerminalMode::Detached; + + Process *process = new Process(detached ? nullptr : this); + if (detached) + QObject::connect(process, &Process::done, process, &Process::deleteLater); + + QObject::connect(process, + &Process::done, + m_interface, + &ExternalTerminalProcessImpl::onStubExited); + + process->setWorkingDirectory(setupData.m_workingDirectory); + + if constexpr (HostOsInfo::isWindowsHost()) { + process->setCommand(cmd); + process->setCreateConsoleOnWindows(true); + process->setProcessMode(ProcessMode::Writer); } else { - CommandLine cmdLine = {terminal.command, {terminal.executeArgs}}; + QString extraArgsFromOptions = detached ? terminal.openArgs : terminal.executeArgs; + CommandLine cmdLine = {terminal.command, {}}; + if (!extraArgsFromOptions.isEmpty()) + cmdLine.addArgs(extraArgsFromOptions, CommandLine::Raw); cmdLine.addCommandLineAsArgs(cmd, CommandLine::Raw); + process->setCommand(cmdLine); + } - m_terminalProcess.setCommand(cmdLine); - m_terminalProcess.start(); + process->start(); + process->waitForStarted(); + if (process->error() != QProcess::UnknownError) { + return make_unexpected( + Tr::tr("Failed to start terminal process: \"%1\"").arg(process->errorString())); } + + qint64 pid = process->processId(); + + return pid; } ExternalTerminalProcessImpl *m_interface; - Process m_terminalProcess; }; public: diff --git a/src/libs/utils/terminalinterface.cpp b/src/libs/utils/terminalinterface.cpp index 7483172329..bb193b4d98 100644 --- a/src/libs/utils/terminalinterface.cpp +++ b/src/libs/utils/terminalinterface.cpp @@ -300,6 +300,25 @@ void TerminalInterface::start() if (isRunning()) return; + if (m_setup.m_terminalMode == TerminalMode::Detached) { + expected_str result; + QMetaObject::invokeMethod( + d->stubCreator, + [this, &result] { + result = d->stubCreator->startStubProcess(m_setup.m_commandLine, m_setup); + }, + d->stubCreator->thread() == QThread::currentThread() ? Qt::DirectConnection + : Qt::BlockingQueuedConnection); + + if (result) { + emit started(*result, 0); + emitFinished(0, QProcess::NormalExit); + } else { + emitError(QProcess::FailedToStart, result.error()); + } + return; + } + const expected_str result = startStubServer(); if (!result) { emitError(QProcess::FailedToStart, msgCommChannelFailed(result.error())); @@ -391,6 +410,8 @@ qint64 TerminalInterface::write(const QByteArray &data) } void TerminalInterface::sendControlSignal(ControlSignal controlSignal) { + QTC_ASSERT(m_setup.m_terminalMode != TerminalMode::Detached, return); + switch (controlSignal) { case ControlSignal::Terminate: case ControlSignal::Kill: diff --git a/src/libs/utils/terminalinterface.h b/src/libs/utils/terminalinterface.h index feb19875ba..15b3a8c69e 100644 --- a/src/libs/utils/terminalinterface.h +++ b/src/libs/utils/terminalinterface.h @@ -14,7 +14,9 @@ class TerminalInterfacePrivate; class StubCreator : public QObject { public: - virtual void startStubProcess(const CommandLine &cmd, const ProcessSetupData &setup) = 0; + virtual expected_str startStubProcess(const CommandLine &cmd, + const ProcessSetupData &setup) + = 0; }; class QTCREATOR_UTILS_EXPORT TerminalInterface : public ProcessInterface -- cgit v1.2.3 From 427640063e2d95752e95b17af13b8ed3e0e4473a Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Fri, 28 Apr 2023 14:15:27 +0200 Subject: QmlJS: Allow disabling static analyzer messages Provide settings to define a customized set of enabled static analyzer messages. Fixes: QTCREATORBUG-29095 Change-Id: Id629e383dd9e3beeef98026759ac66716dc43d23 Reviewed-by: Fabian Kosmale Reviewed-by: Leena Miettinen --- src/libs/qmljs/qmljscheck.cpp | 80 +++++++++++++++++++++++++++++++------------ src/libs/qmljs/qmljscheck.h | 8 +++-- 2 files changed, 65 insertions(+), 23 deletions(-) (limited to 'src/libs') diff --git a/src/libs/qmljs/qmljscheck.cpp b/src/libs/qmljs/qmljscheck.cpp index 8d5bcadad5..51be467387 100644 --- a/src/libs/qmljs/qmljscheck.cpp +++ b/src/libs/qmljs/qmljscheck.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -639,7 +640,46 @@ Q_GLOBAL_STATIC(UnsupportedRootObjectTypesByVisualDesigner, unsupportedRootObjec Q_GLOBAL_STATIC(UnsupportedRootObjectTypesByQmlUi, unsupportedRootObjectTypesByQmlUi) Q_GLOBAL_STATIC(UnsupportedTypesByQmlUi, unsupportedTypesByQmlUi) -Check::Check(Document::Ptr doc, const ContextPtr &context) +QList Check::defaultDisabledMessages() +{ + static const QList disabled = Utils::sorted(QList{ + HintAnonymousFunctionSpacing, + HintDeclareVarsInOneLine, + HintDeclarationsShouldBeAtStartOfFunction, + HintBinaryOperatorSpacing, + HintOneStatementPerLine, + HintExtraParentheses, + + // QmlDesigner related + WarnImperativeCodeNotEditableInVisualDesigner, + WarnUnsupportedTypeInVisualDesigner, + WarnReferenceToParentItemNotSupportedByVisualDesigner, + WarnUndefinedValueForVisualDesigner, + WarnStatesOnlyInRootItemForVisualDesigner, + ErrUnsupportedRootTypeInVisualDesigner, + ErrInvalidIdeInVisualDesigner, + + }); + return disabled; +} + +QList Check::defaultDisabledMessagesForNonQuickUi() +{ + static const QList disabled = Utils::sorted(QList{ + // QmlDesigner related + ErrUnsupportedRootTypeInQmlUi, + ErrUnsupportedTypeInQmlUi, + ErrFunctionsNotSupportedInQmlUi, + ErrBlocksNotSupportedInQmlUi, + ErrBehavioursNotSupportedInQmlUi, + ErrStatesOnlyInRootItemInQmlUi, + ErrReferenceToParentItemNotSupportedInQmlUi, + ErrDoNotMixTranslationFunctionsInQmlUi, + }); + return disabled; +} + +Check::Check(Document::Ptr doc, const ContextPtr &context, Utils::QtcSettings *qtcSettings) : _doc(doc) , _context(context) , _scopeChain(doc, _context) @@ -655,16 +695,25 @@ Check::Check(Document::Ptr doc, const ContextPtr &context) } _enabledMessages = Utils::toSet(Message::allMessageTypes()); - disableMessage(HintAnonymousFunctionSpacing); - disableMessage(HintDeclareVarsInOneLine); - disableMessage(HintDeclarationsShouldBeAtStartOfFunction); - disableMessage(HintBinaryOperatorSpacing); - disableMessage(HintOneStatementPerLine); - disableMessage(HintExtraParentheses); + if (qtcSettings && qtcSettings->value("J.QtQuick/QmlJSEditor.useCustomAnalyzer").toBool()) { + auto disabled = qtcSettings->value("J.QtQuick/QmlJSEditor.disabledMessages").toList(); + for (const QVariant &disabledNumber : disabled) + disableMessage(StaticAnalysis::Type(disabledNumber.toInt())); + + if (!isQtQuick2Ui()) { + auto disabled = qtcSettings->value("J.QtQuick/QmlJSEditor.disabledMessagesNonQuickUI").toList(); + for (const QVariant &disabledNumber : disabled) + disableMessage(StaticAnalysis::Type(disabledNumber.toInt())); + } + } else { + for (auto type : defaultDisabledMessages()) + disableMessage(type); - disableQmlDesignerChecks(); - if (!isQtQuick2Ui()) - disableQmlDesignerUiFileChecks(); + if (!isQtQuick2Ui()) { + for (auto type : defaultDisabledMessagesForNonQuickUi()) + disableMessage(type); + } + } } Check::~Check() @@ -702,17 +751,6 @@ void Check::enableQmlDesignerChecks() //## triggers too often ## check.enableMessage(StaticAnalysis::WarnUndefinedValueForVisualDesigner); } -void Check::disableQmlDesignerChecks() -{ - disableMessage(WarnImperativeCodeNotEditableInVisualDesigner); - disableMessage(WarnUnsupportedTypeInVisualDesigner); - disableMessage(WarnReferenceToParentItemNotSupportedByVisualDesigner); - disableMessage(WarnUndefinedValueForVisualDesigner); - disableMessage(WarnStatesOnlyInRootItemForVisualDesigner); - disableMessage(ErrUnsupportedRootTypeInVisualDesigner); - disableMessage(ErrInvalidIdeInVisualDesigner); -} - void Check::enableQmlDesignerUiFileChecks() { enableMessage(ErrUnsupportedRootTypeInQmlUi); diff --git a/src/libs/qmljs/qmljscheck.h b/src/libs/qmljs/qmljscheck.h index a868bbe15e..60484fc43f 100644 --- a/src/libs/qmljs/qmljscheck.h +++ b/src/libs/qmljs/qmljscheck.h @@ -12,6 +12,8 @@ #include #include +namespace Utils { class QtcSettings; } + namespace QmlJS { class Imports; @@ -22,7 +24,7 @@ class QMLJS_EXPORT Check: protected AST::Visitor public: // prefer taking root scope chain? - Check(Document::Ptr doc, const ContextPtr &context); + Check(Document::Ptr doc, const ContextPtr &context, Utils::QtcSettings *qtcSettings = nullptr); ~Check(); QList operator()(); @@ -31,11 +33,13 @@ public: void disableMessage(StaticAnalysis::Type type); void enableQmlDesignerChecks(); - void disableQmlDesignerChecks(); void enableQmlDesignerUiFileChecks(); void disableQmlDesignerUiFileChecks(); + static QList defaultDisabledMessages(); + static QList defaultDisabledMessagesForNonQuickUi(); + protected: bool preVisit(AST::Node *ast) override; void postVisit(AST::Node *ast) override; -- cgit v1.2.3 From 13dd6788349857452c8e8ace9d3ae2a55fed9468 Mon Sep 17 00:00:00 2001 From: Cristian Adam Date: Thu, 4 May 2023 15:07:18 +0200 Subject: Utils: Fix MinGW build Amends a0f6e8dc04291138ec2305fe7c02a0a460f57fac The issue is that pthread.h is including , which ends up to utils/process.h since it's first in path. Change-Id: I525384083b6952aded4b77c29d00d85f084c60f9 Reviewed-by: Jarek Kobus --- src/libs/utils/process.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/process.h b/src/libs/utils/process.h index f89b6f9b22..f11f3c3c41 100644 --- a/src/libs/utils/process.h +++ b/src/libs/utils/process.h @@ -1,7 +1,11 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#pragma once +#if defined(Q_CC_MINGW) && defined(WIN_PTHREADS_H) && !defined(_INC_PROCESS) + // Arrived here via which wants to include + #include_next +#elif !defined(UTILS_PROCESS_H) +#define UTILS_PROCESS_H #include "utils_global.h" @@ -217,3 +221,5 @@ public: } // namespace Utils QTC_DECLARE_CUSTOM_TASK(ProcessTask, Utils::ProcessTaskAdapter); + +#endif // UTILS_PROCESS_H -- cgit v1.2.3 From 328d4c72954736242138d5092980e65873a2a590 Mon Sep 17 00:00:00 2001 From: hjk Date: Wed, 3 May 2023 07:53:28 +0200 Subject: Layouting: Code cosmetics Change-Id: I0eea49bc5c39679ca66f73616a98e91546e493c2 Reviewed-by: Christian Stenger --- src/libs/utils/layoutbuilder.cpp | 15 ++-------- src/libs/utils/layoutbuilder.h | 65 ++++++++++++++++++---------------------- 2 files changed, 32 insertions(+), 48 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index 3324e9c4f6..33046a07f7 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -323,7 +323,7 @@ void doAddWidget(LayoutBuilder &builder, QWidget *widget) A LayoutBuilder instance is typically used locally within a function and never stored. - \sa addItem(), addItems(), addRow(), finishRow() + \sa addItem(), addItems(), addRow() */ @@ -351,24 +351,15 @@ void LayoutBuilder::addRow(const LayoutItems &items) addItems(items); } -/*! - Instructs a layout builder to finish the current row. - This is implicitly called by LayoutBuilder's destructor. - */ -void LayoutItem::finishRow() -{ - addItem(br); -} - /*! This starts a new row containing \a items. The row can be further extended by other items using \c addItem() or \c addItems(). - \sa finishRow(), addItem(), addItems() + \sa addItem(), addItems() */ void LayoutItem::addRow(const LayoutItems &items) { - finishRow(); + addItem(br); addItems(items); } diff --git a/src/libs/utils/layoutbuilder.h b/src/libs/utils/layoutbuilder.h index 1a9c3439bc..f2efeaf2fe 100644 --- a/src/libs/utils/layoutbuilder.h +++ b/src/libs/utils/layoutbuilder.h @@ -24,48 +24,15 @@ QT_END_NAMESPACE namespace Layouting { -class LayoutBuilder; -class LayoutItem; -class Span; - -// Special items - -class QTCREATOR_UTILS_EXPORT Space -{ -public: - explicit Space(int space) : space(space) {} - const int space; -}; - -class QTCREATOR_UTILS_EXPORT Stretch -{ -public: - explicit Stretch(int stretch = 1) : stretch(stretch) {} - const int stretch; -}; - - // LayoutItem +class LayoutBuilder; +class LayoutItem; using LayoutItems = QList; -void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const std::function &t); -void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, QWidget *t); -void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, QLayout *t); -void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, LayoutItem(*t)()); -void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const QString &t); -void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const Span &t); -void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const Space &t); -void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const Stretch &t); - class QTCREATOR_UTILS_EXPORT LayoutItem { public: - enum class AlignmentType { - DefaultAlignment, - AlignAsFormLabel, - }; - using Setter = std::function; LayoutItem(); @@ -88,7 +55,6 @@ public: void addItem(const LayoutItem &item); void addItems(const LayoutItems &items); void addRow(const LayoutItems &items); - void finishRow(); std::function onAdd; std::function onExit; @@ -96,6 +62,22 @@ public: LayoutItems subItems; }; +// Special items + +class QTCREATOR_UTILS_EXPORT Space +{ +public: + explicit Space(int space) : space(space) {} + const int space; +}; + +class QTCREATOR_UTILS_EXPORT Stretch +{ +public: + explicit Stretch(int stretch = 1) : stretch(stretch) {} + const int stretch; +}; + class QTCREATOR_UTILS_EXPORT Span { public: @@ -181,6 +163,17 @@ public: int exec(int &argc, char *argv[]); }; + +void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const std::function &t); +void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, QWidget *t); +void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, QLayout *t); +void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, LayoutItem(*t)()); +void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const QString &t); +void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const Span &t); +void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const Space &t); +void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const Stretch &t); + + // "Singletons" QTCREATOR_UTILS_EXPORT LayoutItem br(); -- cgit v1.2.3 From 84b2862058e477736bc0b96b05e882ab00f5b407 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Thu, 4 May 2023 09:49:32 +0200 Subject: Utils: Improve CheckBox layouting This is not correct either, but the vast majority of Checkboxes is not added to Forms, so the !form case is a better fallback. Change-Id: I1375b3e23138fb6d881b2331ecf1d0f3a4f5431b Reviewed-by: hjk --- src/libs/utils/aspects.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index 94aee75214..dc1888279a 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -1406,8 +1406,9 @@ void BoolAspect::addToLayout(Layouting::LayoutItem &parent) break; case LabelPlacement::AtCheckBox: { d->m_checkBox->setText(labelText()); -// if (parent.isForm()) FIXME - parent.addItem(createSubWidget()); + // FIXME: + //if (parent.isForm()) + // parent.addItem(createSubWidget()); parent.addItem(d->m_checkBox.data()); break; } -- cgit v1.2.3 From 495f5f006f3fe14c7b472ee6a29925c0aa4c640c Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Fri, 14 Apr 2023 15:43:31 +0200 Subject: Utils: Add toolbarStyle to StyleHelper Task-number: QTCREATORBUG-29054 Change-Id: I01572eb7bdd267ea6aadb9530afc52b3c7834382 Reviewed-by: hjk --- src/libs/utils/stylehelper.cpp | 16 ++++++++++++++++ src/libs/utils/stylehelper.h | 11 ++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/stylehelper.cpp b/src/libs/utils/stylehelper.cpp index f27fb2f8fc..690c04b9df 100644 --- a/src/libs/utils/stylehelper.cpp +++ b/src/libs/utils/stylehelper.cpp @@ -58,6 +58,21 @@ QColor StyleHelper::alphaBlendedColors(const QColor &colorA, const QColor &color ); } +int StyleHelper::navigationWidgetHeight() +{ + return m_toolbarStyle == ToolbarStyleCompact ? 24 : 30; +} + +void StyleHelper::setToolbarStyle(ToolbarStyle style) +{ + m_toolbarStyle = style; +} + +StyleHelper::ToolbarStyle StyleHelper::toolbarStyle() +{ + return m_toolbarStyle; +} + qreal StyleHelper::sidebarFontSize() { return HostOsInfo::isMacHost() ? 10 : 7.5; @@ -89,6 +104,7 @@ QColor StyleHelper::panelTextColor(bool lightColored) return Qt::black; } +StyleHelper::ToolbarStyle StyleHelper::m_toolbarStyle = StyleHelper::defaultToolbarStyle; // Invalid by default, setBaseColor needs to be called at least once QColor StyleHelper::m_baseColor; QColor StyleHelper::m_requestedBaseColor; diff --git a/src/libs/utils/stylehelper.h b/src/libs/utils/stylehelper.h index c7766b0ffe..11fa8a0389 100644 --- a/src/libs/utils/stylehelper.h +++ b/src/libs/utils/stylehelper.h @@ -25,8 +25,16 @@ public: static const unsigned int DEFAULT_BASE_COLOR = 0x666666; static const int progressFadeAnimationDuration = 600; + enum ToolbarStyle { + ToolbarStyleCompact, + ToolbarStyleRelaxed, + }; + // Height of the project explorer navigation bar - static int navigationWidgetHeight() { return 24; } + static int navigationWidgetHeight(); + static void setToolbarStyle(ToolbarStyle style); + static ToolbarStyle toolbarStyle(); + static constexpr ToolbarStyle defaultToolbarStyle = ToolbarStyleCompact; static qreal sidebarFontSize(); static QPalette sidebarFontPalette(const QPalette &original); @@ -122,6 +130,7 @@ public: static QColor ensureReadableOn(const QColor &background, const QColor &desiredForeground); private: + static ToolbarStyle m_toolbarStyle; static QColor m_baseColor; static QColor m_requestedBaseColor; }; -- cgit v1.2.3 From 02637390fa48e69ba5e078cd053bb96484308bef Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Fri, 21 Apr 2023 14:49:26 +0200 Subject: ManhattanStyle: Draw round rectangles in "Relaxed toolbar style" Task-number: QTCREATORBUG-29054 Change-Id: Ic06ddfec87fd9b43c88b3a7dfb9946c3a8b22c60 Reviewed-by: Reviewed-by: hjk --- src/libs/utils/stylehelper.cpp | 17 +++++++++++++++++ src/libs/utils/stylehelper.h | 2 ++ 2 files changed, 19 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/stylehelper.cpp b/src/libs/utils/stylehelper.cpp index 690c04b9df..38a1b17931 100644 --- a/src/libs/utils/stylehelper.cpp +++ b/src/libs/utils/stylehelper.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -455,6 +456,22 @@ void StyleHelper::drawMinimalArrow(QStyle::PrimitiveElement element, QPainter *p painter->drawPixmap(xOffset, yOffset, pixmap); } +void StyleHelper::drawPanelBgRect(QPainter *painter, const QRectF &rect, const QBrush &brush) +{ + if (toolbarStyle() == ToolbarStyleCompact) { + painter->fillRect(rect.toRect(), brush); + } else { + constexpr int margin = 2; + constexpr int radius = 5; + QPainterPath path; + path.addRoundedRect(rect.adjusted(margin, margin, -margin, -margin), radius, radius); + painter->save(); + painter->setRenderHint(QPainter::Antialiasing); + painter->fillPath(path, brush); + painter->restore(); + } +} + void StyleHelper::menuGradient(QPainter *painter, const QRect &spanRect, const QRect &clipRect) { if (StyleHelper::usePixmapCache()) { diff --git a/src/libs/utils/stylehelper.h b/src/libs/utils/stylehelper.h index 11fa8a0389..8ee91978b7 100644 --- a/src/libs/utils/stylehelper.h +++ b/src/libs/utils/stylehelper.h @@ -65,6 +65,8 @@ public: static void drawArrow(QStyle::PrimitiveElement element, QPainter *painter, const QStyleOption *option); static void drawMinimalArrow(QStyle::PrimitiveElement element, QPainter *painter, const QStyleOption *option); + static void drawPanelBgRect(QPainter *painter, const QRectF &rect, const QBrush &brush); + // Gradients used for panels static void horizontalGradient(QPainter *painter, const QRect &spanRect, const QRect &clipRect, bool lightColored = false); static void verticalGradient(QPainter *painter, const QRect &spanRect, const QRect &clipRect, bool lightColored = false); -- cgit v1.2.3 From 09a58ddd12b60455a4e3b28fd7e5091176b71892 Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Mon, 24 Apr 2023 18:24:11 +0200 Subject: Tracing: Follow the active toolbar style This makes sure that the toolbar and toolbuttons in the trace resemble the look of the widget based UI in compact and relaxed toolbar style. This comes with the restriction that an already open tracing scene does only adapt to that change after an IDE restart. Task-number: QTCREATORBUG-29082 Change-Id: I6422227256d8e13658ff5565ae640e15c5e61229 Reviewed-by: hjk --- src/libs/tracing/qml/ImageToolButton.qml | 6 ++++-- src/libs/tracing/qml/MainView.qml | 2 +- src/libs/tracing/qml/RangeDetails.qml | 2 +- src/libs/tracing/qml/TimeDisplay.qml | 2 +- src/libs/tracing/timelinetheme.cpp | 13 ++++++++++++- src/libs/tracing/timelinetheme.h | 2 ++ 6 files changed, 21 insertions(+), 6 deletions(-) (limited to 'src/libs') diff --git a/src/libs/tracing/qml/ImageToolButton.qml b/src/libs/tracing/qml/ImageToolButton.qml index 2d0231494b..45ca12b899 100644 --- a/src/libs/tracing/qml/ImageToolButton.qml +++ b/src/libs/tracing/qml/ImageToolButton.qml @@ -22,10 +22,12 @@ ToolButton { smooth: false } - background: Rectangle { + background: PaddedRectangle { + padding: Theme.compactToolbar() ? 0 : 3 + radius: Theme.compactToolbar() ? 0 : 5 color: (parent.checked || parent.pressed) ? Theme.color(Theme.FancyToolButtonSelectedColor) - : parent.hovered + : (parent.hovered && parent.enabled) ? Theme.color(Theme.FancyToolButtonHoverColor) : "#00000000" } diff --git a/src/libs/tracing/qml/MainView.qml b/src/libs/tracing/qml/MainView.qml index 5aa4e835a3..052ec4aa76 100644 --- a/src/libs/tracing/qml/MainView.qml +++ b/src/libs/tracing/qml/MainView.qml @@ -156,7 +156,7 @@ Rectangle { anchors.top: parent.top anchors.left: parent.left width: 150 - height: 24 + height: Theme.toolBarHeight() onZoomControlChanged: zoomSliderToolBar.visible = !zoomSliderToolBar.visible onJumpToNext: { var next = timelineModelAggregator.nextItem(root.selectedModel, root.selectedItem, diff --git a/src/libs/tracing/qml/RangeDetails.qml b/src/libs/tracing/qml/RangeDetails.qml index 8c0e64f25c..9ce3e996d9 100644 --- a/src/libs/tracing/qml/RangeDetails.qml +++ b/src/libs/tracing/qml/RangeDetails.qml @@ -9,7 +9,7 @@ import QtCreator.Tracing Item { id: rangeDetails - property real titleBarHeight: 20 + property real titleBarHeight: Theme.toolBarHeight() / 1.2 property real borderWidth: 1 property real outerMargin: 10 property real innerMargin: 5 diff --git a/src/libs/tracing/qml/TimeDisplay.qml b/src/libs/tracing/qml/TimeDisplay.qml index ffcbea2e84..64cb0474cf 100644 --- a/src/libs/tracing/qml/TimeDisplay.qml +++ b/src/libs/tracing/qml/TimeDisplay.qml @@ -11,7 +11,7 @@ Item { property double rangeDuration property int textMargin: 5 - property int labelsHeight: 24 + property int labelsHeight: Theme.toolBarHeight() property int fontSize: 8 property int initialBlockLength: 120 property double spacing: width / rangeDuration diff --git a/src/libs/tracing/timelinetheme.cpp b/src/libs/tracing/timelinetheme.cpp index df02cb6396..41c0c44b4f 100644 --- a/src/libs/tracing/timelinetheme.cpp +++ b/src/libs/tracing/timelinetheme.cpp @@ -5,8 +5,9 @@ #include #include -#include +#include #include +#include #include #include @@ -92,4 +93,14 @@ void TimelineTheme::setupTheme(QQmlEngine *engine) engine->addImageProvider(QLatin1String("icons"), new TimelineImageIconProvider); } +bool TimelineTheme::compactToolbar() const +{ + return StyleHelper::toolbarStyle() == StyleHelper::ToolbarStyleCompact; +} + +int TimelineTheme::toolBarHeight() const +{ + return StyleHelper::navigationWidgetHeight(); +} + } // namespace Timeline diff --git a/src/libs/tracing/timelinetheme.h b/src/libs/tracing/timelinetheme.h index ebca62447d..f15eccbdaf 100644 --- a/src/libs/tracing/timelinetheme.h +++ b/src/libs/tracing/timelinetheme.h @@ -22,6 +22,8 @@ public: explicit TimelineTheme(QObject *parent = nullptr); static void setupTheme(QQmlEngine* engine); + Q_INVOKABLE bool compactToolbar() const; + Q_INVOKABLE int toolBarHeight() const; }; } // namespace Timeline -- cgit v1.2.3 From 1c34fc7bca8fc27df28d0459f668a177d1c29c4a Mon Sep 17 00:00:00 2001 From: hjk Date: Fri, 5 May 2023 10:45:39 +0200 Subject: Layouting: Fix advancing by empty cells Change-Id: I404e80e98a8fa53e174a8a372b82e17e8187a52c Reviewed-by: Eike Ziller Reviewed-by: --- src/libs/utils/layoutbuilder.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index 33046a07f7..bda8bcab8e 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -477,7 +477,13 @@ LayoutItem br() LayoutItem empty() { - return {}; + LayoutItem item; + item.onAdd = [](LayoutBuilder &builder) { + ResultItem ri; + ri.span = 1; + builder.stack.last().pendingItems.append(ResultItem()); + }; + return item; } LayoutItem hr() -- cgit v1.2.3 From abce79939a5f314ea68b63aac48444f680cc414b Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Thu, 4 May 2023 17:20:29 +0200 Subject: Tests: Rename tst_QtcProcess -> tst_Process Follows 470c95c94be58905bc3202d3b58175add5f576fa Change-Id: Ie26b5677d28e645ab27aeebf5976b5507385716a Reviewed-by: hjk --- src/libs/utils/process.h | 2 -- 1 file changed, 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/process.h b/src/libs/utils/process.h index f11f3c3c41..b79cac2d39 100644 --- a/src/libs/utils/process.h +++ b/src/libs/utils/process.h @@ -20,8 +20,6 @@ class QDebug; class QTextCodec; QT_END_NAMESPACE -class tst_QtcProcess; - namespace Utils { namespace Internal { class QtcProcessPrivate; } -- cgit v1.2.3 From 652e99830fe00e32b9ea774920a34fde7eb79698 Mon Sep 17 00:00:00 2001 From: hjk Date: Thu, 4 May 2023 08:55:10 +0200 Subject: Layouting: Add some support for spin boxes Change-Id: I5eff963cf605f5239a96daee924e91b2c170f506 Reviewed-by: Alessandro Portale --- src/libs/utils/layoutbuilder.cpp | 55 ++++++++++++++++++++++++++++++++-------- src/libs/utils/layoutbuilder.h | 13 ++++++++++ 2 files changed, 57 insertions(+), 11 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index bda8bcab8e..a4edf7dfab 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -559,6 +560,12 @@ PushButton::PushButton(std::initializer_list items) setupWidget(this); } +SpinBox::SpinBox(std::initializer_list items) +{ + this->subItems = items; + setupWidget(this); +} + TextEdit::TextEdit(std::initializer_list items) { this->subItems = items; @@ -630,17 +637,6 @@ LayoutItem title(const QString &title) }; } -LayoutItem onClicked(const std::function &func, QObject *guard) -{ - return [func, guard](QObject *target) { - if (auto button = qobject_cast(target)) { - QObject::connect(button, &QAbstractButton::clicked, guard ? guard : target, func); - } else { - QTC_CHECK(false); - } - }; -} - LayoutItem text(const QString &text) { return [text](QObject *target) { @@ -698,6 +694,43 @@ LayoutItem columnStretch(int column, int stretch) }; } +// Signals + +LayoutItem onClicked(const std::function &func, QObject *guard) +{ + return [func, guard](QObject *target) { + if (auto button = qobject_cast(target)) { + QObject::connect(button, &QAbstractButton::clicked, guard ? guard : target, func); + } else { + QTC_CHECK(false); + } + }; +} + +LayoutItem onTextChanged(const std::function &func, QObject *guard) +{ + return [func, guard](QObject *target) { + if (auto button = qobject_cast(target)) { + QObject::connect(button, &QSpinBox::textChanged, guard ? guard : target, func); + } else { + QTC_CHECK(false); + } + }; +} + +LayoutItem onValueChanged(const std::function &func, QObject *guard) +{ + return [func, guard](QObject *target) { + if (auto button = qobject_cast(target)) { + QObject::connect(button, &QSpinBox::valueChanged, guard ? guard : target, func); + } else { + QTC_CHECK(false); + } + }; +} + +// Convenience + QWidget *createHr(QWidget *parent) { auto frame = new QFrame(parent); diff --git a/src/libs/utils/layoutbuilder.h b/src/libs/utils/layoutbuilder.h index f2efeaf2fe..cac9fad372 100644 --- a/src/libs/utils/layoutbuilder.h +++ b/src/libs/utils/layoutbuilder.h @@ -143,6 +143,12 @@ public: PushButton(std::initializer_list items); }; +class QTCREATOR_UTILS_EXPORT SpinBox : public LayoutItem +{ +public: + SpinBox(std::initializer_list items); +}; + class QTCREATOR_UTILS_EXPORT Splitter : public LayoutItem { public: @@ -193,8 +199,15 @@ QTCREATOR_UTILS_EXPORT LayoutItem resize(int, int); QTCREATOR_UTILS_EXPORT LayoutItem columnStretch(int column, int stretch); QTCREATOR_UTILS_EXPORT LayoutItem spacing(int); QTCREATOR_UTILS_EXPORT LayoutItem windowTitle(const QString &windowTitle); + +// "Signals" + QTCREATOR_UTILS_EXPORT LayoutItem onClicked(const std::function &, QObject *guard = nullptr); +QTCREATOR_UTILS_EXPORT LayoutItem onTextChanged(const std::function &, + QObject *guard = nullptr); +QTCREATOR_UTILS_EXPORT LayoutItem onValueChanged(const std::function &, + QObject *guard = nullptr); // Convenience -- cgit v1.2.3 From fb50e35db94a0c130c538432c60f0bba6a7bf07a Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Thu, 4 May 2023 21:12:22 +0200 Subject: FilePath: Fix hash function For better performance, include also the scheme and host in qHash calculation. Change-Id: I2a69a128597429b88a71943d248ce038b49285f2 Reviewed-by: Qt CI Bot Reviewed-by: hjk --- src/libs/utils/filepath.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 0fce0a9e4c..de70d4bf9b 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -2072,9 +2072,10 @@ QTCREATOR_UTILS_EXPORT bool operator>=(const FilePath &first, const FilePath &se QTCREATOR_UTILS_EXPORT size_t qHash(const FilePath &filePath, uint seed) { - if (filePath.caseSensitivity() == Qt::CaseInsensitive) - return qHash(filePath.path().toCaseFolded(), seed); - return qHash(filePath.path(), seed); + if (filePath.caseSensitivity() == Qt::CaseSensitive) + return qHash(QStringView(filePath.m_data), seed); + const size_t schemeHostHash = qHash(QStringView(filePath.m_data).mid(filePath.m_pathLen), seed); + return qHash(filePath.path().toCaseFolded(), seed) ^ schemeHostHash; } QTCREATOR_UTILS_EXPORT size_t qHash(const FilePath &filePath) -- cgit v1.2.3 From 62f3d29be4e57324d04fa7e0487f0330d684bd1c Mon Sep 17 00:00:00 2001 From: hjk Date: Fri, 5 May 2023 15:19:58 +0200 Subject: Layouting: Introduce Ids for Items Intenally just wrapping a 'bindTo' result, but less trigger potential for pointer related peladophobia Change-Id: I25171a2675fb0474ce97c04552ac1cf5ffd6ee56 Reviewed-by: Alessandro Portale Reviewed-by: hjk --- src/libs/utils/layoutbuilder.cpp | 13 +++++++++++++ src/libs/utils/layoutbuilder.h | 18 +++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index a4edf7dfab..51b94b582d 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -694,6 +694,19 @@ LayoutItem columnStretch(int column, int stretch) }; } +// Id based setters + +LayoutItem id(Id &out) +{ + return [&out](QObject *target) { out.ob = target; }; +} + +void setText(Id id, const QString &text) +{ + if (auto textEdit = qobject_cast(id.ob)) + textEdit->setText(text); +} + // Signals LayoutItem onClicked(const std::function &func, QObject *guard) diff --git a/src/libs/utils/layoutbuilder.h b/src/libs/utils/layoutbuilder.h index cac9fad372..3898dd5982 100644 --- a/src/libs/utils/layoutbuilder.h +++ b/src/libs/utils/layoutbuilder.h @@ -190,7 +190,7 @@ QTCREATOR_UTILS_EXPORT LayoutItem noMargin(); QTCREATOR_UTILS_EXPORT LayoutItem normalMargin(); QTCREATOR_UTILS_EXPORT LayoutItem withFormAlignment(); -// "Properties" +// "Setters" QTCREATOR_UTILS_EXPORT LayoutItem title(const QString &title); QTCREATOR_UTILS_EXPORT LayoutItem text(const QString &text); @@ -200,6 +200,19 @@ QTCREATOR_UTILS_EXPORT LayoutItem columnStretch(int column, int stretch); QTCREATOR_UTILS_EXPORT LayoutItem spacing(int); QTCREATOR_UTILS_EXPORT LayoutItem windowTitle(const QString &windowTitle); +// "Getters" + +class Id +{ +public: + QObject *ob = nullptr; +}; + +QTCREATOR_UTILS_EXPORT LayoutItem id(Id &out); + +QTCREATOR_UTILS_EXPORT void setText(Id id, const QString &text); + + // "Signals" QTCREATOR_UTILS_EXPORT LayoutItem onClicked(const std::function &, @@ -209,6 +222,8 @@ QTCREATOR_UTILS_EXPORT LayoutItem onTextChanged(const std::function &, QObject *guard = nullptr); +QTCREATOR_UTILS_EXPORT LayoutItem onTextChanged(Id &id, QVariant(*sig)(QObject *)); + // Convenience QTCREATOR_UTILS_EXPORT QWidget *createHr(QWidget *parent = nullptr); @@ -219,4 +234,5 @@ LayoutItem bindTo(T **out) return [out](QObject *target) { *out = qobject_cast(target); }; } + } // Layouting -- cgit v1.2.3 From 1e1befd9eb608322bab41ee214c97d65af65066e Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Thu, 6 Apr 2023 18:31:47 +0200 Subject: Proliferate pathListSeparator() Change-Id: I546107af6a88ad5901659a0a64485e4ebca3a164 Reviewed-by: Reviewed-by: hjk --- src/libs/utils/environment.cpp | 4 +--- src/libs/utils/namevalueitem.cpp | 12 ++---------- 2 files changed, 3 insertions(+), 13 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/environment.cpp b/src/libs/utils/environment.cpp index cacd60fcd6..977e63adb0 100644 --- a/src/libs/utils/environment.cpp +++ b/src/libs/utils/environment.cpp @@ -155,14 +155,13 @@ void Environment::prependOrSet(const QString &key, const QString &value, const Q void Environment::prependOrSetLibrarySearchPath(const FilePath &value) { QTC_CHECK(value.osType() == osType()); + const QChar sep = OsSpecificAspects::pathListSeparator(osType()); switch (osType()) { case OsTypeWindows: { - const QChar sep = ';'; prependOrSet("PATH", value.nativePath(), sep); break; } case OsTypeMac: { - const QChar sep = ':'; const QString nativeValue = value.nativePath(); prependOrSet("DYLD_LIBRARY_PATH", nativeValue, sep); prependOrSet("DYLD_FRAMEWORK_PATH", nativeValue, sep); @@ -170,7 +169,6 @@ void Environment::prependOrSetLibrarySearchPath(const FilePath &value) } case OsTypeLinux: case OsTypeOtherUnix: { - const QChar sep = ':'; prependOrSet("LD_LIBRARY_PATH", value.nativePath(), sep); break; } diff --git a/src/libs/utils/namevalueitem.cpp b/src/libs/utils/namevalueitem.cpp index 5fd3bc39eb..adc7ff5afc 100644 --- a/src/libs/utils/namevalueitem.cpp +++ b/src/libs/utils/namevalueitem.cpp @@ -118,14 +118,6 @@ static QString expand(const NameValueDictionary *dictionary, QString value) return value; } -enum : char { -#ifdef Q_OS_WIN - pathSepC = ';' -#else - pathSepC = ':' -#endif -}; - void NameValueItem::apply(NameValueDictionary *dictionary, Operation op) const { switch (op) { @@ -142,7 +134,7 @@ void NameValueItem::apply(NameValueDictionary *dictionary, Operation op) const const NameValueDictionary::const_iterator it = dictionary->constFind(name); if (it != dictionary->constEnd()) { QString v = dictionary->value(it); - const QChar pathSep{QLatin1Char(pathSepC)}; + const QChar pathSep = HostOsInfo::pathListSeparator(); int sepCount = 0; if (v.startsWith(pathSep)) ++sepCount; @@ -162,7 +154,7 @@ void NameValueItem::apply(NameValueDictionary *dictionary, Operation op) const const NameValueDictionary::const_iterator it = dictionary->constFind(name); if (it != dictionary->constEnd()) { QString v = dictionary->value(it); - const QChar pathSep{QLatin1Char(pathSepC)}; + const QChar pathSep = HostOsInfo::pathListSeparator(); int sepCount = 0; if (v.endsWith(pathSep)) ++sepCount; -- cgit v1.2.3 From ca1e0dae56de06778fd4b91f331ec1c0bb87df39 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Thu, 4 May 2023 14:49:00 +0200 Subject: Utils: Introduce QTC_SCOPED_TIMER() QTC_SCOPED_TIMER() is a handly debug tool that measures the time spent in a block of code. It starts measuring the time when the line where it was placed is reached, and stops measuring the time when the current invocation exits the block in which it was placed (i.e. when QTC_SCOPED_TIMER() goes out of scope). The QTC_SCOPED_TIMER does two printouts: 1. When it starts - it prints the current time, filename and line number. 2. When it ends - it prints the current time, filename, line number and the timeout in ms. The QTC_SCOPED_TIMER() was added into qtcassert.h file on purpose, as this file is included already in most of the codebase. In this way, when it needs to be used, it's enough to add a QTC_SCOPED_TIMER() without adding extra #include. Example use case, after adding the "QTC_SCOPED_TIMER()" into ProjectExplorerPlugin::initialize() as a first line: SCOPED TIMER [14:46:57.959] in [_long_path_here_]/ projectexplorer.cpp:823 started SCOPED TIMER [14:46:58.087] in [_long_path_here_]/ projectexplorer.cpp:823 stopped with timeout: 127ms Change-Id: Iaed3f297c8aeb6e90dd9909e76fc9933599a39b6 Reviewed-by: hjk Reviewed-by: Qt CI Bot --- src/libs/utils/qtcassert.cpp | 28 ++++++++++++++++++++++++++++ src/libs/utils/qtcassert.h | 20 ++++++++++++++++++++ 2 files changed, 48 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/qtcassert.cpp b/src/libs/utils/qtcassert.cpp index 78baa41a4a..019c223ef7 100644 --- a/src/libs/utils/qtcassert.cpp +++ b/src/libs/utils/qtcassert.cpp @@ -8,6 +8,8 @@ #include #include +#include + #if defined(Q_OS_UNIX) #include #include @@ -130,4 +132,30 @@ void writeAssertLocation(const char *msg) dumpBacktrace(maxdepth); } +using namespace std::chrono; + +class ScopedTimerPrivate +{ +public: + const char *m_fileName = nullptr; + const int m_line = 0; + const time_point m_start = system_clock::now(); +}; + +ScopedTimer::ScopedTimer(const char *fileName, int line) + : d(new ScopedTimerPrivate{fileName, line}) +{ + const QByteArray time = QTime::currentTime().toString(Qt::ISODateWithMs).toLatin1(); + qDebug("SCOPED TIMER [%s] in %s:%d started", time.data(), d->m_fileName, d->m_line); +} + +ScopedTimer::~ScopedTimer() +{ + const auto end = system_clock::now(); + const auto elapsed = duration_cast(end - d->m_start); + const QByteArray time = QTime::currentTime().toString(Qt::ISODateWithMs).toLatin1(); + qDebug("SCOPED TIMER [%s] in %s:%d stopped with timeout: %ldms", + time.data(), d->m_fileName, d->m_line, elapsed.count()); +} + } // namespace Utils diff --git a/src/libs/utils/qtcassert.h b/src/libs/utils/qtcassert.h index 8f6b2ec5b8..aab7ab93d0 100644 --- a/src/libs/utils/qtcassert.h +++ b/src/libs/utils/qtcassert.h @@ -5,9 +5,25 @@ #include "utils_global.h" +#include + namespace Utils { + QTCREATOR_UTILS_EXPORT void writeAssertLocation(const char *msg); QTCREATOR_UTILS_EXPORT void dumpBacktrace(int maxdepth); + +class ScopedTimerPrivate; + +class QTCREATOR_UTILS_EXPORT ScopedTimer +{ +public: + ScopedTimer(const char *fileName, int line); + ~ScopedTimer(); + +private: + std::unique_ptr d; +}; + } // Utils #define QTC_ASSERT_STRINGIFY_HELPER(x) #x @@ -21,3 +37,7 @@ QTCREATOR_UTILS_EXPORT void dumpBacktrace(int maxdepth); #define QTC_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QTC_ASSERT_STRING(#cond); action; } do {} while (0) #define QTC_CHECK(cond) if (Q_LIKELY(cond)) {} else { QTC_ASSERT_STRING(#cond); } do {} while (0) #define QTC_GUARD(cond) ((Q_LIKELY(cond)) ? true : (QTC_ASSERT_STRING(#cond), false)) + +#define QTC_CONCAT_HELPER(x, y) x ## y +#define QTC_CONCAT(x, y) QTC_CONCAT_HELPER(x, y) +#define QTC_SCOPED_TIMER() ::Utils::ScopedTimer QTC_CONCAT(_qtc_scoped_timer_, __LINE__)(__FILE__, __LINE__) -- cgit v1.2.3 From 6aa02fe8043d23e57f1f1dc77fa971797a62d5ea Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Thu, 4 May 2023 09:54:24 +0200 Subject: Utils: Combine startStubProcess parameters Change-Id: Ic0515a3864687494bd1e280a82b91a5bafef46b1 Reviewed-by: Christian Stenger --- src/libs/utils/terminalhooks.cpp | 11 +++++------ src/libs/utils/terminalinterface.cpp | 9 +++++---- src/libs/utils/terminalinterface.h | 4 +--- 3 files changed, 11 insertions(+), 13 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/terminalhooks.cpp b/src/libs/utils/terminalhooks.cpp index 355aefe2c2..07ebbd98d2 100644 --- a/src/libs/utils/terminalhooks.cpp +++ b/src/libs/utils/terminalhooks.cpp @@ -42,8 +42,7 @@ class ExternalTerminalProcessImpl final : public TerminalInterface ~ProcessStubCreator() override = default; - expected_str startStubProcess(const CommandLine &cmd, - const ProcessSetupData &setupData) override + expected_str startStubProcess(const ProcessSetupData &setupData) override { const TerminalCommand terminal = TerminalCommand::terminalEmulator(); @@ -56,8 +55,8 @@ class ExternalTerminalProcessImpl final : public TerminalInterface f.write(QString("cd %1\n").arg(setupData.m_workingDirectory.nativePath()).toUtf8()); f.write("clear\n"); f.write(QString("exec '%1' %2\n") - .arg(cmd.executable().nativePath()) - .arg(cmd.arguments()) + .arg(setupData.m_commandLine.executable().nativePath()) + .arg(setupData.m_commandLine.arguments()) .toUtf8()); f.close(); @@ -94,7 +93,7 @@ class ExternalTerminalProcessImpl final : public TerminalInterface process->setWorkingDirectory(setupData.m_workingDirectory); if constexpr (HostOsInfo::isWindowsHost()) { - process->setCommand(cmd); + process->setCommand(setupData.m_commandLine); process->setCreateConsoleOnWindows(true); process->setProcessMode(ProcessMode::Writer); } else { @@ -102,7 +101,7 @@ class ExternalTerminalProcessImpl final : public TerminalInterface CommandLine cmdLine = {terminal.command, {}}; if (!extraArgsFromOptions.isEmpty()) cmdLine.addArgs(extraArgsFromOptions, CommandLine::Raw); - cmdLine.addCommandLineAsArgs(cmd, CommandLine::Raw); + cmdLine.addCommandLineAsArgs(setupData.m_commandLine, CommandLine::Raw); process->setCommand(cmdLine); } diff --git a/src/libs/utils/terminalinterface.cpp b/src/libs/utils/terminalinterface.cpp index bb193b4d98..6972184e32 100644 --- a/src/libs/utils/terminalinterface.cpp +++ b/src/libs/utils/terminalinterface.cpp @@ -304,9 +304,7 @@ void TerminalInterface::start() expected_str result; QMetaObject::invokeMethod( d->stubCreator, - [this, &result] { - result = d->stubCreator->startStubProcess(m_setup.m_commandLine, m_setup); - }, + [this, &result] { result = d->stubCreator->startStubProcess(m_setup); }, d->stubCreator->thread() == QThread::currentThread() ? Qt::DirectConnection : Qt::BlockingQueuedConnection); @@ -386,9 +384,12 @@ void TerminalInterface::start() QTC_ASSERT(d->stubCreator, return); + ProcessSetupData stubSetupData = m_setup; + stubSetupData.m_commandLine = cmd; + QMetaObject::invokeMethod( d->stubCreator, - [cmd, this] { d->stubCreator->startStubProcess(cmd, m_setup); }, + [stubSetupData, this] { d->stubCreator->startStubProcess(stubSetupData); }, d->stubCreator->thread() == QThread::currentThread() ? Qt::DirectConnection : Qt::BlockingQueuedConnection); diff --git a/src/libs/utils/terminalinterface.h b/src/libs/utils/terminalinterface.h index 15b3a8c69e..a1960e7b96 100644 --- a/src/libs/utils/terminalinterface.h +++ b/src/libs/utils/terminalinterface.h @@ -14,9 +14,7 @@ class TerminalInterfacePrivate; class StubCreator : public QObject { public: - virtual expected_str startStubProcess(const CommandLine &cmd, - const ProcessSetupData &setup) - = 0; + virtual expected_str startStubProcess(const ProcessSetupData &setup) = 0; }; class QTCREATOR_UTILS_EXPORT TerminalInterface : public ProcessInterface -- cgit v1.2.3 From 8b3aa900da77b7dc8c5d42c86648ec45d5a28e7d Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Fri, 5 May 2023 15:51:11 +0200 Subject: Utils: Move SearchResultItem/Color into Utils It's going to be reused inside FileSearch. Change-Id: I8993d7158ff31c311c2283d32bc43465a8946a52 Reviewed-by: Qt CI Bot Reviewed-by: Reviewed-by: Eike Ziller --- src/libs/utils/CMakeLists.txt | 1 + src/libs/utils/searchresultitem.cpp | 59 ++++++++++++++++++ src/libs/utils/searchresultitem.h | 121 ++++++++++++++++++++++++++++++++++++ src/libs/utils/utils.qbs | 2 + 4 files changed, 183 insertions(+) create mode 100644 src/libs/utils/searchresultitem.cpp create mode 100644 src/libs/utils/searchresultitem.h (limited to 'src/libs') diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index defbbf2794..e3bbfcf7a9 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -147,6 +147,7 @@ add_qtc_library(Utils runextensions.cpp runextensions.h savefile.cpp savefile.h scopedswap.h + searchresultitem.cpp searchresultitem.h set_algorithm.h settingsaccessor.cpp settingsaccessor.h settingsselector.cpp settingsselector.h diff --git a/src/libs/utils/searchresultitem.cpp b/src/libs/utils/searchresultitem.cpp new file mode 100644 index 0000000000..74881bd396 --- /dev/null +++ b/src/libs/utils/searchresultitem.cpp @@ -0,0 +1,59 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "searchresultitem.h" + +namespace Utils { + +int Search::TextRange::length(const QString &text) const +{ + if (begin.line == end.line) + return end.column - begin.column; + + const int lineCount = end.line - begin.line; + int index = text.indexOf(QChar::LineFeed); + int currentLine = 1; + while (index > 0 && currentLine < lineCount) { + ++index; + index = text.indexOf(QChar::LineFeed, index); + ++currentLine; + } + + if (index < 0) + return 0; + + return index - begin.column + end.column; +} + +SearchResultColor::SearchResultColor(const QColor &textBg, const QColor &textFg, + const QColor &highlightBg, const QColor &highlightFg, + const QColor &functionBg, const QColor &functionFg) + : textBackground(textBg) + , textForeground(textFg) + , highlightBackground(highlightBg) + , highlightForeground(highlightFg) + , containingFunctionBackground(functionBg) + , containingFunctionForeground(functionFg) +{ + if (!highlightBackground.isValid()) + highlightBackground = textBackground; + if (!highlightForeground.isValid()) + highlightForeground = textForeground; + if (!containingFunctionBackground.isValid()) + containingFunctionBackground = textBackground; + if (!containingFunctionForeground.isValid()) + containingFunctionForeground = textForeground; +} + +QTCREATOR_UTILS_EXPORT size_t qHash(SearchResultColor::Style style, uint seed) +{ + int a = int(style); + return ::qHash(a, seed); +} + +void SearchResultItem::setMainRange(int line, int column, int length) +{ + m_mainRange = {{line, column}, {line, column + length}}; +} + +} // namespace Utils diff --git a/src/libs/utils/searchresultitem.h b/src/libs/utils/searchresultitem.h new file mode 100644 index 0000000000..558374704e --- /dev/null +++ b/src/libs/utils/searchresultitem.h @@ -0,0 +1,121 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "utils_global.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace Utils { + +namespace Search { + +class QTCREATOR_UTILS_EXPORT TextPosition +{ +public: + int line = -1; // (0 or -1 for no line number) + int column = -1; // 0-based starting position for a mark (-1 for no mark) + + bool operator<(const TextPosition &other) const + { return line < other.line || (line == other.line && column < other.column); } +}; + +class QTCREATOR_UTILS_EXPORT TextRange +{ +public: + QString mid(const QString &text) const { return text.mid(begin.column, length(text)); } + int length(const QString &text) const; + + TextPosition begin; + TextPosition end; + + bool operator<(const TextRange &other) const { return begin < other.begin; } +}; + +} // namespace Search + +class QTCREATOR_UTILS_EXPORT SearchResultColor +{ +public: + enum class Style { Default, Alt1, Alt2 }; + + SearchResultColor() = default; + SearchResultColor(const QColor &textBg, const QColor &textFg, + const QColor &highlightBg, const QColor &highlightFg, + const QColor &functionBg, const QColor &functionFg); + + QColor textBackground; + QColor textForeground; + QColor highlightBackground; + QColor highlightForeground; + QColor containingFunctionBackground; + QColor containingFunctionForeground; + +private: + QTCREATOR_UTILS_EXPORT friend size_t qHash(Style style, uint seed); +}; + +using SearchResultColors = QHash; + +class QTCREATOR_UTILS_EXPORT SearchResultItem +{ +public: + QStringList path() const { return m_path; } + void setPath(const QStringList &path) { m_path = path; } + void setFilePath(const Utils::FilePath &filePath) { m_path = {filePath.toUserOutput()}; } + + QString lineText() const { return m_lineText; } + void setLineText(const QString &text) { m_lineText = text; } + + QIcon icon() const { return m_icon; } + void setIcon(const QIcon &icon) { m_icon = icon; } + + QVariant userData() const { return m_userData; } + void setUserData(const QVariant &userData) { m_userData = userData; } + + Search::TextRange mainRange() const { return m_mainRange; } + void setMainRange(const Search::TextRange &mainRange) { m_mainRange = mainRange; } + void setMainRange(int line, int column, int length); + + bool useTextEditorFont() const { return m_useTextEditorFont; } + void setUseTextEditorFont(bool useTextEditorFont) { m_useTextEditorFont = useTextEditorFont; } + + SearchResultColor::Style style() const { return m_style; } + void setStyle(SearchResultColor::Style style) { m_style = style; } + + bool selectForReplacement() const { return m_selectForReplacement; } + void setSelectForReplacement(bool select) { m_selectForReplacement = select; } + + std::optional containingFunctionName() const { return m_containingFunctionName; } + + void setContainingFunctionName(const std::optional &containingFunctionName) + { + m_containingFunctionName = containingFunctionName; + } + +private: + QStringList m_path; // hierarchy to the parent item of this item + QString m_lineText; // text to show for the item itself + QIcon m_icon; // icon to show in front of the item (by be null icon to hide) + QVariant m_userData; // user data for identification of the item + Search::TextRange m_mainRange; + bool m_useTextEditorFont = false; + bool m_selectForReplacement = true; + SearchResultColor::Style m_style = SearchResultColor::Style::Default; + std::optional m_containingFunctionName; +}; + +} // namespace Utils + +Q_DECLARE_METATYPE(Utils::SearchResultItem) +Q_DECLARE_METATYPE(Utils::Search::TextPosition) diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index 4f628616a1..fa766b8bc4 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -275,6 +275,8 @@ Project { "savefile.cpp", "savefile.h", "scopedswap.h", + "searchresultitem.cpp", + "searchresultitem.h", "set_algorithm.h", "settingsaccessor.cpp", "settingsaccessor.h", -- cgit v1.2.3 From c879aeb56550494a27d12d004cff6fb6f42fc511 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Fri, 5 May 2023 19:12:47 +0200 Subject: SearchResultItem: Introduce SearchResultItems And reuse it. Change-Id: Ia052297340f2bf2478fbfdb2427b45e30bd9d067 Reviewed-by: Eike Ziller Reviewed-by: Qt CI Bot --- src/libs/utils/searchresultitem.h | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/searchresultitem.h b/src/libs/utils/searchresultitem.h index 558374704e..ea0de8ceec 100644 --- a/src/libs/utils/searchresultitem.h +++ b/src/libs/utils/searchresultitem.h @@ -115,7 +115,10 @@ private: std::optional m_containingFunctionName; }; +using SearchResultItems = QList; + } // namespace Utils Q_DECLARE_METATYPE(Utils::SearchResultItem) +Q_DECLARE_METATYPE(Utils::SearchResultItems) Q_DECLARE_METATYPE(Utils::Search::TextPosition) -- cgit v1.2.3 From cd73f8c6b8dd6e62b3cdff972d7194f4ff34a105 Mon Sep 17 00:00:00 2001 From: Semih Yavuz Date: Fri, 5 May 2023 19:44:56 +0200 Subject: qmljsreformatter: don't default foreach type to "in" Fixes: QTCREATORBUG-29123 Change-Id: I4d3a611c359946c4483388cbf18a0b6f16d0a8d6 Reviewed-by: Fabian Kosmale --- src/libs/qmljs/qmljsreformatter.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/qmljs/qmljsreformatter.cpp b/src/libs/qmljs/qmljsreformatter.cpp index 03a95a668c..232c0361a1 100644 --- a/src/libs/qmljs/qmljsreformatter.cpp +++ b/src/libs/qmljs/qmljsreformatter.cpp @@ -1088,7 +1088,10 @@ protected: out(" "); out(ast->lparenToken); accept(ast->lhs); - out(" in "); + if (ast->type == ForEachType::In) + out(" in "); + else + out(" of "); accept(ast->expression); out(ast->rparenToken); acceptBlockOrIndented(ast->statement); -- cgit v1.2.3 From 0e4a7d5207c522c1d25d03212777b00b79b0e360 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Wed, 3 May 2023 09:11:28 +0200 Subject: Utils: Avoid watching directories of watched files Do not watch directories unconditionally, but only if they contain removed files to check whether those files are readded. This should reduce the number of needlesly watched directories to a minimum and fix performance regressions introduced by 61598eca15e14af64c20d314db382973dfccb2d2. Fixes: QTCREATORBUG-28957 Change-Id: I8fe387e7de32b0fb585074330c7f6ca7eae44730 Reviewed-by: Reviewed-by: Ulf Hermann Reviewed-by: hjk --- src/libs/utils/filesystemwatcher.cpp | 59 ++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 13 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/filesystemwatcher.cpp b/src/libs/utils/filesystemwatcher.cpp index 3f427cce2e..ad7f77c10e 100644 --- a/src/libs/utils/filesystemwatcher.cpp +++ b/src/libs/utils/filesystemwatcher.cpp @@ -275,15 +275,19 @@ void FileSystemWatcher::addFiles(const QStringList &files, WatchMode wm) const int count = ++d->m_staticData->m_fileCount[file]; Q_ASSERT(count > 0); - if (count == 1) + if (count == 1) { toAdd << file; - const QString directory = QFileInfo(file).path(); - const int dirCount = ++d->m_staticData->m_directoryCount[directory]; - Q_ASSERT(dirCount > 0); + QFileInfo fi(file); + if (!fi.exists()) { + const QString directory = fi.path(); + const int dirCount = ++d->m_staticData->m_directoryCount[directory]; + Q_ASSERT(dirCount > 0); - if (dirCount == 1) - toAdd << directory; + if (dirCount == 1) + toAdd << directory; + } + } } if (!toAdd.isEmpty()) @@ -311,15 +315,19 @@ void FileSystemWatcher::removeFiles(const QStringList &files) const int count = --(d->m_staticData->m_fileCount[file]); Q_ASSERT(count >= 0); - if (!count) + if (!count) { toRemove << file; - const QString directory = QFileInfo(file).path(); - const int dirCount = --d->m_staticData->m_directoryCount[directory]; - Q_ASSERT(dirCount >= 0); + QFileInfo fi(file); + if (!fi.exists()) { + const QString directory = fi.path(); + const int dirCount = --d->m_staticData->m_directoryCount[directory]; + Q_ASSERT(dirCount >= 0); - if (!dirCount) - toRemove << directory; + if (!dirCount) + toRemove << directory; + } + } } if (!toRemove.isEmpty()) @@ -418,13 +426,27 @@ QStringList FileSystemWatcher::directories() const void FileSystemWatcher::slotFileChanged(const QString &path) { const auto it = d->m_files.find(path); + QStringList toAdd; if (it != d->m_files.end() && it.value().trigger(path)) { if (debug) qDebug() << this << "triggers on file " << path << it.value().watchMode << it.value().modifiedTime.toString(Qt::ISODate); d->fileChanged(path); + + QFileInfo fi(path); + if (!fi.exists()) { + const QString directory = fi.path(); + const int dirCount = ++d->m_staticData->m_directoryCount[directory]; + Q_ASSERT(dirCount > 0); + + if (dirCount == 1) + toAdd << directory; + } } + + if (!toAdd.isEmpty()) + d->m_staticData->m_watcher->addPaths(toAdd); } void FileSystemWatcher::slotDirectoryChanged(const QString &path) @@ -450,9 +472,20 @@ void FileSystemWatcher::slotDirectoryChanged(const QString &path) for (const QString &rejected : d->m_staticData->m_watcher->addPaths(toReadd)) toReadd.removeOne(rejected); + QStringList toRemove; // If we've successfully added the file, that means it was deleted and replaced. - for (const QString &reAdded : std::as_const(toReadd)) + for (const QString &reAdded : std::as_const(toReadd)) { d->fileChanged(reAdded); + const QString directory = QFileInfo(reAdded).path(); + const int dirCount = --d->m_staticData->m_directoryCount[directory]; + Q_ASSERT(dirCount >= 0); + + if (!dirCount) + toRemove << directory; + } + + if (!toRemove.isEmpty()) + d->m_staticData->m_watcher->removePaths(toRemove); } } -- cgit v1.2.3 From 3f7e92e052469646c85b1eb65918ea84db67088c Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Sun, 7 May 2023 20:58:46 +0200 Subject: Utils: Introduce QTC_STATIC_SCOPED_TIMER() This macro, similar to QTC_SCOPED_TIMER(), measures the time spent in a block of code. In addition, the time spent in a block is saved in a static variable. This macro enables measuring a cumulative time spent inside a certain block of code for all invocations. When a long freeze is being tracked, and QTC_SCOPED_TIMER() indicates that the most of the freeze is spent inside a loop with a big amount of iterations, placing QTC_STATIC_SCOPED_TIMER() inside the loop may detect the part of the loop that composes in total for the longest freeze. In contrary to the QTC_SCOPED_TIMER(), this macro it doesn't print the message when it's entered. It prints the output at first hit, and later, as not to clog the debug output, only at 10ms resolution. Change-Id: I3360a3ab9147d544f90ce914fb78359f7179c767 Reviewed-by: hjk Reviewed-by: Qt CI Bot --- src/libs/utils/CMakeLists.txt | 1 + src/libs/utils/qtcassert.cpp | 28 ------------------ src/libs/utils/qtcassert.h | 20 ------------- src/libs/utils/scopedtimer.cpp | 64 ++++++++++++++++++++++++++++++++++++++++++ src/libs/utils/scopedtimer.h | 38 +++++++++++++++++++++++++ src/libs/utils/utils.qbs | 2 ++ 6 files changed, 105 insertions(+), 48 deletions(-) create mode 100644 src/libs/utils/scopedtimer.cpp create mode 100644 src/libs/utils/scopedtimer.h (limited to 'src/libs') diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index e3bbfcf7a9..f05398094a 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -147,6 +147,7 @@ add_qtc_library(Utils runextensions.cpp runextensions.h savefile.cpp savefile.h scopedswap.h + scopedtimer.cpp scopedtimer.h searchresultitem.cpp searchresultitem.h set_algorithm.h settingsaccessor.cpp settingsaccessor.h diff --git a/src/libs/utils/qtcassert.cpp b/src/libs/utils/qtcassert.cpp index 019c223ef7..78baa41a4a 100644 --- a/src/libs/utils/qtcassert.cpp +++ b/src/libs/utils/qtcassert.cpp @@ -8,8 +8,6 @@ #include #include -#include - #if defined(Q_OS_UNIX) #include #include @@ -132,30 +130,4 @@ void writeAssertLocation(const char *msg) dumpBacktrace(maxdepth); } -using namespace std::chrono; - -class ScopedTimerPrivate -{ -public: - const char *m_fileName = nullptr; - const int m_line = 0; - const time_point m_start = system_clock::now(); -}; - -ScopedTimer::ScopedTimer(const char *fileName, int line) - : d(new ScopedTimerPrivate{fileName, line}) -{ - const QByteArray time = QTime::currentTime().toString(Qt::ISODateWithMs).toLatin1(); - qDebug("SCOPED TIMER [%s] in %s:%d started", time.data(), d->m_fileName, d->m_line); -} - -ScopedTimer::~ScopedTimer() -{ - const auto end = system_clock::now(); - const auto elapsed = duration_cast(end - d->m_start); - const QByteArray time = QTime::currentTime().toString(Qt::ISODateWithMs).toLatin1(); - qDebug("SCOPED TIMER [%s] in %s:%d stopped with timeout: %ldms", - time.data(), d->m_fileName, d->m_line, elapsed.count()); -} - } // namespace Utils diff --git a/src/libs/utils/qtcassert.h b/src/libs/utils/qtcassert.h index aab7ab93d0..8f6b2ec5b8 100644 --- a/src/libs/utils/qtcassert.h +++ b/src/libs/utils/qtcassert.h @@ -5,25 +5,9 @@ #include "utils_global.h" -#include - namespace Utils { - QTCREATOR_UTILS_EXPORT void writeAssertLocation(const char *msg); QTCREATOR_UTILS_EXPORT void dumpBacktrace(int maxdepth); - -class ScopedTimerPrivate; - -class QTCREATOR_UTILS_EXPORT ScopedTimer -{ -public: - ScopedTimer(const char *fileName, int line); - ~ScopedTimer(); - -private: - std::unique_ptr d; -}; - } // Utils #define QTC_ASSERT_STRINGIFY_HELPER(x) #x @@ -37,7 +21,3 @@ private: #define QTC_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QTC_ASSERT_STRING(#cond); action; } do {} while (0) #define QTC_CHECK(cond) if (Q_LIKELY(cond)) {} else { QTC_ASSERT_STRING(#cond); } do {} while (0) #define QTC_GUARD(cond) ((Q_LIKELY(cond)) ? true : (QTC_ASSERT_STRING(#cond), false)) - -#define QTC_CONCAT_HELPER(x, y) x ## y -#define QTC_CONCAT(x, y) QTC_CONCAT_HELPER(x, y) -#define QTC_SCOPED_TIMER() ::Utils::ScopedTimer QTC_CONCAT(_qtc_scoped_timer_, __LINE__)(__FILE__, __LINE__) diff --git a/src/libs/utils/scopedtimer.cpp b/src/libs/utils/scopedtimer.cpp new file mode 100644 index 0000000000..97b8beffda --- /dev/null +++ b/src/libs/utils/scopedtimer.cpp @@ -0,0 +1,64 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "scopedtimer.h" + +#include +#include +#include + +#include + +namespace Utils { + +static QString currentTime() { return QTime::currentTime().toString(Qt::ISODateWithMs); } + +using namespace std::chrono; + +class ScopedTimerPrivate +{ +public: + const char *m_fileName = nullptr; + const int m_line = 0; + std::atomic *m_cumulative = nullptr; + const time_point m_start = system_clock::now(); +}; + +static const char s_scoped[] = "SCOPED TIMER"; +static const char s_scopedCumulative[] = "STATIC SCOPED TIMER"; + +ScopedTimer::ScopedTimer(const char *fileName, int line, std::atomic *cumulative) + : d(new ScopedTimerPrivate{fileName, line, cumulative}) +{ + if (d->m_cumulative) + return; + qDebug().noquote().nospace() << s_scoped << " [" << currentTime() << "] in " << d->m_fileName + << ':' << d->m_line << " started"; +} + +static int64_t toMs(int64_t ns) { return ns / 1000000; } + +ScopedTimer::~ScopedTimer() +{ + const auto elapsed = duration_cast(system_clock::now() - d->m_start); + QString suffix; + if (d->m_cumulative) { + const int64_t nsOld = d->m_cumulative->fetch_add(elapsed.count()); + const int64_t msOld = toMs(nsOld); + const int64_t nsNew = nsOld + elapsed.count(); + const int64_t msNew = toMs(nsNew); + // Always report the first hit, and later, as not to clog the debug output, + // only at 10ms resolution. + if (nsOld != 0 && msOld / 10 == msNew / 10) + return; + + suffix = " cumulative timeout: " + QString::number(msNew) + "ms"; + } else { + suffix = " stopped with timeout: " + QString::number(toMs(elapsed.count())) + "ms"; + } + const char *header = d->m_cumulative ? s_scopedCumulative : s_scoped; + qDebug().noquote().nospace() << header << " [" << currentTime() << "] in " << d->m_fileName + << ':' << d->m_line << suffix; +} + +} // namespace Utils diff --git a/src/libs/utils/scopedtimer.h b/src/libs/utils/scopedtimer.h new file mode 100644 index 0000000000..e6ec42e6f4 --- /dev/null +++ b/src/libs/utils/scopedtimer.h @@ -0,0 +1,38 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "utils_global.h" + +#include +#include + +namespace Utils { + +class ScopedTimerPrivate; + +class QTCREATOR_UTILS_EXPORT ScopedTimer +{ +public: + ScopedTimer(const char *fileName, int line, std::atomic *cumulative = nullptr); + ~ScopedTimer(); + +private: + std::unique_ptr d; +}; + +} // Utils + +#define QTC_CONCAT_HELPER(x, y) x ## y +#define QTC_CONCAT(x, y) QTC_CONCAT_HELPER(x, y) +#define QTC_SCOPED_TIMER() ::Utils::ScopedTimer QTC_CONCAT(_qtc_scoped_timer_, __LINE__)\ +(__FILE__, __LINE__) + +// The macro below expands as follows (in one line): +// static std::atomic _qtc_static_scoped_timer___LINE__ = 0; +// ScopedTimer _qtc_scoped_timer___LINE__(__FILE__, __LINE__, &_qtc_static_scoped_timer___LINE__) +#define QTC_STATIC_SCOPED_TIMER() static std::atomic \ +QTC_CONCAT(_qtc_static_scoped_timer_, __LINE__) = 0; \ +::Utils::ScopedTimer QTC_CONCAT(_qtc_scoped_timer_, __LINE__)\ +(__FILE__, __LINE__, &QTC_CONCAT(_qtc_static_scoped_timer_, __LINE__)) diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index fa766b8bc4..f1940a053d 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -275,6 +275,8 @@ Project { "savefile.cpp", "savefile.h", "scopedswap.h", + "scopedtimer.cpp", + "scopedtimer.h", "searchresultitem.cpp", "searchresultitem.h", "set_algorithm.h", -- cgit v1.2.3 From 936086745ab826932f6559cc51f49ba20718f56a Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Fri, 5 May 2023 14:05:10 +0200 Subject: FileSearch: Get rid of FileSearchResult Use SearchResultItem instead. This change should reduce the remaining freeze described in a9eb732ce6763e22badd92fc8523cebe84b09a84 even more. Change-Id: I102b82ed5677360ccd9e425dd0bdd941d87116f0 Reviewed-by: Eike Ziller --- src/libs/utils/filesearch.cpp | 64 +++++++++++++++++++------------------ src/libs/utils/filesearch.h | 52 ++++++------------------------ src/libs/utils/searchresultitem.cpp | 39 ++++++++++++++++++++-- src/libs/utils/searchresultitem.h | 8 +++++ 4 files changed, 87 insertions(+), 76 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/filesearch.cpp b/src/libs/utils/filesearch.cpp index 02f6409de0..fe65756276 100644 --- a/src/libs/utils/filesearch.cpp +++ b/src/libs/utils/filesearch.cpp @@ -7,6 +7,7 @@ #include "filepath.h" #include "mapreduce.h" #include "qtcassert.h" +#include "searchresultitem.h" #include "stringutils.h" #include "utilstr.h" #include "utiltypes.h" @@ -69,7 +70,7 @@ public: FileSearch(const QString &searchTerm, QTextDocument::FindFlags flags, const QMap &fileToContentsMap); - void operator()(QFutureInterface &futureInterface, + void operator()(QFutureInterface &futureInterface, const FileIterator::Item &item) const; private: @@ -91,7 +92,7 @@ public: QTextDocument::FindFlags flags, const QMap &fileToContentsMap); FileSearchRegExp(const FileSearchRegExp &other); - void operator()(QFutureInterface &futureInterface, + void operator()(QFutureInterface &futureInterface, const FileIterator::Item &item) const; private: @@ -117,7 +118,7 @@ FileSearch::FileSearch(const QString &searchTerm, termDataUpper = searchTermUpper.constData(); } -void FileSearch::operator()(QFutureInterface &futureInterface, +void FileSearch::operator()(QFutureInterface &futureInterface, const FileIterator::Item &item) const { if (futureInterface.isCanceled()) @@ -125,7 +126,7 @@ void FileSearch::operator()(QFutureInterface &futureInterf qCDebug(searchLog) << "Searching in" << item.filePath; futureInterface.setProgressRange(0, 1); futureInterface.setProgressValue(0); - FileSearchResultList results; + SearchResultItems results; QString tempString; if (!getFileContent(item.filePath, item.encoding, &tempString, fileToContentsMap)) { qCDebug(searchLog) << "- failed to get content for" << item.filePath; @@ -190,13 +191,13 @@ void FileSearch::operator()(QFutureInterface &futureInterf } } if (equal) { - const QString resultItemText = clippedText(chunk, MAX_LINE_SIZE); - results << FileSearchResult(item.filePath, - lineNr, - resultItemText, - regionPtr - chunkPtr, - termMaxIndex + 1, - QStringList()); + SearchResultItem result; + result.setFilePath(item.filePath); + result.setMainRange(lineNr, regionPtr - chunkPtr, termMaxIndex + 1); + result.setDisplayText(clippedText(chunk, MAX_LINE_SIZE)); + result.setUserData(QStringList()); + result.setUseTextEditorFont(true); + results << result; regionPtr += termMaxIndex; // another +1 done by for-loop } } @@ -238,7 +239,7 @@ QRegularExpressionMatch FileSearchRegExp::doGuardedMatch(const QString &line, in return expression.match(line, offset); } -void FileSearchRegExp::operator()(QFutureInterface &futureInterface, +void FileSearchRegExp::operator()(QFutureInterface &futureInterface, const FileIterator::Item &item) const { if (!expression.isValid()) { @@ -250,7 +251,7 @@ void FileSearchRegExp::operator()(QFutureInterface &future qCDebug(searchLog) << "Searching in" << item.filePath; futureInterface.setProgressRange(0, 1); futureInterface.setProgressValue(0); - FileSearchResultList results; + SearchResultItems results; QString tempString; if (!getFileContent(item.filePath, item.encoding, &tempString, fileToContentsMap)) { qCDebug(searchLog) << "- failed to get content for" << item.filePath; @@ -270,12 +271,13 @@ void FileSearchRegExp::operator()(QFutureInterface &future int pos = 0; while ((match = doGuardedMatch(line, pos)).hasMatch()) { pos = match.capturedStart(); - results << FileSearchResult(item.filePath, - lineNr, - resultItemText, - pos, - match.capturedLength(), - match.capturedTexts()); + SearchResultItem result; + result.setFilePath(item.filePath); + result.setMainRange(lineNr, pos, match.capturedLength()); + result.setDisplayText(resultItemText); + result.setUserData(match.capturedTexts()); + result.setUseTextEditorFont(true); + results << result; if (match.capturedLength() == 0) break; pos += match.capturedLength(); @@ -299,12 +301,12 @@ struct SearchState SearchState(const QString &term, FileIterator *iterator) : searchTerm(term), files(iterator) {} QString searchTerm; FileIterator *files = nullptr; - FileSearchResultList cachedResults; + SearchResultItems cachedResults; int numFilesSearched = 0; int numMatches = 0; }; -SearchState initFileSearch(QFutureInterface &futureInterface, +SearchState initFileSearch(QFutureInterface &futureInterface, const QString &searchTerm, FileIterator *files) { futureInterface.setProgressRange(0, files->maxProgress()); @@ -312,9 +314,9 @@ SearchState initFileSearch(QFutureInterface &futureInterfa return SearchState(searchTerm, files); } -void collectSearchResults(QFutureInterface &futureInterface, +void collectSearchResults(QFutureInterface &futureInterface, SearchState &state, - const FileSearchResultList &results) + const SearchResultItems &results) { state.numMatches += results.size(); state.cachedResults << results; @@ -333,7 +335,7 @@ void collectSearchResults(QFutureInterface &futureInterfac } } -void cleanUpFileSearch(QFutureInterface &futureInterface, +void cleanUpFileSearch(QFutureInterface &futureInterface, SearchState &state) { if (!state.cachedResults.isEmpty()) { @@ -356,13 +358,13 @@ void cleanUpFileSearch(QFutureInterface &futureInterface, } // namespace -QFuture Utils::findInFiles(const QString &searchTerm, - FileIterator *files, - QTextDocument::FindFlags flags, - const QMap &fileToContentsMap) +QFuture Utils::findInFiles(const QString &searchTerm, + FileIterator *files, + QTextDocument::FindFlags flags, + const QMap &fileToContentsMap) { return mapReduce(files->begin(), files->end(), - [searchTerm, files](QFutureInterface &futureInterface) { + [searchTerm, files](QFutureInterface &futureInterface) { return initFileSearch(futureInterface, searchTerm, files); }, FileSearch(searchTerm, flags, fileToContentsMap), @@ -370,14 +372,14 @@ QFuture Utils::findInFiles(const QString &searchTerm, &cleanUpFileSearch); } -QFuture Utils::findInFilesRegExp( +QFuture Utils::findInFilesRegExp( const QString &searchTerm, FileIterator *files, QTextDocument::FindFlags flags, const QMap &fileToContentsMap) { return mapReduce(files->begin(), files->end(), - [searchTerm, files](QFutureInterface &futureInterface) { + [searchTerm, files](QFutureInterface &futureInterface) { return initFileSearch(futureInterface, searchTerm, files); }, FileSearchRegExp(searchTerm, flags, fileToContentsMap), diff --git a/src/libs/utils/filesearch.h b/src/libs/utils/filesearch.h index ecb6c574af..7bb7af5d33 100644 --- a/src/libs/utils/filesearch.h +++ b/src/libs/utils/filesearch.h @@ -6,8 +6,8 @@ #include "utils_global.h" #include "filepath.h" +#include "searchresultitem.h" -#include #include #include #include @@ -152,54 +152,20 @@ private: QList m_items; }; -class QTCREATOR_UTILS_EXPORT FileSearchResult -{ -public: - FileSearchResult() = default; - FileSearchResult(const FilePath &fileName, int lineNumber, const QString &matchingLine, - int matchStart, int matchLength, - const QStringList ®expCapturedTexts) - : fileName(fileName), - lineNumber(lineNumber), - matchingLine(matchingLine), - matchStart(matchStart), - matchLength(matchLength), - regexpCapturedTexts(regexpCapturedTexts) - {} - - bool operator==(const FileSearchResult &o) const - { - return fileName == o.fileName && lineNumber == o.lineNumber - && matchingLine == o.matchingLine && matchStart == o.matchStart - && matchLength == o.matchLength && regexpCapturedTexts == o.regexpCapturedTexts; - } - bool operator!=(const FileSearchResult &o) const { return !(*this == o); } - - FilePath fileName; - int lineNumber; - QString matchingLine; - int matchStart; - int matchLength; - QStringList regexpCapturedTexts; -}; - -using FileSearchResultList = QList; - -QTCREATOR_UTILS_EXPORT QFuture findInFiles( - const QString &searchTerm, +QTCREATOR_UTILS_EXPORT QFuture findInFiles(const QString &searchTerm, FileIterator *files, QTextDocument::FindFlags flags, - const QMap &fileToContentsMap = QMap()); + const QMap &fileToContentsMap = {}); -QTCREATOR_UTILS_EXPORT QFuture findInFilesRegExp( +QTCREATOR_UTILS_EXPORT QFuture findInFilesRegExp( const QString &searchTerm, FileIterator *files, QTextDocument::FindFlags flags, - const QMap &fileToContentsMap = QMap()); + const QMap &fileToContentsMap = {}); -QTCREATOR_UTILS_EXPORT QString expandRegExpReplacement(const QString &replaceText, const QStringList &capturedTexts); -QTCREATOR_UTILS_EXPORT QString matchCaseReplacement(const QString &originalText, const QString &replaceText); +QTCREATOR_UTILS_EXPORT QString expandRegExpReplacement(const QString &replaceText, + const QStringList &capturedTexts); +QTCREATOR_UTILS_EXPORT QString matchCaseReplacement(const QString &originalText, + const QString &replaceText); } // namespace Utils - -Q_DECLARE_METATYPE(Utils::FileSearchResultList) diff --git a/src/libs/utils/searchresultitem.cpp b/src/libs/utils/searchresultitem.cpp index 74881bd396..1fe0d953f6 100644 --- a/src/libs/utils/searchresultitem.cpp +++ b/src/libs/utils/searchresultitem.cpp @@ -45,15 +45,50 @@ SearchResultColor::SearchResultColor(const QColor &textBg, const QColor &textFg, containingFunctionForeground = textForeground; } +static QString displayText(const QString &line) +{ + QString result = line; + auto end = result.end(); + for (auto it = result.begin(); it != end; ++it) { + if (!it->isSpace() && !it->isPrint()) + *it = QChar('?'); + } + return result; +} + +void SearchResultItem::setDisplayText(const QString &text) +{ + setLineText(displayText(text)); +} + +void SearchResultItem::setMainRange(int line, int column, int length) +{ + m_mainRange = {{line, column}, {line, column + length}}; +} + QTCREATOR_UTILS_EXPORT size_t qHash(SearchResultColor::Style style, uint seed) { int a = int(style); return ::qHash(a, seed); } -void SearchResultItem::setMainRange(int line, int column, int length) +bool Search::TextPosition::operator==(const Search::TextPosition &other) const { - m_mainRange = {{line, column}, {line, column + length}}; + return line == other.line && column == other.column; +} + +bool Search::TextRange::operator==(const Search::TextRange &other) const +{ + return begin == other.begin && end == other.end; +} + +bool SearchResultItem::operator==(const SearchResultItem &other) const +{ + return m_path == other.m_path && m_lineText == other.m_lineText + && m_userData == other.m_userData && m_mainRange == other.m_mainRange + && m_useTextEditorFont == other.m_useTextEditorFont + && m_selectForReplacement == other.m_selectForReplacement && m_style == other.m_style + && m_containingFunctionName == other.m_containingFunctionName; } } // namespace Utils diff --git a/src/libs/utils/searchresultitem.h b/src/libs/utils/searchresultitem.h index ea0de8ceec..080a44f24e 100644 --- a/src/libs/utils/searchresultitem.h +++ b/src/libs/utils/searchresultitem.h @@ -28,6 +28,8 @@ public: bool operator<(const TextPosition &other) const { return line < other.line || (line == other.line && column < other.column); } + bool operator==(const TextPosition &other) const; + bool operator!=(const TextPosition &other) const { return !(operator==(other)); } }; class QTCREATOR_UTILS_EXPORT TextRange @@ -40,6 +42,8 @@ public: TextPosition end; bool operator<(const TextRange &other) const { return begin < other.begin; } + bool operator==(const TextRange &other) const; + bool operator!=(const TextRange &other) const { return !(operator==(other)); } }; } // namespace Search @@ -76,6 +80,7 @@ public: QString lineText() const { return m_lineText; } void setLineText(const QString &text) { m_lineText = text; } + void setDisplayText(const QString &text); QIcon icon() const { return m_icon; } void setIcon(const QIcon &icon) { m_icon = icon; } @@ -103,6 +108,9 @@ public: m_containingFunctionName = containingFunctionName; } + bool operator==(const SearchResultItem &other) const; + bool operator!=(const SearchResultItem &other) const { return !(operator==(other)); } + private: QStringList m_path; // hierarchy to the parent item of this item QString m_lineText; // text to show for the item itself -- cgit v1.2.3 From 3dc775a6dfbc106e3eb28b5b0198e1e8c31a743a Mon Sep 17 00:00:00 2001 From: David Schulz Date: Fri, 5 May 2023 09:50:16 +0200 Subject: MultiTextCursor: iterate over map entries This will ensure that the cursors are iterated over in the order they appear in the document. This fixes an issue on copy and paste a multitext cursors, since the text was copied in the order inside the document, but inserted in the order how the different cursors were added to the multi text cursor. Fixes: QTCREATORBUG-29117 Change-Id: I8475cde7129c378ec7862482226db25e40f61e1b Reviewed-by: Reviewed-by: Christian Stenger --- src/libs/utils/multitextcursor.h | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/multitextcursor.h b/src/libs/utils/multitextcursor.h index b2565f931f..dc554dd227 100644 --- a/src/libs/utils/multitextcursor.h +++ b/src/libs/utils/multitextcursor.h @@ -76,15 +76,37 @@ public: bool operator==(const MultiTextCursor &other) const; bool operator!=(const MultiTextCursor &other) const; - using iterator = std::list::iterator; - using const_iterator = std::list::const_iterator; - - iterator begin() { return m_cursorList.begin(); } - iterator end() { return m_cursorList.end(); } - const_iterator begin() const { return m_cursorList.begin(); } - const_iterator end() const { return m_cursorList.end(); } - const_iterator constBegin() const { return m_cursorList.cbegin(); } - const_iterator constEnd() const { return m_cursorList.cend(); } + template + class BaseIterator { + public: + using iterator_category = std::input_iterator_tag; + using difference_type = int; + using value_type = T; + using pointer = T *; + using reference = T &; + BaseIterator(const mapit &it) : internalit(it) {} + BaseIterator &operator++() { ++internalit; return *this; } + BaseIterator operator++(int) { auto result = *this; ++(*this); return result; } + bool operator==(BaseIterator other) const { return internalit == other.internalit; } + bool operator!=(BaseIterator other) const { return !(*this == other); } + reference operator*() const { return *(internalit->second); } + + private: + mapit internalit; + }; + + using iterator + = BaseIterator::iterator>::iterator>; + using const_iterator + = BaseIterator::iterator>::const_iterator>; + + iterator begin() { return m_cursorMap.begin(); } + iterator end() { return m_cursorMap.end(); } + const_iterator begin() const { return m_cursorMap.begin(); } + const_iterator end() const { return m_cursorMap.end(); } + const_iterator constBegin() const { return m_cursorMap.cbegin(); } + const_iterator constEnd() const { return m_cursorMap.cend(); } static bool multiCursorAddEvent(QKeyEvent *e, QKeySequence::StandardKey matchKey); -- cgit v1.2.3 From e7397ec57609914a496019f9926bfae43da88a5b Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 9 May 2023 14:44:17 +0200 Subject: LayoutBuilder: Fix Splitter construction QSplitter is different insofar as that it doesn't have a Layout, but a list of child widgets. Change-Id: I4e1076e39d20df409c4bab93d79770b6d0e5aa8d Reviewed-by: Alessandro Portale --- src/libs/utils/layoutbuilder.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index 51b94b582d..40862c0434 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -574,8 +574,22 @@ TextEdit::TextEdit(std::initializer_list items) Splitter::Splitter(std::initializer_list items) { - this->subItems = items; - setupWidget(this); // FIXME: Default was Qt::Vertical) + subItems = items; + onAdd = [](LayoutBuilder &builder) { + auto splitter = new QSplitter; + splitter->setOrientation(Qt::Vertical); + builder.stack.append(splitter); + }; + onExit = [](LayoutBuilder &builder) { + const Slice slice = builder.stack.last(); + QSplitter *splitter = qobject_cast(slice.widget); + for (const ResultItem &ri : slice.pendingItems) { + if (ri.widget) + splitter->addWidget(ri.widget); + } + builder.stack.pop_back(); + builder.stack.last().pendingItems.append(ResultItem(splitter)); + }; } TabWidget::TabWidget(std::initializer_list items) -- cgit v1.2.3 From 2e7930b56e1e32343587e0c7c74cdb641050ea30 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 9 May 2023 18:22:42 +0200 Subject: LayoutBuilder: Rename Id to ID The Id was only used in the demo. "ID" is Not exactly canonical naming, but this clashes often with Utils::Id, so mis-spell until we settle on a proper name. Change-Id: I6fdf806c41abf224f7422ec6c9263db3eb357190 Reviewed-by: Alessandro Portale --- src/libs/utils/layoutbuilder.cpp | 4 ++-- src/libs/utils/layoutbuilder.h | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index 40862c0434..f23b5e025f 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -710,12 +710,12 @@ LayoutItem columnStretch(int column, int stretch) // Id based setters -LayoutItem id(Id &out) +LayoutItem id(ID &out) { return [&out](QObject *target) { out.ob = target; }; } -void setText(Id id, const QString &text) +void setText(ID id, const QString &text) { if (auto textEdit = qobject_cast(id.ob)) textEdit->setText(text); diff --git a/src/libs/utils/layoutbuilder.h b/src/libs/utils/layoutbuilder.h index 3898dd5982..33fba0270d 100644 --- a/src/libs/utils/layoutbuilder.h +++ b/src/libs/utils/layoutbuilder.h @@ -202,15 +202,15 @@ QTCREATOR_UTILS_EXPORT LayoutItem windowTitle(const QString &windowTitle); // "Getters" -class Id +class ID { public: QObject *ob = nullptr; }; -QTCREATOR_UTILS_EXPORT LayoutItem id(Id &out); +QTCREATOR_UTILS_EXPORT LayoutItem id(ID &out); -QTCREATOR_UTILS_EXPORT void setText(Id id, const QString &text); +QTCREATOR_UTILS_EXPORT void setText(ID id, const QString &text); // "Signals" @@ -222,7 +222,7 @@ QTCREATOR_UTILS_EXPORT LayoutItem onTextChanged(const std::function &, QObject *guard = nullptr); -QTCREATOR_UTILS_EXPORT LayoutItem onTextChanged(Id &id, QVariant(*sig)(QObject *)); +QTCREATOR_UTILS_EXPORT LayoutItem onTextChanged(ID &id, QVariant(*sig)(QObject *)); // Convenience -- cgit v1.2.3 From c75b59f9b448b9d4ed3f618df705601dcd4eb9db Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Tue, 9 May 2023 22:49:14 +0200 Subject: Process: Rename QtcProcessPrivate -> ProcessPrivate Rename the logging category for Process. Fix inline comments accordingly. Adapt warning/debug messages accordingly. Change-Id: I2b1f0f558701def3afa3c1b04adf629833dba9e7 Reviewed-by: Qt CI Bot Reviewed-by: hjk --- src/libs/utils/commandline.cpp | 2 +- src/libs/utils/deviceshell.cpp | 4 +-- src/libs/utils/filepath.cpp | 2 +- src/libs/utils/launchersocket.cpp | 4 +-- src/libs/utils/launchersocket.h | 2 +- src/libs/utils/macroexpander.cpp | 2 +- src/libs/utils/process.cpp | 66 +++++++++++++++++++-------------------- src/libs/utils/process.h | 8 ++--- src/libs/utils/processinterface.h | 6 ++-- src/libs/utils/processreaper.cpp | 2 +- 10 files changed, 49 insertions(+), 49 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/commandline.cpp b/src/libs/utils/commandline.cpp index 9f6fdec35d..103049ae24 100644 --- a/src/libs/utils/commandline.cpp +++ b/src/libs/utils/commandline.cpp @@ -893,7 +893,7 @@ bool ProcessArgs::expandMacros(QString *cmd, AbstractMacroExpander *mx, OsType o break; case CrtClosed: // Two consecutive quotes make a literal quote - and - // still close quoting. See QtcProcess::quoteArg(). + // still close quoting. See Process::quoteArg(). crtState = CrtInWord; break; case CrtHadQuote: diff --git a/src/libs/utils/deviceshell.cpp b/src/libs/utils/deviceshell.cpp index 627fee3d67..f6474721ac 100644 --- a/src/libs/utils/deviceshell.cpp +++ b/src/libs/utils/deviceshell.cpp @@ -73,11 +73,11 @@ QStringList DeviceShell::missingFeatures() const { return m_missingFeatures; } RunResult DeviceShell::run(const CommandLine &cmd, const QByteArray &stdInData) { - // If the script failed to install, use QtcProcess directly instead. + // If the script failed to install, use Process directly instead. bool useProcess = m_shellScriptState == State::Failed; // Transferring large amounts of stdInData is slow via the shell script. - // Use QtcProcess directly if the size exceeds 100kb. + // Use Process directly if the size exceeds 100kb. useProcess |= stdInData.size() > (1024 * 100); if (useProcess) { diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index de70d4bf9b..313da1cd7c 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -91,7 +91,7 @@ inline bool isWindowsDriveLetter(QChar ch); executed on the associated OS. \note The FilePath passed as executable to a CommandLine is typically - not touched by user code. QtcProcess will use it to determine + not touched by user code. The Process will use it to determine the remote system and apply the necessary conversions internally. \li FilePath::toFSPathString() diff --git a/src/libs/utils/launchersocket.cpp b/src/libs/utils/launchersocket.cpp index ccf2ee7b2f..b1ffb77055 100644 --- a/src/libs/utils/launchersocket.cpp +++ b/src/libs/utils/launchersocket.cpp @@ -228,7 +228,7 @@ void CallerHandle::start(const QString &program, const QStringList &arguments) auto startWhenRunning = [&program, &oldProgram = m_command] { qWarning() << "Trying to start" << program << "while" << oldProgram - << "is still running for the same QtcProcess instance." + << "is still running for the same Process instance." << "The current call will be ignored."; }; QTC_ASSERT(m_processState == QProcess::NotRunning, startWhenRunning(); return); @@ -622,7 +622,7 @@ void LauncherSocket::handleSocketDataAvailable() } } else { // qDebug() << "No handler for token" << m_packetParser.token() << m_handles; - // in this case the QtcProcess was canceled and deleted + // in this case the Process was canceled and deleted } handleSocketDataAvailable(); } diff --git a/src/libs/utils/launchersocket.h b/src/libs/utils/launchersocket.h index 3a9be73e65..be1b7f7f9b 100644 --- a/src/libs/utils/launchersocket.h +++ b/src/libs/utils/launchersocket.h @@ -124,7 +124,7 @@ private: // Moved to the launcher thread, returned to caller's thread. // It's assumed that this object will be alive at least -// as long as the corresponding QtcProcess is alive. +// as long as the corresponding Process is alive. class LauncherHandle : public QObject { diff --git a/src/libs/utils/macroexpander.cpp b/src/libs/utils/macroexpander.cpp index b1ce40d25a..644268d7cf 100644 --- a/src/libs/utils/macroexpander.cpp +++ b/src/libs/utils/macroexpander.cpp @@ -196,7 +196,7 @@ using namespace Internal; you would use the one provided by the variable manager). Mostly the same as MacroExpander::expandedString(), but also has a variant that does the replacement inline instead of returning a new string. - \li Using Utils::QtcProcess::expandMacros(). This expands the string while conforming to the + \li Using Utils::CommandLine::expandMacros(). This expands the string while conforming to the quoting rules of the platform it is run on. Use this function with the variable manager's macro expander if your string will be passed as a command line parameter string to an external command. diff --git a/src/libs/utils/process.cpp b/src/libs/utils/process.cpp index 1b11bd8645..f25bc3d3fd 100644 --- a/src/libs/utils/process.cpp +++ b/src/libs/utils/process.cpp @@ -171,9 +171,9 @@ enum { syncDebug = 0 }; enum { defaultMaxHangTimerCount = 10 }; -static Q_LOGGING_CATEGORY(processLog, "qtc.utils.qtcprocess", QtWarningMsg) -static Q_LOGGING_CATEGORY(processStdoutLog, "qtc.utils.qtcprocess.stdout", QtWarningMsg) -static Q_LOGGING_CATEGORY(processStderrLog, "qtc.utils.qtcprocess.stderr", QtWarningMsg) +static Q_LOGGING_CATEGORY(processLog, "qtc.utils.process", QtWarningMsg) +static Q_LOGGING_CATEGORY(processStdoutLog, "qtc.utils.process.stdout", QtWarningMsg) +static Q_LOGGING_CATEGORY(processStderrLog, "qtc.utils.process.stderr", QtWarningMsg) static DeviceProcessHooks s_deviceHooks; @@ -454,7 +454,7 @@ private: void doDefaultStart(const QString &program, const QStringList &arguments) final { QTC_ASSERT(QThread::currentThread()->eventDispatcher(), - qWarning("QtcProcess::start(): Starting a process in a non QThread thread " + qWarning("Process::start(): Starting a process in a non QThread thread " "may cause infinite hang when destroying the running process.")); ProcessStartHandler *handler = m_process->processStartHandler(); handler->setProcessMode(m_setup.m_processMode); @@ -684,7 +684,7 @@ private: class GeneralProcessBlockingImpl : public ProcessBlockingInterface { public: - GeneralProcessBlockingImpl(QtcProcessPrivate *parent); + GeneralProcessBlockingImpl(ProcessPrivate *parent); void flush() { flushSignals(takeAllSignals()); } bool flushFor(ProcessSignalType signalType) { @@ -708,16 +708,16 @@ private: void handleReadyReadSignal(const ReadyReadSignal *launcherSignal); void handleDoneSignal(const DoneSignal *launcherSignal); - QtcProcessPrivate *m_caller = nullptr; + ProcessPrivate *m_caller = nullptr; std::unique_ptr m_processHandler; mutable QMutex m_mutex; QList m_signals; }; -class QtcProcessPrivate : public QObject +class ProcessPrivate : public QObject { public: - explicit QtcProcessPrivate(Process *parent) + explicit ProcessPrivate(Process *parent) : QObject(parent) , q(parent) , m_killTimer(this) @@ -754,11 +754,11 @@ public: m_process.reset(process); m_process->setParent(this); connect(m_process.get(), &ProcessInterface::started, - this, &QtcProcessPrivate::handleStarted); + this, &ProcessPrivate::handleStarted); connect(m_process.get(), &ProcessInterface::readyRead, - this, &QtcProcessPrivate::handleReadyRead); + this, &ProcessPrivate::handleReadyRead); connect(m_process.get(), &ProcessInterface::done, - this, &QtcProcessPrivate::handleDone); + this, &ProcessPrivate::handleDone); m_blockingInterface.reset(process->processBlockingInterface()); if (!m_blockingInterface) @@ -899,7 +899,7 @@ void ProcessInterfaceHandler::appendSignal(ProcessInterfaceSignal *newSignal) QMetaObject::invokeMethod(m_caller, &GeneralProcessBlockingImpl::flush); } -GeneralProcessBlockingImpl::GeneralProcessBlockingImpl(QtcProcessPrivate *parent) +GeneralProcessBlockingImpl::GeneralProcessBlockingImpl(ProcessPrivate *parent) : m_caller(parent) , m_processHandler(new ProcessInterfaceHandler(this, parent->m_process.get())) { @@ -907,7 +907,7 @@ GeneralProcessBlockingImpl::GeneralProcessBlockingImpl(QtcProcessPrivate *parent parent->m_process.get()->setParent(m_processHandler.get()); m_processHandler->setParent(this); // So the hierarchy looks like: - // QtcProcessPrivate + // ProcessPrivate // | // +- GeneralProcessBlockingImpl // | @@ -1021,7 +1021,7 @@ void GeneralProcessBlockingImpl::appendSignal(ProcessInterfaceSignal *newSignal) m_signals.append(newSignal); } -bool QtcProcessPrivate::waitForSignal(ProcessSignalType newSignal, int msecs) +bool ProcessPrivate::waitForSignal(ProcessSignalType newSignal, int msecs) { const QDeadlineTimer timeout(msecs); const QDeadlineTimer currentKillTimeout(m_killTimer.remainingTime()); @@ -1037,13 +1037,13 @@ bool QtcProcessPrivate::waitForSignal(ProcessSignalType newSignal, int msecs) return result; } -Qt::ConnectionType QtcProcessPrivate::connectionType() const +Qt::ConnectionType ProcessPrivate::connectionType() const { return (m_process->thread() == thread()) ? Qt::DirectConnection : Qt::BlockingQueuedConnection; } -void QtcProcessPrivate::sendControlSignal(ControlSignal controlSignal) +void ProcessPrivate::sendControlSignal(ControlSignal controlSignal) { QTC_ASSERT(QThread::currentThread() == thread(), return); if (!m_process || (m_state == QProcess::NotRunning)) @@ -1057,7 +1057,7 @@ void QtcProcessPrivate::sendControlSignal(ControlSignal controlSignal) }, connectionType()); } -void QtcProcessPrivate::clearForRun() +void ProcessPrivate::clearForRun() { m_hangTimerCount = 0; m_stdOut.clearForRun(); @@ -1073,7 +1073,7 @@ void QtcProcessPrivate::clearForRun() m_resultData = {}; } -ProcessResult QtcProcessPrivate::interpretExitCode(int exitCode) +ProcessResult ProcessPrivate::interpretExitCode(int exitCode) { if (m_exitCodeInterpreter) return m_exitCodeInterpreter(exitCode); @@ -1085,16 +1085,16 @@ ProcessResult QtcProcessPrivate::interpretExitCode(int exitCode) } // Internal /*! - \class Utils::QtcProcess + \class Utils::Process - \brief The QtcProcess class provides functionality for with processes. + \brief The Process class provides functionality for with processes. \sa Utils::ProcessArgs */ Process::Process(QObject *parent) : QObject(parent), - d(new QtcProcessPrivate(this)) + d(new ProcessPrivate(this)) { qRegisterMetaType("ProcessResultData"); static int qProcessExitStatusMeta = qRegisterMetaType(); @@ -1105,7 +1105,7 @@ Process::Process(QObject *parent) Process::~Process() { - QTC_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting QtcProcess instance directly from " + QTC_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting Process instance directly from " "one of its signal handlers will lead to crash!")); if (d->m_process) d->m_process->disconnect(); @@ -1202,7 +1202,7 @@ void Process::start() { QTC_ASSERT(state() == QProcess::NotRunning, return); QTC_ASSERT(!(d->m_process && d->m_guard.isLocked()), - qWarning("Restarting the QtcProcess directly from one of its signal handlers will " + qWarning("Restarting the Process directly from one of its signal handlers will " "lead to crash! Consider calling close() prior to direct restart.")); d->clearForRun(); ProcessInterface *processImpl = nullptr; @@ -1497,7 +1497,7 @@ bool Process::waitForStarted(int msecs) return true; if (d->m_state == QProcess::NotRunning) return false; - return s_waitForStarted.measureAndRun(&QtcProcessPrivate::waitForSignal, d, + return s_waitForStarted.measureAndRun(&ProcessPrivate::waitForSignal, d, ProcessSignalType::Started, msecs); } @@ -1726,7 +1726,7 @@ const QStringList Process::stdErrLines() const QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const Process &r) { QDebug nsp = str.nospace(); - nsp << "QtcProcess: result=" + nsp << "Process: result=" << int(r.d->m_result) << " ex=" << r.exitCode() << '\n' << r.d->m_stdOut.rawData.size() << " bytes stdout, stderr=" << r.d->m_stdErr.rawData << '\n'; return str; @@ -1856,7 +1856,7 @@ void Process::runBlocking(EventLoopMode eventLoopMode) // Do not start the event loop in that case. if (state() == QProcess::Starting) { QTimer timer(this); - connect(&timer, &QTimer::timeout, d, &QtcProcessPrivate::slotTimeout); + connect(&timer, &QTimer::timeout, d, &ProcessPrivate::slotTimeout); timer.setInterval(1000); timer.start(); #ifdef QT_GUI_LIB @@ -1936,7 +1936,7 @@ void Process::setTextChannelMode(Channel channel, TextChannelMode mode) const TextChannelCallback callback = (channel == Channel::Output) ? outputCb : errorCb; ChannelBuffer *buffer = channel == Channel::Output ? &d->m_stdOut : &d->m_stdErr; QTC_ASSERT(buffer->m_textChannelMode == TextChannelMode::Off, qWarning() - << "QtcProcess::setTextChannelMode(): Changing text channel mode for" + << "Process::setTextChannelMode(): Changing text channel mode for" << (channel == Channel::Output ? "Output": "Error") << "channel while it was previously set for this channel."); buffer->m_textChannelMode = mode; @@ -1965,7 +1965,7 @@ TextChannelMode Process::textChannelMode(Channel channel) const return buffer->m_textChannelMode; } -void QtcProcessPrivate::slotTimeout() +void ProcessPrivate::slotTimeout() { if (!m_waitingForUser && (++m_hangTimerCount > m_maxHangTimerCount)) { if (debug) @@ -1986,7 +1986,7 @@ void QtcProcessPrivate::slotTimeout() } } -void QtcProcessPrivate::handleStarted(qint64 processId, qint64 applicationMainThreadId) +void ProcessPrivate::handleStarted(qint64 processId, qint64 applicationMainThreadId) { QTC_CHECK(m_state == QProcess::Starting); m_state = QProcess::Running; @@ -1996,7 +1996,7 @@ void QtcProcessPrivate::handleStarted(qint64 processId, qint64 applicationMainTh emitGuardedSignal(&Process::started); } -void QtcProcessPrivate::handleReadyRead(const QByteArray &outputData, const QByteArray &errorData) +void ProcessPrivate::handleReadyRead(const QByteArray &outputData, const QByteArray &errorData) { QTC_CHECK(m_state == QProcess::Running); @@ -2025,7 +2025,7 @@ void QtcProcessPrivate::handleReadyRead(const QByteArray &outputData, const QByt } } -void QtcProcessPrivate::handleDone(const ProcessResultData &data) +void ProcessPrivate::handleDone(const ProcessResultData &data) { m_killTimer.stop(); const bool wasCanceled = m_resultData.m_canceledByUser; @@ -2091,7 +2091,7 @@ static QString blockingMessage(const QVariant &variant) return "blocking without event loop"; } -void QtcProcessPrivate::setupDebugLog() +void ProcessPrivate::setupDebugLog() { if (!processLog().isDebugEnabled()) return; @@ -2140,7 +2140,7 @@ void QtcProcessPrivate::setupDebugLog() }); } -void QtcProcessPrivate::storeEventLoopDebugInfo(const QVariant &value) +void ProcessPrivate::storeEventLoopDebugInfo(const QVariant &value) { if (processLog().isDebugEnabled()) setProperty(QTC_PROCESS_BLOCKING_TYPE, value); diff --git a/src/libs/utils/process.h b/src/libs/utils/process.h index b79cac2d39..6de4f4418b 100644 --- a/src/libs/utils/process.h +++ b/src/libs/utils/process.h @@ -22,7 +22,7 @@ QT_END_NAMESPACE namespace Utils { -namespace Internal { class QtcProcessPrivate; } +namespace Internal { class ProcessPrivate; } namespace Pty { class Data; } class Environment; @@ -131,7 +131,7 @@ public: // Other enhancements. // These (or some of them) may be potentially moved outside of the class. - // For some we may aggregate in another public utils class (or subclass of QtcProcess)? + // Some of them could be aggregated in another public utils class. // TODO: Unused currently? Should it serve as a compartment for contrary of remoteEnvironment? static Environment systemEnvironmentForBinary(const FilePath &filePath); @@ -198,8 +198,8 @@ signals: private: friend QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const Process &r); - friend class Internal::QtcProcessPrivate; - Internal::QtcProcessPrivate *d = nullptr; + friend class Internal::ProcessPrivate; + Internal::ProcessPrivate *d = nullptr; }; class DeviceProcessHooks diff --git a/src/libs/utils/processinterface.h b/src/libs/utils/processinterface.h index e06076d60b..a0460c5af3 100644 --- a/src/libs/utils/processinterface.h +++ b/src/libs/utils/processinterface.h @@ -14,7 +14,7 @@ namespace Utils { -namespace Internal { class QtcProcessPrivate; } +namespace Internal { class ProcessPrivate; } namespace Pty { @@ -104,7 +104,7 @@ private: // - Done is being called in Starting or Running state. virtual bool waitForSignal(ProcessSignalType signalType, int msecs) = 0; - friend class Internal::QtcProcessPrivate; + friend class Internal::ProcessPrivate; }; class QTCREATOR_UTILS_EXPORT ProcessInterface : public QObject @@ -143,7 +143,7 @@ private: virtual ProcessBlockingInterface *processBlockingInterface() const { return nullptr; } friend class Process; - friend class Internal::QtcProcessPrivate; + friend class Internal::ProcessPrivate; }; } // namespace Utils diff --git a/src/libs/utils/processreaper.cpp b/src/libs/utils/processreaper.cpp index 117d5fcb33..f7235d5529 100644 --- a/src/libs/utils/processreaper.cpp +++ b/src/libs/utils/processreaper.cpp @@ -33,7 +33,7 @@ never ending running process: It looks like when you call terminate() for the adb.exe, it won't stop, never, even after default 30 seconds timeout. The same happens for blocking processes tested in - tst_QtcProcess::killBlockingProcess(). It's hard to say whether any process on Windows can + tst_Process::killBlockingProcess(). It's hard to say whether any process on Windows can be finished by a call to terminate(). Until now, no such a process has been found. Further call to kill() (after a call to terminate()) finishes the process quickly. -- cgit v1.2.3 From b05666d5c53046076415fd4d06abdd82a0674f95 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Tue, 9 May 2023 23:36:18 +0200 Subject: TaskTree: Adapt docs naming to the recent renaming Task-number: QTCREATORBUG-29102 Change-Id: I1edf4d4e38c8853560bca3d3a6da55322a6764d6 Reviewed-by: hjk Reviewed-by: Qt CI Bot --- src/libs/utils/tasktree.cpp | 128 ++++++++++++++++++++++---------------------- 1 file changed, 64 insertions(+), 64 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/tasktree.cpp b/src/libs/utils/tasktree.cpp index ee0982072b..dccb023ff3 100644 --- a/src/libs/utils/tasktree.cpp +++ b/src/libs/utils/tasktree.cpp @@ -673,15 +673,15 @@ void TaskNode::invokeEndHandler(bool success) declarative way. Use the Tasking namespace to build extensible, declarative task tree - structures that contain possibly asynchronous tasks, such as QtcProcess, - FileTransfer, or AsyncTask. TaskTree structures enable you + structures that contain possibly asynchronous tasks, such as Process, + FileTransfer, or Async. TaskTree structures enable you to create a sophisticated mixture of a parallel or sequential flow of tasks in the form of a tree and to run it any time later. \section1 Root Element and Tasks The TaskTree has a mandatory Group root element, which may contain - any number of tasks of various types, such as Process, FileTransfer, + any number of tasks of various types, such as ProcessTask, FileTransferTask, or AsyncTask: \code @@ -689,9 +689,9 @@ void TaskNode::invokeEndHandler(bool success) using namespace Tasking; const Group root { - Process(...), - Async(...), - Transfer(...) + ProcessTask(...), + AsyncTask(...), + FileTransferTask(...) }; TaskTree *taskTree = new TaskTree(root); @@ -701,11 +701,11 @@ void TaskNode::invokeEndHandler(bool success) \endcode The task tree above has a top level element of the Group type that contains - tasks of the type QtcProcess, FileTransfer, and AsyncTask. + tasks of the type ProcessTask, FileTransferTask, and AsyncTask. After taskTree->start() is called, the tasks are run in a chain, starting - with Process. When Process finishes successfully, the Async task is + with ProcessTask. When the ProcessTask finishes successfully, the AsyncTask task is started. Finally, when the asynchronous task finishes successfully, the - FileTransfer task is started. + FileTransferTask task is started. When the last running task finishes with success, the task tree is considered to have run successfully and the TaskTree::done() signal is emitted. @@ -723,27 +723,27 @@ void TaskNode::invokeEndHandler(bool success) const Group root { Group { parallel, - Process(...), - Async(...) + ProcessTask(...), + AsyncTask(...) }, - Transfer(...) + FileTransferTask(...) }; \endcode The example above differs from the first example in that the root element has - a subgroup that contains the Process and Async tasks. The subgroup is a - sibling element of the Transfer task in the root. The subgroup contains an + a subgroup that contains the ProcessTask and AsyncTask. The subgroup is a + sibling element of the FileTransferTask in the root. The subgroup contains an additional \e parallel element that instructs its Group to execute its tasks in parallel. - So, when the tree above is started, the Process and Async tasks start + So, when the tree above is started, the ProcessTask and AsyncTask start immediately and run in parallel. Since the root group doesn't contain a \e parallel element, its direct child tasks are run in sequence. Thus, the - Transfer task starts when the whole subgroup finishes. The group is + FileTransferTask starts when the whole subgroup finishes. The group is considered as finished when all its tasks have finished. The order in which the tasks finish is not relevant. - So, depending on which task lasts longer (Process or Async), the + So, depending on which task lasts longer (ProcessTask or AsyncTask), the following scenarios can take place: \table @@ -757,35 +757,35 @@ void TaskNode::invokeEndHandler(bool success) \li Sub Group starts \li Sub Group starts \row - \li Process starts - \li Process starts + \li ProcessTask starts + \li ProcessTask starts \row - \li Async starts - \li Async starts + \li AsyncTask starts + \li AsyncTask starts \row \li ... \li ... \row - \li \b {Process finishes} - \li \b {Async finishes} + \li \b {ProcessTask finishes} + \li \b {AsyncTask finishes} \row \li ... \li ... \row - \li \b {Async finishes} - \li \b {Process finishes} + \li \b {AsyncTask finishes} + \li \b {ProcessTask finishes} \row \li Sub Group finishes \li Sub Group finishes \row - \li Transfer starts - \li Transfer starts + \li FileTransferTask starts + \li FileTransferTask starts \row \li ... \li ... \row - \li Transfer finishes - \li Transfer finishes + \li FileTransferTask finishes + \li FileTransferTask finishes \row \li Root Group finishes \li Root Group finishes @@ -798,24 +798,24 @@ void TaskNode::invokeEndHandler(bool success) The presented scenarios assume that all tasks run successfully. If a task fails during execution, the task tree finishes with an error. In particular, - when Process finishes with an error while Async is still being executed, - Async is automatically stopped, the subgroup finishes with an error, - Transfer is skipped, and the tree finishes with an error. + when ProcessTask finishes with an error while AsyncTask is still being executed, + the AsyncTask is automatically stopped, the subgroup finishes with an error, + the FileTransferTask is skipped, and the tree finishes with an error. \section1 Task Types Each task type is associated with its corresponding task class that executes - the task. For example, a Process task inside a task tree is associated with - the QtcProcess class that executes the process. The associated objects are + the task. For example, a ProcessTask inside a task tree is associated with + the Process class that executes the process. The associated objects are automatically created, started, and destructed exclusively by the task tree at the appropriate time. - If a root group consists of five sequential Process tasks, and the task tree - executes the group, it creates an instance of QtcProcess for the first - Process task and starts it. If the QtcProcess instance finishes successfully, - the task tree destructs it and creates a new QtcProcess instance for the - second Process, and so on. If the first task finishes with an error, the task - tree stops creating QtcProcess instances, and the root group finishes with an + If a root group consists of five sequential ProcessTask tasks, and the task tree + executes the group, it creates an instance of Process for the first + ProcessTask and starts it. If the Process instance finishes successfully, + the task tree destructs it and creates a new Process instance for the + second ProcessTask, and so on. If the first task finishes with an error, the task + tree stops creating Process instances, and the root group finishes with an error. The following table shows examples of task types and their corresponding task @@ -827,19 +827,19 @@ void TaskNode::invokeEndHandler(bool success) \li Associated Task Class \li Brief Description \row - \li Process - \li Utils::QtcProcess + \li ProcessTask + \li Utils::Process \li Starts processes. \row - \li Async - \li Utils::AsyncTask + \li AsyncTask + \li Utils::Async \li Starts asynchronous tasks; run in separate thread. \row - \li Tree + \li TaskTreeTask \li Utils::TaskTree \li Starts a nested task tree. \row - \li Transfer + \li FileTransferTask \li ProjectExplorer::FileTransfer \li Starts file transfer between different devices. \endtable @@ -856,15 +856,15 @@ void TaskNode::invokeEndHandler(bool success) handler should always take a \e reference to the associated task class object: \code - const auto onSetup = [](QtcProcess &process) { + const auto onSetup = [](Process &process) { process.setCommand({"sleep", {"3"}}); }; const Group root { - Process(onSetup) + ProcessTask(onSetup) }; \endcode - You can modify the passed QtcProcess in the setup handler, so that the task + You can modify the passed Process in the setup handler, so that the task tree can start the process according to your configuration. You do not need to call \e {process.start();} in the setup handler, as the task tree calls it when needed. The setup handler is mandatory @@ -904,21 +904,21 @@ void TaskNode::invokeEndHandler(bool success) to the associated task class object: \code - const auto onSetup = [](QtcProcess &process) { + const auto onSetup = [](Process &process) { process.setCommand({"sleep", {"3"}}); }; - const auto onDone = [](const QtcProcess &process) { + const auto onDone = [](const Process &process) { qDebug() << "Success" << process.cleanedStdOut(); }; - const auto onError = [](const QtcProcess &process) { + const auto onError = [](const Process &process) { qDebug() << "Failure" << process.cleanedStdErr(); }; const Group root { - Process(onSetup, onDone, onError) + ProcessTask(onSetup, onDone, onError) }; \endcode - The done and error handlers may collect output data from QtcProcess, and store it + The done and error handlers may collect output data from Process, and store it for further processing or perform additional actions. The done handler is optional. When used, it must be the second argument of the task constructor. The error handler must always be the third argument. @@ -944,7 +944,7 @@ void TaskNode::invokeEndHandler(bool success) }; const Group root { OnGroupSetup(onGroupSetup), - Process(...) + ProcessTask(...) }; \endcode @@ -967,17 +967,17 @@ void TaskNode::invokeEndHandler(bool success) OnGroupSetup([] { qDebug() << "Root setup"; }), Group { OnGroupSetup([] { qDebug() << "Group 1 setup"; return TaskAction::Continue; }), - Process(...) // Process 1 + ProcessTask(...) // Process 1 }, Group { OnGroupSetup([] { qDebug() << "Group 2 setup"; return TaskAction::StopWithDone; }), - Process(...) // Process 2 + ProcessTask(...) // Process 2 }, Group { OnGroupSetup([] { qDebug() << "Group 3 setup"; return TaskAction::StopWithError; }), - Process(...) // Process 3 + ProcessTask(...) // Process 3 }, - Process(...) // Process 4 + ProcessTask(...) // Process 4 }; \endcode @@ -1039,7 +1039,7 @@ void TaskNode::invokeEndHandler(bool success) \code const Group root { OnGroupSetup([] { qDebug() << "Root setup"; }), - Process(...), + ProcessTask(...), OnGroupDone([] { qDebug() << "Root finished with success"; }), OnGroupError([] { qDebug() << "Root finished with error"; }) }; @@ -1191,7 +1191,7 @@ void TaskNode::invokeEndHandler(bool success) const TreeStorage storage; const auto onLoaderSetup = [source](Async &async) { - async.setAsyncCallData(&load, source); + async.setConcurrentCallData(&load, source); }; // [4] runtime: task tree activates the instance from [5] before invoking handler const auto onLoaderDone = [storage](const Async &async) { @@ -1200,7 +1200,7 @@ void TaskNode::invokeEndHandler(bool success) // [4] runtime: task tree activates the instance from [5] before invoking handler const auto onSaverSetup = [storage, destination](Async &async) { - async.setAsyncCallData(&save, destination, storage->content); + async.setConcurrentCallData(&save, destination, storage->content); }; const auto onSaverDone = [](const Async &async) { qDebug() << "Save done successfully"; @@ -1209,8 +1209,8 @@ void TaskNode::invokeEndHandler(bool success) const Group root { // [5] runtime: task tree creates an instance of CopyStorage when root is entered Storage(storage), - Async(onLoaderSetup, onLoaderDone), - Async(onSaverSetup, onSaverDone) + AsyncTask(onLoaderSetup, onLoaderDone), + AsyncTask(onSaverSetup, onSaverDone) }; return root; } -- cgit v1.2.3 From c68a013a3e02cf2dde6a7683eb15e87b655fd523 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Tue, 9 May 2023 23:43:18 +0200 Subject: Tasking::Tree: Rename Tree into TaskTreeTask Task-number: QTCREATORBUG-29102 Change-Id: I70073bcb44a712c427c8c5aea42c48dbc30eebe0 Reviewed-by: Qt CI Bot Reviewed-by: hjk --- src/libs/utils/tasktree.cpp | 6 +++--- src/libs/utils/tasktree.h | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/tasktree.cpp b/src/libs/utils/tasktree.cpp index dccb023ff3..e27cd8f8bb 100644 --- a/src/libs/utils/tasktree.cpp +++ b/src/libs/utils/tasktree.cpp @@ -1264,7 +1264,7 @@ void TaskNode::invokeEndHandler(bool success) recipe described by the Group root element. As TaskTree is also an asynchronous task, it can be a part of another TaskTree. - To place a nested TaskTree inside another TaskTree, insert the Tasking::Tree + To place a nested TaskTree inside another TaskTree, insert the Tasking::TaskTreeTask element into other tree's Group element. TaskTree reports progress of completed tasks when running. The progress value @@ -1455,13 +1455,13 @@ void TaskTree::setupStorageHandler(const Tasking::TreeStorageBase &storage, } } -TaskTreeAdapter::TaskTreeAdapter() +TaskTreeTaskAdapter::TaskTreeTaskAdapter() { connect(task(), &TaskTree::done, this, [this] { emit done(true); }); connect(task(), &TaskTree::errorOccurred, this, [this] { emit done(false); }); } -void TaskTreeAdapter::start() +void TaskTreeTaskAdapter::start() { task()->start(); } diff --git a/src/libs/utils/tasktree.h b/src/libs/utils/tasktree.h index 418feb797e..46333c3dc8 100644 --- a/src/libs/utils/tasktree.h +++ b/src/libs/utils/tasktree.h @@ -413,10 +413,10 @@ private: TaskTreePrivate *d; }; -class QTCREATOR_UTILS_EXPORT TaskTreeAdapter : public Tasking::TaskAdapter +class QTCREATOR_UTILS_EXPORT TaskTreeTaskAdapter : public Tasking::TaskAdapter { public: - TaskTreeAdapter(); + TaskTreeTaskAdapter(); void start() final; }; @@ -431,4 +431,4 @@ template \ using CustomTaskName = CustomTask>;\ } // namespace Utils::Tasking -QTC_DECLARE_CUSTOM_TASK(Tree, Utils::TaskTreeAdapter); +QTC_DECLARE_CUSTOM_TASK(TaskTreeTask, Utils::TaskTreeTaskAdapter); -- cgit v1.2.3 From 905d76961d9827c4ae55304db9f74f2201a0ed98 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Tue, 9 May 2023 15:11:18 +0200 Subject: CPlusPlus: Don't double uniquify CppDocument::includedFiles removes duplicates. Snapshot::allIncludesForDocument also removes duplicates. Once is enough. This doubles test scan performance on my machine. Change-Id: I892908cf0820cfa11854ac3d82e9175d1fc38043 Reviewed-by: Christian Kandeler --- src/libs/cplusplus/CppDocument.cpp | 7 ++++--- src/libs/cplusplus/CppDocument.h | 7 ++++++- 2 files changed, 10 insertions(+), 4 deletions(-) (limited to 'src/libs') diff --git a/src/libs/cplusplus/CppDocument.cpp b/src/libs/cplusplus/CppDocument.cpp index d28fdd50ba..07714c0fe2 100644 --- a/src/libs/cplusplus/CppDocument.cpp +++ b/src/libs/cplusplus/CppDocument.cpp @@ -296,12 +296,13 @@ void Document::setLastModified(const QDateTime &lastModified) _lastModified = lastModified; } -FilePaths Document::includedFiles() const +FilePaths Document::includedFiles(Duplicates duplicates) const { FilePaths files; for (const Include &i : std::as_const(_resolvedIncludes)) files.append(i.resolvedFileName()); - FilePath::removeDuplicates(files); + if (duplicates == Duplicates::Remove) + FilePath::removeDuplicates(files); return files; } @@ -771,7 +772,7 @@ QSet Snapshot::allIncludesForDocument(const FilePath &filePath) const while (!files.isEmpty()) { FilePath file = files.pop(); if (Document::Ptr doc = document(file)) { - const FilePaths includedFiles = doc->includedFiles(); + const FilePaths includedFiles = doc->includedFiles(Document::Duplicates::Keep); for (const FilePath &inc : includedFiles) { if (!result.contains(inc)) { result.insert(inc); diff --git a/src/libs/cplusplus/CppDocument.h b/src/libs/cplusplus/CppDocument.h index 679663f2d9..00bebf277d 100644 --- a/src/libs/cplusplus/CppDocument.h +++ b/src/libs/cplusplus/CppDocument.h @@ -297,7 +297,12 @@ public: } }; - Utils::FilePaths includedFiles() const; + enum class Duplicates { + Remove, + Keep, + }; + + Utils::FilePaths includedFiles(Duplicates duplicates = Duplicates::Remove) const; void addIncludeFile(const Include &include); const QList &resolvedIncludes() const -- cgit v1.2.3 From d7781c16f7dbfa1b850fd129b0b108c2d2a91070 Mon Sep 17 00:00:00 2001 From: hjk Date: Wed, 10 May 2023 08:02:08 +0200 Subject: Layouting: Remove some now unused functions Change-Id: I9e91bdbf68c38da22bd2378cb7d9596306bbb413 Reviewed-by: Eike Ziller --- src/libs/utils/layoutbuilder.cpp | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index f23b5e025f..251569fdd2 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -239,9 +239,6 @@ public: void addItem(const LayoutItem &item); void addItems(const LayoutItems &items); - void addRow(const LayoutItems &items); - - bool isForm() const; QList stack; }; @@ -324,7 +321,7 @@ void doAddWidget(LayoutBuilder &builder, QWidget *widget) A LayoutBuilder instance is typically used locally within a function and never stored. - \sa addItem(), addItems(), addRow() + \sa addItem(), addItems() */ @@ -346,12 +343,6 @@ void LayoutBuilder::addItems(const LayoutItems &items) addItemHelper(*this, item); } -void LayoutBuilder::addRow(const LayoutItems &items) -{ - addItem(br); - addItems(items); -} - /*! This starts a new row containing \a items. The row can be further extended by other items using \c addItem() or \c addItems(). @@ -401,11 +392,6 @@ QWidget *LayoutItem::emerge() return w; } -bool LayoutBuilder::isForm() const -{ - return qobject_cast(stack.last().layout); -} - static void layoutExit(LayoutBuilder &builder) { builder.stack.last().flush(); -- cgit v1.2.3 From fc95d7a73730f67584891471d912dfd7f65a9cc2 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Wed, 10 May 2023 08:53:35 +0200 Subject: Utils: Improve FilePath::sort Remove the conversion to/from QString for sorting. Change-Id: I89921328b6d9e952c802d41998495bd2ffbb9f99 Reviewed-by: hjk --- src/libs/utils/filepath.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 313da1cd7c..9bf4780596 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -1531,11 +1531,17 @@ void FilePath::removeDuplicates(FilePaths &files) void FilePath::sort(FilePaths &files) { - // FIXME: Improve. - // FIXME: This drops the osType information, which is not correct. - QStringList list = transform(files, &FilePath::toString); - list.sort(); - files = FileUtils::toFilePathList(list); + std::sort(files.begin(), files.end(), [](const FilePath &a, const FilePath &b) { + const int scheme = a.scheme().compare(b.scheme()); + if (scheme != 0) + return scheme < 0; + + const int host = a.host().compare(b.host()); + if (host != 0) + return host < 0; + + return a.pathView() < b.pathView(); + }); } void join(QString &left, const QString &right) -- cgit v1.2.3 From 3f2832289de7a1069937275d22b02c03aabe8776 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Tue, 9 May 2023 13:49:57 +0200 Subject: Utils: move TextPosition/Range to textutils Change-Id: Id94a7a96f3b0f978e94850d67eb4b8fba6c18fe2 Reviewed-by: Jarek Kobus --- src/libs/utils/searchresultitem.cpp | 30 ----------------------------- src/libs/utils/searchresultitem.h | 38 ++++--------------------------------- src/libs/utils/textutils.cpp | 36 +++++++++++++++++++++++++++++++---- src/libs/utils/textutils.h | 30 +++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 68 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/searchresultitem.cpp b/src/libs/utils/searchresultitem.cpp index 1fe0d953f6..2574473c93 100644 --- a/src/libs/utils/searchresultitem.cpp +++ b/src/libs/utils/searchresultitem.cpp @@ -5,26 +5,6 @@ namespace Utils { -int Search::TextRange::length(const QString &text) const -{ - if (begin.line == end.line) - return end.column - begin.column; - - const int lineCount = end.line - begin.line; - int index = text.indexOf(QChar::LineFeed); - int currentLine = 1; - while (index > 0 && currentLine < lineCount) { - ++index; - index = text.indexOf(QChar::LineFeed, index); - ++currentLine; - } - - if (index < 0) - return 0; - - return index - begin.column + end.column; -} - SearchResultColor::SearchResultColor(const QColor &textBg, const QColor &textFg, const QColor &highlightBg, const QColor &highlightFg, const QColor &functionBg, const QColor &functionFg) @@ -72,16 +52,6 @@ QTCREATOR_UTILS_EXPORT size_t qHash(SearchResultColor::Style style, uint seed) return ::qHash(a, seed); } -bool Search::TextPosition::operator==(const Search::TextPosition &other) const -{ - return line == other.line && column == other.column; -} - -bool Search::TextRange::operator==(const Search::TextRange &other) const -{ - return begin == other.begin && end == other.end; -} - bool SearchResultItem::operator==(const SearchResultItem &other) const { return m_path == other.m_path && m_lineText == other.m_lineText diff --git a/src/libs/utils/searchresultitem.h b/src/libs/utils/searchresultitem.h index 080a44f24e..ea9d0332d5 100644 --- a/src/libs/utils/searchresultitem.h +++ b/src/libs/utils/searchresultitem.h @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -18,36 +19,6 @@ namespace Utils { -namespace Search { - -class QTCREATOR_UTILS_EXPORT TextPosition -{ -public: - int line = -1; // (0 or -1 for no line number) - int column = -1; // 0-based starting position for a mark (-1 for no mark) - - bool operator<(const TextPosition &other) const - { return line < other.line || (line == other.line && column < other.column); } - bool operator==(const TextPosition &other) const; - bool operator!=(const TextPosition &other) const { return !(operator==(other)); } -}; - -class QTCREATOR_UTILS_EXPORT TextRange -{ -public: - QString mid(const QString &text) const { return text.mid(begin.column, length(text)); } - int length(const QString &text) const; - - TextPosition begin; - TextPosition end; - - bool operator<(const TextRange &other) const { return begin < other.begin; } - bool operator==(const TextRange &other) const; - bool operator!=(const TextRange &other) const { return !(operator==(other)); } -}; - -} // namespace Search - class QTCREATOR_UTILS_EXPORT SearchResultColor { public: @@ -88,8 +59,8 @@ public: QVariant userData() const { return m_userData; } void setUserData(const QVariant &userData) { m_userData = userData; } - Search::TextRange mainRange() const { return m_mainRange; } - void setMainRange(const Search::TextRange &mainRange) { m_mainRange = mainRange; } + Text::Range mainRange() const { return m_mainRange; } + void setMainRange(const Text::Range &mainRange) { m_mainRange = mainRange; } void setMainRange(int line, int column, int length); bool useTextEditorFont() const { return m_useTextEditorFont; } @@ -116,7 +87,7 @@ private: QString m_lineText; // text to show for the item itself QIcon m_icon; // icon to show in front of the item (by be null icon to hide) QVariant m_userData; // user data for identification of the item - Search::TextRange m_mainRange; + Text::Range m_mainRange; bool m_useTextEditorFont = false; bool m_selectForReplacement = true; SearchResultColor::Style m_style = SearchResultColor::Style::Default; @@ -129,4 +100,3 @@ using SearchResultItems = QList; Q_DECLARE_METATYPE(Utils::SearchResultItem) Q_DECLARE_METATYPE(Utils::SearchResultItems) -Q_DECLARE_METATYPE(Utils::Search::TextPosition) diff --git a/src/libs/utils/textutils.cpp b/src/libs/utils/textutils.cpp index 1222038039..ec440a8902 100644 --- a/src/libs/utils/textutils.cpp +++ b/src/libs/utils/textutils.cpp @@ -6,8 +6,37 @@ #include #include -namespace Utils { -namespace Text { +namespace Utils::Text { + +bool Position::operator==(const Position &other) const +{ + return line == other.line && column == other.column; +} + +int Range::length(const QString &text) const +{ + if (begin.line == end.line) + return end.column - begin.column; + + const int lineCount = end.line - begin.line; + int index = text.indexOf(QChar::LineFeed); + int currentLine = 1; + while (index > 0 && currentLine < lineCount) { + ++index; + index = text.indexOf(QChar::LineFeed, index); + ++currentLine; + } + + if (index < 0) + return 0; + + return index - begin.column + end.column; +} + +bool Range::operator==(const Range &other) const +{ + return begin == other.begin && end == other.end; +} bool convertPosition(const QTextDocument *document, int pos, int *line, int *column) { @@ -211,5 +240,4 @@ void applyReplacements(QTextDocument *doc, const Replacements &replacements) editCursor.endEditBlock(); } -} // Text -} // Utils +} // namespace Utils::Text diff --git a/src/libs/utils/textutils.h b/src/libs/utils/textutils.h index e214f74659..ce8a450302 100644 --- a/src/libs/utils/textutils.h +++ b/src/libs/utils/textutils.h @@ -17,6 +17,34 @@ QT_END_NAMESPACE namespace Utils { namespace Text { +class QTCREATOR_UTILS_EXPORT Position +{ +public: + int line = 0; // 1-based + int column = -1; // 0-based + + bool operator<(const Position &other) const + { return line < other.line || (line == other.line && column < other.column); } + bool operator==(const Position &other) const; + + bool operator!=(const Position &other) const { return !(operator==(other)); } +}; + +class QTCREATOR_UTILS_EXPORT Range +{ +public: + QString mid(const QString &text) const { return text.mid(begin.column, length(text)); } + int length(const QString &text) const; + + Position begin; + Position end; + + bool operator<(const Range &other) const { return begin < other.begin; } + bool operator==(const Range &other) const; + + bool operator!=(const Range &other) const { return !(operator==(other)); } +}; + struct Replacement { Replacement() = default; @@ -66,3 +94,5 @@ QTCREATOR_UTILS_EXPORT QString utf16LineTextInUtf8Buffer(const QByteArray &utf8B } // Text } // Utils + +Q_DECLARE_METATYPE(Utils::Text::Position) -- cgit v1.2.3 From 2196e082958c97e4a39be93b3b12c237bf792cb2 Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Tue, 9 May 2023 20:38:51 +0200 Subject: LayoutBuilder: Avoid leak warnings in Application::exec() Change-Id: I9c05f3015b120220408076d58d29983d2194d9e1 Reviewed-by: hjk --- src/libs/utils/layoutbuilder.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index 251569fdd2..226120b823 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -613,12 +613,12 @@ Application::Application(std::initializer_list items) int Application::exec(int &argc, char *argv[]) { - auto app = new QApplication(argc, argv); + QApplication app(argc, argv); LayoutBuilder builder; addItemHelper(builder, *this); if (QWidget *widget = builder.stack.last().widget) widget->show(); - return app->exec(); + return app.exec(); } // "Properties" -- cgit v1.2.3 From 9c31267e58625ba804379e32a167985f1ed00763 Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Fri, 5 May 2023 12:33:31 +0200 Subject: Remove ineffective resize() calls This removes apparently unnecessary resize() calls on QWidgets based forms which get anyways added to layouts and resized. Most of these size values looked "accidental", i.e. neither divisible by 2 nor by 5, in most cases a remnant from the ui inlining. Change-Id: I95da3b93f2915ef955b5235e5c2ecc94b51f813a Reviewed-by: Reviewed-by: hjk --- src/libs/utils/filewizardpage.cpp | 1 - src/libs/utils/projectintropage.cpp | 1 - 2 files changed, 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/filewizardpage.cpp b/src/libs/utils/filewizardpage.cpp index aae79b1a49..cc1f83b696 100644 --- a/src/libs/utils/filewizardpage.cpp +++ b/src/libs/utils/filewizardpage.cpp @@ -45,7 +45,6 @@ FileWizardPage::FileWizardPage(QWidget *parent) : d(new FileWizardPagePrivate) { setTitle(Tr::tr("Choose the Location")); - resize(368, 102); d->m_defaultSuffixLabel = new QLabel; d->m_nameLabel = new QLabel; diff --git a/src/libs/utils/projectintropage.cpp b/src/libs/utils/projectintropage.cpp index 8df64b97f0..cb7018943d 100644 --- a/src/libs/utils/projectintropage.cpp +++ b/src/libs/utils/projectintropage.cpp @@ -66,7 +66,6 @@ ProjectIntroPage::ProjectIntroPage(QWidget *parent) : WizardPage(parent), d(new ProjectIntroPagePrivate) { - resize(355, 289); setTitle(Tr::tr("Introduction and Project Location")); d->m_descriptionLabel = new QLabel(this); -- cgit v1.2.3 From 5fa073543abaa67aa778c4c13d6eb4202e109a52 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Thu, 4 May 2023 14:14:43 +0200 Subject: Utils: Fix hasSuppressedQuestions QSettings::childKeys() only returns direct child values, not child groups. Therefore we also need to check QSettings::childGroups() here. We don't need to iterate all the keys. Keys are only created with "true" value. Change-Id: Ie4653dca1dfbad85ab895440a756c8e03aa78118 Reviewed-by: Qt CI Bot Reviewed-by: Eike Ziller --- src/libs/utils/checkablemessagebox.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/checkablemessagebox.cpp b/src/libs/utils/checkablemessagebox.cpp index 4420fc66cb..5d3011ff59 100644 --- a/src/libs/utils/checkablemessagebox.cpp +++ b/src/libs/utils/checkablemessagebox.cpp @@ -443,15 +443,9 @@ void CheckableMessageBox::resetAllDoNotAskAgainQuestions(QSettings *settings) bool CheckableMessageBox::hasSuppressedQuestions(QSettings *settings) { QTC_ASSERT(settings, return false); - bool hasSuppressed = false; settings->beginGroup(QLatin1String(kDoNotAskAgainKey)); - const QStringList childKeys = settings->childKeys(); - for (const QString &subKey : childKeys) { - if (settings->value(subKey, false).toBool()) { - hasSuppressed = true; - break; - } - } + const bool hasSuppressed = !settings->childKeys().isEmpty() + || !settings->childGroups().isEmpty(); settings->endGroup(); return hasSuppressed; } -- cgit v1.2.3 From 2d58d1b280959236968211f7811b040ae0d14daa Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Wed, 10 May 2023 08:54:25 +0200 Subject: Utils: Fix groupChecker functionality for aspects Change-Id: I1a44384cfebe8e23d8778ccb97f5846bfcbf0b9f Reviewed-by: hjk --- src/libs/utils/aspects.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index dc1888279a..675f6b116f 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -1431,7 +1431,9 @@ std::function BoolAspect::groupChecker() auto groupBox = qobject_cast(target); QTC_ASSERT(groupBox, return); registerSubWidget(groupBox); - d->m_groupBox = d->m_groupBox; + groupBox->setCheckable(true); + groupBox->setChecked(value()); + d->m_groupBox = groupBox; }; } -- cgit v1.2.3 From 5975657e7747052211310a9bfedaac1fa937fc19 Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Tue, 9 May 2023 13:20:04 +0200 Subject: Utils: Centralize style-related property names as constants This introduces string constants in Utils::StyleHelper. They are used by code all over in Qt Creator to tell ManhattanStyle how to paint certain widgets. Change-Id: Iecca36103f80084cd5fe93fcb6b18b8fbb3a32bb Reviewed-by: Christian Stenger Reviewed-by: Qt CI Bot --- src/libs/advanceddockingsystem/docksplitter.cpp | 4 +++- src/libs/utils/styledbar.cpp | 8 ++++---- src/libs/utils/stylehelper.cpp | 4 ++-- src/libs/utils/stylehelper.h | 15 +++++++++++++++ 4 files changed, 24 insertions(+), 7 deletions(-) (limited to 'src/libs') diff --git a/src/libs/advanceddockingsystem/docksplitter.cpp b/src/libs/advanceddockingsystem/docksplitter.cpp index 22efb63204..a10161da59 100644 --- a/src/libs/advanceddockingsystem/docksplitter.cpp +++ b/src/libs/advanceddockingsystem/docksplitter.cpp @@ -6,6 +6,8 @@ #include "ads_globals_p.h" #include "dockareawidget.h" +#include + #include #include #include @@ -30,7 +32,7 @@ namespace ADS , d(new DockSplitterPrivate(this)) { //setProperty("ads-splitter", true); // TODO - setProperty("minisplitter", true); + setProperty(Utils::StyleHelper::C_MINI_SPLITTER, true); setChildrenCollapsible(false); } diff --git a/src/libs/utils/styledbar.cpp b/src/libs/utils/styledbar.cpp index 7b00c4b495..4e7ec489fb 100644 --- a/src/libs/utils/styledbar.cpp +++ b/src/libs/utils/styledbar.cpp @@ -15,7 +15,7 @@ StyledBar::StyledBar(QWidget *parent) { StyleHelper::setPanelWidget(this); StyleHelper::setPanelWidgetSingleRow(this); - setProperty("lightColored", false); + setProperty(StyleHelper::C_LIGHT_COLORED, false); } void StyledBar::setSingleRow(bool singleRow) @@ -25,14 +25,14 @@ void StyledBar::setSingleRow(bool singleRow) bool StyledBar::isSingleRow() const { - return property("panelwidget_singlerow").toBool(); + return property(StyleHelper::C_PANEL_WIDGET_SINGLE_ROW).toBool(); } void StyledBar::setLightColored(bool lightColored) { if (isLightColored() == lightColored) return; - setProperty("lightColored", lightColored); + setProperty(StyleHelper::C_LIGHT_COLORED, lightColored); const QList children = findChildren(); for (QWidget *childWidget : children) childWidget->style()->polish(childWidget); @@ -40,7 +40,7 @@ void StyledBar::setLightColored(bool lightColored) bool StyledBar::isLightColored() const { - return property("lightColored").toBool(); + return property(StyleHelper::C_LIGHT_COLORED).toBool(); } void StyledBar::paintEvent(QPaintEvent *event) diff --git a/src/libs/utils/stylehelper.cpp b/src/libs/utils/stylehelper.cpp index 38a1b17931..315d9efb23 100644 --- a/src/libs/utils/stylehelper.cpp +++ b/src/libs/utils/stylehelper.cpp @@ -669,12 +669,12 @@ QLinearGradient StyleHelper::statusBarGradient(const QRect &statusBarRect) void StyleHelper::setPanelWidget(QWidget *widget, bool value) { - widget->setProperty("panelwidget", value); + widget->setProperty(C_PANEL_WIDGET, value); } void StyleHelper::setPanelWidgetSingleRow(QWidget *widget, bool value) { - widget->setProperty("panelwidget_singlerow", value); + widget->setProperty(C_PANEL_WIDGET_SINGLE_ROW, value); } bool StyleHelper::isQDSTheme() diff --git a/src/libs/utils/stylehelper.h b/src/libs/utils/stylehelper.h index 8ee91978b7..9e7c3c1911 100644 --- a/src/libs/utils/stylehelper.h +++ b/src/libs/utils/stylehelper.h @@ -25,6 +25,21 @@ public: static const unsigned int DEFAULT_BASE_COLOR = 0x666666; static const int progressFadeAnimationDuration = 600; + constexpr static char C_ALIGN_ARROW[] = "alignarrow"; + constexpr static char C_DRAW_LEFT_BORDER[] = "drawleftborder"; + constexpr static char C_ELIDE_MODE[] = "elidemode"; + constexpr static char C_HIDE_BORDER[] = "hideborder"; + constexpr static char C_HIDE_ICON[] = "hideicon"; + constexpr static char C_HIGHLIGHT_WIDGET[] = "highlightWidget"; + constexpr static char C_LIGHT_COLORED[] = "lightColored"; + constexpr static char C_MINI_SPLITTER[] = "minisplitter"; + constexpr static char C_NOT_ELIDE_ASTERISK[] = "notelideasterisk"; + constexpr static char C_NO_ARROW[] = "noArrow"; + constexpr static char C_PANEL_WIDGET[] = "panelwidget"; + constexpr static char C_PANEL_WIDGET_SINGLE_ROW[] = "panelwidget_singlerow"; + constexpr static char C_SHOW_BORDER[] = "showborder"; + constexpr static char C_TOP_BORDER[] = "topBorder"; + enum ToolbarStyle { ToolbarStyleCompact, ToolbarStyleRelaxed, -- cgit v1.2.3 From e44d0c57dc849c3d18d6b1babfbd8197490d4de2 Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Tue, 9 May 2023 17:38:23 +0200 Subject: Utils: Fix the applying of new base color at run-time If the base color was changed via the color picker that opens with shift-click on the upper left toolbar area, some widgets were not instantly updated with the new color. This change ensures that all (visible) widgets instead of just all top level windows get updated after changing the color. While this method is potentially more expensive, it does not effect the start-up negatively, because on start-up StyleHelper::setBaseColor() gets called before the mainwindow is shown. Fixes: QTCREATORBUG-29135 Change-Id: I227c2f243ffcd9f8210b9d2d1a47ad0494663d50 Reviewed-by: Christian Stenger --- src/libs/utils/stylehelper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/stylehelper.cpp b/src/libs/utils/stylehelper.cpp index 315d9efb23..43f14222ac 100644 --- a/src/libs/utils/stylehelper.cpp +++ b/src/libs/utils/stylehelper.cpp @@ -190,7 +190,7 @@ void StyleHelper::setBaseColor(const QColor &newcolor) if (color.isValid() && color != m_baseColor) { m_baseColor = color; - const QList widgets = QApplication::topLevelWidgets(); + const QWidgetList widgets = QApplication::allWidgets(); for (QWidget *w : widgets) w->update(); } -- cgit v1.2.3 From 5b0c3258bb56fb4de71c28340857761641fe9aa7 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Tue, 9 May 2023 14:08:41 +0200 Subject: Utils: Make column of LineColumn consistently 0-based Change-Id: I4ab153d1c55653936efbcdc13ac04463185930e0 Reviewed-by: Jarek Kobus Reviewed-by: Qt CI Bot --- src/libs/utils/linecolumn.h | 4 ++-- src/libs/utils/textutils.cpp | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/linecolumn.h b/src/libs/utils/linecolumn.h index 78a881d7f4..457ebd6a51 100644 --- a/src/libs/utils/linecolumn.h +++ b/src/libs/utils/linecolumn.h @@ -19,7 +19,7 @@ public: bool isValid() const { - return line >= 0 && column >= 0; + return line > 0 && column >= 0; } friend bool operator==(LineColumn first, LineColumn second) @@ -35,7 +35,7 @@ public: static LineColumn extractFromFileName(QStringView fileName, int &postfixPos); public: - int line = -1; + int line = 0; int column = -1; }; diff --git a/src/libs/utils/textutils.cpp b/src/libs/utils/textutils.cpp index ec440a8902..8b52d51d2c 100644 --- a/src/libs/utils/textutils.cpp +++ b/src/libs/utils/textutils.cpp @@ -60,7 +60,7 @@ OptionalLineColumn convertPosition(const QTextDocument *document, int pos) QTextBlock block = document->findBlock(pos); if (block.isValid()) - optional.emplace(block.blockNumber() + 1, pos - block.position() + 1); + optional.emplace(block.blockNumber() + 1, pos - block.position()); return optional; } @@ -180,8 +180,7 @@ LineColumn utf16LineColumn(const QByteArray &utf8Buffer, int utf8Offset) : 0; lineColumn.column = QString::fromUtf8( utf8Buffer.mid(startOfLineOffset, utf8Offset - startOfLineOffset)) - .length() - + 1; + .length(); return lineColumn; } -- cgit v1.2.3 From 24aea361b7b26bef467292d4f41ced1178b718bf Mon Sep 17 00:00:00 2001 From: David Schulz Date: Tue, 9 May 2023 14:15:38 +0200 Subject: Utils: remove OptionalLineColumn LineColumn already has a isValid() that is sufficient for the use case. Change-Id: I7f6e1d64b66a9af05d74ce0ef45717265dc28ed3 Reviewed-by: Jarek Kobus --- src/libs/utils/linecolumn.h | 2 -- src/libs/utils/textutils.cpp | 11 ++++------- src/libs/utils/textutils.h | 2 +- 3 files changed, 5 insertions(+), 10 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/linecolumn.h b/src/libs/utils/linecolumn.h index 457ebd6a51..922c981d79 100644 --- a/src/libs/utils/linecolumn.h +++ b/src/libs/utils/linecolumn.h @@ -39,8 +39,6 @@ public: int column = -1; }; -using OptionalLineColumn = std::optional; - } // namespace Utils Q_DECLARE_METATYPE(Utils::LineColumn) diff --git a/src/libs/utils/textutils.cpp b/src/libs/utils/textutils.cpp index 8b52d51d2c..55e8a736d0 100644 --- a/src/libs/utils/textutils.cpp +++ b/src/libs/utils/textutils.cpp @@ -53,16 +53,13 @@ bool convertPosition(const QTextDocument *document, int pos, int *line, int *col } } -OptionalLineColumn convertPosition(const QTextDocument *document, int pos) +LineColumn convertPosition(const QTextDocument *document, int pos) { - OptionalLineColumn optional; - - QTextBlock block = document->findBlock(pos); - + const QTextBlock block = document->findBlock(pos); if (block.isValid()) - optional.emplace(block.blockNumber() + 1, pos - block.position()); + return {block.blockNumber() + 1, pos - block.position()}; - return optional; + return {}; } int positionInText(const QTextDocument *textDocument, int line, int column) diff --git a/src/libs/utils/textutils.h b/src/libs/utils/textutils.h index ce8a450302..573dc7aa1d 100644 --- a/src/libs/utils/textutils.h +++ b/src/libs/utils/textutils.h @@ -69,7 +69,7 @@ QTCREATOR_UTILS_EXPORT bool convertPosition(const QTextDocument *document, int pos, int *line, int *column); QTCREATOR_UTILS_EXPORT -OptionalLineColumn convertPosition(const QTextDocument *document, int pos); +LineColumn convertPosition(const QTextDocument *document, int pos); // line and column are 1-based QTCREATOR_UTILS_EXPORT int positionInText(const QTextDocument *textDocument, int line, int column); -- cgit v1.2.3 From 5a0f2e6d15123559d0d489b8b466888af28136ef Mon Sep 17 00:00:00 2001 From: David Schulz Date: Tue, 9 May 2023 14:24:24 +0200 Subject: Utils: move extractFromFileName from LineColumn to Text::Position Change-Id: Ibb2465e66c280d4201377921f69741a050d94bc1 Reviewed-by: Jarek Kobus Reviewed-by: Qt CI Bot --- src/libs/utils/CMakeLists.txt | 2 +- src/libs/utils/linecolumn.cpp | 43 ------------------------------------------- src/libs/utils/linecolumn.h | 5 ----- src/libs/utils/link.cpp | 8 ++++---- src/libs/utils/textutils.cpp | 36 +++++++++++++++++++++++++++++++++++- src/libs/utils/textutils.h | 2 ++ src/libs/utils/utils.qbs | 1 - 7 files changed, 42 insertions(+), 55 deletions(-) delete mode 100644 src/libs/utils/linecolumn.cpp (limited to 'src/libs') diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index f05398094a..5fa076027f 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -89,7 +89,7 @@ add_qtc_library(Utils launcherpackets.cpp launcherpackets.h launchersocket.cpp launchersocket.h layoutbuilder.cpp layoutbuilder.h - linecolumn.cpp linecolumn.h + linecolumn.h link.cpp link.h listmodel.h listutils.h diff --git a/src/libs/utils/linecolumn.cpp b/src/libs/utils/linecolumn.cpp deleted file mode 100644 index 50a24ada62..0000000000 --- a/src/libs/utils/linecolumn.cpp +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "linecolumn.h" - -#include - -namespace Utils { - -/*! - Returns the line and column of a \a fileName and sets the \a postfixPos if - it can find a positional postfix. - - The following patterns are supported: \c {filepath.txt:19}, - \c{filepath.txt:19:12}, \c {filepath.txt+19}, - \c {filepath.txt+19+12}, and \c {filepath.txt(19)}. -*/ - -LineColumn LineColumn::extractFromFileName(QStringView fileName, int &postfixPos) -{ - static const auto regexp = QRegularExpression("[:+](\\d+)?([:+](\\d+)?)?$"); - // (10) MSVC-style - static const auto vsRegexp = QRegularExpression("[(]((\\d+)[)]?)?$"); - const QRegularExpressionMatch match = regexp.match(fileName); - LineColumn lineColumn; - if (match.hasMatch()) { - postfixPos = match.capturedStart(0); - lineColumn.line = 0; // for the case that there's only a : at the end - if (match.lastCapturedIndex() > 0) { - lineColumn.line = match.captured(1).toInt(); - if (match.lastCapturedIndex() > 2) // index 2 includes the + or : for the column number - lineColumn.column = match.captured(3).toInt() - 1; //column is 0 based, despite line being 1 based - } - } else { - const QRegularExpressionMatch vsMatch = vsRegexp.match(fileName); - postfixPos = vsMatch.capturedStart(0); - if (vsMatch.lastCapturedIndex() > 1) // index 1 includes closing ) - lineColumn.line = vsMatch.captured(2).toInt(); - } - return lineColumn; -} - -} // namespace Utils diff --git a/src/libs/utils/linecolumn.h b/src/libs/utils/linecolumn.h index 922c981d79..4cd982b8e7 100644 --- a/src/libs/utils/linecolumn.h +++ b/src/libs/utils/linecolumn.h @@ -14,9 +14,6 @@ namespace Utils { class QTCREATOR_UTILS_EXPORT LineColumn { public: - constexpr LineColumn() = default; - constexpr LineColumn(int line, int column) : line(line), column(column) {} - bool isValid() const { return line > 0 && column >= 0; @@ -32,8 +29,6 @@ public: return !(first == second); } - static LineColumn extractFromFileName(QStringView fileName, int &postfixPos); - public: int line = 0; int column = -1; diff --git a/src/libs/utils/link.cpp b/src/libs/utils/link.cpp index e4c7032eeb..1a4b4f9f9a 100644 --- a/src/libs/utils/link.cpp +++ b/src/libs/utils/link.cpp @@ -3,7 +3,7 @@ #include "link.h" -#include "linecolumn.h" +#include "textutils.h" namespace Utils { @@ -24,10 +24,10 @@ Link Link::fromString(const QString &filePathWithNumbers, bool canContainLineNum link.targetFilePath = FilePath::fromUserInput(filePathWithNumbers); } else { int postfixPos = -1; - const LineColumn lineColumn = LineColumn::extractFromFileName(filePathWithNumbers, postfixPos); + const Text::Position pos = Text::Position::fromFileName(filePathWithNumbers, postfixPos); link.targetFilePath = FilePath::fromUserInput(filePathWithNumbers.left(postfixPos)); - link.targetLine = lineColumn.line; - link.targetColumn = lineColumn.column; + link.targetLine = pos.line; + link.targetColumn = pos.column; } return link; } diff --git a/src/libs/utils/textutils.cpp b/src/libs/utils/textutils.cpp index 55e8a736d0..5c9846a24d 100644 --- a/src/libs/utils/textutils.cpp +++ b/src/libs/utils/textutils.cpp @@ -3,8 +3,9 @@ #include "textutils.h" -#include +#include #include +#include namespace Utils::Text { @@ -13,6 +14,39 @@ bool Position::operator==(const Position &other) const return line == other.line && column == other.column; } +/*! + Returns the text position of a \a fileName and sets the \a postfixPos if + it can find a positional postfix. + + The following patterns are supported: \c {filepath.txt:19}, + \c{filepath.txt:19:12}, \c {filepath.txt+19}, + \c {filepath.txt+19+12}, and \c {filepath.txt(19)}. +*/ + +Position Position::fromFileName(QStringView fileName, int &postfixPos) +{ + static const auto regexp = QRegularExpression("[:+](\\d+)?([:+](\\d+)?)?$"); + // (10) MSVC-style + static const auto vsRegexp = QRegularExpression("[(]((\\d+)[)]?)?$"); + const QRegularExpressionMatch match = regexp.match(fileName); + Position pos; + if (match.hasMatch()) { + postfixPos = match.capturedStart(0); + pos.line = 0; // for the case that there's only a : at the end + if (match.lastCapturedIndex() > 0) { + pos.line = match.captured(1).toInt(); + if (match.lastCapturedIndex() > 2) // index 2 includes the + or : for the column number + pos.column = match.captured(3).toInt() - 1; //column is 0 based, despite line being 1 based + } + } else { + const QRegularExpressionMatch vsMatch = vsRegexp.match(fileName); + postfixPos = vsMatch.capturedStart(0); + if (vsMatch.lastCapturedIndex() > 1) // index 1 includes closing ) + pos.line = vsMatch.captured(2).toInt(); + } + return pos; +} + int Range::length(const QString &text) const { if (begin.line == end.line) diff --git a/src/libs/utils/textutils.h b/src/libs/utils/textutils.h index 573dc7aa1d..e278cf919f 100644 --- a/src/libs/utils/textutils.h +++ b/src/libs/utils/textutils.h @@ -28,6 +28,8 @@ public: bool operator==(const Position &other) const; bool operator!=(const Position &other) const { return !(operator==(other)); } + + static Position fromFileName(QStringView fileName, int &postfixPos); }; class QTCREATOR_UTILS_EXPORT Range diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index f1940a053d..d3c826ef26 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -185,7 +185,6 @@ Project { "launchersocket.h", "layoutbuilder.cpp", "layoutbuilder.h", - "linecolumn.cpp", "linecolumn.h", "link.cpp", "link.h", -- cgit v1.2.3 From e9cd4dd4392a7f82f41d9ca1af7e030b532d756d Mon Sep 17 00:00:00 2001 From: David Schulz Date: Tue, 9 May 2023 14:44:48 +0200 Subject: Utils: use Text::Position instead of LineColumn in textutils Change-Id: I606b0b4f8106bdb2f97383d6c81ac065e7e61858 Reviewed-by: Jarek Kobus --- src/libs/utils/textutils.cpp | 32 +++++++++----------------------- src/libs/utils/textutils.h | 8 +++----- 2 files changed, 12 insertions(+), 28 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/textutils.cpp b/src/libs/utils/textutils.cpp index 5c9846a24d..34dbb5e1fa 100644 --- a/src/libs/utils/textutils.cpp +++ b/src/libs/utils/textutils.cpp @@ -47,6 +47,15 @@ Position Position::fromFileName(QStringView fileName, int &postfixPos) return pos; } +Position Position::fromPositionInDocument(const QTextDocument *document, int pos) +{ + const QTextBlock block = document->findBlock(pos); + if (block.isValid()) + return {block.blockNumber() + 1, pos - block.position()}; + + return {}; +} + int Range::length(const QString &text) const { if (begin.line == end.line) @@ -87,15 +96,6 @@ bool convertPosition(const QTextDocument *document, int pos, int *line, int *col } } -LineColumn convertPosition(const QTextDocument *document, int pos) -{ - const QTextBlock block = document->findBlock(pos); - if (block.isValid()) - return {block.blockNumber() + 1, pos - block.position()}; - - return {}; -} - int positionInText(const QTextDocument *textDocument, int line, int column) { // Deduct 1 from line and column since they are 1-based. @@ -201,20 +201,6 @@ int utf8NthLineOffset(const QTextDocument *textDocument, const QByteArray &buffe return utf8Offset; } -LineColumn utf16LineColumn(const QByteArray &utf8Buffer, int utf8Offset) -{ - LineColumn lineColumn; - lineColumn.line = static_cast( - std::count(utf8Buffer.begin(), utf8Buffer.begin() + utf8Offset, '\n')) - + 1; - const int startOfLineOffset = utf8Offset ? (utf8Buffer.lastIndexOf('\n', utf8Offset - 1) + 1) - : 0; - lineColumn.column = QString::fromUtf8( - utf8Buffer.mid(startOfLineOffset, utf8Offset - startOfLineOffset)) - .length(); - return lineColumn; -} - QString utf16LineTextInUtf8Buffer(const QByteArray &utf8Buffer, int currentUtf8Offset) { const int lineStartUtf8Offset = currentUtf8Offset diff --git a/src/libs/utils/textutils.h b/src/libs/utils/textutils.h index e278cf919f..14e83322a3 100644 --- a/src/libs/utils/textutils.h +++ b/src/libs/utils/textutils.h @@ -5,8 +5,6 @@ #include "utils_global.h" -#include "linecolumn.h" - #include QT_BEGIN_NAMESPACE @@ -29,7 +27,10 @@ public: bool operator!=(const Position &other) const { return !(operator==(other)); } + bool isValid() const { return line > 0 && column >= 0; } + static Position fromFileName(QStringView fileName, int &postfixPos); + static Position fromPositionInDocument(const QTextDocument *document, int pos); }; class QTCREATOR_UTILS_EXPORT Range @@ -70,8 +71,6 @@ QTCREATOR_UTILS_EXPORT void applyReplacements(QTextDocument *doc, const Replacem QTCREATOR_UTILS_EXPORT bool convertPosition(const QTextDocument *document, int pos, int *line, int *column); -QTCREATOR_UTILS_EXPORT -LineColumn convertPosition(const QTextDocument *document, int pos); // line and column are 1-based QTCREATOR_UTILS_EXPORT int positionInText(const QTextDocument *textDocument, int line, int column); @@ -90,7 +89,6 @@ QTCREATOR_UTILS_EXPORT int utf8NthLineOffset(const QTextDocument *textDocument, const QByteArray &buffer, int line); -QTCREATOR_UTILS_EXPORT LineColumn utf16LineColumn(const QByteArray &utf8Buffer, int utf8Offset); QTCREATOR_UTILS_EXPORT QString utf16LineTextInUtf8Buffer(const QByteArray &utf8Buffer, int currentUtf8Offset); -- cgit v1.2.3 From 80f46c28a5608a07d2adb820dfc18f61b811df62 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Thu, 11 May 2023 12:59:34 +0200 Subject: Utils: fix build Change-Id: Ibc9050c8b3dd4a1fcba9e87f70b46908dbbd1dce Reviewed-by: hjk --- src/libs/utils/textutils.h | 1 + 1 file changed, 1 insertion(+) (limited to 'src/libs') diff --git a/src/libs/utils/textutils.h b/src/libs/utils/textutils.h index 14e83322a3..4ba3b0e1a8 100644 --- a/src/libs/utils/textutils.h +++ b/src/libs/utils/textutils.h @@ -5,6 +5,7 @@ #include "utils_global.h" +#include #include QT_BEGIN_NAMESPACE -- cgit v1.2.3 From 7bd3ee8123be30854ed5ef4dbb056c510a300106 Mon Sep 17 00:00:00 2001 From: hjk Date: Thu, 11 May 2023 14:00:31 +0200 Subject: Utils: Add more FilePathChooser config options to StringAspect Will be used in the Beautifier plugin. Change-Id: I6e70f757a25afcdf1d3e3742357d71503f210b2a Reviewed-by: Alessandro Portale --- src/libs/utils/aspects.cpp | 27 +++++++++++++++++++++++++++ src/libs/utils/aspects.h | 3 +++ 2 files changed, 30 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index 675f6b116f..b73f1481a5 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -639,6 +639,9 @@ public: Qt::TextElideMode m_elideMode = Qt::ElideNone; QString m_placeHolderText; + QString m_prompDialogFilter; + QString m_prompDialogTitle; + QStringList m_commandVersionArguments; QString m_historyCompleterKey; PathChooser::Kind m_expectedKind = PathChooser::File; Environment m_environment; @@ -947,6 +950,27 @@ void StringAspect::setPlaceHolderText(const QString &placeHolderText) d->m_textEditDisplay->setPlaceholderText(placeHolderText); } +void StringAspect::setPromptDialogFilter(const QString &filter) +{ + d->m_prompDialogFilter = filter; + if (d->m_pathChooserDisplay) + d->m_pathChooserDisplay->setPromptDialogFilter(filter); +} + +void StringAspect::setPromptDialogTitle(const QString &title) +{ + d->m_prompDialogTitle = title; + if (d->m_pathChooserDisplay) + d->m_pathChooserDisplay->setPromptDialogTitle(title); +} + +void StringAspect::setCommandVersionArguments(const QStringList &arguments) +{ + d->m_commandVersionArguments = arguments; + if (d->m_pathChooserDisplay) + d->m_pathChooserDisplay->setCommandVersionArguments(arguments); +} + /*! Sets \a elideMode as label elide mode. */ @@ -1089,6 +1113,9 @@ void StringAspect::addToLayout(LayoutItem &parent) d->m_pathChooserDisplay->setEnvironment(d->m_environment); d->m_pathChooserDisplay->setBaseDirectory(d->m_baseFileName); d->m_pathChooserDisplay->setOpenTerminalHandler(d->m_openTerminal); + d->m_pathChooserDisplay->setPromptDialogFilter(d->m_prompDialogFilter); + d->m_pathChooserDisplay->setPromptDialogTitle(d->m_prompDialogTitle); + d->m_pathChooserDisplay->setCommandVersionArguments(d->m_commandVersionArguments); if (defaultValue() == value()) d->m_pathChooserDisplay->setDefaultValue(defaultValue()); else diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index 8b75bf911d..c511595e1b 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -382,6 +382,9 @@ public: void setDisplayFilter(const std::function &displayFilter); void setPlaceHolderText(const QString &placeHolderText); + void setPromptDialogFilter(const QString &filter); + void setPromptDialogTitle(const QString &title); + void setCommandVersionArguments(const QStringList &arguments); void setHistoryCompleter(const QString &historyCompleterKey); void setExpectedKind(const PathChooser::Kind expectedKind); void setEnvironment(const Environment &env); -- cgit v1.2.3 From 268da290b2f178eeceff50e41c9fba3f927ad4a4 Mon Sep 17 00:00:00 2001 From: hjk Date: Thu, 11 May 2023 14:14:00 +0200 Subject: Utils: Guard SelectionAspect value access against out-of-bounds access Change-Id: I6ca414015cb55f06b7264949487fafc7eb57efd3 Reviewed-by: Alessandro Portale --- src/libs/utils/aspects.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index b73f1481a5..21c221dc5f 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -1712,12 +1712,14 @@ void SelectionAspect::setDefaultValue(const QString &val) QString SelectionAspect::stringValue() const { - return d->m_options.at(value()).displayName; + const int idx = value(); + return idx >= 0 && idx < d->m_options.size() ? d->m_options.at(idx).displayName : QString(); } QVariant SelectionAspect::itemValue() const { - return d->m_options.at(value()).itemData; + const int idx = value(); + return idx >= 0 && idx < d->m_options.size() ? d->m_options.at(idx).itemData : QVariant(); } void SelectionAspect::addOption(const QString &displayName, const QString &toolTip) -- cgit v1.2.3 From 1acd2499e29b6119fe1db0520867e97ecb89f88b Mon Sep 17 00:00:00 2001 From: David Schulz Date: Tue, 9 May 2023 15:18:42 +0200 Subject: Utils: replace LineColumn with Text::Position Change-Id: Ia69547374efec7412717cbed1eb4162162a89d39 Reviewed-by: Jarek Kobus Reviewed-by: Qt CI Bot Reviewed-by: Cristian Adam --- src/libs/utils/CMakeLists.txt | 1 - src/libs/utils/linecolumn.h | 39 --------------------------------------- src/libs/utils/utils.qbs | 1 - 3 files changed, 41 deletions(-) delete mode 100644 src/libs/utils/linecolumn.h (limited to 'src/libs') diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index 5fa076027f..5762fc3902 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -89,7 +89,6 @@ add_qtc_library(Utils launcherpackets.cpp launcherpackets.h launchersocket.cpp launchersocket.h layoutbuilder.cpp layoutbuilder.h - linecolumn.h link.cpp link.h listmodel.h listutils.h diff --git a/src/libs/utils/linecolumn.h b/src/libs/utils/linecolumn.h deleted file mode 100644 index 4cd982b8e7..0000000000 --- a/src/libs/utils/linecolumn.h +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "utils_global.h" - -#include - -#include - -namespace Utils { - -class QTCREATOR_UTILS_EXPORT LineColumn -{ -public: - bool isValid() const - { - return line > 0 && column >= 0; - } - - friend bool operator==(LineColumn first, LineColumn second) - { - return first.isValid() && first.line == second.line && first.column == second.column; - } - - friend bool operator!=(LineColumn first, LineColumn second) - { - return !(first == second); - } - -public: - int line = 0; - int column = -1; -}; - -} // namespace Utils - -Q_DECLARE_METATYPE(Utils::LineColumn) diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index d3c826ef26..f316a8b75b 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -185,7 +185,6 @@ Project { "launchersocket.h", "layoutbuilder.cpp", "layoutbuilder.h", - "linecolumn.h", "link.cpp", "link.h", "listmodel.h", -- cgit v1.2.3 From fb406a26f49e6b195c9e6e4e9d17e572092add63 Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Thu, 11 May 2023 13:15:55 +0200 Subject: Utils: Turn StyleHelper into a namespace It was in fact just a bag of static functions. Most importantly, it fixes a previouls introduced build error. Amends 5975657e7747052211310a9bfedaac1fa937fc19 Change-Id: Ia7de8bdcf91e1763f9d3b435316a5df9993717de Reviewed-by: Cristian Adam Reviewed-by: Qt CI Bot --- src/libs/utils/stylehelper.cpp | 40 +++++- src/libs/utils/stylehelper.h | 269 +++++++++++++++++++++-------------------- 2 files changed, 173 insertions(+), 136 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/stylehelper.cpp b/src/libs/utils/stylehelper.cpp index 43f14222ac..cc860e715a 100644 --- a/src/libs/utils/stylehelper.cpp +++ b/src/libs/utils/stylehelper.cpp @@ -37,6 +37,11 @@ static int range(float x, int min, int max) namespace Utils { +static StyleHelper::ToolbarStyle m_toolbarStyle = StyleHelper::defaultToolbarStyle; +// Invalid by default, setBaseColor needs to be called at least once +static QColor m_baseColor; +static QColor m_requestedBaseColor; + QColor StyleHelper::mergedColors(const QColor &colorA, const QColor &colorB, int factor) { const int maxFactor = 100; @@ -59,6 +64,21 @@ QColor StyleHelper::alphaBlendedColors(const QColor &colorA, const QColor &color ); } +QColor StyleHelper::sidebarHighlight() +{ + return QColor(255, 255, 255, 40); +} + +QColor StyleHelper::sidebarShadow() +{ + return QColor(0, 0, 0, 40); +} + +QColor StyleHelper::toolBarDropShadowColor() +{ + return QColor(0, 0, 0, 70); +} + int StyleHelper::navigationWidgetHeight() { return m_toolbarStyle == ToolbarStyleCompact ? 24 : 30; @@ -105,11 +125,6 @@ QColor StyleHelper::panelTextColor(bool lightColored) return Qt::black; } -StyleHelper::ToolbarStyle StyleHelper::m_toolbarStyle = StyleHelper::defaultToolbarStyle; -// Invalid by default, setBaseColor needs to be called at least once -QColor StyleHelper::m_baseColor; -QColor StyleHelper::m_requestedBaseColor; - QColor StyleHelper::baseColor(bool lightColored) { static const QColor windowColor = QApplication::palette().color(QPalette::Window); @@ -118,6 +133,11 @@ QColor StyleHelper::baseColor(bool lightColored) return (lightColored || windowColorAsBase) ? windowColor : m_baseColor; } +QColor StyleHelper::requestedBaseColor() +{ + return m_requestedBaseColor; +} + QColor StyleHelper::toolbarBaseColor(bool lightColored) { if (creatorTheme()->flag(Theme::QDSTheme)) @@ -166,6 +186,11 @@ QColor StyleHelper::toolBarBorderColor() clamp(base.value() * 0.80f)); } +QColor StyleHelper::buttonTextColor() +{ + return QColor(0x4c4c4c); +} + // We try to ensure that the actual color used are within // reasonalbe bounds while generating the actual baseColor // from the users request. @@ -495,6 +520,11 @@ void StyleHelper::menuGradient(QPainter *painter, const QRect &spanRect, const Q } } +bool StyleHelper::usePixmapCache() +{ + return true; +} + QPixmap StyleHelper::disabledSideBarIcon(const QPixmap &enabledicon) { QImage im = enabledicon.toImage().convertToFormat(QImage::Format_ARGB32); diff --git a/src/libs/utils/stylehelper.h b/src/libs/utils/stylehelper.h index 9e7c3c1911..2b898cb104 100644 --- a/src/libs/utils/stylehelper.h +++ b/src/libs/utils/stylehelper.h @@ -13,143 +13,150 @@ class QPainter; class QRect; // Note, this is exported but in a private header as qtopengl depends on it. // We should consider adding this as a public helper function. -void qt_blurImage(QPainter *p, QImage &blurImage, qreal radius, bool quality, bool alphaOnly, int transposed = 0); +void qt_blurImage(QPainter *p, QImage &blurImage, qreal radius, bool quality, bool alphaOnly, + int transposed = 0); QT_END_NAMESPACE // Helper class holding all custom color values - -namespace Utils { -class QTCREATOR_UTILS_EXPORT StyleHelper +namespace Utils::StyleHelper { + +const unsigned int DEFAULT_BASE_COLOR = 0x666666; +const int progressFadeAnimationDuration = 600; + +constexpr char C_ALIGN_ARROW[] = "alignarrow"; +constexpr char C_DRAW_LEFT_BORDER[] = "drawleftborder"; +constexpr char C_ELIDE_MODE[] = "elidemode"; +constexpr char C_HIDE_BORDER[] = "hideborder"; +constexpr char C_HIDE_ICON[] = "hideicon"; +constexpr char C_HIGHLIGHT_WIDGET[] = "highlightWidget"; +constexpr char C_LIGHT_COLORED[] = "lightColored"; +constexpr char C_MINI_SPLITTER[] = "minisplitter"; +constexpr char C_NOT_ELIDE_ASTERISK[] = "notelideasterisk"; +constexpr char C_NO_ARROW[] = "noArrow"; +constexpr char C_PANEL_WIDGET[] = "panelwidget"; +constexpr char C_PANEL_WIDGET_SINGLE_ROW[] = "panelwidget_singlerow"; +constexpr char C_SHOW_BORDER[] = "showborder"; +constexpr char C_TOP_BORDER[] = "topBorder"; + +enum ToolbarStyle { + ToolbarStyleCompact, + ToolbarStyleRelaxed, +}; +constexpr ToolbarStyle defaultToolbarStyle = ToolbarStyleCompact; + +// Height of the project explorer navigation bar +QTCREATOR_UTILS_EXPORT int navigationWidgetHeight(); +QTCREATOR_UTILS_EXPORT void setToolbarStyle(ToolbarStyle style); +QTCREATOR_UTILS_EXPORT ToolbarStyle toolbarStyle(); +QTCREATOR_UTILS_EXPORT qreal sidebarFontSize(); +QTCREATOR_UTILS_EXPORT QPalette sidebarFontPalette(const QPalette &original); + +// This is our color table, all colors derive from baseColor +QTCREATOR_UTILS_EXPORT QColor requestedBaseColor(); +QTCREATOR_UTILS_EXPORT QColor baseColor(bool lightColored = false); +QTCREATOR_UTILS_EXPORT QColor toolbarBaseColor(bool lightColored = false); +QTCREATOR_UTILS_EXPORT QColor panelTextColor(bool lightColored = false); +QTCREATOR_UTILS_EXPORT QColor highlightColor(bool lightColored = false); +QTCREATOR_UTILS_EXPORT QColor shadowColor(bool lightColored = false); +QTCREATOR_UTILS_EXPORT QColor borderColor(bool lightColored = false); +QTCREATOR_UTILS_EXPORT QColor toolBarBorderColor(); +QTCREATOR_UTILS_EXPORT QColor buttonTextColor(); +QTCREATOR_UTILS_EXPORT QColor mergedColors(const QColor &colorA, const QColor &colorB, + int factor = 50); +QTCREATOR_UTILS_EXPORT QColor alphaBlendedColors(const QColor &colorA, const QColor &colorB); + +QTCREATOR_UTILS_EXPORT QColor sidebarHighlight(); +QTCREATOR_UTILS_EXPORT QColor sidebarShadow(); +QTCREATOR_UTILS_EXPORT QColor toolBarDropShadowColor(); +QTCREATOR_UTILS_EXPORT QColor notTooBrightHighlightColor(); + +// Sets the base color and makes sure all top level widgets are updated +QTCREATOR_UTILS_EXPORT void setBaseColor(const QColor &color); + +// Draws a shaded anti-aliased arrow +QTCREATOR_UTILS_EXPORT void drawArrow(QStyle::PrimitiveElement element, QPainter *painter, + const QStyleOption *option); +QTCREATOR_UTILS_EXPORT void drawMinimalArrow(QStyle::PrimitiveElement element, QPainter *painter, + const QStyleOption *option); + +QTCREATOR_UTILS_EXPORT void drawPanelBgRect(QPainter *painter, const QRectF &rect, + const QBrush &brush); + +// Gradients used for panels +QTCREATOR_UTILS_EXPORT void horizontalGradient(QPainter *painter, const QRect &spanRect, + const QRect &clipRect, bool lightColored = false); +QTCREATOR_UTILS_EXPORT void verticalGradient(QPainter *painter, const QRect &spanRect, + const QRect &clipRect, bool lightColored = false); +QTCREATOR_UTILS_EXPORT void menuGradient(QPainter *painter, const QRect &spanRect, + const QRect &clipRect); +QTCREATOR_UTILS_EXPORT bool usePixmapCache(); + +QTCREATOR_UTILS_EXPORT QPixmap disabledSideBarIcon(const QPixmap &enabledicon); +QTCREATOR_UTILS_EXPORT void drawIconWithShadow(const QIcon &icon, const QRect &rect, QPainter *p, + QIcon::Mode iconMode, int dipRadius = 3, + const QColor &color = QColor(0, 0, 0, 130), + const QPoint &dipOffset = QPoint(1, -2)); +QTCREATOR_UTILS_EXPORT void drawCornerImage(const QImage &img, QPainter *painter, const QRect &rect, + int left = 0, int top = 0, int right = 0, + int bottom = 0); + +QTCREATOR_UTILS_EXPORT void tintImage(QImage &img, const QColor &tintColor); +QTCREATOR_UTILS_EXPORT QLinearGradient statusBarGradient(const QRect &statusBarRect); +QTCREATOR_UTILS_EXPORT void setPanelWidget(QWidget *widget, bool value = true); +QTCREATOR_UTILS_EXPORT void setPanelWidgetSingleRow(QWidget *widget, bool value = true); + +QTCREATOR_UTILS_EXPORT bool isQDSTheme(); + +class IconFontHelper { public: - static const unsigned int DEFAULT_BASE_COLOR = 0x666666; - static const int progressFadeAnimationDuration = 600; - - constexpr static char C_ALIGN_ARROW[] = "alignarrow"; - constexpr static char C_DRAW_LEFT_BORDER[] = "drawleftborder"; - constexpr static char C_ELIDE_MODE[] = "elidemode"; - constexpr static char C_HIDE_BORDER[] = "hideborder"; - constexpr static char C_HIDE_ICON[] = "hideicon"; - constexpr static char C_HIGHLIGHT_WIDGET[] = "highlightWidget"; - constexpr static char C_LIGHT_COLORED[] = "lightColored"; - constexpr static char C_MINI_SPLITTER[] = "minisplitter"; - constexpr static char C_NOT_ELIDE_ASTERISK[] = "notelideasterisk"; - constexpr static char C_NO_ARROW[] = "noArrow"; - constexpr static char C_PANEL_WIDGET[] = "panelwidget"; - constexpr static char C_PANEL_WIDGET_SINGLE_ROW[] = "panelwidget_singlerow"; - constexpr static char C_SHOW_BORDER[] = "showborder"; - constexpr static char C_TOP_BORDER[] = "topBorder"; - - enum ToolbarStyle { - ToolbarStyleCompact, - ToolbarStyleRelaxed, - }; - - // Height of the project explorer navigation bar - static int navigationWidgetHeight(); - static void setToolbarStyle(ToolbarStyle style); - static ToolbarStyle toolbarStyle(); - static constexpr ToolbarStyle defaultToolbarStyle = ToolbarStyleCompact; - static qreal sidebarFontSize(); - static QPalette sidebarFontPalette(const QPalette &original); - - // This is our color table, all colors derive from baseColor - static QColor requestedBaseColor() { return m_requestedBaseColor; } - static QColor baseColor(bool lightColored = false); - static QColor toolbarBaseColor(bool lightColored = false); - static QColor panelTextColor(bool lightColored = false); - static QColor highlightColor(bool lightColored = false); - static QColor shadowColor(bool lightColored = false); - static QColor borderColor(bool lightColored = false); - static QColor toolBarBorderColor(); - static QColor buttonTextColor() { return QColor(0x4c4c4c); } - static QColor mergedColors(const QColor &colorA, const QColor &colorB, int factor = 50); - static QColor alphaBlendedColors(const QColor &colorA, const QColor &colorB); - - static QColor sidebarHighlight() { return QColor(255, 255, 255, 40); } - static QColor sidebarShadow() { return QColor(0, 0, 0, 40); } - - static QColor toolBarDropShadowColor() { return QColor(0, 0, 0, 70); } - - static QColor notTooBrightHighlightColor(); - - // Sets the base color and makes sure all top level widgets are updated - static void setBaseColor(const QColor &color); - - // Draws a shaded anti-aliased arrow - static void drawArrow(QStyle::PrimitiveElement element, QPainter *painter, const QStyleOption *option); - static void drawMinimalArrow(QStyle::PrimitiveElement element, QPainter *painter, const QStyleOption *option); - - static void drawPanelBgRect(QPainter *painter, const QRectF &rect, const QBrush &brush); - - // Gradients used for panels - static void horizontalGradient(QPainter *painter, const QRect &spanRect, const QRect &clipRect, bool lightColored = false); - static void verticalGradient(QPainter *painter, const QRect &spanRect, const QRect &clipRect, bool lightColored = false); - static void menuGradient(QPainter *painter, const QRect &spanRect, const QRect &clipRect); - static bool usePixmapCache() { return true; } - - static QPixmap disabledSideBarIcon(const QPixmap &enabledicon); - static void drawIconWithShadow(const QIcon &icon, const QRect &rect, QPainter *p, QIcon::Mode iconMode, - int dipRadius = 3, const QColor &color = QColor(0, 0, 0, 130), - const QPoint &dipOffset = QPoint(1, -2)); - static void drawCornerImage(const QImage &img, QPainter *painter, const QRect &rect, - int left = 0, int top = 0, int right = 0, int bottom = 0); - - static void tintImage(QImage &img, const QColor &tintColor); - static QLinearGradient statusBarGradient(const QRect &statusBarRect); - static void setPanelWidget(QWidget *widget, bool value = true); - static void setPanelWidgetSingleRow(QWidget *widget, bool value = true); - - static bool isQDSTheme(); - - class IconFontHelper - { - public: - IconFontHelper(const QString &iconSymbol, - const QColor &color, - const QSize &size, - QIcon::Mode mode = QIcon::Normal, - QIcon::State state = QIcon::Off) - : m_iconSymbol(iconSymbol) - , m_color(color) - , m_size(size) - , m_mode(mode) - , m_state(state) - {} - - QString iconSymbol() const { return m_iconSymbol; } - QColor color() const { return m_color; } - QSize size() const { return m_size; } - QIcon::Mode mode() const { return m_mode; } - QIcon::State state() const { return m_state; } - - private: - QString m_iconSymbol; - QColor m_color; - QSize m_size; - QIcon::Mode m_mode; - QIcon::State m_state; - }; - - static QIcon getIconFromIconFont(const QString &fontName, const QList ¶meters); - static QIcon getIconFromIconFont(const QString &fontName, const QString &iconSymbol, int fontSize, int iconSize, QColor color); - static QIcon getIconFromIconFont(const QString &fontName, const QString &iconSymbol, int fontSize, int iconSize); - static QIcon getCursorFromIconFont(const QString &fontname, const QString &cursorFill, const QString &cursorOutline, - int fontSize, int iconSize); - - static QString dpiSpecificImageFile(const QString &fileName); - static QString imageFileWithResolution(const QString &fileName, int dpr); - static QList availableImageResolutions(const QString &fileName); - - static double luminance(const QColor &color); - static bool isReadableOn(const QColor &background, const QColor &foreground); - // returns a foreground color readable on background (desiredForeground if already readable or adaption fails) - static QColor ensureReadableOn(const QColor &background, const QColor &desiredForeground); + IconFontHelper(const QString &iconSymbol, + const QColor &color, + const QSize &size, + QIcon::Mode mode = QIcon::Normal, + QIcon::State state = QIcon::Off) + : m_iconSymbol(iconSymbol) + , m_color(color) + , m_size(size) + , m_mode(mode) + , m_state(state) + {} + + QString iconSymbol() const { return m_iconSymbol; } + QColor color() const { return m_color; } + QSize size() const { return m_size; } + QIcon::Mode mode() const { return m_mode; } + QIcon::State state() const { return m_state; } private: - static ToolbarStyle m_toolbarStyle; - static QColor m_baseColor; - static QColor m_requestedBaseColor; + QString m_iconSymbol; + QColor m_color; + QSize m_size; + QIcon::Mode m_mode; + QIcon::State m_state; }; -} // namespace Utils +QTCREATOR_UTILS_EXPORT QIcon getIconFromIconFont(const QString &fontName, + const QList ¶meters); +QTCREATOR_UTILS_EXPORT QIcon getIconFromIconFont(const QString &fontName, + const QString &iconSymbol, int fontSize, + int iconSize, QColor color); +QTCREATOR_UTILS_EXPORT QIcon getIconFromIconFont(const QString &fontName, + const QString &iconSymbol, int fontSize, + int iconSize); +QTCREATOR_UTILS_EXPORT QIcon getCursorFromIconFont(const QString &fontname, + const QString &cursorFill, + const QString &cursorOutline, + int fontSize, int iconSize); + +QTCREATOR_UTILS_EXPORT QString dpiSpecificImageFile(const QString &fileName); +QTCREATOR_UTILS_EXPORT QString imageFileWithResolution(const QString &fileName, int dpr); +QTCREATOR_UTILS_EXPORT QList availableImageResolutions(const QString &fileName); + +QTCREATOR_UTILS_EXPORT double luminance(const QColor &color); +QTCREATOR_UTILS_EXPORT bool isReadableOn(const QColor &background, const QColor &foreground); +// returns a foreground color readable on background (desiredForeground if already readable or adaption fails) +QTCREATOR_UTILS_EXPORT QColor ensureReadableOn(const QColor &background, + const QColor &desiredForeground); + +} // namespace Utils::StyleHelper -- cgit v1.2.3 From b8a82d824b2749d9d73d34ed1fe727b45efc34f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Sch=C3=A4pers?= Date: Wed, 10 May 2023 13:33:27 +0200 Subject: Utils: Fix unused parameter warning Change-Id: Ibe4c866b4f3bf39999cfe43bd32287e069e51d2c Reviewed-by: Qt CI Bot Reviewed-by: Jarek Kobus --- src/libs/utils/devicefileaccess.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/devicefileaccess.cpp b/src/libs/utils/devicefileaccess.cpp index bda098e0f6..a29c5bb9be 100644 --- a/src/libs/utils/devicefileaccess.cpp +++ b/src/libs/utils/devicefileaccess.cpp @@ -493,6 +493,8 @@ bool DesktopDeviceFileAccess::hasHardLinks(const FilePath &filePath) const if (s.st_nlink > 1) return true; } +#else + Q_UNUSED(filePath) #endif return false; } -- cgit v1.2.3 From 1a86c7bed4c100e227173ed288a9285b44ecbb57 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Thu, 11 May 2023 15:46:34 +0200 Subject: Utils: Remove empty item after option Triggers a soft assert. Change-Id: I9ad863ceb7e1f377e4f9fbae62cca1d0932a76d6 Reviewed-by: hjk --- src/libs/utils/aspects.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index 21c221dc5f..0f75cfcbde 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -1597,7 +1597,6 @@ void SelectionAspect::addToLayout(Layouting::LayoutItem &parent) button->setChecked(i == value()); button->setEnabled(option.enabled); button->setToolTip(option.tooltip); - parent.addItem(Layouting::empty); parent.addItem(button); d->m_buttons.append(button); d->m_buttonGroup->addButton(button, i); -- cgit v1.2.3 From 5a73d07c72db6be79046ecd619871eeaa73d2973 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 10 May 2023 18:39:25 +0200 Subject: TaskTree: Prepare for de-utils-ization - part 1 Add internal implementation for Guard/GuardLocker. Change-Id: I9bcedee937221c0796143be2f943650a1bb56f38 Reviewed-by: Qt CI Bot Reviewed-by: hjk --- src/libs/utils/tasktree.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/tasktree.cpp b/src/libs/utils/tasktree.cpp index e27cd8f8bb..3e88b0e8a1 100644 --- a/src/libs/utils/tasktree.cpp +++ b/src/libs/utils/tasktree.cpp @@ -3,11 +3,32 @@ #include "tasktree.h" -#include "guard.h" #include "qtcassert.h" #include +class Guard +{ + Q_DISABLE_COPY(Guard) +public: + Guard() = default; + ~Guard() { QTC_CHECK(m_lockCount == 0); } + bool isLocked() const { return m_lockCount; } +private: + int m_lockCount = 0; + friend class GuardLocker; +}; + +class GuardLocker +{ + Q_DISABLE_COPY(GuardLocker) +public: + GuardLocker(Guard &guard) : m_guard(guard) { ++m_guard.m_lockCount; } + ~GuardLocker() { --m_guard.m_lockCount; } +private: + Guard &m_guard; +}; + namespace Utils { namespace Tasking { -- cgit v1.2.3 From 97a66067bb8c85cede9c51c64a271a16f3abd7e8 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 10 May 2023 19:54:52 +0200 Subject: TaskTree: Prepare for de-utils-ization - part 2 Move TaskTree into Tasking namespace. Move Tasking namespace out of Utils namespace. Change-Id: Ib4c1d7f54f1808517e54768dfa27209c33517b61 Reviewed-by: Qt CI Bot Reviewed-by: hjk --- src/libs/utils/async.cpp | 2 -- src/libs/utils/async.h | 2 +- src/libs/utils/barrier.cpp | 4 ++-- src/libs/utils/barrier.h | 10 +++++----- src/libs/utils/filestreamer.cpp | 4 ++-- src/libs/utils/filestreamer.h | 2 +- src/libs/utils/tasktree.cpp | 26 ++++++++++---------------- src/libs/utils/tasktree.h | 30 ++++++++++++------------------ 8 files changed, 33 insertions(+), 47 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/async.cpp b/src/libs/utils/async.cpp index 9295b50dc1..33487db73b 100644 --- a/src/libs/utils/async.cpp +++ b/src/libs/utils/async.cpp @@ -3,8 +3,6 @@ #include "async.h" -#include - namespace Utils { static int s_maxThreadCount = INT_MAX; diff --git a/src/libs/utils/async.h b/src/libs/utils/async.h index 07b3848749..59d91ba0ad 100644 --- a/src/libs/utils/async.h +++ b/src/libs/utils/async.h @@ -211,4 +211,4 @@ public: } // namespace Utils -QTC_DECLARE_CUSTOM_TEMPLATE_TASK(AsyncTask, AsyncTaskAdapter); +QTC_DECLARE_CUSTOM_TEMPLATE_TASK(AsyncTask, Utils::AsyncTaskAdapter); diff --git a/src/libs/utils/barrier.cpp b/src/libs/utils/barrier.cpp index 76cc351088..690391cb0f 100644 --- a/src/libs/utils/barrier.cpp +++ b/src/libs/utils/barrier.cpp @@ -5,7 +5,7 @@ #include "qtcassert.h" -namespace Utils { +namespace Tasking { void Barrier::setLimit(int value) { @@ -44,4 +44,4 @@ void Barrier::stopWithResult(bool success) emit done(success); } -} // namespace Utils +} // namespace Tasking diff --git a/src/libs/utils/barrier.h b/src/libs/utils/barrier.h index 2af08dfdd1..9e5f8d5037 100644 --- a/src/libs/utils/barrier.h +++ b/src/libs/utils/barrier.h @@ -7,7 +7,7 @@ #include "tasktree.h" -namespace Utils { +namespace Tasking { class QTCREATOR_UTILS_EXPORT Barrier final : public QObject { @@ -41,11 +41,11 @@ public: void start() final { task()->start(); } }; -} // namespace Utils +} // namespace Tasking -QTC_DECLARE_CUSTOM_TASK(BarrierTask, Utils::BarrierTaskAdapter); +QTC_DECLARE_CUSTOM_TASK(BarrierTask, Tasking::BarrierTaskAdapter); -namespace Utils::Tasking { +namespace Tasking { template class SharedBarrier @@ -94,4 +94,4 @@ public: }) {} }; -} // namespace Utils::Tasking +} // namespace Tasking diff --git a/src/libs/utils/filestreamer.cpp b/src/libs/utils/filestreamer.cpp index a42e632000..67a8a6cd92 100644 --- a/src/libs/utils/filestreamer.cpp +++ b/src/libs/utils/filestreamer.cpp @@ -284,14 +284,14 @@ private: WriteBuffer *m_writeBuffer = nullptr; }; -class FileStreamReaderAdapter : public Utils::Tasking::TaskAdapter +class FileStreamReaderAdapter : public TaskAdapter { public: FileStreamReaderAdapter() { connect(task(), &FileStreamBase::done, this, &TaskInterface::done); } void start() override { task()->start(); } }; -class FileStreamWriterAdapter : public Utils::Tasking::TaskAdapter +class FileStreamWriterAdapter : public TaskAdapter { public: FileStreamWriterAdapter() { connect(task(), &FileStreamBase::done, this, &TaskInterface::done); } diff --git a/src/libs/utils/filestreamer.h b/src/libs/utils/filestreamer.h index 7d274b9444..3bbca65b8f 100644 --- a/src/libs/utils/filestreamer.h +++ b/src/libs/utils/filestreamer.h @@ -49,7 +49,7 @@ private: class FileStreamerPrivate *d = nullptr; }; -class FileStreamerTaskAdapter : public Utils::Tasking::TaskAdapter +class FileStreamerTaskAdapter : public Tasking::TaskAdapter { public: FileStreamerTaskAdapter() { connect(task(), &FileStreamer::done, this, diff --git a/src/libs/utils/tasktree.cpp b/src/libs/utils/tasktree.cpp index 3e88b0e8a1..2c88546e6c 100644 --- a/src/libs/utils/tasktree.cpp +++ b/src/libs/utils/tasktree.cpp @@ -7,6 +7,8 @@ #include +namespace Tasking { + class Guard { Q_DISABLE_COPY(Guard) @@ -29,9 +31,6 @@ private: Guard &m_guard; }; -namespace Utils { -namespace Tasking { - static TaskAction toTaskAction(bool success) { return success ? TaskAction::StopWithDone : TaskAction::StopWithError; @@ -191,10 +190,6 @@ void TaskItem::setTaskErrorHandler(const TaskEndHandler &handler) m_taskHandler.m_errorHandler = handler; } -} // namespace Tasking - -using namespace Tasking; - class TaskTreePrivate; class TaskNode; @@ -686,8 +681,8 @@ void TaskNode::invokeEndHandler(bool success) } /*! - \class Utils::TaskTree - \inheaderfile utils/tasktree.h + \class TaskTree + \inheaderfile solutions/tasking/tasktree.h \inmodule QtCreator \ingroup mainclasses \brief The TaskTree class runs an async task tree structure defined in a @@ -706,7 +701,6 @@ void TaskNode::invokeEndHandler(bool success) or AsyncTask: \code - using namespace Utils; using namespace Tasking; const Group root { @@ -1285,7 +1279,7 @@ void TaskNode::invokeEndHandler(bool success) recipe described by the Group root element. As TaskTree is also an asynchronous task, it can be a part of another TaskTree. - To place a nested TaskTree inside another TaskTree, insert the Tasking::TaskTreeTask + To place a nested TaskTree inside another TaskTree, insert the TaskTreeTask element into other tree's Group element. TaskTree reports progress of completed tasks when running. The progress value @@ -1344,7 +1338,7 @@ void TaskNode::invokeEndHandler(bool success) asynchronous task: \code - class TimeoutAdapter : public Utils::Tasking::TaskAdapter + class TimeoutAdapter : public Tasking::TaskAdapter { public: TimeoutAdapter() { @@ -1372,7 +1366,7 @@ void TaskNode::invokeEndHandler(bool success) To make QTimer accessible inside TaskTree under the \e Timeout name, register it with QTC_DECLARE_CUSTOM_TASK(Timeout, TimeoutAdapter). Timeout - becomes a new task type inside Utils::Tasking namespace, using TimeoutAdapter. + becomes a new task type inside Tasking namespace, using TimeoutAdapter. The new task type is now registered, and you can use it in TaskTree: @@ -1416,7 +1410,7 @@ TaskTree::~TaskTree() delete d; } -void TaskTree::setupRoot(const Tasking::Group &root) +void TaskTree::setupRoot(const Group &root) { QTC_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return); QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The setupRoot() is called from one of the" @@ -1455,7 +1449,7 @@ int TaskTree::progressValue() const return d->m_progressValue; } -void TaskTree::setupStorageHandler(const Tasking::TreeStorageBase &storage, +void TaskTree::setupStorageHandler(const TreeStorageBase &storage, StorageVoidHandler setupHandler, StorageVoidHandler doneHandler) { @@ -1487,4 +1481,4 @@ void TaskTreeTaskAdapter::start() task()->start(); } -} // namespace Utils +} // namespace Tasking diff --git a/src/libs/utils/tasktree.h b/src/libs/utils/tasktree.h index 46333c3dc8..b37749f9a4 100644 --- a/src/libs/utils/tasktree.h +++ b/src/libs/utils/tasktree.h @@ -9,15 +9,13 @@ #include #include -namespace Utils { +namespace Tasking { class ExecutionContextActivator; class TaskContainer; class TaskNode; class TaskTreePrivate; -namespace Tasking { - class QTCREATOR_UTILS_EXPORT TaskInterface : public QObject { Q_OBJECT @@ -354,8 +352,6 @@ private: }; }; -} // namespace Tasking - class TaskTreePrivate; class QTCREATOR_UTILS_EXPORT TaskTree final : public QObject @@ -364,10 +360,10 @@ class QTCREATOR_UTILS_EXPORT TaskTree final : public QObject public: TaskTree(); - TaskTree(const Tasking::Group &root); + TaskTree(const Group &root); ~TaskTree(); - void setupRoot(const Tasking::Group &root); + void setupRoot(const Group &root); void start(); void stop(); @@ -378,14 +374,12 @@ public: int progressValue() const; // all finished / skipped / stopped tasks, groups itself excluded template - void onStorageSetup(const Tasking::TreeStorage &storage, - StorageHandler &&handler) { + void onStorageSetup(const TreeStorage &storage, StorageHandler &&handler) { setupStorageHandler(storage, wrapHandler(std::forward(handler)), {}); } template - void onStorageDone(const Tasking::TreeStorage &storage, - StorageHandler &&handler) { + void onStorageDone(const TreeStorage &storage, StorageHandler &&handler) { setupStorageHandler(storage, {}, wrapHandler(std::forward(handler))); } @@ -398,7 +392,7 @@ signals: private: using StorageVoidHandler = std::function; - void setupStorageHandler(const Tasking::TreeStorageBase &storage, + void setupStorageHandler(const TreeStorageBase &storage, StorageVoidHandler setupHandler, StorageVoidHandler doneHandler); template @@ -413,22 +407,22 @@ private: TaskTreePrivate *d; }; -class QTCREATOR_UTILS_EXPORT TaskTreeTaskAdapter : public Tasking::TaskAdapter +class QTCREATOR_UTILS_EXPORT TaskTreeTaskAdapter : public TaskAdapter { public: TaskTreeTaskAdapter(); void start() final; }; -} // namespace Utils +} // namespace Tasking #define QTC_DECLARE_CUSTOM_TASK(CustomTaskName, TaskAdapterClass)\ -namespace Utils::Tasking { using CustomTaskName = CustomTask; } +namespace Tasking { using CustomTaskName = CustomTask; } #define QTC_DECLARE_CUSTOM_TEMPLATE_TASK(CustomTaskName, TaskAdapterClass)\ -namespace Utils::Tasking {\ +namespace Tasking {\ template \ using CustomTaskName = CustomTask>;\ -} // namespace Utils::Tasking +} // namespace Tasking -QTC_DECLARE_CUSTOM_TASK(TaskTreeTask, Utils::TaskTreeTaskAdapter); +QTC_DECLARE_CUSTOM_TASK(TaskTreeTask, TaskTreeTaskAdapter); -- cgit v1.2.3 From fe470d60a2c9a4ccae07a4acd87d8d1a9fb696bb Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 10 May 2023 20:20:21 +0200 Subject: TaskTree: Prepare for de-utils-ization - part 3 Rename QTC_* macros into TASKING_*. Change-Id: I809ebf678f20df612a3211c38ebbfe6d4bf6492d Reviewed-by: hjk Reviewed-by: Qt CI Bot --- src/libs/utils/async.h | 2 +- src/libs/utils/barrier.h | 2 +- src/libs/utils/filestreamer.cpp | 4 ++-- src/libs/utils/filestreamer.h | 2 +- src/libs/utils/process.h | 2 +- src/libs/utils/tasktree.h | 6 +++--- 6 files changed, 9 insertions(+), 9 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/async.h b/src/libs/utils/async.h index 59d91ba0ad..b08ceaf3dc 100644 --- a/src/libs/utils/async.h +++ b/src/libs/utils/async.h @@ -211,4 +211,4 @@ public: } // namespace Utils -QTC_DECLARE_CUSTOM_TEMPLATE_TASK(AsyncTask, Utils::AsyncTaskAdapter); +TASKING_DECLARE_TEMPLATE_TASK(AsyncTask, Utils::AsyncTaskAdapter); diff --git a/src/libs/utils/barrier.h b/src/libs/utils/barrier.h index 9e5f8d5037..fe2d7bbe62 100644 --- a/src/libs/utils/barrier.h +++ b/src/libs/utils/barrier.h @@ -43,7 +43,7 @@ public: } // namespace Tasking -QTC_DECLARE_CUSTOM_TASK(BarrierTask, Tasking::BarrierTaskAdapter); +TASKING_DECLARE_TASK(BarrierTask, Tasking::BarrierTaskAdapter); namespace Tasking { diff --git a/src/libs/utils/filestreamer.cpp b/src/libs/utils/filestreamer.cpp index 67a8a6cd92..2d4d490940 100644 --- a/src/libs/utils/filestreamer.cpp +++ b/src/libs/utils/filestreamer.cpp @@ -300,8 +300,8 @@ public: } // namespace Utils -QTC_DECLARE_CUSTOM_TASK(Reader, Utils::FileStreamReaderAdapter); -QTC_DECLARE_CUSTOM_TASK(Writer, Utils::FileStreamWriterAdapter); +TASKING_DECLARE_TASK(Reader, Utils::FileStreamReaderAdapter); +TASKING_DECLARE_TASK(Writer, Utils::FileStreamWriterAdapter); namespace Utils { diff --git a/src/libs/utils/filestreamer.h b/src/libs/utils/filestreamer.h index 3bbca65b8f..8e46d7d164 100644 --- a/src/libs/utils/filestreamer.h +++ b/src/libs/utils/filestreamer.h @@ -59,4 +59,4 @@ public: } // namespace Utils -QTC_DECLARE_CUSTOM_TASK(FileStreamerTask, Utils::FileStreamerTaskAdapter); +TASKING_DECLARE_TASK(FileStreamerTask, Utils::FileStreamerTaskAdapter); diff --git a/src/libs/utils/process.h b/src/libs/utils/process.h index 6de4f4418b..272e25f377 100644 --- a/src/libs/utils/process.h +++ b/src/libs/utils/process.h @@ -218,6 +218,6 @@ public: } // namespace Utils -QTC_DECLARE_CUSTOM_TASK(ProcessTask, Utils::ProcessTaskAdapter); +TASKING_DECLARE_TASK(ProcessTask, Utils::ProcessTaskAdapter); #endif // UTILS_PROCESS_H diff --git a/src/libs/utils/tasktree.h b/src/libs/utils/tasktree.h index b37749f9a4..d8545564b9 100644 --- a/src/libs/utils/tasktree.h +++ b/src/libs/utils/tasktree.h @@ -416,13 +416,13 @@ public: } // namespace Tasking -#define QTC_DECLARE_CUSTOM_TASK(CustomTaskName, TaskAdapterClass)\ +#define TASKING_DECLARE_TASK(CustomTaskName, TaskAdapterClass)\ namespace Tasking { using CustomTaskName = CustomTask; } -#define QTC_DECLARE_CUSTOM_TEMPLATE_TASK(CustomTaskName, TaskAdapterClass)\ +#define TASKING_DECLARE_TEMPLATE_TASK(CustomTaskName, TaskAdapterClass)\ namespace Tasking {\ template \ using CustomTaskName = CustomTask>;\ } // namespace Tasking -QTC_DECLARE_CUSTOM_TASK(TaskTreeTask, TaskTreeTaskAdapter); +TASKING_DECLARE_TASK(TaskTreeTask, TaskTreeTaskAdapter); -- cgit v1.2.3 From a1792c653ebeaa251523bddc0e66dbda70551bf7 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 10 May 2023 20:25:05 +0200 Subject: TaskTree: Prepare for de-utils-ization - part 4 Add internal implementation for qtcassert.{c,h}. Change-Id: I646b302be2f86e32e91c8c6fafa7b799e48773c2 Reviewed-by: Qt CI Bot Reviewed-by: hjk --- src/libs/utils/barrier.cpp | 9 +++++++-- src/libs/utils/tasktree.cpp | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/barrier.cpp b/src/libs/utils/barrier.cpp index 690391cb0f..c4daa033b4 100644 --- a/src/libs/utils/barrier.cpp +++ b/src/libs/utils/barrier.cpp @@ -3,10 +3,15 @@ #include "barrier.h" -#include "qtcassert.h" - namespace Tasking { +// That's cut down qtcassert.{c,h} to avoid the dependency. +#define QTC_STRINGIFY_HELPER(x) #x +#define QTC_STRINGIFY(x) QTC_STRINGIFY_HELPER(x) +#define QTC_STRING(cond) qDebug("SOFT ASSERT: \"%s\" in %s: %s", cond, __FILE__, QTC_STRINGIFY(__LINE__)) +#define QTC_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QTC_STRING(#cond); action; } do {} while (0) +#define QTC_CHECK(cond) if (cond) {} else { QTC_STRING(#cond); } do {} while (0) + void Barrier::setLimit(int value) { QTC_ASSERT(!isRunning(), return); diff --git a/src/libs/utils/tasktree.cpp b/src/libs/utils/tasktree.cpp index 2c88546e6c..75de54c374 100644 --- a/src/libs/utils/tasktree.cpp +++ b/src/libs/utils/tasktree.cpp @@ -3,12 +3,17 @@ #include "tasktree.h" -#include "qtcassert.h" - #include namespace Tasking { +// That's cut down qtcassert.{c,h} to avoid the dependency. +#define QTC_STRINGIFY_HELPER(x) #x +#define QTC_STRINGIFY(x) QTC_STRINGIFY_HELPER(x) +#define QTC_STRING(cond) qDebug("SOFT ASSERT: \"%s\" in %s: %s", cond, __FILE__, QTC_STRINGIFY(__LINE__)) +#define QTC_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QTC_STRING(#cond); action; } do {} while (0) +#define QTC_CHECK(cond) if (cond) {} else { QTC_STRING(#cond); } do {} while (0) + class Guard { Q_DISABLE_COPY(Guard) -- cgit v1.2.3 From 1a98dda5c417e892cadbedb656829cf2ba4d1d0a Mon Sep 17 00:00:00 2001 From: David Schulz Date: Thu, 11 May 2023 09:34:53 +0200 Subject: Utils: fix Text::Range length and remove mid Those functions are based on the assumption that the passed text starts at the begin position, which was good enough for search results, but if used in other parts of the codebase it might give unwanted results. Calculate the length of the range now as expected and subtract the beginning lines. In order to still got the correct results for the text result texts modify the result range to always start at the first line before calculating the length of the range. Also add tests for the modified functionality Change-Id: I7ccd75b642dda6dd4f738877cbe3543d46c03652 Reviewed-by: Qt CI Bot Reviewed-by: Jarek Kobus --- src/libs/utils/textutils.cpp | 24 ++++++++++++++++-------- src/libs/utils/textutils.h | 2 +- 2 files changed, 17 insertions(+), 9 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/textutils.cpp b/src/libs/utils/textutils.cpp index 34dbb5e1fa..8cf7a41ed6 100644 --- a/src/libs/utils/textutils.cpp +++ b/src/libs/utils/textutils.cpp @@ -58,22 +58,30 @@ Position Position::fromPositionInDocument(const QTextDocument *document, int pos int Range::length(const QString &text) const { + if (end.line < begin.line) + return -1; + if (begin.line == end.line) return end.column - begin.column; - const int lineCount = end.line - begin.line; - int index = text.indexOf(QChar::LineFeed); + int index = 0; int currentLine = 1; - while (index > 0 && currentLine < lineCount) { + while (currentLine < begin.line) { + index = text.indexOf(QChar::LineFeed, index); + if (index < 0) + return -1; ++index; + ++currentLine; + } + const int beginIndex = index + begin.column; + while (currentLine < end.line) { index = text.indexOf(QChar::LineFeed, index); + if (index < 0) + return -1; + ++index; ++currentLine; } - - if (index < 0) - return 0; - - return index - begin.column + end.column; + return index + end.column - beginIndex; } bool Range::operator==(const Range &other) const diff --git a/src/libs/utils/textutils.h b/src/libs/utils/textutils.h index 4ba3b0e1a8..6ee82274dc 100644 --- a/src/libs/utils/textutils.h +++ b/src/libs/utils/textutils.h @@ -37,7 +37,6 @@ public: class QTCREATOR_UTILS_EXPORT Range { public: - QString mid(const QString &text) const { return text.mid(begin.column, length(text)); } int length(const QString &text) const; Position begin; @@ -97,3 +96,4 @@ QTCREATOR_UTILS_EXPORT QString utf16LineTextInUtf8Buffer(const QByteArray &utf8B } // Utils Q_DECLARE_METATYPE(Utils::Text::Position) +Q_DECLARE_METATYPE(Utils::Text::Range) -- cgit v1.2.3 From ed6f5e348687b6f22e803448a3c54965d573c45d Mon Sep 17 00:00:00 2001 From: hjk Date: Thu, 11 May 2023 16:55:01 +0200 Subject: Layouting: Add a plain 'Widget' item Change-Id: Id419b1efd56f51fb282b11c4b241b96eb7d7d0ae Reviewed-by: Alessandro Portale --- src/libs/utils/layoutbuilder.cpp | 6 ++++++ src/libs/utils/layoutbuilder.h | 6 ++++++ 2 files changed, 12 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index 226120b823..e3582f6ade 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -534,6 +534,12 @@ void setupWidget(LayoutItem *item) item->onExit = widgetExit; }; +Widget::Widget(std::initializer_list items) +{ + this->subItems = items; + setupWidget(this); +} + Group::Group(std::initializer_list items) { this->subItems = items; diff --git a/src/libs/utils/layoutbuilder.h b/src/libs/utils/layoutbuilder.h index 33fba0270d..5f69e0ef6a 100644 --- a/src/libs/utils/layoutbuilder.h +++ b/src/libs/utils/layoutbuilder.h @@ -112,6 +112,12 @@ public: Form(std::initializer_list items); }; +class QTCREATOR_UTILS_EXPORT Widget : public LayoutItem +{ +public: + Widget(std::initializer_list items); +}; + class QTCREATOR_UTILS_EXPORT Stack : public LayoutItem { public: -- cgit v1.2.3 From 6149fd8bfa742d45b258bd8d4f08e7ce867841e8 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Fri, 12 May 2023 11:12:20 +0200 Subject: Utils: Cache FilePath::qHash Calling toCaseFolded is expensive if the same filepath is hashed often, so we calculate the hash value once and cache the result. One such case was found to happen when parsing tests, as the cpp parser is queried a lot and it uses hash maps / sets with filepaths. Change-Id: Ic3ca7f09e8f108f5a89b3fdb17743ae7c3258001 Reviewed-by: hjk --- src/libs/utils/filepath.cpp | 15 +++++++++++---- src/libs/utils/filepath.h | 1 + 2 files changed, 12 insertions(+), 4 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 9bf4780596..ab6ec4da1f 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -441,6 +441,7 @@ void FilePath::setParts(const QStringView scheme, const QStringView host, QStrin if (path.length() >= 3 && path[0] == '/' && path[1] == '.' && path[2] == '/') path = path.mid(3); + m_hash = 0; m_data = path.toString() + scheme.toString() + host.toString(); m_schemeLen = scheme.size(); m_hostLen = host.size(); @@ -2078,10 +2079,16 @@ QTCREATOR_UTILS_EXPORT bool operator>=(const FilePath &first, const FilePath &se QTCREATOR_UTILS_EXPORT size_t qHash(const FilePath &filePath, uint seed) { - if (filePath.caseSensitivity() == Qt::CaseSensitive) - return qHash(QStringView(filePath.m_data), seed); - const size_t schemeHostHash = qHash(QStringView(filePath.m_data).mid(filePath.m_pathLen), seed); - return qHash(filePath.path().toCaseFolded(), seed) ^ schemeHostHash; + Q_UNUSED(seed); + + if (filePath.m_hash == 0) { + if (filePath.caseSensitivity() == Qt::CaseSensitive) + filePath.m_hash = qHash(QStringView(filePath.m_data), 0); + else + filePath.m_hash = qHash(filePath.m_data.toCaseFolded(), 0); + } + + return filePath.m_hash; } QTCREATOR_UTILS_EXPORT size_t qHash(const FilePath &filePath) diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h index 6465169154..142542b734 100644 --- a/src/libs/utils/filepath.h +++ b/src/libs/utils/filepath.h @@ -277,6 +277,7 @@ private: unsigned int m_pathLen = 0; unsigned short m_schemeLen = 0; unsigned short m_hostLen = 0; + mutable size_t m_hash = 0; }; class QTCREATOR_UTILS_EXPORT DeviceFileHooks -- cgit v1.2.3 From 69fbe727525c01d7d84ec5f860b99f04086da294 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Fri, 12 May 2023 13:33:45 +0200 Subject: Utils: Use FilePath hash in operator== Change-Id: Ibbea420a5a5353da221d285b70272cdf9b09d0d0 Reviewed-by: Jarek Kobus --- src/libs/utils/filepath.cpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index ab6ec4da1f..5681837582 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -2042,6 +2042,9 @@ DeviceFileHooks &DeviceFileHooks::instance() QTCREATOR_UTILS_EXPORT bool operator==(const FilePath &first, const FilePath &second) { + if (first.m_hash != 0 && second.m_hash != 0 && first.m_hash != second.m_hash) + return false; + return first.pathView().compare(second.pathView(), first.caseSensitivity()) == 0 && first.host() == second.host() && first.scheme() == second.scheme(); -- cgit v1.2.3 From 0dbc208a070812de867cec27c960ed7de31e8bf7 Mon Sep 17 00:00:00 2001 From: Cristian Adam Date: Fri, 12 May 2023 13:18:35 +0200 Subject: CMake: Add -fPIC also for OBJECT libraries add_qtc_library would have set the POSITION_INDEPENDENT_CODE property for STATIC libraries on UNIX based systems. The OBJECT libraries need the same treatment. Change-Id: Ia333a36ea0f35d7db3ed876cdde5b895b47644c7 Reviewed-by: Eike Ziller --- src/libs/3rdparty/cplusplus/CMakeLists.txt | 1 - src/libs/sqlite/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/3rdparty/cplusplus/CMakeLists.txt b/src/libs/3rdparty/cplusplus/CMakeLists.txt index 2758ffe6ee..d9f130b470 100644 --- a/src/libs/3rdparty/cplusplus/CMakeLists.txt +++ b/src/libs/3rdparty/cplusplus/CMakeLists.txt @@ -41,7 +41,6 @@ add_qtc_library(3rd_cplusplus OBJECT TypeVisitor.cpp TypeVisitor.h cppassert.h SKIP_PCH - PROPERTIES POSITION_INDEPENDENT_CODE ON ) set(export_symbol_declaration DEFINES CPLUSPLUS_BUILD_LIB) diff --git a/src/libs/sqlite/CMakeLists.txt b/src/libs/sqlite/CMakeLists.txt index 7f33b18921..4c7cd774c6 100644 --- a/src/libs/sqlite/CMakeLists.txt +++ b/src/libs/sqlite/CMakeLists.txt @@ -1,5 +1,5 @@ add_qtc_library(SqliteC OBJECT - PROPERTIES AUTOMOC OFF AUTOUIC OFF QT_COMPILE_OPTIONS_DISABLE_WARNINGS ON POSITION_INDEPENDENT_CODE ON + PROPERTIES AUTOMOC OFF AUTOUIC OFF QT_COMPILE_OPTIONS_DISABLE_WARNINGS ON DEFINES SQLITE_CORE SQLITE_CUSTOM_INCLUDE=config.h $<$:SQLITE_DEBUG> PROPERTIES COMPILE_OPTIONS $,/FIconfig.h,-includeconfig.h> PUBLIC_INCLUDES -- cgit v1.2.3 From ddee1c92a48dde69d236eb14570f5a03d07dbe42 Mon Sep 17 00:00:00 2001 From: hjk Date: Fri, 12 May 2023 13:18:07 +0200 Subject: Utils: Add operator() as a way to access .value() for some aspects This can make the user code look a bit nicer. Change-Id: I98867114810ede2f04342144f600682ff3c261b4 Reviewed-by: Alessandro Portale Reviewed-by: Christian Stenger --- src/libs/utils/aspects.h | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index c511595e1b..3e4bd9ca96 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -229,6 +229,7 @@ public: void setVolatileValue(const QVariant &val) override; void emitChangedValue() override; + bool operator()() const { return value(); } bool value() const; void setValue(bool val); bool defaultValue() const; @@ -372,6 +373,8 @@ public: // Hook between UI and StringAspect: using ValueAcceptor = std::function(const QString &, const QString &)>; void setValueAcceptor(ValueAcceptor &&acceptor); + + QString operator()() const { return value(); } QString value() const; void setValue(const QString &val); @@ -449,6 +452,7 @@ public: QVariant volatileValue() const override; void setVolatileValue(const QVariant &val) override; + qint64 operator()() const { return value(); } qint64 value() const; void setValue(qint64 val); @@ -486,6 +490,7 @@ public: QVariant volatileValue() const override; void setVolatileValue(const QVariant &val) override; + double operator()() const { return value(); } double value() const; void setValue(double val); -- cgit v1.2.3 From 5cf5b1ae3f94edcffab95044b9d361d5d6a28b17 Mon Sep 17 00:00:00 2001 From: hjk Date: Fri, 12 May 2023 14:13:28 +0200 Subject: Utils: Add an AspectContainer::changed() signal In contrast to applied() only emitted if anything was dirty before. Ideally, applied() would not be needed, but I am not sure about downstream uses. Change-Id: Ie0c293b8730c503fc4409884a33207ee9ca5f129 Reviewed-by: Alessandro Portale --- src/libs/utils/aspects.cpp | 5 +++++ src/libs/utils/aspects.h | 1 + 2 files changed, 6 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index 0f75cfcbde..1eecb1de17 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -2475,10 +2475,15 @@ void AspectContainer::setSettingsGroups(const QString &groupKey, const QString & void AspectContainer::apply() { + const bool willChange = isDirty(); + for (BaseAspect *aspect : std::as_const(d->m_items)) aspect->apply(); emit applied(); + + if (willChange) + emit changed(); } void AspectContainer::cancel() diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index 3e4bd9ca96..266a41f8ee 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -695,6 +695,7 @@ public: signals: void applied(); + void changed(); void fromMapFinished(); private: -- cgit v1.2.3 From 4c1a161abd946b4bf51c6cc573728cf25f47876e Mon Sep 17 00:00:00 2001 From: hjk Date: Fri, 12 May 2023 16:27:01 +0200 Subject: Utils: Fix some IntegerAspect value display issue ... when a display scale factor was set. Change-Id: I764db99e444f9cc70871c3dbec101d0b65542c4a Reviewed-by: Christian Stenger --- src/libs/utils/aspects.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index 1eecb1de17..8aa1f77bee 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -1913,7 +1913,7 @@ void IntegerAspect::addToLayout(Layouting::LayoutItem &parent) if (isAutoApply()) { connect(d->m_spinBox.data(), &QSpinBox::valueChanged, - this, [this] { setValue(d->m_spinBox->value()); }); + this, [this] { setValue(d->m_spinBox->value() * d->m_displayScaleFactor); }); } } @@ -1940,7 +1940,7 @@ void IntegerAspect::setValue(qint64 value) { if (BaseAspect::setValueQuietly(value)) { if (d->m_spinBox) - d->m_spinBox->setValue(value); + d->m_spinBox->setValue(value / d->m_displayScaleFactor); //qDebug() << "SetValue: Changing" << labelText() << " to " << value; emit changed(); //QTC_CHECK(!labelText().isEmpty()); -- cgit v1.2.3 From ab8f3d78ae6bfeaa0253cca9de35b206fa8e02de Mon Sep 17 00:00:00 2001 From: hjk Date: Fri, 12 May 2023 08:38:01 +0200 Subject: Utils: Use QAbstractButton instead of QCheckBox in BoolAspect Opens the path to use e.g. QRadioButton. Change-Id: Idb1591c0a1486181b8aeb51edb93bc4bfecef834 Reviewed-by: Alessandro Portale Reviewed-by: --- src/libs/utils/aspects.cpp | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index 8aa1f77bee..ae3841c647 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -585,7 +585,7 @@ class BoolAspectPrivate { public: BoolAspect::LabelPlacement m_labelPlacement = BoolAspect::LabelPlacement::AtCheckBox; - QPointer m_checkBox; // Owned by configuration widget + QPointer m_button; // Owned by configuration widget QPointer m_groupBox; // For BoolAspects handling GroupBox check boxes }; @@ -1424,31 +1424,31 @@ BoolAspect::~BoolAspect() = default; */ void BoolAspect::addToLayout(Layouting::LayoutItem &parent) { - QTC_CHECK(!d->m_checkBox); - d->m_checkBox = createSubWidget(); + QTC_CHECK(!d->m_button); + d->m_button = createSubWidget(); switch (d->m_labelPlacement) { case LabelPlacement::AtCheckBoxWithoutDummyLabel: - d->m_checkBox->setText(labelText()); - parent.addItem(d->m_checkBox.data()); + d->m_button->setText(labelText()); + parent.addItem(d->m_button.data()); break; case LabelPlacement::AtCheckBox: { - d->m_checkBox->setText(labelText()); + d->m_button->setText(labelText()); // FIXME: //if (parent.isForm()) // parent.addItem(createSubWidget()); - parent.addItem(d->m_checkBox.data()); + parent.addItem(d->m_button.data()); break; } case LabelPlacement::InExtraLabel: - addLabeledItem(parent, d->m_checkBox); + addLabeledItem(parent, d->m_button); break; } - d->m_checkBox->setChecked(value()); + d->m_button->setChecked(value()); if (isAutoApply()) { - connect(d->m_checkBox.data(), &QAbstractButton::clicked, + connect(d->m_button.data(), &QAbstractButton::clicked, this, [this](bool val) { setValue(val); }); } - connect(d->m_checkBox.data(), &QAbstractButton::clicked, + connect(d->m_button.data(), &QAbstractButton::clicked, this, &BoolAspect::volatileValueChanged); } @@ -1486,8 +1486,8 @@ QAction *BoolAspect::action() QVariant BoolAspect::volatileValue() const { QTC_CHECK(!isAutoApply()); - if (d->m_checkBox) - return d->m_checkBox->isChecked(); + if (d->m_button) + return d->m_button->isChecked(); if (d->m_groupBox) return d->m_groupBox->isChecked(); QTC_CHECK(false); @@ -1497,8 +1497,8 @@ QVariant BoolAspect::volatileValue() const void BoolAspect::setVolatileValue(const QVariant &val) { QTC_CHECK(!isAutoApply()); - if (d->m_checkBox) - d->m_checkBox->setChecked(val.toBool()); + if (d->m_button) + d->m_button->setChecked(val.toBool()); else if (d->m_groupBox) d->m_groupBox->setChecked(val.toBool()); } @@ -1521,8 +1521,8 @@ bool BoolAspect::value() const void BoolAspect::setValue(bool value) { if (BaseAspect::setValueQuietly(value)) { - if (d->m_checkBox) - d->m_checkBox->setChecked(value); + if (d->m_button) + d->m_button->setChecked(value); //qDebug() << "SetValue: Changing" << labelText() << " to " << value; emit changed(); //QTC_CHECK(!labelText().isEmpty()); -- cgit v1.2.3 From e71f9b9c87076302617e55f800b717a2912b0f65 Mon Sep 17 00:00:00 2001 From: hjk Date: Fri, 12 May 2023 18:29:24 +0200 Subject: Uitls: Make BaseAspect::isDirty() virtual It's sometimes not so easy to trigger the volatileValue() != value() branch, so create a way to be explicit when needed. Change-Id: I322508a33c935077038d730fd09c819419901353 Reviewed-by: Reviewed-by: Alessandro Portale --- src/libs/utils/aspects.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index 266a41f8ee..b610c1ffbe 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -115,7 +115,7 @@ public: virtual void apply(); virtual void cancel(); virtual void finish(); - bool isDirty() const; + virtual bool isDirty() const; bool hasAction() const; class QTCREATOR_UTILS_EXPORT Data -- cgit v1.2.3 From c158921af32e44e9e2850d853f83450651758dd7 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Thu, 4 May 2023 09:06:46 +0200 Subject: ProjectExplorer: Don't allow remote run in terminal Currently the process stub does not support starting / debugging processes on remote devices. To reflect this the "Run In Terminal" aspect is disabled for remote targets. Fixes: QTCREATORBUG-29058 Change-Id: I9b3bcd65d4db468c683f2743a49227bfbecaf3d3 Reviewed-by: hjk --- src/libs/utils/terminalinterface.cpp | 2 -- 1 file changed, 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/terminalinterface.cpp b/src/libs/utils/terminalinterface.cpp index 6972184e32..6767c16c61 100644 --- a/src/libs/utils/terminalinterface.cpp +++ b/src/libs/utils/terminalinterface.cpp @@ -183,8 +183,6 @@ void TerminalInterface::onStubReadyRead() emitFinished(out.mid(5).toInt(), QProcess::NormalExit); } else if (out.startsWith("crash ")) { emitFinished(out.mid(6).toInt(), QProcess::CrashExit); - } else if (out.startsWith("qtc: ")) { - emit readyRead(out.mid(5) + "\n", {}); } else { emitError(QProcess::UnknownError, msgUnexpectedOutput(out)); break; -- cgit v1.2.3 From c82d7ccdcde982b40ecb6690af2dd182fd8dba75 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Mon, 15 May 2023 11:03:16 +0200 Subject: Utils: Delay close signal in pty process The close signal of the conpty process needs to be delayed as it otherwise might arrive before the last output of the process. This should be fixed in the future by using overlapped io for the pipes. Change-Id: I49fe4863672a0b14f5766bbe5ee7b1d8894594ca Reviewed-by: Orgad Shaneh --- src/libs/3rdparty/libptyqt/conptyprocess.cpp | 2 +- src/libs/3rdparty/libptyqt/conptyprocess.h | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) (limited to 'src/libs') diff --git a/src/libs/3rdparty/libptyqt/conptyprocess.cpp b/src/libs/3rdparty/libptyqt/conptyprocess.cpp index 687d3116f5..b9a4afd126 100644 --- a/src/libs/3rdparty/libptyqt/conptyprocess.cpp +++ b/src/libs/3rdparty/libptyqt/conptyprocess.cpp @@ -184,7 +184,7 @@ bool ConPtyProcess::startProcess(const QString &executable, if (!m_aboutToDestruct) emit notifier()->aboutToClose(); m_shellCloseWaitNotifier->setEnabled(false); - }); + }, Qt::QueuedConnection); //this code runned in separate thread m_readThread = QThread::create([this]() { diff --git a/src/libs/3rdparty/libptyqt/conptyprocess.h b/src/libs/3rdparty/libptyqt/conptyprocess.h index ac94048981..d4ffd62b7e 100644 --- a/src/libs/3rdparty/libptyqt/conptyprocess.h +++ b/src/libs/3rdparty/libptyqt/conptyprocess.h @@ -115,11 +115,7 @@ public: void emitReadyRead() { - //for emit signal from PtyBuffer own thread - QTimer::singleShot(1, this, [this]() - { - emit readyRead(); - }); + emit readyRead(); } private: -- cgit v1.2.3 From b4734ff7279284eec4f0ced0e4de07cb348c481e Mon Sep 17 00:00:00 2001 From: David Schulz Date: Fri, 12 May 2023 10:59:04 +0200 Subject: Utils: add tests for Position::fromFileName Change-Id: I321b91567e47e08883c7b991cd24d02bb8a9b9c6 Reviewed-by: Jarek Kobus --- src/libs/utils/textutils.cpp | 9 ++++++++- src/libs/utils/textutils.h | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/textutils.cpp b/src/libs/utils/textutils.cpp index 8cf7a41ed6..9cb7c1e011 100644 --- a/src/libs/utils/textutils.cpp +++ b/src/libs/utils/textutils.cpp @@ -32,7 +32,6 @@ Position Position::fromFileName(QStringView fileName, int &postfixPos) Position pos; if (match.hasMatch()) { postfixPos = match.capturedStart(0); - pos.line = 0; // for the case that there's only a : at the end if (match.lastCapturedIndex() > 0) { pos.line = match.captured(1).toInt(); if (match.lastCapturedIndex() > 2) // index 2 includes the + or : for the column number @@ -44,6 +43,8 @@ Position Position::fromFileName(QStringView fileName, int &postfixPos) if (vsMatch.lastCapturedIndex() > 1) // index 1 includes closing ) pos.line = vsMatch.captured(2).toInt(); } + if (pos.line > 0 && pos.column < 0) + pos.column = 0; // if we got a valid line make sure to return a valid TextPosition return pos; } @@ -264,4 +265,10 @@ void applyReplacements(QTextDocument *doc, const Replacements &replacements) editCursor.endEditBlock(); } +QDebug &operator<<(QDebug &stream, const Position &pos) +{ + stream << "line: " << pos.line << ", column: " << pos.column; + return stream; +} + } // namespace Utils::Text diff --git a/src/libs/utils/textutils.h b/src/libs/utils/textutils.h index 6ee82274dc..df1eb73d8e 100644 --- a/src/libs/utils/textutils.h +++ b/src/libs/utils/textutils.h @@ -92,6 +92,8 @@ QTCREATOR_UTILS_EXPORT int utf8NthLineOffset(const QTextDocument *textDocument, QTCREATOR_UTILS_EXPORT QString utf16LineTextInUtf8Buffer(const QByteArray &utf8Buffer, int currentUtf8Offset); +QTCREATOR_UTILS_EXPORT QDebug &operator<<(QDebug &stream, const Position &pos); + } // Text } // Utils -- cgit v1.2.3 From 8b2d7977ca74c09e12d235d16a5c6473e60e9961 Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Mon, 15 May 2023 14:04:34 +0200 Subject: Utils: Make CTAD work with std::unexpected Aliases are not working with CTAD before C++ 20. Instead of aliasing some types we simply importing the namespace tl into the namespace Utils. This enables some not aliased things too. Change-Id: Ic61a50bedbbf7253ecb5bb1f6dc0624dcc704aa0 Reviewed-by: Reviewed-by: Marcus Tillmanns Reviewed-by: Qt CI Bot --- src/libs/utils/expected.h | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/expected.h b/src/libs/utils/expected.h index 943cfe5591..33231c1246 100644 --- a/src/libs/utils/expected.h +++ b/src/libs/utils/expected.h @@ -9,24 +9,11 @@ namespace Utils { -template -using expected = tl::expected; +using namespace tl; template using expected_str = tl::expected; -template -using unexpected = tl::unexpected; -using unexpect_t = tl::unexpect_t; - -static constexpr unexpect_t unexpect{}; - -template -constexpr unexpected> make_unexpected(E &&e) -{ - return tl::make_unexpected(e); -} - } // namespace Utils //! If 'expected' has an error the error will be printed and the 'action' will be executed. -- cgit v1.2.3 From 9758e7145828e1303a33af3b1ae04c4210b55a28 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Fri, 12 May 2023 10:59:29 +0200 Subject: Utils: add tests for Position::fromPositionInDocument Change-Id: I2b530cf62a4defe0292c51834b1e5093a7d5e55f Reviewed-by: Reviewed-by: Jarek Kobus Reviewed-by: Qt CI Bot --- src/libs/utils/textutils.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/textutils.cpp b/src/libs/utils/textutils.cpp index 9cb7c1e011..a57e531059 100644 --- a/src/libs/utils/textutils.cpp +++ b/src/libs/utils/textutils.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "textutils.h" +#include "qtcassert.h" #include #include @@ -50,6 +51,7 @@ Position Position::fromFileName(QStringView fileName, int &postfixPos) Position Position::fromPositionInDocument(const QTextDocument *document, int pos) { + QTC_ASSERT(document, return {}); const QTextBlock block = document->findBlock(pos); if (block.isValid()) return {block.blockNumber() + 1, pos - block.position()}; -- cgit v1.2.3 From b91f234c7daf03b116ac5bd882cac286d520ee7f Mon Sep 17 00:00:00 2001 From: hjk Date: Thu, 2 Mar 2023 15:29:50 +0100 Subject: ExtensionSystem: Start reworking initialization order system Allow specifying object creation without immediate creation (but create them nevetheless very soon). Change-Id: I01b91f7cf753ced61705d3f26352548b268be6b5 Reviewed-by: Eike Ziller --- src/libs/extensionsystem/iplugin.cpp | 53 +++++++++++++++++++++++++++++++++ src/libs/extensionsystem/iplugin.h | 25 ++++++++++++++++ src/libs/extensionsystem/pluginspec.cpp | 6 +++- 3 files changed, 83 insertions(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/extensionsystem/iplugin.cpp b/src/libs/extensionsystem/iplugin.cpp index 9f7ca221e5..6661a46f61 100644 --- a/src/libs/extensionsystem/iplugin.cpp +++ b/src/libs/extensionsystem/iplugin.cpp @@ -161,12 +161,47 @@ namespace ExtensionSystem { namespace Internal { +class ObjectInitializer +{ +public: + ObjectCreator creator; + ObjectDestructor destructor; + ObjectCreationPolicy policy; +}; + class IPluginPrivate { public: + void tryCreateObjects(); + QList testCreators; + + QList objectInitializers; + QList> objectDestructors; + + // For debugging purposes: + QList createdObjects; // Not owned. }; +void IPluginPrivate::tryCreateObjects() +{ + QList unhandledObjectInitializers; + + for (const ObjectInitializer &initializer : std::as_const(objectInitializers)) { + if (!initializer.policy.dependsOn.isEmpty()) { + qWarning("Initialization dependencies are not supported yet"); + unhandledObjectInitializers.append(initializer); + continue; + } + + void *object = initializer.creator(); + createdObjects.append(object); + objectDestructors.append([initializer, object] { initializer.destructor(object); }); + } + + objectInitializers = unhandledObjectInitializers; +} + } // Internal /*! @@ -182,10 +217,20 @@ IPlugin::IPlugin() */ IPlugin::~IPlugin() { + for (const std::function &dtor : std::as_const(d->objectDestructors)) + dtor(); + delete d; d = nullptr; } +void IPlugin::addManagedHelper(const ObjectCreator &creator, + const ObjectDestructor &destructor, + const ObjectCreationPolicy &policy) +{ + d->objectInitializers.append({creator, destructor, policy}); +} + bool IPlugin::initialize(const QStringList &arguments, QString *errorString) { Q_UNUSED(arguments) @@ -194,6 +239,14 @@ bool IPlugin::initialize(const QStringList &arguments, QString *errorString) return true; } +/*! + \internal +*/ +void IPlugin::tryCreateObjects() +{ + d->tryCreateObjects(); +} + /*! Registers a function object that creates a test object. diff --git a/src/libs/extensionsystem/iplugin.h b/src/libs/extensionsystem/iplugin.h index 5a8fceb3d6..3e477e42e8 100644 --- a/src/libs/extensionsystem/iplugin.h +++ b/src/libs/extensionsystem/iplugin.h @@ -5,6 +5,8 @@ #include "extensionsystem_global.h" +#include + #include #include @@ -15,6 +17,17 @@ namespace Internal { class IPluginPrivate; } using TestCreator = std::function; +using ObjectCreator = std::function; +using ObjectDestructor = std::function; + +struct EXTENSIONSYSTEM_EXPORT ObjectCreationPolicy +{ + // Can be empty if nothing depends on it. + Utils::Id id; + // Objects with empty dependencies are created as soon as possible. + QList dependsOn; +}; + class EXTENSIONSYSTEM_EXPORT IPlugin : public QObject { Q_OBJECT @@ -39,6 +52,7 @@ public: // Deprecated in 10.0, use addTest() virtual QVector createTestObjects() const; + virtual void tryCreateObjects(); protected: virtual void initialize() {} @@ -47,6 +61,17 @@ protected: void addTest(Args && ...args) { addTestCreator([args...] { return new Test(args...); }); } void addTestCreator(const TestCreator &creator); + template + void addManaged(const ObjectCreationPolicy &policy = {}) { + addManagedHelper([]() -> void * { return new Type(); }, + [](void *p) { delete static_cast(p); }, + policy); + } + + void addManagedHelper(const ObjectCreator &creator, + const ObjectDestructor &destructor, + const ObjectCreationPolicy &policy); + signals: void asynchronousShutdownFinished(); diff --git a/src/libs/extensionsystem/pluginspec.cpp b/src/libs/extensionsystem/pluginspec.cpp index d18f410e81..62b3c0096f 100644 --- a/src/libs/extensionsystem/pluginspec.cpp +++ b/src/libs/extensionsystem/pluginspec.cpp @@ -1119,6 +1119,7 @@ bool PluginSpecPrivate::initializePlugin() hasError = true; return false; } + plugin->tryCreateObjects(); state = PluginSpec::Initialized; return true; } @@ -1145,6 +1146,7 @@ bool PluginSpecPrivate::initializeExtensions() return false; } plugin->extensionsInitialized(); + plugin->tryCreateObjects(); state = PluginSpec::Running; return true; } @@ -1164,7 +1166,9 @@ bool PluginSpecPrivate::delayedInitialize() hasError = true; return false; } - return plugin->delayedInitialize(); + const bool res = plugin->delayedInitialize(); + plugin->tryCreateObjects(); + return res; } /*! -- cgit v1.2.3 From 1e595c6afe6032c04d42a41c4d030b3c7980008c Mon Sep 17 00:00:00 2001 From: Cristian Adam Date: Tue, 16 May 2023 12:42:04 +0200 Subject: Terminal: Fix exiting of conpty terminals QMetaObject would complain about not knowing about the HANDLE type. Change-Id: Iae240bed37c892561eaacdc1d22cf4ae0173df29 Reviewed-by: Marcus Tillmanns --- src/libs/3rdparty/libptyqt/conptyprocess.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/3rdparty/libptyqt/conptyprocess.cpp b/src/libs/3rdparty/libptyqt/conptyprocess.cpp index b9a4afd126..cb18b33206 100644 --- a/src/libs/3rdparty/libptyqt/conptyprocess.cpp +++ b/src/libs/3rdparty/libptyqt/conptyprocess.cpp @@ -83,6 +83,8 @@ HRESULT ConPtyProcess::initializeStartupInfoAttachedToPseudoConsole(STARTUPINFOE return hr; } +Q_DECLARE_METATYPE(HANDLE) + ConPtyProcess::ConPtyProcess() : IPtyProcess() , m_ptyHandler { INVALID_HANDLE_VALUE } @@ -90,7 +92,7 @@ ConPtyProcess::ConPtyProcess() , m_hPipeOut { INVALID_HANDLE_VALUE } , m_readThread(nullptr) { - + qRegisterMetaType("HANDLE"); } ConPtyProcess::~ConPtyProcess() -- cgit v1.2.3 From 671621d79bc9fa8542562503a12d381f054b27e8 Mon Sep 17 00:00:00 2001 From: hjk Date: Mon, 15 May 2023 18:24:20 +0200 Subject: Utils: Introduce a FilePathAspect A shallow wrapper around a StringAspect with a suitable operator(). Change-Id: I0a5e121565d03573faa5c3f4085d72db2b9c3774 Reviewed-by: Christian Stenger --- src/libs/utils/aspects.cpp | 22 ++++++++++++++++++++++ src/libs/utils/aspects.h | 8 ++++++++ 2 files changed, 30 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index ae3841c647..541b258c0b 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -1325,6 +1325,28 @@ void StringAspect::makeCheckable(CheckBoxPlacement checkBoxPlacement, update(); } + +/*! + \class Utils::FilePathAspect + \inmodule QtCreator + + \brief A file path aspect is shallow wrapper around a Utils::StringAspect that + represents a file in the file system. + + It is displayed by default using Utils::PathChooser. + + The visual representation often contains a label in front of the display + of the actual value. + + \sa Utils::StringAspect +*/ + + +FilePathAspect::FilePathAspect() +{ + setDisplayStyle(PathChooserDisplay); +} + /*! \class Utils::ColorAspect \inmodule QtCreator diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index b610c1ffbe..1a2570ea83 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -439,6 +439,14 @@ protected: std::unique_ptr d; }; +class QTCREATOR_UTILS_EXPORT FilePathAspect : public StringAspect +{ +public: + FilePathAspect(); + + FilePath operator()() const { return filePath(); } +}; + class QTCREATOR_UTILS_EXPORT IntegerAspect : public BaseAspect { Q_OBJECT -- cgit v1.2.3 From 77c19ae213a3e4cbc9717db2a14124ac7ca59e28 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 16 May 2023 12:51:21 +0200 Subject: Utils: Add minimal Flow layout support to layout builder It will be used in the CppCheck option page. The de-facto copy of utils/flowlayout.{cpp,h} is intentional to keep the layout builder usable stand-alone. Change-Id: Ibda3ece2aeb3cbb1badaf6083b52ebb63b6524ac Reviewed-by: Alessandro Portale --- src/libs/utils/layoutbuilder.cpp | 178 +++++++++++++++++++++++++++++++++++++++ src/libs/utils/layoutbuilder.h | 6 ++ 2 files changed, 184 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index e3582f6ade..43dd68c790 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -27,6 +27,153 @@ namespace Layouting { #define QTC_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QTC_STRING(#cond); action; } do {} while (0) #define QTC_CHECK(cond) if (cond) {} else { QTC_STRING(#cond); } do {} while (0) +class FlowLayout final : public QLayout +{ + Q_OBJECT + +public: + explicit FlowLayout(QWidget *parent, int margin = -1, int hSpacing = -1, int vSpacing = -1) + : QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing) + { + setContentsMargins(margin, margin, margin, margin); + } + + FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1) + : m_hSpace(hSpacing), m_vSpace(vSpacing) + { + setContentsMargins(margin, margin, margin, margin); + } + + ~FlowLayout() override + { + QLayoutItem *item; + while ((item = takeAt(0))) + delete item; + } + + void addItem(QLayoutItem *item) override { itemList.append(item); } + + int horizontalSpacing() const + { + if (m_hSpace >= 0) + return m_hSpace; + else + return smartSpacing(QStyle::PM_LayoutHorizontalSpacing); + } + + int verticalSpacing() const + { + if (m_vSpace >= 0) + return m_vSpace; + else + return smartSpacing(QStyle::PM_LayoutVerticalSpacing); + } + + Qt::Orientations expandingDirections() const override + { + return {}; + } + + bool hasHeightForWidth() const override { return true; } + + int heightForWidth(int width) const override + { + int height = doLayout(QRect(0, 0, width, 0), true); + return height; + } + + int count() const override { return itemList.size(); } + + QLayoutItem *itemAt(int index) const override + { + return itemList.value(index); + } + + QSize minimumSize() const override + { + QSize size; + for (QLayoutItem *item : itemList) + size = size.expandedTo(item->minimumSize()); + + int left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + size += QSize(left + right, top + bottom); + return size; + } + + void setGeometry(const QRect &rect) override + { + QLayout::setGeometry(rect); + doLayout(rect, false); + } + + QSize sizeHint() const override + { + return minimumSize(); + } + + QLayoutItem *takeAt(int index) override + { + if (index >= 0 && index < itemList.size()) + return itemList.takeAt(index); + else + return nullptr; + } + +private: + int doLayout(const QRect &rect, bool testOnly) const + { + int left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom); + int x = effectiveRect.x(); + int y = effectiveRect.y(); + int lineHeight = 0; + + for (QLayoutItem *item : itemList) { + QWidget *wid = item->widget(); + int spaceX = horizontalSpacing(); + if (spaceX == -1) + spaceX = wid->style()->layoutSpacing( + QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal); + int spaceY = verticalSpacing(); + if (spaceY == -1) + spaceY = wid->style()->layoutSpacing( + QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical); + int nextX = x + item->sizeHint().width() + spaceX; + if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) { + x = effectiveRect.x(); + y = y + lineHeight + spaceY; + nextX = x + item->sizeHint().width() + spaceX; + lineHeight = 0; + } + + if (!testOnly) + item->setGeometry(QRect(QPoint(x, y), item->sizeHint())); + + x = nextX; + lineHeight = qMax(lineHeight, item->sizeHint().height()); + } + return y + lineHeight - rect.y() + bottom; + } + + int smartSpacing(QStyle::PixelMetric pm) const + { + QObject *parent = this->parent(); + if (!parent) { + return -1; + } else if (parent->isWidgetType()) { + auto pw = static_cast(parent); + return pw->style()->pixelMetric(pm, nullptr, pw); + } else { + return static_cast(parent)->spacing(); + } + } + + QList itemList; + int m_hSpace; + int m_vSpace; +}; /*! \class Layouting::LayoutItem @@ -136,6 +283,23 @@ static void addItemToBoxLayout(QBoxLayout *layout, const ResultItem &item) } } +static void addItemToFlowLayout(FlowLayout *layout, const ResultItem &item) +{ + if (QWidget *w = item.widget) { + layout->addWidget(w); + } else if (QLayout *l = item.layout) { + layout->addItem(l); +// } else if (item.stretch != -1) { +// layout->addStretch(item.stretch); +// } else if (item.space != -1) { +// layout->addSpacing(item.space); + } else if (!item.text.isEmpty()) { + layout->addWidget(createLabel(item.text)); + } else { + QTC_CHECK(false); + } +} + void Slice::flush() { if (pendingItems.empty()) @@ -212,6 +376,11 @@ void Slice::flush() for (const ResultItem &item : std::as_const(pendingItems)) addItemToBoxLayout(boxLayout, item); + } else if (auto flowLayout = qobject_cast(layout)) { + + for (const ResultItem &item : std::as_const(pendingItems)) + addItemToFlowLayout(flowLayout, item); + } else if (auto stackLayout = qobject_cast(layout)) { for (const ResultItem &item : std::as_const(pendingItems)) { if (item.widget) @@ -425,6 +594,13 @@ Row::Row(std::initializer_list items) onExit = layoutExit; } +Flow::Flow(std::initializer_list items) +{ + subItems = items; + onAdd = [](LayoutBuilder &builder) { builder.stack.append(new FlowLayout); }; + onExit = layoutExit; +} + Grid::Grid(std::initializer_list items) { subItems = items; @@ -809,3 +985,5 @@ void createItem(LayoutItem *item, const Span &t) } } // Layouting + +#include "layoutbuilder.moc" diff --git a/src/libs/utils/layoutbuilder.h b/src/libs/utils/layoutbuilder.h index 5f69e0ef6a..d716ad034c 100644 --- a/src/libs/utils/layoutbuilder.h +++ b/src/libs/utils/layoutbuilder.h @@ -98,6 +98,12 @@ public: Row(std::initializer_list items); }; +class QTCREATOR_UTILS_EXPORT Flow : public LayoutItem +{ +public: + Flow(std::initializer_list items); +}; + class QTCREATOR_UTILS_EXPORT Grid : public LayoutItem { public: -- cgit v1.2.3 From a21b96f4b6bc252c20824c0d807583df4b905f49 Mon Sep 17 00:00:00 2001 From: hjk Date: Fri, 12 May 2023 10:09:52 +0200 Subject: Utils: Allow a BoolAspect to adopt an external button This will be used by the apply machinery and allows more complex setups than the automatically generated internal CheckBox button. Change-Id: I237a9283253f11bcb76e0366a0b6c5a0346fdfd8 Reviewed-by: Reviewed-by: Alessandro Portale --- src/libs/utils/aspects.cpp | 16 ++++++++++++++-- src/libs/utils/aspects.h | 2 ++ 2 files changed, 16 insertions(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index 541b258c0b..549a394273 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -587,6 +587,7 @@ public: BoolAspect::LabelPlacement m_labelPlacement = BoolAspect::LabelPlacement::AtCheckBox; QPointer m_button; // Owned by configuration widget QPointer m_groupBox; // For BoolAspects handling GroupBox check boxes + bool m_buttonIsAdopted = false; }; class ColorAspectPrivate @@ -1446,8 +1447,10 @@ BoolAspect::~BoolAspect() = default; */ void BoolAspect::addToLayout(Layouting::LayoutItem &parent) { - QTC_CHECK(!d->m_button); - d->m_button = createSubWidget(); + if (!d->m_buttonIsAdopted) { + QTC_CHECK(!d->m_button); + d->m_button = createSubWidget(); + } switch (d->m_labelPlacement) { case LabelPlacement::AtCheckBoxWithoutDummyLabel: d->m_button->setText(labelText()); @@ -1474,6 +1477,15 @@ void BoolAspect::addToLayout(Layouting::LayoutItem &parent) this, &BoolAspect::volatileValueChanged); } +void BoolAspect::adoptButton(QAbstractButton *button) +{ + QTC_ASSERT(button, return); + QTC_CHECK(!d->m_button); + d->m_button = button; + d->m_buttonIsAdopted = true; + registerSubWidget(button); +} + std::function BoolAspect::groupChecker() { return [this](QObject *target) { diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index 1a2570ea83..f6a35fa4ef 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -240,6 +240,8 @@ public: LabelPlacement labelPlacement = LabelPlacement::InExtraLabel); void setLabelPlacement(LabelPlacement labelPlacement); + void adoptButton(QAbstractButton *button); + signals: void valueChanged(bool newValue); void volatileValueChanged(bool newValue); -- cgit v1.2.3 From f84199f8b70bb03b66a0dbac3ff4dcdb56094d20 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 10 May 2023 21:38:41 +0200 Subject: Solutions: Long live Solutions! Short live Tasking in Solutions! Add src/libs/solutions/README.md with the motivation and hints. Move TaskTree and Barrier from Utils into Tasking object lib, the first solution in Solutions project. Tasking: Some more work is still required for adapting auto and manual tests. Currently they use Async task, which stayed in Utils. For Qt purposed we most probably need to have a clone of Async task inside the Tasking namespace that is more Qt-like (no Utils::FutureSynchronizer, no priority field, global QThreadPool instead of a custom one for Creator). Change-Id: I5d10a2d68170ffa467d8c299be5995b9aa4f8f77 Reviewed-by: Cristian Adam Reviewed-by: hjk Reviewed-by: Eike Ziller Reviewed-by: Qt CI Bot --- src/libs/CMakeLists.txt | 16 +- src/libs/libs.qbs | 1 + src/libs/solutions/CMakeLists.txt | 1 + src/libs/solutions/README.md | 48 + src/libs/solutions/solutions.qbs | 7 + src/libs/solutions/tasking/CMakeLists.txt | 9 + src/libs/solutions/tasking/barrier.cpp | 52 + src/libs/solutions/tasking/barrier.h | 97 ++ src/libs/solutions/tasking/tasking.qbs | 14 + src/libs/solutions/tasking/tasking_global.h | 14 + src/libs/solutions/tasking/tasktree.cpp | 1489 +++++++++++++++++++++++++++ src/libs/solutions/tasking/tasktree.h | 428 ++++++++ src/libs/utils/CMakeLists.txt | 4 +- src/libs/utils/async.h | 3 +- src/libs/utils/barrier.cpp | 52 - src/libs/utils/barrier.h | 97 -- src/libs/utils/filestreamer.cpp | 3 +- src/libs/utils/filestreamer.h | 3 +- src/libs/utils/process.h | 3 +- src/libs/utils/tasktree.cpp | 1489 --------------------------- src/libs/utils/tasktree.h | 428 -------- src/libs/utils/utils.qbs | 5 +- 22 files changed, 2178 insertions(+), 2085 deletions(-) create mode 100644 src/libs/solutions/CMakeLists.txt create mode 100644 src/libs/solutions/README.md create mode 100644 src/libs/solutions/solutions.qbs create mode 100644 src/libs/solutions/tasking/CMakeLists.txt create mode 100644 src/libs/solutions/tasking/barrier.cpp create mode 100644 src/libs/solutions/tasking/barrier.h create mode 100644 src/libs/solutions/tasking/tasking.qbs create mode 100644 src/libs/solutions/tasking/tasking_global.h create mode 100644 src/libs/solutions/tasking/tasktree.cpp create mode 100644 src/libs/solutions/tasking/tasktree.h delete mode 100644 src/libs/utils/barrier.cpp delete mode 100644 src/libs/utils/barrier.h delete mode 100644 src/libs/utils/tasktree.cpp delete mode 100644 src/libs/utils/tasktree.h (limited to 'src/libs') diff --git a/src/libs/CMakeLists.txt b/src/libs/CMakeLists.txt index fc6b53d81c..2ee109d5cd 100644 --- a/src/libs/CMakeLists.txt +++ b/src/libs/CMakeLists.txt @@ -2,22 +2,22 @@ add_subdirectory(3rdparty) add_subdirectory(advanceddockingsystem) add_subdirectory(aggregation) +add_subdirectory(cplusplus) add_subdirectory(extensionsystem) -add_subdirectory(utils) +add_subdirectory(glsl) +add_subdirectory(languageserverprotocol) add_subdirectory(languageutils) -add_subdirectory(cplusplus) add_subdirectory(modelinglib) add_subdirectory(nanotrace) -add_subdirectory(qmljs) add_subdirectory(qmldebug) add_subdirectory(qmleditorwidgets) -add_subdirectory(glsl) -add_subdirectory(languageserverprotocol) -add_subdirectory(sqlite) -add_subdirectory(tracing) +add_subdirectory(qmljs) add_subdirectory(qmlpuppetcommunication) - add_subdirectory(qtcreatorcdbext) +add_subdirectory(solutions) +add_subdirectory(sqlite) +add_subdirectory(tracing) +add_subdirectory(utils) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/qlitehtml/src/CMakeLists.txt) option(BUILD_LIBRARY_QLITEHTML "Build library qlitehtml." ${BUILD_LIBRARIES_BY_DEFAULT}) diff --git a/src/libs/libs.qbs b/src/libs/libs.qbs index 141e2a6547..ffc3017cba 100644 --- a/src/libs/libs.qbs +++ b/src/libs/libs.qbs @@ -19,6 +19,7 @@ Project { "qmljs/qmljs.qbs", "qmldebug/qmldebug.qbs", "qtcreatorcdbext/qtcreatorcdbext.qbs", + "solutions/solutions.qbs", "sqlite/sqlite.qbs", "tracing/tracing.qbs", "utils/process_ctrlc_stub.qbs", diff --git a/src/libs/solutions/CMakeLists.txt b/src/libs/solutions/CMakeLists.txt new file mode 100644 index 0000000000..694d940195 --- /dev/null +++ b/src/libs/solutions/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(tasking) diff --git a/src/libs/solutions/README.md b/src/libs/solutions/README.md new file mode 100644 index 0000000000..ab4cc9727d --- /dev/null +++ b/src/libs/solutions/README.md @@ -0,0 +1,48 @@ +# Solutions project + +The Solutions project is designed to contain a collection of +object libraries, independent on any Creator's specific code, +ready to be a part of Qt. Kind of a staging area for possible +inclusions into Qt. + +## Motivation and benefits + +- Such a separation will ensure no future back dependencies to the Creator + specific code are introduced by mistake during maintenance. +- Easy to compile outside of Creator code. +- General hub of ideas to be considered by foundation team to be integrated + into Qt. +- The more stuff of a general purpose goes into Qt, the less maintenance work + for Creator. + +## Conformity of solutions + +Each solution: +- Is a separate object lib. +- Is placed in a separate subdirectory. +- Is enclosed within a namespace (namespace name = solution name). +- Should compile independently, i.e. there are no cross-includes + between solutions. + +## Dependencies of solution libraries + +**Do not add dependencies to non-Qt libraries.** +The only allowed dependencies are to Qt libraries. +Especially, don't add dependencies to any Creator's library +nor to any 3rd party library. + +If you can't avoid a dependency to the other Creator's library +in your solution, place it somewhere else (e.g. inside Utils library). + +The Utils lib depends on the solution libraries. + +## Predictions on possible integration into Qt + +The solutions in this project may have a bigger / faster chance to be +integrated into Qt when they: +- Conform to Qt API style. +- Integrate easily with existing classes / types in Qt + (instead of providing own structures / data types). +- Have full docs. +- Have auto tests. +- Have at least one example (however, autotests often play this role, too). diff --git a/src/libs/solutions/solutions.qbs b/src/libs/solutions/solutions.qbs new file mode 100644 index 0000000000..6184dce2af --- /dev/null +++ b/src/libs/solutions/solutions.qbs @@ -0,0 +1,7 @@ +Project { + name: "Solutions" + + references: [ + "tasking/tasking.qbs", + ].concat(project.additionalLibs) +} diff --git a/src/libs/solutions/tasking/CMakeLists.txt b/src/libs/solutions/tasking/CMakeLists.txt new file mode 100644 index 0000000000..5beed2fe5b --- /dev/null +++ b/src/libs/solutions/tasking/CMakeLists.txt @@ -0,0 +1,9 @@ +add_qtc_library(Tasking OBJECT +# Never add dependencies to non-Qt libraries for this library + DEPENDS Qt::Core + PUBLIC_DEFINES TASKING_LIBRARY + SOURCES + barrier.cpp barrier.h + tasking_global.h + tasktree.cpp tasktree.h +) diff --git a/src/libs/solutions/tasking/barrier.cpp b/src/libs/solutions/tasking/barrier.cpp new file mode 100644 index 0000000000..c4daa033b4 --- /dev/null +++ b/src/libs/solutions/tasking/barrier.cpp @@ -0,0 +1,52 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "barrier.h" + +namespace Tasking { + +// That's cut down qtcassert.{c,h} to avoid the dependency. +#define QTC_STRINGIFY_HELPER(x) #x +#define QTC_STRINGIFY(x) QTC_STRINGIFY_HELPER(x) +#define QTC_STRING(cond) qDebug("SOFT ASSERT: \"%s\" in %s: %s", cond, __FILE__, QTC_STRINGIFY(__LINE__)) +#define QTC_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QTC_STRING(#cond); action; } do {} while (0) +#define QTC_CHECK(cond) if (cond) {} else { QTC_STRING(#cond); } do {} while (0) + +void Barrier::setLimit(int value) +{ + QTC_ASSERT(!isRunning(), return); + QTC_ASSERT(value > 0, return); + + m_limit = value; +} + +void Barrier::start() +{ + QTC_ASSERT(!isRunning(), return); + m_current = 0; + m_result = {}; +} + +void Barrier::advance() +{ + // Calling advance on finished is OK + QTC_ASSERT(isRunning() || m_result, return); + if (!isRunning()) // no-op + return; + ++m_current; + if (m_current == m_limit) + stopWithResult(true); +} + +void Barrier::stopWithResult(bool success) +{ + // Calling stopWithResult on finished is OK when the same success is passed + QTC_ASSERT(isRunning() || (m_result && *m_result == success), return); + if (!isRunning()) // no-op + return; + m_current = -1; + m_result = success; + emit done(success); +} + +} // namespace Tasking diff --git a/src/libs/solutions/tasking/barrier.h b/src/libs/solutions/tasking/barrier.h new file mode 100644 index 0000000000..6939da5b36 --- /dev/null +++ b/src/libs/solutions/tasking/barrier.h @@ -0,0 +1,97 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "tasking_global.h" + +#include "tasktree.h" + +namespace Tasking { + +class TASKING_EXPORT Barrier final : public QObject +{ + Q_OBJECT + +public: + void setLimit(int value); + int limit() const { return m_limit; } + + void start(); + void advance(); // If limit reached, stops with true + void stopWithResult(bool success); // Ignores limit + + bool isRunning() const { return m_current >= 0; } + int current() const { return m_current; } + std::optional result() const { return m_result; } + +signals: + void done(bool success); + +private: + std::optional m_result = {}; + int m_limit = 1; + int m_current = -1; +}; + +class TASKING_EXPORT BarrierTaskAdapter : public Tasking::TaskAdapter +{ +public: + BarrierTaskAdapter() { connect(task(), &Barrier::done, this, &TaskInterface::done); } + void start() final { task()->start(); } +}; + +} // namespace Tasking + +TASKING_DECLARE_TASK(BarrierTask, Tasking::BarrierTaskAdapter); + +namespace Tasking { + +template +class SharedBarrier +{ +public: + static_assert(Limit > 0, "SharedBarrier's limit should be 1 or more."); + SharedBarrier() : m_barrier(new Barrier) { + m_barrier->setLimit(Limit); + m_barrier->start(); + } + Barrier *barrier() const { return m_barrier.get(); } + +private: + std::shared_ptr m_barrier; +}; + +template +using MultiBarrier = TreeStorage>; + +// Can't write: "MultiBarrier barrier;". Only "MultiBarrier<> barrier;" would work. +// Can't have one alias with default type in C++17, getting the following error: +// alias template deduction only available with C++20. +using SingleBarrier = MultiBarrier<1>; + +class TASKING_EXPORT WaitForBarrierTask : public BarrierTask +{ +public: + template + WaitForBarrierTask(const MultiBarrier &sharedBarrier) + : BarrierTask([sharedBarrier](Barrier &barrier) { + SharedBarrier *activeBarrier = sharedBarrier.activeStorage(); + if (!activeBarrier) { + qWarning("The barrier referenced from WaitForBarrier element " + "is not reachable in the running tree. " + "It is possible that no barrier was added to the tree, " + "or the storage is not reachable from where it is referenced. " + "The WaitForBarrier task will finish with error. "); + return TaskAction::StopWithError; + } + Barrier *activeSharedBarrier = activeBarrier->barrier(); + const std::optional result = activeSharedBarrier->result(); + if (result.has_value()) + return result.value() ? TaskAction::StopWithDone : TaskAction::StopWithError; + QObject::connect(activeSharedBarrier, &Barrier::done, &barrier, &Barrier::stopWithResult); + return TaskAction::Continue; + }) {} +}; + +} // namespace Tasking diff --git a/src/libs/solutions/tasking/tasking.qbs b/src/libs/solutions/tasking/tasking.qbs new file mode 100644 index 0000000000..8697b9c009 --- /dev/null +++ b/src/libs/solutions/tasking/tasking.qbs @@ -0,0 +1,14 @@ +QtcLibrary { + name: "Tasking" + Depends { name: "Qt"; submodules: ["core"] } + cpp.defines: base.concat("TASKING_LIBRARY") + + files: [ + "barrier.cpp", + "barrier.h", + "tasking_global.h", + "tasktree.cpp", + "tasktree.h", + ] +} + diff --git a/src/libs/solutions/tasking/tasking_global.h b/src/libs/solutions/tasking/tasking_global.h new file mode 100644 index 0000000000..d7e76fa9e6 --- /dev/null +++ b/src/libs/solutions/tasking/tasking_global.h @@ -0,0 +1,14 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +#if defined(TASKING_LIBRARY) +# define TASKING_EXPORT Q_DECL_EXPORT +#elif defined(TASKING_STATIC_LIBRARY) +# define TASKING_EXPORT +#else +# define TASKING_EXPORT Q_DECL_IMPORT +#endif diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp new file mode 100644 index 0000000000..4a66d7f674 --- /dev/null +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -0,0 +1,1489 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "tasktree.h" + +#include + +namespace Tasking { + +// That's cut down qtcassert.{c,h} to avoid the dependency. +#define QTC_STRINGIFY_HELPER(x) #x +#define QTC_STRINGIFY(x) QTC_STRINGIFY_HELPER(x) +#define QTC_STRING(cond) qDebug("SOFT ASSERT: \"%s\" in %s: %s", cond, __FILE__, QTC_STRINGIFY(__LINE__)) +#define QTC_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QTC_STRING(#cond); action; } do {} while (0) +#define QTC_CHECK(cond) if (cond) {} else { QTC_STRING(#cond); } do {} while (0) + +class Guard +{ + Q_DISABLE_COPY(Guard) +public: + Guard() = default; + ~Guard() { QTC_CHECK(m_lockCount == 0); } + bool isLocked() const { return m_lockCount; } +private: + int m_lockCount = 0; + friend class GuardLocker; +}; + +class GuardLocker +{ + Q_DISABLE_COPY(GuardLocker) +public: + GuardLocker(Guard &guard) : m_guard(guard) { ++m_guard.m_lockCount; } + ~GuardLocker() { --m_guard.m_lockCount; } +private: + Guard &m_guard; +}; + +static TaskAction toTaskAction(bool success) +{ + return success ? TaskAction::StopWithDone : TaskAction::StopWithError; +} + +bool TreeStorageBase::isValid() const +{ + return m_storageData && m_storageData->m_constructor && m_storageData->m_destructor; +} + +TreeStorageBase::TreeStorageBase(StorageConstructor ctor, StorageDestructor dtor) + : m_storageData(new StorageData{ctor, dtor}) { } + +TreeStorageBase::StorageData::~StorageData() +{ + QTC_CHECK(m_storageHash.isEmpty()); + for (void *ptr : std::as_const(m_storageHash)) + m_destructor(ptr); +} + +void *TreeStorageBase::activeStorageVoid() const +{ + QTC_ASSERT(m_storageData->m_activeStorage, qWarning( + "The referenced storage is not reachable in the running tree. " + "A nullptr will be returned which might lead to a crash in the calling code. " + "It is possible that no storage was added to the tree, " + "or the storage is not reachable from where it is referenced."); + return nullptr); + const auto it = m_storageData->m_storageHash.constFind(m_storageData->m_activeStorage); + QTC_ASSERT(it != m_storageData->m_storageHash.constEnd(), return nullptr); + return it.value(); +} + +int TreeStorageBase::createStorage() const +{ + QTC_ASSERT(m_storageData->m_constructor, return 0); // TODO: add isValid()? + QTC_ASSERT(m_storageData->m_destructor, return 0); + QTC_ASSERT(m_storageData->m_activeStorage == 0, return 0); // TODO: should be allowed? + const int newId = ++m_storageData->m_storageCounter; + m_storageData->m_storageHash.insert(newId, m_storageData->m_constructor()); + return newId; +} + +void TreeStorageBase::deleteStorage(int id) const +{ + QTC_ASSERT(m_storageData->m_constructor, return); // TODO: add isValid()? + QTC_ASSERT(m_storageData->m_destructor, return); + QTC_ASSERT(m_storageData->m_activeStorage == 0, return); // TODO: should be allowed? + const auto it = m_storageData->m_storageHash.constFind(id); + QTC_ASSERT(it != m_storageData->m_storageHash.constEnd(), return); + m_storageData->m_destructor(it.value()); + m_storageData->m_storageHash.erase(it); +} + +// passing 0 deactivates currently active storage +void TreeStorageBase::activateStorage(int id) const +{ + if (id == 0) { + QTC_ASSERT(m_storageData->m_activeStorage, return); + m_storageData->m_activeStorage = 0; + return; + } + QTC_ASSERT(m_storageData->m_activeStorage == 0, return); + const auto it = m_storageData->m_storageHash.find(id); + QTC_ASSERT(it != m_storageData->m_storageHash.end(), return); + m_storageData->m_activeStorage = id; +} + +ParallelLimit sequential(1); +ParallelLimit parallel(0); +Workflow stopOnError(WorkflowPolicy::StopOnError); +Workflow continueOnError(WorkflowPolicy::ContinueOnError); +Workflow stopOnDone(WorkflowPolicy::StopOnDone); +Workflow continueOnDone(WorkflowPolicy::ContinueOnDone); +Workflow optional(WorkflowPolicy::Optional); + +void TaskItem::addChildren(const QList &children) +{ + QTC_ASSERT(m_type == Type::Group, qWarning("Only Group may have children, skipping..."); + return); + for (const TaskItem &child : children) { + switch (child.m_type) { + case Type::Group: + m_children.append(child); + break; + case Type::Limit: + QTC_ASSERT(m_type == Type::Group, qWarning("Execution Mode may only be a child of a " + "Group, skipping..."); return); + m_parallelLimit = child.m_parallelLimit; // TODO: Assert on redefinition? + break; + case Type::Policy: + QTC_ASSERT(m_type == Type::Group, qWarning("Workflow Policy may only be a child of a " + "Group, skipping..."); return); + m_workflowPolicy = child.m_workflowPolicy; // TODO: Assert on redefinition? + break; + case Type::TaskHandler: + QTC_ASSERT(child.m_taskHandler.m_createHandler, + qWarning("Task Create Handler can't be null, skipping..."); return); + m_children.append(child); + break; + case Type::GroupHandler: + QTC_ASSERT(m_type == Type::Group, qWarning("Group Handler may only be a " + "child of a Group, skipping..."); break); + QTC_ASSERT(!child.m_groupHandler.m_setupHandler + || !m_groupHandler.m_setupHandler, + qWarning("Group Setup Handler redefinition, overriding...")); + QTC_ASSERT(!child.m_groupHandler.m_doneHandler + || !m_groupHandler.m_doneHandler, + qWarning("Group Done Handler redefinition, overriding...")); + QTC_ASSERT(!child.m_groupHandler.m_errorHandler + || !m_groupHandler.m_errorHandler, + qWarning("Group Error Handler redefinition, overriding...")); + if (child.m_groupHandler.m_setupHandler) + m_groupHandler.m_setupHandler = child.m_groupHandler.m_setupHandler; + if (child.m_groupHandler.m_doneHandler) + m_groupHandler.m_doneHandler = child.m_groupHandler.m_doneHandler; + if (child.m_groupHandler.m_errorHandler) + m_groupHandler.m_errorHandler = child.m_groupHandler.m_errorHandler; + break; + case Type::Storage: + m_storageList.append(child.m_storageList); + break; + } + } +} + +void TaskItem::setTaskSetupHandler(const TaskSetupHandler &handler) +{ + if (!handler) { + qWarning("Setting empty Setup Handler is no-op, skipping..."); + return; + } + if (m_taskHandler.m_setupHandler) + qWarning("Setup Handler redefinition, overriding..."); + m_taskHandler.m_setupHandler = handler; +} + +void TaskItem::setTaskDoneHandler(const TaskEndHandler &handler) +{ + if (!handler) { + qWarning("Setting empty Done Handler is no-op, skipping..."); + return; + } + if (m_taskHandler.m_doneHandler) + qWarning("Done Handler redefinition, overriding..."); + m_taskHandler.m_doneHandler = handler; +} + +void TaskItem::setTaskErrorHandler(const TaskEndHandler &handler) +{ + if (!handler) { + qWarning("Setting empty Error Handler is no-op, skipping..."); + return; + } + if (m_taskHandler.m_errorHandler) + qWarning("Error Handler redefinition, overriding..."); + m_taskHandler.m_errorHandler = handler; +} + +class TaskTreePrivate; +class TaskNode; + +class TaskTreePrivate +{ +public: + TaskTreePrivate(TaskTree *taskTree) + : q(taskTree) {} + + void start(); + void stop(); + void advanceProgress(int byValue); + void emitStartedAndProgress(); + void emitProgress(); + void emitDone(); + void emitError(); + QList addStorages(const QList &storages); + void callSetupHandler(TreeStorageBase storage, int storageId) { + callStorageHandler(storage, storageId, &StorageHandler::m_setupHandler); + } + void callDoneHandler(TreeStorageBase storage, int storageId) { + callStorageHandler(storage, storageId, &StorageHandler::m_doneHandler); + } + struct StorageHandler { + TaskTree::StorageVoidHandler m_setupHandler = {}; + TaskTree::StorageVoidHandler m_doneHandler = {}; + }; + typedef TaskTree::StorageVoidHandler StorageHandler::*HandlerPtr; // ptr to class member + void callStorageHandler(TreeStorageBase storage, int storageId, HandlerPtr ptr) + { + const auto it = m_storageHandlers.constFind(storage); + if (it == m_storageHandlers.constEnd()) + return; + GuardLocker locker(m_guard); + const StorageHandler storageHandler = *it; + storage.activateStorage(storageId); + if (storageHandler.*ptr) + (storageHandler.*ptr)(storage.activeStorageVoid()); + storage.activateStorage(0); + } + + TaskTree *q = nullptr; + Guard m_guard; + int m_progressValue = 0; + QSet m_storages; + QHash m_storageHandlers; + std::unique_ptr m_root = nullptr; // Keep me last in order to destruct first +}; + +class TaskContainer +{ +public: + TaskContainer(TaskTreePrivate *taskTreePrivate, const TaskItem &task, + TaskNode *parentNode, TaskContainer *parentContainer) + : m_constData(taskTreePrivate, task, parentNode, parentContainer, this) {} + TaskAction start(); + TaskAction continueStart(TaskAction startAction, int nextChild); + TaskAction startChildren(int nextChild); + TaskAction childDone(bool success); + void stop(); + void invokeEndHandler(); + bool isRunning() const { return m_runtimeData.has_value(); } + bool isStarting() const { return isRunning() && m_runtimeData->m_startGuard.isLocked(); } + + struct ConstData { + ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task, TaskNode *parentNode, + TaskContainer *parentContainer, TaskContainer *thisContainer); + ~ConstData() { qDeleteAll(m_children); } + TaskTreePrivate * const m_taskTreePrivate = nullptr; + TaskNode * const m_parentNode = nullptr; + TaskContainer * const m_parentContainer = nullptr; + + const int m_parallelLimit = 1; + const WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError; + const TaskItem::GroupHandler m_groupHandler; + const QList m_storageList; + const QList m_children; + const int m_taskCount = 0; + }; + + struct RuntimeData { + RuntimeData(const ConstData &constData); + ~RuntimeData(); + + static QList createStorages(const TaskContainer::ConstData &constData); + void callStorageDoneHandlers(); + bool updateSuccessBit(bool success); + int currentLimit() const; + + const ConstData &m_constData; + const QList m_storageIdList; + int m_doneCount = 0; + bool m_successBit = true; + Guard m_startGuard; + }; + + const ConstData m_constData; + std::optional m_runtimeData; +}; + +class TaskNode : public QObject +{ +public: + TaskNode(TaskTreePrivate *taskTreePrivate, const TaskItem &task, + TaskContainer *parentContainer) + : m_taskHandler(task.taskHandler()) + , m_container(taskTreePrivate, task, this, parentContainer) + {} + + // If returned value != Continue, childDone() needs to be called in parent container (in caller) + // in order to unwind properly. + TaskAction start(); + void stop(); + void invokeEndHandler(bool success); + bool isRunning() const { return m_task || m_container.isRunning(); } + bool isTask() const { return bool(m_taskHandler.m_createHandler); } + int taskCount() const { return isTask() ? 1 : m_container.m_constData.m_taskCount; } + TaskContainer *parentContainer() const { return m_container.m_constData.m_parentContainer; } + +private: + const TaskItem::TaskHandler m_taskHandler; + TaskContainer m_container; + std::unique_ptr m_task; +}; + +void TaskTreePrivate::start() +{ + QTC_ASSERT(m_root, return); + m_progressValue = 0; + emitStartedAndProgress(); + // TODO: check storage handlers for not existing storages in tree + for (auto it = m_storageHandlers.cbegin(); it != m_storageHandlers.cend(); ++it) { + QTC_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't " + "exist in task tree. Its handlers will never be called.")); + } + m_root->start(); +} + +void TaskTreePrivate::stop() +{ + QTC_ASSERT(m_root, return); + if (!m_root->isRunning()) + return; + // TODO: should we have canceled flag (passed to handler)? + // Just one done handler with result flag: + // FinishedWithSuccess, FinishedWithError, Canceled, TimedOut. + // Canceled either directly by user, or by workflow policy - doesn't matter, in both + // cases canceled from outside. + m_root->stop(); + emitError(); +} + +void TaskTreePrivate::advanceProgress(int byValue) +{ + if (byValue == 0) + return; + QTC_CHECK(byValue > 0); + QTC_CHECK(m_progressValue + byValue <= m_root->taskCount()); + m_progressValue += byValue; + emitProgress(); +} + +void TaskTreePrivate::emitStartedAndProgress() +{ + GuardLocker locker(m_guard); + emit q->started(); + emit q->progressValueChanged(m_progressValue); +} + +void TaskTreePrivate::emitProgress() +{ + GuardLocker locker(m_guard); + emit q->progressValueChanged(m_progressValue); +} + +void TaskTreePrivate::emitDone() +{ + QTC_CHECK(m_progressValue == m_root->taskCount()); + GuardLocker locker(m_guard); + emit q->done(); +} + +void TaskTreePrivate::emitError() +{ + QTC_CHECK(m_progressValue == m_root->taskCount()); + GuardLocker locker(m_guard); + emit q->errorOccurred(); +} + +QList TaskTreePrivate::addStorages(const QList &storages) +{ + QList addedStorages; + for (const TreeStorageBase &storage : storages) { + QTC_ASSERT(!m_storages.contains(storage), qWarning("Can't add the same storage into " + "one TaskTree twice, skipping..."); continue); + addedStorages << storage; + m_storages << storage; + } + return addedStorages; +} + +class ExecutionContextActivator +{ +public: + ExecutionContextActivator(TaskContainer *container) + : m_container(container) { activateContext(m_container); } + ~ExecutionContextActivator() { deactivateContext(m_container); } + +private: + static void activateContext(TaskContainer *container) + { + QTC_ASSERT(container && container->isRunning(), return); + const TaskContainer::ConstData &constData = container->m_constData; + if (constData.m_parentContainer) + activateContext(constData.m_parentContainer); + for (int i = 0; i < constData.m_storageList.size(); ++i) + constData.m_storageList[i].activateStorage(container->m_runtimeData->m_storageIdList.value(i)); + } + static void deactivateContext(TaskContainer *container) + { + QTC_ASSERT(container && container->isRunning(), return); + const TaskContainer::ConstData &constData = container->m_constData; + for (int i = constData.m_storageList.size() - 1; i >= 0; --i) // iterate in reverse order + constData.m_storageList[i].activateStorage(0); + if (constData.m_parentContainer) + deactivateContext(constData.m_parentContainer); + } + TaskContainer *m_container = nullptr; +}; + +template > +ReturnType invokeHandler(TaskContainer *container, Handler &&handler, Args &&...args) +{ + ExecutionContextActivator activator(container); + GuardLocker locker(container->m_constData.m_taskTreePrivate->m_guard); + return std::invoke(std::forward(handler), std::forward(args)...); +} + +static QList createChildren(TaskTreePrivate *taskTreePrivate, TaskContainer *container, + const TaskItem &task) +{ + QList result; + const QList &children = task.children(); + for (const TaskItem &child : children) + result.append(new TaskNode(taskTreePrivate, child, container)); + return result; +} + +TaskContainer::ConstData::ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task, + TaskNode *parentNode, TaskContainer *parentContainer, + TaskContainer *thisContainer) + : m_taskTreePrivate(taskTreePrivate) + , m_parentNode(parentNode) + , m_parentContainer(parentContainer) + , m_parallelLimit(task.parallelLimit()) + , m_workflowPolicy(task.workflowPolicy()) + , m_groupHandler(task.groupHandler()) + , m_storageList(taskTreePrivate->addStorages(task.storageList())) + , m_children(createChildren(taskTreePrivate, thisContainer, task)) + , m_taskCount(std::accumulate(m_children.cbegin(), m_children.cend(), 0, + [](int r, TaskNode *n) { return r + n->taskCount(); })) +{} + +QList TaskContainer::RuntimeData::createStorages(const TaskContainer::ConstData &constData) +{ + QList storageIdList; + for (const TreeStorageBase &storage : constData.m_storageList) { + const int storageId = storage.createStorage(); + storageIdList.append(storageId); + constData.m_taskTreePrivate->callSetupHandler(storage, storageId); + } + return storageIdList; +} + +void TaskContainer::RuntimeData::callStorageDoneHandlers() +{ + for (int i = m_constData.m_storageList.size() - 1; i >= 0; --i) { // iterate in reverse order + const TreeStorageBase storage = m_constData.m_storageList[i]; + const int storageId = m_storageIdList.value(i); + m_constData.m_taskTreePrivate->callDoneHandler(storage, storageId); + } +} + +TaskContainer::RuntimeData::RuntimeData(const ConstData &constData) + : m_constData(constData) + , m_storageIdList(createStorages(constData)) +{ + m_successBit = m_constData.m_workflowPolicy != WorkflowPolicy::StopOnDone + && m_constData.m_workflowPolicy != WorkflowPolicy::ContinueOnDone; +} + +TaskContainer::RuntimeData::~RuntimeData() +{ + for (int i = m_constData.m_storageList.size() - 1; i >= 0; --i) { // iterate in reverse order + const TreeStorageBase storage = m_constData.m_storageList[i]; + const int storageId = m_storageIdList.value(i); + storage.deleteStorage(storageId); + } +} + +bool TaskContainer::RuntimeData::updateSuccessBit(bool success) +{ + if (m_constData.m_workflowPolicy == WorkflowPolicy::Optional) + return m_successBit; + + const bool donePolicy = m_constData.m_workflowPolicy == WorkflowPolicy::StopOnDone + || m_constData.m_workflowPolicy == WorkflowPolicy::ContinueOnDone; + m_successBit = donePolicy ? (m_successBit || success) : (m_successBit && success); + return m_successBit; +} + +int TaskContainer::RuntimeData::currentLimit() const +{ + const int childCount = m_constData.m_children.size(); + return m_constData.m_parallelLimit + ? qMin(m_doneCount + m_constData.m_parallelLimit, childCount) : childCount; +} + +TaskAction TaskContainer::start() +{ + QTC_CHECK(!isRunning()); + m_runtimeData.emplace(m_constData); + + TaskAction startAction = TaskAction::Continue; + if (m_constData.m_groupHandler.m_setupHandler) { + startAction = invokeHandler(this, m_constData.m_groupHandler.m_setupHandler); + if (startAction != TaskAction::Continue) + m_constData.m_taskTreePrivate->advanceProgress(m_constData.m_taskCount); + } + if (startAction == TaskAction::Continue) { + if (m_constData.m_children.isEmpty()) + startAction = TaskAction::StopWithDone; + } + return continueStart(startAction, 0); +} + +TaskAction TaskContainer::continueStart(TaskAction startAction, int nextChild) +{ + const TaskAction groupAction = startAction == TaskAction::Continue ? startChildren(nextChild) + : startAction; + QTC_CHECK(isRunning()); // TODO: superfluous + if (groupAction != TaskAction::Continue) { + const bool success = m_runtimeData->updateSuccessBit(groupAction == TaskAction::StopWithDone); + invokeEndHandler(); + if (TaskContainer *parentContainer = m_constData.m_parentContainer) { + QTC_CHECK(parentContainer->isRunning()); + if (!parentContainer->isStarting()) + parentContainer->childDone(success); + } else if (success) { + m_constData.m_taskTreePrivate->emitDone(); + } else { + m_constData.m_taskTreePrivate->emitError(); + } + } + return groupAction; +} + +TaskAction TaskContainer::startChildren(int nextChild) +{ + QTC_CHECK(isRunning()); + GuardLocker locker(m_runtimeData->m_startGuard); + for (int i = nextChild; i < m_constData.m_children.size(); ++i) { + const int limit = m_runtimeData->currentLimit(); + if (i >= limit) + break; + + const TaskAction startAction = m_constData.m_children.at(i)->start(); + if (startAction == TaskAction::Continue) + continue; + + const TaskAction finalizeAction = childDone(startAction == TaskAction::StopWithDone); + if (finalizeAction == TaskAction::Continue) + continue; + + int skippedTaskCount = 0; + // Skip scheduled but not run yet. The current (i) was already notified. + for (int j = i + 1; j < limit; ++j) + skippedTaskCount += m_constData.m_children.at(j)->taskCount(); + m_constData.m_taskTreePrivate->advanceProgress(skippedTaskCount); + return finalizeAction; + } + return TaskAction::Continue; +} + +TaskAction TaskContainer::childDone(bool success) +{ + QTC_CHECK(isRunning()); + const int limit = m_runtimeData->currentLimit(); // Read before bumping m_doneCount and stop() + const bool shouldStop = (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnDone && success) + || (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnError && !success); + if (shouldStop) + stop(); + + ++m_runtimeData->m_doneCount; + const bool updatedSuccess = m_runtimeData->updateSuccessBit(success); + const TaskAction startAction + = (shouldStop || m_runtimeData->m_doneCount == m_constData.m_children.size()) + ? toTaskAction(updatedSuccess) : TaskAction::Continue; + + if (isStarting()) + return startAction; + return continueStart(startAction, limit); +} + +void TaskContainer::stop() +{ + if (!isRunning()) + return; + + const int limit = m_runtimeData->currentLimit(); + for (int i = 0; i < limit; ++i) + m_constData.m_children.at(i)->stop(); + + int skippedTaskCount = 0; + for (int i = limit; i < m_constData.m_children.size(); ++i) + skippedTaskCount += m_constData.m_children.at(i)->taskCount(); + + m_constData.m_taskTreePrivate->advanceProgress(skippedTaskCount); +} + +void TaskContainer::invokeEndHandler() +{ + const TaskItem::GroupHandler &groupHandler = m_constData.m_groupHandler; + if (m_runtimeData->m_successBit && groupHandler.m_doneHandler) + invokeHandler(this, groupHandler.m_doneHandler); + else if (!m_runtimeData->m_successBit && groupHandler.m_errorHandler) + invokeHandler(this, groupHandler.m_errorHandler); + m_runtimeData->callStorageDoneHandlers(); + m_runtimeData.reset(); +} + +TaskAction TaskNode::start() +{ + QTC_CHECK(!isRunning()); + if (!isTask()) + return m_container.start(); + + m_task.reset(m_taskHandler.m_createHandler()); + const TaskAction startAction = m_taskHandler.m_setupHandler + ? invokeHandler(parentContainer(), m_taskHandler.m_setupHandler, *m_task.get()) + : TaskAction::Continue; + if (startAction != TaskAction::Continue) { + m_container.m_constData.m_taskTreePrivate->advanceProgress(1); + m_task.reset(); + return startAction; + } + const std::shared_ptr unwindAction + = std::make_shared(TaskAction::Continue); + connect(m_task.get(), &TaskInterface::done, this, [=](bool success) { + invokeEndHandler(success); + disconnect(m_task.get(), &TaskInterface::done, this, nullptr); + m_task.release()->deleteLater(); + QTC_ASSERT(parentContainer() && parentContainer()->isRunning(), return); + if (parentContainer()->isStarting()) + *unwindAction = toTaskAction(success); + else + parentContainer()->childDone(success); + }); + + m_task->start(); + return *unwindAction; +} + +void TaskNode::stop() +{ + if (!isRunning()) + return; + + if (!m_task) { + m_container.stop(); + m_container.invokeEndHandler(); + return; + } + + // TODO: cancelHandler? + // TODO: call TaskInterface::stop() ? + invokeEndHandler(false); + m_task.reset(); +} + +void TaskNode::invokeEndHandler(bool success) +{ + if (success && m_taskHandler.m_doneHandler) + invokeHandler(parentContainer(), m_taskHandler.m_doneHandler, *m_task.get()); + else if (!success && m_taskHandler.m_errorHandler) + invokeHandler(parentContainer(), m_taskHandler.m_errorHandler, *m_task.get()); + m_container.m_constData.m_taskTreePrivate->advanceProgress(1); +} + +/*! + \class TaskTree + \inheaderfile solutions/tasking/tasktree.h + \inmodule QtCreator + \ingroup mainclasses + \brief The TaskTree class runs an async task tree structure defined in a + declarative way. + + Use the Tasking namespace to build extensible, declarative task tree + structures that contain possibly asynchronous tasks, such as Process, + FileTransfer, or Async. TaskTree structures enable you + to create a sophisticated mixture of a parallel or sequential flow of tasks + in the form of a tree and to run it any time later. + + \section1 Root Element and Tasks + + The TaskTree has a mandatory Group root element, which may contain + any number of tasks of various types, such as ProcessTask, FileTransferTask, + or AsyncTask: + + \code + using namespace Tasking; + + const Group root { + ProcessTask(...), + AsyncTask(...), + FileTransferTask(...) + }; + + TaskTree *taskTree = new TaskTree(root); + connect(taskTree, &TaskTree::done, ...); // a successfully finished handler + connect(taskTree, &TaskTree::errorOccurred, ...); // an erroneously finished handler + taskTree->start(); + \endcode + + The task tree above has a top level element of the Group type that contains + tasks of the type ProcessTask, FileTransferTask, and AsyncTask. + After taskTree->start() is called, the tasks are run in a chain, starting + with ProcessTask. When the ProcessTask finishes successfully, the AsyncTask task is + started. Finally, when the asynchronous task finishes successfully, the + FileTransferTask task is started. + + When the last running task finishes with success, the task tree is considered + to have run successfully and the TaskTree::done() signal is emitted. + When a task finishes with an error, the execution of the task tree is stopped + and the remaining tasks are skipped. The task tree finishes with an error and + sends the TaskTree::errorOccurred() signal. + + \section1 Groups + + The parent of the Group sees it as a single task. Like other tasks, + the group can be started and it can finish with success or an error. + The Group elements can be nested to create a tree structure: + + \code + const Group root { + Group { + parallel, + ProcessTask(...), + AsyncTask(...) + }, + FileTransferTask(...) + }; + \endcode + + The example above differs from the first example in that the root element has + a subgroup that contains the ProcessTask and AsyncTask. The subgroup is a + sibling element of the FileTransferTask in the root. The subgroup contains an + additional \e parallel element that instructs its Group to execute its tasks + in parallel. + + So, when the tree above is started, the ProcessTask and AsyncTask start + immediately and run in parallel. Since the root group doesn't contain a + \e parallel element, its direct child tasks are run in sequence. Thus, the + FileTransferTask starts when the whole subgroup finishes. The group is + considered as finished when all its tasks have finished. The order in which + the tasks finish is not relevant. + + So, depending on which task lasts longer (ProcessTask or AsyncTask), the + following scenarios can take place: + + \table + \header + \li Scenario 1 + \li Scenario 2 + \row + \li Root Group starts + \li Root Group starts + \row + \li Sub Group starts + \li Sub Group starts + \row + \li ProcessTask starts + \li ProcessTask starts + \row + \li AsyncTask starts + \li AsyncTask starts + \row + \li ... + \li ... + \row + \li \b {ProcessTask finishes} + \li \b {AsyncTask finishes} + \row + \li ... + \li ... + \row + \li \b {AsyncTask finishes} + \li \b {ProcessTask finishes} + \row + \li Sub Group finishes + \li Sub Group finishes + \row + \li FileTransferTask starts + \li FileTransferTask starts + \row + \li ... + \li ... + \row + \li FileTransferTask finishes + \li FileTransferTask finishes + \row + \li Root Group finishes + \li Root Group finishes + \endtable + + The differences between the scenarios are marked with bold. Three dots mean + that an unspecified amount of time passes between previous and next events + (a task or tasks continue to run). No dots between events + means that they occur synchronously. + + The presented scenarios assume that all tasks run successfully. If a task + fails during execution, the task tree finishes with an error. In particular, + when ProcessTask finishes with an error while AsyncTask is still being executed, + the AsyncTask is automatically stopped, the subgroup finishes with an error, + the FileTransferTask is skipped, and the tree finishes with an error. + + \section1 Task Types + + Each task type is associated with its corresponding task class that executes + the task. For example, a ProcessTask inside a task tree is associated with + the Process class that executes the process. The associated objects are + automatically created, started, and destructed exclusively by the task tree + at the appropriate time. + + If a root group consists of five sequential ProcessTask tasks, and the task tree + executes the group, it creates an instance of Process for the first + ProcessTask and starts it. If the Process instance finishes successfully, + the task tree destructs it and creates a new Process instance for the + second ProcessTask, and so on. If the first task finishes with an error, the task + tree stops creating Process instances, and the root group finishes with an + error. + + The following table shows examples of task types and their corresponding task + classes: + + \table + \header + \li Task Type (Tasking Namespace) + \li Associated Task Class + \li Brief Description + \row + \li ProcessTask + \li Utils::Process + \li Starts processes. + \row + \li AsyncTask + \li Utils::Async + \li Starts asynchronous tasks; run in separate thread. + \row + \li TaskTreeTask + \li Utils::TaskTree + \li Starts a nested task tree. + \row + \li FileTransferTask + \li ProjectExplorer::FileTransfer + \li Starts file transfer between different devices. + \endtable + + \section1 Task Handlers + + Use Task handlers to set up a task for execution and to enable reading + the output data from the task when it finishes with success or an error. + + \section2 Task Start Handler + + When a corresponding task class object is created and before it's started, + the task tree invokes a mandatory user-provided setup handler. The setup + handler should always take a \e reference to the associated task class object: + + \code + const auto onSetup = [](Process &process) { + process.setCommand({"sleep", {"3"}}); + }; + const Group root { + ProcessTask(onSetup) + }; + \endcode + + You can modify the passed Process in the setup handler, so that the task + tree can start the process according to your configuration. + You do not need to call \e {process.start();} in the setup handler, + as the task tree calls it when needed. The setup handler is mandatory + and must be the first argument of the task's constructor. + + Optionally, the setup handler may return a TaskAction. The returned + TaskAction influences the further start behavior of a given task. The + possible values are: + + \table + \header + \li TaskAction Value + \li Brief Description + \row + \li Continue + \li The task is started normally. This is the default behavior when the + setup handler doesn't return TaskAction (that is, its return type is + void). + \row + \li StopWithDone + \li The task won't be started and it will report success to its parent. + \row + \li StopWithError + \li The task won't be started and it will report an error to its parent. + \endtable + + This is useful for running a task only when a condition is met and the data + needed to evaluate this condition is not known until previously started tasks + finish. This way, the setup handler dynamically decides whether to start the + corresponding task normally or skip it and report success or an error. + For more information about inter-task data exchange, see \l Storage. + + \section2 Task's Done and Error Handlers + + When a running task finishes, the task tree invokes an optionally provided + done or error handler. Both handlers should always take a \e {const reference} + to the associated task class object: + + \code + const auto onSetup = [](Process &process) { + process.setCommand({"sleep", {"3"}}); + }; + const auto onDone = [](const Process &process) { + qDebug() << "Success" << process.cleanedStdOut(); + }; + const auto onError = [](const Process &process) { + qDebug() << "Failure" << process.cleanedStdErr(); + }; + const Group root { + ProcessTask(onSetup, onDone, onError) + }; + \endcode + + The done and error handlers may collect output data from Process, and store it + for further processing or perform additional actions. The done handler is optional. + When used, it must be the second argument of the task constructor. + The error handler must always be the third argument. + You can omit the handlers or substitute the ones that you do not need with curly braces ({}). + + \note If the task setup handler returns StopWithDone or StopWithError, + neither the done nor error handler is invoked. + + \section1 Group Handlers + + Similarly to task handlers, group handlers enable you to set up a group to + execute and to apply more actions when the whole group finishes with + success or an error. + + \section2 Group's Start Handler + + The task tree invokes the group start handler before it starts the child + tasks. The group handler doesn't take any arguments: + + \code + const auto onGroupSetup = [] { + qDebug() << "Entering the group"; + }; + const Group root { + OnGroupSetup(onGroupSetup), + ProcessTask(...) + }; + \endcode + + The group setup handler is optional. To define a group setup handler, add an + OnGroupSetup element to a group. The argument of OnGroupSetup is a user + handler. If you add more than one OnGroupSetup element to a group, an assert + is triggered at runtime that includes an error message. + + Like the task start handler, the group start handler may return TaskAction. + The returned TaskAction value affects the start behavior of the + whole group. If you do not specify a group start handler or its return type + is void, the default group's action is TaskAction::Continue, so that all + tasks are started normally. Otherwise, when the start handler returns + TaskAction::StopWithDone or TaskAction::StopWithError, the tasks are not + started (they are skipped) and the group itself reports success or failure, + depending on the returned value, respectively. + + \code + const Group root { + OnGroupSetup([] { qDebug() << "Root setup"; }), + Group { + OnGroupSetup([] { qDebug() << "Group 1 setup"; return TaskAction::Continue; }), + ProcessTask(...) // Process 1 + }, + Group { + OnGroupSetup([] { qDebug() << "Group 2 setup"; return TaskAction::StopWithDone; }), + ProcessTask(...) // Process 2 + }, + Group { + OnGroupSetup([] { qDebug() << "Group 3 setup"; return TaskAction::StopWithError; }), + ProcessTask(...) // Process 3 + }, + ProcessTask(...) // Process 4 + }; + \endcode + + In the above example, all subgroups of a root group define their setup handlers. + The following scenario assumes that all started processes finish with success: + + \table + \header + \li Scenario + \li Comment + \row + \li Root Group starts + \li Doesn't return TaskAction, so its tasks are executed. + \row + \li Group 1 starts + \li Returns Continue, so its tasks are executed. + \row + \li Process 1 starts + \li + \row + \li ... + \li ... + \row + \li Process 1 finishes (success) + \li + \row + \li Group 1 finishes (success) + \li + \row + \li Group 2 starts + \li Returns StopWithDone, so Process 2 is skipped and Group 2 reports + success. + \row + \li Group 2 finishes (success) + \li + \row + \li Group 3 starts + \li Returns StopWithError, so Process 3 is skipped and Group 3 reports + an error. + \row + \li Group 3 finishes (error) + \li + \row + \li Root Group finishes (error) + \li Group 3, which is a direct child of the root group, finished with an + error, so the root group stops executing, skips Process 4, which has + not started yet, and reports an error. + \endtable + + \section2 Groups's Done and Error Handlers + + A Group's done or error handler is executed after the successful or failed + execution of its tasks, respectively. The final value reported by the + group depends on its \l {Workflow Policy}. The handlers can apply other + necessary actions. The done and error handlers are defined inside the + OnGroupDone and OnGroupError elements of a group, respectively. They do not + take arguments: + + \code + const Group root { + OnGroupSetup([] { qDebug() << "Root setup"; }), + ProcessTask(...), + OnGroupDone([] { qDebug() << "Root finished with success"; }), + OnGroupError([] { qDebug() << "Root finished with error"; }) + }; + \endcode + + The group done and error handlers are optional. If you add more than one + OnGroupDone or OnGroupError each to a group, an assert is triggered at + runtime that includes an error message. + + \note Even if the group setup handler returns StopWithDone or StopWithError, + one of the task's done or error handlers is invoked. This behavior differs + from that of task handlers and might change in the future. + + \section1 Other Group Elements + + A group can contain other elements that describe the processing flow, such as + the execution mode or workflow policy. It can also contain storage elements + that are responsible for collecting and sharing custom common data gathered + during group execution. + + \section2 Execution Mode + + The execution mode element in a Group specifies how the direct child tasks of + the Group are started. + + \table + \header + \li Execution Mode + \li Description + \row + \li sequential + \li Default. When a Group has no execution mode, it runs in the + sequential mode. All the direct child tasks of a group are started + in a chain, so that when one task finishes, the next one starts. + This enables you to pass the results from the previous task + as input to the next task before it starts. This mode guarantees + that the next task is started only after the previous task finishes. + \row + \li parallel + \li All the direct child tasks of a group are started after the group is + started, without waiting for the previous tasks to finish. In this + mode, all tasks run simultaneously. + \row + \li ParallelLimit(int limit) + \li In this mode, a limited number of direct child tasks run simultaneously. + The \e limit defines the maximum number of tasks running in parallel + in a group. When the group is started, the first batch tasks is + started (the number of tasks in batch equals to passed limit, at most), + while the others are kept waiting. When a running task finishes, + the group starts the next remaining one, so that the \e limit + of simultaneously running tasks inside a group isn't exceeded. + This repeats on every child task's finish until all child tasks are started. + This enables you to limit the maximum number of tasks that + run simultaneously, for example if running too many processes might + block the machine for a long time. The value 1 means \e sequential + execution. The value 0 means unlimited and equals \e parallel. + \endtable + + In all execution modes, a group starts tasks in the oder in which they appear. + + If a child of a group is also a group (in a nested tree), the child group + runs its tasks according to its own execution mode. + + \section2 Workflow Policy + + The workflow policy element in a Group specifies how the group should behave + when its direct child tasks finish: + + \table + \header + \li Workflow Policy + \li Description + \row + \li stopOnError + \li Default. If a task finishes with an error, the group: + \list 1 + \li Stops the running tasks (if any - for example, in parallel + mode). + \li Skips executing tasks it has not started (for example, in the + sequential mode). + \li Immediately finishes with an error. + \endlist + If all child tasks finish successfully or the group is empty, the group + finishes with success. + \row + \li continueOnError + \li Similar to stopOnError, but in case any child finishes with + an error, the execution continues until all tasks finish, + and the group reports an error afterwards, even when some other + tasks in group finished with success. + If a task finishes with an error, the group: + \list 1 + \li Continues executing the tasks that are running or have not + started yet. + \li Finishes with an error when all tasks finish. + \endlist + If all tasks finish successfully or the group is empty, the group + finishes with success. + \row + \li stopOnDone + \li If a task finishes with success, the group: + \list 1 + \li Stops running tasks and skips those that it has not started. + \li Immediately finishes with success. + \endlist + If all tasks finish with an error or the group is empty, the group + finishes with an error. + \row + \li continueOnDone + \li Similar to stopOnDone, but in case any child finishes + successfully, the execution continues until all tasks finish, + and the group reports success afterwards, even when some other + tasks in group finished with an error. + If a task finishes with success, the group: + \list 1 + \li Continues executing the tasks that are running or have not + started yet. + \li Finishes with success when all tasks finish. + \endlist + If all tasks finish with an error or the group is empty, the group + finishes with an error. + \row + \li optional + \li The group executes all tasks and ignores their return state. If all + tasks finish or the group is empty, the group finishes with success. + \endtable + + If a child of a group is also a group (in a nested tree), the child group + runs its tasks according to its own workflow policy. + + \section2 Storage + + Use the Storage element to exchange information between tasks. Especially, + in the sequential execution mode, when a task needs data from another task + before it can start. For example, a task tree that copies data by reading + it from a source and writing it to a destination might look as follows: + + \code + static QByteArray load(const FilePath &fileName) { ... } + static void save(const FilePath &fileName, const QByteArray &array) { ... } + + static TaskItem diffRecipe(const FilePath &source, const FilePath &destination) + { + struct CopyStorage { // [1] custom inter-task struct + QByteArray content; // [2] custom inter-task data + }; + + // [3] instance of custom inter-task struct manageable by task tree + const TreeStorage storage; + + const auto onLoaderSetup = [source](Async &async) { + async.setConcurrentCallData(&load, source); + }; + // [4] runtime: task tree activates the instance from [5] before invoking handler + const auto onLoaderDone = [storage](const Async &async) { + storage->content = async.result(); + }; + + // [4] runtime: task tree activates the instance from [5] before invoking handler + const auto onSaverSetup = [storage, destination](Async &async) { + async.setConcurrentCallData(&save, destination, storage->content); + }; + const auto onSaverDone = [](const Async &async) { + qDebug() << "Save done successfully"; + }; + + const Group root { + // [5] runtime: task tree creates an instance of CopyStorage when root is entered + Storage(storage), + AsyncTask(onLoaderSetup, onLoaderDone), + AsyncTask(onSaverSetup, onSaverDone) + }; + return root; + } + \endcode + + In the example above, the inter-task data consists of a QByteArray content + variable [2] enclosed in a CopyStorage custom struct [1]. If the loader + finishes successfully, it stores the data in a CopyStorage::content + variable. The saver then uses the variable to configure the saving task. + + To enable a task tree to manage the CopyStorage struct, an instance of + TreeStorage is created [3]. If a copy of this object is + inserted as group's child task [5], an instance of CopyStorage struct is + created dynamically when the task tree enters this group. When the task + tree leaves this group, the existing instance of CopyStorage struct is + destructed as it's no longer needed. + + If several task trees that hold a copy of the common TreeStorage + instance run simultaneously, each task tree contains its own copy of the + CopyStorage struct. + + You can access CopyStorage from any handler in the group with a storage object. + This includes all handlers of all descendant tasks of the group with + a storage object. To access the custom struct in a handler, pass the + copy of the TreeStorage object to the handler (for example, in + a lambda capture) [4]. + + When the task tree invokes a handler in a subtree containing the storage [5], + the task tree activates its own CopyStorage instance inside the + TreeStorage object. Therefore, the CopyStorage struct may be + accessed only from within the handler body. To access the currently active + CopyStorage from within TreeStorage, use the TreeStorage::operator->() + or TreeStorage::activeStorage() method. + + The following list summarizes how to employ a Storage object into the task + tree: + \list 1 + \li Define the custom structure MyStorage with custom data [1], [2] + \li Create an instance of TreeStorage storage [3] + \li Pass the TreeStorage instance to handlers [4] + \li Insert the TreeStorage instance into a group [5] + \endlist + + \note The current implementation assumes that all running task trees + containing copies of the same TreeStorage run in the same thread. Otherwise, + the behavior is undefined. + + \section1 TaskTree + + TaskTree executes the tree structure of asynchronous tasks according to the + recipe described by the Group root element. + + As TaskTree is also an asynchronous task, it can be a part of another TaskTree. + To place a nested TaskTree inside another TaskTree, insert the TaskTreeTask + element into other tree's Group element. + + TaskTree reports progress of completed tasks when running. The progress value + is increased when a task finishes or is skipped or stopped. + When TaskTree is finished and the TaskTree::done() or TaskTree::errorOccurred() + signal is emitted, the current value of the progress equals the maximum + progress value. Maximum progress equals the total number of tasks in a tree. + A nested TaskTree is counted as a single task, and its child tasks are not + counted in the top level tree. Groups themselves are not counted as tasks, + but their tasks are counted. + + To set additional initial data for the running tree, modify the storage + instances in a tree when it creates them by installing a storage setup + handler: + + \code + TreeStorage storage; + Group root = ...; // storage placed inside root's group and inside handlers + TaskTree taskTree(root); + auto initStorage = [](CopyStorage *storage){ + storage->content = "initial content"; + }; + taskTree.onStorageSetup(storage, initStorage); + taskTree.start(); + \endcode + + When the running task tree creates a CopyStorage instance, and before any + handler inside a tree is called, the task tree calls the initStorage handler, + to enable setting up initial data of the storage, unique to this particular + run of taskTree. + + Similarly, to collect some additional result data from the running tree, + read it from storage instances in the tree when they are about to be + destroyed. To do this, install a storage done handler: + + \code + TreeStorage storage; + Group root = ...; // storage placed inside root's group and inside handlers + TaskTree taskTree(root); + auto collectStorage = [](CopyStorage *storage){ + qDebug() << "final content" << storage->content; + }; + taskTree.onStorageDone(storage, collectStorage); + taskTree.start(); + \endcode + + When the running task tree is about to destroy a CopyStorage instance, the + task tree calls the collectStorage handler, to enable reading the final data + from the storage, unique to this particular run of taskTree. + + \section1 Task Adapters + + To extend a TaskTree with new a task type, implement a simple adapter class + derived from the TaskAdapter class template. The following class is an + adapter for a single shot timer, which may be considered as a new + asynchronous task: + + \code + class TimeoutAdapter : public Tasking::TaskAdapter + { + public: + TimeoutAdapter() { + task()->setSingleShot(true); + task()->setInterval(1000); + connect(task(), &QTimer::timeout, this, [this] { emit done(true); }); + } + void start() final { task()->start(); } + }; + + QTC_DECLARE_CUSTOM_TASK(Timeout, TimeoutAdapter); + \endcode + + You must derive the custom adapter from the TaskAdapter class template + instantiated with a template parameter of the class implementing a running + task. The code above uses QTimer to run the task. This class appears + later as an argument to the task's handlers. The instance of this class + parameter automatically becomes a member of the TaskAdapter template, and is + accessible through the TaskAdapter::task() method. The constructor + of TimeoutAdapter initially configures the QTimer object and connects + to the QTimer::timeout signal. When the signal is triggered, TimeoutAdapter + emits the done(true) signal to inform the task tree that the task finished + successfully. If it emits done(false), the task finished with an error. + The TaskAdapter::start() method starts the timer. + + To make QTimer accessible inside TaskTree under the \e Timeout name, + register it with QTC_DECLARE_CUSTOM_TASK(Timeout, TimeoutAdapter). Timeout + becomes a new task type inside Tasking namespace, using TimeoutAdapter. + + The new task type is now registered, and you can use it in TaskTree: + + \code + const auto onTimeoutSetup = [](QTimer &task) { + task.setInterval(2000); + }; + const auto onTimeoutDone = [](const QTimer &task) { + qDebug() << "timeout triggered"; + }; + + const Group root { + Timeout(onTimeoutSetup, onTimeoutDone) + }; + \endcode + + When a task tree containing the root from the above example is started, it + prints a debug message within two seconds and then finishes successfully. + + \note The class implementing the running task should have a default constructor, + and objects of this class should be freely destructible. It should be allowed + to destroy a running object, preferably without waiting for the running task + to finish (that is, safe non-blocking destructor of a running task). +*/ + +TaskTree::TaskTree() + : d(new TaskTreePrivate(this)) +{ +} + +TaskTree::TaskTree(const Group &root) : TaskTree() +{ + setupRoot(root); +} + +TaskTree::~TaskTree() +{ + QTC_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting TaskTree instance directly from " + "one of its handlers will lead to crash!")); + // TODO: delete storages explicitly here? + delete d; +} + +void TaskTree::setupRoot(const Group &root) +{ + QTC_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return); + QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The setupRoot() is called from one of the" + "TaskTree handlers, ingoring..."); return); + d->m_storages.clear(); + d->m_root.reset(new TaskNode(d, root, nullptr)); +} + +void TaskTree::start() +{ + QTC_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return); + QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The start() is called from one of the" + "TaskTree handlers, ingoring..."); return); + d->start(); +} + +void TaskTree::stop() +{ + QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The stop() is called from one of the" + "TaskTree handlers, ingoring..."); return); + d->stop(); +} + +bool TaskTree::isRunning() const +{ + return d->m_root && d->m_root->isRunning(); +} + +int TaskTree::taskCount() const +{ + return d->m_root ? d->m_root->taskCount() : 0; +} + +int TaskTree::progressValue() const +{ + return d->m_progressValue; +} + +void TaskTree::setupStorageHandler(const TreeStorageBase &storage, + StorageVoidHandler setupHandler, + StorageVoidHandler doneHandler) +{ + auto it = d->m_storageHandlers.find(storage); + if (it == d->m_storageHandlers.end()) { + d->m_storageHandlers.insert(storage, {setupHandler, doneHandler}); + return; + } + if (setupHandler) { + QTC_ASSERT(!it->m_setupHandler, + qWarning("The storage has its setup handler defined, overriding...")); + it->m_setupHandler = setupHandler; + } + if (doneHandler) { + QTC_ASSERT(!it->m_doneHandler, + qWarning("The storage has its done handler defined, overriding...")); + it->m_doneHandler = doneHandler; + } +} + +TaskTreeTaskAdapter::TaskTreeTaskAdapter() +{ + connect(task(), &TaskTree::done, this, [this] { emit done(true); }); + connect(task(), &TaskTree::errorOccurred, this, [this] { emit done(false); }); +} + +void TaskTreeTaskAdapter::start() +{ + task()->start(); +} + +} // namespace Tasking diff --git a/src/libs/solutions/tasking/tasktree.h b/src/libs/solutions/tasking/tasktree.h new file mode 100644 index 0000000000..bbee1a3fe8 --- /dev/null +++ b/src/libs/solutions/tasking/tasktree.h @@ -0,0 +1,428 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "tasking_global.h" + +#include +#include +#include + +namespace Tasking { + +class ExecutionContextActivator; +class TaskContainer; +class TaskNode; +class TaskTreePrivate; + +class TASKING_EXPORT TaskInterface : public QObject +{ + Q_OBJECT + +public: + TaskInterface() = default; + virtual void start() = 0; + +signals: + void done(bool success); +}; + +class TASKING_EXPORT TreeStorageBase +{ +public: + bool isValid() const; + +protected: + using StorageConstructor = std::function; + using StorageDestructor = std::function; + + TreeStorageBase(StorageConstructor ctor, StorageDestructor dtor); + void *activeStorageVoid() const; + +private: + int createStorage() const; + void deleteStorage(int id) const; + void activateStorage(int id) const; + + friend bool operator==(const TreeStorageBase &first, const TreeStorageBase &second) + { return first.m_storageData == second.m_storageData; } + + friend bool operator!=(const TreeStorageBase &first, const TreeStorageBase &second) + { return first.m_storageData != second.m_storageData; } + + friend size_t qHash(const TreeStorageBase &storage, uint seed = 0) + { return size_t(storage.m_storageData.get()) ^ seed; } + + struct StorageData { + ~StorageData(); + StorageConstructor m_constructor = {}; + StorageDestructor m_destructor = {}; + QHash m_storageHash = {}; + int m_activeStorage = 0; // 0 means no active storage + int m_storageCounter = 0; + }; + QSharedPointer m_storageData; + friend TaskContainer; + friend TaskTreePrivate; + friend ExecutionContextActivator; +}; + +template +class TreeStorage : public TreeStorageBase +{ +public: + TreeStorage() : TreeStorageBase(TreeStorage::ctor(), TreeStorage::dtor()) {} + StorageStruct &operator*() const noexcept { return *activeStorage(); } + StorageStruct *operator->() const noexcept { return activeStorage(); } + StorageStruct *activeStorage() const { + return static_cast(activeStorageVoid()); + } + +private: + static StorageConstructor ctor() { return [] { return new StorageStruct; }; } + static StorageDestructor dtor() { + return [](void *storage) { delete static_cast(storage); }; + } +}; + +// WorkflowPolicy: +// 1. When all children finished with done -> report done, otherwise: +// a) Report error on first error and stop executing other children (including their subtree) +// b) On first error - continue executing all children and report error afterwards +// 2. When all children finished with error -> report error, otherwise: +// a) Report done on first done and stop executing other children (including their subtree) +// b) On first done - continue executing all children and report done afterwards +// 3. Always run all children, ignore their result and report done afterwards + +enum class WorkflowPolicy { + StopOnError, // 1a - Reports error on first child error, otherwise done (if all children were done) + ContinueOnError, // 1b - The same, but children execution continues. When no children it reports done. + StopOnDone, // 2a - Reports done on first child done, otherwise error (if all children were error) + ContinueOnDone, // 2b - The same, but children execution continues. When no children it reports done. (?) + Optional // 3 - Always reports done after all children finished +}; + +enum class TaskAction +{ + Continue, + StopWithDone, + StopWithError +}; + +class TASKING_EXPORT TaskItem +{ +public: + // Internal, provided by QTC_DECLARE_CUSTOM_TASK + using TaskCreateHandler = std::function; + // Called prior to task start, just after createHandler + using TaskSetupHandler = std::function; + // Called on task done / error + using TaskEndHandler = std::function; + // Called when group entered + using GroupSetupHandler = std::function; + // Called when group done / error + using GroupEndHandler = std::function; + + struct TaskHandler { + TaskCreateHandler m_createHandler; + TaskSetupHandler m_setupHandler = {}; + TaskEndHandler m_doneHandler = {}; + TaskEndHandler m_errorHandler = {}; + }; + + struct GroupHandler { + GroupSetupHandler m_setupHandler; + GroupEndHandler m_doneHandler = {}; + GroupEndHandler m_errorHandler = {}; + }; + + int parallelLimit() const { return m_parallelLimit; } + WorkflowPolicy workflowPolicy() const { return m_workflowPolicy; } + TaskHandler taskHandler() const { return m_taskHandler; } + GroupHandler groupHandler() const { return m_groupHandler; } + QList children() const { return m_children; } + QList storageList() const { return m_storageList; } + +protected: + enum class Type { + Group, + Storage, + Limit, + Policy, + TaskHandler, + GroupHandler + }; + + TaskItem() = default; + TaskItem(int parallelLimit) + : m_type(Type::Limit) + , m_parallelLimit(parallelLimit) {} + TaskItem(WorkflowPolicy policy) + : m_type(Type::Policy) + , m_workflowPolicy(policy) {} + TaskItem(const TaskHandler &handler) + : m_type(Type::TaskHandler) + , m_taskHandler(handler) {} + TaskItem(const GroupHandler &handler) + : m_type(Type::GroupHandler) + , m_groupHandler(handler) {} + TaskItem(const TreeStorageBase &storage) + : m_type(Type::Storage) + , m_storageList{storage} {} + void addChildren(const QList &children); + + void setTaskSetupHandler(const TaskSetupHandler &handler); + void setTaskDoneHandler(const TaskEndHandler &handler); + void setTaskErrorHandler(const TaskEndHandler &handler); + +private: + Type m_type = Type::Group; + int m_parallelLimit = 1; // 0 means unlimited + WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError; + TaskHandler m_taskHandler; + GroupHandler m_groupHandler; + QList m_storageList; + QList m_children; +}; + +class TASKING_EXPORT Group : public TaskItem +{ +public: + Group(const QList &children) { addChildren(children); } + Group(std::initializer_list children) { addChildren(children); } +}; + +class TASKING_EXPORT Storage : public TaskItem +{ +public: + Storage(const TreeStorageBase &storage) : TaskItem(storage) { } +}; + +class TASKING_EXPORT ParallelLimit : public TaskItem +{ +public: + ParallelLimit(int parallelLimit) : TaskItem(qMax(parallelLimit, 0)) {} +}; + +class TASKING_EXPORT Workflow : public TaskItem +{ +public: + Workflow(WorkflowPolicy policy) : TaskItem(policy) {} +}; + +class TASKING_EXPORT OnGroupSetup : public TaskItem +{ +public: + template + OnGroupSetup(SetupFunction &&function) + : TaskItem({wrapSetup(std::forward(function))}) {} + +private: + template + static TaskItem::GroupSetupHandler wrapSetup(SetupFunction &&function) { + static constexpr bool isDynamic = std::is_same_v>>; + constexpr bool isVoid = std::is_same_v>>; + static_assert(isDynamic || isVoid, + "Group setup handler needs to take no arguments and has to return " + "void or TaskAction. The passed handler doesn't fulfill these requirements."); + return [=] { + if constexpr (isDynamic) + return std::invoke(function); + std::invoke(function); + return TaskAction::Continue; + }; + }; +}; + +class TASKING_EXPORT OnGroupDone : public TaskItem +{ +public: + OnGroupDone(const GroupEndHandler &handler) : TaskItem({{}, handler}) {} +}; + +class TASKING_EXPORT OnGroupError : public TaskItem +{ +public: + OnGroupError(const GroupEndHandler &handler) : TaskItem({{}, {}, handler}) {} +}; + +// Synchronous invocation. Similarly to Group - isn't counted as a task inside taskCount() +class TASKING_EXPORT Sync : public Group +{ + +public: + template + Sync(Function &&function) : Group(init(std::forward(function))) {} + +private: + template + static QList init(Function &&function) { + constexpr bool isInvocable = std::is_invocable_v>; + static_assert(isInvocable, + "Sync element: The synchronous function can't take any arguments."); + constexpr bool isBool = std::is_same_v>>; + constexpr bool isVoid = std::is_same_v>>; + static_assert(isBool || isVoid, + "Sync element: The synchronous function has to return void or bool."); + if constexpr (isBool) { + return {OnGroupSetup([function] { return function() ? TaskAction::StopWithDone + : TaskAction::StopWithError; })}; + } + return {OnGroupSetup([function] { function(); return TaskAction::StopWithDone; })}; + }; + +}; + +TASKING_EXPORT extern ParallelLimit sequential; +TASKING_EXPORT extern ParallelLimit parallel; +TASKING_EXPORT extern Workflow stopOnError; +TASKING_EXPORT extern Workflow continueOnError; +TASKING_EXPORT extern Workflow stopOnDone; +TASKING_EXPORT extern Workflow continueOnDone; +TASKING_EXPORT extern Workflow optional; + +template +class TaskAdapter : public TaskInterface +{ +public: + using Type = Task; + TaskAdapter() = default; + Task *task() { return &m_task; } + const Task *task() const { return &m_task; } +private: + Task m_task; +}; + +template +class CustomTask : public TaskItem +{ +public: + using Task = typename Adapter::Type; + using EndHandler = std::function; + static Adapter *createAdapter() { return new Adapter; } + CustomTask() : TaskItem({&createAdapter}) {} + template + CustomTask(SetupFunction &&function, const EndHandler &done = {}, const EndHandler &error = {}) + : TaskItem({&createAdapter, wrapSetup(std::forward(function)), + wrapEnd(done), wrapEnd(error)}) {} + + template + CustomTask &onSetup(SetupFunction &&function) { + setTaskSetupHandler(wrapSetup(std::forward(function))); + return *this; + } + CustomTask &onDone(const EndHandler &handler) { + setTaskDoneHandler(wrapEnd(handler)); + return *this; + } + CustomTask &onError(const EndHandler &handler) { + setTaskErrorHandler(wrapEnd(handler)); + return *this; + } + +private: + template + static TaskItem::TaskSetupHandler wrapSetup(SetupFunction &&function) { + static constexpr bool isDynamic = std::is_same_v, typename Adapter::Type &>>; + constexpr bool isVoid = std::is_same_v, typename Adapter::Type &>>; + static_assert(isDynamic || isVoid, + "Task setup handler needs to take (Task &) as an argument and has to return " + "void or TaskAction. The passed handler doesn't fulfill these requirements."); + return [=](TaskInterface &taskInterface) { + Adapter &adapter = static_cast(taskInterface); + if constexpr (isDynamic) + return std::invoke(function, *adapter.task()); + std::invoke(function, *adapter.task()); + return TaskAction::Continue; + }; + }; + + static TaskEndHandler wrapEnd(const EndHandler &handler) { + if (!handler) + return {}; + return [handler](const TaskInterface &taskInterface) { + const Adapter &adapter = static_cast(taskInterface); + handler(*adapter.task()); + }; + }; +}; + +class TaskTreePrivate; + +class TASKING_EXPORT TaskTree final : public QObject +{ + Q_OBJECT + +public: + TaskTree(); + TaskTree(const Group &root); + ~TaskTree(); + + void setupRoot(const Group &root); + + void start(); + void stop(); + bool isRunning() const; + + int taskCount() const; + int progressMaximum() const { return taskCount(); } + int progressValue() const; // all finished / skipped / stopped tasks, groups itself excluded + + template + void onStorageSetup(const TreeStorage &storage, StorageHandler &&handler) { + setupStorageHandler(storage, + wrapHandler(std::forward(handler)), {}); + } + template + void onStorageDone(const TreeStorage &storage, StorageHandler &&handler) { + setupStorageHandler(storage, + {}, wrapHandler(std::forward(handler))); + } + +signals: + void started(); + void done(); + void errorOccurred(); + void progressValueChanged(int value); // updated whenever task finished / skipped / stopped + +private: + using StorageVoidHandler = std::function; + void setupStorageHandler(const TreeStorageBase &storage, + StorageVoidHandler setupHandler, + StorageVoidHandler doneHandler); + template + StorageVoidHandler wrapHandler(StorageHandler &&handler) { + return [=](void *voidStruct) { + StorageStruct *storageStruct = static_cast(voidStruct); + std::invoke(handler, storageStruct); + }; + } + + friend class TaskTreePrivate; + TaskTreePrivate *d; +}; + +class TASKING_EXPORT TaskTreeTaskAdapter : public TaskAdapter +{ +public: + TaskTreeTaskAdapter(); + void start() final; +}; + +} // namespace Tasking + +#define TASKING_DECLARE_TASK(CustomTaskName, TaskAdapterClass)\ +namespace Tasking { using CustomTaskName = CustomTask; } + +#define TASKING_DECLARE_TEMPLATE_TASK(CustomTaskName, TaskAdapterClass)\ +namespace Tasking {\ +template \ +using CustomTaskName = CustomTask>;\ +} // namespace Tasking + +TASKING_DECLARE_TASK(TaskTreeTask, TaskTreeTaskAdapter); diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index 5762fc3902..51511b117d 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -1,5 +1,5 @@ add_qtc_library(Utils - DEPENDS Qt::Qml Qt::Xml + DEPENDS Tasking Qt::Qml Qt::Xml PUBLIC_DEPENDS Qt::Concurrent Qt::Core Qt::Network Qt::Gui Qt::Widgets Qt::Core5Compat @@ -13,7 +13,6 @@ add_qtc_library(Utils archive.cpp archive.h aspects.cpp aspects.h async.cpp async.h - barrier.cpp barrier.h basetreeview.cpp basetreeview.h benchmarker.cpp benchmarker.h buildablehelperlibrary.cpp buildablehelperlibrary.h @@ -171,7 +170,6 @@ add_qtc_library(Utils styleanimator.cpp styleanimator.h styledbar.cpp styledbar.h stylehelper.cpp stylehelper.h - tasktree.cpp tasktree.h templateengine.cpp templateengine.h temporarydirectory.cpp temporarydirectory.h temporaryfile.cpp temporaryfile.h diff --git a/src/libs/utils/async.h b/src/libs/utils/async.h index b08ceaf3dc..f8dc6813a7 100644 --- a/src/libs/utils/async.h +++ b/src/libs/utils/async.h @@ -7,7 +7,8 @@ #include "futuresynchronizer.h" #include "qtcassert.h" -#include "tasktree.h" + +#include #include #include diff --git a/src/libs/utils/barrier.cpp b/src/libs/utils/barrier.cpp deleted file mode 100644 index c4daa033b4..0000000000 --- a/src/libs/utils/barrier.cpp +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "barrier.h" - -namespace Tasking { - -// That's cut down qtcassert.{c,h} to avoid the dependency. -#define QTC_STRINGIFY_HELPER(x) #x -#define QTC_STRINGIFY(x) QTC_STRINGIFY_HELPER(x) -#define QTC_STRING(cond) qDebug("SOFT ASSERT: \"%s\" in %s: %s", cond, __FILE__, QTC_STRINGIFY(__LINE__)) -#define QTC_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QTC_STRING(#cond); action; } do {} while (0) -#define QTC_CHECK(cond) if (cond) {} else { QTC_STRING(#cond); } do {} while (0) - -void Barrier::setLimit(int value) -{ - QTC_ASSERT(!isRunning(), return); - QTC_ASSERT(value > 0, return); - - m_limit = value; -} - -void Barrier::start() -{ - QTC_ASSERT(!isRunning(), return); - m_current = 0; - m_result = {}; -} - -void Barrier::advance() -{ - // Calling advance on finished is OK - QTC_ASSERT(isRunning() || m_result, return); - if (!isRunning()) // no-op - return; - ++m_current; - if (m_current == m_limit) - stopWithResult(true); -} - -void Barrier::stopWithResult(bool success) -{ - // Calling stopWithResult on finished is OK when the same success is passed - QTC_ASSERT(isRunning() || (m_result && *m_result == success), return); - if (!isRunning()) // no-op - return; - m_current = -1; - m_result = success; - emit done(success); -} - -} // namespace Tasking diff --git a/src/libs/utils/barrier.h b/src/libs/utils/barrier.h deleted file mode 100644 index fe2d7bbe62..0000000000 --- a/src/libs/utils/barrier.h +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "utils_global.h" - -#include "tasktree.h" - -namespace Tasking { - -class QTCREATOR_UTILS_EXPORT Barrier final : public QObject -{ - Q_OBJECT - -public: - void setLimit(int value); - int limit() const { return m_limit; } - - void start(); - void advance(); // If limit reached, stops with true - void stopWithResult(bool success); // Ignores limit - - bool isRunning() const { return m_current >= 0; } - int current() const { return m_current; } - std::optional result() const { return m_result; } - -signals: - void done(bool success); - -private: - std::optional m_result = {}; - int m_limit = 1; - int m_current = -1; -}; - -class QTCREATOR_UTILS_EXPORT BarrierTaskAdapter : public Tasking::TaskAdapter -{ -public: - BarrierTaskAdapter() { connect(task(), &Barrier::done, this, &TaskInterface::done); } - void start() final { task()->start(); } -}; - -} // namespace Tasking - -TASKING_DECLARE_TASK(BarrierTask, Tasking::BarrierTaskAdapter); - -namespace Tasking { - -template -class SharedBarrier -{ -public: - static_assert(Limit > 0, "SharedBarrier's limit should be 1 or more."); - SharedBarrier() : m_barrier(new Barrier) { - m_barrier->setLimit(Limit); - m_barrier->start(); - } - Barrier *barrier() const { return m_barrier.get(); } - -private: - std::shared_ptr m_barrier; -}; - -template -using MultiBarrier = TreeStorage>; - -// Can't write: "MultiBarrier barrier;". Only "MultiBarrier<> barrier;" would work. -// Can't have one alias with default type in C++17, getting the following error: -// alias template deduction only available with C++20. -using SingleBarrier = MultiBarrier<1>; - -class QTCREATOR_UTILS_EXPORT WaitForBarrierTask : public BarrierTask -{ -public: - template - WaitForBarrierTask(const MultiBarrier &sharedBarrier) - : BarrierTask([sharedBarrier](Barrier &barrier) { - SharedBarrier *activeBarrier = sharedBarrier.activeStorage(); - if (!activeBarrier) { - qWarning("The barrier referenced from WaitForBarrier element " - "is not reachable in the running tree. " - "It is possible that no barrier was added to the tree, " - "or the storage is not reachable from where it is referenced. " - "The WaitForBarrier task will finish with error. "); - return TaskAction::StopWithError; - } - Barrier *activeSharedBarrier = activeBarrier->barrier(); - const std::optional result = activeSharedBarrier->result(); - if (result.has_value()) - return result.value() ? TaskAction::StopWithDone : TaskAction::StopWithError; - QObject::connect(activeSharedBarrier, &Barrier::done, &barrier, &Barrier::stopWithResult); - return TaskAction::Continue; - }) {} -}; - -} // namespace Tasking diff --git a/src/libs/utils/filestreamer.cpp b/src/libs/utils/filestreamer.cpp index 2d4d490940..6d4a768336 100644 --- a/src/libs/utils/filestreamer.cpp +++ b/src/libs/utils/filestreamer.cpp @@ -4,9 +4,10 @@ #include "filestreamer.h" #include "async.h" -#include "barrier.h" #include "process.h" +#include + #include #include #include diff --git a/src/libs/utils/filestreamer.h b/src/libs/utils/filestreamer.h index 8e46d7d164..a5b6ff4f25 100644 --- a/src/libs/utils/filestreamer.h +++ b/src/libs/utils/filestreamer.h @@ -6,7 +6,8 @@ #include "utils_global.h" #include "filepath.h" -#include "tasktree.h" + +#include #include diff --git a/src/libs/utils/process.h b/src/libs/utils/process.h index 272e25f377..83f1bb990b 100644 --- a/src/libs/utils/process.h +++ b/src/libs/utils/process.h @@ -11,7 +11,8 @@ #include "commandline.h" #include "processenums.h" -#include "tasktree.h" + +#include #include diff --git a/src/libs/utils/tasktree.cpp b/src/libs/utils/tasktree.cpp deleted file mode 100644 index 75de54c374..0000000000 --- a/src/libs/utils/tasktree.cpp +++ /dev/null @@ -1,1489 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "tasktree.h" - -#include - -namespace Tasking { - -// That's cut down qtcassert.{c,h} to avoid the dependency. -#define QTC_STRINGIFY_HELPER(x) #x -#define QTC_STRINGIFY(x) QTC_STRINGIFY_HELPER(x) -#define QTC_STRING(cond) qDebug("SOFT ASSERT: \"%s\" in %s: %s", cond, __FILE__, QTC_STRINGIFY(__LINE__)) -#define QTC_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QTC_STRING(#cond); action; } do {} while (0) -#define QTC_CHECK(cond) if (cond) {} else { QTC_STRING(#cond); } do {} while (0) - -class Guard -{ - Q_DISABLE_COPY(Guard) -public: - Guard() = default; - ~Guard() { QTC_CHECK(m_lockCount == 0); } - bool isLocked() const { return m_lockCount; } -private: - int m_lockCount = 0; - friend class GuardLocker; -}; - -class GuardLocker -{ - Q_DISABLE_COPY(GuardLocker) -public: - GuardLocker(Guard &guard) : m_guard(guard) { ++m_guard.m_lockCount; } - ~GuardLocker() { --m_guard.m_lockCount; } -private: - Guard &m_guard; -}; - -static TaskAction toTaskAction(bool success) -{ - return success ? TaskAction::StopWithDone : TaskAction::StopWithError; -} - -bool TreeStorageBase::isValid() const -{ - return m_storageData && m_storageData->m_constructor && m_storageData->m_destructor; -} - -TreeStorageBase::TreeStorageBase(StorageConstructor ctor, StorageDestructor dtor) - : m_storageData(new StorageData{ctor, dtor}) { } - -TreeStorageBase::StorageData::~StorageData() -{ - QTC_CHECK(m_storageHash.isEmpty()); - for (void *ptr : std::as_const(m_storageHash)) - m_destructor(ptr); -} - -void *TreeStorageBase::activeStorageVoid() const -{ - QTC_ASSERT(m_storageData->m_activeStorage, qWarning( - "The referenced storage is not reachable in the running tree. " - "A nullptr will be returned which might lead to a crash in the calling code. " - "It is possible that no storage was added to the tree, " - "or the storage is not reachable from where it is referenced."); - return nullptr); - const auto it = m_storageData->m_storageHash.constFind(m_storageData->m_activeStorage); - QTC_ASSERT(it != m_storageData->m_storageHash.constEnd(), return nullptr); - return it.value(); -} - -int TreeStorageBase::createStorage() const -{ - QTC_ASSERT(m_storageData->m_constructor, return 0); // TODO: add isValid()? - QTC_ASSERT(m_storageData->m_destructor, return 0); - QTC_ASSERT(m_storageData->m_activeStorage == 0, return 0); // TODO: should be allowed? - const int newId = ++m_storageData->m_storageCounter; - m_storageData->m_storageHash.insert(newId, m_storageData->m_constructor()); - return newId; -} - -void TreeStorageBase::deleteStorage(int id) const -{ - QTC_ASSERT(m_storageData->m_constructor, return); // TODO: add isValid()? - QTC_ASSERT(m_storageData->m_destructor, return); - QTC_ASSERT(m_storageData->m_activeStorage == 0, return); // TODO: should be allowed? - const auto it = m_storageData->m_storageHash.constFind(id); - QTC_ASSERT(it != m_storageData->m_storageHash.constEnd(), return); - m_storageData->m_destructor(it.value()); - m_storageData->m_storageHash.erase(it); -} - -// passing 0 deactivates currently active storage -void TreeStorageBase::activateStorage(int id) const -{ - if (id == 0) { - QTC_ASSERT(m_storageData->m_activeStorage, return); - m_storageData->m_activeStorage = 0; - return; - } - QTC_ASSERT(m_storageData->m_activeStorage == 0, return); - const auto it = m_storageData->m_storageHash.find(id); - QTC_ASSERT(it != m_storageData->m_storageHash.end(), return); - m_storageData->m_activeStorage = id; -} - -ParallelLimit sequential(1); -ParallelLimit parallel(0); -Workflow stopOnError(WorkflowPolicy::StopOnError); -Workflow continueOnError(WorkflowPolicy::ContinueOnError); -Workflow stopOnDone(WorkflowPolicy::StopOnDone); -Workflow continueOnDone(WorkflowPolicy::ContinueOnDone); -Workflow optional(WorkflowPolicy::Optional); - -void TaskItem::addChildren(const QList &children) -{ - QTC_ASSERT(m_type == Type::Group, qWarning("Only Group may have children, skipping..."); - return); - for (const TaskItem &child : children) { - switch (child.m_type) { - case Type::Group: - m_children.append(child); - break; - case Type::Limit: - QTC_ASSERT(m_type == Type::Group, qWarning("Execution Mode may only be a child of a " - "Group, skipping..."); return); - m_parallelLimit = child.m_parallelLimit; // TODO: Assert on redefinition? - break; - case Type::Policy: - QTC_ASSERT(m_type == Type::Group, qWarning("Workflow Policy may only be a child of a " - "Group, skipping..."); return); - m_workflowPolicy = child.m_workflowPolicy; // TODO: Assert on redefinition? - break; - case Type::TaskHandler: - QTC_ASSERT(child.m_taskHandler.m_createHandler, - qWarning("Task Create Handler can't be null, skipping..."); return); - m_children.append(child); - break; - case Type::GroupHandler: - QTC_ASSERT(m_type == Type::Group, qWarning("Group Handler may only be a " - "child of a Group, skipping..."); break); - QTC_ASSERT(!child.m_groupHandler.m_setupHandler - || !m_groupHandler.m_setupHandler, - qWarning("Group Setup Handler redefinition, overriding...")); - QTC_ASSERT(!child.m_groupHandler.m_doneHandler - || !m_groupHandler.m_doneHandler, - qWarning("Group Done Handler redefinition, overriding...")); - QTC_ASSERT(!child.m_groupHandler.m_errorHandler - || !m_groupHandler.m_errorHandler, - qWarning("Group Error Handler redefinition, overriding...")); - if (child.m_groupHandler.m_setupHandler) - m_groupHandler.m_setupHandler = child.m_groupHandler.m_setupHandler; - if (child.m_groupHandler.m_doneHandler) - m_groupHandler.m_doneHandler = child.m_groupHandler.m_doneHandler; - if (child.m_groupHandler.m_errorHandler) - m_groupHandler.m_errorHandler = child.m_groupHandler.m_errorHandler; - break; - case Type::Storage: - m_storageList.append(child.m_storageList); - break; - } - } -} - -void TaskItem::setTaskSetupHandler(const TaskSetupHandler &handler) -{ - if (!handler) { - qWarning("Setting empty Setup Handler is no-op, skipping..."); - return; - } - if (m_taskHandler.m_setupHandler) - qWarning("Setup Handler redefinition, overriding..."); - m_taskHandler.m_setupHandler = handler; -} - -void TaskItem::setTaskDoneHandler(const TaskEndHandler &handler) -{ - if (!handler) { - qWarning("Setting empty Done Handler is no-op, skipping..."); - return; - } - if (m_taskHandler.m_doneHandler) - qWarning("Done Handler redefinition, overriding..."); - m_taskHandler.m_doneHandler = handler; -} - -void TaskItem::setTaskErrorHandler(const TaskEndHandler &handler) -{ - if (!handler) { - qWarning("Setting empty Error Handler is no-op, skipping..."); - return; - } - if (m_taskHandler.m_errorHandler) - qWarning("Error Handler redefinition, overriding..."); - m_taskHandler.m_errorHandler = handler; -} - -class TaskTreePrivate; -class TaskNode; - -class TaskTreePrivate -{ -public: - TaskTreePrivate(TaskTree *taskTree) - : q(taskTree) {} - - void start(); - void stop(); - void advanceProgress(int byValue); - void emitStartedAndProgress(); - void emitProgress(); - void emitDone(); - void emitError(); - QList addStorages(const QList &storages); - void callSetupHandler(TreeStorageBase storage, int storageId) { - callStorageHandler(storage, storageId, &StorageHandler::m_setupHandler); - } - void callDoneHandler(TreeStorageBase storage, int storageId) { - callStorageHandler(storage, storageId, &StorageHandler::m_doneHandler); - } - struct StorageHandler { - TaskTree::StorageVoidHandler m_setupHandler = {}; - TaskTree::StorageVoidHandler m_doneHandler = {}; - }; - typedef TaskTree::StorageVoidHandler StorageHandler::*HandlerPtr; // ptr to class member - void callStorageHandler(TreeStorageBase storage, int storageId, HandlerPtr ptr) - { - const auto it = m_storageHandlers.constFind(storage); - if (it == m_storageHandlers.constEnd()) - return; - GuardLocker locker(m_guard); - const StorageHandler storageHandler = *it; - storage.activateStorage(storageId); - if (storageHandler.*ptr) - (storageHandler.*ptr)(storage.activeStorageVoid()); - storage.activateStorage(0); - } - - TaskTree *q = nullptr; - Guard m_guard; - int m_progressValue = 0; - QSet m_storages; - QHash m_storageHandlers; - std::unique_ptr m_root = nullptr; // Keep me last in order to destruct first -}; - -class TaskContainer -{ -public: - TaskContainer(TaskTreePrivate *taskTreePrivate, const TaskItem &task, - TaskNode *parentNode, TaskContainer *parentContainer) - : m_constData(taskTreePrivate, task, parentNode, parentContainer, this) {} - TaskAction start(); - TaskAction continueStart(TaskAction startAction, int nextChild); - TaskAction startChildren(int nextChild); - TaskAction childDone(bool success); - void stop(); - void invokeEndHandler(); - bool isRunning() const { return m_runtimeData.has_value(); } - bool isStarting() const { return isRunning() && m_runtimeData->m_startGuard.isLocked(); } - - struct ConstData { - ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task, TaskNode *parentNode, - TaskContainer *parentContainer, TaskContainer *thisContainer); - ~ConstData() { qDeleteAll(m_children); } - TaskTreePrivate * const m_taskTreePrivate = nullptr; - TaskNode * const m_parentNode = nullptr; - TaskContainer * const m_parentContainer = nullptr; - - const int m_parallelLimit = 1; - const WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError; - const TaskItem::GroupHandler m_groupHandler; - const QList m_storageList; - const QList m_children; - const int m_taskCount = 0; - }; - - struct RuntimeData { - RuntimeData(const ConstData &constData); - ~RuntimeData(); - - static QList createStorages(const TaskContainer::ConstData &constData); - void callStorageDoneHandlers(); - bool updateSuccessBit(bool success); - int currentLimit() const; - - const ConstData &m_constData; - const QList m_storageIdList; - int m_doneCount = 0; - bool m_successBit = true; - Guard m_startGuard; - }; - - const ConstData m_constData; - std::optional m_runtimeData; -}; - -class TaskNode : public QObject -{ -public: - TaskNode(TaskTreePrivate *taskTreePrivate, const TaskItem &task, - TaskContainer *parentContainer) - : m_taskHandler(task.taskHandler()) - , m_container(taskTreePrivate, task, this, parentContainer) - {} - - // If returned value != Continue, childDone() needs to be called in parent container (in caller) - // in order to unwind properly. - TaskAction start(); - void stop(); - void invokeEndHandler(bool success); - bool isRunning() const { return m_task || m_container.isRunning(); } - bool isTask() const { return bool(m_taskHandler.m_createHandler); } - int taskCount() const { return isTask() ? 1 : m_container.m_constData.m_taskCount; } - TaskContainer *parentContainer() const { return m_container.m_constData.m_parentContainer; } - -private: - const TaskItem::TaskHandler m_taskHandler; - TaskContainer m_container; - std::unique_ptr m_task; -}; - -void TaskTreePrivate::start() -{ - QTC_ASSERT(m_root, return); - m_progressValue = 0; - emitStartedAndProgress(); - // TODO: check storage handlers for not existing storages in tree - for (auto it = m_storageHandlers.cbegin(); it != m_storageHandlers.cend(); ++it) { - QTC_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't " - "exist in task tree. Its handlers will never be called.")); - } - m_root->start(); -} - -void TaskTreePrivate::stop() -{ - QTC_ASSERT(m_root, return); - if (!m_root->isRunning()) - return; - // TODO: should we have canceled flag (passed to handler)? - // Just one done handler with result flag: - // FinishedWithSuccess, FinishedWithError, Canceled, TimedOut. - // Canceled either directly by user, or by workflow policy - doesn't matter, in both - // cases canceled from outside. - m_root->stop(); - emitError(); -} - -void TaskTreePrivate::advanceProgress(int byValue) -{ - if (byValue == 0) - return; - QTC_CHECK(byValue > 0); - QTC_CHECK(m_progressValue + byValue <= m_root->taskCount()); - m_progressValue += byValue; - emitProgress(); -} - -void TaskTreePrivate::emitStartedAndProgress() -{ - GuardLocker locker(m_guard); - emit q->started(); - emit q->progressValueChanged(m_progressValue); -} - -void TaskTreePrivate::emitProgress() -{ - GuardLocker locker(m_guard); - emit q->progressValueChanged(m_progressValue); -} - -void TaskTreePrivate::emitDone() -{ - QTC_CHECK(m_progressValue == m_root->taskCount()); - GuardLocker locker(m_guard); - emit q->done(); -} - -void TaskTreePrivate::emitError() -{ - QTC_CHECK(m_progressValue == m_root->taskCount()); - GuardLocker locker(m_guard); - emit q->errorOccurred(); -} - -QList TaskTreePrivate::addStorages(const QList &storages) -{ - QList addedStorages; - for (const TreeStorageBase &storage : storages) { - QTC_ASSERT(!m_storages.contains(storage), qWarning("Can't add the same storage into " - "one TaskTree twice, skipping..."); continue); - addedStorages << storage; - m_storages << storage; - } - return addedStorages; -} - -class ExecutionContextActivator -{ -public: - ExecutionContextActivator(TaskContainer *container) - : m_container(container) { activateContext(m_container); } - ~ExecutionContextActivator() { deactivateContext(m_container); } - -private: - static void activateContext(TaskContainer *container) - { - QTC_ASSERT(container && container->isRunning(), return); - const TaskContainer::ConstData &constData = container->m_constData; - if (constData.m_parentContainer) - activateContext(constData.m_parentContainer); - for (int i = 0; i < constData.m_storageList.size(); ++i) - constData.m_storageList[i].activateStorage(container->m_runtimeData->m_storageIdList.value(i)); - } - static void deactivateContext(TaskContainer *container) - { - QTC_ASSERT(container && container->isRunning(), return); - const TaskContainer::ConstData &constData = container->m_constData; - for (int i = constData.m_storageList.size() - 1; i >= 0; --i) // iterate in reverse order - constData.m_storageList[i].activateStorage(0); - if (constData.m_parentContainer) - deactivateContext(constData.m_parentContainer); - } - TaskContainer *m_container = nullptr; -}; - -template > -ReturnType invokeHandler(TaskContainer *container, Handler &&handler, Args &&...args) -{ - ExecutionContextActivator activator(container); - GuardLocker locker(container->m_constData.m_taskTreePrivate->m_guard); - return std::invoke(std::forward(handler), std::forward(args)...); -} - -static QList createChildren(TaskTreePrivate *taskTreePrivate, TaskContainer *container, - const TaskItem &task) -{ - QList result; - const QList &children = task.children(); - for (const TaskItem &child : children) - result.append(new TaskNode(taskTreePrivate, child, container)); - return result; -} - -TaskContainer::ConstData::ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task, - TaskNode *parentNode, TaskContainer *parentContainer, - TaskContainer *thisContainer) - : m_taskTreePrivate(taskTreePrivate) - , m_parentNode(parentNode) - , m_parentContainer(parentContainer) - , m_parallelLimit(task.parallelLimit()) - , m_workflowPolicy(task.workflowPolicy()) - , m_groupHandler(task.groupHandler()) - , m_storageList(taskTreePrivate->addStorages(task.storageList())) - , m_children(createChildren(taskTreePrivate, thisContainer, task)) - , m_taskCount(std::accumulate(m_children.cbegin(), m_children.cend(), 0, - [](int r, TaskNode *n) { return r + n->taskCount(); })) -{} - -QList TaskContainer::RuntimeData::createStorages(const TaskContainer::ConstData &constData) -{ - QList storageIdList; - for (const TreeStorageBase &storage : constData.m_storageList) { - const int storageId = storage.createStorage(); - storageIdList.append(storageId); - constData.m_taskTreePrivate->callSetupHandler(storage, storageId); - } - return storageIdList; -} - -void TaskContainer::RuntimeData::callStorageDoneHandlers() -{ - for (int i = m_constData.m_storageList.size() - 1; i >= 0; --i) { // iterate in reverse order - const TreeStorageBase storage = m_constData.m_storageList[i]; - const int storageId = m_storageIdList.value(i); - m_constData.m_taskTreePrivate->callDoneHandler(storage, storageId); - } -} - -TaskContainer::RuntimeData::RuntimeData(const ConstData &constData) - : m_constData(constData) - , m_storageIdList(createStorages(constData)) -{ - m_successBit = m_constData.m_workflowPolicy != WorkflowPolicy::StopOnDone - && m_constData.m_workflowPolicy != WorkflowPolicy::ContinueOnDone; -} - -TaskContainer::RuntimeData::~RuntimeData() -{ - for (int i = m_constData.m_storageList.size() - 1; i >= 0; --i) { // iterate in reverse order - const TreeStorageBase storage = m_constData.m_storageList[i]; - const int storageId = m_storageIdList.value(i); - storage.deleteStorage(storageId); - } -} - -bool TaskContainer::RuntimeData::updateSuccessBit(bool success) -{ - if (m_constData.m_workflowPolicy == WorkflowPolicy::Optional) - return m_successBit; - - const bool donePolicy = m_constData.m_workflowPolicy == WorkflowPolicy::StopOnDone - || m_constData.m_workflowPolicy == WorkflowPolicy::ContinueOnDone; - m_successBit = donePolicy ? (m_successBit || success) : (m_successBit && success); - return m_successBit; -} - -int TaskContainer::RuntimeData::currentLimit() const -{ - const int childCount = m_constData.m_children.size(); - return m_constData.m_parallelLimit - ? qMin(m_doneCount + m_constData.m_parallelLimit, childCount) : childCount; -} - -TaskAction TaskContainer::start() -{ - QTC_CHECK(!isRunning()); - m_runtimeData.emplace(m_constData); - - TaskAction startAction = TaskAction::Continue; - if (m_constData.m_groupHandler.m_setupHandler) { - startAction = invokeHandler(this, m_constData.m_groupHandler.m_setupHandler); - if (startAction != TaskAction::Continue) - m_constData.m_taskTreePrivate->advanceProgress(m_constData.m_taskCount); - } - if (startAction == TaskAction::Continue) { - if (m_constData.m_children.isEmpty()) - startAction = TaskAction::StopWithDone; - } - return continueStart(startAction, 0); -} - -TaskAction TaskContainer::continueStart(TaskAction startAction, int nextChild) -{ - const TaskAction groupAction = startAction == TaskAction::Continue ? startChildren(nextChild) - : startAction; - QTC_CHECK(isRunning()); // TODO: superfluous - if (groupAction != TaskAction::Continue) { - const bool success = m_runtimeData->updateSuccessBit(groupAction == TaskAction::StopWithDone); - invokeEndHandler(); - if (TaskContainer *parentContainer = m_constData.m_parentContainer) { - QTC_CHECK(parentContainer->isRunning()); - if (!parentContainer->isStarting()) - parentContainer->childDone(success); - } else if (success) { - m_constData.m_taskTreePrivate->emitDone(); - } else { - m_constData.m_taskTreePrivate->emitError(); - } - } - return groupAction; -} - -TaskAction TaskContainer::startChildren(int nextChild) -{ - QTC_CHECK(isRunning()); - GuardLocker locker(m_runtimeData->m_startGuard); - for (int i = nextChild; i < m_constData.m_children.size(); ++i) { - const int limit = m_runtimeData->currentLimit(); - if (i >= limit) - break; - - const TaskAction startAction = m_constData.m_children.at(i)->start(); - if (startAction == TaskAction::Continue) - continue; - - const TaskAction finalizeAction = childDone(startAction == TaskAction::StopWithDone); - if (finalizeAction == TaskAction::Continue) - continue; - - int skippedTaskCount = 0; - // Skip scheduled but not run yet. The current (i) was already notified. - for (int j = i + 1; j < limit; ++j) - skippedTaskCount += m_constData.m_children.at(j)->taskCount(); - m_constData.m_taskTreePrivate->advanceProgress(skippedTaskCount); - return finalizeAction; - } - return TaskAction::Continue; -} - -TaskAction TaskContainer::childDone(bool success) -{ - QTC_CHECK(isRunning()); - const int limit = m_runtimeData->currentLimit(); // Read before bumping m_doneCount and stop() - const bool shouldStop = (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnDone && success) - || (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnError && !success); - if (shouldStop) - stop(); - - ++m_runtimeData->m_doneCount; - const bool updatedSuccess = m_runtimeData->updateSuccessBit(success); - const TaskAction startAction - = (shouldStop || m_runtimeData->m_doneCount == m_constData.m_children.size()) - ? toTaskAction(updatedSuccess) : TaskAction::Continue; - - if (isStarting()) - return startAction; - return continueStart(startAction, limit); -} - -void TaskContainer::stop() -{ - if (!isRunning()) - return; - - const int limit = m_runtimeData->currentLimit(); - for (int i = 0; i < limit; ++i) - m_constData.m_children.at(i)->stop(); - - int skippedTaskCount = 0; - for (int i = limit; i < m_constData.m_children.size(); ++i) - skippedTaskCount += m_constData.m_children.at(i)->taskCount(); - - m_constData.m_taskTreePrivate->advanceProgress(skippedTaskCount); -} - -void TaskContainer::invokeEndHandler() -{ - const TaskItem::GroupHandler &groupHandler = m_constData.m_groupHandler; - if (m_runtimeData->m_successBit && groupHandler.m_doneHandler) - invokeHandler(this, groupHandler.m_doneHandler); - else if (!m_runtimeData->m_successBit && groupHandler.m_errorHandler) - invokeHandler(this, groupHandler.m_errorHandler); - m_runtimeData->callStorageDoneHandlers(); - m_runtimeData.reset(); -} - -TaskAction TaskNode::start() -{ - QTC_CHECK(!isRunning()); - if (!isTask()) - return m_container.start(); - - m_task.reset(m_taskHandler.m_createHandler()); - const TaskAction startAction = m_taskHandler.m_setupHandler - ? invokeHandler(parentContainer(), m_taskHandler.m_setupHandler, *m_task.get()) - : TaskAction::Continue; - if (startAction != TaskAction::Continue) { - m_container.m_constData.m_taskTreePrivate->advanceProgress(1); - m_task.reset(); - return startAction; - } - const std::shared_ptr unwindAction - = std::make_shared(TaskAction::Continue); - connect(m_task.get(), &TaskInterface::done, this, [=](bool success) { - invokeEndHandler(success); - disconnect(m_task.get(), &TaskInterface::done, this, nullptr); - m_task.release()->deleteLater(); - QTC_ASSERT(parentContainer() && parentContainer()->isRunning(), return); - if (parentContainer()->isStarting()) - *unwindAction = toTaskAction(success); - else - parentContainer()->childDone(success); - }); - - m_task->start(); - return *unwindAction; -} - -void TaskNode::stop() -{ - if (!isRunning()) - return; - - if (!m_task) { - m_container.stop(); - m_container.invokeEndHandler(); - return; - } - - // TODO: cancelHandler? - // TODO: call TaskInterface::stop() ? - invokeEndHandler(false); - m_task.reset(); -} - -void TaskNode::invokeEndHandler(bool success) -{ - if (success && m_taskHandler.m_doneHandler) - invokeHandler(parentContainer(), m_taskHandler.m_doneHandler, *m_task.get()); - else if (!success && m_taskHandler.m_errorHandler) - invokeHandler(parentContainer(), m_taskHandler.m_errorHandler, *m_task.get()); - m_container.m_constData.m_taskTreePrivate->advanceProgress(1); -} - -/*! - \class TaskTree - \inheaderfile solutions/tasking/tasktree.h - \inmodule QtCreator - \ingroup mainclasses - \brief The TaskTree class runs an async task tree structure defined in a - declarative way. - - Use the Tasking namespace to build extensible, declarative task tree - structures that contain possibly asynchronous tasks, such as Process, - FileTransfer, or Async. TaskTree structures enable you - to create a sophisticated mixture of a parallel or sequential flow of tasks - in the form of a tree and to run it any time later. - - \section1 Root Element and Tasks - - The TaskTree has a mandatory Group root element, which may contain - any number of tasks of various types, such as ProcessTask, FileTransferTask, - or AsyncTask: - - \code - using namespace Tasking; - - const Group root { - ProcessTask(...), - AsyncTask(...), - FileTransferTask(...) - }; - - TaskTree *taskTree = new TaskTree(root); - connect(taskTree, &TaskTree::done, ...); // a successfully finished handler - connect(taskTree, &TaskTree::errorOccurred, ...); // an erroneously finished handler - taskTree->start(); - \endcode - - The task tree above has a top level element of the Group type that contains - tasks of the type ProcessTask, FileTransferTask, and AsyncTask. - After taskTree->start() is called, the tasks are run in a chain, starting - with ProcessTask. When the ProcessTask finishes successfully, the AsyncTask task is - started. Finally, when the asynchronous task finishes successfully, the - FileTransferTask task is started. - - When the last running task finishes with success, the task tree is considered - to have run successfully and the TaskTree::done() signal is emitted. - When a task finishes with an error, the execution of the task tree is stopped - and the remaining tasks are skipped. The task tree finishes with an error and - sends the TaskTree::errorOccurred() signal. - - \section1 Groups - - The parent of the Group sees it as a single task. Like other tasks, - the group can be started and it can finish with success or an error. - The Group elements can be nested to create a tree structure: - - \code - const Group root { - Group { - parallel, - ProcessTask(...), - AsyncTask(...) - }, - FileTransferTask(...) - }; - \endcode - - The example above differs from the first example in that the root element has - a subgroup that contains the ProcessTask and AsyncTask. The subgroup is a - sibling element of the FileTransferTask in the root. The subgroup contains an - additional \e parallel element that instructs its Group to execute its tasks - in parallel. - - So, when the tree above is started, the ProcessTask and AsyncTask start - immediately and run in parallel. Since the root group doesn't contain a - \e parallel element, its direct child tasks are run in sequence. Thus, the - FileTransferTask starts when the whole subgroup finishes. The group is - considered as finished when all its tasks have finished. The order in which - the tasks finish is not relevant. - - So, depending on which task lasts longer (ProcessTask or AsyncTask), the - following scenarios can take place: - - \table - \header - \li Scenario 1 - \li Scenario 2 - \row - \li Root Group starts - \li Root Group starts - \row - \li Sub Group starts - \li Sub Group starts - \row - \li ProcessTask starts - \li ProcessTask starts - \row - \li AsyncTask starts - \li AsyncTask starts - \row - \li ... - \li ... - \row - \li \b {ProcessTask finishes} - \li \b {AsyncTask finishes} - \row - \li ... - \li ... - \row - \li \b {AsyncTask finishes} - \li \b {ProcessTask finishes} - \row - \li Sub Group finishes - \li Sub Group finishes - \row - \li FileTransferTask starts - \li FileTransferTask starts - \row - \li ... - \li ... - \row - \li FileTransferTask finishes - \li FileTransferTask finishes - \row - \li Root Group finishes - \li Root Group finishes - \endtable - - The differences between the scenarios are marked with bold. Three dots mean - that an unspecified amount of time passes between previous and next events - (a task or tasks continue to run). No dots between events - means that they occur synchronously. - - The presented scenarios assume that all tasks run successfully. If a task - fails during execution, the task tree finishes with an error. In particular, - when ProcessTask finishes with an error while AsyncTask is still being executed, - the AsyncTask is automatically stopped, the subgroup finishes with an error, - the FileTransferTask is skipped, and the tree finishes with an error. - - \section1 Task Types - - Each task type is associated with its corresponding task class that executes - the task. For example, a ProcessTask inside a task tree is associated with - the Process class that executes the process. The associated objects are - automatically created, started, and destructed exclusively by the task tree - at the appropriate time. - - If a root group consists of five sequential ProcessTask tasks, and the task tree - executes the group, it creates an instance of Process for the first - ProcessTask and starts it. If the Process instance finishes successfully, - the task tree destructs it and creates a new Process instance for the - second ProcessTask, and so on. If the first task finishes with an error, the task - tree stops creating Process instances, and the root group finishes with an - error. - - The following table shows examples of task types and their corresponding task - classes: - - \table - \header - \li Task Type (Tasking Namespace) - \li Associated Task Class - \li Brief Description - \row - \li ProcessTask - \li Utils::Process - \li Starts processes. - \row - \li AsyncTask - \li Utils::Async - \li Starts asynchronous tasks; run in separate thread. - \row - \li TaskTreeTask - \li Utils::TaskTree - \li Starts a nested task tree. - \row - \li FileTransferTask - \li ProjectExplorer::FileTransfer - \li Starts file transfer between different devices. - \endtable - - \section1 Task Handlers - - Use Task handlers to set up a task for execution and to enable reading - the output data from the task when it finishes with success or an error. - - \section2 Task Start Handler - - When a corresponding task class object is created and before it's started, - the task tree invokes a mandatory user-provided setup handler. The setup - handler should always take a \e reference to the associated task class object: - - \code - const auto onSetup = [](Process &process) { - process.setCommand({"sleep", {"3"}}); - }; - const Group root { - ProcessTask(onSetup) - }; - \endcode - - You can modify the passed Process in the setup handler, so that the task - tree can start the process according to your configuration. - You do not need to call \e {process.start();} in the setup handler, - as the task tree calls it when needed. The setup handler is mandatory - and must be the first argument of the task's constructor. - - Optionally, the setup handler may return a TaskAction. The returned - TaskAction influences the further start behavior of a given task. The - possible values are: - - \table - \header - \li TaskAction Value - \li Brief Description - \row - \li Continue - \li The task is started normally. This is the default behavior when the - setup handler doesn't return TaskAction (that is, its return type is - void). - \row - \li StopWithDone - \li The task won't be started and it will report success to its parent. - \row - \li StopWithError - \li The task won't be started and it will report an error to its parent. - \endtable - - This is useful for running a task only when a condition is met and the data - needed to evaluate this condition is not known until previously started tasks - finish. This way, the setup handler dynamically decides whether to start the - corresponding task normally or skip it and report success or an error. - For more information about inter-task data exchange, see \l Storage. - - \section2 Task's Done and Error Handlers - - When a running task finishes, the task tree invokes an optionally provided - done or error handler. Both handlers should always take a \e {const reference} - to the associated task class object: - - \code - const auto onSetup = [](Process &process) { - process.setCommand({"sleep", {"3"}}); - }; - const auto onDone = [](const Process &process) { - qDebug() << "Success" << process.cleanedStdOut(); - }; - const auto onError = [](const Process &process) { - qDebug() << "Failure" << process.cleanedStdErr(); - }; - const Group root { - ProcessTask(onSetup, onDone, onError) - }; - \endcode - - The done and error handlers may collect output data from Process, and store it - for further processing or perform additional actions. The done handler is optional. - When used, it must be the second argument of the task constructor. - The error handler must always be the third argument. - You can omit the handlers or substitute the ones that you do not need with curly braces ({}). - - \note If the task setup handler returns StopWithDone or StopWithError, - neither the done nor error handler is invoked. - - \section1 Group Handlers - - Similarly to task handlers, group handlers enable you to set up a group to - execute and to apply more actions when the whole group finishes with - success or an error. - - \section2 Group's Start Handler - - The task tree invokes the group start handler before it starts the child - tasks. The group handler doesn't take any arguments: - - \code - const auto onGroupSetup = [] { - qDebug() << "Entering the group"; - }; - const Group root { - OnGroupSetup(onGroupSetup), - ProcessTask(...) - }; - \endcode - - The group setup handler is optional. To define a group setup handler, add an - OnGroupSetup element to a group. The argument of OnGroupSetup is a user - handler. If you add more than one OnGroupSetup element to a group, an assert - is triggered at runtime that includes an error message. - - Like the task start handler, the group start handler may return TaskAction. - The returned TaskAction value affects the start behavior of the - whole group. If you do not specify a group start handler or its return type - is void, the default group's action is TaskAction::Continue, so that all - tasks are started normally. Otherwise, when the start handler returns - TaskAction::StopWithDone or TaskAction::StopWithError, the tasks are not - started (they are skipped) and the group itself reports success or failure, - depending on the returned value, respectively. - - \code - const Group root { - OnGroupSetup([] { qDebug() << "Root setup"; }), - Group { - OnGroupSetup([] { qDebug() << "Group 1 setup"; return TaskAction::Continue; }), - ProcessTask(...) // Process 1 - }, - Group { - OnGroupSetup([] { qDebug() << "Group 2 setup"; return TaskAction::StopWithDone; }), - ProcessTask(...) // Process 2 - }, - Group { - OnGroupSetup([] { qDebug() << "Group 3 setup"; return TaskAction::StopWithError; }), - ProcessTask(...) // Process 3 - }, - ProcessTask(...) // Process 4 - }; - \endcode - - In the above example, all subgroups of a root group define their setup handlers. - The following scenario assumes that all started processes finish with success: - - \table - \header - \li Scenario - \li Comment - \row - \li Root Group starts - \li Doesn't return TaskAction, so its tasks are executed. - \row - \li Group 1 starts - \li Returns Continue, so its tasks are executed. - \row - \li Process 1 starts - \li - \row - \li ... - \li ... - \row - \li Process 1 finishes (success) - \li - \row - \li Group 1 finishes (success) - \li - \row - \li Group 2 starts - \li Returns StopWithDone, so Process 2 is skipped and Group 2 reports - success. - \row - \li Group 2 finishes (success) - \li - \row - \li Group 3 starts - \li Returns StopWithError, so Process 3 is skipped and Group 3 reports - an error. - \row - \li Group 3 finishes (error) - \li - \row - \li Root Group finishes (error) - \li Group 3, which is a direct child of the root group, finished with an - error, so the root group stops executing, skips Process 4, which has - not started yet, and reports an error. - \endtable - - \section2 Groups's Done and Error Handlers - - A Group's done or error handler is executed after the successful or failed - execution of its tasks, respectively. The final value reported by the - group depends on its \l {Workflow Policy}. The handlers can apply other - necessary actions. The done and error handlers are defined inside the - OnGroupDone and OnGroupError elements of a group, respectively. They do not - take arguments: - - \code - const Group root { - OnGroupSetup([] { qDebug() << "Root setup"; }), - ProcessTask(...), - OnGroupDone([] { qDebug() << "Root finished with success"; }), - OnGroupError([] { qDebug() << "Root finished with error"; }) - }; - \endcode - - The group done and error handlers are optional. If you add more than one - OnGroupDone or OnGroupError each to a group, an assert is triggered at - runtime that includes an error message. - - \note Even if the group setup handler returns StopWithDone or StopWithError, - one of the task's done or error handlers is invoked. This behavior differs - from that of task handlers and might change in the future. - - \section1 Other Group Elements - - A group can contain other elements that describe the processing flow, such as - the execution mode or workflow policy. It can also contain storage elements - that are responsible for collecting and sharing custom common data gathered - during group execution. - - \section2 Execution Mode - - The execution mode element in a Group specifies how the direct child tasks of - the Group are started. - - \table - \header - \li Execution Mode - \li Description - \row - \li sequential - \li Default. When a Group has no execution mode, it runs in the - sequential mode. All the direct child tasks of a group are started - in a chain, so that when one task finishes, the next one starts. - This enables you to pass the results from the previous task - as input to the next task before it starts. This mode guarantees - that the next task is started only after the previous task finishes. - \row - \li parallel - \li All the direct child tasks of a group are started after the group is - started, without waiting for the previous tasks to finish. In this - mode, all tasks run simultaneously. - \row - \li ParallelLimit(int limit) - \li In this mode, a limited number of direct child tasks run simultaneously. - The \e limit defines the maximum number of tasks running in parallel - in a group. When the group is started, the first batch tasks is - started (the number of tasks in batch equals to passed limit, at most), - while the others are kept waiting. When a running task finishes, - the group starts the next remaining one, so that the \e limit - of simultaneously running tasks inside a group isn't exceeded. - This repeats on every child task's finish until all child tasks are started. - This enables you to limit the maximum number of tasks that - run simultaneously, for example if running too many processes might - block the machine for a long time. The value 1 means \e sequential - execution. The value 0 means unlimited and equals \e parallel. - \endtable - - In all execution modes, a group starts tasks in the oder in which they appear. - - If a child of a group is also a group (in a nested tree), the child group - runs its tasks according to its own execution mode. - - \section2 Workflow Policy - - The workflow policy element in a Group specifies how the group should behave - when its direct child tasks finish: - - \table - \header - \li Workflow Policy - \li Description - \row - \li stopOnError - \li Default. If a task finishes with an error, the group: - \list 1 - \li Stops the running tasks (if any - for example, in parallel - mode). - \li Skips executing tasks it has not started (for example, in the - sequential mode). - \li Immediately finishes with an error. - \endlist - If all child tasks finish successfully or the group is empty, the group - finishes with success. - \row - \li continueOnError - \li Similar to stopOnError, but in case any child finishes with - an error, the execution continues until all tasks finish, - and the group reports an error afterwards, even when some other - tasks in group finished with success. - If a task finishes with an error, the group: - \list 1 - \li Continues executing the tasks that are running or have not - started yet. - \li Finishes with an error when all tasks finish. - \endlist - If all tasks finish successfully or the group is empty, the group - finishes with success. - \row - \li stopOnDone - \li If a task finishes with success, the group: - \list 1 - \li Stops running tasks and skips those that it has not started. - \li Immediately finishes with success. - \endlist - If all tasks finish with an error or the group is empty, the group - finishes with an error. - \row - \li continueOnDone - \li Similar to stopOnDone, but in case any child finishes - successfully, the execution continues until all tasks finish, - and the group reports success afterwards, even when some other - tasks in group finished with an error. - If a task finishes with success, the group: - \list 1 - \li Continues executing the tasks that are running or have not - started yet. - \li Finishes with success when all tasks finish. - \endlist - If all tasks finish with an error or the group is empty, the group - finishes with an error. - \row - \li optional - \li The group executes all tasks and ignores their return state. If all - tasks finish or the group is empty, the group finishes with success. - \endtable - - If a child of a group is also a group (in a nested tree), the child group - runs its tasks according to its own workflow policy. - - \section2 Storage - - Use the Storage element to exchange information between tasks. Especially, - in the sequential execution mode, when a task needs data from another task - before it can start. For example, a task tree that copies data by reading - it from a source and writing it to a destination might look as follows: - - \code - static QByteArray load(const FilePath &fileName) { ... } - static void save(const FilePath &fileName, const QByteArray &array) { ... } - - static TaskItem diffRecipe(const FilePath &source, const FilePath &destination) - { - struct CopyStorage { // [1] custom inter-task struct - QByteArray content; // [2] custom inter-task data - }; - - // [3] instance of custom inter-task struct manageable by task tree - const TreeStorage storage; - - const auto onLoaderSetup = [source](Async &async) { - async.setConcurrentCallData(&load, source); - }; - // [4] runtime: task tree activates the instance from [5] before invoking handler - const auto onLoaderDone = [storage](const Async &async) { - storage->content = async.result(); - }; - - // [4] runtime: task tree activates the instance from [5] before invoking handler - const auto onSaverSetup = [storage, destination](Async &async) { - async.setConcurrentCallData(&save, destination, storage->content); - }; - const auto onSaverDone = [](const Async &async) { - qDebug() << "Save done successfully"; - }; - - const Group root { - // [5] runtime: task tree creates an instance of CopyStorage when root is entered - Storage(storage), - AsyncTask(onLoaderSetup, onLoaderDone), - AsyncTask(onSaverSetup, onSaverDone) - }; - return root; - } - \endcode - - In the example above, the inter-task data consists of a QByteArray content - variable [2] enclosed in a CopyStorage custom struct [1]. If the loader - finishes successfully, it stores the data in a CopyStorage::content - variable. The saver then uses the variable to configure the saving task. - - To enable a task tree to manage the CopyStorage struct, an instance of - TreeStorage is created [3]. If a copy of this object is - inserted as group's child task [5], an instance of CopyStorage struct is - created dynamically when the task tree enters this group. When the task - tree leaves this group, the existing instance of CopyStorage struct is - destructed as it's no longer needed. - - If several task trees that hold a copy of the common TreeStorage - instance run simultaneously, each task tree contains its own copy of the - CopyStorage struct. - - You can access CopyStorage from any handler in the group with a storage object. - This includes all handlers of all descendant tasks of the group with - a storage object. To access the custom struct in a handler, pass the - copy of the TreeStorage object to the handler (for example, in - a lambda capture) [4]. - - When the task tree invokes a handler in a subtree containing the storage [5], - the task tree activates its own CopyStorage instance inside the - TreeStorage object. Therefore, the CopyStorage struct may be - accessed only from within the handler body. To access the currently active - CopyStorage from within TreeStorage, use the TreeStorage::operator->() - or TreeStorage::activeStorage() method. - - The following list summarizes how to employ a Storage object into the task - tree: - \list 1 - \li Define the custom structure MyStorage with custom data [1], [2] - \li Create an instance of TreeStorage storage [3] - \li Pass the TreeStorage instance to handlers [4] - \li Insert the TreeStorage instance into a group [5] - \endlist - - \note The current implementation assumes that all running task trees - containing copies of the same TreeStorage run in the same thread. Otherwise, - the behavior is undefined. - - \section1 TaskTree - - TaskTree executes the tree structure of asynchronous tasks according to the - recipe described by the Group root element. - - As TaskTree is also an asynchronous task, it can be a part of another TaskTree. - To place a nested TaskTree inside another TaskTree, insert the TaskTreeTask - element into other tree's Group element. - - TaskTree reports progress of completed tasks when running. The progress value - is increased when a task finishes or is skipped or stopped. - When TaskTree is finished and the TaskTree::done() or TaskTree::errorOccurred() - signal is emitted, the current value of the progress equals the maximum - progress value. Maximum progress equals the total number of tasks in a tree. - A nested TaskTree is counted as a single task, and its child tasks are not - counted in the top level tree. Groups themselves are not counted as tasks, - but their tasks are counted. - - To set additional initial data for the running tree, modify the storage - instances in a tree when it creates them by installing a storage setup - handler: - - \code - TreeStorage storage; - Group root = ...; // storage placed inside root's group and inside handlers - TaskTree taskTree(root); - auto initStorage = [](CopyStorage *storage){ - storage->content = "initial content"; - }; - taskTree.onStorageSetup(storage, initStorage); - taskTree.start(); - \endcode - - When the running task tree creates a CopyStorage instance, and before any - handler inside a tree is called, the task tree calls the initStorage handler, - to enable setting up initial data of the storage, unique to this particular - run of taskTree. - - Similarly, to collect some additional result data from the running tree, - read it from storage instances in the tree when they are about to be - destroyed. To do this, install a storage done handler: - - \code - TreeStorage storage; - Group root = ...; // storage placed inside root's group and inside handlers - TaskTree taskTree(root); - auto collectStorage = [](CopyStorage *storage){ - qDebug() << "final content" << storage->content; - }; - taskTree.onStorageDone(storage, collectStorage); - taskTree.start(); - \endcode - - When the running task tree is about to destroy a CopyStorage instance, the - task tree calls the collectStorage handler, to enable reading the final data - from the storage, unique to this particular run of taskTree. - - \section1 Task Adapters - - To extend a TaskTree with new a task type, implement a simple adapter class - derived from the TaskAdapter class template. The following class is an - adapter for a single shot timer, which may be considered as a new - asynchronous task: - - \code - class TimeoutAdapter : public Tasking::TaskAdapter - { - public: - TimeoutAdapter() { - task()->setSingleShot(true); - task()->setInterval(1000); - connect(task(), &QTimer::timeout, this, [this] { emit done(true); }); - } - void start() final { task()->start(); } - }; - - QTC_DECLARE_CUSTOM_TASK(Timeout, TimeoutAdapter); - \endcode - - You must derive the custom adapter from the TaskAdapter class template - instantiated with a template parameter of the class implementing a running - task. The code above uses QTimer to run the task. This class appears - later as an argument to the task's handlers. The instance of this class - parameter automatically becomes a member of the TaskAdapter template, and is - accessible through the TaskAdapter::task() method. The constructor - of TimeoutAdapter initially configures the QTimer object and connects - to the QTimer::timeout signal. When the signal is triggered, TimeoutAdapter - emits the done(true) signal to inform the task tree that the task finished - successfully. If it emits done(false), the task finished with an error. - The TaskAdapter::start() method starts the timer. - - To make QTimer accessible inside TaskTree under the \e Timeout name, - register it with QTC_DECLARE_CUSTOM_TASK(Timeout, TimeoutAdapter). Timeout - becomes a new task type inside Tasking namespace, using TimeoutAdapter. - - The new task type is now registered, and you can use it in TaskTree: - - \code - const auto onTimeoutSetup = [](QTimer &task) { - task.setInterval(2000); - }; - const auto onTimeoutDone = [](const QTimer &task) { - qDebug() << "timeout triggered"; - }; - - const Group root { - Timeout(onTimeoutSetup, onTimeoutDone) - }; - \endcode - - When a task tree containing the root from the above example is started, it - prints a debug message within two seconds and then finishes successfully. - - \note The class implementing the running task should have a default constructor, - and objects of this class should be freely destructible. It should be allowed - to destroy a running object, preferably without waiting for the running task - to finish (that is, safe non-blocking destructor of a running task). -*/ - -TaskTree::TaskTree() - : d(new TaskTreePrivate(this)) -{ -} - -TaskTree::TaskTree(const Group &root) : TaskTree() -{ - setupRoot(root); -} - -TaskTree::~TaskTree() -{ - QTC_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting TaskTree instance directly from " - "one of its handlers will lead to crash!")); - // TODO: delete storages explicitly here? - delete d; -} - -void TaskTree::setupRoot(const Group &root) -{ - QTC_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return); - QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The setupRoot() is called from one of the" - "TaskTree handlers, ingoring..."); return); - d->m_storages.clear(); - d->m_root.reset(new TaskNode(d, root, nullptr)); -} - -void TaskTree::start() -{ - QTC_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return); - QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The start() is called from one of the" - "TaskTree handlers, ingoring..."); return); - d->start(); -} - -void TaskTree::stop() -{ - QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The stop() is called from one of the" - "TaskTree handlers, ingoring..."); return); - d->stop(); -} - -bool TaskTree::isRunning() const -{ - return d->m_root && d->m_root->isRunning(); -} - -int TaskTree::taskCount() const -{ - return d->m_root ? d->m_root->taskCount() : 0; -} - -int TaskTree::progressValue() const -{ - return d->m_progressValue; -} - -void TaskTree::setupStorageHandler(const TreeStorageBase &storage, - StorageVoidHandler setupHandler, - StorageVoidHandler doneHandler) -{ - auto it = d->m_storageHandlers.find(storage); - if (it == d->m_storageHandlers.end()) { - d->m_storageHandlers.insert(storage, {setupHandler, doneHandler}); - return; - } - if (setupHandler) { - QTC_ASSERT(!it->m_setupHandler, - qWarning("The storage has its setup handler defined, overriding...")); - it->m_setupHandler = setupHandler; - } - if (doneHandler) { - QTC_ASSERT(!it->m_doneHandler, - qWarning("The storage has its done handler defined, overriding...")); - it->m_doneHandler = doneHandler; - } -} - -TaskTreeTaskAdapter::TaskTreeTaskAdapter() -{ - connect(task(), &TaskTree::done, this, [this] { emit done(true); }); - connect(task(), &TaskTree::errorOccurred, this, [this] { emit done(false); }); -} - -void TaskTreeTaskAdapter::start() -{ - task()->start(); -} - -} // namespace Tasking diff --git a/src/libs/utils/tasktree.h b/src/libs/utils/tasktree.h deleted file mode 100644 index d8545564b9..0000000000 --- a/src/libs/utils/tasktree.h +++ /dev/null @@ -1,428 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "utils_global.h" - -#include -#include -#include - -namespace Tasking { - -class ExecutionContextActivator; -class TaskContainer; -class TaskNode; -class TaskTreePrivate; - -class QTCREATOR_UTILS_EXPORT TaskInterface : public QObject -{ - Q_OBJECT - -public: - TaskInterface() = default; - virtual void start() = 0; - -signals: - void done(bool success); -}; - -class QTCREATOR_UTILS_EXPORT TreeStorageBase -{ -public: - bool isValid() const; - -protected: - using StorageConstructor = std::function; - using StorageDestructor = std::function; - - TreeStorageBase(StorageConstructor ctor, StorageDestructor dtor); - void *activeStorageVoid() const; - -private: - int createStorage() const; - void deleteStorage(int id) const; - void activateStorage(int id) const; - - friend bool operator==(const TreeStorageBase &first, const TreeStorageBase &second) - { return first.m_storageData == second.m_storageData; } - - friend bool operator!=(const TreeStorageBase &first, const TreeStorageBase &second) - { return first.m_storageData != second.m_storageData; } - - friend size_t qHash(const TreeStorageBase &storage, uint seed = 0) - { return size_t(storage.m_storageData.get()) ^ seed; } - - struct StorageData { - ~StorageData(); - StorageConstructor m_constructor = {}; - StorageDestructor m_destructor = {}; - QHash m_storageHash = {}; - int m_activeStorage = 0; // 0 means no active storage - int m_storageCounter = 0; - }; - QSharedPointer m_storageData; - friend TaskContainer; - friend TaskTreePrivate; - friend ExecutionContextActivator; -}; - -template -class TreeStorage : public TreeStorageBase -{ -public: - TreeStorage() : TreeStorageBase(TreeStorage::ctor(), TreeStorage::dtor()) {} - StorageStruct &operator*() const noexcept { return *activeStorage(); } - StorageStruct *operator->() const noexcept { return activeStorage(); } - StorageStruct *activeStorage() const { - return static_cast(activeStorageVoid()); - } - -private: - static StorageConstructor ctor() { return [] { return new StorageStruct; }; } - static StorageDestructor dtor() { - return [](void *storage) { delete static_cast(storage); }; - } -}; - -// WorkflowPolicy: -// 1. When all children finished with done -> report done, otherwise: -// a) Report error on first error and stop executing other children (including their subtree) -// b) On first error - continue executing all children and report error afterwards -// 2. When all children finished with error -> report error, otherwise: -// a) Report done on first done and stop executing other children (including their subtree) -// b) On first done - continue executing all children and report done afterwards -// 3. Always run all children, ignore their result and report done afterwards - -enum class WorkflowPolicy { - StopOnError, // 1a - Reports error on first child error, otherwise done (if all children were done) - ContinueOnError, // 1b - The same, but children execution continues. When no children it reports done. - StopOnDone, // 2a - Reports done on first child done, otherwise error (if all children were error) - ContinueOnDone, // 2b - The same, but children execution continues. When no children it reports done. (?) - Optional // 3 - Always reports done after all children finished -}; - -enum class TaskAction -{ - Continue, - StopWithDone, - StopWithError -}; - -class QTCREATOR_UTILS_EXPORT TaskItem -{ -public: - // Internal, provided by QTC_DECLARE_CUSTOM_TASK - using TaskCreateHandler = std::function; - // Called prior to task start, just after createHandler - using TaskSetupHandler = std::function; - // Called on task done / error - using TaskEndHandler = std::function; - // Called when group entered - using GroupSetupHandler = std::function; - // Called when group done / error - using GroupEndHandler = std::function; - - struct TaskHandler { - TaskCreateHandler m_createHandler; - TaskSetupHandler m_setupHandler = {}; - TaskEndHandler m_doneHandler = {}; - TaskEndHandler m_errorHandler = {}; - }; - - struct GroupHandler { - GroupSetupHandler m_setupHandler; - GroupEndHandler m_doneHandler = {}; - GroupEndHandler m_errorHandler = {}; - }; - - int parallelLimit() const { return m_parallelLimit; } - WorkflowPolicy workflowPolicy() const { return m_workflowPolicy; } - TaskHandler taskHandler() const { return m_taskHandler; } - GroupHandler groupHandler() const { return m_groupHandler; } - QList children() const { return m_children; } - QList storageList() const { return m_storageList; } - -protected: - enum class Type { - Group, - Storage, - Limit, - Policy, - TaskHandler, - GroupHandler - }; - - TaskItem() = default; - TaskItem(int parallelLimit) - : m_type(Type::Limit) - , m_parallelLimit(parallelLimit) {} - TaskItem(WorkflowPolicy policy) - : m_type(Type::Policy) - , m_workflowPolicy(policy) {} - TaskItem(const TaskHandler &handler) - : m_type(Type::TaskHandler) - , m_taskHandler(handler) {} - TaskItem(const GroupHandler &handler) - : m_type(Type::GroupHandler) - , m_groupHandler(handler) {} - TaskItem(const TreeStorageBase &storage) - : m_type(Type::Storage) - , m_storageList{storage} {} - void addChildren(const QList &children); - - void setTaskSetupHandler(const TaskSetupHandler &handler); - void setTaskDoneHandler(const TaskEndHandler &handler); - void setTaskErrorHandler(const TaskEndHandler &handler); - -private: - Type m_type = Type::Group; - int m_parallelLimit = 1; // 0 means unlimited - WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError; - TaskHandler m_taskHandler; - GroupHandler m_groupHandler; - QList m_storageList; - QList m_children; -}; - -class QTCREATOR_UTILS_EXPORT Group : public TaskItem -{ -public: - Group(const QList &children) { addChildren(children); } - Group(std::initializer_list children) { addChildren(children); } -}; - -class QTCREATOR_UTILS_EXPORT Storage : public TaskItem -{ -public: - Storage(const TreeStorageBase &storage) : TaskItem(storage) { } -}; - -class QTCREATOR_UTILS_EXPORT ParallelLimit : public TaskItem -{ -public: - ParallelLimit(int parallelLimit) : TaskItem(qMax(parallelLimit, 0)) {} -}; - -class QTCREATOR_UTILS_EXPORT Workflow : public TaskItem -{ -public: - Workflow(WorkflowPolicy policy) : TaskItem(policy) {} -}; - -class QTCREATOR_UTILS_EXPORT OnGroupSetup : public TaskItem -{ -public: - template - OnGroupSetup(SetupFunction &&function) - : TaskItem({wrapSetup(std::forward(function))}) {} - -private: - template - static TaskItem::GroupSetupHandler wrapSetup(SetupFunction &&function) { - static constexpr bool isDynamic = std::is_same_v>>; - constexpr bool isVoid = std::is_same_v>>; - static_assert(isDynamic || isVoid, - "Group setup handler needs to take no arguments and has to return " - "void or TaskAction. The passed handler doesn't fulfill these requirements."); - return [=] { - if constexpr (isDynamic) - return std::invoke(function); - std::invoke(function); - return TaskAction::Continue; - }; - }; -}; - -class QTCREATOR_UTILS_EXPORT OnGroupDone : public TaskItem -{ -public: - OnGroupDone(const GroupEndHandler &handler) : TaskItem({{}, handler}) {} -}; - -class QTCREATOR_UTILS_EXPORT OnGroupError : public TaskItem -{ -public: - OnGroupError(const GroupEndHandler &handler) : TaskItem({{}, {}, handler}) {} -}; - -// Synchronous invocation. Similarly to Group - isn't counted as a task inside taskCount() -class QTCREATOR_UTILS_EXPORT Sync : public Group -{ - -public: - template - Sync(Function &&function) : Group(init(std::forward(function))) {} - -private: - template - static QList init(Function &&function) { - constexpr bool isInvocable = std::is_invocable_v>; - static_assert(isInvocable, - "Sync element: The synchronous function can't take any arguments."); - constexpr bool isBool = std::is_same_v>>; - constexpr bool isVoid = std::is_same_v>>; - static_assert(isBool || isVoid, - "Sync element: The synchronous function has to return void or bool."); - if constexpr (isBool) { - return {OnGroupSetup([function] { return function() ? TaskAction::StopWithDone - : TaskAction::StopWithError; })}; - } - return {OnGroupSetup([function] { function(); return TaskAction::StopWithDone; })}; - }; - -}; - -QTCREATOR_UTILS_EXPORT extern ParallelLimit sequential; -QTCREATOR_UTILS_EXPORT extern ParallelLimit parallel; -QTCREATOR_UTILS_EXPORT extern Workflow stopOnError; -QTCREATOR_UTILS_EXPORT extern Workflow continueOnError; -QTCREATOR_UTILS_EXPORT extern Workflow stopOnDone; -QTCREATOR_UTILS_EXPORT extern Workflow continueOnDone; -QTCREATOR_UTILS_EXPORT extern Workflow optional; - -template -class TaskAdapter : public TaskInterface -{ -public: - using Type = Task; - TaskAdapter() = default; - Task *task() { return &m_task; } - const Task *task() const { return &m_task; } -private: - Task m_task; -}; - -template -class CustomTask : public TaskItem -{ -public: - using Task = typename Adapter::Type; - using EndHandler = std::function; - static Adapter *createAdapter() { return new Adapter; } - CustomTask() : TaskItem({&createAdapter}) {} - template - CustomTask(SetupFunction &&function, const EndHandler &done = {}, const EndHandler &error = {}) - : TaskItem({&createAdapter, wrapSetup(std::forward(function)), - wrapEnd(done), wrapEnd(error)}) {} - - template - CustomTask &onSetup(SetupFunction &&function) { - setTaskSetupHandler(wrapSetup(std::forward(function))); - return *this; - } - CustomTask &onDone(const EndHandler &handler) { - setTaskDoneHandler(wrapEnd(handler)); - return *this; - } - CustomTask &onError(const EndHandler &handler) { - setTaskErrorHandler(wrapEnd(handler)); - return *this; - } - -private: - template - static TaskItem::TaskSetupHandler wrapSetup(SetupFunction &&function) { - static constexpr bool isDynamic = std::is_same_v, typename Adapter::Type &>>; - constexpr bool isVoid = std::is_same_v, typename Adapter::Type &>>; - static_assert(isDynamic || isVoid, - "Task setup handler needs to take (Task &) as an argument and has to return " - "void or TaskAction. The passed handler doesn't fulfill these requirements."); - return [=](TaskInterface &taskInterface) { - Adapter &adapter = static_cast(taskInterface); - if constexpr (isDynamic) - return std::invoke(function, *adapter.task()); - std::invoke(function, *adapter.task()); - return TaskAction::Continue; - }; - }; - - static TaskEndHandler wrapEnd(const EndHandler &handler) { - if (!handler) - return {}; - return [handler](const TaskInterface &taskInterface) { - const Adapter &adapter = static_cast(taskInterface); - handler(*adapter.task()); - }; - }; -}; - -class TaskTreePrivate; - -class QTCREATOR_UTILS_EXPORT TaskTree final : public QObject -{ - Q_OBJECT - -public: - TaskTree(); - TaskTree(const Group &root); - ~TaskTree(); - - void setupRoot(const Group &root); - - void start(); - void stop(); - bool isRunning() const; - - int taskCount() const; - int progressMaximum() const { return taskCount(); } - int progressValue() const; // all finished / skipped / stopped tasks, groups itself excluded - - template - void onStorageSetup(const TreeStorage &storage, StorageHandler &&handler) { - setupStorageHandler(storage, - wrapHandler(std::forward(handler)), {}); - } - template - void onStorageDone(const TreeStorage &storage, StorageHandler &&handler) { - setupStorageHandler(storage, - {}, wrapHandler(std::forward(handler))); - } - -signals: - void started(); - void done(); - void errorOccurred(); - void progressValueChanged(int value); // updated whenever task finished / skipped / stopped - -private: - using StorageVoidHandler = std::function; - void setupStorageHandler(const TreeStorageBase &storage, - StorageVoidHandler setupHandler, - StorageVoidHandler doneHandler); - template - StorageVoidHandler wrapHandler(StorageHandler &&handler) { - return [=](void *voidStruct) { - StorageStruct *storageStruct = static_cast(voidStruct); - std::invoke(handler, storageStruct); - }; - } - - friend class TaskTreePrivate; - TaskTreePrivate *d; -}; - -class QTCREATOR_UTILS_EXPORT TaskTreeTaskAdapter : public TaskAdapter -{ -public: - TaskTreeTaskAdapter(); - void start() final; -}; - -} // namespace Tasking - -#define TASKING_DECLARE_TASK(CustomTaskName, TaskAdapterClass)\ -namespace Tasking { using CustomTaskName = CustomTask; } - -#define TASKING_DECLARE_TEMPLATE_TASK(CustomTaskName, TaskAdapterClass)\ -namespace Tasking {\ -template \ -using CustomTaskName = CustomTask>;\ -} // namespace Tasking - -TASKING_DECLARE_TASK(TaskTreeTask, TaskTreeTaskAdapter); diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index f316a8b75b..d1c8d0e95e 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -35,6 +35,7 @@ Project { Depends { name: "Qt"; submodules: ["concurrent", "core-private", "network", "qml", "widgets", "xml"] } Depends { name: "Qt.macextras"; condition: Qt.core.versionMajor < 6 && qbs.targetOS.contains("macos") } + Depends { name: "Tasking" } Depends { name: "app_version_header" } Depends { name: "ptyqt" } @@ -51,8 +52,6 @@ Project { "aspects.h", "async.cpp", "async.h", - "barrier.cpp", - "barrier.h", "basetreeview.cpp", "basetreeview.h", "benchmarker.cpp", @@ -308,8 +307,6 @@ Project { "styledbar.h", "stylehelper.cpp", "stylehelper.h", - "tasktree.cpp", - "tasktree.h", "templateengine.cpp", "templateengine.h", "temporarydirectory.cpp", -- cgit v1.2.3 From 1fc2459b6219a0163f2a52bb636f6ec473791615 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Fri, 28 Apr 2023 08:39:20 +0200 Subject: Utils: Unify CheckableMessageBox and make it look more native Change-Id: I5690c16f38cfd2058e01441283bec28d44cadf75 Reviewed-by: Eike Ziller Reviewed-by: Qt CI Bot --- src/libs/utils/checkablemessagebox.cpp | 522 +++++++++------------------------ src/libs/utils/checkablemessagebox.h | 277 +++++++++++------ 2 files changed, 334 insertions(+), 465 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/checkablemessagebox.cpp b/src/libs/utils/checkablemessagebox.cpp index 5d3011ff59..92b111ed7c 100644 --- a/src/libs/utils/checkablemessagebox.cpp +++ b/src/libs/utils/checkablemessagebox.cpp @@ -3,6 +3,7 @@ #include "checkablemessagebox.h" +#include "hostosinfo.h" #include "qtcassert.h" #include "utilstr.h" @@ -30,398 +31,159 @@ static const char kDoNotAskAgainKey[] = "DoNotAskAgain"; namespace Utils { -class CheckableMessageBoxPrivate -{ -public: - CheckableMessageBoxPrivate(QDialog *q) - { - QSizePolicy sizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); - - pixmapLabel = new QLabel(q); - sizePolicy.setHorizontalStretch(0); - sizePolicy.setVerticalStretch(0); - sizePolicy.setHeightForWidth(pixmapLabel->sizePolicy().hasHeightForWidth()); - pixmapLabel->setSizePolicy(sizePolicy); - pixmapLabel->setVisible(false); - pixmapLabel->setFocusPolicy(Qt::NoFocus); - - auto pixmapSpacer = - new QSpacerItem(0, 5, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); - - messageLabel = new QLabel(q); - messageLabel->setMinimumSize(QSize(300, 0)); - messageLabel->setWordWrap(true); - messageLabel->setOpenExternalLinks(true); - messageLabel->setTextInteractionFlags(Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse); - messageLabel->setFocusPolicy(Qt::NoFocus); - messageLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); - - checkBox = new QCheckBox(q); - checkBox->setText(Tr::tr("Do not ask again")); - - const QString showText = Tr::tr("Show Details..."); - detailsButton = new QPushButton(showText, q); - detailsButton->setAutoDefault(false); - detailsButton->hide(); - detailsText = new QTextEdit(q); - detailsText->hide(); - QObject::connect(detailsButton, &QPushButton::clicked, detailsText, [this, showText] { - detailsText->setVisible(!detailsText->isVisible()); - detailsButton->setText( - detailsText->isVisible() ? Tr::tr("Hide Details...") : showText); - }); - - buttonBox = new QDialogButtonBox(q); - buttonBox->setOrientation(Qt::Horizontal); - buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); - - auto verticalLayout = new QVBoxLayout(); - verticalLayout->addWidget(pixmapLabel); - verticalLayout->addItem(pixmapSpacer); - - auto horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->addLayout(verticalLayout); - horizontalLayout_2->addWidget(messageLabel, 10); - - auto horizontalLayout = new QHBoxLayout(); - horizontalLayout->addWidget(checkBox); - horizontalLayout->addStretch(10); - - auto detailsButtonLayout = new QHBoxLayout; - detailsButtonLayout->addWidget(detailsButton); - detailsButtonLayout->addStretch(10); - - auto verticalLayout_2 = new QVBoxLayout(q); - verticalLayout_2->addLayout(horizontalLayout_2); - verticalLayout_2->addLayout(horizontalLayout); - verticalLayout_2->addLayout(detailsButtonLayout); - verticalLayout_2->addWidget(detailsText, 10); - verticalLayout_2->addStretch(1); - verticalLayout_2->addWidget(buttonBox); - } - - QLabel *pixmapLabel = nullptr; - QLabel *messageLabel = nullptr; - QCheckBox *checkBox = nullptr; - QDialogButtonBox *buttonBox = nullptr; - QAbstractButton *clickedButton = nullptr; - QPushButton *detailsButton = nullptr; - QTextEdit *detailsText = nullptr; - QMessageBox::Icon icon = QMessageBox::NoIcon; -}; - -CheckableMessageBox::CheckableMessageBox(QWidget *parent) : - QDialog(parent), - d(new CheckableMessageBoxPrivate(this)) -{ - setModal(true); - connect(d->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(d->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - connect(d->buttonBox, &QDialogButtonBox::clicked, - this, [this](QAbstractButton *b) { d->clickedButton = b; }); -} - -CheckableMessageBox::~CheckableMessageBox() -{ - delete d; -} - -QAbstractButton *CheckableMessageBox::clickedButton() const -{ - return d->clickedButton; -} - -QDialogButtonBox::StandardButton CheckableMessageBox::clickedStandardButton() const -{ - if (d->clickedButton) - return d->buttonBox->standardButton(d->clickedButton); - return QDialogButtonBox::NoButton; -} - -QString CheckableMessageBox::text() const -{ - return d->messageLabel->text(); -} - -void CheckableMessageBox::setText(const QString &t) -{ - d->messageLabel->setText(t); -} - -QMessageBox::Icon CheckableMessageBox::icon() const -{ - return d->icon; -} - -// See QMessageBoxPrivate::standardIcon -static QPixmap pixmapForIcon(QMessageBox::Icon icon, QWidget *w) -{ - const QStyle *style = w ? w->style() : QApplication::style(); - const int iconSize = style->pixelMetric(QStyle::PM_MessageBoxIconSize, nullptr, w); - QIcon tmpIcon; - switch (icon) { - case QMessageBox::Information: - tmpIcon = style->standardIcon(QStyle::SP_MessageBoxInformation, nullptr, w); - break; - case QMessageBox::Warning: - tmpIcon = style->standardIcon(QStyle::SP_MessageBoxWarning, nullptr, w); - break; - case QMessageBox::Critical: - tmpIcon = style->standardIcon(QStyle::SP_MessageBoxCritical, nullptr, w); - break; - case QMessageBox::Question: - tmpIcon = style->standardIcon(QStyle::SP_MessageBoxQuestion, nullptr, w); - break; - default: - break; - } - if (!tmpIcon.isNull()) { - QWindow *window = nullptr; - if (w) { - window = w->windowHandle(); - if (!window) { - if (const QWidget *nativeParent = w->nativeParentWidget()) - window = nativeParent->windowHandle(); - } +static QMessageBox::StandardButton exec( + QWidget *parent, + QMessageBox::Icon icon, + const QString &title, + const QString &text, + std::optional decider, + QMessageBox::StandardButtons buttons, + QMessageBox::StandardButton defaultButton, + QMessageBox::StandardButton acceptButton, + QMap buttonTextOverrides) +{ + QMessageBox msgBox(parent); + msgBox.setWindowTitle(title); + msgBox.setIcon(icon); + msgBox.setText(text); + msgBox.setTextFormat(Qt::RichText); + msgBox.setTextInteractionFlags(Qt::LinksAccessibleByKeyboard | Qt::LinksAccessibleByMouse); + + if (HostOsInfo::isMacHost()) { + // Message boxes on macOS cannot display links. + // If the message contains a link, we need to disable native dialogs. + if (text.contains("= QT_VERSION_CHECK(6, 6, 0) + msgBox.setOptions(QMessageBox::Option::DontUseNativeDialog); +#endif } - return tmpIcon.pixmap(window, QSize(iconSize, iconSize)); - } - return QPixmap(); -} - -void CheckableMessageBox::setIcon(QMessageBox::Icon icon) -{ - d->icon = icon; - const QPixmap pixmap = pixmapForIcon(icon, this); - d->pixmapLabel->setPixmap(pixmap); - d->pixmapLabel->setVisible(!pixmap.isNull()); -} - -bool CheckableMessageBox::isChecked() const -{ - return d->checkBox->isChecked(); -} - -void CheckableMessageBox::setChecked(bool s) -{ - d->checkBox->setChecked(s); -} - -QString CheckableMessageBox::checkBoxText() const -{ - return d->checkBox->text(); -} - -void CheckableMessageBox::setCheckBoxText(const QString &t) -{ - d->checkBox->setText(t); -} - -bool CheckableMessageBox::isCheckBoxVisible() const -{ - return d->checkBox->isVisible(); -} - -void CheckableMessageBox::setCheckBoxVisible(bool v) -{ - d->checkBox->setVisible(v); -} - -QString CheckableMessageBox::detailedText() const -{ - return d->detailsText->toPlainText(); -} - -void CheckableMessageBox::setDetailedText(const QString &text) -{ - d->detailsText->setText(text); - if (!text.isEmpty()) - d->detailsButton->setVisible(true); -} - -QDialogButtonBox::StandardButtons CheckableMessageBox::standardButtons() const -{ - return d->buttonBox->standardButtons(); -} - -void CheckableMessageBox::setStandardButtons(QDialogButtonBox::StandardButtons s) -{ - d->buttonBox->setStandardButtons(s); -} - -QPushButton *CheckableMessageBox::button(QDialogButtonBox::StandardButton b) const -{ - return d->buttonBox->button(b); -} - -QPushButton *CheckableMessageBox::addButton(const QString &text, QDialogButtonBox::ButtonRole role) -{ - return d->buttonBox->addButton(text, role); -} - -QDialogButtonBox::StandardButton CheckableMessageBox::defaultButton() const -{ - const QList buttons = d->buttonBox->buttons(); - for (QAbstractButton *b : buttons) - if (auto *pb = qobject_cast(b)) - if (pb->isDefault()) - return d->buttonBox->standardButton(pb); - return QDialogButtonBox::NoButton; -} - -void CheckableMessageBox::setDefaultButton(QDialogButtonBox::StandardButton s) -{ - if (QPushButton *b = d->buttonBox->button(s)) { - b->setDefault(true); - b->setFocus(); } -} - -QDialogButtonBox::StandardButton -CheckableMessageBox::question(QWidget *parent, - const QString &title, - const QString &question, - const QString &checkBoxText, - bool *checkBoxSetting, - QDialogButtonBox::StandardButtons buttons, - QDialogButtonBox::StandardButton defaultButton) -{ - CheckableMessageBox mb(parent); - mb.setWindowTitle(title); - mb.setIcon(QMessageBox::Question); - mb.setText(question); - mb.setCheckBoxText(checkBoxText); - mb.setChecked(*checkBoxSetting); - mb.setStandardButtons(buttons); - mb.setDefaultButton(defaultButton); - mb.exec(); - *checkBoxSetting = mb.isChecked(); - return mb.clickedStandardButton(); -} -QDialogButtonBox::StandardButton -CheckableMessageBox::information(QWidget *parent, - const QString &title, - const QString &text, - const QString &checkBoxText, - bool *checkBoxSetting, - QDialogButtonBox::StandardButtons buttons, - QDialogButtonBox::StandardButton defaultButton) -{ - CheckableMessageBox mb(parent); - mb.setWindowTitle(title); - mb.setIcon(QMessageBox::Information); - mb.setText(text); - mb.setCheckBoxText(checkBoxText); - mb.setChecked(*checkBoxSetting); - mb.setStandardButtons(buttons); - mb.setDefaultButton(defaultButton); - mb.exec(); - *checkBoxSetting = mb.isChecked(); - return mb.clickedStandardButton(); -} - -QMessageBox::StandardButton CheckableMessageBox::dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton db) -{ - return static_cast(int(db)); -} - -bool CheckableMessageBox::shouldAskAgain(QSettings *settings, const QString &settingsSubKey) -{ - if (QTC_GUARD(settings)) { - settings->beginGroup(QLatin1String(kDoNotAskAgainKey)); - bool shouldNotAsk = settings->value(settingsSubKey, false).toBool(); - settings->endGroup(); - if (shouldNotAsk) - return false; + if (decider) { + if (!CheckableMessageBox::shouldAskAgain(*decider)) + return acceptButton; + + msgBox.setCheckBox(new QCheckBox); + + std::visit( + [&msgBox](auto &&decider) { + using T = std::decay_t; + msgBox.checkBox()->setText(decider.text); + if constexpr (std::is_same_v) { + msgBox.checkBox()->setChecked(decider.value); + } else if constexpr (std::is_same_v) { + msgBox.checkBox()->setChecked( + decider.settings->value(decider.settingsSubKey, false).toBool()); + } else if constexpr (std::is_same_v) { + msgBox.checkBox()->setChecked(decider.aspect.value()); + } + }, + *decider); } - return true; -} -enum DoNotAskAgainType{Question, Information}; + msgBox.setStandardButtons(buttons); + msgBox.setDefaultButton(defaultButton); + for (auto it = buttonTextOverrides.constBegin(); it != buttonTextOverrides.constEnd(); ++it) + msgBox.button(it.key())->setText(it.value()); + msgBox.exec(); + + QMessageBox::StandardButton clickedBtn = msgBox.standardButton(msgBox.clickedButton()); + + if (decider && msgBox.checkBox()->isChecked() + && (acceptButton == QMessageBox::NoButton || clickedBtn == acceptButton)) + CheckableMessageBox::doNotAskAgain(*decider); + return clickedBtn; +} + +QMessageBox::StandardButton CheckableMessageBox::question( + QWidget *parent, + const QString &title, + const QString &question, + std::optional decider, + QMessageBox::StandardButtons buttons, + QMessageBox::StandardButton defaultButton, + QMessageBox::StandardButton acceptButton, + QMap buttonTextOverrides) +{ + return exec(parent, + QMessageBox::Question, + title, + question, + decider, + buttons, + defaultButton, + acceptButton, + buttonTextOverrides); +} + +QMessageBox::StandardButton CheckableMessageBox::information( + QWidget *parent, + const QString &title, + const QString &text, + std::optional decider, + QMessageBox::StandardButtons buttons, + QMessageBox::StandardButton defaultButton, + QMap buttonTextOverrides) +{ + return exec(parent, + QMessageBox::Information, + title, + text, + decider, + buttons, + defaultButton, + QMessageBox::NoButton, + buttonTextOverrides); +} + +void CheckableMessageBox::doNotAskAgain(Decider &decider) +{ + std::visit( + [](auto &&decider) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + decider.value = true; + } else if constexpr (std::is_same_v) { + decider.settings->beginGroup(QLatin1String(kDoNotAskAgainKey)); + decider.settings->setValue(decider.settingsSubKey, true); + decider.settings->endGroup(); + } else if constexpr (std::is_same_v) { + decider.aspect.setValue(true); + } + }, + decider); +} + +bool CheckableMessageBox::shouldAskAgain(const Decider &decider) +{ + bool result = std::visit( + [](auto &&decider) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return !decider.value; + } else if constexpr (std::is_same_v) { + decider.settings->beginGroup(QLatin1String(kDoNotAskAgainKey)); + bool shouldNotAsk = decider.settings->value(decider.settingsSubKey, false).toBool(); + decider.settings->endGroup(); + return !shouldNotAsk; + } else if constexpr (std::is_same_v) { + return !decider.aspect.value(); + } + }, + decider); -void initDoNotAskAgainMessageBox(CheckableMessageBox &messageBox, const QString &title, - const QString &text, QDialogButtonBox::StandardButtons buttons, - QDialogButtonBox::StandardButton defaultButton, - DoNotAskAgainType type) -{ - messageBox.setWindowTitle(title); - messageBox.setIcon(type == Information ? QMessageBox::Information : QMessageBox::Question); - messageBox.setText(text); - messageBox.setCheckBoxVisible(true); - messageBox.setCheckBoxText(type == Information ? CheckableMessageBox::msgDoNotShowAgain() - : CheckableMessageBox::msgDoNotAskAgain()); - messageBox.setChecked(false); - messageBox.setStandardButtons(buttons); - messageBox.setDefaultButton(defaultButton); + return result; } -void CheckableMessageBox::doNotAskAgain(QSettings *settings, const QString &settingsSubKey) +bool CheckableMessageBox::shouldAskAgain(QSettings *settings, const QString &key) { - if (!settings) - return; - - settings->beginGroup(QLatin1String(kDoNotAskAgainKey)); - settings->setValue(settingsSubKey, true); - settings->endGroup(); + return shouldAskAgain(make_decider(settings, key)); } -/*! - Shows a message box with given \a title and \a text, and a \gui {Do not ask again} check box. - If the user checks the check box and accepts the dialog with the \a acceptButton, - further invocations of this function with the same \a settings and \a settingsSubKey will not - show the dialog, but instantly return \a acceptButton. - - Returns the clicked button, or QDialogButtonBox::NoButton if the user rejects the dialog - with the escape key, or \a acceptButton if the dialog is suppressed. -*/ -QDialogButtonBox::StandardButton -CheckableMessageBox::doNotAskAgainQuestion(QWidget *parent, const QString &title, - const QString &text, QSettings *settings, - const QString &settingsSubKey, - QDialogButtonBox::StandardButtons buttons, - QDialogButtonBox::StandardButton defaultButton, - QDialogButtonBox::StandardButton acceptButton) - -{ - if (!shouldAskAgain(settings, settingsSubKey)) - return acceptButton; - - CheckableMessageBox messageBox(parent); - initDoNotAskAgainMessageBox(messageBox, title, text, buttons, defaultButton, Question); - messageBox.exec(); - if (messageBox.isChecked() && (messageBox.clickedStandardButton() == acceptButton)) - doNotAskAgain(settings, settingsSubKey); - - return messageBox.clickedStandardButton(); -} - -/*! - Shows a message box with given \a title and \a text, and a \gui {Do not show again} check box. - If the user checks the check box and quits the dialog, further invocations of this - function with the same \a settings and \a settingsSubKey will not show the dialog, but instantly return. - - Returns the clicked button, or QDialogButtonBox::NoButton if the user rejects the dialog - with the escape key, or \a defaultButton if the dialog is suppressed. -*/ -QDialogButtonBox::StandardButton -CheckableMessageBox::doNotShowAgainInformation(QWidget *parent, const QString &title, - const QString &text, QSettings *settings, - const QString &settingsSubKey, - QDialogButtonBox::StandardButtons buttons, - QDialogButtonBox::StandardButton defaultButton) - +void CheckableMessageBox::doNotAskAgain(QSettings *settings, const QString &key) { - if (!shouldAskAgain(settings, settingsSubKey)) - return defaultButton; - - CheckableMessageBox messageBox(parent); - initDoNotAskAgainMessageBox(messageBox, title, text, buttons, defaultButton, Information); - messageBox.exec(); - if (messageBox.isChecked()) - doNotAskAgain(settings, settingsSubKey); - - return messageBox.clickedStandardButton(); + Decider decider = make_decider(settings, key); + return doNotAskAgain(decider); } /*! diff --git a/src/libs/utils/checkablemessagebox.h b/src/libs/utils/checkablemessagebox.h index eb80e15eac..e6988fc8e9 100644 --- a/src/libs/utils/checkablemessagebox.h +++ b/src/libs/utils/checkablemessagebox.h @@ -5,7 +5,8 @@ #include "utils_global.h" -#include +#include "aspects.h" + #include QT_BEGIN_NAMESPACE @@ -16,100 +17,206 @@ namespace Utils { class CheckableMessageBoxPrivate; -class QTCREATOR_UTILS_EXPORT CheckableMessageBox : public QDialog +class QTCREATOR_UTILS_EXPORT CheckableMessageBox { - Q_OBJECT - Q_PROPERTY(QString text READ text WRITE setText) - Q_PROPERTY(QMessageBox::Icon icon READ icon WRITE setIcon) - Q_PROPERTY(bool isChecked READ isChecked WRITE setChecked) - Q_PROPERTY(QString checkBoxText READ checkBoxText WRITE setCheckBoxText) - Q_PROPERTY(QDialogButtonBox::StandardButtons buttons READ standardButtons WRITE setStandardButtons) - Q_PROPERTY(QDialogButtonBox::StandardButton defaultButton READ defaultButton WRITE setDefaultButton) - public: - explicit CheckableMessageBox(QWidget *parent); - ~CheckableMessageBox() override; - - static QDialogButtonBox::StandardButton - question(QWidget *parent, - const QString &title, - const QString &question, - const QString &checkBoxText, - bool *checkBoxSetting, - QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Yes|QDialogButtonBox::No, - QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::No); - - static QDialogButtonBox::StandardButton - information(QWidget *parent, - const QString &title, - const QString &text, - const QString &checkBoxText, - bool *checkBoxSetting, - QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Ok, - QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::NoButton); - - static QDialogButtonBox::StandardButton - doNotAskAgainQuestion(QWidget *parent, - const QString &title, - const QString &text, - QSettings *settings, - const QString &settingsSubKey, - QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Yes|QDialogButtonBox::No, - QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::No, - QDialogButtonBox::StandardButton acceptButton = QDialogButtonBox::Yes); - - static QDialogButtonBox::StandardButton - doNotShowAgainInformation(QWidget *parent, - const QString &title, - const QString &text, - QSettings *settings, - const QString &settingsSubKey, - QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Ok, - QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::NoButton); - - QString text() const; - void setText(const QString &); - - bool isChecked() const; - void setChecked(bool s); - - QString checkBoxText() const; - void setCheckBoxText(const QString &); - - bool isCheckBoxVisible() const; - void setCheckBoxVisible(bool); - - QString detailedText() const; - void setDetailedText(const QString &text); - - QDialogButtonBox::StandardButtons standardButtons() const; - void setStandardButtons(QDialogButtonBox::StandardButtons s); - QPushButton *button(QDialogButtonBox::StandardButton b) const; - QPushButton *addButton(const QString &text, QDialogButtonBox::ButtonRole role); - - QDialogButtonBox::StandardButton defaultButton() const; - void setDefaultButton(QDialogButtonBox::StandardButton s); - - QMessageBox::Icon icon() const; - void setIcon(QMessageBox::Icon icon); - - // Query the result - QAbstractButton *clickedButton() const; - QDialogButtonBox::StandardButton clickedStandardButton() const; + struct BoolDecision + { + QString text; + bool &value; + }; + + struct SettingsDecision + { + QString text; + QSettings *settings; + QString settingsSubKey; + }; + + struct AspectDecision + { + QString text; + BoolAspect &aspect; + }; + + using Decider = std::variant; + + static Decider make_decider(QSettings *settings, + const QString &settingsSubKey, + const QString &text = msgDoNotAskAgain()) + { + return Decider{SettingsDecision{text, settings, settingsSubKey}}; + } + + static Decider make_decider(bool &value, const QString &text = msgDoNotAskAgain()) + { + return Decider{BoolDecision{text, value}}; + } + + static Decider make_decider(BoolAspect &aspect, const QString &text = msgDoNotAskAgain()) + { + return Decider{AspectDecision{text, aspect}}; + } + + static QMessageBox::StandardButton question( + QWidget *parent, + const QString &title, + const QString &question, + std::optional decider = std::nullopt, + QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No, + QMessageBox::StandardButton defaultButton = QMessageBox::No, + QMessageBox::StandardButton acceptButton = QMessageBox::Yes, + QMap buttonTextOverrides = {}); + + static QMessageBox::StandardButton question( + QWidget *parent, + const QString &title, + const QString &question, + bool &value, + QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No, + QMessageBox::StandardButton defaultButton = QMessageBox::No, + QMessageBox::StandardButton acceptButton = QMessageBox::Yes, + QMap buttonTextOverrides = {}, + const QString &text = msgDoNotAskAgain()) + { + Decider decider = make_decider(value, text); + return CheckableMessageBox::question(parent, + title, + question, + decider, + buttons, + defaultButton, + acceptButton, + buttonTextOverrides); + } + + static QMessageBox::StandardButton question( + QWidget *parent, + const QString &title, + const QString &question, + QSettings *settings, + const QString &settingsSubKey, + QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No, + QMessageBox::StandardButton defaultButton = QMessageBox::No, + QMessageBox::StandardButton acceptButton = QMessageBox::Yes, + QMap buttonTextOverrides = {}, + const QString &text = msgDoNotAskAgain()) + { + Decider decider = make_decider(settings, settingsSubKey, text); + return CheckableMessageBox::question(parent, + title, + question, + decider, + buttons, + defaultButton, + acceptButton, + buttonTextOverrides); + } + + static QMessageBox::StandardButton question( + QWidget *parent, + const QString &title, + const QString &question, + BoolAspect &aspect, + QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No, + QMessageBox::StandardButton defaultButton = QMessageBox::No, + QMessageBox::StandardButton acceptButton = QMessageBox::Yes, + QMap buttonTextOverrides = {}, + const QString &text = msgDoNotAskAgain()) + { + Decider decider = make_decider(aspect, text); + return CheckableMessageBox::question(parent, + title, + question, + decider, + buttons, + defaultButton, + acceptButton, + buttonTextOverrides); + } + + static QMessageBox::StandardButton information( + QWidget *parent, + const QString &title, + const QString &text, + std::optional decider = std::nullopt, + QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton, + QMap buttonTextOverrides = {}); + + static QMessageBox::StandardButton information( + QWidget *parent, + const QString &title, + const QString &information, + bool &value, + QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton, + QMap buttonTextOverrides = {}, + const QString &text = msgDoNotAskAgain()) + { + Decider decider = make_decider(value, text); + return CheckableMessageBox::information(parent, + title, + information, + decider, + buttons, + defaultButton, + buttonTextOverrides); + } + + static QMessageBox::StandardButton information( + QWidget *parent, + const QString &title, + const QString &information, + QSettings *settings, + const QString &settingsSubKey, + QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton, + QMap buttonTextOverrides = {}, + const QString &text = msgDoNotAskAgain()) + { + Decider decider = make_decider(settings, settingsSubKey, text); + return CheckableMessageBox::information(parent, + title, + information, + decider, + buttons, + defaultButton, + buttonTextOverrides); + } + + static QMessageBox::StandardButton information( + QWidget *parent, + const QString &title, + const QString &information, + BoolAspect &aspect, + QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton, + QMap buttonTextOverrides = {}, + const QString &text = msgDoNotAskAgain()) + { + Decider decider = make_decider(aspect, text); + return CheckableMessageBox::information(parent, + title, + information, + decider, + buttons, + defaultButton, + buttonTextOverrides); + } // check and set "ask again" status - static bool shouldAskAgain(QSettings *settings, const QString &settingsSubKey); - static void doNotAskAgain(QSettings *settings, const QString &settingsSubKey); + static bool shouldAskAgain(const Decider &decider); + static void doNotAskAgain(Decider &decider); + + static bool shouldAskAgain(QSettings *settings, const QString &key); + static void doNotAskAgain(QSettings *settings, const QString &key); // Conversion convenience - static QMessageBox::StandardButton dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton); static void resetAllDoNotAskAgainQuestions(QSettings *settings); static bool hasSuppressedQuestions(QSettings *settings); static QString msgDoNotAskAgain(); static QString msgDoNotShowAgain(); - -private: - CheckableMessageBoxPrivate *d; }; } // namespace Utils -- cgit v1.2.3 From a468bc2f029e50c0516eb712d09562c80ee1bdb1 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Wed, 17 May 2023 11:31:49 +0200 Subject: Fix Qbs build Amends f84199f8b70bb03b66a0dbac3ff4dcdb56094d20 and reverts 796cfceb3a27337e2f613624a8c223761fdd44b8. Change-Id: I7eb686c012bd99cddf36aa16219e3f33de2b15b2 Reviewed-by: Christian Kandeler Reviewed-by: Eike Ziller --- src/libs/utils/utils.qbs | 1 + 1 file changed, 1 insertion(+) (limited to 'src/libs') diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index d1c8d0e95e..18dcf1675c 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -471,6 +471,7 @@ Project { Export { Depends { name: "Qt"; submodules: ["concurrent", "widgets" ] } + Depends { name: "Tasking" } cpp.includePaths: base.concat("mimetypes2") } } -- cgit v1.2.3 From c7bf77ae72d27f6bada34eea184ca283a4a113f9 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Wed, 17 May 2023 12:04:09 +0200 Subject: CheckableMessageBox: Make semantics of bool value clearer Change-Id: I06a43ab986e6f028cf07ea5e9700c831a591cbf2 Reviewed-by: Marcus Tillmanns --- src/libs/utils/checkablemessagebox.cpp | 6 +++--- src/libs/utils/checkablemessagebox.h | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/checkablemessagebox.cpp b/src/libs/utils/checkablemessagebox.cpp index 92b111ed7c..29951fd9c4 100644 --- a/src/libs/utils/checkablemessagebox.cpp +++ b/src/libs/utils/checkablemessagebox.cpp @@ -70,7 +70,7 @@ static QMessageBox::StandardButton exec( using T = std::decay_t; msgBox.checkBox()->setText(decider.text); if constexpr (std::is_same_v) { - msgBox.checkBox()->setChecked(decider.value); + msgBox.checkBox()->setChecked(decider.doNotAskAgain); } else if constexpr (std::is_same_v) { msgBox.checkBox()->setChecked( decider.settings->value(decider.settingsSubKey, false).toBool()); @@ -142,7 +142,7 @@ void CheckableMessageBox::doNotAskAgain(Decider &decider) [](auto &&decider) { using T = std::decay_t; if constexpr (std::is_same_v) { - decider.value = true; + decider.doNotAskAgain = true; } else if constexpr (std::is_same_v) { decider.settings->beginGroup(QLatin1String(kDoNotAskAgainKey)); decider.settings->setValue(decider.settingsSubKey, true); @@ -160,7 +160,7 @@ bool CheckableMessageBox::shouldAskAgain(const Decider &decider) [](auto &&decider) { using T = std::decay_t; if constexpr (std::is_same_v) { - return !decider.value; + return !decider.doNotAskAgain; } else if constexpr (std::is_same_v) { decider.settings->beginGroup(QLatin1String(kDoNotAskAgainKey)); bool shouldNotAsk = decider.settings->value(decider.settingsSubKey, false).toBool(); diff --git a/src/libs/utils/checkablemessagebox.h b/src/libs/utils/checkablemessagebox.h index e6988fc8e9..1f4c84e1a7 100644 --- a/src/libs/utils/checkablemessagebox.h +++ b/src/libs/utils/checkablemessagebox.h @@ -23,7 +23,7 @@ public: struct BoolDecision { QString text; - bool &value; + bool &doNotAskAgain; }; struct SettingsDecision @@ -48,9 +48,9 @@ public: return Decider{SettingsDecision{text, settings, settingsSubKey}}; } - static Decider make_decider(bool &value, const QString &text = msgDoNotAskAgain()) + static Decider make_decider(bool &doNotAskAgain, const QString &text = msgDoNotAskAgain()) { - return Decider{BoolDecision{text, value}}; + return Decider{BoolDecision{text, doNotAskAgain}}; } static Decider make_decider(BoolAspect &aspect, const QString &text = msgDoNotAskAgain()) -- cgit v1.2.3 From 2cd2324963b7f0ea23e0e6aad5cd04ad42cda049 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Wed, 17 May 2023 13:00:47 +0200 Subject: CheckableMessageBox: Simplify checkbox setup If the dialog is shown, the checkbox must be unchecked, because otherwise the dialog would not have been shown. Change-Id: I34e8034975baef710997e0cdb3c7d2f8b0c94cd2 Reviewed-by: Marcus Tillmanns --- src/libs/utils/checkablemessagebox.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/checkablemessagebox.cpp b/src/libs/utils/checkablemessagebox.cpp index 29951fd9c4..bf566e53a8 100644 --- a/src/libs/utils/checkablemessagebox.cpp +++ b/src/libs/utils/checkablemessagebox.cpp @@ -64,19 +64,11 @@ static QMessageBox::StandardButton exec( return acceptButton; msgBox.setCheckBox(new QCheckBox); + msgBox.checkBox()->setChecked(false); std::visit( [&msgBox](auto &&decider) { - using T = std::decay_t; msgBox.checkBox()->setText(decider.text); - if constexpr (std::is_same_v) { - msgBox.checkBox()->setChecked(decider.doNotAskAgain); - } else if constexpr (std::is_same_v) { - msgBox.checkBox()->setChecked( - decider.settings->value(decider.settingsSubKey, false).toBool()); - } else if constexpr (std::is_same_v) { - msgBox.checkBox()->setChecked(decider.aspect.value()); - } }, *decider); } -- cgit v1.2.3 From b2e30e7ef86595c537062cb8261a0735b389949c Mon Sep 17 00:00:00 2001 From: David Schulz Date: Tue, 16 May 2023 11:17:27 +0200 Subject: Utils: add Position::fromCursor with tests Change-Id: I1cd989eaf7e75bc04f171989f9f9fe932402abef Reviewed-by: Qt CI Bot Reviewed-by: Jarek Kobus --- src/libs/utils/textutils.cpp | 5 +++++ src/libs/utils/textutils.h | 1 + 2 files changed, 6 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/textutils.cpp b/src/libs/utils/textutils.cpp index a57e531059..8b17cc7e01 100644 --- a/src/libs/utils/textutils.cpp +++ b/src/libs/utils/textutils.cpp @@ -59,6 +59,11 @@ Position Position::fromPositionInDocument(const QTextDocument *document, int pos return {}; } +Position Position::fromCursor(const QTextCursor &c) +{ + return c.isNull() ? Position{} : Position{c.blockNumber() + 1, c.positionInBlock()}; +} + int Range::length(const QString &text) const { if (end.line < begin.line) diff --git a/src/libs/utils/textutils.h b/src/libs/utils/textutils.h index df1eb73d8e..96b7afbeb8 100644 --- a/src/libs/utils/textutils.h +++ b/src/libs/utils/textutils.h @@ -32,6 +32,7 @@ public: static Position fromFileName(QStringView fileName, int &postfixPos); static Position fromPositionInDocument(const QTextDocument *document, int pos); + static Position fromCursor(const QTextCursor &cursor); }; class QTCREATOR_UTILS_EXPORT Range -- cgit v1.2.3 From 5abb1959c5883ddd74c0ddbba477a4e22881b2a8 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Sun, 14 May 2023 16:50:15 +0200 Subject: ScopedTimer: Make it possible to provide optional message string When optional message argument is provided, both macros print the message instead of __FILE__ and __LINE__ info. It helps to ease the identification of the exact place in code when many macros are added - custom message may be more informative that the file and line location. Change-Id: I7a3ccbdaca2858b44dcbd51a8f9330160dab73e9 Reviewed-by: Qt CI Bot Reviewed-by: hjk --- src/libs/utils/scopedtimer.cpp | 41 ++++++++++++++++++++++------------------- src/libs/utils/scopedtimer.h | 24 +++++++++++++++++++----- 2 files changed, 41 insertions(+), 24 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/scopedtimer.cpp b/src/libs/utils/scopedtimer.cpp index 97b8beffda..6a4522e28d 100644 --- a/src/libs/utils/scopedtimer.cpp +++ b/src/libs/utils/scopedtimer.cpp @@ -3,7 +3,6 @@ #include "scopedtimer.h" -#include #include #include @@ -15,25 +14,31 @@ static QString currentTime() { return QTime::currentTime().toString(Qt::ISODateW using namespace std::chrono; +static const char s_scoped[] = "SCOPED TIMER"; +static const char s_scopedCumulative[] = "STATIC SCOPED TIMER"; + class ScopedTimerPrivate { public: - const char *m_fileName = nullptr; - const int m_line = 0; - std::atomic *m_cumulative = nullptr; + QString header() const { + const char *scopedTimerType = m_data.m_cumulative ? s_scopedCumulative : s_scoped; + const QString prefix = QLatin1String(scopedTimerType) + " [" + currentTime() + "] "; + const QString infix = m_data.m_message.isEmpty() + ? QLatin1String(m_data.m_fileName) + ':' + QString::number(m_data.m_line) + : m_data.m_message; + return prefix + infix + ' '; + } + + const ScopedTimerData m_data; const time_point m_start = system_clock::now(); }; -static const char s_scoped[] = "SCOPED TIMER"; -static const char s_scopedCumulative[] = "STATIC SCOPED TIMER"; - -ScopedTimer::ScopedTimer(const char *fileName, int line, std::atomic *cumulative) - : d(new ScopedTimerPrivate{fileName, line, cumulative}) +ScopedTimer::ScopedTimer(const ScopedTimerData &data) + : d(new ScopedTimerPrivate{data}) { - if (d->m_cumulative) + if (d->m_data.m_cumulative) return; - qDebug().noquote().nospace() << s_scoped << " [" << currentTime() << "] in " << d->m_fileName - << ':' << d->m_line << " started"; + qDebug().noquote().nospace() << d->header() << "started"; } static int64_t toMs(int64_t ns) { return ns / 1000000; } @@ -42,8 +47,8 @@ ScopedTimer::~ScopedTimer() { const auto elapsed = duration_cast(system_clock::now() - d->m_start); QString suffix; - if (d->m_cumulative) { - const int64_t nsOld = d->m_cumulative->fetch_add(elapsed.count()); + if (d->m_data.m_cumulative) { + const int64_t nsOld = d->m_data.m_cumulative->fetch_add(elapsed.count()); const int64_t msOld = toMs(nsOld); const int64_t nsNew = nsOld + elapsed.count(); const int64_t msNew = toMs(nsNew); @@ -52,13 +57,11 @@ ScopedTimer::~ScopedTimer() if (nsOld != 0 && msOld / 10 == msNew / 10) return; - suffix = " cumulative timeout: " + QString::number(msNew) + "ms"; + suffix = "cumulative timeout: " + QString::number(msNew) + "ms"; } else { - suffix = " stopped with timeout: " + QString::number(toMs(elapsed.count())) + "ms"; + suffix = "stopped with timeout: " + QString::number(toMs(elapsed.count())) + "ms"; } - const char *header = d->m_cumulative ? s_scopedCumulative : s_scoped; - qDebug().noquote().nospace() << header << " [" << currentTime() << "] in " << d->m_fileName - << ':' << d->m_line << suffix; + qDebug().noquote().nospace() << d->header() << suffix; } } // namespace Utils diff --git a/src/libs/utils/scopedtimer.h b/src/libs/utils/scopedtimer.h index e6ec42e6f4..d66ef15841 100644 --- a/src/libs/utils/scopedtimer.h +++ b/src/libs/utils/scopedtimer.h @@ -5,6 +5,8 @@ #include "utils_global.h" +#include + #include #include @@ -12,10 +14,19 @@ namespace Utils { class ScopedTimerPrivate; +class QTCREATOR_UTILS_EXPORT ScopedTimerData +{ +public: + QString m_message; + const char *m_fileName = nullptr; + int m_line = 0; + std::atomic *m_cumulative = nullptr; +}; + class QTCREATOR_UTILS_EXPORT ScopedTimer { public: - ScopedTimer(const char *fileName, int line, std::atomic *cumulative = nullptr); + ScopedTimer(const ScopedTimerData &data); ~ScopedTimer(); private: @@ -24,15 +35,18 @@ private: } // Utils +// The "message" argument of QTC_SCOPED_TIMER() and QTC_STATIC_SCOPED_TIMER() macros is optional. +// When provided, it should evaluate to QString. + #define QTC_CONCAT_HELPER(x, y) x ## y #define QTC_CONCAT(x, y) QTC_CONCAT_HELPER(x, y) -#define QTC_SCOPED_TIMER() ::Utils::ScopedTimer QTC_CONCAT(_qtc_scoped_timer_, __LINE__)\ -(__FILE__, __LINE__) +#define QTC_SCOPED_TIMER(message) ::Utils::ScopedTimer QTC_CONCAT(_qtc_scoped_timer_, __LINE__)\ +({{message}, __FILE__, __LINE__}) // The macro below expands as follows (in one line): // static std::atomic _qtc_static_scoped_timer___LINE__ = 0; // ScopedTimer _qtc_scoped_timer___LINE__(__FILE__, __LINE__, &_qtc_static_scoped_timer___LINE__) -#define QTC_STATIC_SCOPED_TIMER() static std::atomic \ +#define QTC_STATIC_SCOPED_TIMER(message) static std::atomic \ QTC_CONCAT(_qtc_static_scoped_timer_, __LINE__) = 0; \ ::Utils::ScopedTimer QTC_CONCAT(_qtc_scoped_timer_, __LINE__)\ -(__FILE__, __LINE__, &QTC_CONCAT(_qtc_static_scoped_timer_, __LINE__)) +({{message}, __FILE__, __LINE__, &QTC_CONCAT(_qtc_static_scoped_timer_, __LINE__)}) -- cgit v1.2.3 From 9c78ef983a1203515a7552784a119f1ba3df4a71 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Mon, 15 May 2023 10:52:45 +0200 Subject: TaskTree: Add runBlocking() helpers To be used in non-main threads and in autotests. Change-Id: If37be854f65c9cfe94eb781a28dc8db4365809e1 Reviewed-by: Reviewed-by: hjk Reviewed-by: Qt CI Bot --- src/libs/solutions/tasking/tasktree.cpp | 51 +++++++++++++++++++++++++++++++++ src/libs/solutions/tasking/tasktree.h | 11 +++++++ 2 files changed, 62 insertions(+) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index 4a66d7f674..58235cd814 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -3,7 +3,10 @@ #include "tasktree.h" +#include +#include #include +#include namespace Tasking { @@ -1444,6 +1447,54 @@ bool TaskTree::isRunning() const return d->m_root && d->m_root->isRunning(); } +bool TaskTree::runBlocking(const QFuture &future, int timeoutMs) +{ + if (isRunning() || future.isCanceled()) + return false; + + bool ok = false; + QEventLoop loop; + + const auto finalize = [&loop, &ok](bool success) { + ok = success; + // Otherwise, the tasks from inside the running tree that were deleteLater() + // will be leaked. Refer to the QObject::deleteLater() docs. + QMetaObject::invokeMethod(&loop, [&loop] { loop.quit(); }, Qt::QueuedConnection); + }; + + QFutureWatcher watcher; + connect(&watcher, &QFutureWatcherBase::canceled, this, &TaskTree::stop); + watcher.setFuture(future); + + connect(this, &TaskTree::done, &loop, [finalize] { finalize(true); }); + connect(this, &TaskTree::errorOccurred, &loop, [finalize] { finalize(false); }); + start(); + if (!isRunning()) + return ok; + + QTimer timer; + if (timeoutMs) { + timer.setSingleShot(true); + timer.setInterval(timeoutMs); + connect(&timer, &QTimer::timeout, this, &TaskTree::stop); + timer.start(); + } + + loop.exec(QEventLoop::ExcludeUserInputEvents); + if (!ok) { + auto nonConstFuture = future; + nonConstFuture.cancel(); + } + return ok; +} + +bool TaskTree::runBlocking(int timeoutMs) +{ + QPromise dummy; + dummy.start(); + return runBlocking(dummy.future(), timeoutMs); +} + int TaskTree::taskCount() const { return d->m_root ? d->m_root->taskCount() : 0; diff --git a/src/libs/solutions/tasking/tasktree.h b/src/libs/solutions/tasking/tasktree.h index bbee1a3fe8..462eb5bd2e 100644 --- a/src/libs/solutions/tasking/tasktree.h +++ b/src/libs/solutions/tasking/tasktree.h @@ -9,6 +9,11 @@ #include #include +QT_BEGIN_NAMESPACE +template +class QFuture; +QT_END_NAMESPACE + namespace Tasking { class ExecutionContextActivator; @@ -369,6 +374,12 @@ public: void stop(); bool isRunning() const; + // Helper methods. They execute a local event loop with ExcludeUserInputEvents. + // The passed future is used for listening to the cancel event. + // Don't use it in main thread. To be used in non-main threads or in auto tests. + bool runBlocking(const QFuture &future, int timeoutMs = 0); + bool runBlocking(int timeoutMs = 0); + int taskCount() const; int progressMaximum() const { return taskCount(); } int progressValue() const; // all finished / skipped / stopped tasks, groups itself excluded -- cgit v1.2.3 From 2b174a763faf458a347504896a75db5f5440e7a7 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Mon, 15 May 2023 17:54:23 +0200 Subject: FileStreamer: Reuse TaskTree::runBlocking() Reuse it also in FileSystemAccessTest. Change-Id: I6ce1c926bd5d3a617b8badb0905e7b2fd58b4745 Reviewed-by: Marcus Tillmanns Reviewed-by: Qt CI Bot Reviewed-by: --- src/libs/utils/filestreamer.cpp | 33 ++++----------------------------- 1 file changed, 4 insertions(+), 29 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/filestreamer.cpp b/src/libs/utils/filestreamer.cpp index 6d4a768336..55e984c2fb 100644 --- a/src/libs/utils/filestreamer.cpp +++ b/src/libs/utils/filestreamer.cpp @@ -333,8 +333,8 @@ static Group interDeviceTransferTask(const FilePath &source, const FilePath &des storage->writer, &FileStreamWriter::write); }; const auto finalizeReader = [=](const FileStreamReader &) { - QTC_CHECK(storage->writer != nullptr); - storage->writer->closeWriteChannel(); + if (storage->writer) // writer may be deleted before the reader on TaskTree::stop(). + storage->writer->closeWriteChannel(); }; const auto setupWriter = [=](FileStreamWriter &writer) { writer.setFilePath(destination); @@ -370,33 +370,8 @@ static void transfer(QPromise &promise, const FilePath &source, const File if (promise.isCanceled()) return; - std::unique_ptr taskTree(new TaskTree(transferTask(source, destination))); - - QEventLoop eventLoop; - bool finalized = false; - const auto finalize = [loop = &eventLoop, &taskTree, &finalized](int exitCode) { - if (finalized) // finalize only once - return; - finalized = true; - // Give the tree a chance to delete later all tasks that have finished and caused - // emission of tree's done or errorOccurred signal. - // TODO: maybe these signals should be sent queued already? - QMetaObject::invokeMethod(loop, [loop, &taskTree, exitCode] { - taskTree.reset(); - loop->exit(exitCode); - }, Qt::QueuedConnection); - }; - QTimer timer; - timer.setInterval(50); - QObject::connect(&timer, &QTimer::timeout, [&promise, finalize] { - if (promise.isCanceled()) - finalize(2); - }); - QObject::connect(taskTree.get(), &TaskTree::done, &eventLoop, [=] { finalize(0); }); - QObject::connect(taskTree.get(), &TaskTree::errorOccurred, &eventLoop, [=] { finalize(1); }); - taskTree->start(); - timer.start(); - if (eventLoop.exec()) + TaskTree taskTree(transferTask(source, destination)); + if (!taskTree.runBlocking(promise.future())) promise.future().cancel(); } -- cgit v1.2.3 From 376c1cf2466a583ecc9187f8f95b613d9ed96f9c Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Mon, 15 May 2023 17:54:23 +0200 Subject: FileStreamWriter: Add some comments into d'tor When d'tor of the parent Async runs, it busy waits for the WriteBuffer's thread to finish, and afterwards QObject's d'tor deletes the child WriteBuffer object. Change-Id: Ifc696b3e56735e697d8c54c2471f89e323d3c0d1 Reviewed-by: Reviewed-by: hjk Reviewed-by: Qt CI Bot --- src/libs/utils/filestreamer.cpp | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/filestreamer.cpp b/src/libs/utils/filestreamer.cpp index 55e984c2fb..ababb94cf6 100644 --- a/src/libs/utils/filestreamer.cpp +++ b/src/libs/utils/filestreamer.cpp @@ -223,6 +223,11 @@ public: ~FileStreamWriter() { // TODO: should d'tor remove unfinished file write leftovers? if (m_writeBuffer && isBuffered()) m_writeBuffer->cancel(); + // m_writeBuffer is a child of either Process or Async. So, if m_writeBuffer + // is still alive now (in case when TaskTree::stop() was called), the FileStreamBase + // d'tor is going to delete m_writeBuffer later. In case of Async, coming from + // localTask(), the d'tor of Async, run by FileStreamBase, busy waits for the + // already canceled here WriteBuffer to finish before deleting WriteBuffer child. } void setWriteData(const QByteArray &writeData) { -- cgit v1.2.3 From 5ae82a88cf8e8572aa4e948fc1cbc3732aa1f440 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Thu, 18 May 2023 19:14:39 +0200 Subject: TaskTree tasks: Make task naming consistent Task-number: QTCREATORBUG-29102 Change-Id: I96dfde58b684a3b48704778b92cdf2f869bbb7b1 Reviewed-by: Reviewed-by: Qt CI Bot Reviewed-by: hjk --- src/libs/utils/filestreamer.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/filestreamer.cpp b/src/libs/utils/filestreamer.cpp index ababb94cf6..d24b61dbde 100644 --- a/src/libs/utils/filestreamer.cpp +++ b/src/libs/utils/filestreamer.cpp @@ -306,8 +306,8 @@ public: } // namespace Utils -TASKING_DECLARE_TASK(Reader, Utils::FileStreamReaderAdapter); -TASKING_DECLARE_TASK(Writer, Utils::FileStreamWriterAdapter); +TASKING_DECLARE_TASK(FileStreamReaderTask, Utils::FileStreamReaderAdapter); +TASKING_DECLARE_TASK(FileStreamWriterTask, Utils::FileStreamWriterAdapter); namespace Utils { @@ -353,10 +353,10 @@ static Group interDeviceTransferTask(const FilePath &source, const FilePath &des Storage(writerReadyBarrier), parallel, Storage(storage), - Writer(setupWriter), + FileStreamWriterTask(setupWriter), Group { WaitForBarrierTask(writerReadyBarrier), - Reader(setupReader, finalizeReader, finalizeReader) + FileStreamReaderTask(setupReader, finalizeReader, finalizeReader) } }; @@ -408,14 +408,14 @@ private: m_readBuffer += data; }); }; - return Reader(setup); + return FileStreamReaderTask(setup); } TaskItem writerTask() { const auto setup = [this](FileStreamWriter &writer) { writer.setFilePath(m_destination); writer.setWriteData(m_writeBuffer); }; - return Writer(setup); + return FileStreamWriterTask(setup); } TaskItem transferTask() { const auto setup = [this](Async &async) { -- cgit v1.2.3 From c603e01535dd50849ae17498922f27a142db7b01 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 17 May 2023 17:38:10 +0200 Subject: TaskTree: Don't derive TaskNode from QObject It reduces the time spent inside TaskTree::setupRoot() by ~30% for big trees (~7000 tasks). Change-Id: Ic65ed0fdf511977d9cc2fe22bdac814516e9883d Reviewed-by: Qt CI Bot Reviewed-by: hjk --- src/libs/solutions/tasking/tasktree.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index 58235cd814..e3605aaf02 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -298,7 +298,7 @@ public: std::optional m_runtimeData; }; -class TaskNode : public QObject +class TaskNode { public: TaskNode(TaskTreePrivate *taskTreePrivate, const TaskItem &task, @@ -316,6 +316,7 @@ public: bool isTask() const { return bool(m_taskHandler.m_createHandler); } int taskCount() const { return isTask() ? 1 : m_container.m_constData.m_taskCount; } TaskContainer *parentContainer() const { return m_container.m_constData.m_parentContainer; } + TaskTree *taskTree() const { return m_container.m_constData.m_taskTreePrivate->q; } private: const TaskItem::TaskHandler m_taskHandler; @@ -647,9 +648,9 @@ TaskAction TaskNode::start() } const std::shared_ptr unwindAction = std::make_shared(TaskAction::Continue); - connect(m_task.get(), &TaskInterface::done, this, [=](bool success) { + QObject::connect(m_task.get(), &TaskInterface::done, taskTree(), [=](bool success) { invokeEndHandler(success); - disconnect(m_task.get(), &TaskInterface::done, this, nullptr); + QObject::disconnect(m_task.get(), &TaskInterface::done, taskTree(), nullptr); m_task.release()->deleteLater(); QTC_ASSERT(parentContainer() && parentContainer()->isRunning(), return); if (parentContainer()->isStarting()) -- cgit v1.2.3 From 3763370ab9e93f97d8acc63aed710c32e6034229 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Fri, 19 May 2023 09:35:09 +0200 Subject: TaskTree: Add Q_DISABLE_COPY_MOVE() into internal classes Change-Id: I1b599902dfeebed93378a4d38bd3deb786f572b9 Reviewed-by: hjk --- src/libs/solutions/tasking/tasktree.cpp | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index e3605aaf02..6930e11727 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -203,6 +203,8 @@ class TaskNode; class TaskTreePrivate { + Q_DISABLE_COPY_MOVE(TaskTreePrivate) + public: TaskTreePrivate(TaskTree *taskTree) : q(taskTree) {} @@ -249,6 +251,8 @@ public: class TaskContainer { + Q_DISABLE_COPY_MOVE(TaskContainer) + public: TaskContainer(TaskTreePrivate *taskTreePrivate, const TaskItem &task, TaskNode *parentNode, TaskContainer *parentContainer) @@ -300,6 +304,8 @@ public: class TaskNode { + Q_DISABLE_COPY_MOVE(TaskNode) + public: TaskNode(TaskTreePrivate *taskTreePrivate, const TaskItem &task, TaskContainer *parentContainer) -- cgit v1.2.3 From 7bfc3197aa9518a2a08cf8bbdc6ea1425feb003d Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Fri, 19 May 2023 10:31:02 +0200 Subject: TaskTree: Add missing include Amends 9c78ef983a1203515a7552784a119f1ba3df4a71 Change-Id: Id912771b2d23c6856233705a054c0e8e1e9b5a41 Reviewed-by: hjk --- src/libs/solutions/tasking/tasktree.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index 6930e11727..9544e356ac 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include -- cgit v1.2.3 From 7501d7587fc931a1454c0fea8412c2cee9a1c065 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 17 May 2023 19:28:57 +0200 Subject: TaskTree: Introduce WorkflowPolicy::StopOnFinished The policy is useful mainly in parallel mode. It stops executing the Group when any task finishes. It reports the task's result. Change-Id: I7aa98365cdc4c1eb869ab419d42d0cc5438d43bf Reviewed-by: Reviewed-by: hjk --- src/libs/solutions/tasking/tasktree.cpp | 76 +++++++++++++++++++-------------- src/libs/solutions/tasking/tasktree.h | 24 ++++++----- 2 files changed, 59 insertions(+), 41 deletions(-) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index 9544e356ac..32e7f3eadd 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -114,6 +114,7 @@ Workflow stopOnError(WorkflowPolicy::StopOnError); Workflow continueOnError(WorkflowPolicy::ContinueOnError); Workflow stopOnDone(WorkflowPolicy::StopOnDone); Workflow continueOnDone(WorkflowPolicy::ContinueOnDone); +Workflow stopOnFinished(WorkflowPolicy::StopOnFinished); Workflow optional(WorkflowPolicy::Optional); void TaskItem::addChildren(const QList &children) @@ -511,6 +512,10 @@ bool TaskContainer::RuntimeData::updateSuccessBit(bool success) { if (m_constData.m_workflowPolicy == WorkflowPolicy::Optional) return m_successBit; + if (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnFinished) { + m_successBit = success; + return m_successBit; + } const bool donePolicy = m_constData.m_workflowPolicy == WorkflowPolicy::StopOnDone || m_constData.m_workflowPolicy == WorkflowPolicy::ContinueOnDone; @@ -595,8 +600,9 @@ TaskAction TaskContainer::childDone(bool success) { QTC_CHECK(isRunning()); const int limit = m_runtimeData->currentLimit(); // Read before bumping m_doneCount and stop() - const bool shouldStop = (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnDone && success) - || (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnError && !success); + const bool shouldStop = m_constData.m_workflowPolicy == WorkflowPolicy::StopOnFinished + || (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnDone && success) + || (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnError && !success); if (shouldStop) stop(); @@ -1146,15 +1152,15 @@ void TaskNode::invokeEndHandler(bool success) \row \li stopOnError \li Default. If a task finishes with an error, the group: - \list 1 - \li Stops the running tasks (if any - for example, in parallel - mode). - \li Skips executing tasks it has not started (for example, in the - sequential mode). - \li Immediately finishes with an error. - \endlist - If all child tasks finish successfully or the group is empty, the group - finishes with success. + \list 1 + \li Stops the running tasks (if any - for example, in parallel + mode). + \li Skips executing tasks it has not started (for example, in the + sequential mode). + \li Immediately finishes with an error. + \endlist + If all child tasks finish successfully or the group is empty, the group + finishes with success. \row \li continueOnError \li Similar to stopOnError, but in case any child finishes with @@ -1162,22 +1168,22 @@ void TaskNode::invokeEndHandler(bool success) and the group reports an error afterwards, even when some other tasks in group finished with success. If a task finishes with an error, the group: - \list 1 - \li Continues executing the tasks that are running or have not - started yet. - \li Finishes with an error when all tasks finish. - \endlist - If all tasks finish successfully or the group is empty, the group - finishes with success. + \list 1 + \li Continues executing the tasks that are running or have not + started yet. + \li Finishes with an error when all tasks finish. + \endlist + If all tasks finish successfully or the group is empty, the group + finishes with success. \row \li stopOnDone \li If a task finishes with success, the group: - \list 1 - \li Stops running tasks and skips those that it has not started. - \li Immediately finishes with success. - \endlist - If all tasks finish with an error or the group is empty, the group - finishes with an error. + \list 1 + \li Stops running tasks and skips those that it has not started. + \li Immediately finishes with success. + \endlist + If all tasks finish with an error or the group is empty, the group + finishes with an error. \row \li continueOnDone \li Similar to stopOnDone, but in case any child finishes @@ -1185,13 +1191,21 @@ void TaskNode::invokeEndHandler(bool success) and the group reports success afterwards, even when some other tasks in group finished with an error. If a task finishes with success, the group: - \list 1 - \li Continues executing the tasks that are running or have not - started yet. - \li Finishes with success when all tasks finish. - \endlist - If all tasks finish with an error or the group is empty, the group - finishes with an error. + \list 1 + \li Continues executing the tasks that are running or have not + started yet. + \li Finishes with success when all tasks finish. + \endlist + If all tasks finish with an error or the group is empty, the group + finishes with an error. + \row + \li stopOnFinished + \li The group starts as many tasks as it can. When a task finishes + the group stops and reports the task's result. + When the group is empty, it finishes immediately with success. + Useful only in parallel mode. + In sequential mode, only the first task is started, and when finished, + the group finishes too, so the other tasks are ignored. \row \li optional \li The group executes all tasks and ignores their return state. If all diff --git a/src/libs/solutions/tasking/tasktree.h b/src/libs/solutions/tasking/tasktree.h index 462eb5bd2e..407db58b0a 100644 --- a/src/libs/solutions/tasking/tasktree.h +++ b/src/libs/solutions/tasking/tasktree.h @@ -93,19 +93,22 @@ private: // WorkflowPolicy: // 1. When all children finished with done -> report done, otherwise: -// a) Report error on first error and stop executing other children (including their subtree) -// b) On first error - continue executing all children and report error afterwards +// a) Report error on first error and stop executing other children (including their subtree). +// b) On first error - continue executing all children and report error afterwards. // 2. When all children finished with error -> report error, otherwise: -// a) Report done on first done and stop executing other children (including their subtree) -// b) On first done - continue executing all children and report done afterwards -// 3. Always run all children, ignore their result and report done afterwards +// a) Report done on first done and stop executing other children (including their subtree). +// b) On first done - continue executing all children and report done afterwards. +// 3. Stops on first finished child. In sequential mode it will never run other children then the first one. +// Useful only in parallel mode. +// 4. Always run all children, ignore their result and report done afterwards. enum class WorkflowPolicy { - StopOnError, // 1a - Reports error on first child error, otherwise done (if all children were done) - ContinueOnError, // 1b - The same, but children execution continues. When no children it reports done. - StopOnDone, // 2a - Reports done on first child done, otherwise error (if all children were error) - ContinueOnDone, // 2b - The same, but children execution continues. When no children it reports done. (?) - Optional // 3 - Always reports done after all children finished + StopOnError, // 1a - Reports error on first child error, otherwise done (if all children were done). + ContinueOnError, // 1b - The same, but children execution continues. Reports done when no children. + StopOnDone, // 2a - Reports done on first child done, otherwise error (if all children were error). + ContinueOnDone, // 2b - The same, but children execution continues. Reports error when no children. + StopOnFinished, // 3 - Stops on first finished child and report its result. + Optional // 4 - Reports done after all children finished. }; enum class TaskAction @@ -287,6 +290,7 @@ TASKING_EXPORT extern Workflow stopOnError; TASKING_EXPORT extern Workflow continueOnError; TASKING_EXPORT extern Workflow stopOnDone; TASKING_EXPORT extern Workflow continueOnDone; +TASKING_EXPORT extern Workflow stopOnFinished; TASKING_EXPORT extern Workflow optional; template -- cgit v1.2.3 From 40d7fcc1d402d94f9e1fd5dd12da8e4f7ad97b44 Mon Sep 17 00:00:00 2001 From: hjk Date: Wed, 17 May 2023 16:34:54 +0200 Subject: Utils: Remove check against 'autoapply' for some volatile aspect value This was based on the wrong assumption that on !autoapply aspects (i.e. aspects in settings pages the non-applied value would never be needed by user code. This is, however, not the case when e.g. temporary checkbox states or values in comboboxes are used to enable/disable parts of the ui while interacting with the page. Change-Id: I4fe6a0df8137083a0a0faecc3ba20792caa5a747 Reviewed-by: Reviewed-by: Alessandro Portale --- src/libs/utils/aspects.cpp | 5 ----- 1 file changed, 5 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index 549a394273..082f4f8244 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -1224,7 +1224,6 @@ void StringAspect::addToLayout(LayoutItem &parent) QVariant StringAspect::volatileValue() const { - QTC_CHECK(!isAutoApply()); switch (d->m_displayStyle) { case PathChooserDisplay: QTC_ASSERT(d->m_pathChooserDisplay, return {}); @@ -1519,7 +1518,6 @@ QAction *BoolAspect::action() QVariant BoolAspect::volatileValue() const { - QTC_CHECK(!isAutoApply()); if (d->m_button) return d->m_button->isChecked(); if (d->m_groupBox) @@ -1530,7 +1528,6 @@ QVariant BoolAspect::volatileValue() const void BoolAspect::setVolatileValue(const QVariant &val) { - QTC_CHECK(!isAutoApply()); if (d->m_button) d->m_button->setChecked(val.toBool()); else if (d->m_groupBox) @@ -1660,7 +1657,6 @@ void SelectionAspect::addToLayout(Layouting::LayoutItem &parent) QVariant SelectionAspect::volatileValue() const { - QTC_CHECK(!isAutoApply()); switch (d->m_displayStyle) { case DisplayStyle::RadioButtons: QTC_ASSERT(d->m_buttonGroup, return {}); @@ -1674,7 +1670,6 @@ QVariant SelectionAspect::volatileValue() const void SelectionAspect::setVolatileValue(const QVariant &val) { - QTC_CHECK(!isAutoApply()); switch (d->m_displayStyle) { case DisplayStyle::RadioButtons: { if (d->m_buttonGroup) { -- cgit v1.2.3 From ceff14303a39df571b062de0b0289e21fda07856 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Tue, 2 May 2023 09:28:52 +0200 Subject: TaskTree: Prepare for OnGroup... -> onGroup... renaming Change-Id: I52b695999b53b80fb8dbb77895080f6c1b86a58f Reviewed-by: Qt CI Bot Reviewed-by: Reviewed-by: hjk --- src/libs/solutions/tasking/tasktree.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index 32e7f3eadd..8e79485a8f 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -976,11 +976,11 @@ void TaskNode::invokeEndHandler(bool success) tasks. The group handler doesn't take any arguments: \code - const auto onGroupSetup = [] { + const auto onSetup = [] { qDebug() << "Entering the group"; }; const Group root { - OnGroupSetup(onGroupSetup), + OnGroupSetup(onSetup), ProcessTask(...) }; \endcode -- cgit v1.2.3 From 04382d39a07b31f28ac1a75b66dd729c200a782c Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 17 May 2023 19:57:23 +0200 Subject: TaskTree: Introduce onGroup...() functions The OnGroup... elements are going to be replaced with these functions. Change-Id: Ia271a89062cc9c6b18384607b20b1f68d273bcde Reviewed-by: Qt CI Bot Reviewed-by: hjk --- src/libs/solutions/tasking/tasktree.cpp | 10 ++++++++ src/libs/solutions/tasking/tasktree.h | 44 +++++++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index 8e79485a8f..06a3804f9d 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -40,6 +40,16 @@ private: Guard &m_guard; }; +TaskItem onGroupDone(const TaskItem::GroupEndHandler &handler) +{ + return Group::onGroupDone(handler); +} + +TaskItem onGroupError(const TaskItem::GroupEndHandler &handler) +{ + return Group::onGroupError(handler); +} + static TaskAction toTaskAction(bool success) { return success ? TaskAction::StopWithDone : TaskAction::StopWithError; diff --git a/src/libs/solutions/tasking/tasktree.h b/src/libs/solutions/tasking/tasktree.h index 407db58b0a..08391d9d63 100644 --- a/src/libs/solutions/tasking/tasktree.h +++ b/src/libs/solutions/tasking/tasktree.h @@ -183,6 +183,7 @@ protected: void setTaskSetupHandler(const TaskSetupHandler &handler); void setTaskDoneHandler(const TaskEndHandler &handler); void setTaskErrorHandler(const TaskEndHandler &handler); + static TaskItem createGroupHandler(const GroupHandler &handler) { return TaskItem(handler); } private: Type m_type = Type::Group; @@ -199,8 +200,47 @@ class TASKING_EXPORT Group : public TaskItem public: Group(const QList &children) { addChildren(children); } Group(std::initializer_list children) { addChildren(children); } + + template + static TaskItem onGroupSetup(SetupHandler &&handler) { + return createGroupHandler({wrapGroupSetup(std::forward(handler))}); + } + static TaskItem onGroupDone(const GroupEndHandler &handler) { + return createGroupHandler({{}, handler}); + } + static TaskItem onGroupError(const GroupEndHandler &handler) { + return createGroupHandler({{}, {}, handler}); + } + +private: + template + static GroupSetupHandler wrapGroupSetup(SetupHandler &&handler) + { + static constexpr bool isDynamic + = std::is_same_v>>; + constexpr bool isVoid + = std::is_same_v>>; + static_assert(isDynamic || isVoid, + "Group setup handler needs to take no arguments and has to return " + "void or TaskAction. The passed handler doesn't fulfill these requirements."); + return [=] { + if constexpr (isDynamic) + return std::invoke(handler); + std::invoke(handler); + return TaskAction::Continue; + }; + }; }; +template +static TaskItem onGroupSetup(SetupHandler &&handler) +{ + return Group::onGroupSetup(std::forward(handler)); +} + +TASKING_EXPORT TaskItem onGroupDone(const TaskItem::GroupEndHandler &handler); +TASKING_EXPORT TaskItem onGroupError(const TaskItem::GroupEndHandler &handler); + class TASKING_EXPORT Storage : public TaskItem { public: @@ -276,10 +316,10 @@ private: static_assert(isBool || isVoid, "Sync element: The synchronous function has to return void or bool."); if constexpr (isBool) { - return {OnGroupSetup([function] { return function() ? TaskAction::StopWithDone + return {onGroupSetup([function] { return function() ? TaskAction::StopWithDone : TaskAction::StopWithError; })}; } - return {OnGroupSetup([function] { function(); return TaskAction::StopWithDone; })}; + return {onGroupSetup([function] { function(); return TaskAction::StopWithDone; })}; }; }; -- cgit v1.2.3 From 56fda87389fbc277ca3c3636660209dfc5da82ed Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 17 May 2023 20:03:57 +0200 Subject: TaskTree: Get rid of OnGroup... elements Change-Id: Id1b600999d2051eff8d2207db8235ad08eb6e663 Reviewed-by: Reviewed-by: Qt CI Bot Reviewed-by: hjk --- src/libs/solutions/tasking/tasktree.cpp | 24 ++++++++++----------- src/libs/solutions/tasking/tasktree.h | 38 --------------------------------- 2 files changed, 12 insertions(+), 50 deletions(-) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index 06a3804f9d..57429b9051 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -990,14 +990,14 @@ void TaskNode::invokeEndHandler(bool success) qDebug() << "Entering the group"; }; const Group root { - OnGroupSetup(onSetup), + onGroupSetup(onSetup), ProcessTask(...) }; \endcode The group setup handler is optional. To define a group setup handler, add an - OnGroupSetup element to a group. The argument of OnGroupSetup is a user - handler. If you add more than one OnGroupSetup element to a group, an assert + onGroupSetup element to a group. The argument of onGroupSetup is a user + handler. If you add more than one onGroupSetup element to a group, an assert is triggered at runtime that includes an error message. Like the task start handler, the group start handler may return TaskAction. @@ -1011,17 +1011,17 @@ void TaskNode::invokeEndHandler(bool success) \code const Group root { - OnGroupSetup([] { qDebug() << "Root setup"; }), + onGroupSetup([] { qDebug() << "Root setup"; }), Group { - OnGroupSetup([] { qDebug() << "Group 1 setup"; return TaskAction::Continue; }), + onGroupSetup([] { qDebug() << "Group 1 setup"; return TaskAction::Continue; }), ProcessTask(...) // Process 1 }, Group { - OnGroupSetup([] { qDebug() << "Group 2 setup"; return TaskAction::StopWithDone; }), + onGroupSetup([] { qDebug() << "Group 2 setup"; return TaskAction::StopWithDone; }), ProcessTask(...) // Process 2 }, Group { - OnGroupSetup([] { qDebug() << "Group 3 setup"; return TaskAction::StopWithError; }), + onGroupSetup([] { qDebug() << "Group 3 setup"; return TaskAction::StopWithError; }), ProcessTask(...) // Process 3 }, ProcessTask(...) // Process 4 @@ -1080,20 +1080,20 @@ void TaskNode::invokeEndHandler(bool success) execution of its tasks, respectively. The final value reported by the group depends on its \l {Workflow Policy}. The handlers can apply other necessary actions. The done and error handlers are defined inside the - OnGroupDone and OnGroupError elements of a group, respectively. They do not + onGroupDone and onGroupError elements of a group, respectively. They do not take arguments: \code const Group root { - OnGroupSetup([] { qDebug() << "Root setup"; }), + onGroupSetup([] { qDebug() << "Root setup"; }), ProcessTask(...), - OnGroupDone([] { qDebug() << "Root finished with success"; }), - OnGroupError([] { qDebug() << "Root finished with error"; }) + onGroupDone([] { qDebug() << "Root finished with success"; }), + onGroupError([] { qDebug() << "Root finished with error"; }) }; \endcode The group done and error handlers are optional. If you add more than one - OnGroupDone or OnGroupError each to a group, an assert is triggered at + onGroupDone or onGroupError each to a group, an assert is triggered at runtime that includes an error message. \note Even if the group setup handler returns StopWithDone or StopWithError, diff --git a/src/libs/solutions/tasking/tasktree.h b/src/libs/solutions/tasking/tasktree.h index 08391d9d63..7b37c36eb9 100644 --- a/src/libs/solutions/tasking/tasktree.h +++ b/src/libs/solutions/tasking/tasktree.h @@ -259,44 +259,6 @@ public: Workflow(WorkflowPolicy policy) : TaskItem(policy) {} }; -class TASKING_EXPORT OnGroupSetup : public TaskItem -{ -public: - template - OnGroupSetup(SetupFunction &&function) - : TaskItem({wrapSetup(std::forward(function))}) {} - -private: - template - static TaskItem::GroupSetupHandler wrapSetup(SetupFunction &&function) { - static constexpr bool isDynamic = std::is_same_v>>; - constexpr bool isVoid = std::is_same_v>>; - static_assert(isDynamic || isVoid, - "Group setup handler needs to take no arguments and has to return " - "void or TaskAction. The passed handler doesn't fulfill these requirements."); - return [=] { - if constexpr (isDynamic) - return std::invoke(function); - std::invoke(function); - return TaskAction::Continue; - }; - }; -}; - -class TASKING_EXPORT OnGroupDone : public TaskItem -{ -public: - OnGroupDone(const GroupEndHandler &handler) : TaskItem({{}, handler}) {} -}; - -class TASKING_EXPORT OnGroupError : public TaskItem -{ -public: - OnGroupError(const GroupEndHandler &handler) : TaskItem({{}, {}, handler}) {} -}; - // Synchronous invocation. Similarly to Group - isn't counted as a task inside taskCount() class TASKING_EXPORT Sync : public Group { -- cgit v1.2.3 From c098b261dc54fa8edbdd2bde17841cfe8b7e64a0 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Thu, 18 May 2023 13:16:40 +0200 Subject: TaskTree: Refactor Group internal data Introduce the GroupData structure. In this way it's easily possible to add extra properties of already used types, e.g. int. It's also possible to easily create elements with multiple properties. Simplify internal TaskItem::Type enum. Get rid of special ParallelLimit and Workflow elements. Provide global parallelLimit() and workflowPolicy() functions. Make global items (e.g. parallel, stopOnDone, etc...) const. Change-Id: Ic5628255b542fd6c5a5565b055ff11804c8d7b68 Reviewed-by: Qt CI Bot Reviewed-by: Reviewed-by: hjk --- src/libs/solutions/tasking/tasktree.cpp | 93 ++++++++++++++++++--------------- src/libs/solutions/tasking/tasktree.h | 93 ++++++++++++++------------------- 2 files changed, 92 insertions(+), 94 deletions(-) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index 57429b9051..4e9cab7bb9 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -50,6 +50,25 @@ TaskItem onGroupError(const TaskItem::GroupEndHandler &handler) return Group::onGroupError(handler); } +TaskItem parallelLimit(int limit) +{ + return Group::parallelLimit(qMax(limit, 0)); +} + +TaskItem workflowPolicy(WorkflowPolicy policy) +{ + return Group::workflowPolicy(policy); +} + +const TaskItem sequential = parallelLimit(1); +const TaskItem parallel = parallelLimit(0); +const TaskItem stopOnError = workflowPolicy(WorkflowPolicy::StopOnError); +const TaskItem continueOnError = workflowPolicy(WorkflowPolicy::ContinueOnError); +const TaskItem stopOnDone = workflowPolicy(WorkflowPolicy::StopOnDone); +const TaskItem continueOnDone = workflowPolicy(WorkflowPolicy::ContinueOnDone); +const TaskItem stopOnFinished = workflowPolicy(WorkflowPolicy::StopOnFinished); +const TaskItem optional = workflowPolicy(WorkflowPolicy::Optional); + static TaskAction toTaskAction(bool success) { return success ? TaskAction::StopWithDone : TaskAction::StopWithError; @@ -118,15 +137,6 @@ void TreeStorageBase::activateStorage(int id) const m_storageData->m_activeStorage = id; } -ParallelLimit sequential(1); -ParallelLimit parallel(0); -Workflow stopOnError(WorkflowPolicy::StopOnError); -Workflow continueOnError(WorkflowPolicy::ContinueOnError); -Workflow stopOnDone(WorkflowPolicy::StopOnDone); -Workflow continueOnDone(WorkflowPolicy::ContinueOnDone); -Workflow stopOnFinished(WorkflowPolicy::StopOnFinished); -Workflow optional(WorkflowPolicy::Optional); - void TaskItem::addChildren(const QList &children) { QTC_ASSERT(m_type == Type::Group, qWarning("Only Group may have children, skipping..."); @@ -136,40 +146,41 @@ void TaskItem::addChildren(const QList &children) case Type::Group: m_children.append(child); break; - case Type::Limit: - QTC_ASSERT(m_type == Type::Group, qWarning("Execution Mode may only be a child of a " - "Group, skipping..."); return); - m_parallelLimit = child.m_parallelLimit; // TODO: Assert on redefinition? - break; - case Type::Policy: - QTC_ASSERT(m_type == Type::Group, qWarning("Workflow Policy may only be a child of a " - "Group, skipping..."); return); - m_workflowPolicy = child.m_workflowPolicy; // TODO: Assert on redefinition? + case Type::GroupData: + if (child.m_groupData.m_groupHandler.m_setupHandler) { + QTC_ASSERT(!m_groupData.m_groupHandler.m_setupHandler, + qWarning("Group Setup Handler redefinition, overriding...")); + m_groupData.m_groupHandler.m_setupHandler + = child.m_groupData.m_groupHandler.m_setupHandler; + } + if (child.m_groupData.m_groupHandler.m_doneHandler) { + QTC_ASSERT(!m_groupData.m_groupHandler.m_doneHandler, + qWarning("Group Done Handler redefinition, overriding...")); + m_groupData.m_groupHandler.m_doneHandler + = child.m_groupData.m_groupHandler.m_doneHandler; + } + if (child.m_groupData.m_groupHandler.m_errorHandler) { + QTC_ASSERT(!m_groupData.m_groupHandler.m_errorHandler, + qWarning("Group Error Handler redefinition, overriding...")); + m_groupData.m_groupHandler.m_errorHandler + = child.m_groupData.m_groupHandler.m_errorHandler; + } + if (child.m_groupData.m_parallelLimit) { + QTC_ASSERT(!m_groupData.m_parallelLimit, + qWarning("Group Execution Mode redefinition, overriding...")); + m_groupData.m_parallelLimit = child.m_groupData.m_parallelLimit; + } + if (child.m_groupData.m_workflowPolicy) { + QTC_ASSERT(!m_groupData.m_workflowPolicy, + qWarning("Group Workflow Policy redefinition, overriding...")); + m_groupData.m_workflowPolicy = child.m_groupData.m_workflowPolicy; + } break; case Type::TaskHandler: QTC_ASSERT(child.m_taskHandler.m_createHandler, qWarning("Task Create Handler can't be null, skipping..."); return); m_children.append(child); break; - case Type::GroupHandler: - QTC_ASSERT(m_type == Type::Group, qWarning("Group Handler may only be a " - "child of a Group, skipping..."); break); - QTC_ASSERT(!child.m_groupHandler.m_setupHandler - || !m_groupHandler.m_setupHandler, - qWarning("Group Setup Handler redefinition, overriding...")); - QTC_ASSERT(!child.m_groupHandler.m_doneHandler - || !m_groupHandler.m_doneHandler, - qWarning("Group Done Handler redefinition, overriding...")); - QTC_ASSERT(!child.m_groupHandler.m_errorHandler - || !m_groupHandler.m_errorHandler, - qWarning("Group Error Handler redefinition, overriding...")); - if (child.m_groupHandler.m_setupHandler) - m_groupHandler.m_setupHandler = child.m_groupHandler.m_setupHandler; - if (child.m_groupHandler.m_doneHandler) - m_groupHandler.m_doneHandler = child.m_groupHandler.m_doneHandler; - if (child.m_groupHandler.m_errorHandler) - m_groupHandler.m_errorHandler = child.m_groupHandler.m_errorHandler; - break; case Type::Storage: m_storageList.append(child.m_storageList); break; @@ -472,9 +483,9 @@ TaskContainer::ConstData::ConstData(TaskTreePrivate *taskTreePrivate, const Task : m_taskTreePrivate(taskTreePrivate) , m_parentNode(parentNode) , m_parentContainer(parentContainer) - , m_parallelLimit(task.parallelLimit()) - , m_workflowPolicy(task.workflowPolicy()) - , m_groupHandler(task.groupHandler()) + , m_parallelLimit(task.groupData().m_parallelLimit.value_or(1)) + , m_workflowPolicy(task.groupData().m_workflowPolicy.value_or(WorkflowPolicy::StopOnError)) + , m_groupHandler(task.groupData().m_groupHandler) , m_storageList(taskTreePrivate->addStorages(task.storageList())) , m_children(createChildren(taskTreePrivate, thisContainer, task)) , m_taskCount(std::accumulate(m_children.cbegin(), m_children.cend(), 0, @@ -1130,7 +1141,7 @@ void TaskNode::invokeEndHandler(bool success) started, without waiting for the previous tasks to finish. In this mode, all tasks run simultaneously. \row - \li ParallelLimit(int limit) + \li parallelLimit(int limit) \li In this mode, a limited number of direct child tasks run simultaneously. The \e limit defines the maximum number of tasks running in parallel in a group. When the group is started, the first batch tasks is diff --git a/src/libs/solutions/tasking/tasktree.h b/src/libs/solutions/tasking/tasktree.h index 7b37c36eb9..1754e09263 100644 --- a/src/libs/solutions/tasking/tasktree.h +++ b/src/libs/solutions/tasking/tasktree.h @@ -18,7 +18,6 @@ namespace Tasking { class ExecutionContextActivator; class TaskContainer; -class TaskNode; class TaskTreePrivate; class TASKING_EXPORT TaskInterface : public QObject @@ -68,9 +67,9 @@ private: int m_storageCounter = 0; }; QSharedPointer m_storageData; + friend ExecutionContextActivator; friend TaskContainer; friend TaskTreePrivate; - friend ExecutionContextActivator; }; template @@ -145,54 +144,50 @@ public: GroupEndHandler m_errorHandler = {}; }; - int parallelLimit() const { return m_parallelLimit; } - WorkflowPolicy workflowPolicy() const { return m_workflowPolicy; } - TaskHandler taskHandler() const { return m_taskHandler; } - GroupHandler groupHandler() const { return m_groupHandler; } + struct GroupData { + GroupHandler m_groupHandler = {}; + std::optional m_parallelLimit = {}; + std::optional m_workflowPolicy = {}; + }; + QList children() const { return m_children; } + GroupData groupData() const { return m_groupData; } QList storageList() const { return m_storageList; } + TaskHandler taskHandler() const { return m_taskHandler; } protected: enum class Type { Group, + GroupData, Storage, - Limit, - Policy, - TaskHandler, - GroupHandler + TaskHandler }; TaskItem() = default; - TaskItem(int parallelLimit) - : m_type(Type::Limit) - , m_parallelLimit(parallelLimit) {} - TaskItem(WorkflowPolicy policy) - : m_type(Type::Policy) - , m_workflowPolicy(policy) {} - TaskItem(const TaskHandler &handler) - : m_type(Type::TaskHandler) - , m_taskHandler(handler) {} - TaskItem(const GroupHandler &handler) - : m_type(Type::GroupHandler) - , m_groupHandler(handler) {} + TaskItem(const GroupData &data) + : m_type(Type::GroupData) + , m_groupData(data) {} TaskItem(const TreeStorageBase &storage) : m_type(Type::Storage) , m_storageList{storage} {} + TaskItem(const TaskHandler &handler) + : m_type(Type::TaskHandler) + , m_taskHandler(handler) {} void addChildren(const QList &children); void setTaskSetupHandler(const TaskSetupHandler &handler); void setTaskDoneHandler(const TaskEndHandler &handler); void setTaskErrorHandler(const TaskEndHandler &handler); - static TaskItem createGroupHandler(const GroupHandler &handler) { return TaskItem(handler); } + static TaskItem groupHandler(const GroupHandler &handler) { return TaskItem({handler}); } + static TaskItem parallelLimit(int limit) { return TaskItem({{}, limit}); } + static TaskItem workflowPolicy(WorkflowPolicy policy) { return TaskItem({{}, {}, policy}); } private: Type m_type = Type::Group; - int m_parallelLimit = 1; // 0 means unlimited - WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError; - TaskHandler m_taskHandler; - GroupHandler m_groupHandler; - QList m_storageList; QList m_children; + GroupData m_groupData; + QList m_storageList; + TaskHandler m_taskHandler; }; class TASKING_EXPORT Group : public TaskItem @@ -201,16 +196,19 @@ public: Group(const QList &children) { addChildren(children); } Group(std::initializer_list children) { addChildren(children); } + // GroupData related: template static TaskItem onGroupSetup(SetupHandler &&handler) { - return createGroupHandler({wrapGroupSetup(std::forward(handler))}); + return groupHandler({wrapGroupSetup(std::forward(handler))}); } static TaskItem onGroupDone(const GroupEndHandler &handler) { - return createGroupHandler({{}, handler}); + return groupHandler({{}, handler}); } static TaskItem onGroupError(const GroupEndHandler &handler) { - return createGroupHandler({{}, {}, handler}); + return groupHandler({{}, {}, handler}); } + using TaskItem::parallelLimit; // Default: 1 (sequential). 0 means unlimited (parallel). + using TaskItem::workflowPolicy; // Default: WorkflowPolicy::StopOnError. private: template @@ -240,6 +238,17 @@ static TaskItem onGroupSetup(SetupHandler &&handler) TASKING_EXPORT TaskItem onGroupDone(const TaskItem::GroupEndHandler &handler); TASKING_EXPORT TaskItem onGroupError(const TaskItem::GroupEndHandler &handler); +TASKING_EXPORT TaskItem parallelLimit(int limit); +TASKING_EXPORT TaskItem workflowPolicy(WorkflowPolicy policy); + +TASKING_EXPORT extern const TaskItem sequential; +TASKING_EXPORT extern const TaskItem parallel; +TASKING_EXPORT extern const TaskItem stopOnError; +TASKING_EXPORT extern const TaskItem continueOnError; +TASKING_EXPORT extern const TaskItem stopOnDone; +TASKING_EXPORT extern const TaskItem continueOnDone; +TASKING_EXPORT extern const TaskItem stopOnFinished; +TASKING_EXPORT extern const TaskItem optional; class TASKING_EXPORT Storage : public TaskItem { @@ -247,18 +256,6 @@ public: Storage(const TreeStorageBase &storage) : TaskItem(storage) { } }; -class TASKING_EXPORT ParallelLimit : public TaskItem -{ -public: - ParallelLimit(int parallelLimit) : TaskItem(qMax(parallelLimit, 0)) {} -}; - -class TASKING_EXPORT Workflow : public TaskItem -{ -public: - Workflow(WorkflowPolicy policy) : TaskItem(policy) {} -}; - // Synchronous invocation. Similarly to Group - isn't counted as a task inside taskCount() class TASKING_EXPORT Sync : public Group { @@ -283,18 +280,8 @@ private: } return {onGroupSetup([function] { function(); return TaskAction::StopWithDone; })}; }; - }; -TASKING_EXPORT extern ParallelLimit sequential; -TASKING_EXPORT extern ParallelLimit parallel; -TASKING_EXPORT extern Workflow stopOnError; -TASKING_EXPORT extern Workflow continueOnError; -TASKING_EXPORT extern Workflow stopOnDone; -TASKING_EXPORT extern Workflow continueOnDone; -TASKING_EXPORT extern Workflow stopOnFinished; -TASKING_EXPORT extern Workflow optional; - template class TaskAdapter : public TaskInterface { -- cgit v1.2.3 From d740a355bb1954714234edd13194ce158540bcc0 Mon Sep 17 00:00:00 2001 From: hjk Date: Fri, 19 May 2023 10:09:59 +0200 Subject: Utils: Rework CheckableMessageBox Remove function overloads, thes are hard to read, to use and to extend. I'd even argue this should be a plain default ctor and a few setters + exec(), pretty much like Process::start() nowadays. Move "decider" magic into a structure that can be filled ad-hoc outside checkablemessagebox.cpp paving the ground for: ...removing aspect dependency from CheckableMessageBox, Instead, add a convenience function to BoolAspect. Arguably, the latter is not needed and could be done on the user side. Use pointers instead of mutable references for in-out parameter. Makes the "specialness" visible on the user side. Pass ICore::settings() centrally as done elsewhere to reduce line noise on the user side. Change-Id: Ibb366353d1ea35401723fd05ce05672617a0a8fd Reviewed-by: Marcus Tillmanns --- src/libs/utils/aspects.cpp | 12 +- src/libs/utils/aspects.h | 4 +- src/libs/utils/checkablemessagebox.cpp | 141 ++++++++++------------- src/libs/utils/checkablemessagebox.h | 201 ++++----------------------------- 4 files changed, 94 insertions(+), 264 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index 082f4f8244..e96014eba1 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -4,6 +4,7 @@ #include "aspects.h" #include "algorithm.h" +#include "checkablemessagebox.h" #include "environment.h" #include "fancylineedit.h" #include "layoutbuilder.h" @@ -1358,11 +1359,10 @@ FilePathAspect::FilePathAspect() The color aspect is displayed using a QtColorButton. */ -ColorAspect::ColorAspect(const QString &settingsKey) +ColorAspect::ColorAspect() : d(new Internal::ColorAspectPrivate) { setDefaultValue(QColor::fromRgb(0, 0, 0)); - setSettingsKey(settingsKey); setSpan(1, 1); addDataExtractor(this, &ColorAspect::value, &Data::value); @@ -1587,6 +1587,14 @@ void BoolAspect::setLabelPlacement(BoolAspect::LabelPlacement labelPlacement) d->m_labelPlacement = labelPlacement; } +CheckableDecider BoolAspect::checkableDecider() +{ + return CheckableDecider( + [this] { return !value(); }, + [this] { setValue(true); } + ); +} + /*! \class Utils::SelectionAspect \inmodule QtCreator diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index f6a35fa4ef..1086c7445b 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -24,6 +24,7 @@ namespace Utils { class AspectContainer; class BoolAspect; +class CheckableDecider; namespace Internal { class AspectContainerPrivate; @@ -222,6 +223,7 @@ public: void addToLayout(Layouting::LayoutItem &parent) override; std::function groupChecker(); + Utils::CheckableDecider checkableDecider(); QAction *action() override; @@ -255,7 +257,7 @@ class QTCREATOR_UTILS_EXPORT ColorAspect : public BaseAspect Q_OBJECT public: - explicit ColorAspect(const QString &settingsKey = QString()); + ColorAspect(); ~ColorAspect() override; struct Data : BaseAspect::Data diff --git a/src/libs/utils/checkablemessagebox.cpp b/src/libs/utils/checkablemessagebox.cpp index bf566e53a8..d09c294175 100644 --- a/src/libs/utils/checkablemessagebox.cpp +++ b/src/libs/utils/checkablemessagebox.cpp @@ -31,16 +31,19 @@ static const char kDoNotAskAgainKey[] = "DoNotAskAgain"; namespace Utils { +static QSettings *theSettings; + static QMessageBox::StandardButton exec( QWidget *parent, QMessageBox::Icon icon, const QString &title, const QString &text, - std::optional decider, + const CheckableDecider &decider, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton, QMessageBox::StandardButton acceptButton, - QMap buttonTextOverrides) + QMap buttonTextOverrides, + const QString &msg) { QMessageBox msgBox(parent); msgBox.setWindowTitle(title); @@ -59,18 +62,13 @@ static QMessageBox::StandardButton exec( } } - if (decider) { - if (!CheckableMessageBox::shouldAskAgain(*decider)) + if (decider.shouldAskAgain) { + if (!decider.shouldAskAgain()) return acceptButton; msgBox.setCheckBox(new QCheckBox); msgBox.checkBox()->setChecked(false); - - std::visit( - [&msgBox](auto &&decider) { - msgBox.checkBox()->setText(decider.text); - }, - *decider); + msgBox.checkBox()->setText(msg); } msgBox.setStandardButtons(buttons); @@ -81,21 +79,44 @@ static QMessageBox::StandardButton exec( QMessageBox::StandardButton clickedBtn = msgBox.standardButton(msgBox.clickedButton()); - if (decider && msgBox.checkBox()->isChecked() + if (decider.doNotAskAgain && msgBox.checkBox()->isChecked() && (acceptButton == QMessageBox::NoButton || clickedBtn == acceptButton)) - CheckableMessageBox::doNotAskAgain(*decider); + decider.doNotAskAgain(); return clickedBtn; } +CheckableDecider::CheckableDecider(const QString &settingsSubKey) +{ + QTC_ASSERT(theSettings, return); + shouldAskAgain = [settingsSubKey] { + theSettings->beginGroup(QLatin1String(kDoNotAskAgainKey)); + bool shouldNotAsk = theSettings->value(settingsSubKey, false).toBool(); + theSettings->endGroup(); + return !shouldNotAsk; + }; + doNotAskAgain = [settingsSubKey] { + theSettings->beginGroup(QLatin1String(kDoNotAskAgainKey)); + theSettings->setValue(settingsSubKey, true); + theSettings->endGroup(); + }; +} + +CheckableDecider::CheckableDecider(bool *storage) +{ + shouldAskAgain = [storage] { return !*storage; }; + doNotAskAgain = [storage] { *storage = true; }; +} + QMessageBox::StandardButton CheckableMessageBox::question( QWidget *parent, const QString &title, const QString &question, - std::optional decider, + const CheckableDecider &decider, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton, QMessageBox::StandardButton acceptButton, - QMap buttonTextOverrides) + QMap buttonTextOverrides, + const QString &msg) { return exec(parent, QMessageBox::Question, @@ -105,17 +126,19 @@ QMessageBox::StandardButton CheckableMessageBox::question( buttons, defaultButton, acceptButton, - buttonTextOverrides); + buttonTextOverrides, + msg.isEmpty() ? msgDoNotAskAgain() : msg); } QMessageBox::StandardButton CheckableMessageBox::information( QWidget *parent, const QString &title, const QString &text, - std::optional decider, + const CheckableDecider &decider, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton, - QMap buttonTextOverrides) + QMap buttonTextOverrides, + const QString &msg) { return exec(parent, QMessageBox::Information, @@ -125,82 +148,33 @@ QMessageBox::StandardButton CheckableMessageBox::information( buttons, defaultButton, QMessageBox::NoButton, - buttonTextOverrides); -} - -void CheckableMessageBox::doNotAskAgain(Decider &decider) -{ - std::visit( - [](auto &&decider) { - using T = std::decay_t; - if constexpr (std::is_same_v) { - decider.doNotAskAgain = true; - } else if constexpr (std::is_same_v) { - decider.settings->beginGroup(QLatin1String(kDoNotAskAgainKey)); - decider.settings->setValue(decider.settingsSubKey, true); - decider.settings->endGroup(); - } else if constexpr (std::is_same_v) { - decider.aspect.setValue(true); - } - }, - decider); -} - -bool CheckableMessageBox::shouldAskAgain(const Decider &decider) -{ - bool result = std::visit( - [](auto &&decider) { - using T = std::decay_t; - if constexpr (std::is_same_v) { - return !decider.doNotAskAgain; - } else if constexpr (std::is_same_v) { - decider.settings->beginGroup(QLatin1String(kDoNotAskAgainKey)); - bool shouldNotAsk = decider.settings->value(decider.settingsSubKey, false).toBool(); - decider.settings->endGroup(); - return !shouldNotAsk; - } else if constexpr (std::is_same_v) { - return !decider.aspect.value(); - } - }, - decider); - - return result; -} - -bool CheckableMessageBox::shouldAskAgain(QSettings *settings, const QString &key) -{ - return shouldAskAgain(make_decider(settings, key)); -} - -void CheckableMessageBox::doNotAskAgain(QSettings *settings, const QString &key) -{ - Decider decider = make_decider(settings, key); - return doNotAskAgain(decider); + buttonTextOverrides, + msg.isEmpty() ? msgDoNotShowAgain() : msg); } /*! - Resets all suppression settings for doNotAskAgainQuestion() found in \a settings, + Resets all suppression settings for doNotAskAgainQuestion() so all these message boxes are shown again. */ -void CheckableMessageBox::resetAllDoNotAskAgainQuestions(QSettings *settings) +void CheckableMessageBox::resetAllDoNotAskAgainQuestions() { - QTC_ASSERT(settings, return); - settings->beginGroup(QLatin1String(kDoNotAskAgainKey)); - settings->remove(QString()); - settings->endGroup(); + QTC_ASSERT(theSettings, return); + theSettings->beginGroup(QLatin1String(kDoNotAskAgainKey)); + theSettings->remove(QString()); + theSettings->endGroup(); } /*! Returns whether any message boxes from doNotAskAgainQuestion() are suppressed - in the \a settings. + in the settings. */ -bool CheckableMessageBox::hasSuppressedQuestions(QSettings *settings) +bool CheckableMessageBox::hasSuppressedQuestions() { - QTC_ASSERT(settings, return false); - settings->beginGroup(QLatin1String(kDoNotAskAgainKey)); - const bool hasSuppressed = !settings->childKeys().isEmpty() - || !settings->childGroups().isEmpty(); - settings->endGroup(); + QTC_ASSERT(theSettings, return false); + theSettings->beginGroup(QLatin1String(kDoNotAskAgainKey)); + const bool hasSuppressed = !theSettings->childKeys().isEmpty() + || !theSettings->childGroups().isEmpty(); + theSettings->endGroup(); return hasSuppressed; } @@ -222,4 +196,9 @@ QString CheckableMessageBox::msgDoNotShowAgain() return Tr::tr("Do not &show again"); } +void CheckableMessageBox::initialize(QSettings *settings) +{ + theSettings = settings; +} + } // namespace Utils diff --git a/src/libs/utils/checkablemessagebox.h b/src/libs/utils/checkablemessagebox.h index 1f4c84e1a7..3f156b2de1 100644 --- a/src/libs/utils/checkablemessagebox.h +++ b/src/libs/utils/checkablemessagebox.h @@ -5,8 +5,6 @@ #include "utils_global.h" -#include "aspects.h" - #include QT_BEGIN_NAMESPACE @@ -15,208 +13,51 @@ QT_END_NAMESPACE namespace Utils { -class CheckableMessageBoxPrivate; +class QTCREATOR_UTILS_EXPORT CheckableDecider +{ +public: + CheckableDecider() = default; + CheckableDecider(const QString &settingsSubKey); + CheckableDecider(bool *doNotAskAgain); + CheckableDecider(const std::function &should, const std::function &doNot) + : shouldAskAgain(should), doNotAskAgain(doNot) + {} + + std::function shouldAskAgain; + std::function doNotAskAgain; +}; class QTCREATOR_UTILS_EXPORT CheckableMessageBox { public: - struct BoolDecision - { - QString text; - bool &doNotAskAgain; - }; - - struct SettingsDecision - { - QString text; - QSettings *settings; - QString settingsSubKey; - }; - - struct AspectDecision - { - QString text; - BoolAspect &aspect; - }; - - using Decider = std::variant; - - static Decider make_decider(QSettings *settings, - const QString &settingsSubKey, - const QString &text = msgDoNotAskAgain()) - { - return Decider{SettingsDecision{text, settings, settingsSubKey}}; - } - - static Decider make_decider(bool &doNotAskAgain, const QString &text = msgDoNotAskAgain()) - { - return Decider{BoolDecision{text, doNotAskAgain}}; - } - - static Decider make_decider(BoolAspect &aspect, const QString &text = msgDoNotAskAgain()) - { - return Decider{AspectDecision{text, aspect}}; - } - - static QMessageBox::StandardButton question( - QWidget *parent, - const QString &title, - const QString &question, - std::optional decider = std::nullopt, - QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No, - QMessageBox::StandardButton defaultButton = QMessageBox::No, - QMessageBox::StandardButton acceptButton = QMessageBox::Yes, - QMap buttonTextOverrides = {}); - - static QMessageBox::StandardButton question( - QWidget *parent, - const QString &title, - const QString &question, - bool &value, - QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No, - QMessageBox::StandardButton defaultButton = QMessageBox::No, - QMessageBox::StandardButton acceptButton = QMessageBox::Yes, - QMap buttonTextOverrides = {}, - const QString &text = msgDoNotAskAgain()) - { - Decider decider = make_decider(value, text); - return CheckableMessageBox::question(parent, - title, - question, - decider, - buttons, - defaultButton, - acceptButton, - buttonTextOverrides); - } - - static QMessageBox::StandardButton question( - QWidget *parent, - const QString &title, - const QString &question, - QSettings *settings, - const QString &settingsSubKey, - QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No, - QMessageBox::StandardButton defaultButton = QMessageBox::No, - QMessageBox::StandardButton acceptButton = QMessageBox::Yes, - QMap buttonTextOverrides = {}, - const QString &text = msgDoNotAskAgain()) - { - Decider decider = make_decider(settings, settingsSubKey, text); - return CheckableMessageBox::question(parent, - title, - question, - decider, - buttons, - defaultButton, - acceptButton, - buttonTextOverrides); - } - static QMessageBox::StandardButton question( QWidget *parent, const QString &title, const QString &question, - BoolAspect &aspect, + const CheckableDecider &decider, QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No, QMessageBox::StandardButton defaultButton = QMessageBox::No, QMessageBox::StandardButton acceptButton = QMessageBox::Yes, QMap buttonTextOverrides = {}, - const QString &text = msgDoNotAskAgain()) - { - Decider decider = make_decider(aspect, text); - return CheckableMessageBox::question(parent, - title, - question, - decider, - buttons, - defaultButton, - acceptButton, - buttonTextOverrides); - } + const QString &msg = {}); static QMessageBox::StandardButton information( QWidget *parent, const QString &title, const QString &text, - std::optional decider = std::nullopt, - QMessageBox::StandardButtons buttons = QMessageBox::Ok, - QMessageBox::StandardButton defaultButton = QMessageBox::NoButton, - QMap buttonTextOverrides = {}); - - static QMessageBox::StandardButton information( - QWidget *parent, - const QString &title, - const QString &information, - bool &value, - QMessageBox::StandardButtons buttons = QMessageBox::Ok, - QMessageBox::StandardButton defaultButton = QMessageBox::NoButton, - QMap buttonTextOverrides = {}, - const QString &text = msgDoNotAskAgain()) - { - Decider decider = make_decider(value, text); - return CheckableMessageBox::information(parent, - title, - information, - decider, - buttons, - defaultButton, - buttonTextOverrides); - } - - static QMessageBox::StandardButton information( - QWidget *parent, - const QString &title, - const QString &information, - QSettings *settings, - const QString &settingsSubKey, + const CheckableDecider &decider, QMessageBox::StandardButtons buttons = QMessageBox::Ok, QMessageBox::StandardButton defaultButton = QMessageBox::NoButton, QMap buttonTextOverrides = {}, - const QString &text = msgDoNotAskAgain()) - { - Decider decider = make_decider(settings, settingsSubKey, text); - return CheckableMessageBox::information(parent, - title, - information, - decider, - buttons, - defaultButton, - buttonTextOverrides); - } - - static QMessageBox::StandardButton information( - QWidget *parent, - const QString &title, - const QString &information, - BoolAspect &aspect, - QMessageBox::StandardButtons buttons = QMessageBox::Ok, - QMessageBox::StandardButton defaultButton = QMessageBox::NoButton, - QMap buttonTextOverrides = {}, - const QString &text = msgDoNotAskAgain()) - { - Decider decider = make_decider(aspect, text); - return CheckableMessageBox::information(parent, - title, - information, - decider, - buttons, - defaultButton, - buttonTextOverrides); - } - - // check and set "ask again" status - static bool shouldAskAgain(const Decider &decider); - static void doNotAskAgain(Decider &decider); - - static bool shouldAskAgain(QSettings *settings, const QString &key); - static void doNotAskAgain(QSettings *settings, const QString &key); + const QString &msg = {}); // Conversion convenience - static void resetAllDoNotAskAgainQuestions(QSettings *settings); - static bool hasSuppressedQuestions(QSettings *settings); + static void resetAllDoNotAskAgainQuestions(); + static bool hasSuppressedQuestions(); static QString msgDoNotAskAgain(); static QString msgDoNotShowAgain(); + + static void initialize(QSettings *settings); }; } // namespace Utils -- cgit v1.2.3 From d0881f55420062f6898a091090935307c80b617b Mon Sep 17 00:00:00 2001 From: hjk Date: Mon, 22 May 2023 08:15:05 +0200 Subject: Utils: Remove a unused aspect ctor parameters Change-Id: I6e3c6ce7b04a7817f35da10f1975ae2fec62b9e4 Reviewed-by: Christian Stenger --- src/libs/utils/aspects.cpp | 3 +-- src/libs/utils/aspects.h | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index e96014eba1..613a5561ea 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -1426,11 +1426,10 @@ void ColorAspect::setVolatileValue(const QVariant &val) */ -BoolAspect::BoolAspect(const QString &settingsKey) +BoolAspect::BoolAspect() : d(new Internal::BoolAspectPrivate) { setDefaultValue(false); - setSettingsKey(settingsKey); setSpan(2, 1); addDataExtractor(this, &BoolAspect::value, &Data::value); diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index 1086c7445b..ecfc9ee86b 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -213,7 +213,7 @@ class QTCREATOR_UTILS_EXPORT BoolAspect : public BaseAspect Q_OBJECT public: - explicit BoolAspect(const QString &settingsKey = QString()); + BoolAspect(); ~BoolAspect() override; struct Data : BaseAspect::Data -- cgit v1.2.3 From c9638ff64291351b846d28c28f077bbe70f6c1f0 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Fri, 19 May 2023 19:31:22 +0200 Subject: TaskTree: Fix docs about empty Group and different workflows Add tests for empty Group with different workflow policies. Add tests for successful / failing task in a Group with different workflow policies. Change-Id: I50129713f836d2146b119ceb73b5ae43abf01639 Reviewed-by: hjk --- src/libs/solutions/tasking/tasktree.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index 4e9cab7bb9..3ee5f89f9f 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -1180,8 +1180,7 @@ void TaskNode::invokeEndHandler(bool success) sequential mode). \li Immediately finishes with an error. \endlist - If all child tasks finish successfully or the group is empty, the group - finishes with success. + If all child tasks finish successfully, the group finishes with success. \row \li continueOnError \li Similar to stopOnError, but in case any child finishes with @@ -1194,8 +1193,7 @@ void TaskNode::invokeEndHandler(bool success) started yet. \li Finishes with an error when all tasks finish. \endlist - If all tasks finish successfully or the group is empty, the group - finishes with success. + If all tasks finish successfully, the group finishes with success. \row \li stopOnDone \li If a task finishes with success, the group: @@ -1203,8 +1201,7 @@ void TaskNode::invokeEndHandler(bool success) \li Stops running tasks and skips those that it has not started. \li Immediately finishes with success. \endlist - If all tasks finish with an error or the group is empty, the group - finishes with an error. + If all tasks finish with an error, the group finishes with an error. \row \li continueOnDone \li Similar to stopOnDone, but in case any child finishes @@ -1217,22 +1214,22 @@ void TaskNode::invokeEndHandler(bool success) started yet. \li Finishes with success when all tasks finish. \endlist - If all tasks finish with an error or the group is empty, the group - finishes with an error. + If all tasks finish with an error, the group finishes with an error. \row \li stopOnFinished \li The group starts as many tasks as it can. When a task finishes the group stops and reports the task's result. - When the group is empty, it finishes immediately with success. Useful only in parallel mode. In sequential mode, only the first task is started, and when finished, the group finishes too, so the other tasks are ignored. \row \li optional \li The group executes all tasks and ignores their return state. If all - tasks finish or the group is empty, the group finishes with success. + tasks finish, the group finishes with success. \endtable + When the group is empty, it finishes immediately with success, + regardless of its workflow policy. If a child of a group is also a group (in a nested tree), the child group runs its tasks according to its own workflow policy. -- cgit v1.2.3 From d8ed764611cc43bc00534ec5116e8389ab5b1387 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Thu, 18 May 2023 20:52:20 +0200 Subject: TaskTree: Bring the documentation back It vanished after recent move into Tasking lib. Amends f84199f8b70bb03b66a0dbac3ff4dcdb56094d20 Change-Id: I561784c82285de41c2e29c257940678eeeccb5eb Reviewed-by: Eike Ziller --- src/libs/solutions/tasking/tasktree.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index 3ee5f89f9f..5b5ca0890f 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -724,7 +724,13 @@ void TaskNode::invokeEndHandler(bool success) } /*! - \class TaskTree + \namespace Tasking + The Tasking namespace contains a general purpose TaskTree solution. + It depends on Qt only, and doesn't depend on any \QC specific code. +*/ + +/*! + \class Tasking::TaskTree \inheaderfile solutions/tasking/tasktree.h \inmodule QtCreator \ingroup mainclasses -- cgit v1.2.3 From c5818cfe6d272b8f485acbfac7c93120003f1dee Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Mon, 22 May 2023 11:42:32 +0200 Subject: Utils: Fix pathchooser handling for aspects ..especially when entering a path manually. Fixes some soft asserts regarding the call guard and re-allows to type a backslash directly to separate path from sub-path instead of using wild workarounds like adding a dummy character and adding the backslash before this. Change-Id: I8cc8aaccf414d0fd9acc03d7c69e10ddd88dbfd9 Reviewed-by: hjk --- src/libs/utils/aspects.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index 613a5561ea..e2dda5e31c 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -1134,7 +1134,7 @@ void StringAspect::addToLayout(LayoutItem &parent) if (d->m_blockAutoApply) return; d->m_blockAutoApply = true; - setValue(d->m_pathChooserDisplay->filePath().toString()); + setValueQuietly(d->m_pathChooserDisplay->filePath().toString()); d->m_blockAutoApply = false; }; connect(d->m_pathChooserDisplay, &PathChooser::editingFinished, this, setPathChooserValue); @@ -1142,7 +1142,7 @@ void StringAspect::addToLayout(LayoutItem &parent) } else { connect(d->m_pathChooserDisplay, &PathChooser::textChanged, this, [this](const QString &path) { - setValue(path); + setValueQuietly(path); }); } } -- cgit v1.2.3 From 8c288bf05fa8b0df10778421f71f7406fc175bd3 Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Mon, 22 May 2023 14:38:47 +0200 Subject: Doc: Add "\inmodule QtCreator" to \class and \namespace docs To get rid of QDoc warnings. Change-Id: Idd39b7ae4327798c376f424c94d6617bcaee2258 Reviewed-by: hjk --- src/libs/solutions/tasking/tasktree.cpp | 1 + src/libs/utils/ansiescapecodehandler.cpp | 1 + src/libs/utils/aspects.cpp | 1 + src/libs/utils/checkablemessagebox.cpp | 1 + src/libs/utils/classnamevalidatinglineedit.cpp | 1 + src/libs/utils/commandline.cpp | 2 ++ src/libs/utils/completingtextedit.cpp | 4 +++- src/libs/utils/detailswidget.cpp | 1 + src/libs/utils/elidinglabel.cpp | 1 + src/libs/utils/faketooltip.cpp | 1 + src/libs/utils/fancylineedit.cpp | 1 + src/libs/utils/fancymainwindow.cpp | 4 +++- src/libs/utils/fileinprojectfinder.cpp | 1 + src/libs/utils/filenamevalidatinglineedit.cpp | 1 + src/libs/utils/filepath.cpp | 4 +++- src/libs/utils/filesystemwatcher.cpp | 1 + src/libs/utils/fileutils.cpp | 4 +++- src/libs/utils/filewizardpage.cpp | 1 + src/libs/utils/futuresynchronizer.cpp | 4 +++- src/libs/utils/guard.cpp | 4 +++- src/libs/utils/headerviewstretcher.cpp | 1 + src/libs/utils/itemviews.cpp | 4 ++++ src/libs/utils/macroexpander.cpp | 1 + src/libs/utils/navigationtreeview.cpp | 1 + src/libs/utils/optionpushbutton.cpp | 1 + src/libs/utils/parameteraction.cpp | 1 + src/libs/utils/pathlisteditor.cpp | 1 + src/libs/utils/persistentsettings.cpp | 2 ++ src/libs/utils/port.cpp | 4 +++- src/libs/utils/process.cpp | 2 ++ src/libs/utils/processhandle.cpp | 1 + src/libs/utils/projectintropage.cpp | 1 + src/libs/utils/statuslabel.cpp | 1 + src/libs/utils/textfieldcheckbox.cpp | 1 + src/libs/utils/textfieldcombobox.cpp | 1 + src/libs/utils/textfileformat.cpp | 1 + src/libs/utils/treemodel.cpp | 1 + src/libs/utils/utils.qdoc | 1 + src/libs/utils/wizard.cpp | 4 +++- src/libs/utils/wizardpage.cpp | 4 +++- 40 files changed, 64 insertions(+), 9 deletions(-) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index 5b5ca0890f..dd7f2fea98 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -725,6 +725,7 @@ void TaskNode::invokeEndHandler(bool success) /*! \namespace Tasking + \inmodule QtCreator The Tasking namespace contains a general purpose TaskTree solution. It depends on Qt only, and doesn't depend on any \QC specific code. */ diff --git a/src/libs/utils/ansiescapecodehandler.cpp b/src/libs/utils/ansiescapecodehandler.cpp index 0909a3d2c6..91f13d9a25 100644 --- a/src/libs/utils/ansiescapecodehandler.cpp +++ b/src/libs/utils/ansiescapecodehandler.cpp @@ -9,6 +9,7 @@ namespace Utils { /*! \class Utils::AnsiEscapeCodeHandler + \inmodule QtCreator \brief The AnsiEscapeCodeHandler class parses text and extracts ANSI escape codes from it. diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index 613a5561ea..71a61051fc 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -2326,6 +2326,7 @@ void IntegersAspect::setDefaultValue(const QList &value) /*! \class Utils::TextDisplay + \inmodule QtCreator \brief A text display is a phony aspect with the sole purpose of providing some text display using an Utils::InfoLabel in places where otherwise diff --git a/src/libs/utils/checkablemessagebox.cpp b/src/libs/utils/checkablemessagebox.cpp index d09c294175..3c57a73667 100644 --- a/src/libs/utils/checkablemessagebox.cpp +++ b/src/libs/utils/checkablemessagebox.cpp @@ -18,6 +18,7 @@ /*! \class Utils::CheckableMessageBox + \inmodule QtCreator \brief The CheckableMessageBox class implements a message box suitable for questions with a diff --git a/src/libs/utils/classnamevalidatinglineedit.cpp b/src/libs/utils/classnamevalidatinglineedit.cpp index 01235d57e3..3515abef02 100644 --- a/src/libs/utils/classnamevalidatinglineedit.cpp +++ b/src/libs/utils/classnamevalidatinglineedit.cpp @@ -10,6 +10,7 @@ /*! \class Utils::ClassNameValidatingLineEdit + \inmodule QtCreator \brief The ClassNameValidatingLineEdit class implements a line edit that validates a C++ class name and emits a signal diff --git a/src/libs/utils/commandline.cpp b/src/libs/utils/commandline.cpp index 103049ae24..858e6e861f 100644 --- a/src/libs/utils/commandline.cpp +++ b/src/libs/utils/commandline.cpp @@ -41,6 +41,7 @@ namespace Utils { /*! \class Utils::ProcessArgs + \inmodule QtCreator \brief The ProcessArgs class provides functionality for dealing with shell-quoted process arguments. @@ -1398,6 +1399,7 @@ QString ProcessArgs::toString() const /*! \class Utils::CommandLine + \inmodule QtCreator \brief The CommandLine class represents a command line of a QProcess or similar utility. diff --git a/src/libs/utils/completingtextedit.cpp b/src/libs/utils/completingtextedit.cpp index 66d1e7e49f..0fbdd2068b 100644 --- a/src/libs/utils/completingtextedit.cpp +++ b/src/libs/utils/completingtextedit.cpp @@ -13,7 +13,9 @@ static bool isEndOfWordChar(const QChar &c) return !c.isLetterOrNumber() && c.category() != QChar::Punctuation_Connector; } -/*! \class Utils::CompletingTextEdit +/*! + \class Utils::CompletingTextEdit + \inmodule QtCreator \brief The CompletingTextEdit class is a QTextEdit with auto-completion support. diff --git a/src/libs/utils/detailswidget.cpp b/src/libs/utils/detailswidget.cpp index 420ed11b40..ec7a3d22eb 100644 --- a/src/libs/utils/detailswidget.cpp +++ b/src/libs/utils/detailswidget.cpp @@ -20,6 +20,7 @@ /*! \class Utils::DetailsWidget + \inmodule QtCreator \brief The DetailsWidget class implements a button to expand a \e Details area. diff --git a/src/libs/utils/elidinglabel.cpp b/src/libs/utils/elidinglabel.cpp index cf74d5f3d9..0b328a82b1 100644 --- a/src/libs/utils/elidinglabel.cpp +++ b/src/libs/utils/elidinglabel.cpp @@ -9,6 +9,7 @@ /*! \class Utils::ElidingLabel + \inmodule QtCreator \brief The ElidingLabel class is a label suitable for displaying elided text. diff --git a/src/libs/utils/faketooltip.cpp b/src/libs/utils/faketooltip.cpp index 571d202763..c94ce528e0 100644 --- a/src/libs/utils/faketooltip.cpp +++ b/src/libs/utils/faketooltip.cpp @@ -8,6 +8,7 @@ /*! \class Utils::FakeToolTip + \inmodule QtCreator \brief The FakeToolTip class is a widget that pretends to be a tooltip. diff --git a/src/libs/utils/fancylineedit.cpp b/src/libs/utils/fancylineedit.cpp index 03e33c67b6..5a85928b1d 100644 --- a/src/libs/utils/fancylineedit.cpp +++ b/src/libs/utils/fancylineedit.cpp @@ -26,6 +26,7 @@ /*! \class Utils::FancyLineEdit + \inmodule QtCreator \brief The FancyLineEdit class is an enhanced line edit with several opt-in features. diff --git a/src/libs/utils/fancymainwindow.cpp b/src/libs/utils/fancymainwindow.cpp index a19fc6c3c7..323e2508eb 100644 --- a/src/libs/utils/fancymainwindow.cpp +++ b/src/libs/utils/fancymainwindow.cpp @@ -311,7 +311,9 @@ void DockWidget::handleToplevelChanged(bool floating) -/*! \class Utils::FancyMainWindow +/*! + \class Utils::FancyMainWindow + \inmodule QtCreator \brief The FancyMainWindow class is a MainWindow with dock widgets and additional "lock" functionality diff --git a/src/libs/utils/fileinprojectfinder.cpp b/src/libs/utils/fileinprojectfinder.cpp index 6ee3c34bec..6ec36212eb 100644 --- a/src/libs/utils/fileinprojectfinder.cpp +++ b/src/libs/utils/fileinprojectfinder.cpp @@ -40,6 +40,7 @@ static bool checkPath(const FilePath &candidate, int matchLength, /*! \class Utils::FileInProjectFinder + \inmodule QtCreator \brief The FileInProjectFinder class is a helper class to find the \e original file in the project directory for a given file URL. diff --git a/src/libs/utils/filenamevalidatinglineedit.cpp b/src/libs/utils/filenamevalidatinglineedit.cpp index 58112ceb6c..5602e72d1a 100644 --- a/src/libs/utils/filenamevalidatinglineedit.cpp +++ b/src/libs/utils/filenamevalidatinglineedit.cpp @@ -10,6 +10,7 @@ /*! \class Utils::FileNameValidatingLineEdit + \inmodule QtCreator \brief The FileNameValidatingLineEdit class is a control that lets the user choose a (base) file name, based on a QLineEdit. diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 5681837582..049debe9ee 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -37,7 +37,9 @@ static DeviceFileHooks s_deviceHooks; inline bool isWindowsDriveLetter(QChar ch); -/*! \class Utils::FilePath +/*! + \class Utils::FilePath + \inmodule QtCreator \brief The FilePath class is an abstraction for handles to objects in a (possibly remote) file system, similar to a URL or, in the local diff --git a/src/libs/utils/filesystemwatcher.cpp b/src/libs/utils/filesystemwatcher.cpp index d1c2e00311..9a4d7b5693 100644 --- a/src/libs/utils/filesystemwatcher.cpp +++ b/src/libs/utils/filesystemwatcher.cpp @@ -28,6 +28,7 @@ static inline quint64 getFileLimit() /*! \class Utils::FileSystemWatcher + \inmodule QtCreator \brief The FileSystemWatcher class is a file watcher that internally uses a centralized QFileSystemWatcher and enforces limits on Mac OS. diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index 98d190e296..a6cbebf368 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -259,7 +259,9 @@ TempFileSaver::~TempFileSaver() QFile::remove(m_filePath.toString()); } -/*! \class Utils::FileUtils +/*! + \class Utils::FileUtils + \inmodule QtCreator \brief The FileUtils class contains file and directory related convenience functions. diff --git a/src/libs/utils/filewizardpage.cpp b/src/libs/utils/filewizardpage.cpp index cc1f83b696..a883f96056 100644 --- a/src/libs/utils/filewizardpage.cpp +++ b/src/libs/utils/filewizardpage.cpp @@ -8,6 +8,7 @@ /*! \class Utils::FileWizardPage + \inmodule QtCreator \brief The FileWizardPage class is a standard wizard page for a single file letting the user choose name diff --git a/src/libs/utils/futuresynchronizer.cpp b/src/libs/utils/futuresynchronizer.cpp index baef71f4ef..da3347f35e 100644 --- a/src/libs/utils/futuresynchronizer.cpp +++ b/src/libs/utils/futuresynchronizer.cpp @@ -3,7 +3,9 @@ #include "futuresynchronizer.h" -/*! \class Utils::FutureSynchronizer +/*! + \class Utils::FutureSynchronizer + \inmodule QtCreator \brief The FutureSynchronizer is an enhanced version of QFutureSynchronizer. */ diff --git a/src/libs/utils/guard.cpp b/src/libs/utils/guard.cpp index a70faf2c40..0273e7c9b8 100644 --- a/src/libs/utils/guard.cpp +++ b/src/libs/utils/guard.cpp @@ -4,7 +4,9 @@ #include "guard.h" #include "qtcassert.h" -/*! \class Utils::Guard +/*! + \class Utils::Guard + \inmodule QtCreator \brief The Guard class implements a recursive guard with locking mechanism. diff --git a/src/libs/utils/headerviewstretcher.cpp b/src/libs/utils/headerviewstretcher.cpp index 3bc5c1d515..2c0dd00ca0 100644 --- a/src/libs/utils/headerviewstretcher.cpp +++ b/src/libs/utils/headerviewstretcher.cpp @@ -10,6 +10,7 @@ using namespace Utils; /*! \class Utils::HeaderViewStretcher + \inmodule QtCreator \brief The HeaderViewStretcher class fixes QHeaderView to resize all columns to contents, except one diff --git a/src/libs/utils/itemviews.cpp b/src/libs/utils/itemviews.cpp index 8af7eba5a1..27f7cbb428 100644 --- a/src/libs/utils/itemviews.cpp +++ b/src/libs/utils/itemviews.cpp @@ -5,6 +5,7 @@ /*! \class Utils::TreeView + \inmodule QtCreator \brief The TreeView adds setActivationMode to QTreeView to allow for single click/double click behavior on @@ -15,6 +16,7 @@ /*! \class Utils::TreeWidget + \inmodule QtCreator \brief The TreeWidget adds setActivationMode to QTreeWidget to allow for single click/double click behavior on @@ -25,6 +27,7 @@ /*! \class Utils::ListView + \inmodule QtCreator \brief The ListView adds setActivationMode to QListView to allow for single click/double click behavior on @@ -35,6 +38,7 @@ /*! \class Utils::ListWidget + \inmodule QtCreator \brief The ListWidget adds setActivationMode to QListWidget to allow for single click/double click behavior on diff --git a/src/libs/utils/macroexpander.cpp b/src/libs/utils/macroexpander.cpp index 644268d7cf..43d23d2d19 100644 --- a/src/libs/utils/macroexpander.cpp +++ b/src/libs/utils/macroexpander.cpp @@ -104,6 +104,7 @@ using namespace Internal; /*! \class Utils::MacroExpander + \inmodule QtCreator \brief The MacroExpander class manages \QC wide variables, that a user can enter into many string settings. The variables are replaced by an actual value when the string is used, similar to how environment variables are expanded by a shell. diff --git a/src/libs/utils/navigationtreeview.cpp b/src/libs/utils/navigationtreeview.cpp index 8394388f17..7a6bd4be23 100644 --- a/src/libs/utils/navigationtreeview.cpp +++ b/src/libs/utils/navigationtreeview.cpp @@ -9,6 +9,7 @@ /*! \class Utils::NavigationTreeView + \inmodule QtCreator \brief The NavigationTreeView class implements a general TreeView for any sidebar widget. diff --git a/src/libs/utils/optionpushbutton.cpp b/src/libs/utils/optionpushbutton.cpp index f98f9ee5a3..c3993fef4b 100644 --- a/src/libs/utils/optionpushbutton.cpp +++ b/src/libs/utils/optionpushbutton.cpp @@ -11,6 +11,7 @@ namespace Utils { /*! \class Utils::OptionPushButton + \inmodule QtCreator \brief The OptionPushButton class implements a QPushButton for which the menu is only opened if the user presses the menu indicator. diff --git a/src/libs/utils/parameteraction.cpp b/src/libs/utils/parameteraction.cpp index 13efd9fa01..a77154b41f 100644 --- a/src/libs/utils/parameteraction.cpp +++ b/src/libs/utils/parameteraction.cpp @@ -5,6 +5,7 @@ /*! \class Utils::ParameterAction + \inmodule QtCreator \brief The ParameterAction class is intended for actions that act on a 'current', string-type parameter (typically a file name), for example 'Save file %1'. diff --git a/src/libs/utils/pathlisteditor.cpp b/src/libs/utils/pathlisteditor.cpp index fc7beb7597..f2349b275f 100644 --- a/src/libs/utils/pathlisteditor.cpp +++ b/src/libs/utils/pathlisteditor.cpp @@ -16,6 +16,7 @@ /*! \class Utils::PathListEditor + \inmodule QtCreator \brief The PathListEditor class is a control that lets the user edit a list of (directory) paths diff --git a/src/libs/utils/persistentsettings.cpp b/src/libs/utils/persistentsettings.cpp index 1db834bff7..2daf2934c5 100644 --- a/src/libs/utils/persistentsettings.cpp +++ b/src/libs/utils/persistentsettings.cpp @@ -50,6 +50,7 @@ static QRect stringToRectangle(const QString &v) /*! \class Utils::PersistentSettingsReader + \inmodule QtCreator \brief The PersistentSettingsReader class reads a QVariantMap of arbitrary, nested data structures from an XML file. @@ -349,6 +350,7 @@ FilePath PersistentSettingsReader::filePath() /*! \class Utils::PersistentSettingsWriter + \inmodule QtCreator \brief The PersistentSettingsWriter class serializes a QVariantMap of arbitrary, nested data structures to an XML file. diff --git a/src/libs/utils/port.cpp b/src/libs/utils/port.cpp index c41a65335d..7ddd3ea0da 100644 --- a/src/libs/utils/port.cpp +++ b/src/libs/utils/port.cpp @@ -10,7 +10,9 @@ #include -/*! \class Utils::Port +/*! + \class Utils::Port + \inmodule QtCreator \brief The Port class implements a wrapper around a 16 bit port number to be used in conjunction with IP addresses. diff --git a/src/libs/utils/process.cpp b/src/libs/utils/process.cpp index f25bc3d3fd..9a798ec11c 100644 --- a/src/libs/utils/process.cpp +++ b/src/libs/utils/process.cpp @@ -1086,6 +1086,7 @@ ProcessResult ProcessPrivate::interpretExitCode(int exitCode) /*! \class Utils::Process + \inmodule QtCreator \brief The Process class provides functionality for with processes. @@ -1596,6 +1597,7 @@ QString Process::readAllStandardError() /*! \class Utils::SynchronousProcess + \inmodule QtCreator \brief The SynchronousProcess class runs a synchronous process in its own event loop that blocks only user input events. Thus, it allows for the GUI to diff --git a/src/libs/utils/processhandle.cpp b/src/libs/utils/processhandle.cpp index 686b1157ad..06850457d3 100644 --- a/src/libs/utils/processhandle.cpp +++ b/src/libs/utils/processhandle.cpp @@ -7,6 +7,7 @@ namespace Utils { /*! \class Utils::ProcessHandle + \inmodule QtCreator \brief The ProcessHandle class is a helper class to describe a process. Encapsulates parameters of a running process, local (PID) or remote (to be diff --git a/src/libs/utils/projectintropage.cpp b/src/libs/utils/projectintropage.cpp index cb7018943d..dba9a801e4 100644 --- a/src/libs/utils/projectintropage.cpp +++ b/src/libs/utils/projectintropage.cpp @@ -22,6 +22,7 @@ /*! \class Utils::ProjectIntroPage + \inmodule QtCreator \brief The ProjectIntroPage class is the standard wizard page for a project, letting the user choose its name diff --git a/src/libs/utils/statuslabel.cpp b/src/libs/utils/statuslabel.cpp index 32db235b66..26d5a5b361 100644 --- a/src/libs/utils/statuslabel.cpp +++ b/src/libs/utils/statuslabel.cpp @@ -7,6 +7,7 @@ /*! \class Utils::StatusLabel + \inmodule QtCreator \brief The StatusLabel class displays messages for a while with a timeout. */ diff --git a/src/libs/utils/textfieldcheckbox.cpp b/src/libs/utils/textfieldcheckbox.cpp index 5dae8538ae..a00f840422 100644 --- a/src/libs/utils/textfieldcheckbox.cpp +++ b/src/libs/utils/textfieldcheckbox.cpp @@ -7,6 +7,7 @@ namespace Utils { /*! \class Utils::TextFieldCheckBox + \inmodule QtCreator \brief The TextFieldCheckBox class is a aheckbox that plays with \c QWizard::registerField. diff --git a/src/libs/utils/textfieldcombobox.cpp b/src/libs/utils/textfieldcombobox.cpp index 5d72f523a5..790358a1df 100644 --- a/src/libs/utils/textfieldcombobox.cpp +++ b/src/libs/utils/textfieldcombobox.cpp @@ -9,6 +9,7 @@ namespace Utils { /*! \class Utils::TextFieldComboBox + \inmodule QtCreator \brief The TextFieldComboBox class is a non-editable combo box for text editing purposes that plays with \c QWizard::registerField (providing a settable 'text' property). diff --git a/src/libs/utils/textfileformat.cpp b/src/libs/utils/textfileformat.cpp index 28fb243abf..094c6d0d72 100644 --- a/src/libs/utils/textfileformat.cpp +++ b/src/libs/utils/textfileformat.cpp @@ -33,6 +33,7 @@ QDebug operator<<(QDebug d, const TextFileFormat &format) /*! \class Utils::TextFileFormat + \inmodule QtCreator \brief The TextFileFormat class describes the format of a text file and provides autodetection. diff --git a/src/libs/utils/treemodel.cpp b/src/libs/utils/treemodel.cpp index 344a77d837..53477b53c9 100644 --- a/src/libs/utils/treemodel.cpp +++ b/src/libs/utils/treemodel.cpp @@ -895,6 +895,7 @@ void TreeItem::propagateModel(BaseTreeModel *m) /*! \class Utils::TreeModel + \inmodule QtCreator \brief The TreeModel class is a convienience base class for models to use in a QTreeView. diff --git a/src/libs/utils/utils.qdoc b/src/libs/utils/utils.qdoc index c071704331..ae6d39b589 100644 --- a/src/libs/utils/utils.qdoc +++ b/src/libs/utils/utils.qdoc @@ -3,6 +3,7 @@ /*! \namespace Utils + \inmodule QtCreator The Utils namespace contains a collection of utility classes and functions for use by all plugins. diff --git a/src/libs/utils/wizard.cpp b/src/libs/utils/wizard.cpp index 9fca25eaea..ce37d80705 100644 --- a/src/libs/utils/wizard.cpp +++ b/src/libs/utils/wizard.cpp @@ -23,7 +23,9 @@ #include -/*! \class Utils::Wizard +/*! + \class Utils::Wizard + \inmodule QtCreator \brief The Wizard class implements a wizard with a progress bar on the left. diff --git a/src/libs/utils/wizardpage.cpp b/src/libs/utils/wizardpage.cpp index 740f46e8fc..c4730b7011 100644 --- a/src/libs/utils/wizardpage.cpp +++ b/src/libs/utils/wizardpage.cpp @@ -5,7 +5,9 @@ #include "wizard.h" -/*! \class Utils::WizardPage +/*! + \class Utils::WizardPage + \inmodule QtCreator \brief QWizardPage with a couple of improvements. -- cgit v1.2.3 From 1467aedb8dfcb1ee716352164d729f2a0ea28ad5 Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Mon, 22 May 2023 15:03:08 +0200 Subject: Doc: Turn docs for DeviceShell::DeviceShell() into a comment The notation was causing QDoc errors, and according to the developer this is more like a comment to other developers than documentation. Change-Id: Ibbf3f64252f164c361315f8ecf16e3422703bb1c Reviewed-by: Marcus Tillmanns --- src/libs/utils/deviceshell.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/deviceshell.cpp b/src/libs/utils/deviceshell.cpp index f6474721ac..ae983b6f48 100644 --- a/src/libs/utils/deviceshell.cpp +++ b/src/libs/utils/deviceshell.cpp @@ -14,10 +14,10 @@ Q_LOGGING_CATEGORY(deviceShellLog, "qtc.utils.deviceshell", QtWarningMsg) namespace Utils { -/*! +/* * The multiplex script waits for input via stdin. * - * To start a command, a message is send with + * To start a command, a message is sent with * the format " "" \n" * To stop the script, simply send "exit\n" via stdin * -- cgit v1.2.3 From 231397efe2e63741688b7dfc1e5a582ae4903df7 Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Mon, 22 May 2023 15:30:48 +0200 Subject: Doc: Fix qdoc command to "\a env" To fix a qdoc warning. However, qdoc cannot find the function to bind these docs to in any header file. Change-Id: I86b88bbd9e6f0731f8f79587981c46b720b4a91f Reviewed-by: Eike Ziller --- src/libs/utils/commandline.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/commandline.cpp b/src/libs/utils/commandline.cpp index 858e6e861f..b259aaf7e6 100644 --- a/src/libs/utils/commandline.cpp +++ b/src/libs/utils/commandline.cpp @@ -218,7 +218,7 @@ static QStringList doSplitArgsWin(const QString &args, ProcessArgs::SplitError * If \a err is not NULL, stores a status code at the pointer target. For more information, see \l SplitError. - If \env is not NULL, performs variable substitution with the + If \a env is not NULL, performs variable substitution with the given environment. Returns a list of unquoted words or an empty list if an error occurred. @@ -254,7 +254,6 @@ static QStringList doSplitArgsWin(const QString &args, ProcessArgs::SplitError * \c{foo " bar}. */ - static QStringList splitArgsWin(const QString &_args, bool abortOnMeta, ProcessArgs::SplitError *err, const Environment *env, const QString *pwd) -- cgit v1.2.3 From 3dcdbe9069c452e2f0eacb925aa7412e63dc4762 Mon Sep 17 00:00:00 2001 From: hjk Date: Mon, 22 May 2023 10:32:24 +0200 Subject: FancyLineEdit: Mark placeholder text that doesn't pass validation ... also for place holder text. While this omission was apparently intentional, the opposite behavior was used in the Beautifier settings, arguably being a better standard as this makes clear to the user that the pre-selected placeholder value won't be ok to use. Change-Id: Iaf15b1351de929dee57329efdf18d7e831b4f8bc Reviewed-by: Reviewed-by: Eike Ziller --- src/libs/utils/fancylineedit.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/fancylineedit.cpp b/src/libs/utils/fancylineedit.cpp index 03e33c67b6..443845e780 100644 --- a/src/libs/utils/fancylineedit.cpp +++ b/src/libs/utils/fancylineedit.cpp @@ -115,6 +115,7 @@ public: const QColor m_okTextColor; const QColor m_errorTextColor; + const QColor m_placeholderTextColor; QString m_errorMessage; }; @@ -123,7 +124,9 @@ FancyLineEditPrivate::FancyLineEditPrivate(FancyLineEdit *parent) : m_lineEdit(parent), m_completionShortcut(completionShortcut()->key(), parent), m_okTextColor(creatorTheme()->color(Theme::TextColorNormal)), - m_errorTextColor(creatorTheme()->color(Theme::TextColorError)) + m_errorTextColor(creatorTheme()->color(Theme::TextColorError)), + m_placeholderTextColor(creatorTheme()->color(Theme::PalettePlaceholderText)) + { m_completionShortcut.setContext(Qt::WidgetShortcut); connect(completionShortcut(), &CompletionShortcut::keyChanged, @@ -485,15 +488,18 @@ void FancyLineEdit::validate() setToolTip(d->m_errorMessage); d->m_toolTipSet = true; } - // Changed..figure out if valid changed. DisplayingPlaceholderText is not valid, - // but should not show error color. Also trigger on the first change. + // Changed..figure out if valid changed. Also trigger on the first change. + // Invalid DisplayingPlaceholderText shows also error color. if (newState != d->m_state || d->m_firstChange) { const bool validHasChanged = (d->m_state == Valid) != (newState == Valid); d->m_state = newState; d->m_firstChange = false; QPalette p = palette(); - p.setColor(QPalette::Active, QPalette::Text, newState == Invalid ? d->m_errorTextColor : d->m_okTextColor); + p.setColor(QPalette::Active, QPalette::Text, + newState == Invalid ? d->m_errorTextColor : d->m_okTextColor); + p.setColor(QPalette::Active, QPalette::PlaceholderText, + validates ? d->m_placeholderTextColor : d->m_errorTextColor); setPalette(p); if (validHasChanged) -- cgit v1.2.3 From a4c962aa33ac74e0a357f30f9b2f90111f8d865a Mon Sep 17 00:00:00 2001 From: hjk Date: Mon, 22 May 2023 11:35:52 +0200 Subject: Utils: Introduce aspect ctors referring to an "wrapping" AspectContainer This removes the need to manual 'registerAspect' calls in most cases. Whether the containers owns the registered aspects or just references them is still determined by AspectContainer::setOwnsSubAspects() Change-Id: Iadd17c919287f625bf5eb4964de4149d4da5a0f9 Reviewed-by: Alessandro Portale --- src/libs/utils/aspects.cpp | 36 +++++++++++++++++++----------------- src/libs/utils/aspects.h | 18 +++++++++--------- 2 files changed, 28 insertions(+), 26 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index e2dda5e31c..7baa9ba38c 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -92,9 +92,11 @@ public: /*! Constructs a BaseAspect. */ -BaseAspect::BaseAspect() +BaseAspect::BaseAspect(AspectContainer *container) : d(new Internal::BaseAspectPrivate) { + if (container) + container->registerAspect(this); addDataExtractor(this, &BaseAspect::value, &Data::value); } @@ -766,8 +768,8 @@ public: Constructs a StringAspect. */ -StringAspect::StringAspect() - : d(new Internal::StringAspectPrivate) +StringAspect::StringAspect(AspectContainer *container) + : BaseAspect(container), d(new Internal::StringAspectPrivate) { setDefaultValue(QString()); setSpan(2, 1); // Default: Label + something @@ -1359,8 +1361,8 @@ FilePathAspect::FilePathAspect() The color aspect is displayed using a QtColorButton. */ -ColorAspect::ColorAspect() - : d(new Internal::ColorAspectPrivate) +ColorAspect::ColorAspect(AspectContainer *container) + : BaseAspect(container), d(new Internal::ColorAspectPrivate) { setDefaultValue(QColor::fromRgb(0, 0, 0)); setSpan(1, 1); @@ -1426,8 +1428,8 @@ void ColorAspect::setVolatileValue(const QVariant &val) */ -BoolAspect::BoolAspect() - : d(new Internal::BoolAspectPrivate) +BoolAspect::BoolAspect(AspectContainer *container) + : BaseAspect(container), d(new Internal::BoolAspectPrivate) { setDefaultValue(false); setSpan(2, 1); @@ -1605,8 +1607,8 @@ CheckableDecider BoolAspect::checkableDecider() QRadioButtons in a QButtonGroup. */ -SelectionAspect::SelectionAspect() - : d(new Internal::SelectionAspectPrivate) +SelectionAspect::SelectionAspect(AspectContainer *container) + : BaseAspect(container), d(new Internal::SelectionAspectPrivate) { setSpan(2, 1); } @@ -1808,8 +1810,8 @@ QVariant SelectionAspect::itemValueForIndex(int index) const checkable items. */ -MultiSelectionAspect::MultiSelectionAspect() - : d(new Internal::MultiSelectionAspectPrivate(this)) +MultiSelectionAspect::MultiSelectionAspect(AspectContainer *container) + : BaseAspect(container), d(new Internal::MultiSelectionAspectPrivate(this)) { setDefaultValue(QStringList()); setSpan(2, 1); @@ -1915,8 +1917,8 @@ void MultiSelectionAspect::setValue(const QStringList &value) // IntegerAspect -IntegerAspect::IntegerAspect() - : d(new Internal::IntegerAspectPrivate) +IntegerAspect::IntegerAspect(AspectContainer *container) + : BaseAspect(container), d(new Internal::IntegerAspectPrivate) { setDefaultValue(qint64(0)); setSpan(2, 1); @@ -2051,8 +2053,8 @@ void IntegerAspect::setSingleStep(qint64 step) the display of the spin box. */ -DoubleAspect::DoubleAspect() - : d(new Internal::DoubleAspectPrivate) +DoubleAspect::DoubleAspect(AspectContainer *container) + : BaseAspect(container), d(new Internal::DoubleAspectPrivate) { setDefaultValue(double(0)); setSpan(2, 1); @@ -2206,8 +2208,8 @@ TriState TriState::fromVariant(const QVariant &variant) that is a list of strings. */ -StringListAspect::StringListAspect() - : d(new Internal::StringListAspectPrivate) +StringListAspect::StringListAspect(AspectContainer *container) + : BaseAspect(container), d(new Internal::StringListAspectPrivate) { setDefaultValue(QStringList()); } diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index ecfc9ee86b..5fb16356ce 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -45,7 +45,7 @@ class QTCREATOR_UTILS_EXPORT BaseAspect : public QObject Q_OBJECT public: - BaseAspect(); + BaseAspect(AspectContainer *container = nullptr); ~BaseAspect() override; Id id() const; @@ -213,7 +213,7 @@ class QTCREATOR_UTILS_EXPORT BoolAspect : public BaseAspect Q_OBJECT public: - BoolAspect(); + BoolAspect(AspectContainer *container = nullptr); ~BoolAspect() override; struct Data : BaseAspect::Data @@ -257,7 +257,7 @@ class QTCREATOR_UTILS_EXPORT ColorAspect : public BaseAspect Q_OBJECT public: - ColorAspect(); + ColorAspect(AspectContainer *container = nullptr); ~ColorAspect() override; struct Data : BaseAspect::Data @@ -282,7 +282,7 @@ class QTCREATOR_UTILS_EXPORT SelectionAspect : public BaseAspect Q_OBJECT public: - SelectionAspect(); + SelectionAspect(AspectContainer *container = nullptr); ~SelectionAspect() override; void addToLayout(Layouting::LayoutItem &parent) override; @@ -336,7 +336,7 @@ class QTCREATOR_UTILS_EXPORT MultiSelectionAspect : public BaseAspect Q_OBJECT public: - MultiSelectionAspect(); + MultiSelectionAspect(AspectContainer *container = nullptr); ~MultiSelectionAspect() override; void addToLayout(Layouting::LayoutItem &parent) override; @@ -359,7 +359,7 @@ class QTCREATOR_UTILS_EXPORT StringAspect : public BaseAspect Q_OBJECT public: - StringAspect(); + StringAspect(AspectContainer *container = nullptr); ~StringAspect() override; struct Data : BaseAspect::Data @@ -456,7 +456,7 @@ class QTCREATOR_UTILS_EXPORT IntegerAspect : public BaseAspect Q_OBJECT public: - IntegerAspect(); + IntegerAspect(AspectContainer *container = nullptr); ~IntegerAspect() override; void addToLayout(Layouting::LayoutItem &parent) override; @@ -494,7 +494,7 @@ class QTCREATOR_UTILS_EXPORT DoubleAspect : public BaseAspect Q_OBJECT public: - DoubleAspect(); + DoubleAspect(AspectContainer *container = nullptr); ~DoubleAspect() override; void addToLayout(Layouting::LayoutItem &parent) override; @@ -563,7 +563,7 @@ class QTCREATOR_UTILS_EXPORT StringListAspect : public BaseAspect Q_OBJECT public: - StringListAspect(); + StringListAspect(AspectContainer *container = nullptr); ~StringListAspect() override; void addToLayout(Layouting::LayoutItem &parent) override; -- cgit v1.2.3 From c02750428c03439da7d722885639ef578e3fe746 Mon Sep 17 00:00:00 2001 From: hjk Date: Mon, 22 May 2023 17:31:46 +0200 Subject: Utils: Rework FilePath::searchInDirectory Avoid the detour through Environment::search* by copying and adapting some code. Long term the Environment::search* functions may go as FilePath is generally a better entry point into the remote world nowadays. Change-Id: I352d6fb68292d76f29a3454a786322bfe081d53d Reviewed-by: Marcus Tillmanns --- src/libs/utils/filepath.cpp | 113 +++++++++++++++++++++++++++++++++----------- src/libs/utils/filepath.h | 14 ++++-- 2 files changed, 95 insertions(+), 32 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 049debe9ee..9ec738fdf2 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -712,20 +712,39 @@ bool FilePath::isSameFile(const FilePath &other) const return false; } -static FilePaths appendExeExtensions(const Environment &env, const FilePath &executable) -{ - FilePaths execs = {executable}; - if (executable.osType() == OsTypeWindows) { - // Check all the executable extensions on windows: - // PATHEXT is only used if the executable has no extension - if (executable.suffixView().isEmpty()) { - const QStringList extensions = env.expandedValueForKey("PATHEXT").split(';'); - - for (const QString &ext : extensions) - execs << executable.stringAppended(ext.toLower()); +static FilePaths appendExeExtensions(const FilePath &executable, + FilePath::MatchScope matchScope) +{ + FilePaths result = {executable}; + const QStringView suffix = executable.suffixView(); + if (executable.osType() == OsTypeWindows && suffix.isEmpty()) { + switch (matchScope) { + case FilePath::ExactMatchOnly: + break; + case FilePath::WithExeSuffix: + result.append(executable.stringAppended(".exe")); + break; + case FilePath::WithBatSuffix: + result.append(executable.stringAppended(".bat")); + break; + case FilePath::WithExeOrBatSuffix: + result.append(executable.stringAppended(".exe")); + result.append(executable.stringAppended(".bat")); + break; + case FilePath::WithAnySuffix: { + // Check all the executable extensions on windows: + // PATHEXT is only used if the executable has no extension + static const QStringList extensions = Environment::systemEnvironment() + .expandedValueForKey("PATHEXT").split(';'); + for (const QString &ext : extensions) + result.append(executable.stringAppended(ext.toLower())); + break; + } + default: + break; } } - return execs; + return result; } bool FilePath::isSameExecutable(const FilePath &other) const @@ -736,9 +755,8 @@ bool FilePath::isSameExecutable(const FilePath &other) const if (!isSameDevice(other)) return false; - const Environment env = other.deviceEnvironment(); - const FilePaths exe1List = appendExeExtensions(env, *this); - const FilePaths exe2List = appendExeExtensions(env, other); + const FilePaths exe1List = appendExeExtensions(*this, WithAnySuffix); + const FilePaths exe2List = appendExeExtensions(other, WithAnySuffix); for (const FilePath &f1 : exe1List) { for (const FilePath &f2 : exe2List) { if (f1.isSameFile(f2)) @@ -1480,32 +1498,63 @@ FilePath FilePath::withNewPath(const QString &newPath) const assert(fullPath == FilePath::fromUrl("docker://123/usr/bin/make")) \endcode */ -FilePath FilePath::searchInDirectories(const FilePaths &dirs, const FilePathPredicate &filter) const + +FilePath FilePath::searchInDirectories(const FilePaths &dirs, + const FilePathPredicate &filter, + const MatchScope &matchScope) const { - if (isAbsolutePath()) - return *this; - return deviceEnvironment().searchInDirectories(path(), dirs, filter); + if (isEmpty()) + return {}; + + const FilePaths execs = appendExeExtensions(*this, matchScope); + + if (isAbsolutePath()) { + for (const FilePath &filePath : execs) { + if (filePath.isExecutableFile() && (!filter || filter(filePath))) + return filePath; + } + return {}; + } + + QSet alreadyCheckedDirectories; + + for (const FilePath &dir : dirs) { + // Compare the initial size of the set with the size after insertion to check + // if the directory was already checked. + const int initialCount = alreadyCheckedDirectories.count(); + alreadyCheckedDirectories.insert(dir); + const bool wasAlreadyChecked = alreadyCheckedDirectories.count() == initialCount; + + if (dir.isEmpty() || wasAlreadyChecked) + continue; + + for (const FilePath &exe : execs) { + const FilePath filePath = dir / exe.path(); + if (filePath.isExecutableFile() && (!filter || filter(filePath))) + return filePath; + } + } + + return {}; } FilePath FilePath::searchInPath(const FilePaths &additionalDirs, PathAmending amending, - const FilePathPredicate &filter) const + const FilePathPredicate &filter, + const MatchScope &matchScope) const { if (isAbsolutePath()) return *this; - FilePaths directories = deviceEnvironment().path(); - if (needsDevice()) { - directories = Utils::transform(directories, [this](const FilePath &filePath) { - return withNewPath(filePath.path()); - }); - } + + FilePaths directories = devicePathEnvironmentVariable(); + if (!additionalDirs.isEmpty()) { if (amending == AppendToPath) directories.append(additionalDirs); else directories = additionalDirs + directories; } - return searchInDirectories(directories, filter); + return searchInDirectories(directories, filter, matchScope); } Environment FilePath::deviceEnvironment() const @@ -1517,6 +1566,16 @@ Environment FilePath::deviceEnvironment() const return Environment::systemEnvironment(); } +FilePaths FilePath::devicePathEnvironmentVariable() const +{ + FilePaths result = deviceEnvironment().path(); + if (needsDevice()) { + for (FilePath &dir : result) + dir.setParts(this->scheme(), this->host(), dir.path()); + } + return result; +} + QString FilePath::formatFilePaths(const FilePaths &files, const QString &separator) { const QStringList nativeFiles = transform(files, &FilePath::toUserOutput); diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h index 142542b734..2a41c6c615 100644 --- a/src/libs/utils/filepath.h +++ b/src/libs/utils/filepath.h @@ -161,9 +161,8 @@ public: [[nodiscard]] FilePath withExecutableSuffix() const; [[nodiscard]] FilePath relativeChildPath(const FilePath &parent) const; [[nodiscard]] FilePath relativePathFrom(const FilePath &anchor) const; - [[nodiscard]] FilePath searchInDirectories(const FilePaths &dirs, - const FilePathPredicate &filter = {}) const; [[nodiscard]] Environment deviceEnvironment() const; + [[nodiscard]] FilePaths devicePathEnvironmentVariable() const; [[nodiscard]] FilePath withNewPath(const QString &newPath) const; [[nodiscard]] FilePath withNewMappedPath(const FilePath &newPath) const; @@ -183,12 +182,17 @@ public: const FileFilter &filter); enum PathAmending { AppendToPath, PrependToPath }; + enum MatchScope { ExactMatchOnly, WithExeSuffix, WithBatSuffix, + WithExeOrBatSuffix, WithAnySuffix }; + + [[nodiscard]] FilePath searchInDirectories(const FilePaths &dirs, + const FilePathPredicate &filter = {}, + const MatchScope &matchScope = {}) const; [[nodiscard]] FilePath searchInPath(const FilePaths &additionalDirs = {}, PathAmending = AppendToPath, - const FilePathPredicate &filter = {}) const; + const FilePathPredicate &filter = {}, + const MatchScope &matchScope = {}) const; - enum MatchScope { ExactMatchOnly, WithExeSuffix, WithBatSuffix, - WithExeOrBatSuffix, WithAnySuffix }; std::optional refersToExecutableFile(MatchScope considerScript) const; [[nodiscard]] expected_str tmpDir() const; -- cgit v1.2.3 From ee6789c523174efb872a7f5a97da01b93eb56420 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Mon, 22 May 2023 13:21:57 +0200 Subject: Centralize a PluginManager::isShuttingDown() status Instead of keeping track of this in plugins individually. Change-Id: Ia2650f0f647d4a63d2010cef688aa56f6020c338 Reviewed-by: hjk --- src/libs/extensionsystem/pluginmanager.cpp | 6 ++++++ src/libs/extensionsystem/pluginmanager.h | 1 + src/libs/extensionsystem/pluginmanager_p.h | 1 + 3 files changed, 8 insertions(+) (limited to 'src/libs') diff --git a/src/libs/extensionsystem/pluginmanager.cpp b/src/libs/extensionsystem/pluginmanager.cpp index 3ee9951055..ae2dbf8903 100644 --- a/src/libs/extensionsystem/pluginmanager.cpp +++ b/src/libs/extensionsystem/pluginmanager.cpp @@ -1034,6 +1034,7 @@ void PluginManagerPrivate::readSettings() */ void PluginManagerPrivate::stopAll() { + m_isShuttingDown = true; if (delayedInitializeTimer && delayedInitializeTimer->isActive()) { delayedInitializeTimer->stop(); delete delayedInitializeTimer; @@ -1839,6 +1840,11 @@ bool PluginManager::isInitializationDone() return d->m_isInitializationDone; } +bool PluginManager::isShuttingDown() +{ + return d->m_isShuttingDown; +} + /*! Retrieves one object with \a name from the object pool. \sa addObject() diff --git a/src/libs/extensionsystem/pluginmanager.h b/src/libs/extensionsystem/pluginmanager.h index a56cb7ba9d..ecd0ee70b7 100644 --- a/src/libs/extensionsystem/pluginmanager.h +++ b/src/libs/extensionsystem/pluginmanager.h @@ -129,6 +129,7 @@ public: static QString platformName(); static bool isInitializationDone(); + static bool isShuttingDown(); static void remoteArguments(const QString &serializedArguments, QObject *socket); static void shutdown(); diff --git a/src/libs/extensionsystem/pluginmanager_p.h b/src/libs/extensionsystem/pluginmanager_p.h index 86c3a6c362..c7a4291a6b 100644 --- a/src/libs/extensionsystem/pluginmanager_p.h +++ b/src/libs/extensionsystem/pluginmanager_p.h @@ -124,6 +124,7 @@ public: bool m_isInitializationDone = false; bool enableCrashCheck = true; + bool m_isShuttingDown = false; QHash> m_scenarios; QString m_requestedScenario; -- cgit v1.2.3 From f776f6f3ec524eab27a59241482322223137e59b Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Tue, 23 May 2023 09:13:26 +0200 Subject: Doc: Add Layouting namespace to make its classes' docs visible Change-Id: I3b713f4c9bd65f279e41368ce5ce1a25ea17f176 Reviewed-by: hjk --- src/libs/utils/layoutbuilder.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index 43dd68c790..017a147897 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -175,6 +175,14 @@ private: int m_vSpace; }; +/*! + \namespace Layouting + \inmodule QtCreator + + \brief The Layouting namespace contains classes for use with layout builders. +*/ + + /*! \class Layouting::LayoutItem \inmodule QtCreator @@ -469,18 +477,18 @@ void doAddWidget(LayoutBuilder &builder, QWidget *widget) \class Layouting::Space \inmodule QtCreator - \brief The Layouting::Space class represents some empty space in a layout. + \brief The Space class represents some empty space in a layout. */ /*! \class Layouting::Stretch \inmodule QtCreator - \brief The Layouting::Stretch class represents some stretch in a layout. + \brief The Stretch class represents some stretch in a layout. */ /*! - \class LayoutBuilder + \class Layouting::LayoutBuilder \inmodule QtCreator \brief The LayoutBuilder class provides a convenient way to fill \c QFormLayout -- cgit v1.2.3 From 8e7ad13ad2f58469d9c11a8d60e5a4aed724cdfd Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Tue, 23 May 2023 08:49:27 +0200 Subject: Fix qbs build Was broken in an impressive number of ways by latest Design Studio merge. Change-Id: I25f56827074a8c16a1a9c18884e1f63e8eaf6ef1 Reviewed-by: Christian Stenger --- src/libs/advanceddockingsystem/advanceddockingsystem.qbs | 3 ++- src/libs/utils/utils.qbs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/advanceddockingsystem/advanceddockingsystem.qbs b/src/libs/advanceddockingsystem/advanceddockingsystem.qbs index 9778f62949..751449cf15 100644 --- a/src/libs/advanceddockingsystem/advanceddockingsystem.qbs +++ b/src/libs/advanceddockingsystem/advanceddockingsystem.qbs @@ -7,7 +7,7 @@ QtcLibrary { cpp.defines: base.concat("ADVANCEDDOCKINGSYSTEM_LIBRARY") cpp.includePaths: base.concat([".", linux.prefix]) - Depends { name: "Qt"; submodules: ["widgets", "core", "gui"] } + Depends { name: "Qt"; submodules: ["widgets", "xml"] } Depends { name: "Utils" } Group { @@ -31,6 +31,7 @@ QtcLibrary { "floatingdockcontainer.cpp", "floatingdockcontainer.h", "floatingdragpreview.cpp", "floatingdragpreview.h", "iconprovider.cpp", "iconprovider.h", + "workspace.cpp", "workspace.h", "workspacedialog.cpp", "workspacedialog.h", "workspacemodel.cpp", "workspacemodel.h", "workspaceview.cpp", "workspaceview.h", diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index 3196d9b34b..298b23c1e9 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -337,7 +337,7 @@ Project { "headerviewstretcher.h", "uncommentselection.cpp", "uncommentselection.h", - "uniqueobjectptr.h" + "uniqueobjectptr.h", "unixutils.cpp", "unixutils.h", "url.cpp", -- cgit v1.2.3 From 9c6e3b724adbd4c45d8bae414e4012c6a29e1675 Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Tue, 23 May 2023 09:24:20 +0200 Subject: Doc: Add \brief commands to namespace docs to show them in tables ...and at the top of the topics. Change-Id: I3d521a351e06d765a79304db897c5cffa9fee0df Reviewed-by: Jarek Kobus --- src/libs/solutions/tasking/tasktree.cpp | 6 ++++-- src/libs/utils/utils.qdoc | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index dd7f2fea98..6d3e7eb95b 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -726,8 +726,10 @@ void TaskNode::invokeEndHandler(bool success) /*! \namespace Tasking \inmodule QtCreator - The Tasking namespace contains a general purpose TaskTree solution. - It depends on Qt only, and doesn't depend on any \QC specific code. + \brief The Tasking namespace contains a general purpose TaskTree solution. + + The Tasking namespace depends on Qt only, and doesn't depend on any \QC + specific code. */ /*! diff --git a/src/libs/utils/utils.qdoc b/src/libs/utils/utils.qdoc index ae6d39b589..bc9f843498 100644 --- a/src/libs/utils/utils.qdoc +++ b/src/libs/utils/utils.qdoc @@ -5,6 +5,6 @@ \namespace Utils \inmodule QtCreator - The Utils namespace contains a collection of utility classes and functions for use by all + \brief The Utils namespace contains a collection of utility classes and functions for use by all plugins. */ -- cgit v1.2.3 From 0c0fb744e068d08f50cfa6678f814128fd63c0aa Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Tue, 23 May 2023 11:03:31 +0200 Subject: Utils: Fix missing include Change-Id: I98e5a00f9f4f09cc9c09f3d0436ba5dfc20e3b31 Reviewed-by: Marcus Tillmanns --- src/libs/utils/checkablemessagebox.h | 1 + 1 file changed, 1 insertion(+) (limited to 'src/libs') diff --git a/src/libs/utils/checkablemessagebox.h b/src/libs/utils/checkablemessagebox.h index 3f156b2de1..297ff22b62 100644 --- a/src/libs/utils/checkablemessagebox.h +++ b/src/libs/utils/checkablemessagebox.h @@ -5,6 +5,7 @@ #include "utils_global.h" +#include #include QT_BEGIN_NAMESPACE -- cgit v1.2.3 From ce0ab1cd27ddf10352912a9ec51e2aca9556c0a3 Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Mon, 22 May 2023 15:57:00 +0200 Subject: Doc: Replace \p with \a and remove \param The qdoc command for arguments/parameters is \a. It is enough to place it somewhere in the text to make qdoc happy. Change-Id: I164fbd63277787a68b0216ad3fbbed768b975d91 Reviewed-by: Eike Ziller Reviewed-by: Reviewed-by: Marcus Tillmanns --- src/libs/utils/filepath.cpp | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 9ec738fdf2..fa91a09d05 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -1327,11 +1327,11 @@ bool FilePath::startsWithDriveLetter() const /*! \brief Relative path from \a parent to this. - Returns a empty FilePath if this is not a child of \p parent. + Returns a empty \c FilePath if this is not a child of \a parent. + \a parent is the Parent to calculate the relative path to. That is, this never returns a path starting with "../" - \param parent The Parent to calculate the relative path to. - Returns The relative path of this to \p parent if this is a child of \p parent. + Returns the relative path of this to \a parent if this is a child of \a parent. */ FilePath FilePath::relativeChildPath(const FilePath &parent) const { @@ -1728,16 +1728,16 @@ qint64 FilePath::bytesAvailable() const } /*! - \brief Checks if this is newer than \p timeStamp + \brief Checks if this is newer than \a timeStamp. - \param timeStamp The time stamp to compare with - Returns true if this is newer than \p timeStamp. - If this is a directory, the function will recursively check all files and return - true if one of them is newer than \a timeStamp. If this is a single file, true will - be returned if the file is newer than \a timeStamp. + The time stamp \a timeStamp to compare with. + Returns \c true if this is newer than \a timeStamp. + If this is a directory, the function will recursively check all files and return + \c true if one of them is newer than \a timeStamp. If this is a single file, \c true will + be returned if the file is newer than \a timeStamp. Returns whether at least one file in \a filePath has a newer date than - \p timeStamp. + \a timeStamp. */ bool FilePath::isNewerThan(const QDateTime &timeStamp) const { @@ -1907,7 +1907,7 @@ QString FilePath::shortNativePath() const } /*! - \brief Checks whether the path is relative + \brief Checks whether the path is relative. Returns true if the path is relative. */ @@ -1924,11 +1924,9 @@ bool FilePath::isRelativePath() const } /*! - \brief Appends the tail to this, if the tail is a relative path. + \brief Appends the \a tail to this, if the tail is a relative path. - \param tail The tail to append. - - Returns tail if tail is absolute, otherwise this + tail. + Returns the tail if the tail is absolute, otherwise this + tail. */ FilePath FilePath::resolvePath(const FilePath &tail) const { @@ -1940,11 +1938,9 @@ FilePath FilePath::resolvePath(const FilePath &tail) const } /*! - \brief Appends the tail to this, if the tail is a relative path. - - \param tail The tail to append. + \brief Appends the \a tail to this, if the tail is a relative path. - Returns tail if tail is absolute, otherwise this + tail. + Returns the tail if the tail is absolute, otherwise this + tail. */ FilePath FilePath::resolvePath(const QString &tail) const { @@ -1962,7 +1958,7 @@ expected_str FilePath::localSource() const } /*! - \brief Cleans path part similar to QDir::cleanPath() + \brief Cleans path part similar to \c QDir::cleanPath(). \list \li directory separators normalized (that is, platform-native -- cgit v1.2.3 From e45609194d81ae13eb0b531351b42d1b60608f94 Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Mon, 22 May 2023 15:12:07 +0200 Subject: Doc: Remove period from the end of \sa The HTML generator adds it automatically and qdoc warns about it. Change-Id: I3917d7d23b16446e28ce7bfeb8f9195f21efd7fc Reviewed-by: hjk Reviewed-by: --- src/libs/utils/aspects.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index 7d509a2107..59c0ee3f8b 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -2441,7 +2441,7 @@ void AspectContainer::registerAspects(const AspectContainer &aspects) /*! Retrieves a BaseAspect with a given \a id, or nullptr if no such aspect is contained. - \sa BaseAspect. + \sa BaseAspect */ BaseAspect *AspectContainer::aspect(Id id) const { -- cgit v1.2.3 From ae26fa0dd736768477f00731dc2a66ca1d0d9b59 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 23 May 2023 09:14:23 +0200 Subject: Utils: Add a FilePath::searchAllInDirectories A variation that does not stop on the first found item. Useful for auto-detection scenarios. Use "WithAnySuffix" as default to cover .cmd and .bat etc. Change-Id: I48f36eff06699c046e34c8e2646546bcff20ae8b Reviewed-by: Marcus Tillmanns --- src/libs/utils/filepath.cpp | 74 ++++++++++++++++++++++++++++++++++++++++----- src/libs/utils/filepath.h | 11 +++++-- 2 files changed, 75 insertions(+), 10 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index fa91a09d05..2df5783034 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -1538,25 +1538,83 @@ FilePath FilePath::searchInDirectories(const FilePaths &dirs, return {}; } -FilePath FilePath::searchInPath(const FilePaths &additionalDirs, - PathAmending amending, - const FilePathPredicate &filter, - const MatchScope &matchScope) const +FilePaths FilePath::searchAllInDirectories(const FilePaths &dirs, + const FilePathPredicate &filter, + const MatchScope &matchScope) const { - if (isAbsolutePath()) - return *this; + if (isEmpty()) + return {}; + + const FilePaths execs = appendExeExtensions(*this, matchScope); + + FilePaths result; + if (isAbsolutePath()) { + for (const FilePath &filePath : execs) { + if (filePath.isExecutableFile() && (!filter || filter(filePath))) + result.append(filePath); + } + return result; + } - FilePaths directories = devicePathEnvironmentVariable(); + QSet alreadyCheckedDirectories; + + for (const FilePath &dir : dirs) { + // Compare the initial size of the set with the size after insertion to check + // if the directory was already checked. + const int initialCount = alreadyCheckedDirectories.count(); + alreadyCheckedDirectories.insert(dir); + const bool wasAlreadyChecked = alreadyCheckedDirectories.count() == initialCount; + + if (dir.isEmpty() || wasAlreadyChecked) + continue; + + for (const FilePath &exe : execs) { + const FilePath filePath = dir / exe.path(); + if (filePath.isExecutableFile() && (!filter || filter(filePath))) + result.append(filePath); + } + } + + return result; +} + +static FilePaths dirsFromPath(const FilePath &anchor, + const FilePaths &additionalDirs, + FilePath::PathAmending amending) +{ + FilePaths directories = anchor.devicePathEnvironmentVariable(); if (!additionalDirs.isEmpty()) { - if (amending == AppendToPath) + if (amending == FilePath::AppendToPath) directories.append(additionalDirs); else directories = additionalDirs + directories; } + + return directories; +} + +FilePath FilePath::searchInPath(const FilePaths &additionalDirs, + PathAmending amending, + const FilePathPredicate &filter, + MatchScope matchScope) const +{ + if (isAbsolutePath()) + return *this; + + const FilePaths directories = dirsFromPath(*this, additionalDirs, amending); return searchInDirectories(directories, filter, matchScope); } +FilePaths FilePath::searchAllInPath(const FilePaths &additionalDirs, + PathAmending amending, + const FilePathPredicate &filter, + MatchScope matchScope) const +{ + const FilePaths directories = dirsFromPath(*this, additionalDirs, amending); + return searchAllInDirectories(directories, filter, matchScope); +} + Environment FilePath::deviceEnvironment() const { if (needsDevice()) { diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h index 2a41c6c615..4462b473cd 100644 --- a/src/libs/utils/filepath.h +++ b/src/libs/utils/filepath.h @@ -187,11 +187,18 @@ public: [[nodiscard]] FilePath searchInDirectories(const FilePaths &dirs, const FilePathPredicate &filter = {}, - const MatchScope &matchScope = {}) const; + const MatchScope &matchScope = WithAnySuffix) const; + [[nodiscard]] FilePaths searchAllInDirectories(const FilePaths &dirs, + const FilePathPredicate &filter = {}, + const MatchScope &matchScope = WithAnySuffix) const; [[nodiscard]] FilePath searchInPath(const FilePaths &additionalDirs = {}, PathAmending = AppendToPath, const FilePathPredicate &filter = {}, - const MatchScope &matchScope = {}) const; + MatchScope matchScope = WithAnySuffix) const; + [[nodiscard]] FilePaths searchAllInPath(const FilePaths &additionalDirs = {}, + PathAmending = AppendToPath, + const FilePathPredicate &filter = {}, + MatchScope matchScope = WithAnySuffix) const; std::optional refersToExecutableFile(MatchScope considerScript) const; -- cgit v1.2.3 From b6a1935562360adf4b29bd17907a008497c702b7 Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Tue, 23 May 2023 11:18:26 +0200 Subject: Doc: Fix qdoc warning caused by a changed argument name Change-Id: Id2f28994f6cd1829bfc4bdc23c87aabe40f44f2c Reviewed-by: hjk --- src/libs/utils/layoutbuilder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index 017a147897..b603b0bf8f 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -549,7 +549,7 @@ void LayoutItem::addItems(const LayoutItems &items) } /*! - Attach the constructed layout to the provided \c QWidget \a parent. + Attaches the constructed layout to the provided QWidget \a w. This operation can only be performed once per LayoutBuilder instance. */ -- cgit v1.2.3 From a0f64b133d3b1443a1e2638fa30c05b5b47e46b0 Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Tue, 23 May 2023 14:09:48 +0200 Subject: Doc: Fix qdoc warnings in aspects docs Add \a commands and mark things \internal if qdoc cannot find them. Change-Id: If7a9303e5053af4eb58a08caafd53ffa6ee604b5 Reviewed-by: hjk --- src/libs/utils/aspects.cpp | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index 59c0ee3f8b..54eba86eaf 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -90,7 +90,9 @@ public: */ /*! - Constructs a BaseAspect. + Constructs a base aspect. + + If \a container is non-null, the aspect is made known to the container. */ BaseAspect::BaseAspect(AspectContainer *container) : d(new Internal::BaseAspectPrivate) @@ -124,9 +126,9 @@ QVariant BaseAspect::value() const } /*! - Sets value. + Sets \a value. - Emits changed() if the value changed. + Emits \c changed() if the value changed. */ void BaseAspect::setValue(const QVariant &value) { @@ -137,7 +139,7 @@ void BaseAspect::setValue(const QVariant &value) } /*! - Sets value without emitting changed() + Sets \a value without emitting \c changed(). Returns whether the value changed. */ @@ -155,7 +157,7 @@ QVariant BaseAspect::defaultValue() const } /*! - Sets a default value and the current value for this aspect. + Sets a default \a value and the current value for this aspect. \note The current value will be set silently to the same value. It is reasonable to only set default values in the setup phase @@ -339,7 +341,7 @@ bool BaseAspect::isAutoApply() const } /*! - Sets auto-apply mode. When auto-apply mode is on, user interaction to this + Sets auto-apply mode. When auto-apply mode is \a on, user interaction to this aspect's widget will not modify the \c value of the aspect until \c apply() is called programmatically. @@ -370,7 +372,7 @@ QString BaseAspect::settingsKey() const } /*! - Sets the key to be used when accessing the settings. + Sets the \a key to be used when accessing the settings. \sa settingsKey() */ @@ -380,7 +382,7 @@ void BaseAspect::setSettingsKey(const QString &key) } /*! - Sets the key and group to be used when accessing the settings. + Sets the \a key and \a group to be used when accessing the settings. \sa settingsKey() */ @@ -419,8 +421,8 @@ QAction *BaseAspect::action() } /*! - Adds the visual representation of this aspect to a layout using - a layout builder. + Adds the visual representation of this aspect to the layout with the + specified \a parent using a layout builder. */ void BaseAspect::addToLayout(LayoutItem &) { @@ -528,7 +530,7 @@ void BaseAspect::saveToMap(QVariantMap &data, const QVariant &value, } /*! - Retrieves the internal value of this BaseAspect from a \c QVariantMap. + Retrieves the internal value of this BaseAspect from the QVariantMap \a map. */ void BaseAspect::fromMap(const QVariantMap &map) { @@ -537,7 +539,7 @@ void BaseAspect::fromMap(const QVariantMap &map) } /*! - Stores the internal value of this BaseAspect into a \c QVariantMap. + Stores the internal value of this BaseAspect into the QVariantMap \a map. */ void BaseAspect::toMap(QVariantMap &map) const { @@ -765,7 +767,7 @@ public: */ /*! - Constructs a StringAspect. + Constructs the string aspect \a container. */ StringAspect::StringAspect(AspectContainer *container) @@ -800,7 +802,7 @@ QString StringAspect::value() const } /*! - Sets the \a value of this StringAspect from an ordinary \c QString. + Sets the value, \a val, of this StringAspect from an ordinary \c QString. */ void StringAspect::setValue(const QString &val) { @@ -2149,7 +2151,7 @@ void DoubleAspect::setSingleStep(double step) /*! - \class Utils::BaseTristateAspect + \class Utils::TriStateAspect \inmodule QtCreator \brief A tristate aspect is a property of some object that can have @@ -2273,6 +2275,7 @@ void StringListAspect::removeValues(const QStringList &values) /*! \class Utils::IntegerListAspect + \internal \inmodule QtCreator \brief A string list aspect represents a property of some object -- cgit v1.2.3 From 962d9d55d0944f8ceba6e45baa60cb7bb309dc6f Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 23 May 2023 15:46:44 +0200 Subject: Utils: Also allow FilePathAspects to auto-register Task-number: QTCREATORBUG-29167 Change-Id: Iba301764072cc1ca3d3a335a8106ab121733b387 Reviewed-by: Alessandro Portale --- src/libs/utils/aspects.cpp | 3 ++- src/libs/utils/aspects.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index 54eba86eaf..e900a680fc 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -1347,7 +1347,8 @@ void StringAspect::makeCheckable(CheckBoxPlacement checkBoxPlacement, */ -FilePathAspect::FilePathAspect() +FilePathAspect::FilePathAspect(AspectContainer *container) + : StringAspect(container) { setDisplayStyle(PathChooserDisplay); } diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index 5fb16356ce..a55e8bff74 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -446,7 +446,7 @@ protected: class QTCREATOR_UTILS_EXPORT FilePathAspect : public StringAspect { public: - FilePathAspect(); + FilePathAspect(AspectContainer *container = nullptr); FilePath operator()() const { return filePath(); } }; -- cgit v1.2.3 From 815a05a94ab1eace8c63e2f08ee89889206367a9 Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Tue, 23 May 2023 13:58:49 +0200 Subject: QmlEditorWidgets: Fix color popup for Rectangle widget The color buttons for background color and border color did not open the color picker popup. The buttons need to be set checkable, which broke while "inlining" .ui files. Amends: 200a66644ef3d02bfb9969f6e9010f35fbec62ae Fixes: QTCREATORBUG-29195 Change-Id: Icd71df1bcfad6472a90691d2c353f7039b52004e Reviewed-by: hjk --- src/libs/qmleditorwidgets/contextpanewidgetrectangle.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'src/libs') diff --git a/src/libs/qmleditorwidgets/contextpanewidgetrectangle.cpp b/src/libs/qmleditorwidgets/contextpanewidgetrectangle.cpp index 52afeff187..5da883f426 100644 --- a/src/libs/qmleditorwidgets/contextpanewidgetrectangle.cpp +++ b/src/libs/qmleditorwidgets/contextpanewidgetrectangle.cpp @@ -33,13 +33,19 @@ ContextPaneWidgetRectangle::ContextPaneWidgetRectangle(QWidget *parent) return result; }; + const auto colorButton = [] { + auto result = new ColorButton; + result->setCheckable(true); + result->setShowArrow(false); + return result; + }; + m_gradientLabel = new QLabel(Tr::tr("Gradient")); m_gradientLabel->setAlignment(Qt::AlignBottom); m_gradientLine = new GradientLine; m_gradientLine->setMinimumWidth(240); - m_colorColorButton = new ColorButton; - m_colorColorButton->setShowArrow(false); + m_colorColorButton = colorButton(); m_colorSolid = toolButton("icon_color_solid"); m_colorGradient = toolButton("icon_color_gradient"); m_colorNone = toolButton("icon_color_none"); @@ -48,8 +54,7 @@ ContextPaneWidgetRectangle::ContextPaneWidgetRectangle(QWidget *parent) colorButtons->addButton(m_colorGradient); colorButtons->addButton(m_colorNone); - m_borderColorButton = new ColorButton; - m_borderColorButton->setShowArrow(false); + m_borderColorButton = colorButton(); m_borderSolid = toolButton("icon_color_solid"); m_borderNone = toolButton("icon_color_none"); auto borderButtons = new QButtonGroup(this); -- cgit v1.2.3 From 861b98a76a4f51e2bac1159d0f1f194f12ceed25 Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Wed, 24 May 2023 00:29:46 +0300 Subject: FilePath: Remove const ref for enum argument Change-Id: I86c5466cdcd8f74816456e70463157f28a4e37bc Reviewed-by: Marcus Tillmanns --- src/libs/utils/filepath.cpp | 4 ++-- src/libs/utils/filepath.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 2df5783034..31d24f14b0 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -1501,7 +1501,7 @@ FilePath FilePath::withNewPath(const QString &newPath) const FilePath FilePath::searchInDirectories(const FilePaths &dirs, const FilePathPredicate &filter, - const MatchScope &matchScope) const + MatchScope matchScope) const { if (isEmpty()) return {}; @@ -1540,7 +1540,7 @@ FilePath FilePath::searchInDirectories(const FilePaths &dirs, FilePaths FilePath::searchAllInDirectories(const FilePaths &dirs, const FilePathPredicate &filter, - const MatchScope &matchScope) const + MatchScope matchScope) const { if (isEmpty()) return {}; diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h index 4462b473cd..f313ba5ff9 100644 --- a/src/libs/utils/filepath.h +++ b/src/libs/utils/filepath.h @@ -187,10 +187,10 @@ public: [[nodiscard]] FilePath searchInDirectories(const FilePaths &dirs, const FilePathPredicate &filter = {}, - const MatchScope &matchScope = WithAnySuffix) const; + MatchScope matchScope = WithAnySuffix) const; [[nodiscard]] FilePaths searchAllInDirectories(const FilePaths &dirs, const FilePathPredicate &filter = {}, - const MatchScope &matchScope = WithAnySuffix) const; + MatchScope matchScope = WithAnySuffix) const; [[nodiscard]] FilePath searchInPath(const FilePaths &additionalDirs = {}, PathAmending = AppendToPath, const FilePathPredicate &filter = {}, -- cgit v1.2.3 From 00d156c8ca12e1b30c66a79b30d4f726a21f31c0 Mon Sep 17 00:00:00 2001 From: Mitch Curtis Date: Fri, 19 May 2023 16:55:46 +0800 Subject: Fix exclusion pattern tooltip text It says "included" instead of "excluded". Change-Id: Ib74f2adbc6e6f10a9ff79662e5be609a89fe89d6 Reviewed-by: Eike Ziller --- src/libs/utils/filesearch.cpp | 8 +++++--- src/libs/utils/filesearch.h | 7 ++++++- 2 files changed, 11 insertions(+), 4 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/filesearch.cpp b/src/libs/utils/filesearch.cpp index fe65756276..073b6057a9 100644 --- a/src/libs/utils/filesearch.cpp +++ b/src/libs/utils/filesearch.cpp @@ -540,10 +540,12 @@ QString msgExclusionPatternLabel() return Tr::tr("Excl&usion pattern:"); } -QString msgFilePatternToolTip() +QString msgFilePatternToolTip(InclusionType inclusionType) { - return Tr::tr("List of comma separated wildcard filters. " - "Files with file name or full file path matching any filter are included."); + return Tr::tr("List of comma separated wildcard filters. ") + + (inclusionType == InclusionType::Included + ? Tr::tr("Files with file name or full file path matching any filter are included.") + : Tr::tr("Files with file name or full file path matching any filter are excluded.")); } QString matchCaseReplacement(const QString &originalText, const QString &replaceText) diff --git a/src/libs/utils/filesearch.h b/src/libs/utils/filesearch.h index 7bb7af5d33..c3d81faa12 100644 --- a/src/libs/utils/filesearch.h +++ b/src/libs/utils/filesearch.h @@ -40,8 +40,13 @@ QString msgFilePatternLabel(); QTCREATOR_UTILS_EXPORT QString msgExclusionPatternLabel(); +enum class InclusionType { + Included, + Excluded +}; + QTCREATOR_UTILS_EXPORT -QString msgFilePatternToolTip(); +QString msgFilePatternToolTip(InclusionType inclusionType = InclusionType::Included); class QTCREATOR_UTILS_EXPORT FileIterator { -- cgit v1.2.3 From ec9d9586fc199ea7b5f6cef0f0dbbe7025d62c26 Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Tue, 23 May 2023 16:49:20 +0200 Subject: Doc: Add \a commands and document Utils::MathUtils namespace Fixes qdoc warnings. Change-Id: I666e6db9ee3a3a5c83f093f2e48981701f137d5c Reviewed-by: Jarek Kobus Reviewed-by: --- src/libs/utils/mathutils.cpp | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/mathutils.cpp b/src/libs/utils/mathutils.cpp index 77d9d5e5b2..a25f4515b0 100644 --- a/src/libs/utils/mathutils.cpp +++ b/src/libs/utils/mathutils.cpp @@ -5,12 +5,22 @@ #include +/*! + \namespace Utils::MathUtils + \inmodule QtCreator + + \brief Contains functions for interpolation. +*/ + namespace Utils::MathUtils { /*! Linear interpolation: - For x = x1 it returns y1. - For x = x2 it returns y2. + + \list + \li For \a x = \a x1 it returns \a y1. + \li For \a x = \a x2 it returns \a y2. + \endlist */ int interpolateLinear(int x, int x1, int x2, int y1, int y2) { @@ -29,9 +39,13 @@ int interpolateLinear(int x, int x1, int x2, int y1, int y2) /*! Tangential interpolation: - For x = 0 it returns y1. - For x = xHalfLife it returns 50 % of the distance between y1 and y2. - For x = infinity it returns y2. + + \list + \li For \a x = 0 it returns \a y1. + \li For \a x = \a xHalfLife it returns 50 % of the distance between + \a y1 and \a y2. + \li For \a x = infinity it returns \a y2. + \endlist */ int interpolateTangential(int x, int xHalfLife, int y1, int y2) { @@ -46,9 +60,13 @@ int interpolateTangential(int x, int xHalfLife, int y1, int y2) /*! Exponential interpolation: - For x = 0 it returns y1. - For x = xHalfLife it returns 50 % of the distance between y1 and y2. - For x = infinity it returns y2. + + \list + \li For \a x = 0 it returns \a y1. + \li For \a x = \a xHalfLife it returns 50 % of the distance between + \a y1 and \a y2. + \li For \a x = infinity it returns \a y2. + \endlist */ int interpolateExponential(int x, int xHalfLife, int y1, int y2) { -- cgit v1.2.3 From 6448efba0f60df938a3f739f5197bff4f440c822 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Wed, 24 May 2023 07:38:32 +0200 Subject: ADS: Fix Qbs build Change-Id: Ie7d7dba3bc8da21e65f2ff4d1c2aad534729429c Reviewed-by: hjk --- src/libs/advanceddockingsystem/advanceddockingsystem.qbs | 1 + 1 file changed, 1 insertion(+) (limited to 'src/libs') diff --git a/src/libs/advanceddockingsystem/advanceddockingsystem.qbs b/src/libs/advanceddockingsystem/advanceddockingsystem.qbs index 751449cf15..8d758b3f7e 100644 --- a/src/libs/advanceddockingsystem/advanceddockingsystem.qbs +++ b/src/libs/advanceddockingsystem/advanceddockingsystem.qbs @@ -33,6 +33,7 @@ QtcLibrary { "iconprovider.cpp", "iconprovider.h", "workspace.cpp", "workspace.h", "workspacedialog.cpp", "workspacedialog.h", + "workspaceinputdialog.cpp", "workspaceinputdialog.h", "workspacemodel.cpp", "workspacemodel.h", "workspaceview.cpp", "workspaceview.h", ] -- cgit v1.2.3 From c124e837c589668e18054ae5c609ef5f77b4042f Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Wed, 24 May 2023 09:36:35 +0200 Subject: Doc: Add docs for Utils::Environment::systemEnvironment() Because we link to it from the Utils namespace docs. Change-Id: I4d320b34687e7a6304cbedcbf7e2e5d3a43642b6 Reviewed-by: Eike Ziller --- src/libs/utils/environment.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/environment.cpp b/src/libs/utils/environment.cpp index 977e63adb0..a65a48aa67 100644 --- a/src/libs/utils/environment.cpp +++ b/src/libs/utils/environment.cpp @@ -12,6 +12,13 @@ #include #include +/*! + \class Utils::Environment + \inmodule QtCreator + + \brief The Environment class sets \QC's system environment. +*/ + namespace Utils { static QReadWriteLock s_envMutex; @@ -184,6 +191,14 @@ void Environment::prependOrSetLibrarySearchPaths(const FilePaths &values) }); } +/*! + Returns \QC's system environment. + + This can be different from the system environment that \QC started in if the + user changed it in \uicontrol Preferences > \uicontrol Environment > + \uicontrol System > \uicontrol Environment. +*/ + Environment Environment::systemEnvironment() { QReadLocker lock(&s_envMutex); -- cgit v1.2.3 From 86ff142f8efb9047105709378c95f27a697ea578 Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Tue, 23 May 2023 12:45:51 +0200 Subject: Utils: Treat FilePath with variables as user input when expanding When macro-expanding FilePaths with environment variables, it can happen that non-conformant path separators end up in the intermediate expanded string. For example with "%{Env:PROGRAMFILES}" on Windows. Since we generally treat paths from environment variables as user input, it makes sense to treat macros as user input as-well in this case. Change-Id: I0daa57b7dbf3d8fd25c98fb82b2beb1bc6ded825 Reviewed-by: Reviewed-by: hjk --- src/libs/utils/macroexpander.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/macroexpander.cpp b/src/libs/utils/macroexpander.cpp index 43d23d2d19..5d3bba0229 100644 --- a/src/libs/utils/macroexpander.cpp +++ b/src/libs/utils/macroexpander.cpp @@ -275,7 +275,7 @@ QString MacroExpander::expand(const QString &stringWithVariables) const FilePath MacroExpander::expand(const FilePath &fileNameWithVariables) const { // We want single variables to expand to fully qualified strings. - return FilePath::fromString(expand(fileNameWithVariables.toString())); + return FilePath::fromUserInput(expand(fileNameWithVariables.toString())); } QByteArray MacroExpander::expand(const QByteArray &stringWithVariables) const -- cgit v1.2.3 From 0234ab68963be07a055942993f99c5e254f3e0f8 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 23 May 2023 18:37:29 +0200 Subject: Utils: More aspects with new scheme Task-number: QTCREATORBUG-29167 Change-Id: I76977d4d740556d28423ce9f632ee47e81822ee6 Reviewed-by: Reviewed-by: Alessandro Portale --- src/libs/utils/aspects.cpp | 3 ++- src/libs/utils/aspects.h | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index e900a680fc..3652380ed7 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -2283,7 +2283,8 @@ void StringListAspect::removeValues(const QStringList &values) that is a list of strings. */ -IntegersAspect::IntegersAspect() +IntegersAspect::IntegersAspect(AspectContainer *container) + : BaseAspect(container) { setDefaultValue({}); } diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index a55e8bff74..87f283bdcd 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -290,6 +290,7 @@ public: void setVolatileValue(const QVariant &val) override; void finish() override; + int operator()() const { return value(); } int value() const; void setValue(int val); @@ -585,12 +586,13 @@ class QTCREATOR_UTILS_EXPORT IntegersAspect : public BaseAspect Q_OBJECT public: - IntegersAspect(); + IntegersAspect(AspectContainer *container = nullptr); ~IntegersAspect() override; void addToLayout(Layouting::LayoutItem &parent) override; void emitChangedValue() override; + QList operator()() const { return value(); } QList value() const; void setValue(const QList &value); -- cgit v1.2.3 From 75710fa36936d506db5ba865613c51f966861e42 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 23 May 2023 17:49:52 +0200 Subject: Utils: Remove LabelPlacement::AtCheckBoxWithoutDummyLabel This is identical in remaining functionality to AtCheckBox after the recent layout builder changes (or rather, can be adjusted on the layouting side by having appropriate empty cells) Task-number: QTCREATORBUG-29167 Change-Id: Ic357de6fb756acb5926afe1fd361ee4b18b17afd Reviewed-by: Alessandro Portale --- src/libs/utils/aspects.cpp | 10 +--------- src/libs/utils/aspects.h | 2 +- 2 files changed, 2 insertions(+), 10 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index 3652380ed7..461a391534 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -1455,18 +1455,10 @@ void BoolAspect::addToLayout(Layouting::LayoutItem &parent) d->m_button = createSubWidget(); } switch (d->m_labelPlacement) { - case LabelPlacement::AtCheckBoxWithoutDummyLabel: + case LabelPlacement::AtCheckBox: d->m_button->setText(labelText()); parent.addItem(d->m_button.data()); break; - case LabelPlacement::AtCheckBox: { - d->m_button->setText(labelText()); - // FIXME: - //if (parent.isForm()) - // parent.addItem(createSubWidget()); - parent.addItem(d->m_button.data()); - break; - } case LabelPlacement::InExtraLabel: addLabeledItem(parent, d->m_button); break; diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index 87f283bdcd..2cae52a104 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -237,7 +237,7 @@ public: bool defaultValue() const; void setDefaultValue(bool val); - enum class LabelPlacement { AtCheckBox, AtCheckBoxWithoutDummyLabel, InExtraLabel }; + enum class LabelPlacement { AtCheckBox, InExtraLabel }; void setLabel(const QString &labelText, LabelPlacement labelPlacement = LabelPlacement::InExtraLabel); void setLabelPlacement(LabelPlacement labelPlacement); -- cgit v1.2.3 From 9bb126c0d6ff46bd00950261eb3eb9205f1d3879 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Wed, 10 May 2023 09:37:25 +0200 Subject: Utils: make column of convertPosition 0-based to merge it into Position Change-Id: I239b3cb33b8ad59ac4097c919155ab5ca7d57b8e Reviewed-by: Qt CI Bot Reviewed-by: Jarek Kobus --- src/libs/languageserverprotocol/lsptypes.cpp | 6 +++--- src/libs/utils/textutils.cpp | 4 ++-- src/libs/utils/textutils.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'src/libs') diff --git a/src/libs/languageserverprotocol/lsptypes.cpp b/src/libs/languageserverprotocol/lsptypes.cpp index bd7798dcef..72a5f9f428 100644 --- a/src/libs/languageserverprotocol/lsptypes.cpp +++ b/src/libs/languageserverprotocol/lsptypes.cpp @@ -275,7 +275,7 @@ Position Position::withOffset(int offset, const QTextDocument *doc) const int line; int character; Utils::Text::convertPosition(doc, toPositionInDocument(doc) + offset, &line, &character); - return Position(line - 1, character - 1); + return Position(line - 1, character); } Range::Range(const Position &start, const Position &end) @@ -288,11 +288,11 @@ Range::Range(const QTextCursor &cursor) { int line, character = 0; Utils::Text::convertPosition(cursor.document(), cursor.selectionStart(), &line, &character); - if (line <= 0 || character <= 0) + if (line <= 0 || character < 0) return; setStart(Position(line - 1, character - 1)); Utils::Text::convertPosition(cursor.document(), cursor.selectionEnd(), &line, &character); - if (line <= 0 || character <= 0) + if (line <= 0 || character < 0) return; setEnd(Position(line - 1, character - 1)); } diff --git a/src/libs/utils/textutils.cpp b/src/libs/utils/textutils.cpp index 8b17cc7e01..28a9e12e1b 100644 --- a/src/libs/utils/textutils.cpp +++ b/src/libs/utils/textutils.cpp @@ -102,12 +102,12 @@ bool convertPosition(const QTextDocument *document, int pos, int *line, int *col QTextBlock block = document->findBlock(pos); if (!block.isValid()) { (*line) = -1; - (*column) = -1; + (*column) = 0; return false; } else { // line and column are both 1-based (*line) = block.blockNumber() + 1; - (*column) = pos - block.position() + 1; + (*column) = pos - block.position(); return true; } } diff --git a/src/libs/utils/textutils.h b/src/libs/utils/textutils.h index 96b7afbeb8..80e4150c1d 100644 --- a/src/libs/utils/textutils.h +++ b/src/libs/utils/textutils.h @@ -68,7 +68,7 @@ using Replacements = std::vector; QTCREATOR_UTILS_EXPORT void applyReplacements(QTextDocument *doc, const Replacements &replacements); -// line is 1-based, column is 1-based +// line is 1-based, column is 0-based QTCREATOR_UTILS_EXPORT bool convertPosition(const QTextDocument *document, int pos, int *line, int *column); -- cgit v1.2.3 From 31090ded156173e17a6be2772f4f098de60787a6 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 24 May 2023 19:41:16 +0200 Subject: FileSearch: Remove unused function Change-Id: I8af9a3db055f530792e68095c22c696392f79872 Reviewed-by: Reviewed-by: Eike Ziller Reviewed-by: Qt CI Bot --- src/libs/utils/filesearch.cpp | 10 ---------- src/libs/utils/filesearch.h | 4 ---- 2 files changed, 14 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/filesearch.cpp b/src/libs/utils/filesearch.cpp index 073b6057a9..a72ddc6ab0 100644 --- a/src/libs/utils/filesearch.cpp +++ b/src/libs/utils/filesearch.cpp @@ -10,7 +10,6 @@ #include "searchresultitem.h" #include "stringutils.h" #include "utilstr.h" -#include "utiltypes.h" #include #include @@ -499,15 +498,6 @@ static bool isFileIncluded(const QList &filterRegs, return isIncluded && (exclusionRegs.isEmpty() || !matches(exclusionRegs, filePath)); } -FilePathPredicate filterFileFunction(const QStringList &filters, const QStringList &exclusionFilters) -{ - const QList filterRegs = filtersToRegExps(filters); - const QList exclusionRegs = filtersToRegExps(exclusionFilters); - return [filterRegs, exclusionRegs](const FilePath &filePath) { - return isFileIncluded(filterRegs, exclusionRegs, filePath); - }; -} - std::function filterFilesFunction(const QStringList &filters, const QStringList &exclusionFilters) { diff --git a/src/libs/utils/filesearch.h b/src/libs/utils/filesearch.h index c3d81faa12..fc6ce62ab0 100644 --- a/src/libs/utils/filesearch.h +++ b/src/libs/utils/filesearch.h @@ -23,10 +23,6 @@ QT_END_NAMESPACE namespace Utils { -QTCREATOR_UTILS_EXPORT -std::function filterFileFunction(const QStringList &filterRegs, - const QStringList &exclusionRegs); - QTCREATOR_UTILS_EXPORT std::function filterFilesFunction(const QStringList &filters, const QStringList &exclusionFilters); -- cgit v1.2.3 From 85f16335628769e84942ad2d4140c3409890240b Mon Sep 17 00:00:00 2001 From: David Schulz Date: Thu, 25 May 2023 07:31:04 +0200 Subject: LSP: Fix Range(QTextCursor &) constructor amends 9bb126c0d6ff46bd00950261eb3eb9205f1d3879 Change-Id: I5afe9ecfff2339593368eb24e2b034460ce197e8 Reviewed-by: Christian Kandeler --- src/libs/languageserverprotocol/lsptypes.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/languageserverprotocol/lsptypes.cpp b/src/libs/languageserverprotocol/lsptypes.cpp index 72a5f9f428..6f0b57c1e3 100644 --- a/src/libs/languageserverprotocol/lsptypes.cpp +++ b/src/libs/languageserverprotocol/lsptypes.cpp @@ -290,11 +290,11 @@ Range::Range(const QTextCursor &cursor) Utils::Text::convertPosition(cursor.document(), cursor.selectionStart(), &line, &character); if (line <= 0 || character < 0) return; - setStart(Position(line - 1, character - 1)); + setStart(Position(line - 1, character)); Utils::Text::convertPosition(cursor.document(), cursor.selectionEnd(), &line, &character); if (line <= 0 || character < 0) return; - setEnd(Position(line - 1, character - 1)); + setEnd(Position(line - 1, character)); } bool Range::contains(const Range &other) const -- cgit v1.2.3 From 2d32fe4b573d0216eb32b46510df9d02b2726b95 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 23 May 2023 09:37:23 +0200 Subject: Utils: Remove Environment::searchInDirectories Was functionally replaced by FilePath::searchInDirectories. Change-Id: I8808cbdb114fb9b6b4e1f94e13aa9e67b9c56d6a Reviewed-by: Marcus Tillmanns --- src/libs/utils/environment.cpp | 19 ------------------- src/libs/utils/environment.h | 3 --- 2 files changed, 22 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/environment.cpp b/src/libs/utils/environment.cpp index a65a48aa67..0a95587aef 100644 --- a/src/libs/utils/environment.cpp +++ b/src/libs/utils/environment.cpp @@ -300,25 +300,6 @@ static void searchInDirectoriesHelper(const SearchResultCallback &resultCallback return; } -FilePath Environment::searchInDirectories(const QString &executable, - const FilePaths &dirs, - const FilePathPredicate &func) const -{ - FilePath result; - searchInDirectoriesHelper( - [&result](const FilePath &path) { - result = path; - return IterationPolicy::Stop; - }, - *this, - executable, - dirs, - func, - false); - - return result; -} - FilePath Environment::searchInPath(const QString &executable, const FilePaths &additionalDirs, const FilePathPredicate &func) const diff --git a/src/libs/utils/environment.h b/src/libs/utils/environment.h index 7afa48ba0b..843232878b 100644 --- a/src/libs/utils/environment.h +++ b/src/libs/utils/environment.h @@ -61,9 +61,6 @@ public: FilePath searchInPath(const QString &executable, const FilePaths &additionalDirs = FilePaths(), const FilePathPredicate &func = {}) const; - FilePath searchInDirectories(const QString &executable, - const FilePaths &dirs, - const FilePathPredicate &func = {}) const; FilePaths findAllInPath(const QString &executable, const FilePaths &additionalDirs = {}, const FilePathPredicate &func = {}) const; -- cgit v1.2.3 From 0d95c68b2146f704a77b9ba4a8c0b07df584d03d Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Thu, 25 May 2023 10:58:49 +0200 Subject: Doc: Add missing \a commands to get rid of qdoc warnings Change-Id: Ida0f0cabae0cc151967098312b358803a48dd92c Reviewed-by: Eike Ziller --- src/libs/utils/fileinprojectfinder.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/fileinprojectfinder.cpp b/src/libs/utils/fileinprojectfinder.cpp index 6ec36212eb..957f694179 100644 --- a/src/libs/utils/fileinprojectfinder.cpp +++ b/src/libs/utils/fileinprojectfinder.cpp @@ -110,13 +110,16 @@ void FileInProjectFinder::addMappedPath(const FilePath &localFilePath, const QSt } /*! - Returns the best match for the given file URL in the project directory. + Returns the best match for the file URL \a fileUrl in the project directory. The function first checks whether the file inside the project directory exists. If not, the leading directory in the path is stripped, and the - now shorter - path is checked for existence, and so on. Second, it tries to locate the file in the sysroot - folder specified. Third, we walk the list of project files, and search for a file name match - there. If all fails, it returns the original path from the file URL. + folder specified. Third, it walks the list of project files and searches for a file name match + there. + + If all fails, the function returns the original path from the file URL. To + indicate that no match was found in the project, \a success is set to false. */ FilePaths FileInProjectFinder::findFile(const QUrl &fileUrl, bool *success) const { -- cgit v1.2.3 From 06365fa39fd6621de2452d99ad057bb27cf190a4 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Thu, 25 May 2023 10:56:07 +0200 Subject: Terminal: External terminal if blocked by modal Change-Id: I89ba438c7a9f4d593e849b9b7ca2daf202cca625 Reviewed-by: hjk --- src/libs/utils/CMakeLists.txt | 1 + src/libs/utils/externalterminalprocessimpl.cpp | 92 ++++++++++++++++++++++++ src/libs/utils/externalterminalprocessimpl.h | 29 ++++++++ src/libs/utils/terminalhooks.cpp | 98 +------------------------- src/libs/utils/utils.qbs | 2 + 5 files changed, 125 insertions(+), 97 deletions(-) create mode 100644 src/libs/utils/externalterminalprocessimpl.cpp create mode 100644 src/libs/utils/externalterminalprocessimpl.h (limited to 'src/libs') diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index 8322ad8a45..aa6a3e199e 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -45,6 +45,7 @@ add_qtc_library(Utils execmenu.cpp execmenu.h executeondestruction.h expected.h + externalterminalprocessimpl.cpp externalterminalprocessimpl.h fadingindicator.cpp fadingindicator.h faketooltip.cpp faketooltip.h fancylineedit.cpp fancylineedit.h diff --git a/src/libs/utils/externalterminalprocessimpl.cpp b/src/libs/utils/externalterminalprocessimpl.cpp new file mode 100644 index 0000000000..366554884d --- /dev/null +++ b/src/libs/utils/externalterminalprocessimpl.cpp @@ -0,0 +1,92 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "externalterminalprocessimpl.h" +#include "process.h" +#include "terminalcommand.h" +#include "utilstr.h" + +#include + +namespace Utils { + +ExternalTerminalProcessImpl::ExternalTerminalProcessImpl() +{ + setStubCreator(new ProcessStubCreator(this)); +} + +ProcessStubCreator::ProcessStubCreator(TerminalInterface *interface) + : m_interface(interface) +{} + +expected_str ProcessStubCreator::startStubProcess(const ProcessSetupData &setupData) +{ + const TerminalCommand terminal = TerminalCommand::terminalEmulator(); + + if (HostOsInfo::isMacHost() && terminal.command == "Terminal.app") { + QTemporaryFile f; + f.setAutoRemove(false); + f.open(); + f.setPermissions(QFile::ExeUser | QFile::ReadUser | QFile::WriteUser); + f.write("#!/bin/sh\n"); + f.write(QString("cd %1\n").arg(setupData.m_workingDirectory.nativePath()).toUtf8()); + f.write("clear\n"); + f.write(QString("exec '%1' %2\n") + .arg(setupData.m_commandLine.executable().nativePath()) + .arg(setupData.m_commandLine.arguments()) + .toUtf8()); + f.close(); + + const QString path = f.fileName(); + const QString exe + = QString("tell app \"Terminal\" to do script \"'%1'; rm -f '%1'; exit\"").arg(path); + + Process process; + + process.setCommand({"osascript", {"-e", "tell app \"Terminal\" to activate", "-e", exe}}); + process.runBlocking(); + + if (process.exitCode() != 0) { + return make_unexpected( + Tr::tr("Failed to start terminal process: \"%1\"").arg(process.errorString())); + } + + return 0; + } + + bool detached = setupData.m_terminalMode == TerminalMode::Detached; + + Process *process = new Process(detached ? nullptr : this); + if (detached) + QObject::connect(process, &Process::done, process, &Process::deleteLater); + + QObject::connect(process, &Process::done, m_interface, &TerminalInterface::onStubExited); + + process->setWorkingDirectory(setupData.m_workingDirectory); + + if constexpr (HostOsInfo::isWindowsHost()) { + process->setCommand(setupData.m_commandLine); + process->setCreateConsoleOnWindows(true); + process->setProcessMode(ProcessMode::Writer); + } else { + QString extraArgsFromOptions = detached ? terminal.openArgs : terminal.executeArgs; + CommandLine cmdLine = {terminal.command, {}}; + if (!extraArgsFromOptions.isEmpty()) + cmdLine.addArgs(extraArgsFromOptions, CommandLine::Raw); + cmdLine.addCommandLineAsArgs(setupData.m_commandLine, CommandLine::Raw); + process->setCommand(cmdLine); + } + + process->start(); + process->waitForStarted(); + if (process->error() != QProcess::UnknownError) { + return make_unexpected( + Tr::tr("Failed to start terminal process: \"%1\"").arg(process->errorString())); + } + + qint64 pid = process->processId(); + + return pid; +} + +} // namespace Utils diff --git a/src/libs/utils/externalterminalprocessimpl.h b/src/libs/utils/externalterminalprocessimpl.h new file mode 100644 index 0000000000..cbb3370071 --- /dev/null +++ b/src/libs/utils/externalterminalprocessimpl.h @@ -0,0 +1,29 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "terminalinterface.h" + +namespace Utils { + +class ProcessStubCreator; + +class QTCREATOR_UTILS_EXPORT ExternalTerminalProcessImpl final : public TerminalInterface +{ +public: + ExternalTerminalProcessImpl(); +}; + +class QTCREATOR_UTILS_EXPORT ProcessStubCreator : public StubCreator +{ +public: + ProcessStubCreator(TerminalInterface *interface); + ~ProcessStubCreator() override = default; + + expected_str startStubProcess(const ProcessSetupData &setupData) override; + + TerminalInterface *m_interface; +}; + +} // namespace Utils diff --git a/src/libs/utils/terminalhooks.cpp b/src/libs/utils/terminalhooks.cpp index 07ebbd98d2..3bda25b109 100644 --- a/src/libs/utils/terminalhooks.cpp +++ b/src/libs/utils/terminalhooks.cpp @@ -3,14 +3,11 @@ #include "terminalhooks.h" +#include "externalterminalprocessimpl.h" #include "filepath.h" #include "process.h" -#include "terminalcommand.h" -#include "terminalinterface.h" -#include "utilstr.h" #include -#include namespace Utils::Terminal { @@ -31,99 +28,6 @@ FilePath defaultShellForDevice(const FilePath &deviceRoot) return deviceRoot.withNewMappedPath(shell); } -class ExternalTerminalProcessImpl final : public TerminalInterface -{ - class ProcessStubCreator : public StubCreator - { - public: - ProcessStubCreator(ExternalTerminalProcessImpl *interface) - : m_interface(interface) - {} - - ~ProcessStubCreator() override = default; - - expected_str startStubProcess(const ProcessSetupData &setupData) override - { - const TerminalCommand terminal = TerminalCommand::terminalEmulator(); - - if (HostOsInfo::isMacHost() && terminal.command == "Terminal.app") { - QTemporaryFile f; - f.setAutoRemove(false); - f.open(); - f.setPermissions(QFile::ExeUser | QFile::ReadUser | QFile::WriteUser); - f.write("#!/bin/sh\n"); - f.write(QString("cd %1\n").arg(setupData.m_workingDirectory.nativePath()).toUtf8()); - f.write("clear\n"); - f.write(QString("exec '%1' %2\n") - .arg(setupData.m_commandLine.executable().nativePath()) - .arg(setupData.m_commandLine.arguments()) - .toUtf8()); - f.close(); - - const QString path = f.fileName(); - const QString exe - = QString("tell app \"Terminal\" to do script \"'%1'; rm -f '%1'; exit\"") - .arg(path); - - Process process; - - process.setCommand( - {"osascript", {"-e", "tell app \"Terminal\" to activate", "-e", exe}}); - process.runBlocking(); - - if (process.exitCode() != 0) { - return make_unexpected(Tr::tr("Failed to start terminal process: \"%1\"") - .arg(process.errorString())); - } - - return 0; - } - - bool detached = setupData.m_terminalMode == TerminalMode::Detached; - - Process *process = new Process(detached ? nullptr : this); - if (detached) - QObject::connect(process, &Process::done, process, &Process::deleteLater); - - QObject::connect(process, - &Process::done, - m_interface, - &ExternalTerminalProcessImpl::onStubExited); - - process->setWorkingDirectory(setupData.m_workingDirectory); - - if constexpr (HostOsInfo::isWindowsHost()) { - process->setCommand(setupData.m_commandLine); - process->setCreateConsoleOnWindows(true); - process->setProcessMode(ProcessMode::Writer); - } else { - QString extraArgsFromOptions = detached ? terminal.openArgs : terminal.executeArgs; - CommandLine cmdLine = {terminal.command, {}}; - if (!extraArgsFromOptions.isEmpty()) - cmdLine.addArgs(extraArgsFromOptions, CommandLine::Raw); - cmdLine.addCommandLineAsArgs(setupData.m_commandLine, CommandLine::Raw); - process->setCommand(cmdLine); - } - - process->start(); - process->waitForStarted(); - if (process->error() != QProcess::UnknownError) { - return make_unexpected( - Tr::tr("Failed to start terminal process: \"%1\"").arg(process->errorString())); - } - - qint64 pid = process->processId(); - - return pid; - } - - ExternalTerminalProcessImpl *m_interface; - }; - -public: - ExternalTerminalProcessImpl() { setStubCreator(new ProcessStubCreator(this)); } -}; - class HooksPrivate { public: diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index 298b23c1e9..df23fc3ba2 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -110,6 +110,8 @@ Project { "execmenu.cpp", "execmenu.h", "executeondestruction.h", + "externalterminalprocessimpl.cpp", + "externalterminalprocessimpl.h", "fadingindicator.cpp", "fadingindicator.h", "faketooltip.cpp", -- cgit v1.2.3 From 001b3ab626fb279056db32b8d915ec8659c0b4c4 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 23 May 2023 11:10:00 +0200 Subject: Utils: Remove now-unused Environment::findAllInPath Change-Id: I562309c292ab0c5ae317593e40e5105bbcf89bf8 Reviewed-by: Marcus Tillmanns --- src/libs/utils/environment.cpp | 19 ------------------- src/libs/utils/environment.h | 3 --- 2 files changed, 22 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/environment.cpp b/src/libs/utils/environment.cpp index 0a95587aef..46972d1351 100644 --- a/src/libs/utils/environment.cpp +++ b/src/libs/utils/environment.cpp @@ -319,25 +319,6 @@ FilePath Environment::searchInPath(const QString &executable, return result; } -FilePaths Environment::findAllInPath(const QString &executable, - const FilePaths &additionalDirs, - const FilePathPredicate &func) const -{ - QSet result; - searchInDirectoriesHelper( - [&result](const FilePath &path) { - result.insert(path); - return IterationPolicy::Continue; - }, - *this, - executable, - additionalDirs, - func, - true); - - return result.values(); -} - FilePaths Environment::path() const { return pathListValue("PATH"); diff --git a/src/libs/utils/environment.h b/src/libs/utils/environment.h index 843232878b..63fe697bd6 100644 --- a/src/libs/utils/environment.h +++ b/src/libs/utils/environment.h @@ -61,9 +61,6 @@ public: FilePath searchInPath(const QString &executable, const FilePaths &additionalDirs = FilePaths(), const FilePathPredicate &func = {}) const; - FilePaths findAllInPath(const QString &executable, - const FilePaths &additionalDirs = {}, - const FilePathPredicate &func = {}) const; FilePaths path() const; FilePaths pathListValue(const QString &varName) const; -- cgit v1.2.3 From 38c64e5419d1e6454fbfcc41ed316b2e15d4b640 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Thu, 25 May 2023 12:07:34 +0200 Subject: Utils: Fix opening external Terminal Change-Id: Id5c650f6f40f4ab8233360e20673dd9f0277c09d Reviewed-by: hjk --- src/libs/utils/externalterminalprocessimpl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/externalterminalprocessimpl.cpp b/src/libs/utils/externalterminalprocessimpl.cpp index 366554884d..efdbc9aca3 100644 --- a/src/libs/utils/externalterminalprocessimpl.cpp +++ b/src/libs/utils/externalterminalprocessimpl.cpp @@ -69,7 +69,7 @@ expected_str ProcessStubCreator::startStubProcess(const ProcessSetupData process->setCreateConsoleOnWindows(true); process->setProcessMode(ProcessMode::Writer); } else { - QString extraArgsFromOptions = detached ? terminal.openArgs : terminal.executeArgs; + QString extraArgsFromOptions = terminal.executeArgs; CommandLine cmdLine = {terminal.command, {}}; if (!extraArgsFromOptions.isEmpty()) cmdLine.addArgs(extraArgsFromOptions, CommandLine::Raw); -- cgit v1.2.3 From 320064f4310bcb826931deeff9ed4d5f87a72c2b Mon Sep 17 00:00:00 2001 From: hjk Date: Wed, 24 May 2023 10:49:48 +0200 Subject: Utils: Allow more finegrained subaspect ownership in AspectContainer Currently this luckily conincides with the register/addAspect schism but that's not necessarily true in the future. Change-Id: I05a59d74182dbdf81193ebd790d6f9bab2d30439 Reviewed-by: Alessandro Portale --- src/libs/utils/aspects.cpp | 16 ++++++---------- src/libs/utils/aspects.h | 5 ++--- 2 files changed, 8 insertions(+), 13 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index 461a391534..0e7050a7e0 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -2399,9 +2399,9 @@ namespace Internal { class AspectContainerPrivate { public: - QList m_items; // Not owned + QList m_items; // Both owned and non-owned. + QList m_ownedItems; // Owned only. bool m_autoApply = true; - bool m_ownsSubAspects = false; QStringList m_settingsGroup; }; @@ -2416,17 +2416,18 @@ AspectContainer::AspectContainer(QObject *parent) */ AspectContainer::~AspectContainer() { - if (d->m_ownsSubAspects) - qDeleteAll(d->m_items); + qDeleteAll(d->m_ownedItems); } /*! \internal */ -void AspectContainer::registerAspect(BaseAspect *aspect) +void AspectContainer::registerAspect(BaseAspect *aspect, bool takeOwnership) { aspect->setAutoApply(d->m_autoApply); d->m_items.append(aspect); + if (takeOwnership) + d->m_ownedItems.append(aspect); } void AspectContainer::registerAspects(const AspectContainer &aspects) @@ -2547,11 +2548,6 @@ void AspectContainer::setAutoApply(bool on) aspect->setAutoApply(on); } -void AspectContainer::setOwnsSubAspects(bool on) -{ - d->m_ownsSubAspects = on; -} - bool AspectContainer::isDirty() const { for (BaseAspect *aspect : std::as_const(d->m_items)) { diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index 2cae52a104..35e8a62b8f 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -651,14 +651,14 @@ public: AspectContainer(const AspectContainer &) = delete; AspectContainer &operator=(const AspectContainer &) = delete; - void registerAspect(BaseAspect *aspect); + void registerAspect(BaseAspect *aspect, bool takeOwnership = false); void registerAspects(const AspectContainer &aspects); template Aspect *addAspect(Args && ...args) { auto aspect = new Aspect(args...); - registerAspect(aspect); + registerAspect(aspect, true); return aspect; } @@ -679,7 +679,6 @@ public: bool equals(const AspectContainer &other) const; void copyFrom(const AspectContainer &other); void setAutoApply(bool on); - void setOwnsSubAspects(bool on); bool isDirty() const; template T *aspect() const -- cgit v1.2.3 From e037bd20044ce3d9c90c4b9af43feb59dfcc2449 Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Wed, 24 May 2023 16:40:40 +0200 Subject: Doc: Fix qdoc warnings in FilePath docs Also fix some misc style issues, such as missing punctuation. Change-Id: If5a9243eb9ce57c87096f9f0e184c8a802df54aa Reviewed-by: Eike Ziller --- src/libs/utils/filepath.cpp | 86 +++++++++++++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 30 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 31d24f14b0..ad871413a2 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -119,7 +119,7 @@ inline bool isWindowsDriveLetter(QChar ch); Converts the FilePath to the slash convention of the associated OS and adds the scheme and host as a " on " suffix. - This is useful for static user-facing output in he GUI + This is useful for static user-facing output in the GUI. \li FilePath::fromVariant(), FilePath::toVariant() @@ -157,7 +157,7 @@ FilePath::FilePath() } /*! - Constructs a FilePath from \a info + Constructs a FilePath from \a info. */ FilePath FilePath::fromFileInfo(const QFileInfo &info) { @@ -172,6 +172,11 @@ QFileInfo FilePath::toFileInfo() const return QFileInfo(toFSPathString()); } +/*! + Constructs a FilePath from \a variant. + + \sa toVariant() +*/ FilePath FilePath::fromVariant(const QVariant &variant) { return fromSettings(variant); // FIXME: Use variant.value() @@ -281,7 +286,7 @@ QUrl FilePath::toUrl() const } /*! - returns a QString to display to the user, including the device prefix + Returns a QString to display to the user, including the device prefix. Converts the separators to the native format of the system this path belongs to. @@ -491,6 +496,9 @@ bool FilePath::ensureExistingFile() const return fileAccess()->ensureExistingFile(*this); } +/*! + Returns a bool indicating whether this is an executable file. +*/ bool FilePath::isExecutableFile() const { return fileAccess()->isExecutableFile(*this); @@ -498,10 +506,11 @@ bool FilePath::isExecutableFile() const /*! Returns a bool indicating on whether a process with this FilePath's - .nativePath() is likely to start. + native path is likely to start. - This is equivalent to \c isExecutableFile() in general. - On Windows, it will check appending various suffixes, too. + This is equivalent to \l isExecutableFile() in general. + On Windows, it might append various suffixes depending on + \a matchScope. */ std::optional FilePath::refersToExecutableFile(MatchScope matchScope) const { @@ -578,7 +587,7 @@ bool FilePath::hasHardLinks() const Returns true if the directory could be created, false if not, even if it existed before. - \sa ensureWriteableDir() + \sa ensureWritableDir() */ bool FilePath::createDir() const { @@ -616,9 +625,7 @@ FilePaths FilePath::dirEntries(QDir::Filters filters) const } /*! - This runs \a callBack on each directory entry matching all \a filters and - either of the specified \a nameFilters. - An empty \nameFilters list matches every name. + Runs \a callBack on each directory entry matching the \a filter. */ void FilePath::iterateDirectory(const IterateDirCallback &callBack, const FileFilter &filter) const @@ -966,12 +973,13 @@ QString doCleanPath(const QString &input_) return input.left(prefixLen) + path; } -/*! Find the parent directory of a given directory. +/*! + Finds the parent directory of the file path. - Returns an empty FilePath if the current directory is already + Returns an empty file path if the file path is already a root level directory. - Returns \a FilePath with the last segment removed. + Returns a file path with the last segment removed. */ FilePath FilePath::parentDir() const { @@ -1039,6 +1047,15 @@ FilePath FilePath::normalizedPathName() const return result; } +/*! + Converts the file path to the slash convention of the associated + OS and adds the scheme and host as a " on " suffix. + + This is useful for static user-facing output in the GUI. + + If \a args is not empty, it is added to the output after the file path: + " on ". +*/ QString FilePath::displayName(const QString &args) const { QString deviceName; @@ -1201,10 +1218,10 @@ bool FilePath::hasFileAccess() const } /*! - Constructs a FilePath from \a filePath. The \a defaultExtension is appended - to \a filePath if that does not have an extension already. + Constructs a FilePath from \a filepath. The \a defaultExtension is appended + to \a filepath if that does not have an extension already. - \a filePath is not checked for validity. + \a filepath is not checked for validity. */ FilePath FilePath::fromStringWithExtension(const QString &filepath, const QString &defaultExtension) { @@ -1225,7 +1242,7 @@ FilePath FilePath::fromStringWithExtension(const QString &filepath, const QStrin /*! Constructs a FilePath from \a filePath - The path \a filePath is cleaned and ~ replaces by the home path. + The path \a filePath is cleaned, and ~ is replaced by the home path. */ FilePath FilePath::fromUserInput(const QString &filePath) { @@ -1236,9 +1253,10 @@ FilePath FilePath::fromUserInput(const QString &filePath) } /*! - Constructs a FilePath from \a filePath, which is encoded as UTF-8. + Constructs a FilePath from \a filename with \a filenameSize, which is + encoded as UTF-8. - \a filePath is not checked for validity. + \a filename is not checked for validity. */ FilePath FilePath::fromUtf8(const char *filename, int filenameSize) { @@ -1259,6 +1277,12 @@ QVariant FilePath::toSettings() const return toString(); } +/*! + Returns the FilePath as a variant. + + To be used for type-agnostic internal interfaces like storage in + QAbstractItemModels. +*/ QVariant FilePath::toVariant() const { // FIXME: Use qVariantFromValue @@ -1391,7 +1415,7 @@ FilePath FilePath::relativePathFrom(const FilePath &anchor) const } /*! - Returns the relativePath of \a absolutePath to given \a absoluteAnchorPath. + Returns the relative path of \a absolutePath to given \a absoluteAnchorPath. Both paths must be an absolute path to a directory. Example usage: @@ -1402,7 +1426,7 @@ FilePath FilePath::relativePathFrom(const FilePath &anchor) const The debug output will be "../b/ar". - \see FilePath::relativePath + \see FilePath::isRelativePath(), FilePath::relativePathFrom(), FilePath::relativeChildPath() */ QString FilePath::calcRelativePath(const QString &absolutePath, const QString &absoluteAnchorPath) { @@ -1488,8 +1512,9 @@ FilePath FilePath::withNewPath(const QString &newPath) const } /*! - Search for a binary corresponding to this object in the PATH of - the device implied by this object's scheme and host. + Search for a binary corresponding to this object on each directory entry + specified by \a dirs matching the \a filter with the \a matchScope of the + file path. Example usage: \code @@ -1794,7 +1819,7 @@ qint64 FilePath::bytesAvailable() const \c true if one of them is newer than \a timeStamp. If this is a single file, \c true will be returned if the file is newer than \a timeStamp. - Returns whether at least one file in \a filePath has a newer date than + Returns whether at least one file in the file path has a newer date than \a timeStamp. */ bool FilePath::isNewerThan(const QDateTime &timeStamp) const @@ -1874,12 +1899,13 @@ FilePath FilePath::resolveSymlinks() const } /*! -* \brief Recursively resolves possibly present symlinks in this file name. -* On Windows, also resolves SUBST and re-mounted NTFS drives. -* Unlike QFileInfo::canonicalFilePath(), this function will not return an empty -* string if path doesn't exist. -* -* Returns the canonical path. + Recursively resolves possibly present symlinks in this file name. + + On Windows, also resolves SUBST and re-mounted NTFS drives. + Unlike QFileInfo::canonicalFilePath(), this function will not return an empty + string if path doesn't exist. + + Returns the canonical path. */ FilePath FilePath::canonicalPath() const { -- cgit v1.2.3 From 07764bdf1517cce3e484ac4e7371ff2aee060803 Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Thu, 25 May 2023 13:53:44 +0200 Subject: Doc: Remove broken \sa commands from CheckableMessageBox docs - The referred functions are not documented - Use \uicontrol instead of the deprecated \gui macro Change-Id: I993d62923657cd018caa87827dc89c6aa4b6d092 Reviewed-by: Eike Ziller --- src/libs/utils/checkablemessagebox.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/checkablemessagebox.cpp b/src/libs/utils/checkablemessagebox.cpp index 3c57a73667..d3ede7d4e7 100644 --- a/src/libs/utils/checkablemessagebox.cpp +++ b/src/libs/utils/checkablemessagebox.cpp @@ -21,8 +21,8 @@ \inmodule QtCreator \brief The CheckableMessageBox class implements a message box suitable for - questions with a - "Do not ask me again" checkbox. + questions with a \uicontrol {Do not ask again} or \uicontrol {Do not show again} + checkbox. Emulates the QMessageBox API with static conveniences. The message label can open external URLs. @@ -180,8 +180,7 @@ bool CheckableMessageBox::hasSuppressedQuestions() } /*! - Returns the standard \gui {Do not ask again} check box text. - \sa doNotAskAgainQuestion() + Returns the standard \uicontrol {Do not ask again} check box text. */ QString CheckableMessageBox::msgDoNotAskAgain() { @@ -189,8 +188,7 @@ QString CheckableMessageBox::msgDoNotAskAgain() } /*! - Returns the standard \gui {Do not show again} check box text. - \sa doNotShowAgainInformation() + Returns the standard \uicontrol {Do not show again} check box text. */ QString CheckableMessageBox::msgDoNotShowAgain() { -- cgit v1.2.3 From c8f29b9e0148202ab1959466e14fa23411fd8214 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Wed, 24 May 2023 15:18:48 +0200 Subject: CppEditor: Add support for init statements in if conditions Fixes: QTCREATORBUG-29182 Change-Id: I9b7969da694b368236246123ad0028d8e754e903 Reviewed-by: Christian Stenger --- src/libs/3rdparty/cplusplus/AST.h | 2 ++ src/libs/3rdparty/cplusplus/ASTClone.cpp | 4 ++++ src/libs/3rdparty/cplusplus/ASTMatcher.cpp | 10 ++++++++++ src/libs/3rdparty/cplusplus/ASTVisit.cpp | 2 ++ src/libs/3rdparty/cplusplus/Bind.cpp | 4 ++++ src/libs/3rdparty/cplusplus/Parser.cpp | 27 +++++++++++++++++++++------ src/libs/3rdparty/cplusplus/Token.h | 1 + 7 files changed, 44 insertions(+), 6 deletions(-) (limited to 'src/libs') diff --git a/src/libs/3rdparty/cplusplus/AST.h b/src/libs/3rdparty/cplusplus/AST.h index 0f40424464..6a5da23306 100644 --- a/src/libs/3rdparty/cplusplus/AST.h +++ b/src/libs/3rdparty/cplusplus/AST.h @@ -1779,6 +1779,8 @@ public: int if_token = 0; int constexpr_token = 0; int lparen_token = 0; + DeclarationAST *initDecl = nullptr; + StatementAST *initStmt = nullptr; ExpressionAST *condition = nullptr; int rparen_token = 0; StatementAST *statement = nullptr; diff --git a/src/libs/3rdparty/cplusplus/ASTClone.cpp b/src/libs/3rdparty/cplusplus/ASTClone.cpp index e494ad71ac..57617bb118 100644 --- a/src/libs/3rdparty/cplusplus/ASTClone.cpp +++ b/src/libs/3rdparty/cplusplus/ASTClone.cpp @@ -785,6 +785,10 @@ IfStatementAST *IfStatementAST::clone(MemoryPool *pool) const ast->if_token = if_token; ast->constexpr_token = constexpr_token; ast->lparen_token = lparen_token; + if (initDecl) + ast->initDecl = initDecl->clone(pool); + if (initStmt) + ast->initStmt = initStmt->clone(pool); if (condition) ast->condition = condition->clone(pool); ast->rparen_token = rparen_token; diff --git a/src/libs/3rdparty/cplusplus/ASTMatcher.cpp b/src/libs/3rdparty/cplusplus/ASTMatcher.cpp index de6f6fc87c..6f12efb8ac 100644 --- a/src/libs/3rdparty/cplusplus/ASTMatcher.cpp +++ b/src/libs/3rdparty/cplusplus/ASTMatcher.cpp @@ -1318,6 +1318,16 @@ bool ASTMatcher::match(IfStatementAST *node, IfStatementAST *pattern) pattern->lparen_token = node->lparen_token; + if (!pattern->initDecl) + pattern->initDecl = node->initDecl; + else if (!AST::match(node->initDecl, pattern->initDecl, this)) + return false; + + if (!pattern->initStmt) + pattern->initStmt = node->initStmt; + else if (!AST::match(node->initStmt, pattern->initStmt, this)) + return false; + if (! pattern->condition) pattern->condition = node->condition; else if (! AST::match(node->condition, pattern->condition, this)) diff --git a/src/libs/3rdparty/cplusplus/ASTVisit.cpp b/src/libs/3rdparty/cplusplus/ASTVisit.cpp index 17941d8122..7e8f75f233 100644 --- a/src/libs/3rdparty/cplusplus/ASTVisit.cpp +++ b/src/libs/3rdparty/cplusplus/ASTVisit.cpp @@ -563,6 +563,8 @@ void ForStatementAST::accept0(ASTVisitor *visitor) void IfStatementAST::accept0(ASTVisitor *visitor) { if (visitor->visit(this)) { + accept(initDecl, visitor); + accept(initStmt, visitor); accept(condition, visitor); accept(statement, visitor); accept(else_statement, visitor); diff --git a/src/libs/3rdparty/cplusplus/Bind.cpp b/src/libs/3rdparty/cplusplus/Bind.cpp index 202c36a51f..e3d38a09a8 100644 --- a/src/libs/3rdparty/cplusplus/Bind.cpp +++ b/src/libs/3rdparty/cplusplus/Bind.cpp @@ -1528,6 +1528,10 @@ bool Bind::visit(IfStatementAST *ast) ast->symbol = block; Scope *previousScope = switchScope(block); + if (ast->initDecl) + this->declaration(ast->initDecl); + else if (ast->initStmt) + this->statement(ast->initStmt); /*ExpressionTy condition =*/ this->expression(ast->condition); this->statement(ast->statement); this->statement(ast->else_statement); diff --git a/src/libs/3rdparty/cplusplus/Parser.cpp b/src/libs/3rdparty/cplusplus/Parser.cpp index f8c737c097..31e290d1ad 100644 --- a/src/libs/3rdparty/cplusplus/Parser.cpp +++ b/src/libs/3rdparty/cplusplus/Parser.cpp @@ -3511,12 +3511,14 @@ bool Parser::parseExpressionStatement(StatementAST *&node) ExpressionAST *expression = nullptr; if (parseExpression(expression)) { - ExpressionStatementAST *ast = new (previousPool) ExpressionStatementAST; - if (expression) - ast->expression = expression->clone(previousPool); - match(T_SEMICOLON, &ast->semicolon_token); - node = ast; - parsed = true; + if (LA() == T_SEMICOLON) { + ExpressionStatementAST *ast = new (previousPool) ExpressionStatementAST; + ast->semicolon_token = consumeToken(); + if (expression) + ast->expression = expression->clone(previousPool); + node = ast; + parsed = true; + } } _inExpressionStatement = wasInExpressionStatement; @@ -4072,6 +4074,19 @@ bool Parser::parseIfStatement(StatementAST *&node) ast->constexpr_token = consumeToken(); } match(T_LPAREN, &ast->lparen_token); + + // C++17: init-statement + if (_languageFeatures.cxx17Enabled) { + const int savedCursor = cursor(); + const bool savedBlockErrors = _translationUnit->blockErrors(true); + if (!parseSimpleDeclaration(ast->initDecl)) { + rewind(savedCursor); + if (!parseExpressionStatement(ast->initStmt)) + rewind(savedCursor); + } + _translationUnit->blockErrors(savedBlockErrors); + } + parseCondition(ast->condition); match(T_RPAREN, &ast->rparen_token); if (! parseStatement(ast->statement)) diff --git a/src/libs/3rdparty/cplusplus/Token.h b/src/libs/3rdparty/cplusplus/Token.h index 204096893a..f853d29c77 100644 --- a/src/libs/3rdparty/cplusplus/Token.h +++ b/src/libs/3rdparty/cplusplus/Token.h @@ -455,6 +455,7 @@ struct LanguageFeatures unsigned int cxxEnabled : 1; unsigned int cxx11Enabled : 1; unsigned int cxx14Enabled : 1; + unsigned int cxx17Enabled : 1; unsigned int cxx20Enabled : 1; unsigned int objCEnabled : 1; unsigned int c99Enabled : 1; -- cgit v1.2.3 From f4b02be1fa5f4bedbfa8a8ec256cadc0cea5c8fb Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Thu, 25 May 2023 14:20:46 +0200 Subject: Doc: Remove links to the undocumented attachToWidget function Change-Id: Id828e5d1c889289261c628f3f2dbe42206b8b892 Reviewed-by: Eike Ziller --- src/libs/utils/progressindicator.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/progressindicator.cpp b/src/libs/utils/progressindicator.cpp index 5e09191765..6e129cd58e 100644 --- a/src/libs/utils/progressindicator.cpp +++ b/src/libs/utils/progressindicator.cpp @@ -185,11 +185,7 @@ void ProgressIndicatorPainter::nextAnimationStep() /*! Constructs a ProgressIndicator of the size \a size and with the parent \a parent. - Use \l attachToWidget to make the progress indicator automatically resize and center on the - parent widget. - - \sa attachToWidget - \sa setIndicatorSize + \sa setIndicatorSize() */ ProgressIndicator::ProgressIndicator(ProgressIndicatorSize size, QWidget *parent) : OverlayWidget(parent) -- cgit v1.2.3 From f415d4c786e8e7f1b0964e234be44728d78f1efd Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Thu, 25 May 2023 14:17:31 +0200 Subject: Doc: Add \a commands to FileSystemWatcher and stringutils docs Change-Id: Ie76e2d7387df630a512413bd214461979ad2ec91 Reviewed-by: Eike Ziller --- src/libs/utils/filesystemwatcher.cpp | 4 ++-- src/libs/utils/stringutils.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/filesystemwatcher.cpp b/src/libs/utils/filesystemwatcher.cpp index 9a4d7b5693..155f83fba0 100644 --- a/src/libs/utils/filesystemwatcher.cpp +++ b/src/libs/utils/filesystemwatcher.cpp @@ -188,7 +188,7 @@ void FileSystemWatcherPrivate::autoReloadPostponed(bool postponed) } /*! - Adds directories to watcher 0. + Creates a file system watcher with the ID 0 and the owner \a parent. */ FileSystemWatcher::FileSystemWatcher(QObject *parent) : @@ -198,7 +198,7 @@ FileSystemWatcher::FileSystemWatcher(QObject *parent) : } /*! - Adds directories to a watcher with the specified \a id. + Creates a file system watcher with the ID \a id and the owner \a parent. */ FileSystemWatcher::FileSystemWatcher(int id, QObject *parent) : diff --git a/src/libs/utils/stringutils.cpp b/src/libs/utils/stringutils.cpp index f860216961..a33a08e3d9 100644 --- a/src/libs/utils/stringutils.cpp +++ b/src/libs/utils/stringutils.cpp @@ -458,7 +458,7 @@ QTCREATOR_UTILS_EXPORT QString normalizeNewlines(const QString &text) /*! Joins all the not empty string list's \a strings into a single string with each element - separated by the given separator (which can be an empty string). + separated by the given \a separator (which can be an empty string). */ QTCREATOR_UTILS_EXPORT QString joinStrings(const QStringList &strings, QChar separator) { -- cgit v1.2.3 From eed303450d96471f258254d71eee070f2c8caf4b Mon Sep 17 00:00:00 2001 From: Cristian Adam Date: Thu, 25 May 2023 17:28:19 +0200 Subject: Sqlite: Fix compilation with QTC_STATIC_BUILD Change-Id: Ie94439d190245e821c17de73075397695d1af413 Reviewed-by: Marco Bubke --- src/libs/sqlite/sqliteexception.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'src/libs') diff --git a/src/libs/sqlite/sqliteexception.h b/src/libs/sqlite/sqliteexception.h index d030f65280..f0cadfc748 100644 --- a/src/libs/sqlite/sqliteexception.h +++ b/src/libs/sqlite/sqliteexception.h @@ -3,6 +3,7 @@ #pragma once +#include "sqlite3_fwd.h" #include "sqliteglobal.h" #include @@ -10,10 +11,6 @@ #include #include -extern "C" { -struct sqlite3; -} - namespace Sqlite { class SQLITE_EXPORT Exception : public std::exception -- cgit v1.2.3 From 6ab66690af8cdb8522a4c5a6b654ccd0d5d49635 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 23 May 2023 11:37:16 +0200 Subject: Utils: Simplify remaining Environment::searchInPath implementation Use the parts that have recently been copied/moved to FilePath Change-Id: I4d5b5941c55052e58ae4b34c4c49432f63a13cde Reviewed-by: Marcus Tillmanns Reviewed-by: --- src/libs/utils/environment.cpp | 101 ++--------------------------------------- 1 file changed, 4 insertions(+), 97 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/environment.cpp b/src/libs/utils/environment.cpp index 46972d1351..653525e1c3 100644 --- a/src/libs/utils/environment.cpp +++ b/src/libs/utils/environment.cpp @@ -212,111 +212,18 @@ void Environment::setupEnglishOutput() using SearchResultCallback = std::function; -static IterationPolicy searchInDirectory(const SearchResultCallback &resultCallback, - const FilePaths &execs, - const FilePath &directory, - QSet &alreadyCheckedDirectories, - const FilePathPredicate &filter = {}) -{ - // Compare the initial size of the set with the size after insertion to check if the directory - // was already checked. - const int initialCount = alreadyCheckedDirectories.count(); - alreadyCheckedDirectories.insert(directory); - const bool wasAlreadyChecked = alreadyCheckedDirectories.count() == initialCount; - - if (directory.isEmpty() || wasAlreadyChecked) - return IterationPolicy::Continue; - - for (const FilePath &exec : execs) { - const FilePath filePath = directory / exec.path(); - if (filePath.isExecutableFile() && (!filter || filter(filePath))) { - if (resultCallback(filePath) == IterationPolicy::Stop) - return IterationPolicy::Stop; - } - } - return IterationPolicy::Continue; -} - -static FilePaths appendExeExtensions(const Environment &env, const FilePath &executable) -{ - FilePaths execs{executable}; - if (env.osType() == OsTypeWindows) { - // Check all the executable extensions on windows: - // PATHEXT is only used if the executable has no extension - if (executable.suffix().isEmpty()) { - const QStringList extensions = env.expandedValueForKey("PATHEXT").split(';'); - - for (const QString &ext : extensions) - execs << executable.stringAppended(ext.toLower()); - } - } - return execs; -} - QString Environment::expandedValueForKey(const QString &key) const { const NameValueDictionary &dict = resolved(); return expandVariables(dict.value(key)); } -static void searchInDirectoriesHelper(const SearchResultCallback &resultCallback, - const Environment &env, - const QString &executable, - const FilePaths &dirs, - const FilePathPredicate &func, - bool usePath) -{ - if (executable.isEmpty()) - return; - - const FilePath exec = FilePath::fromUserInput(QDir::cleanPath(env.expandVariables(executable))); - const FilePaths execs = appendExeExtensions(env, exec); - - if (exec.isAbsolutePath()) { - for (const FilePath &path : execs) { - if (path.isExecutableFile() && (!func || func(path))) - if (resultCallback(path) == IterationPolicy::Stop) - return; - } - return; - } - - QSet alreadyCheckedDirectories; - for (const FilePath &dir : dirs) { - if (searchInDirectory(resultCallback, execs, dir, alreadyCheckedDirectories, func) - == IterationPolicy::Stop) - return; - } - - if (usePath) { - QTC_ASSERT(!executable.contains('/'), return); - - for (const FilePath &p : env.path()) { - if (searchInDirectory(resultCallback, execs, p, alreadyCheckedDirectories, func) - == IterationPolicy::Stop) - return; - } - } - return; -} - FilePath Environment::searchInPath(const QString &executable, const FilePaths &additionalDirs, - const FilePathPredicate &func) const -{ - FilePath result; - searchInDirectoriesHelper( - [&result](const FilePath &path) { - result = path; - return IterationPolicy::Stop; - }, - *this, - executable, - additionalDirs, - func, - true); - - return result; + const FilePathPredicate &filter) const +{ + const FilePath exec = FilePath::fromUserInput(expandVariables(executable)); + return exec.searchInPath(additionalDirs, {}, filter, FilePath::WithAnySuffix); } FilePaths Environment::path() const -- cgit v1.2.3 From 2ad153f30e99c58ea1ef4d50e3b803e98620d119 Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Thu, 25 May 2023 13:05:02 +0200 Subject: Doc: Add \a commands and return values to TextFileFormat docs To fix qdoc warnings. Change-Id: I6258223e83517c1206e8bdb4db20db4e8fc60751 Reviewed-by: Eike Ziller --- src/libs/utils/textfileformat.cpp | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/textfileformat.cpp b/src/libs/utils/textfileformat.cpp index 094c6d0d72..264c6c4351 100644 --- a/src/libs/utils/textfileformat.cpp +++ b/src/libs/utils/textfileformat.cpp @@ -52,7 +52,7 @@ QDebug operator<<(QDebug d, const TextFileFormat &format) TextFileFormat::TextFileFormat() = default; /*! - Detects the format of text data. + Detects the format of text \a data. */ TextFileFormat TextFileFormat::detect(const QByteArray &data) @@ -85,7 +85,8 @@ TextFileFormat TextFileFormat::detect(const QByteArray &data) } /*! - Returns a piece of text suitable as display for a encoding error. + Returns a piece of text specified by \a data suitable as display for + an encoding error. */ QByteArray TextFileFormat::decodingErrorSample(const QByteArray &data) @@ -153,7 +154,7 @@ bool decodeTextFileContent(const QByteArray &dataBA, } /*! - Decodes data to a plain string. + Returns \a data decoded to a plain string, \a target. */ bool TextFileFormat::decode(const QByteArray &data, QString *target) const @@ -163,7 +164,7 @@ bool TextFileFormat::decode(const QByteArray &data, QString *target) const } /*! - Decodes data to a list of strings. + Returns \a data decoded to a list of strings, \a target. Intended for use with progress bars loading large files. */ @@ -212,7 +213,12 @@ TextFileFormat::ReadResult readTextFile(const FilePath &filePath, const QTextCod } /*! - Reads a text file into a list of strings. + Reads a text file from \a filePath into a list of strings, \a plainTextList + using \a defaultCodec and text file format \a format. + + Returns whether decoding was possible without errors. If errors occur, + returns an error message, \a errorString and a sample error, + \a decodingErrorSample. */ TextFileFormat::ReadResult @@ -230,7 +236,11 @@ TextFileFormat::ReadResult } /*! - Reads a text file into a string. + Reads a text file from \a filePath into a string, \a plainText using + \a defaultCodec and text file format \a format. + + Returns whether decoding was possible without errors. + */ TextFileFormat::ReadResult @@ -279,7 +289,10 @@ TextFileFormat::ReadResult TextFileFormat::readFileUTF8(const FilePath &filePath } /*! - Writes out a text file. + Writes out a text file to \a filePath into a string, \a plainText. + + Returns whether decoding was possible without errors. If errors occur, + returns an error message, \a errorString. */ bool TextFileFormat::writeFile(const FilePath &filePath, QString plainText, QString *errorString) const -- cgit v1.2.3 From 83b971b2388ece591a56e3c518cf73ba07484a5c Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Thu, 25 May 2023 14:09:41 +0200 Subject: Doc: Mark docs in Utils \internal if classes or functions not found Change-Id: I3182534fefc51f573892d6f80f59ce5613f95fdc Reviewed-by: Eike Ziller --- src/libs/utils/commandline.cpp | 1 + src/libs/utils/filesystemmodel.cpp | 17 +++++++++++++---- src/libs/utils/fsengine/fileiconprovider.cpp | 14 +++++++++++--- src/libs/utils/layoutbuilder.cpp | 5 ++++- src/libs/utils/treemodel.cpp | 13 +++++++++++++ 5 files changed, 42 insertions(+), 8 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/commandline.cpp b/src/libs/utils/commandline.cpp index b259aaf7e6..ba1cc4edc4 100644 --- a/src/libs/utils/commandline.cpp +++ b/src/libs/utils/commandline.cpp @@ -196,6 +196,7 @@ static QStringList doSplitArgsWin(const QString &args, ProcessArgs::SplitError * } /*! + \internal Splits \a _args according to system shell word splitting and quoting rules. \section1 Unix diff --git a/src/libs/utils/filesystemmodel.cpp b/src/libs/utils/filesystemmodel.cpp index 60961c3599..9a10f3b91f 100644 --- a/src/libs/utils/filesystemmodel.cpp +++ b/src/libs/utils/filesystemmodel.cpp @@ -209,7 +209,8 @@ private: }; /*! - Creates thread + \internal + Creates a thread. */ FileInfoGatherer::FileInfoGatherer(QObject *parent) : QThread(parent) @@ -219,7 +220,8 @@ FileInfoGatherer::FileInfoGatherer(QObject *parent) } /*! - Destroys thread + \internal + Destroys a thread. */ FileInfoGatherer::~FileInfoGatherer() { @@ -265,6 +267,7 @@ QFileIconProvider *FileInfoGatherer::iconProvider() const } /*! + \internal Fetch extended information for all \a files in \a path \sa updateFile(), update(), resolvedName() @@ -295,6 +298,7 @@ void FileInfoGatherer::fetchExtendedInformation(const QString &path, const QStri } /*! + \internal Fetch extended information for all \a filePath \sa fetchExtendedInformation() @@ -1719,7 +1723,7 @@ QStringList FileSystemModel::mimeTypes() const \a indexes. The format used to describe the items corresponding to the indexes is obtained from the mimeTypes() function. - If the list of indexes is empty, \nullptr is returned rather than a + If the list of indexes is empty, \c nullptr is returned rather than a serialized empty list. */ QMimeData *FileSystemModel::mimeData(const QModelIndexList &indexes) const @@ -1799,7 +1803,8 @@ QHash FileSystemModel::roleNames() const } /*! - \enum FileSystemModel::Option + \internal + \enum Utils::FileSystemModel::Option \since 5.14 \value DontWatchForChanges Do not add file watchers to the paths. @@ -1847,6 +1852,7 @@ bool FileSystemModel::testOption(Option option) const } /*! + \internal \property FileSystemModel::options \brief the various options that affect the model \since 5.14 @@ -2121,6 +2127,7 @@ QDir::Filters FileSystemModel::filter() const } /*! + \internal \property FileSystemModel::resolveSymlinks \brief Whether the directory model should resolve symbolic links @@ -2146,6 +2153,7 @@ bool FileSystemModel::resolveSymlinks() const } /*! + \internal \property FileSystemModel::readOnly \brief Whether the directory model allows writing to the file system @@ -2165,6 +2173,7 @@ bool FileSystemModel::isReadOnly() const } /*! + \internal \property FileSystemModel::nameFilterDisables \brief Whether files that don't pass the name filter are hidden or disabled diff --git a/src/libs/utils/fsengine/fileiconprovider.cpp b/src/libs/utils/fsengine/fileiconprovider.cpp index 07ea4b3f5a..cd2cc060a2 100644 --- a/src/libs/utils/fsengine/fileiconprovider.cpp +++ b/src/libs/utils/fsengine/fileiconprovider.cpp @@ -28,6 +28,7 @@ Q_LOGGING_CATEGORY(fileIconProvider, "qtc.core.fileiconprovider", QtWarningMsg) /*! \class Utils::FileIconProvider + \internal \inmodule QtCreator \brief Provides functions for registering custom overlay icons for system icons. @@ -220,6 +221,7 @@ QIcon FileIconProviderImplementation::icon(const FilePath &filePath) const } /*! + \internal Returns the icon associated with the file suffix in \a filePath. If there is none, the default icon of the operating system is returned. */ @@ -230,14 +232,16 @@ QIcon icon(const FilePath &filePath) } /*! - * \overload - */ + \internal + \overload +*/ QIcon icon(QFileIconProvider::IconType type) { return instance()->icon(type); } /*! + \internal Creates a pixmap with \a baseIcon and lays \a overlayIcon over it. */ QPixmap overlayIcon(const QPixmap &baseIcon, const QIcon &overlayIcon) @@ -249,6 +253,7 @@ QPixmap overlayIcon(const QPixmap &baseIcon, const QIcon &overlayIcon) } /*! + \internal Creates a pixmap with \a baseIcon at \a size and \a overlay. */ QPixmap overlayIcon(QStyle::StandardPixmap baseIcon, const QIcon &overlay, const QSize &size) @@ -257,6 +262,7 @@ QPixmap overlayIcon(QStyle::StandardPixmap baseIcon, const QIcon &overlay, const } /*! + \internal Registers an icon at \a path for a given \a suffix, overlaying the system file icon. */ @@ -266,6 +272,7 @@ void registerIconOverlayForSuffix(const QString &path, const QString &suffix) } /*! + \internal Registers \a icon for all the suffixes of a the mime type \a mimeType, overlaying the system file icon. */ @@ -275,7 +282,8 @@ void registerIconOverlayForMimeType(const QIcon &icon, const QString &mimeType) } /*! - * \overload + \internal + \overload */ void registerIconOverlayForMimeType(const QString &path, const QString &mimeType) { diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index b603b0bf8f..bea7faafd3 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -205,6 +205,7 @@ LayoutItem::~LayoutItem() = default; /*! \fn template LayoutItem(const T &t) + \internal Constructs a layout item proxy for \a t. @@ -489,6 +490,7 @@ void doAddWidget(LayoutBuilder &builder, QWidget *widget) /*! \class Layouting::LayoutBuilder + \internal \inmodule QtCreator \brief The LayoutBuilder class provides a convenient way to fill \c QFormLayout @@ -505,6 +507,7 @@ void doAddWidget(LayoutBuilder &builder, QWidget *widget) LayoutBuilder::LayoutBuilder() = default; /*! + \internal Destructs a layout builder. */ LayoutBuilder::~LayoutBuilder() = default; @@ -521,7 +524,7 @@ void LayoutBuilder::addItems(const LayoutItems &items) } /*! - This starts a new row containing \a items. The row can be further extended by + Starts a new row containing \a items. The row can be further extended by other items using \c addItem() or \c addItems(). \sa addItem(), addItems() diff --git a/src/libs/utils/treemodel.cpp b/src/libs/utils/treemodel.cpp index 53477b53c9..e5e5aa59b9 100644 --- a/src/libs/utils/treemodel.cpp +++ b/src/libs/utils/treemodel.cpp @@ -72,6 +72,7 @@ private: }; /*! + \internal Connect to all of the models signals. Whenever anything happens recheck everything. */ @@ -135,6 +136,7 @@ void ModelTest::runAllTests() } /*! + \internal nonDestructiveBasicTest tries to call a number of the basic functions (not all) to make sure the model doesn't outright segfault, testing the functions that makes sense. */ @@ -173,6 +175,7 @@ void ModelTest::nonDestructiveBasicTest() } /*! + \internal Tests model's implementation of QAbstractItemModel::rowCount() and hasChildren() Models that are dynamically populated are not as fully tested here. @@ -200,6 +203,7 @@ void ModelTest::rowCount() } /*! + \internal Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren() */ void ModelTest::columnCount() @@ -218,6 +222,7 @@ void ModelTest::columnCount() } /*! + \internal Tests model's implementation of QAbstractItemModel::hasIndex() */ void ModelTest::hasIndex() @@ -242,6 +247,7 @@ void ModelTest::hasIndex() } /*! + \internal Tests model's implementation of QAbstractItemModel::index() */ void ModelTest::index() @@ -274,6 +280,7 @@ void ModelTest::index() } /*! + \internal Tests model's implementation of QAbstractItemModel::parent() */ void ModelTest::parent() @@ -322,6 +329,7 @@ void ModelTest::parent() } /*! + \internal Called from the parent() test. A model that returns an index of parent X should also return X when asking @@ -430,6 +438,7 @@ void ModelTest::checkChildren(const QModelIndex &parent, int currentDepth) } /*! + \internal Tests model's implementation of QAbstractItemModel::data() */ void ModelTest::data() @@ -494,6 +503,7 @@ void ModelTest::data() } /*! + \internal Store what is about to be inserted to make sure it actually happens \sa rowsInserted() @@ -510,6 +520,7 @@ void ModelTest::rowsAboutToBeInserted(const QModelIndex &parent, int start, int } /*! + \internal Confirm that what was said was going to happen actually did \sa rowsAboutToBeInserted() @@ -547,6 +558,7 @@ void ModelTest::layoutChanged() } /*! + \internal Store what is about to be inserted to make sure it actually happens \sa rowsRemoved() @@ -562,6 +574,7 @@ void ModelTest::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int e } /*! + \internal Confirm that what was said was going to happen actually did \sa rowsAboutToBeRemoved() -- cgit v1.2.3 From 48e2c66a4ee8c19be0e28fddfe996e9526506216 Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Fri, 26 May 2023 09:11:00 +0200 Subject: Doc: Remove docs for Utils::SynchronousProcess ...that has been integrated into QtcProcess. Change-Id: I39b6e4cb5d713d59345015b2a471cd4e6ef99f57 Reviewed-by: Eike Ziller --- src/libs/utils/process.cpp | 31 ------------------------------- 1 file changed, 31 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/process.cpp b/src/libs/utils/process.cpp index 9a798ec11c..85d3fa54ed 100644 --- a/src/libs/utils/process.cpp +++ b/src/libs/utils/process.cpp @@ -1595,37 +1595,6 @@ QString Process::readAllStandardError() return QString::fromUtf8(readAllRawStandardError()); } -/*! - \class Utils::SynchronousProcess - \inmodule QtCreator - - \brief The SynchronousProcess class runs a synchronous process in its own - event loop that blocks only user input events. Thus, it allows for the GUI to - repaint and append output to log windows. - - The callbacks set with setStdOutCallback(), setStdErrCallback() are called - with complete lines based on the '\\n' marker. - They would typically be used for log windows. - - Alternatively you can used setStdOutLineCallback() and setStdErrLineCallback() - to process the output line by line. - - There is a timeout handling that takes effect after the last data have been - read from stdout/stdin (as opposed to waitForFinished(), which measures time - since it was invoked). It is thus also suitable for slow processes that - continuously output data (like version system operations). - - The property timeOutMessageBoxEnabled influences whether a message box is - shown asking the user if they want to kill the process on timeout (default: false). - - There are also static utility functions for dealing with fully synchronous - processes, like reading the output with correct timeout handling. - - Caution: This class should NOT be used if there is a chance that the process - triggers opening dialog boxes (for example, by file watchers triggering), - as this will cause event loop problems. -*/ - QString Process::exitMessage() const { const QString fullCmd = commandLine().toUserOutput(); -- cgit v1.2.3 From d2ed96a055a7cc4b65d2bb2c32df300b226af57d Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Fri, 26 May 2023 09:34:20 +0200 Subject: VariableChooser: Use same type name for declaration & definition Fixes documentation issue. Change-Id: Id237998b651bdf7887a0be846464265f734001c4 Reviewed-by: Qt CI Bot Reviewed-by: Leena Miettinen Reviewed-by: hjk --- src/libs/utils/variablechooser.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/variablechooser.h b/src/libs/utils/variablechooser.h index 1fe08a1e59..257dcea5e2 100644 --- a/src/libs/utils/variablechooser.h +++ b/src/libs/utils/variablechooser.h @@ -13,6 +13,8 @@ namespace Utils { class MacroExpander; +using MacroExpanderProvider = std::function; + namespace Internal { class VariableChooserPrivate; } class QTCREATOR_UTILS_EXPORT VariableChooser : public QWidget @@ -23,7 +25,7 @@ public: explicit VariableChooser(QWidget *parent = nullptr); ~VariableChooser() override; - void addMacroExpanderProvider(const std::function &provider); + void addMacroExpanderProvider(const MacroExpanderProvider &provider); void addSupportedWidget(QWidget *textcontrol, const QByteArray &ownName = QByteArray()); static void addSupportForChildWidgets(QWidget *parent, MacroExpander *expander); -- cgit v1.2.3 From a7ab1ba98711cb286286bdb928225c967fcd6b29 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Thu, 25 May 2023 07:56:39 +0200 Subject: TaskTree: Doc corrections Make docs more consistent. Add some more precision when something is not clear. Do some adaptations for behavioral changes. Change-Id: I95c76fedf2c9d611702097842452186ea4cdf8b0 Reviewed-by: Reviewed-by: Leena Miettinen --- src/libs/solutions/tasking/tasktree.cpp | 85 +++++++++++++++++---------------- 1 file changed, 45 insertions(+), 40 deletions(-) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index 6d3e7eb95b..3d7f63216c 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -916,7 +916,7 @@ void TaskNode::invokeEndHandler(bool success) Use Task handlers to set up a task for execution and to enable reading the output data from the task when it finishes with success or an error. - \section2 Task Start Handler + \section2 Task's Start Handler When a corresponding task class object is created and before it's started, the task tree invokes a mandatory user-provided setup handler. The setup @@ -933,9 +933,9 @@ void TaskNode::invokeEndHandler(bool success) You can modify the passed Process in the setup handler, so that the task tree can start the process according to your configuration. - You do not need to call \e {process.start();} in the setup handler, - as the task tree calls it when needed. The setup handler is mandatory - and must be the first argument of the task's constructor. + You should not call \e {process.start();} in the setup handler, + as the task tree calls it when needed. The setup handler is optional. When used, + it must be the first argument of the task's constructor. Optionally, the setup handler may return a TaskAction. The returned TaskAction influences the further start behavior of a given task. The @@ -947,7 +947,7 @@ void TaskNode::invokeEndHandler(bool success) \li Brief Description \row \li Continue - \li The task is started normally. This is the default behavior when the + \li The task will be started normally. This is the default behavior when the setup handler doesn't return TaskAction (that is, its return type is void). \row @@ -960,7 +960,7 @@ void TaskNode::invokeEndHandler(bool success) This is useful for running a task only when a condition is met and the data needed to evaluate this condition is not known until previously started tasks - finish. This way, the setup handler dynamically decides whether to start the + finish. In this way, the setup handler dynamically decides whether to start the corresponding task normally or skip it and report success or an error. For more information about inter-task data exchange, see \l Storage. @@ -987,8 +987,8 @@ void TaskNode::invokeEndHandler(bool success) The done and error handlers may collect output data from Process, and store it for further processing or perform additional actions. The done handler is optional. - When used, it must be the second argument of the task constructor. - The error handler must always be the third argument. + When used, it must be the second argument of the task's constructor. + The error handler is also optional. When used, it must always be the third argument. You can omit the handlers or substitute the ones that you do not need with curly braces ({}). \note If the task setup handler returns StopWithDone or StopWithError, @@ -1020,7 +1020,7 @@ void TaskNode::invokeEndHandler(bool success) handler. If you add more than one onGroupSetup element to a group, an assert is triggered at runtime that includes an error message. - Like the task start handler, the group start handler may return TaskAction. + Like the task's start handler, the group start handler may return TaskAction. The returned TaskAction value affects the start behavior of the whole group. If you do not specify a group start handler or its return type is void, the default group's action is TaskAction::Continue, so that all @@ -1117,7 +1117,7 @@ void TaskNode::invokeEndHandler(bool success) runtime that includes an error message. \note Even if the group setup handler returns StopWithDone or StopWithError, - one of the task's done or error handlers is invoked. This behavior differs + one of the group's done or error handlers is invoked. This behavior differs from that of task handlers and might change in the future. \section1 Other Group Elements @@ -1173,7 +1173,7 @@ void TaskNode::invokeEndHandler(bool success) \section2 Workflow Policy The workflow policy element in a Group specifies how the group should behave - when its direct child tasks finish: + when any of its \e direct child's tasks finish: \table \header @@ -1185,8 +1185,8 @@ void TaskNode::invokeEndHandler(bool success) \list 1 \li Stops the running tasks (if any - for example, in parallel mode). - \li Skips executing tasks it has not started (for example, in the - sequential mode). + \li Skips executing tasks it has not started yet (for example, in the + sequential mode - those, that are placed after the failed task). \li Immediately finishes with an error. \endlist If all child tasks finish successfully, the group finishes with success. @@ -1207,7 +1207,10 @@ void TaskNode::invokeEndHandler(bool success) \li stopOnDone \li If a task finishes with success, the group: \list 1 - \li Stops running tasks and skips those that it has not started. + \li Stops the running tasks (if any - for example, in parallel + mode). + \li Skips executing tasks it has not started yet (for example, in the + sequential mode - those, that are placed after the successfully finished task). \li Immediately finishes with success. \endlist If all tasks finish with an error, the group finishes with an error. @@ -1226,27 +1229,27 @@ void TaskNode::invokeEndHandler(bool success) If all tasks finish with an error, the group finishes with an error. \row \li stopOnFinished - \li The group starts as many tasks as it can. When a task finishes + \li The group starts as many tasks as it can. When a task finishes, the group stops and reports the task's result. Useful only in parallel mode. In sequential mode, only the first task is started, and when finished, the group finishes too, so the other tasks are ignored. \row \li optional - \li The group executes all tasks and ignores their return state. If all + \li The group executes all tasks and ignores their return state. When all tasks finish, the group finishes with success. \endtable - When the group is empty, it finishes immediately with success, + When a Group is empty, it finishes immediately with success, regardless of its workflow policy. - If a child of a group is also a group (in a nested tree), the child group + If a child of a group is also a group, the child group runs its tasks according to its own workflow policy. \section2 Storage Use the Storage element to exchange information between tasks. Especially, - in the sequential execution mode, when a task needs data from another task - before it can start. For example, a task tree that copies data by reading + in the sequential execution mode, when a task needs data from another, + already finished task, before it can start. For example, a task tree that copies data by reading it from a source and writing it to a destination might look as follows: \code @@ -1265,21 +1268,22 @@ void TaskNode::invokeEndHandler(bool success) const auto onLoaderSetup = [source](Async &async) { async.setConcurrentCallData(&load, source); }; - // [4] runtime: task tree activates the instance from [5] before invoking handler + // [4] runtime: task tree activates the instance from [7] before invoking handler const auto onLoaderDone = [storage](const Async &async) { - storage->content = async.result(); + storage->content = async.result(); // [5] loader stores the result in storage }; - // [4] runtime: task tree activates the instance from [5] before invoking handler + // [4] runtime: task tree activates the instance from [7] before invoking handler const auto onSaverSetup = [storage, destination](Async &async) { - async.setConcurrentCallData(&save, destination, storage->content); + const QByteArray content = storage->content; // [6] saver takes data from storage + async.setConcurrentCallData(&save, destination, content); }; const auto onSaverDone = [](const Async &async) { qDebug() << "Save done successfully"; }; const Group root { - // [5] runtime: task tree creates an instance of CopyStorage when root is entered + // [7] runtime: task tree creates an instance of CopyStorage when root is entered Storage(storage), AsyncTask(onLoaderSetup, onLoaderDone), AsyncTask(onSaverSetup, onSaverDone) @@ -1291,11 +1295,11 @@ void TaskNode::invokeEndHandler(bool success) In the example above, the inter-task data consists of a QByteArray content variable [2] enclosed in a CopyStorage custom struct [1]. If the loader finishes successfully, it stores the data in a CopyStorage::content - variable. The saver then uses the variable to configure the saving task. + variable [5]. The saver then uses the variable to configure the saving task [6]. To enable a task tree to manage the CopyStorage struct, an instance of TreeStorage is created [3]. If a copy of this object is - inserted as group's child task [5], an instance of CopyStorage struct is + inserted as group's child task [7], an instance of CopyStorage struct is created dynamically when the task tree enters this group. When the task tree leaves this group, the existing instance of CopyStorage struct is destructed as it's no longer needed. @@ -1310,12 +1314,12 @@ void TaskNode::invokeEndHandler(bool success) copy of the TreeStorage object to the handler (for example, in a lambda capture) [4]. - When the task tree invokes a handler in a subtree containing the storage [5], + When the task tree invokes a handler in a subtree containing the storage [7], the task tree activates its own CopyStorage instance inside the TreeStorage object. Therefore, the CopyStorage struct may be accessed only from within the handler body. To access the currently active - CopyStorage from within TreeStorage, use the TreeStorage::operator->() - or TreeStorage::activeStorage() method. + CopyStorage from within TreeStorage, use the TreeStorage::operator->(), + TreeStorage::operator*() or TreeStorage::activeStorage() method. The following list summarizes how to employ a Storage object into the task tree: @@ -1323,7 +1327,8 @@ void TaskNode::invokeEndHandler(bool success) \li Define the custom structure MyStorage with custom data [1], [2] \li Create an instance of TreeStorage storage [3] \li Pass the TreeStorage instance to handlers [4] - \li Insert the TreeStorage instance into a group [5] + \li Access the MyStorage instance in handlers [5], [6] + \li Insert the TreeStorage instance into a group [7] \endlist \note The current implementation assumes that all running task trees @@ -1395,10 +1400,10 @@ void TaskNode::invokeEndHandler(bool success) asynchronous task: \code - class TimeoutAdapter : public Tasking::TaskAdapter + class TimeoutTaskAdapter : public Tasking::TaskAdapter { public: - TimeoutAdapter() { + TimeoutTaskAdapter() { task()->setSingleShot(true); task()->setInterval(1000); connect(task(), &QTimer::timeout, this, [this] { emit done(true); }); @@ -1406,7 +1411,7 @@ void TaskNode::invokeEndHandler(bool success) void start() final { task()->start(); } }; - QTC_DECLARE_CUSTOM_TASK(Timeout, TimeoutAdapter); + QTC_DECLARE_CUSTOM_TASK(TimeoutTask, TimeoutTaskAdapter); \endcode You must derive the custom adapter from the TaskAdapter class template @@ -1415,15 +1420,15 @@ void TaskNode::invokeEndHandler(bool success) later as an argument to the task's handlers. The instance of this class parameter automatically becomes a member of the TaskAdapter template, and is accessible through the TaskAdapter::task() method. The constructor - of TimeoutAdapter initially configures the QTimer object and connects - to the QTimer::timeout signal. When the signal is triggered, TimeoutAdapter + of TimeoutTaskAdapter initially configures the QTimer object and connects + to the QTimer::timeout signal. When the signal is triggered, TimeoutTaskAdapter emits the done(true) signal to inform the task tree that the task finished successfully. If it emits done(false), the task finished with an error. The TaskAdapter::start() method starts the timer. - To make QTimer accessible inside TaskTree under the \e Timeout name, - register it with QTC_DECLARE_CUSTOM_TASK(Timeout, TimeoutAdapter). Timeout - becomes a new task type inside Tasking namespace, using TimeoutAdapter. + To make QTimer accessible inside TaskTree under the \e TimeoutTask name, + register it with QTC_DECLARE_CUSTOM_TASK(TimeoutTask, TimeoutTaskAdapter). + TimeoutTask becomes a new task type inside Tasking namespace, using TimeoutTaskAdapter. The new task type is now registered, and you can use it in TaskTree: @@ -1436,7 +1441,7 @@ void TaskNode::invokeEndHandler(bool success) }; const Group root { - Timeout(onTimeoutSetup, onTimeoutDone) + TimeoutTask(onTimeoutSetup, onTimeoutDone) }; \endcode -- cgit v1.2.3 From b7f814372823df1d5de49207ab0346bad460bf9d Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Fri, 26 May 2023 13:04:31 +0200 Subject: Tr: Various small fixes Change-Id: Ic86d6b6a4aae7b301557eaa4296beb9a31399e03 Reviewed-by: Leena Miettinen Reviewed-by: Qt CI Bot --- src/libs/languageserverprotocol/jsonrpcmessages.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/languageserverprotocol/jsonrpcmessages.cpp b/src/libs/languageserverprotocol/jsonrpcmessages.cpp index 31183cc0cc..cef4c47cb0 100644 --- a/src/libs/languageserverprotocol/jsonrpcmessages.cpp +++ b/src/libs/languageserverprotocol/jsonrpcmessages.cpp @@ -77,8 +77,7 @@ JsonRpcMessage::JsonRpcMessage(const BaseMessage &message) if (doc.isObject()) m_jsonObject = doc.object(); else if (doc.isNull()) - m_parseError = - Tr::tr("Could not parse JSON message \"%1\".").arg(error.errorString()); + m_parseError = Tr::tr("Could not parse JSON message: \"%1\".").arg(error.errorString()); else m_parseError = Tr::tr("Expected a JSON object, but got a JSON \"%1\" value.").arg(docType(doc)); -- cgit v1.2.3 From f2c1455025805b013587c01007ce18247e909182 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Fri, 26 May 2023 11:29:50 +0200 Subject: Tr: Wrap file paths with "" Change-Id: Iee0e941ff503ff485e8e9c0d9fe3e52eea9042d5 Reviewed-by: Leena Miettinen --- src/libs/advanceddockingsystem/workspaceview.cpp | 8 ++++---- src/libs/utils/devicefileaccess.cpp | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) (limited to 'src/libs') diff --git a/src/libs/advanceddockingsystem/workspaceview.cpp b/src/libs/advanceddockingsystem/workspaceview.cpp index bab54e5e92..1b4cfc7e27 100644 --- a/src/libs/advanceddockingsystem/workspaceview.cpp +++ b/src/libs/advanceddockingsystem/workspaceview.cpp @@ -325,10 +325,10 @@ bool WorkspaceView::confirmWorkspaceDelete(const QStringList &fileNames) { const QString title = fileNames.size() == 1 ? Tr::tr("Delete Workspace") : Tr::tr("Delete Workspaces"); - const QString question - = fileNames.size() == 1 - ? Tr::tr("Delete workspace %1?").arg(fileNames.first()) - : Tr::tr("Delete these workspaces?\n %1").arg(fileNames.join("\n ")); + const QString question = fileNames.size() == 1 + ? Tr::tr("Delete workspace \"%1\"?").arg(fileNames.first()) + : Tr::tr("Delete these workspaces?") + + QString("\n %1").arg(fileNames.join("\n ")); return QMessageBox::question(parentWidget(), title, question, QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes; } diff --git a/src/libs/utils/devicefileaccess.cpp b/src/libs/utils/devicefileaccess.cpp index a29c5bb9be..ef6640d8a2 100644 --- a/src/libs/utils/devicefileaccess.cpp +++ b/src/libs/utils/devicefileaccess.cpp @@ -197,15 +197,15 @@ expected_str DeviceFileAccess::copyRecursively(const FilePath &src, const FilePath &target) const { if (!src.isDir()) { - return make_unexpected(Tr::tr("Cannot copy from %1, it is not a directory.") - .arg(src.toUserOutput()) - .arg(target.toUserOutput())); + return make_unexpected( + Tr::tr("Cannot copy from \"%1\", it is not a directory.").arg(src.toUserOutput())); } if (!target.ensureWritableDir()) { - return make_unexpected(Tr::tr("Cannot copy %1 to %2, it is not a writable directory.") - .arg(src.toUserOutput()) - .arg(target.toUserOutput())); + return make_unexpected( + Tr::tr("Cannot copy \"%1\" to \"%2\", it is not a writable directory.") + .arg(src.toUserOutput()) + .arg(target.toUserOutput())); } #ifdef UTILS_STATIC_LIBRARY -- cgit v1.2.3 From 6e7bb28f0979dea21c7ed42a34103cbaeea8123d Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Fri, 26 May 2023 11:52:43 +0200 Subject: Tr: Add missing full stops Change-Id: I2debc56d0740eaa30c7d597eae18910f319c1d98 Reviewed-by: Leena Miettinen --- src/libs/qmljs/qmljsbind.cpp | 2 +- src/libs/utils/devicefileaccess.cpp | 25 +++++++++++++------------ 2 files changed, 14 insertions(+), 13 deletions(-) (limited to 'src/libs') diff --git a/src/libs/qmljs/qmljsbind.cpp b/src/libs/qmljs/qmljsbind.cpp index e2143ed68e..6ccddcbe63 100644 --- a/src/libs/qmljs/qmljsbind.cpp +++ b/src/libs/qmljs/qmljsbind.cpp @@ -326,7 +326,7 @@ bool Bind::visit(UiInlineComponent *ast) if (!_currentComponentName.isEmpty()) { _currentComponentName += "."; _diagnosticMessages->append( - errorMessage(ast, Tr::tr("Nested inline components are not supported"))); + errorMessage(ast, Tr::tr("Nested inline components are not supported."))); } _currentComponentName += ast->name.toString(); _rootObjectValue = nullptr; diff --git a/src/libs/utils/devicefileaccess.cpp b/src/libs/utils/devicefileaccess.cpp index ef6640d8a2..f5bb1d2048 100644 --- a/src/libs/utils/devicefileaccess.cpp +++ b/src/libs/utils/devicefileaccess.cpp @@ -161,7 +161,7 @@ expected_str DeviceFileAccess::copyFile(const FilePath &filePath, const Fi Q_UNUSED(target) QTC_CHECK(false); return make_unexpected( - Tr::tr("copyFile is not implemented for \"%1\"").arg(filePath.toUserOutput())); + Tr::tr("copyFile is not implemented for \"%1\".").arg(filePath.toUserOutput())); } expected_str copyRecursively_fallback(const FilePath &src, const FilePath &target) @@ -305,7 +305,7 @@ expected_str DeviceFileAccess::fileContents(const FilePath &filePath Q_UNUSED(offset) QTC_CHECK(false); return make_unexpected( - Tr::tr("fileContents is not implemented for \"%1\"").arg(filePath.toUserOutput())); + Tr::tr("fileContents is not implemented for \"%1\".").arg(filePath.toUserOutput())); } expected_str DeviceFileAccess::writeFileContents(const FilePath &filePath, @@ -317,7 +317,7 @@ expected_str DeviceFileAccess::writeFileContents(const FilePath &filePat Q_UNUSED(offset) QTC_CHECK(false); return make_unexpected( - Tr::tr("writeFileContents is not implemented for \"%1\"").arg(filePath.toUserOutput())); + Tr::tr("writeFileContents is not implemented for \"%1\".").arg(filePath.toUserOutput())); } FilePathInfo DeviceFileAccess::filePathInfo(const FilePath &filePath) const @@ -382,8 +382,8 @@ expected_str DeviceFileAccess::createTempFile(const FilePath &filePath { Q_UNUSED(filePath) QTC_CHECK(false); - return make_unexpected(Tr::tr("createTempFile is not implemented for \"%1\"") - .arg(filePath.toUserOutput())); + return make_unexpected( + Tr::tr("createTempFile is not implemented for \"%1\".").arg(filePath.toUserOutput())); } @@ -658,10 +658,10 @@ expected_str DesktopDeviceFileAccess::fileContents(const FilePath &f const QString path = filePath.path(); QFile f(path); if (!f.exists()) - return make_unexpected(Tr::tr("File \"%1\" does not exist").arg(path)); + return make_unexpected(Tr::tr("File \"%1\" does not exist.").arg(path)); if (!f.open(QFile::ReadOnly)) - return make_unexpected(Tr::tr("Could not open File \"%1\"").arg(path)); + return make_unexpected(Tr::tr("Could not open File \"%1\".").arg(path)); if (offset != 0) f.seek(offset); @@ -686,14 +686,14 @@ expected_str DesktopDeviceFileAccess::writeFileContents(const FilePath & const bool isOpened = file.open(QFile::WriteOnly | QFile::Truncate); if (!isOpened) return make_unexpected( - Tr::tr("Could not open file \"%1\" for writing").arg(filePath.toUserOutput())); + Tr::tr("Could not open file \"%1\" for writing.").arg(filePath.toUserOutput())); if (offset != 0) file.seek(offset); qint64 res = file.write(data); if (res != data.size()) return make_unexpected( - Tr::tr("Could not write to file \"%1\" (only %2 of %3 bytes written)") + Tr::tr("Could not write to file \"%1\" (only %2 of %3 bytes written).") .arg(filePath.toUserOutput()) .arg(res) .arg(data.size())); @@ -705,8 +705,9 @@ expected_str DesktopDeviceFileAccess::createTempFile(const FilePath &f QTemporaryFile file(filePath.path()); file.setAutoRemove(false); if (!file.open()) { - return make_unexpected(Tr::tr("Could not create temporary file in \"%1\" (%2)") - .arg(filePath.toUserOutput()).arg(file.errorString())); + return make_unexpected(Tr::tr("Could not create temporary file in \"%1\" (%2).") + .arg(filePath.toUserOutput()) + .arg(file.errorString())); } return filePath.withNewPath(file.fileName()); } @@ -1039,7 +1040,7 @@ expected_str UnixDeviceFileAccess::createTempFile(const FilePath &file } newPath = filePath.withNewPath(tmplate); if (--maxTries == 0) { - return make_unexpected(Tr::tr("Failed creating temporary file \"%1\" (too many tries)") + return make_unexpected(Tr::tr("Failed creating temporary file \"%1\" (too many tries).") .arg(filePath.toUserOutput())); } } while (newPath.exists()); -- cgit v1.2.3 From fb59c70dcb1ace439cf9a03e95d90dd34dbe9584 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Fri, 26 May 2023 14:10:38 +0200 Subject: CppEditor: Revert changes to parseExpressionStatement() Amends c8f29b9e0148202ab1959466e14fa23411fd8214. It turns out that there are contexts where we want to parse an expression statement even with the semicolon missing (e.g. completion on incomplete code). So leave the existing functions unchanged and do the thorough check afterwards in parseIfStatement(). Change-Id: Id6209ef1abfe9d155c5b9381e6ae655cc721feb2 Reviewed-by: Qt CI Bot Reviewed-by: Christian Stenger --- src/libs/3rdparty/cplusplus/AST.h | 1 - src/libs/3rdparty/cplusplus/ASTClone.cpp | 2 -- src/libs/3rdparty/cplusplus/ASTMatcher.cpp | 5 ----- src/libs/3rdparty/cplusplus/ASTVisit.cpp | 1 - src/libs/3rdparty/cplusplus/Bind.cpp | 4 +--- src/libs/3rdparty/cplusplus/Parser.cpp | 34 +++++++++++++++++++----------- 6 files changed, 23 insertions(+), 24 deletions(-) (limited to 'src/libs') diff --git a/src/libs/3rdparty/cplusplus/AST.h b/src/libs/3rdparty/cplusplus/AST.h index 6a5da23306..3f48e192b9 100644 --- a/src/libs/3rdparty/cplusplus/AST.h +++ b/src/libs/3rdparty/cplusplus/AST.h @@ -1779,7 +1779,6 @@ public: int if_token = 0; int constexpr_token = 0; int lparen_token = 0; - DeclarationAST *initDecl = nullptr; StatementAST *initStmt = nullptr; ExpressionAST *condition = nullptr; int rparen_token = 0; diff --git a/src/libs/3rdparty/cplusplus/ASTClone.cpp b/src/libs/3rdparty/cplusplus/ASTClone.cpp index 57617bb118..c9c2fc8294 100644 --- a/src/libs/3rdparty/cplusplus/ASTClone.cpp +++ b/src/libs/3rdparty/cplusplus/ASTClone.cpp @@ -785,8 +785,6 @@ IfStatementAST *IfStatementAST::clone(MemoryPool *pool) const ast->if_token = if_token; ast->constexpr_token = constexpr_token; ast->lparen_token = lparen_token; - if (initDecl) - ast->initDecl = initDecl->clone(pool); if (initStmt) ast->initStmt = initStmt->clone(pool); if (condition) diff --git a/src/libs/3rdparty/cplusplus/ASTMatcher.cpp b/src/libs/3rdparty/cplusplus/ASTMatcher.cpp index 6f12efb8ac..b264098a96 100644 --- a/src/libs/3rdparty/cplusplus/ASTMatcher.cpp +++ b/src/libs/3rdparty/cplusplus/ASTMatcher.cpp @@ -1318,11 +1318,6 @@ bool ASTMatcher::match(IfStatementAST *node, IfStatementAST *pattern) pattern->lparen_token = node->lparen_token; - if (!pattern->initDecl) - pattern->initDecl = node->initDecl; - else if (!AST::match(node->initDecl, pattern->initDecl, this)) - return false; - if (!pattern->initStmt) pattern->initStmt = node->initStmt; else if (!AST::match(node->initStmt, pattern->initStmt, this)) diff --git a/src/libs/3rdparty/cplusplus/ASTVisit.cpp b/src/libs/3rdparty/cplusplus/ASTVisit.cpp index 7e8f75f233..5b0ef3ce33 100644 --- a/src/libs/3rdparty/cplusplus/ASTVisit.cpp +++ b/src/libs/3rdparty/cplusplus/ASTVisit.cpp @@ -563,7 +563,6 @@ void ForStatementAST::accept0(ASTVisitor *visitor) void IfStatementAST::accept0(ASTVisitor *visitor) { if (visitor->visit(this)) { - accept(initDecl, visitor); accept(initStmt, visitor); accept(condition, visitor); accept(statement, visitor); diff --git a/src/libs/3rdparty/cplusplus/Bind.cpp b/src/libs/3rdparty/cplusplus/Bind.cpp index e3d38a09a8..c85d401c49 100644 --- a/src/libs/3rdparty/cplusplus/Bind.cpp +++ b/src/libs/3rdparty/cplusplus/Bind.cpp @@ -1528,9 +1528,7 @@ bool Bind::visit(IfStatementAST *ast) ast->symbol = block; Scope *previousScope = switchScope(block); - if (ast->initDecl) - this->declaration(ast->initDecl); - else if (ast->initStmt) + if (ast->initStmt) this->statement(ast->initStmt); /*ExpressionTy condition =*/ this->expression(ast->condition); this->statement(ast->statement); diff --git a/src/libs/3rdparty/cplusplus/Parser.cpp b/src/libs/3rdparty/cplusplus/Parser.cpp index 31e290d1ad..6bf19f5982 100644 --- a/src/libs/3rdparty/cplusplus/Parser.cpp +++ b/src/libs/3rdparty/cplusplus/Parser.cpp @@ -3511,14 +3511,12 @@ bool Parser::parseExpressionStatement(StatementAST *&node) ExpressionAST *expression = nullptr; if (parseExpression(expression)) { - if (LA() == T_SEMICOLON) { - ExpressionStatementAST *ast = new (previousPool) ExpressionStatementAST; - ast->semicolon_token = consumeToken(); - if (expression) - ast->expression = expression->clone(previousPool); - node = ast; - parsed = true; - } + ExpressionStatementAST *ast = new (previousPool) ExpressionStatementAST; + if (expression) + ast->expression = expression->clone(previousPool); + match(T_SEMICOLON, &ast->semicolon_token); + node = ast; + parsed = true; } _inExpressionStatement = wasInExpressionStatement; @@ -4079,14 +4077,26 @@ bool Parser::parseIfStatement(StatementAST *&node) if (_languageFeatures.cxx17Enabled) { const int savedCursor = cursor(); const bool savedBlockErrors = _translationUnit->blockErrors(true); - if (!parseSimpleDeclaration(ast->initDecl)) { + bool foundInitStmt = parseExpressionOrDeclarationStatement(ast->initStmt); + if (foundInitStmt) + foundInitStmt = ast->initStmt; + if (foundInitStmt) { + if (const auto exprStmt = ast->initStmt->asExpressionStatement()) { + foundInitStmt = exprStmt->semicolon_token; + } else if (const auto declStmt = ast->initStmt->asDeclarationStatement()) { + foundInitStmt = declStmt->declaration + && declStmt->declaration->asSimpleDeclaration() + && declStmt->declaration->asSimpleDeclaration()->semicolon_token; + } else { + foundInitStmt = false; + } + } + if (!foundInitStmt) { + ast->initStmt = nullptr; rewind(savedCursor); - if (!parseExpressionStatement(ast->initStmt)) - rewind(savedCursor); } _translationUnit->blockErrors(savedBlockErrors); } - parseCondition(ast->condition); match(T_RPAREN, &ast->rparen_token); if (! parseStatement(ast->statement)) -- cgit v1.2.3 From 08b5f64c1a280177d937e2c27d5e3a582ae03eff Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Fri, 26 May 2023 13:50:44 +0200 Subject: Doc: Mark destructors as \internal instead of \reimp in Aspects docs Document BoolAspect::value(). Change-Id: Ia7f359e9302d371e3ea79fc3fce04e3c3c9a22a0 Reviewed-by: Eike Ziller --- src/libs/utils/aspects.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index 0e7050a7e0..d3610b2c7d 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -1441,7 +1441,7 @@ BoolAspect::BoolAspect(AspectContainer *container) } /*! - \reimp + \internal */ BoolAspect::~BoolAspect() = default; @@ -1537,7 +1537,7 @@ void BoolAspect::emitChangedValue() /*! - \reimp + Returns the value of the boolean aspect as a boolean value. */ bool BoolAspect::value() const @@ -1609,7 +1609,7 @@ SelectionAspect::SelectionAspect(AspectContainer *container) } /*! - \reimp + \internal */ SelectionAspect::~SelectionAspect() = default; @@ -1813,7 +1813,7 @@ MultiSelectionAspect::MultiSelectionAspect(AspectContainer *container) } /*! - \reimp + \internal */ MultiSelectionAspect::~MultiSelectionAspect() = default; @@ -1922,7 +1922,7 @@ IntegerAspect::IntegerAspect(AspectContainer *container) } /*! - \reimp + \internal */ IntegerAspect::~IntegerAspect() = default; @@ -2056,7 +2056,7 @@ DoubleAspect::DoubleAspect(AspectContainer *container) } /*! - \reimp + \internal */ DoubleAspect::~DoubleAspect() = default; @@ -2210,7 +2210,7 @@ StringListAspect::StringListAspect(AspectContainer *container) } /*! - \reimp + \internal */ StringListAspect::~StringListAspect() = default; @@ -2282,7 +2282,7 @@ IntegersAspect::IntegersAspect(AspectContainer *container) } /*! - \reimp + \internal */ IntegersAspect::~IntegersAspect() = default; @@ -2346,7 +2346,7 @@ TextDisplay::TextDisplay(const QString &message, InfoLabel::InfoType type) } /*! - \reimp + \internal */ TextDisplay::~TextDisplay() = default; @@ -2412,7 +2412,7 @@ AspectContainer::AspectContainer(QObject *parent) {} /*! - \reimp + \internal */ AspectContainer::~AspectContainer() { -- cgit v1.2.3 From 4ecb016196b80b4cd66759912e20ff1da3d57148 Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Thu, 25 May 2023 11:11:54 +0200 Subject: Doc: Add missing \a commands to MacroExpander docs Change-Id: I153e4a4e7c687d6f524bbbff42c758282bb5deaf Reviewed-by: Eike Ziller Reviewed-by: --- src/libs/utils/macroexpander.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/macroexpander.cpp b/src/libs/utils/macroexpander.cpp index 5d3bba0229..4dc18b23f6 100644 --- a/src/libs/utils/macroexpander.cpp +++ b/src/libs/utils/macroexpander.cpp @@ -247,7 +247,6 @@ QString MacroExpander::value(const QByteArray &variable, bool *found) const * See the MacroExpander overview documentation for other ways to expand variables. * * \sa MacroExpander - * \sa macroExpander() */ QString MacroExpander::expand(const QString &stringWithVariables) const { @@ -323,10 +322,11 @@ static QByteArray fullPrefix(const QByteArray &prefix) * Makes the given string-valued \a prefix known to the variable manager, * together with a localized \a description. * - * The \a value PrefixFunction will be called and gets the full variable name - * with the prefix stripped as input. + * The \a value \c PrefixFunction will be called and gets the full variable name + * with the prefix stripped as input. It is displayed to users if \a visible is + * \c true. * - * \sa registerVariables(), registerIntVariable(), registerFileVariables() + * \sa registerVariable(), registerIntVariable(), registerFileVariables() */ void MacroExpander::registerPrefix(const QByteArray &prefix, const QString &description, const MacroExpander::PrefixFunction &value, bool visible) @@ -341,6 +341,9 @@ void MacroExpander::registerPrefix(const QByteArray &prefix, const QString &desc * Makes the given string-valued \a variable known to the variable manager, * together with a localized \a description. * + * The \a value \c StringFunction is called to retrieve the current value of the + * variable. It is displayed to users if \a visibleInChooser is \c true. + * * \sa registerFileVariables(), registerIntVariable(), registerPrefix() */ void MacroExpander::registerVariable(const QByteArray &variable, @@ -355,6 +358,9 @@ void MacroExpander::registerVariable(const QByteArray &variable, * Makes the given integral-valued \a variable known to the variable manager, * together with a localized \a description. * + * The \a value \c IntFunction is called to retrieve the current value of the + * variable. + * * \sa registerVariable(), registerFileVariables(), registerPrefix() */ void MacroExpander::registerIntVariable(const QByteArray &variable, @@ -373,6 +379,10 @@ void MacroExpander::registerIntVariable(const QByteArray &variable, * variables such as \c{CurrentDocument:FilePath} with description * "Current Document: Full path including file name." * + * Takes a function that returns a FilePath as a \a base. + * + * The variable is displayed to users if \a visibleInChooser is \c true. + * * \sa registerVariable(), registerIntVariable(), registerPrefix() */ void MacroExpander::registerFileVariables(const QByteArray &prefix, -- cgit v1.2.3 From c04a4a1ae04d70ee7ca3dfe1d514f6ac37098c8f Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Fri, 26 May 2023 17:00:14 +0200 Subject: Doc: Fix qdoc warnings - Mark undocumented but existing functions with \c (instead of \l or \sa) - Remove reference to functions I could no longer find - Fix other broken links - Fix reference to an image whose file format changed to WEBP - Use {} instead of "" to mark alt text for images - Add missing \a commands - Add class name to a function name so that it can be found Change-Id: I10655bb0356c7417ab0e14a3ce620930f4ee8349 Reviewed-by: Reviewed-by: Eike Ziller --- src/libs/extensionsystem/iplugin.cpp | 7 +++---- src/libs/utils/progressindicator.cpp | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) (limited to 'src/libs') diff --git a/src/libs/extensionsystem/iplugin.cpp b/src/libs/extensionsystem/iplugin.cpp index 6661a46f61..2af4925573 100644 --- a/src/libs/extensionsystem/iplugin.cpp +++ b/src/libs/extensionsystem/iplugin.cpp @@ -248,7 +248,8 @@ void IPlugin::tryCreateObjects() } /*! - Registers a function object that creates a test object. + Registers a function object that creates a test object with the owner + \a creator. The created objects are meant to be passed on to \l QTest::qExec(). @@ -264,9 +265,7 @@ void IPlugin::addTestCreator(const TestCreator &creator) } /*! - \deprecated [10.0] Use addTest() instead - - \sa addTest() + \deprecated [10.0] Use \c addTest() instead. */ QVector IPlugin::createTestObjects() const { diff --git a/src/libs/utils/progressindicator.cpp b/src/libs/utils/progressindicator.cpp index 6e129cd58e..d1ce125dbe 100644 --- a/src/libs/utils/progressindicator.cpp +++ b/src/libs/utils/progressindicator.cpp @@ -200,7 +200,7 @@ ProgressIndicator::ProgressIndicator(ProgressIndicatorSize size, QWidget *parent /*! Changes the size of the progress indicator to \a size. - \sa indicatorSize + \sa ProgressIndicatorPainter::indicatorSize() */ void ProgressIndicator::setIndicatorSize(ProgressIndicatorSize size) { @@ -211,7 +211,7 @@ void ProgressIndicator::setIndicatorSize(ProgressIndicatorSize size) /*! Returns the size of the indicator in device independent pixels. - \sa indicatorSize + \sa ProgressIndicatorPainter::indicatorSize() */ QSize ProgressIndicator::sizeHint() const { -- cgit v1.2.3 From 6286ae98531f93ed3b49f7e279c4afee79ab881b Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 30 May 2023 10:09:50 +0200 Subject: Utils: Use the path from the current object ... in Environment::searchInPath(). Amends 6ab66690. Change-Id: I04984c6a84c4448a6cd6d4d2677c84ed54376fee Reviewed-by: Orgad Shaneh --- src/libs/utils/environment.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/environment.cpp b/src/libs/utils/environment.cpp index 653525e1c3..b9af2dea92 100644 --- a/src/libs/utils/environment.cpp +++ b/src/libs/utils/environment.cpp @@ -223,7 +223,8 @@ FilePath Environment::searchInPath(const QString &executable, const FilePathPredicate &filter) const { const FilePath exec = FilePath::fromUserInput(expandVariables(executable)); - return exec.searchInPath(additionalDirs, {}, filter, FilePath::WithAnySuffix); + const FilePaths dirs = path() + additionalDirs; + return exec.searchInDirectories(dirs, filter, FilePath::WithAnySuffix); } FilePaths Environment::path() const -- cgit v1.2.3 From 05b922ca945581b05201b064636e3da20fb52757 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Thu, 25 May 2023 17:32:36 +0200 Subject: TaskTree: Add documentation for group handlers Document onGroupSetup, onGroupDone and onGroupError methods, TaskItem::Group{Start,End}Handler and TaskAction enum. Change-Id: I7516b867a2e3ce33b8f15a18f85d1e61d673d65e Reviewed-by: Qt CI Bot Reviewed-by: Leena Miettinen Reviewed-by: Marcus Tillmanns --- src/libs/solutions/tasking/tasktree.cpp | 122 ++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index 3d7f63216c..a71afe6085 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -40,11 +40,133 @@ private: Guard &m_guard; }; +/*! + \class Tasking::TaskItem + \inheaderfile solutions/tasking/tasktree.h + \inmodule QtCreator + \ingroup mainclasses + \brief The TaskItem class represents the basic element for composing nested tree structures. +*/ + +/*! + \enum Tasking::TaskAction + + This enum is optionally returned from the group's or task's setup handler function. + It instructs the running task tree on how to proceed after the setup handler's execution + finished. + \value Continue + Default. The group's or task's execution continues nomally. + When a group's or task's setup handler returns void, it's assumed that + it returned Continue. + \value StopWithDone + The group's or task's execution stops immediately with success. + When returned from the group's setup handler, all child tasks are skipped, + and the group's onGroupDone handler is invoked (if provided). + When returned from the task's setup handler, the task isn't started, + its done handler isn't invoked, and the task reports success to its parent. + \value StopWithError + The group's or task's execution stops immediately with an error. + When returned from the group's setup handler, all child tasks are skipped, + and the group's onGroupError handler is invoked (if provided). + When returned from the task's setup handler, the task isn't started, + its error handler isn't invoked, and the task reports an error to its parent. +*/ + +/*! + \typealias TaskItem::GroupSetupHandler + + Type alias for \c std::function. + + The GroupSetupHandler is used when constructing the onGroupSetup element. + Any function with the above signature, when passed as a group setup handler, + will be called by the running task tree when the group executions starts. + + The return value of the handler instructs the running group on how to proceed + after the handler's invocation is finished. The default return value of TaskAction::Continue + instructs the group to continue running, i.e. to start executing its child tasks. + The return value of TaskAction::StopWithDone or TaskAction::StopWithError + instructs the group to skip the child tasks' execution and finish immediately with + success or an error, respectively. + + When the return type is either TaskAction::StopWithDone + of TaskAction::StopWithError, the group's done or error handler (if provided) + is called synchronously immediately afterwards. + + \note Even if the group setup handler returns StopWithDone or StopWithError, + one of the group's done or error handlers is invoked. This behavior differs + from that of task handlers and might change in the future. + + The onGroupSetup accepts also functions in the shortened form of \c std::function, + i.e. the return value is void. In this case it's assumed that the return value + is TaskAction::Continue by default. + + \sa onGroupSetup +*/ + +/*! + \typealias TaskItem::GroupEndHandler + + Type alias for \c std::function\. + + The GroupEndHandler is used when constructing the onGroupDone and onGroupError elements. + Any function with the above signature, when passed as a group done or error handler, + will be called by the running task tree when the group ends with success or an error, + respectively. + + \sa onGroupDone, onGroupError +*/ + +/*! + \fn template TaskItem onGroupSetup(SetupHandler &&handler) + + Constructs a group's element holding the group setup handler. + The \a handler is invoked whenever the group starts. + + The passed \a handler is either of \c std::function or \c std::function + type. For more information on possible argument type, refer to \l {TaskItem::GroupSetupHandler}. + + When the \a handler is invoked, none of the group's child tasks are running yet. + + If a group contains the Storage elements, the \a handler is invoked + after the storages are constructed, so that the \a handler may already + perform some initial modifications to the active storages. + + \sa TaskItem::GroupSetupHandler, onGroupDone, onGroupError +*/ + +/*! + Constructs a group's element holding the group done handler. + The \a handler is invoked whenever the group finishes with success. + Depending on the group's workflow policy, this handler may also be called + when the running group is stopped (e.g. when optional element was used). + + When the \a handler is invoked, all of the group's child tasks are already finished. + + If a group contains the Storage elements, the \a handler is invoked + before the storages are destructed, so that the \a handler may still + perform a last read of the active storages' data. + + \sa TaskItem::GroupEndHandler, onGroupSetup, onGroupError +*/ TaskItem onGroupDone(const TaskItem::GroupEndHandler &handler) { return Group::onGroupDone(handler); } +/*! + Constructs a group's element holding the group error handler. + The \a handler is invoked whenever the group finishes with an error. + Depending on the group's workflow policy, this handler may also be called + when the running group is stopped (e.g. when stopOnError element was used). + + When the \a handler is invoked, all of the group's child tasks are already finished. + + If a group contains the Storage elements, the \a handler is invoked + before the storages are destructed, so that the \a handler may still + perform a last read of the active storages' data. + + \sa TaskItem::GroupEndHandler, onGroupSetup, onGroupDone +*/ TaskItem onGroupError(const TaskItem::GroupEndHandler &handler) { return Group::onGroupError(handler); -- cgit v1.2.3 From 78f2dda7ef7bfe196dad3923cb06539c7bf0f884 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Fri, 26 May 2023 18:30:18 +0200 Subject: TaskTree: Add docs for execution mode Transform it form TaskTree's description into description of sequential and parallel global variables, and into docs for parallelLimit() global function. Change-Id: I4aa2bac2f47778cde039cee77052359264224f93 Reviewed-by: Leena Miettinen --- src/libs/solutions/tasking/tasktree.cpp | 108 +++++++++++++++++++++----------- 1 file changed, 72 insertions(+), 36 deletions(-) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index a71afe6085..071190dd9e 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -48,6 +48,32 @@ private: \brief The TaskItem class represents the basic element for composing nested tree structures. */ +/*! + \variable sequential + A convenient global group's element describing the sequential execution mode. + + This is the default execution mode of the Group element. + + When a Group has no execution mode, it runs in the sequential mode. + All the direct child tasks of a group are started in a chain, so that when one task finishes, + the next one starts. This enables you to pass the results from the previous task + as input to the next task before it starts. This mode guarantees that the next task + is started only after the previous task finishes. + + \sa parallel, parallelLimit +*/ + +/*! + \variable parallel + A convenient global group's element describing the parallel execution mode. + + All the direct child tasks of a group are started after the group is started, + without waiting for the previous child tasks to finish. + In this mode, all child tasks run simultaneously. + + \sa sequential, parallelLimit +*/ + /*! \enum Tasking::TaskAction @@ -172,6 +198,47 @@ TaskItem onGroupError(const TaskItem::GroupEndHandler &handler) return Group::onGroupError(handler); } +/*! + Constructs a group's element describing the \l{Execution Mode}{execution mode}. + + The execution mode element in a Group specifies how the direct child tasks of + the Group are started. + + For convenience, when appropriate, the \l sequential or \l parallel global elements + may be used instead. + + The \a limit defines the maximum number of direct child tasks running in parallel: + + \list + \li When \a limit equals to 0, there is no limit, and all direct child tasks are started + together, in the oder in which they appear in a group. This means the fully parallel + execution, and the \l parallel element may be used instead. + + \li When \a limit equals to 1, it means that only one child task may run at the time. + This means the sequential execution, and the \l sequential element may be used instead. + In this case child tasks run in chain, so the next child task starts after + the previous child task has finished. + + \li When other positive number is passed as \a limit, the group's child tasks run + in parallel, but with a limited number of tasks running simultanously. + The \e limit defines the maximum number of tasks running in parallel in a group. + When the group is started, the first batch of tasks is started + (the number of tasks in a batch equals to the passed \a limit, at most), + while the others are kept waiting. When any running task finishes, + the group starts the next remaining one, so that the \e limit of simultaneously + running tasks inside a group isn't exceeded. This repeats on every child task's + finish until all child tasks are started. This enables you to limit the maximum + number of tasks that run simultaneously, for example if running too many processes might + block the machine for a long time. + \endlist + + In all execution modes, a group starts tasks in the oder in which they appear. + + If a child of a group is also a group, the child group runs its tasks according + to its own execution mode. + + \sa sequential, parallel +*/ TaskItem parallelLimit(int limit) { return Group::parallelLimit(qMax(limit, 0)); @@ -1252,45 +1319,14 @@ void TaskNode::invokeEndHandler(bool success) \section2 Execution Mode The execution mode element in a Group specifies how the direct child tasks of - the Group are started. - - \table - \header - \li Execution Mode - \li Description - \row - \li sequential - \li Default. When a Group has no execution mode, it runs in the - sequential mode. All the direct child tasks of a group are started - in a chain, so that when one task finishes, the next one starts. - This enables you to pass the results from the previous task - as input to the next task before it starts. This mode guarantees - that the next task is started only after the previous task finishes. - \row - \li parallel - \li All the direct child tasks of a group are started after the group is - started, without waiting for the previous tasks to finish. In this - mode, all tasks run simultaneously. - \row - \li parallelLimit(int limit) - \li In this mode, a limited number of direct child tasks run simultaneously. - The \e limit defines the maximum number of tasks running in parallel - in a group. When the group is started, the first batch tasks is - started (the number of tasks in batch equals to passed limit, at most), - while the others are kept waiting. When a running task finishes, - the group starts the next remaining one, so that the \e limit - of simultaneously running tasks inside a group isn't exceeded. - This repeats on every child task's finish until all child tasks are started. - This enables you to limit the maximum number of tasks that - run simultaneously, for example if running too many processes might - block the machine for a long time. The value 1 means \e sequential - execution. The value 0 means unlimited and equals \e parallel. - \endtable + the Group are started. The most common execution modes are \l sequential and + \l parallel. It's also possible to specify the limit of tasks running + in parallel by using the parallelLimit function. In all execution modes, a group starts tasks in the oder in which they appear. - If a child of a group is also a group (in a nested tree), the child group - runs its tasks according to its own execution mode. + If a child of a group is also a group, the child group runs its tasks + according to its own execution mode. \section2 Workflow Policy -- cgit v1.2.3 From d64954b02915e9a38d800bcc7d90b92b75de9081 Mon Sep 17 00:00:00 2001 From: Cristian Adam Date: Thu, 25 May 2023 17:17:52 +0200 Subject: Utils: Fix QTC_STATIC_BUILD build Change-Id: I14ac8c99708aba548d1054b37e5a445f6ac1a2b7 Reviewed-by: Reviewed-by: Eike Ziller --- src/libs/utils/layoutbuilder.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/layoutbuilder.h b/src/libs/utils/layoutbuilder.h index d716ad034c..1f774ba146 100644 --- a/src/libs/utils/layoutbuilder.h +++ b/src/libs/utils/layoutbuilder.h @@ -11,6 +11,8 @@ #if defined(UTILS_LIBRARY) # define QTCREATOR_UTILS_EXPORT Q_DECL_EXPORT +#elif defined(UTILS_STATIC_LIBRARY) +# define QTCREATOR_UTILS_EXPORT #else # define QTCREATOR_UTILS_EXPORT Q_DECL_IMPORT #endif -- cgit v1.2.3 From 047814c6daea2d5b8afa833e5bdb7e4a32279bf6 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Tue, 30 May 2023 14:08:45 +0200 Subject: Utils: fix likelyContainsLink for markdown This allows us to also port the link in the resource tooltip to markdown. Change-Id: Iec0e19ff68db76290139e457694485222f0a38f3 Reviewed-by: Eike Ziller --- src/libs/utils/tooltip/tips.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/tooltip/tips.cpp b/src/libs/utils/tooltip/tips.cpp index ea20c735d8..180b8f960f 100644 --- a/src/libs/utils/tooltip/tips.cpp +++ b/src/libs/utils/tooltip/tips.cpp @@ -133,9 +133,13 @@ TextTip::TextTip(QWidget *parent) : TipLabel(parent) setWindowOpacity(style()->styleHint(QStyle::SH_ToolTipLabel_Opacity, nullptr, this) / 255.0); } -static bool likelyContainsLink(const QString &s) +static bool likelyContainsLink(const QString &s, const Qt::TextFormat &format) { - return s.contains(QLatin1String("href"), Qt::CaseInsensitive); + if (s.contains(QLatin1String("href"), Qt::CaseInsensitive)) + return true; + if (format == Qt::MarkdownText) + return s.contains("]("); + return false; } void TextTip::setContent(const QVariant &content) @@ -148,13 +152,13 @@ void TextTip::setContent(const QVariant &content) m_format = item.second; } - bool containsLink = likelyContainsLink(m_text); + bool containsLink = likelyContainsLink(m_text, m_format); setOpenExternalLinks(containsLink); } bool TextTip::isInteractive() const { - return likelyContainsLink(m_text); + return likelyContainsLink(m_text, m_format); } void TextTip::configure(const QPoint &pos) -- cgit v1.2.3 From cbca40401b62556753d12a354b80748c5d3d72cd Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Sun, 28 May 2023 12:42:05 +0200 Subject: TaskTree: Fix calling the proper group handler on stop Add tests for it. Change-Id: Ibb04b21c217196c9bbf6761851f4e1a300139a8c Reviewed-by: Marcus Tillmanns Reviewed-by: Qt CI Bot --- src/libs/solutions/tasking/tasktree.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index 071190dd9e..a3fb9347bb 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -893,6 +893,7 @@ void TaskNode::stop() if (!m_task) { m_container.stop(); + m_container.m_runtimeData->updateSuccessBit(false); m_container.invokeEndHandler(); return; } -- cgit v1.2.3 From ffdb0c7dcc958aaba17abf127923e4dfe02604d3 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Fri, 26 May 2023 19:44:17 +0200 Subject: TaskTree: Some corrections to the copy example Use QString instead of FilePath. Rename the diffRecipe into copyRecipe. Add some code that constructs the tree, connects to its done signal and starts the tree. Change-Id: I40e1c4784c736347682071b3e3e99db599ce102a Reviewed-by: hjk Reviewed-by: Qt CI Bot --- src/libs/solutions/tasking/tasktree.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index a3fb9347bb..540342ac8f 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -1412,10 +1412,10 @@ void TaskNode::invokeEndHandler(bool success) it from a source and writing it to a destination might look as follows: \code - static QByteArray load(const FilePath &fileName) { ... } - static void save(const FilePath &fileName, const QByteArray &array) { ... } + static QByteArray load(const QString &fileName) { ... } + static void save(const QString &fileName, const QByteArray &array) { ... } - static TaskItem diffRecipe(const FilePath &source, const FilePath &destination) + static TaskItem copyRecipe(const QString &source, const QString &destination) { struct CopyStorage { // [1] custom inter-task struct QByteArray content; // [2] custom inter-task data @@ -1449,6 +1449,13 @@ void TaskNode::invokeEndHandler(bool success) }; return root; } + + const QString source = ...; + const QString destination = ...; + TaskTree taskTree(copyRecipe(source, destination)); + connect(&taskTree, &TaskTree::done, + &taskTree, [] { qDebug() << "The copying finished successfully."; }); + tasktree.start(); \endcode In the example above, the inter-task data consists of a QByteArray content -- cgit v1.2.3 From 4e01ca18d10b839636c1d60c0520816526d92a7f Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Sun, 28 May 2023 17:20:55 +0200 Subject: TaskTree: Rename optional into finishAllAndDone Rationale: 1. More descriptive. 2. More consistent with a planned new finishAllAndError policy. 3. Limits the possibilities of making a conflict with std::optional. Change-Id: I5155630188e4b699e6c18b13a101e0e2d4fe98f2 Reviewed-by: Qt CI Bot Reviewed-by: Reviewed-by: hjk --- src/libs/solutions/tasking/tasktree.cpp | 8 ++++---- src/libs/solutions/tasking/tasktree.h | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index 540342ac8f..054647752a 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -164,7 +164,7 @@ private: Constructs a group's element holding the group done handler. The \a handler is invoked whenever the group finishes with success. Depending on the group's workflow policy, this handler may also be called - when the running group is stopped (e.g. when optional element was used). + when the running group is stopped (e.g. when finishAllAndDone element was used). When the \a handler is invoked, all of the group's child tasks are already finished. @@ -256,7 +256,7 @@ const TaskItem continueOnError = workflowPolicy(WorkflowPolicy::ContinueOnError) const TaskItem stopOnDone = workflowPolicy(WorkflowPolicy::StopOnDone); const TaskItem continueOnDone = workflowPolicy(WorkflowPolicy::ContinueOnDone); const TaskItem stopOnFinished = workflowPolicy(WorkflowPolicy::StopOnFinished); -const TaskItem optional = workflowPolicy(WorkflowPolicy::Optional); +const TaskItem finishAllAndDone = workflowPolicy(WorkflowPolicy::FinishAllAndDone); static TaskAction toTaskAction(bool success) { @@ -720,7 +720,7 @@ TaskContainer::RuntimeData::~RuntimeData() bool TaskContainer::RuntimeData::updateSuccessBit(bool success) { - if (m_constData.m_workflowPolicy == WorkflowPolicy::Optional) + if (m_constData.m_workflowPolicy == WorkflowPolicy::FinishAllAndDone) return m_successBit; if (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnFinished) { m_successBit = success; @@ -1394,7 +1394,7 @@ void TaskNode::invokeEndHandler(bool success) In sequential mode, only the first task is started, and when finished, the group finishes too, so the other tasks are ignored. \row - \li optional + \li finishAllAndDone \li The group executes all tasks and ignores their return state. When all tasks finish, the group finishes with success. \endtable diff --git a/src/libs/solutions/tasking/tasktree.h b/src/libs/solutions/tasking/tasktree.h index 1754e09263..872bce2ed8 100644 --- a/src/libs/solutions/tasking/tasktree.h +++ b/src/libs/solutions/tasking/tasktree.h @@ -99,15 +99,15 @@ private: // b) On first done - continue executing all children and report done afterwards. // 3. Stops on first finished child. In sequential mode it will never run other children then the first one. // Useful only in parallel mode. -// 4. Always run all children, ignore their result and report done afterwards. +// 4. Always run all children, let them finish, ignore their results and report done afterwards. enum class WorkflowPolicy { - StopOnError, // 1a - Reports error on first child error, otherwise done (if all children were done). - ContinueOnError, // 1b - The same, but children execution continues. Reports done when no children. - StopOnDone, // 2a - Reports done on first child done, otherwise error (if all children were error). - ContinueOnDone, // 2b - The same, but children execution continues. Reports error when no children. - StopOnFinished, // 3 - Stops on first finished child and report its result. - Optional // 4 - Reports done after all children finished. + StopOnError, // 1a - Reports error on first child error, otherwise done (if all children were done). + ContinueOnError, // 1b - The same, but children execution continues. Reports done when no children. + StopOnDone, // 2a - Reports done on first child done, otherwise error (if all children were error). + ContinueOnDone, // 2b - The same, but children execution continues. Reports error when no children. + StopOnFinished, // 3 - Stops on first finished child and report its result. + FinishAllAndDone // 4 - Reports done after all children finished. }; enum class TaskAction @@ -248,7 +248,7 @@ TASKING_EXPORT extern const TaskItem continueOnError; TASKING_EXPORT extern const TaskItem stopOnDone; TASKING_EXPORT extern const TaskItem continueOnDone; TASKING_EXPORT extern const TaskItem stopOnFinished; -TASKING_EXPORT extern const TaskItem optional; +TASKING_EXPORT extern const TaskItem finishAllAndDone; class TASKING_EXPORT Storage : public TaskItem { -- cgit v1.2.3 From 361eb17fbf576c6f00e9bb9dbf0a129a3b725922 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Fri, 26 May 2023 15:13:41 +0200 Subject: FancyLineEdit: Fix placeholder text color Amends 3dcdbe9069c452e2f0eacb925aa7412e63dc4762 Using the theme's PalettePlaceholderText only works for themes that explicitly set it. Grab it from the application palette instead, which is correct for all themes. Observable by typing something into Locator and removing the typed text again. Change-Id: Iee7f900275ab7bcb37d87a2f7acddfee55fe9f79 Reviewed-by: Qt CI Bot Reviewed-by: Christian Stenger Reviewed-by: --- src/libs/utils/fancylineedit.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/fancylineedit.cpp b/src/libs/utils/fancylineedit.cpp index ae931ec1ca..08b480c88c 100644 --- a/src/libs/utils/fancylineedit.cpp +++ b/src/libs/utils/fancylineedit.cpp @@ -11,6 +11,7 @@ #include "utilsicons.h" #include "utilstr.h" +#include #include #include #include @@ -126,7 +127,7 @@ FancyLineEditPrivate::FancyLineEditPrivate(FancyLineEdit *parent) : m_completionShortcut(completionShortcut()->key(), parent), m_okTextColor(creatorTheme()->color(Theme::TextColorNormal)), m_errorTextColor(creatorTheme()->color(Theme::TextColorError)), - m_placeholderTextColor(creatorTheme()->color(Theme::PalettePlaceholderText)) + m_placeholderTextColor(QApplication::palette().color(QPalette::PlaceholderText)) { m_completionShortcut.setContext(Qt::WidgetShortcut); -- cgit v1.2.3 From e38b2cab44c5817ecde5242dd6d8ee1971266c3b Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Tue, 30 May 2023 16:49:00 +0200 Subject: DeviceShell: Refrain from using potentially invalidated iterators The stored container iterator, after container is modified, may be already invalidated. Avoid storing the iterators and do the search again after potential container modification. Use QHash instead of QMap for faster insertions / lookups. Amends 0135c47849bb1962fd379de210a01a918c6e8b4e Change-Id: I0a4641d3b410836a5b3b9be252059e4e37fa94e3 Reviewed-by: Marcus Tillmanns --- src/libs/utils/deviceshell.cpp | 3 ++- src/libs/utils/deviceshell.h | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/deviceshell.cpp b/src/libs/utils/deviceshell.cpp index ae983b6f48..f96c5da8f2 100644 --- a/src/libs/utils/deviceshell.cpp +++ b/src/libs/utils/deviceshell.cpp @@ -104,7 +104,7 @@ RunResult DeviceShell::run(const CommandLine &cmd, const QByteArray &stdInData) QWaitCondition waiter; const int id = ++m_currentId; - const auto it = m_commandOutput.insert(id, CommandRun{{-1, {}, {}}, &waiter}); + m_commandOutput.insert(id, CommandRun{{-1, {}, {}}, &waiter}); QMetaObject::invokeMethod(m_shellProcess.get(), [this, id, cmd, stdInData] { const QString command = QString("%1 \"%2\" %3\n").arg(id) @@ -115,6 +115,7 @@ RunResult DeviceShell::run(const CommandLine &cmd, const QByteArray &stdInData) waiter.wait(&m_commandMutex); + const auto it = m_commandOutput.constFind(id); const RunResult result = *it; m_commandOutput.erase(it); diff --git a/src/libs/utils/deviceshell.h b/src/libs/utils/deviceshell.h index 052aac1838..e5bc4ad7af 100644 --- a/src/libs/utils/deviceshell.h +++ b/src/libs/utils/deviceshell.h @@ -7,7 +7,7 @@ #include "fileutils.h" -#include +#include #include #include #include @@ -78,8 +78,7 @@ private: int m_currentId{0}; QMutex m_commandMutex; - // QMap is used here to preserve iterators - QMap m_commandOutput; + QHash m_commandOutput; QByteArray m_commandBuffer; State m_shellScriptState = State::Unknown; -- cgit v1.2.3 From 1a9025cdd52628c13fa7c325bf34475e3e64a3f7 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Mon, 29 May 2023 16:57:08 +0200 Subject: TaskTree: Add docs for Workflow Policy Transform it form TaskTree's description into WorkflowPolicy enum docs. Document global workflow policy elements and global workflowPolicy() function. Change-Id: I4af3f7ffa703bbb1a9370e2fd1f9242a68131295 Reviewed-by: Reviewed-by: Qt CI Bot Reviewed-by: Leena Miettinen --- src/libs/solutions/tasking/tasktree.cpp | 227 ++++++++++++++++++++++---------- 1 file changed, 157 insertions(+), 70 deletions(-) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index 054647752a..62243f373a 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -48,6 +48,114 @@ private: \brief The TaskItem class represents the basic element for composing nested tree structures. */ +/*! + \enum Tasking::WorkflowPolicy + + This enum describes the possible behavior of the Group element when any group's child task + finishes its execution. It's also used when the running Group is stopped. + + \value StopOnError + Default. Corresponds to the stopOnError global element. + If any child task finishes with an error, the group stops and finishes with an error. + If all child tasks finished with success, the group finishes with success. + If a group is empty, it finishes with success. + \value ContinueOnError + Corresponds to the continueOnError global element. + Similar to stopOnError, but in case any child finishes with an error, + the execution continues until all tasks finish, and the group reports an error + afterwards, even when some other tasks in the group finished with success. + If all child tasks finish successfully, the group finishes with success. + If a group is empty, it finishes with success. + \value StopOnDone + Corresponds to the stopOnDone global element. + If any child task finishes with success, the group stops and finishes with success. + If all child tasks finished with an error, the group finishes with an error. + If a group is empty, it finishes with an error. + \value ContinueOnDone + Corresponds to the continueOnDone global element. + Similar to stopOnDone, but in case any child finishes successfully, + the execution continues until all tasks finish, and the group reports success + afterwards, even when some other tasks in the group finished with an error. + If all child tasks finish with an error, the group finishes with an error. + If a group is empty, it finishes with an error. + \value StopOnFinished + Corresponds to the stopOnFinished global element. + The group starts as many tasks as it can. When any task finishes, + the group stops and reports the task's result. + Useful only in parallel mode. + In sequential mode, only the first task is started, and when finished, + the group finishes too, so the other tasks are always skipped. + If a group is empty, it finishes with an error. + \value FinishAllAndDone + Corresponds to the finishAllAndDone global element. + The group executes all tasks and ignores their return results. When all + tasks finished, the group finishes with success. + If a group is empty, it finishes with success. + \value FinishAllAndError + Corresponds to the finishAllAndError global element. + The group executes all tasks and ignores their return results. When all + tasks finished, the group finishes with an error. + If a group is empty, it finishes with an error. + + Whenever a child task's result causes the Group to stop, + i.e. in case of StopOnError, StopOnDone, or StopOnFinished policies, + the Group stops the other running child tasks (if any - for example in parallel mode), + and skips executing tasks it has not started yet (for example, in the sequential mode - + those, that are placed after the failed task). Both stopping and skipping child tasks + may happen when parallelLimit is used. + + The table below summarizes the differences between various workflow policies: + + \table + \header + \li \l WorkflowPolicy + \li Executes all child tasks + \li Result + \li Result when the group is empty + \row + \li StopOnError + \li Stops when any child task finished with an error and reports an error + \li An error when at least one child task failed, success otherwise + \li Success + \row + \li ContinueOnError + \li Yes + \li An error when at least one child task failed, success otherwise + \li Success + \row + \li StopOnDone + \li Stops when any child task finished with success and reports success + \li Success when at least one child task succeeded, an error otherwise + \li An error + \row + \li ContinueOnDone + \li Yes + \li Success when at least one child task succeeded, an error otherwise + \li An error + \row + \li StopOnFinished + \li Stops when any child task finished and reports child task's result + \li Success or an error, depending on the finished child task's result + \li An error + \row + \li FinishAllAndDone + \li Yes + \li Success + \li Success + \row + \li FinishAllAndError + \li Yes + \li An error + \li An error + \endtable + + If a child of a group is also a group, the child group runs its tasks according to its own + workflow policy. When a parent group stops the running child group because + of parent group's workflow policy, i.e. when the StopOnError, StopOnDone, or StopOnFinished + policy was used for the parent, the child group's result is reported according to the + \b Result column and to the \b {child group's workflow policy} row in the table above. +*/ + /*! \variable sequential A convenient global group's element describing the sequential execution mode. @@ -74,6 +182,43 @@ private: \sa sequential, parallelLimit */ +/*! + \variable stopOnError + A convenient global group's element describing the StopOnError workflow policy. + + This is the default workflow policy of the Group element. +*/ + +/*! + \variable continueOnError + A convenient global group's element describing the ContinueOnError workflow policy. +*/ + +/*! + \variable stopOnDone + A convenient global group's element describing the StopOnDone workflow policy. +*/ + +/*! + \variable continueOnDone + A convenient global group's element describing the ContinueOnDone workflow policy. +*/ + +/*! + \variable stopOnFinished + A convenient global group's element describing the StopOnFinished workflow policy. +*/ + +/*! + \variable finishAllAndDone + A convenient global group's element describing the FinishAllAndDone workflow policy. +*/ + +/*! + \variable finishAllAndError + A convenient global group's element describing the FinishAllAndError workflow policy. +*/ + /*! \enum Tasking::TaskAction @@ -244,6 +389,14 @@ TaskItem parallelLimit(int limit) return Group::parallelLimit(qMax(limit, 0)); } +/*! + Constructs a group's workflow policy element for a given \a policy. + + For convenience, global elements may be used instead. + + \sa stopOnError, continueOnError, stopOnDone, continueOnDone, stopOnFinished, finishAllAndDone, + finishAllAndError, WorkflowPolicy +*/ TaskItem workflowPolicy(WorkflowPolicy policy) { return Group::workflowPolicy(policy); @@ -1332,77 +1485,11 @@ void TaskNode::invokeEndHandler(bool success) \section2 Workflow Policy The workflow policy element in a Group specifies how the group should behave - when any of its \e direct child's tasks finish: + when any of its \e direct child's tasks finish. For a detailed description of possible + policies, refer to WorkflowPolicy. - \table - \header - \li Workflow Policy - \li Description - \row - \li stopOnError - \li Default. If a task finishes with an error, the group: - \list 1 - \li Stops the running tasks (if any - for example, in parallel - mode). - \li Skips executing tasks it has not started yet (for example, in the - sequential mode - those, that are placed after the failed task). - \li Immediately finishes with an error. - \endlist - If all child tasks finish successfully, the group finishes with success. - \row - \li continueOnError - \li Similar to stopOnError, but in case any child finishes with - an error, the execution continues until all tasks finish, - and the group reports an error afterwards, even when some other - tasks in group finished with success. - If a task finishes with an error, the group: - \list 1 - \li Continues executing the tasks that are running or have not - started yet. - \li Finishes with an error when all tasks finish. - \endlist - If all tasks finish successfully, the group finishes with success. - \row - \li stopOnDone - \li If a task finishes with success, the group: - \list 1 - \li Stops the running tasks (if any - for example, in parallel - mode). - \li Skips executing tasks it has not started yet (for example, in the - sequential mode - those, that are placed after the successfully finished task). - \li Immediately finishes with success. - \endlist - If all tasks finish with an error, the group finishes with an error. - \row - \li continueOnDone - \li Similar to stopOnDone, but in case any child finishes - successfully, the execution continues until all tasks finish, - and the group reports success afterwards, even when some other - tasks in group finished with an error. - If a task finishes with success, the group: - \list 1 - \li Continues executing the tasks that are running or have not - started yet. - \li Finishes with success when all tasks finish. - \endlist - If all tasks finish with an error, the group finishes with an error. - \row - \li stopOnFinished - \li The group starts as many tasks as it can. When a task finishes, - the group stops and reports the task's result. - Useful only in parallel mode. - In sequential mode, only the first task is started, and when finished, - the group finishes too, so the other tasks are ignored. - \row - \li finishAllAndDone - \li The group executes all tasks and ignores their return state. When all - tasks finish, the group finishes with success. - \endtable - - When a Group is empty, it finishes immediately with success, - regardless of its workflow policy. - If a child of a group is also a group, the child group - runs its tasks according to its own workflow policy. + If a child of a group is also a group, the child group runs its tasks + according to its own workflow policy. \section2 Storage -- cgit v1.2.3 From 44e9d56c0463e35bdfede1af4f9da9dd70e13820 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Tue, 30 May 2023 13:52:11 +0200 Subject: Utils: Fix macOS permissions parsing Change-Id: I5fdde04c197b5db323fc8630c4ee4b2c197d947a Reviewed-by: Marcus Tillmanns Reviewed-by: Qt CI Bot --- src/libs/utils/fileutils.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index a6cbebf368..2cecc2810b 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -629,12 +629,18 @@ FilePathInfo::FileFlags fileInfoFlagsfromStatMode(const QString &hexString, int FilePathInfo::FileFlags result; - if (mode & IRUSR) + if (mode & IRUSR) { result |= FilePathInfo::ReadOwnerPerm; - if (mode & IWUSR) + result |= FilePathInfo::ReadUserPerm; + } + if (mode & IWUSR) { result |= FilePathInfo::WriteOwnerPerm; - if (mode & IXUSR) + result |= FilePathInfo::WriteUserPerm; + } + if (mode & IXUSR) { result |= FilePathInfo::ExeOwnerPerm; + result |= FilePathInfo::ExeUserPerm; + } if (mode & IRGRP) result |= FilePathInfo::ReadGroupPerm; if (mode & IWGRP) -- cgit v1.2.3 From 3c08ebbe265fbe34656b7e641300b8daabf3cffb Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Mon, 29 May 2023 11:46:20 +0200 Subject: TaskTree: Make enums known to the Qt meta object system Change-Id: I72d8b74460febe1d7ad7d840e25cea02cd77b308 Reviewed-by: Marcus Tillmanns Reviewed-by: Reviewed-by: Qt CI Bot --- src/libs/solutions/tasking/tasktree.h | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.h b/src/libs/solutions/tasking/tasktree.h index 872bce2ed8..37403628a5 100644 --- a/src/libs/solutions/tasking/tasktree.h +++ b/src/libs/solutions/tasking/tasktree.h @@ -16,6 +16,8 @@ QT_END_NAMESPACE namespace Tasking { +Q_NAMESPACE + class ExecutionContextActivator; class TaskContainer; class TaskTreePrivate; @@ -109,6 +111,7 @@ enum class WorkflowPolicy { StopOnFinished, // 3 - Stops on first finished child and report its result. FinishAllAndDone // 4 - Reports done after all children finished. }; +Q_ENUM_NS(WorkflowPolicy); enum class TaskAction { @@ -116,6 +119,7 @@ enum class TaskAction StopWithDone, StopWithError }; +Q_ENUM_NS(TaskAction); class TASKING_EXPORT TaskItem { -- cgit v1.2.3 From 93759796984138e5fca5781e22381dcba6a51824 Mon Sep 17 00:00:00 2001 From: hjk Date: Thu, 1 Jun 2023 09:51:27 +0200 Subject: Utils: Make PathChooser remote support configurable in aspects ... and switch is _on_ by default. Change-Id: I82e66da477dae1ee955b81babc6230b67e530d45 Reviewed-by: Tim Jenssen --- src/libs/utils/aspects.cpp | 9 +++++++++ src/libs/utils/aspects.h | 1 + 2 files changed, 10 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index d3610b2c7d..bb637f31b0 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -670,6 +670,7 @@ public: // Used to block recursive editingFinished signals for example when return is pressed, and // the validation changes focus by opening a dialog bool m_blockAutoApply = false; + bool m_allowPathFromDevice = true; template void updateWidgetFromCheckStatus(StringAspect *aspect, Widget *w) { @@ -977,6 +978,13 @@ void StringAspect::setCommandVersionArguments(const QStringList &arguments) d->m_pathChooserDisplay->setCommandVersionArguments(arguments); } +void StringAspect::setAllowPathFromDevice(bool allowPathFromDevice) +{ + d->m_allowPathFromDevice = allowPathFromDevice; + if (d->m_pathChooserDisplay) + d->m_pathChooserDisplay->setAllowPathFromDevice(allowPathFromDevice); +} + /*! Sets \a elideMode as label elide mode. */ @@ -1122,6 +1130,7 @@ void StringAspect::addToLayout(LayoutItem &parent) d->m_pathChooserDisplay->setPromptDialogFilter(d->m_prompDialogFilter); d->m_pathChooserDisplay->setPromptDialogTitle(d->m_prompDialogTitle); d->m_pathChooserDisplay->setCommandVersionArguments(d->m_commandVersionArguments); + d->m_pathChooserDisplay->setAllowPathFromDevice(d->m_allowPathFromDevice); if (defaultValue() == value()) d->m_pathChooserDisplay->setDefaultValue(defaultValue()); else diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index 35e8a62b8f..db3c036cec 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -406,6 +406,7 @@ public: void setOpenTerminalHandler(const std::function &openTerminal); void setAutoApplyOnEditingFinished(bool applyOnEditingFinished); void setElideMode(Qt::TextElideMode elideMode); + void setAllowPathFromDevice(bool allowPathFromDevice); void validateInput(); -- cgit v1.2.3 From 5e9eadfc5816cc30ac221e533e416cc7ebc713af Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Sun, 28 May 2023 22:07:17 +0200 Subject: TaskTree: Introduce finishAllAndError workflow policy It's going to be used in timeout task. Change-Id: I2abd65b461cab445ada7a0ad5e1bbe07d1b6323b Reviewed-by: Qt CI Bot Reviewed-by: Marcus Tillmanns Reviewed-by: --- src/libs/solutions/tasking/tasktree.cpp | 8 ++++++-- src/libs/solutions/tasking/tasktree.h | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index 62243f373a..6f2bb7ad4f 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -404,12 +404,14 @@ TaskItem workflowPolicy(WorkflowPolicy policy) const TaskItem sequential = parallelLimit(1); const TaskItem parallel = parallelLimit(0); + const TaskItem stopOnError = workflowPolicy(WorkflowPolicy::StopOnError); const TaskItem continueOnError = workflowPolicy(WorkflowPolicy::ContinueOnError); const TaskItem stopOnDone = workflowPolicy(WorkflowPolicy::StopOnDone); const TaskItem continueOnDone = workflowPolicy(WorkflowPolicy::ContinueOnDone); const TaskItem stopOnFinished = workflowPolicy(WorkflowPolicy::StopOnFinished); const TaskItem finishAllAndDone = workflowPolicy(WorkflowPolicy::FinishAllAndDone); +const TaskItem finishAllAndError = workflowPolicy(WorkflowPolicy::FinishAllAndError); static TaskAction toTaskAction(bool success) { @@ -859,7 +861,8 @@ TaskContainer::RuntimeData::RuntimeData(const ConstData &constData) , m_storageIdList(createStorages(constData)) { m_successBit = m_constData.m_workflowPolicy != WorkflowPolicy::StopOnDone - && m_constData.m_workflowPolicy != WorkflowPolicy::ContinueOnDone; + && m_constData.m_workflowPolicy != WorkflowPolicy::ContinueOnDone + && m_constData.m_workflowPolicy != WorkflowPolicy::FinishAllAndError; } TaskContainer::RuntimeData::~RuntimeData() @@ -873,7 +876,8 @@ TaskContainer::RuntimeData::~RuntimeData() bool TaskContainer::RuntimeData::updateSuccessBit(bool success) { - if (m_constData.m_workflowPolicy == WorkflowPolicy::FinishAllAndDone) + if (m_constData.m_workflowPolicy == WorkflowPolicy::FinishAllAndDone + || m_constData.m_workflowPolicy == WorkflowPolicy::FinishAllAndError) return m_successBit; if (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnFinished) { m_successBit = success; diff --git a/src/libs/solutions/tasking/tasktree.h b/src/libs/solutions/tasking/tasktree.h index 37403628a5..c10cf495c3 100644 --- a/src/libs/solutions/tasking/tasktree.h +++ b/src/libs/solutions/tasking/tasktree.h @@ -102,6 +102,7 @@ private: // 3. Stops on first finished child. In sequential mode it will never run other children then the first one. // Useful only in parallel mode. // 4. Always run all children, let them finish, ignore their results and report done afterwards. +// 5. Always run all children, let them finish, ignore their results and report error afterwards. enum class WorkflowPolicy { StopOnError, // 1a - Reports error on first child error, otherwise done (if all children were done). @@ -109,7 +110,8 @@ enum class WorkflowPolicy { StopOnDone, // 2a - Reports done on first child done, otherwise error (if all children were error). ContinueOnDone, // 2b - The same, but children execution continues. Reports error when no children. StopOnFinished, // 3 - Stops on first finished child and report its result. - FinishAllAndDone // 4 - Reports done after all children finished. + FinishAllAndDone, // 4 - Reports done after all children finished. + FinishAllAndError // 5 - Reports error after all children finished. }; Q_ENUM_NS(WorkflowPolicy); @@ -247,12 +249,14 @@ TASKING_EXPORT TaskItem workflowPolicy(WorkflowPolicy policy); TASKING_EXPORT extern const TaskItem sequential; TASKING_EXPORT extern const TaskItem parallel; + TASKING_EXPORT extern const TaskItem stopOnError; TASKING_EXPORT extern const TaskItem continueOnError; TASKING_EXPORT extern const TaskItem stopOnDone; TASKING_EXPORT extern const TaskItem continueOnDone; TASKING_EXPORT extern const TaskItem stopOnFinished; TASKING_EXPORT extern const TaskItem finishAllAndDone; +TASKING_EXPORT extern const TaskItem finishAllAndError; class TASKING_EXPORT Storage : public TaskItem { -- cgit v1.2.3 From f6e7dbd4166401fa40c5bcab83893d375b42e6a9 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Sun, 28 May 2023 22:47:59 +0200 Subject: TaskTree: Introduce Timeout task By default, when finished, it returns success. In order to convert it into failing task, enclose it inside a Group with finishAllAndError. Reuse it in tasking tests. Task-number: QTCREATORBUG-28741 Change-Id: Ic81203203e0b139d4f9bfd553279ecb01cd303f4 Reviewed-by: Marcus Tillmanns Reviewed-by: Qt CI Bot --- src/libs/solutions/tasking/tasktree.cpp | 10 ++++++++++ src/libs/solutions/tasking/tasktree.h | 8 ++++++++ 2 files changed, 18 insertions(+) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index 6f2bb7ad4f..7fc5c66591 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -1848,4 +1848,14 @@ void TaskTreeTaskAdapter::start() task()->start(); } +TimeoutTaskAdapter::TimeoutTaskAdapter() +{ + *task() = std::chrono::milliseconds::zero(); +} + +void TimeoutTaskAdapter::start() +{ + QTimer::singleShot(*task(), this, [this] { emit done(true); }); +} + } // namespace Tasking diff --git a/src/libs/solutions/tasking/tasktree.h b/src/libs/solutions/tasking/tasktree.h index c10cf495c3..78593aa182 100644 --- a/src/libs/solutions/tasking/tasktree.h +++ b/src/libs/solutions/tasking/tasktree.h @@ -426,6 +426,13 @@ public: void start() final; }; +class TASKING_EXPORT TimeoutTaskAdapter : public TaskAdapter +{ +public: + TimeoutTaskAdapter(); + void start() final; +}; + } // namespace Tasking #define TASKING_DECLARE_TASK(CustomTaskName, TaskAdapterClass)\ @@ -438,3 +445,4 @@ using CustomTaskName = CustomTask>;\ } // namespace Tasking TASKING_DECLARE_TASK(TaskTreeTask, TaskTreeTaskAdapter); +TASKING_DECLARE_TASK(TimeoutTask, TimeoutTaskAdapter); -- cgit v1.2.3 From 1599106a224fc56c472078963fc1095b110c67a6 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 31 May 2023 18:29:11 +0200 Subject: TaskTree: Make the TimeoutTask reliable Ensure the timeout tasks preserve the right order of their done signals. Change-Id: I62508d0710eb2324d7c347a9907a899c97d3975d Reviewed-by: Marcus Tillmanns --- src/libs/solutions/tasking/tasktree.cpp | 85 ++++++++++++++++++++++++++++++++- src/libs/solutions/tasking/tasktree.h | 4 ++ 2 files changed, 88 insertions(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index 7fc5c66591..f5d67625a8 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -9,6 +9,8 @@ #include #include +using namespace std::chrono; + namespace Tasking { // That's cut down qtcassert.{c,h} to avoid the dependency. @@ -1848,14 +1850,95 @@ void TaskTreeTaskAdapter::start() task()->start(); } +using TimeoutCallback = std::function; + +struct TimerData +{ + system_clock::time_point m_deadline; + QPointer m_context; + TimeoutCallback m_callback; +}; + +QMutex s_mutex; +std::atomic_int s_timerId = 0; +QHash s_timerIdToTimerData = {}; +QMultiMap s_deadlineToTimerId = {}; + +static QList prepareForActivation(int timerId) +{ + QMutexLocker lock(&s_mutex); + const auto it = s_timerIdToTimerData.constFind(timerId); + if (it == s_timerIdToTimerData.cend()) + return {}; // the timer was already activated + + const system_clock::time_point deadline = it->m_deadline; + QList toActivate; + auto itMap = s_deadlineToTimerId.cbegin(); + while (itMap != s_deadlineToTimerId.cend()) { + if (itMap.key() > deadline) + break; + + const auto it = s_timerIdToTimerData.constFind(itMap.value()); + if (it != s_timerIdToTimerData.cend()) { + toActivate.append(it.value()); + s_timerIdToTimerData.erase(it); + } + itMap = s_deadlineToTimerId.erase(itMap); + } + return toActivate; +} + +static void removeTimerId(int timerId) +{ + QMutexLocker lock(&s_mutex); + const auto it = s_timerIdToTimerData.constFind(timerId); + QTC_ASSERT(it != s_timerIdToTimerData.cend(), + qWarning("Removing active timerId failed."); return); + + const system_clock::time_point deadline = it->m_deadline; + s_timerIdToTimerData.erase(it); + + const int removedCount = s_deadlineToTimerId.remove(deadline, timerId); + QTC_ASSERT(removedCount == 1, qWarning("Removing active timerId failed."); return); +} + +static void handleTimeout(int timerId) +{ + const QList toActivate = prepareForActivation(timerId); + for (const TimerData &timerData : toActivate) { + if (timerData.m_context) + QMetaObject::invokeMethod(timerData.m_context.get(), timerData.m_callback); + } +} + +static int scheduleTimeout(milliseconds timeout, QObject *context, const TimeoutCallback &callback) +{ + const int timerId = s_timerId.fetch_add(1) + 1; + const system_clock::time_point deadline = system_clock::now() + timeout; + QTimer::singleShot(timeout, context, [timerId] { handleTimeout(timerId); }); + QMutexLocker lock(&s_mutex); + s_timerIdToTimerData.emplace(timerId, TimerData{deadline, context, callback}); + s_deadlineToTimerId.insert(deadline, timerId); + return timerId; +} + TimeoutTaskAdapter::TimeoutTaskAdapter() { *task() = std::chrono::milliseconds::zero(); } +TimeoutTaskAdapter::~TimeoutTaskAdapter() +{ + if (m_timerId) + removeTimerId(*m_timerId); +} + void TimeoutTaskAdapter::start() { - QTimer::singleShot(*task(), this, [this] { emit done(true); }); + if (*task() == milliseconds::zero()) + QTimer::singleShot(0, this, [this] { emit done(true); }); + else + m_timerId = scheduleTimeout(*task(), this, [this] { m_timerId = {}; emit done(true); }); } } // namespace Tasking diff --git a/src/libs/solutions/tasking/tasktree.h b/src/libs/solutions/tasking/tasktree.h index 78593aa182..6962ed5cab 100644 --- a/src/libs/solutions/tasking/tasktree.h +++ b/src/libs/solutions/tasking/tasktree.h @@ -430,7 +430,11 @@ class TASKING_EXPORT TimeoutTaskAdapter : public TaskAdapter m_timerId; }; } // namespace Tasking -- cgit v1.2.3 From 619735d99dd30690bb183c5334b6a836813cf25c Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Mon, 29 May 2023 00:13:12 +0200 Subject: TaskTree: Introduce withTimeout() Make it available for Group or CustomTask items. Note, that when withTimeout() is used, the total number of tasks grows by one. Change-Id: Idc71737ba66b92bdc4bf17599c793b1127d22f5e Reviewed-by: Marcus Tillmanns Reviewed-by: Qt CI Bot --- src/libs/solutions/tasking/tasktree.cpp | 17 +++++++++++++++++ src/libs/solutions/tasking/tasktree.h | 12 ++++++++++++ 2 files changed, 29 insertions(+) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index f5d67625a8..8c6f587914 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -567,6 +567,23 @@ void TaskItem::setTaskErrorHandler(const TaskEndHandler &handler) m_taskHandler.m_errorHandler = handler; } +TaskItem TaskItem::withTimeout(const TaskItem &item, milliseconds timeout, + const GroupEndHandler &handler) +{ + const TimeoutTask::EndHandler taskHandler = handler + ? [handler](const milliseconds &) { handler(); } : TimeoutTask::EndHandler(); + return Group { + parallel, + stopOnFinished, + Group { + finishAllAndError, + TimeoutTask([timeout](milliseconds &timeoutData) { timeoutData = timeout; }, + taskHandler) + }, + item + }; +} + class TaskTreePrivate; class TaskNode; diff --git a/src/libs/solutions/tasking/tasktree.h b/src/libs/solutions/tasking/tasktree.h index 6962ed5cab..1d596316ca 100644 --- a/src/libs/solutions/tasking/tasktree.h +++ b/src/libs/solutions/tasking/tasktree.h @@ -187,6 +187,8 @@ protected: static TaskItem groupHandler(const GroupHandler &handler) { return TaskItem({handler}); } static TaskItem parallelLimit(int limit) { return TaskItem({{}, limit}); } static TaskItem workflowPolicy(WorkflowPolicy policy) { return TaskItem({{}, {}, policy}); } + static TaskItem withTimeout(const TaskItem &item, std::chrono::milliseconds timeout, + const GroupEndHandler &handler = {}); private: Type m_type = Type::Group; @@ -216,6 +218,11 @@ public: using TaskItem::parallelLimit; // Default: 1 (sequential). 0 means unlimited (parallel). using TaskItem::workflowPolicy; // Default: WorkflowPolicy::StopOnError. + TaskItem withTimeout(std::chrono::milliseconds timeout, + const GroupEndHandler &handler = {}) const { + return TaskItem::withTimeout(*this, timeout, handler); + } + private: template static GroupSetupHandler wrapGroupSetup(SetupHandler &&handler) @@ -329,6 +336,11 @@ public: return *this; } + TaskItem withTimeout(std::chrono::milliseconds timeout, + const GroupEndHandler &handler = {}) const { + return TaskItem::withTimeout(*this, timeout, handler); + } + private: template static TaskItem::TaskSetupHandler wrapSetup(SetupFunction &&function) { -- cgit v1.2.3 From b2dadeb30c6cfbbe53426281ed5974d2788eeaee Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Mon, 29 May 2023 15:47:11 +0200 Subject: TaskTree: Fix the empty Group's return result Get back to the originally intended behavior for StopOnDone and ContinueOnDone workflow policies. By default, these policies report an error until at least one child task finished with success. Since the group is empty, no child finished with success, so the group should report an error. Change also the return result for the StopOnFinished policy when placed in an empty group. The policy is meant to report the finished child's result. Since no task finished, report an error in this case. Fix tests accordingly. Amends c9638ff64291351b846d28c28f077bbe70f6c1f0 Change-Id: Idc449e8c68a658755bf566df56844126167f1751 Reviewed-by: Reviewed-by: Marcus Tillmanns Reviewed-by: Qt CI Bot --- src/libs/solutions/tasking/tasktree.cpp | 36 +++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 11 deletions(-) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index 8c6f587914..60caa217ef 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -679,8 +679,8 @@ public: const ConstData &m_constData; const QList m_storageIdList; - int m_doneCount = 0; bool m_successBit = true; + int m_doneCount = 0; Guard m_startGuard; }; @@ -875,14 +875,28 @@ void TaskContainer::RuntimeData::callStorageDoneHandlers() } } +static bool initialSuccessBit(WorkflowPolicy workflowPolicy) +{ + switch (workflowPolicy) { + case WorkflowPolicy::StopOnError: + case WorkflowPolicy::ContinueOnError: + case WorkflowPolicy::FinishAllAndDone: + return true; + case WorkflowPolicy::StopOnDone: + case WorkflowPolicy::ContinueOnDone: + case WorkflowPolicy::StopOnFinished: + case WorkflowPolicy::FinishAllAndError: + return false; + } + QTC_CHECK(false); + return false; +} + TaskContainer::RuntimeData::RuntimeData(const ConstData &constData) : m_constData(constData) , m_storageIdList(createStorages(constData)) -{ - m_successBit = m_constData.m_workflowPolicy != WorkflowPolicy::StopOnDone - && m_constData.m_workflowPolicy != WorkflowPolicy::ContinueOnDone - && m_constData.m_workflowPolicy != WorkflowPolicy::FinishAllAndError; -} + , m_successBit(initialSuccessBit(m_constData.m_workflowPolicy)) +{} TaskContainer::RuntimeData::~RuntimeData() { @@ -896,10 +910,10 @@ TaskContainer::RuntimeData::~RuntimeData() bool TaskContainer::RuntimeData::updateSuccessBit(bool success) { if (m_constData.m_workflowPolicy == WorkflowPolicy::FinishAllAndDone - || m_constData.m_workflowPolicy == WorkflowPolicy::FinishAllAndError) - return m_successBit; - if (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnFinished) { - m_successBit = success; + || m_constData.m_workflowPolicy == WorkflowPolicy::FinishAllAndError + || m_constData.m_workflowPolicy == WorkflowPolicy::StopOnFinished) { + if (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnFinished) + m_successBit = success; return m_successBit; } @@ -929,7 +943,7 @@ TaskAction TaskContainer::start() } if (startAction == TaskAction::Continue) { if (m_constData.m_children.isEmpty()) - startAction = TaskAction::StopWithDone; + startAction = toTaskAction(m_runtimeData->m_successBit); } return continueStart(startAction, 0); } -- cgit v1.2.3 From 3ee32c1a3b54542bd41df8b7bd420cb0554a9fc5 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Mon, 29 May 2023 01:10:39 +0200 Subject: TaskTree: Reuse withTimeout() Add static runBlocking() overloads. Replace int timeout arg with std::chrono::milliseconds. Change-Id: Id10a010f05eda8452cd7e4cd9ee46216087fc70e Reviewed-by: Reviewed-by: Marcus Tillmanns Reviewed-by: Qt CI Bot --- src/libs/solutions/tasking/tasktree.cpp | 35 +++++++++++++++++++-------------- src/libs/solutions/tasking/tasktree.h | 8 ++++++-- src/libs/utils/filestreamer.cpp | 3 +-- 3 files changed, 27 insertions(+), 19 deletions(-) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index 60caa217ef..f9fe58375e 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -1791,9 +1791,16 @@ bool TaskTree::isRunning() const return d->m_root && d->m_root->isRunning(); } -bool TaskTree::runBlocking(const QFuture &future, int timeoutMs) +bool TaskTree::runBlocking() { - if (isRunning() || future.isCanceled()) + QPromise dummy; + dummy.start(); + return runBlocking(dummy.future()); +} + +bool TaskTree::runBlocking(const QFuture &future) +{ + if (future.isCanceled()) return false; bool ok = false; @@ -1812,17 +1819,7 @@ bool TaskTree::runBlocking(const QFuture &future, int timeoutMs) connect(this, &TaskTree::done, &loop, [finalize] { finalize(true); }); connect(this, &TaskTree::errorOccurred, &loop, [finalize] { finalize(false); }); - start(); - if (!isRunning()) - return ok; - - QTimer timer; - if (timeoutMs) { - timer.setSingleShot(true); - timer.setInterval(timeoutMs); - connect(&timer, &QTimer::timeout, this, &TaskTree::stop); - timer.start(); - } + QTimer::singleShot(0, this, &TaskTree::start); loop.exec(QEventLoop::ExcludeUserInputEvents); if (!ok) { @@ -1832,11 +1829,19 @@ bool TaskTree::runBlocking(const QFuture &future, int timeoutMs) return ok; } -bool TaskTree::runBlocking(int timeoutMs) +bool TaskTree::runBlocking(const Group &recipe, milliseconds timeout) { QPromise dummy; dummy.start(); - return runBlocking(dummy.future(), timeoutMs); + return TaskTree::runBlocking(recipe, dummy.future(), timeout); +} + +bool TaskTree::runBlocking(const Group &recipe, const QFuture &future, milliseconds timeout) +{ + const Group root = timeout == milliseconds::max() ? recipe + : Group { recipe.withTimeout(timeout) }; + TaskTree taskTree(root); + return taskTree.runBlocking(future); } int TaskTree::taskCount() const diff --git a/src/libs/solutions/tasking/tasktree.h b/src/libs/solutions/tasking/tasktree.h index 1d596316ca..b4119219a1 100644 --- a/src/libs/solutions/tasking/tasktree.h +++ b/src/libs/solutions/tasking/tasktree.h @@ -390,8 +390,12 @@ public: // Helper methods. They execute a local event loop with ExcludeUserInputEvents. // The passed future is used for listening to the cancel event. // Don't use it in main thread. To be used in non-main threads or in auto tests. - bool runBlocking(const QFuture &future, int timeoutMs = 0); - bool runBlocking(int timeoutMs = 0); + bool runBlocking(); + bool runBlocking(const QFuture &future); + static bool runBlocking(const Group &recipe, + std::chrono::milliseconds timeout = std::chrono::milliseconds::max()); + static bool runBlocking(const Group &recipe, const QFuture &future, + std::chrono::milliseconds timeout = std::chrono::milliseconds::max()); int taskCount() const; int progressMaximum() const { return taskCount(); } diff --git a/src/libs/utils/filestreamer.cpp b/src/libs/utils/filestreamer.cpp index d24b61dbde..693849b7c5 100644 --- a/src/libs/utils/filestreamer.cpp +++ b/src/libs/utils/filestreamer.cpp @@ -375,8 +375,7 @@ static void transfer(QPromise &promise, const FilePath &source, const File if (promise.isCanceled()) return; - TaskTree taskTree(transferTask(source, destination)); - if (!taskTree.runBlocking(promise.future())) + if (!TaskTree::runBlocking(transferTask(source, destination), promise.future())) promise.future().cancel(); } -- cgit v1.2.3 From 64c48af15be4fda6f39383fbf9f589c4df1d8bc4 Mon Sep 17 00:00:00 2001 From: hjk Date: Wed, 31 May 2023 18:04:22 +0200 Subject: ProjectManager: Auto-register build settings aspects Add the necessary contructor to TriStateAspect, too. Change-Id: Ieb0f19cdf95f7492380d7c4e5663f455e4da3452 Reviewed-by: Christian Stenger --- src/libs/utils/aspects.cpp | 5 ++++- src/libs/utils/aspects.h | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index bb637f31b0..3dc5da0a4b 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -2162,8 +2162,11 @@ void DoubleAspect::setSingleStep(double step) Its visual representation is a QComboBox with three items. */ -TriStateAspect::TriStateAspect(const QString &onString, const QString &offString, +TriStateAspect::TriStateAspect(AspectContainer *container, + const QString &onString, + const QString &offString, const QString &defaultString) + : SelectionAspect(container) { setDisplayStyle(DisplayStyle::ComboBox); setDefaultValue(TriState::Default); diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index db3c036cec..a0ebf5a045 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -549,7 +549,8 @@ class QTCREATOR_UTILS_EXPORT TriStateAspect : public SelectionAspect Q_OBJECT public: - TriStateAspect(const QString &onString = {}, + TriStateAspect(AspectContainer *container = nullptr, + const QString &onString = {}, const QString &offString = {}, const QString &defaultString = {}); -- cgit v1.2.3 From 1afa720f2cfee575096fc7f6afc5a0760496675b Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Thu, 1 Jun 2023 13:00:52 +0300 Subject: FilePath: Replace Q_OS_WINDOWS with Q_OS_WIN for consistency Change-Id: Ia624c804e54fe4c5213351078a7aa9c8dec9f262 Reviewed-by: hjk --- src/libs/utils/filepath.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index ad871413a2..9059a37ecc 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -1914,7 +1914,7 @@ FilePath FilePath::canonicalPath() const return *this; } -#ifdef Q_OS_WINDOWS +#ifdef Q_OS_WIN DWORD flagsAndAttrs = FILE_ATTRIBUTE_NORMAL; if (isDir()) flagsAndAttrs |= FILE_FLAG_BACKUP_SEMANTICS; -- cgit v1.2.3 From fd7cb1181fc1651c2836f490894d8b582724c0e7 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Thu, 1 Jun 2023 16:02:26 +0200 Subject: Tasking: Fix qbs build Change-Id: I0ad1232a997a98902e6e9a7972f2af8e04a6b096 Reviewed-by: Jarek Kobus Reviewed-by: Qt CI Bot --- src/libs/solutions/tasking/tasktree.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/libs') diff --git a/src/libs/solutions/tasking/tasktree.h b/src/libs/solutions/tasking/tasktree.h index b4119219a1..647c680b5b 100644 --- a/src/libs/solutions/tasking/tasktree.h +++ b/src/libs/solutions/tasking/tasktree.h @@ -16,7 +16,7 @@ QT_END_NAMESPACE namespace Tasking { -Q_NAMESPACE +Q_NAMESPACE_EXPORT(TASKING_EXPORT) class ExecutionContextActivator; class TaskContainer; -- cgit v1.2.3 From a5dfbe01d59cf1756a2bbeb7870fe994f1527a0a Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Thu, 1 Jun 2023 12:58:46 +0300 Subject: Utils: Support hardlink detection also on Windows Change-Id: I717899ef73e965438ecd28983397ffc90a7ff570 Reviewed-by: David Schulz Reviewed-by: Qt CI Patch Build Bot --- src/libs/utils/devicefileaccess.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/devicefileaccess.cpp b/src/libs/utils/devicefileaccess.cpp index f5bb1d2048..bd139d9b16 100644 --- a/src/libs/utils/devicefileaccess.cpp +++ b/src/libs/utils/devicefileaccess.cpp @@ -493,6 +493,20 @@ bool DesktopDeviceFileAccess::hasHardLinks(const FilePath &filePath) const if (s.st_nlink > 1) return true; } +#elif defined(Q_OS_WIN) + const HANDLE handle = CreateFile((wchar_t *) filePath.toUserOutput().utf16(), + 0, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + if (handle == INVALID_HANDLE_VALUE) + return false; + + FILE_STANDARD_INFO info; + if (GetFileInformationByHandleEx(handle, FileStandardInfo, &info, sizeof(info))) + return info.NumberOfLinks > 1; #else Q_UNUSED(filePath) #endif -- cgit v1.2.3 From 5bacd9328eaef0a3ec8917df769ab6b7850dcc02 Mon Sep 17 00:00:00 2001 From: hjk Date: Thu, 1 Jun 2023 11:15:16 +0200 Subject: Utils: Add a overload taking a AspecContainer for TextDisplay Change-Id: If6cc933e852b80c4ec6abcc2477db8852392ca69 Reviewed-by: Christian Stenger Reviewed-by: Qt CI Patch Build Bot --- src/libs/utils/aspects.cpp | 4 ++++ src/libs/utils/aspects.h | 1 + 2 files changed, 5 insertions(+) (limited to 'src/libs') diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index 3dc5da0a4b..57d358a7c1 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -2346,6 +2346,10 @@ void IntegersAspect::setDefaultValue(const QList &value) A text display does not have a real value. */ +TextDisplay::TextDisplay(AspectContainer *container) + : BaseAspect(container) +{} + /*! Constructs a text display showing the \a message with an icon representing type \a type. diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index a0ebf5a045..b99c28c0be 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -610,6 +610,7 @@ class QTCREATOR_UTILS_EXPORT TextDisplay : public BaseAspect Q_OBJECT public: + explicit TextDisplay(AspectContainer *container); TextDisplay(const QString &message = {}, InfoLabel::InfoType type = InfoLabel::None); ~TextDisplay() override; -- cgit v1.2.3