aboutsummaryrefslogtreecommitdiffstats
path: root/src/libs
diff options
context:
space:
mode:
authorTim Jenssen <tim.jenssen@qt.io>2023-06-02 21:23:26 +0200
committerTim Jenssen <tim.jenssen@qt.io>2023-06-02 19:26:05 +0000
commit002d84cb15e314a207346fdfd6cf78055d1b8b92 (patch)
treef3354186e1b3f7e80a2786acfaff6656eb7cdb09 /src/libs
parentbc6487068892be8c99404998c93dc6125551cf18 (diff)
parente759ce310fe6f30724f29b809bf0bcab1df88e1d (diff)
Merge remote-tracking branch 'origin/11.0' into qds/dev
Conflicts: src/plugins/qmldesigner/designercore/metainfo/metainfo.cpp src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.cpp tests/auto/qml/qmlprojectmanager/fileformat/fileformat.qbs tests/auto/qml/qmlprojectmanager/fileformat/tst_fileformat.cpp Change-Id: I257f1908917bcc58805619b53b6866f2f73ca544
Diffstat (limited to 'src/libs')
-rw-r--r--src/libs/3rdparty/CMakeLists.txt6
-rw-r--r--src/libs/3rdparty/cplusplus/AST.cpp60
-rw-r--r--src/libs/3rdparty/cplusplus/AST.h161
-rw-r--r--src/libs/3rdparty/cplusplus/ASTClone.cpp99
-rw-r--r--src/libs/3rdparty/cplusplus/ASTMatch0.cpp51
-rw-r--r--src/libs/3rdparty/cplusplus/ASTMatcher.cpp131
-rw-r--r--src/libs/3rdparty/cplusplus/ASTMatcher.h7
-rw-r--r--src/libs/3rdparty/cplusplus/ASTVisit.cpp62
-rw-r--r--src/libs/3rdparty/cplusplus/ASTVisitor.h14
-rw-r--r--src/libs/3rdparty/cplusplus/ASTfwd.h7
-rw-r--r--src/libs/3rdparty/cplusplus/Bind.cpp12
-rw-r--r--src/libs/3rdparty/cplusplus/Bind.h2
-rw-r--r--src/libs/3rdparty/cplusplus/CMakeLists.txt1
-rw-r--r--src/libs/3rdparty/cplusplus/Keywords.cpp104
-rw-r--r--src/libs/3rdparty/cplusplus/Keywords.kwgen10
-rw-r--r--src/libs/3rdparty/cplusplus/Lexer.cpp7
-rw-r--r--src/libs/3rdparty/cplusplus/Parser.cpp329
-rw-r--r--src/libs/3rdparty/cplusplus/Parser.h8
-rw-r--r--src/libs/3rdparty/cplusplus/Symbol.h4
-rw-r--r--src/libs/3rdparty/cplusplus/Token.cpp8
-rw-r--r--src/libs/3rdparty/cplusplus/Token.h9
-rw-r--r--src/libs/3rdparty/libptyqt/.clang-format1
-rw-r--r--src/libs/3rdparty/libptyqt/CMakeLists.txt28
-rw-r--r--src/libs/3rdparty/libptyqt/LICENSE21
-rw-r--r--src/libs/3rdparty/libptyqt/conptyprocess.cpp343
-rw-r--r--src/libs/3rdparty/libptyqt/conptyprocess.h165
-rw-r--r--src/libs/3rdparty/libptyqt/iptyprocess.h56
-rw-r--r--src/libs/3rdparty/libptyqt/ptyqt.cpp45
-rw-r--r--src/libs/3rdparty/libptyqt/ptyqt.h12
-rw-r--r--src/libs/3rdparty/libptyqt/ptyqt.qbs45
-rw-r--r--src/libs/3rdparty/libptyqt/unixptyprocess.cpp374
-rw-r--r--src/libs/3rdparty/libptyqt/unixptyprocess.h66
-rw-r--r--src/libs/3rdparty/libptyqt/winptyprocess.cpp278
-rw-r--r--src/libs/3rdparty/libptyqt/winptyprocess.h43
-rw-r--r--src/libs/3rdparty/libvterm/CMakeLists.txt18
-rw-r--r--src/libs/3rdparty/libvterm/CONTRIBUTING22
-rw-r--r--src/libs/3rdparty/libvterm/LICENSE23
-rw-r--r--src/libs/3rdparty/libvterm/include/vterm.h637
-rw-r--r--src/libs/3rdparty/libvterm/include/vterm_keycodes.h61
-rw-r--r--src/libs/3rdparty/libvterm/src/encoding.c230
-rw-r--r--src/libs/3rdparty/libvterm/src/encoding/DECdrawing.inc36
-rw-r--r--src/libs/3rdparty/libvterm/src/encoding/uk.inc6
-rw-r--r--src/libs/3rdparty/libvterm/src/fullwidth.inc111
-rw-r--r--src/libs/3rdparty/libvterm/src/keyboard.c226
-rw-r--r--src/libs/3rdparty/libvterm/src/mouse.c99
-rw-r--r--src/libs/3rdparty/libvterm/src/parser.c402
-rw-r--r--src/libs/3rdparty/libvterm/src/pen.c613
-rw-r--r--src/libs/3rdparty/libvterm/src/rect.h56
-rw-r--r--src/libs/3rdparty/libvterm/src/screen.c1183
-rw-r--r--src/libs/3rdparty/libvterm/src/state.c2315
-rw-r--r--src/libs/3rdparty/libvterm/src/unicode.c313
-rw-r--r--src/libs/3rdparty/libvterm/src/utf8.h39
-rw-r--r--src/libs/3rdparty/libvterm/src/vterm.c429
-rw-r--r--src/libs/3rdparty/libvterm/src/vterm_internal.h296
-rw-r--r--src/libs/3rdparty/libvterm/vterm.pc.in8
-rw-r--r--src/libs/3rdparty/libvterm/vterm.qbs34
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/syntaxhighlighter.cpp2
-rw-r--r--src/libs/3rdparty/winpty/.gitattributes19
-rw-r--r--src/libs/3rdparty/winpty/.gitignore16
-rw-r--r--src/libs/3rdparty/winpty/CMakeLists.txt1
-rw-r--r--src/libs/3rdparty/winpty/LICENSE21
-rw-r--r--src/libs/3rdparty/winpty/README.md151
-rw-r--r--src/libs/3rdparty/winpty/RELEASES.md280
-rw-r--r--src/libs/3rdparty/winpty/VERSION.txt1
-rw-r--r--src/libs/3rdparty/winpty/appveyor.yml16
-rw-r--r--src/libs/3rdparty/winpty/configure167
-rw-r--r--src/libs/3rdparty/winpty/misc/.gitignore2
-rw-r--r--src/libs/3rdparty/winpty/misc/BufferResizeTests.cc90
-rw-r--r--src/libs/3rdparty/winpty/misc/ChangeScreenBuffer.cc53
-rw-r--r--src/libs/3rdparty/winpty/misc/ClearConsole.cc72
-rw-r--r--src/libs/3rdparty/winpty/misc/ConinMode.cc117
-rw-r--r--src/libs/3rdparty/winpty/misc/ConinMode.ps1116
-rw-r--r--src/libs/3rdparty/winpty/misc/ConoutMode.cc113
-rw-r--r--src/libs/3rdparty/winpty/misc/DebugClient.py42
-rw-r--r--src/libs/3rdparty/winpty/misc/DebugServer.py63
-rw-r--r--src/libs/3rdparty/winpty/misc/DumpLines.py5
-rw-r--r--src/libs/3rdparty/winpty/misc/EnableExtendedFlags.txt46
-rw-r--r--src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP437-Consolas.txt528
-rw-r--r--src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP437-Lucida.txt633
-rw-r--r--src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP932.txt630
-rw-r--r--src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP936.txt630
-rw-r--r--src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP949.txt630
-rw-r--r--src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP950.txt630
-rw-r--r--src/libs/3rdparty/winpty/misc/Font-Report-June2016/MinimumWindowWidths.txt16
-rw-r--r--src/libs/3rdparty/winpty/misc/Font-Report-June2016/Results.txt4
-rw-r--r--src/libs/3rdparty/winpty/misc/Font-Report-June2016/Windows10SetFontBugginess.txt144
-rw-r--r--src/libs/3rdparty/winpty/misc/FontSurvey.cc100
-rw-r--r--src/libs/3rdparty/winpty/misc/FormatChar.h21
-rw-r--r--src/libs/3rdparty/winpty/misc/FreezePerfTest.cc62
-rw-r--r--src/libs/3rdparty/winpty/misc/GetCh.cc20
-rw-r--r--src/libs/3rdparty/winpty/misc/GetConsolePos.cc41
-rw-r--r--src/libs/3rdparty/winpty/misc/GetFont.cc261
-rw-r--r--src/libs/3rdparty/winpty/misc/IdentifyConsoleWindow.ps151
-rw-r--r--src/libs/3rdparty/winpty/misc/IsNewConsole.cc87
-rw-r--r--src/libs/3rdparty/winpty/misc/MouseInputNotes.txt90
-rw-r--r--src/libs/3rdparty/winpty/misc/MoveConsoleWindow.cc34
-rw-r--r--src/libs/3rdparty/winpty/misc/Notes.txt219
-rw-r--r--src/libs/3rdparty/winpty/misc/OSVersion.cc27
-rw-r--r--src/libs/3rdparty/winpty/misc/ScreenBufferFreezeInactive.cc101
-rw-r--r--src/libs/3rdparty/winpty/misc/ScreenBufferTest.cc671
-rw-r--r--src/libs/3rdparty/winpty/misc/ScreenBufferTest2.cc151
-rw-r--r--src/libs/3rdparty/winpty/misc/SelectAllTest.cc45
-rw-r--r--src/libs/3rdparty/winpty/misc/SetBufInfo.cc90
-rw-r--r--src/libs/3rdparty/winpty/misc/SetBufferSize.cc32
-rw-r--r--src/libs/3rdparty/winpty/misc/SetCursorPos.cc10
-rw-r--r--src/libs/3rdparty/winpty/misc/SetFont.cc145
-rw-r--r--src/libs/3rdparty/winpty/misc/SetWindowRect.cc36
-rw-r--r--src/libs/3rdparty/winpty/misc/ShowArgv.cc12
-rw-r--r--src/libs/3rdparty/winpty/misc/ShowConsoleInput.cc40
-rw-r--r--src/libs/3rdparty/winpty/misc/Spew.py5
-rw-r--r--src/libs/3rdparty/winpty/misc/TestUtil.cc172
-rw-r--r--src/libs/3rdparty/winpty/misc/UnicodeDoubleWidthTest.cc102
-rw-r--r--src/libs/3rdparty/winpty/misc/UnicodeWideTest1.cc246
-rw-r--r--src/libs/3rdparty/winpty/misc/UnicodeWideTest2.cc130
-rw-r--r--src/libs/3rdparty/winpty/misc/UnixEcho.cc89
-rw-r--r--src/libs/3rdparty/winpty/misc/Utf16Echo.cc46
-rw-r--r--src/libs/3rdparty/winpty/misc/VeryLargeRead.cc122
-rw-r--r--src/libs/3rdparty/winpty/misc/VkEscapeTest.cc56
-rw-r--r--src/libs/3rdparty/winpty/misc/Win10ResizeWhileFrozen.cc52
-rw-r--r--src/libs/3rdparty/winpty/misc/Win10WrapTest1.cc57
-rw-r--r--src/libs/3rdparty/winpty/misc/Win10WrapTest2.cc30
-rw-r--r--src/libs/3rdparty/winpty/misc/Win32Echo1.cc26
-rw-r--r--src/libs/3rdparty/winpty/misc/Win32Echo2.cc19
-rw-r--r--src/libs/3rdparty/winpty/misc/Win32Test1.cc46
-rw-r--r--src/libs/3rdparty/winpty/misc/Win32Test2.cc70
-rw-r--r--src/libs/3rdparty/winpty/misc/Win32Test3.cc78
-rw-r--r--src/libs/3rdparty/winpty/misc/Win32Write1.cc44
-rw-r--r--src/libs/3rdparty/winpty/misc/WindowsBugCrashReader.cc27
-rw-r--r--src/libs/3rdparty/winpty/misc/WriteConsole.cc106
-rw-r--r--src/libs/3rdparty/winpty/misc/build32.sh9
-rw-r--r--src/libs/3rdparty/winpty/misc/build64.sh9
-rw-r--r--src/libs/3rdparty/winpty/misc/color-test.sh212
-rw-r--r--src/libs/3rdparty/winpty/misc/font-notes.txt300
-rw-r--r--src/libs/3rdparty/winpty/misc/winbug-15048.cc201
-rw-r--r--src/libs/3rdparty/winpty/ship/build-pty4j-libpty.bat36
-rw-r--r--src/libs/3rdparty/winpty/ship/common_ship.py89
-rw-r--r--src/libs/3rdparty/winpty/ship/make_msvc_package.py163
-rw-r--r--src/libs/3rdparty/winpty/ship/ship.py104
-rw-r--r--src/libs/3rdparty/winpty/src/CMakeLists.txt111
-rw-r--r--src/libs/3rdparty/winpty/src/agent/Agent.cc612
-rw-r--r--src/libs/3rdparty/winpty/src/agent/Agent.h103
-rw-r--r--src/libs/3rdparty/winpty/src/agent/AgentCreateDesktop.cc84
-rw-r--r--src/libs/3rdparty/winpty/src/agent/AgentCreateDesktop.h28
-rw-r--r--src/libs/3rdparty/winpty/src/agent/ConsoleFont.cc698
-rw-r--r--src/libs/3rdparty/winpty/src/agent/ConsoleFont.h28
-rw-r--r--src/libs/3rdparty/winpty/src/agent/ConsoleInput.cc852
-rw-r--r--src/libs/3rdparty/winpty/src/agent/ConsoleInput.h109
-rw-r--r--src/libs/3rdparty/winpty/src/agent/ConsoleInputReencoding.cc121
-rw-r--r--src/libs/3rdparty/winpty/src/agent/ConsoleInputReencoding.h36
-rw-r--r--src/libs/3rdparty/winpty/src/agent/ConsoleLine.cc152
-rw-r--r--src/libs/3rdparty/winpty/src/agent/ConsoleLine.h41
-rw-r--r--src/libs/3rdparty/winpty/src/agent/Coord.h87
-rw-r--r--src/libs/3rdparty/winpty/src/agent/DebugShowInput.cc239
-rw-r--r--src/libs/3rdparty/winpty/src/agent/DebugShowInput.h32
-rw-r--r--src/libs/3rdparty/winpty/src/agent/DefaultInputMap.cc422
-rw-r--r--src/libs/3rdparty/winpty/src/agent/DefaultInputMap.h28
-rw-r--r--src/libs/3rdparty/winpty/src/agent/DsrSender.h30
-rw-r--r--src/libs/3rdparty/winpty/src/agent/EventLoop.cc99
-rw-r--r--src/libs/3rdparty/winpty/src/agent/EventLoop.h47
-rw-r--r--src/libs/3rdparty/winpty/src/agent/InputMap.cc246
-rw-r--r--src/libs/3rdparty/winpty/src/agent/InputMap.h114
-rw-r--r--src/libs/3rdparty/winpty/src/agent/LargeConsoleRead.cc71
-rw-r--r--src/libs/3rdparty/winpty/src/agent/LargeConsoleRead.h68
-rw-r--r--src/libs/3rdparty/winpty/src/agent/NamedPipe.cc378
-rw-r--r--src/libs/3rdparty/winpty/src/agent/NamedPipe.h125
-rw-r--r--src/libs/3rdparty/winpty/src/agent/Scraper.cc699
-rw-r--r--src/libs/3rdparty/winpty/src/agent/Scraper.h103
-rw-r--r--src/libs/3rdparty/winpty/src/agent/SimplePool.h75
-rw-r--r--src/libs/3rdparty/winpty/src/agent/SmallRect.h143
-rw-r--r--src/libs/3rdparty/winpty/src/agent/Terminal.cc535
-rw-r--r--src/libs/3rdparty/winpty/src/agent/Terminal.h69
-rw-r--r--src/libs/3rdparty/winpty/src/agent/UnicodeEncoding.h157
-rw-r--r--src/libs/3rdparty/winpty/src/agent/UnicodeEncodingTest.cc189
-rw-r--r--src/libs/3rdparty/winpty/src/agent/Win32Console.cc107
-rw-r--r--src/libs/3rdparty/winpty/src/agent/Win32Console.h67
-rw-r--r--src/libs/3rdparty/winpty/src/agent/Win32ConsoleBuffer.cc193
-rw-r--r--src/libs/3rdparty/winpty/src/agent/Win32ConsoleBuffer.h99
-rw-r--r--src/libs/3rdparty/winpty/src/agent/main.cc120
-rw-r--r--src/libs/3rdparty/winpty/src/agent/subdir.mk61
-rw-r--r--src/libs/3rdparty/winpty/src/configurations.gypi60
-rw-r--r--src/libs/3rdparty/winpty/src/debugserver/DebugServer.cc117
-rw-r--r--src/libs/3rdparty/winpty/src/debugserver/subdir.mk41
-rw-r--r--src/libs/3rdparty/winpty/src/include/winpty.h242
-rw-r--r--src/libs/3rdparty/winpty/src/include/winpty_constants.h131
-rw-r--r--src/libs/3rdparty/winpty/src/libwinpty/AgentLocation.cc75
-rw-r--r--src/libs/3rdparty/winpty/src/libwinpty/AgentLocation.h28
-rw-r--r--src/libs/3rdparty/winpty/src/libwinpty/LibWinptyException.h54
-rw-r--r--src/libs/3rdparty/winpty/src/libwinpty/WinptyInternal.h72
-rw-r--r--src/libs/3rdparty/winpty/src/libwinpty/subdir.mk46
-rw-r--r--src/libs/3rdparty/winpty/src/libwinpty/winpty.cc970
-rw-r--r--src/libs/3rdparty/winpty/src/shared/AgentMsg.h38
-rw-r--r--src/libs/3rdparty/winpty/src/shared/BackgroundDesktop.cc122
-rw-r--r--src/libs/3rdparty/winpty/src/shared/BackgroundDesktop.h73
-rw-r--r--src/libs/3rdparty/winpty/src/shared/Buffer.cc103
-rw-r--r--src/libs/3rdparty/winpty/src/shared/Buffer.h102
-rw-r--r--src/libs/3rdparty/winpty/src/shared/DebugClient.cc187
-rw-r--r--src/libs/3rdparty/winpty/src/shared/DebugClient.h38
-rw-r--r--src/libs/3rdparty/winpty/src/shared/GenRandom.cc138
-rw-r--r--src/libs/3rdparty/winpty/src/shared/GenRandom.h55
-rw-r--r--src/libs/3rdparty/winpty/src/shared/GetCommitHash.bat13
-rw-r--r--src/libs/3rdparty/winpty/src/shared/Mutex.h54
-rw-r--r--src/libs/3rdparty/winpty/src/shared/OsModule.h63
-rw-r--r--src/libs/3rdparty/winpty/src/shared/OwnedHandle.cc36
-rw-r--r--src/libs/3rdparty/winpty/src/shared/OwnedHandle.h45
-rw-r--r--src/libs/3rdparty/winpty/src/shared/PrecompiledHeader.h43
-rw-r--r--src/libs/3rdparty/winpty/src/shared/StringBuilder.h227
-rw-r--r--src/libs/3rdparty/winpty/src/shared/StringBuilderTest.cc114
-rw-r--r--src/libs/3rdparty/winpty/src/shared/StringUtil.cc55
-rw-r--r--src/libs/3rdparty/winpty/src/shared/StringUtil.h80
-rw-r--r--src/libs/3rdparty/winpty/src/shared/TimeMeasurement.h63
-rw-r--r--src/libs/3rdparty/winpty/src/shared/UnixCtrlChars.h45
-rw-r--r--src/libs/3rdparty/winpty/src/shared/UpdateGenVersion.bat20
-rw-r--r--src/libs/3rdparty/winpty/src/shared/WindowsSecurity.cc460
-rw-r--r--src/libs/3rdparty/winpty/src/shared/WindowsSecurity.h104
-rw-r--r--src/libs/3rdparty/winpty/src/shared/WindowsVersion.cc252
-rw-r--r--src/libs/3rdparty/winpty/src/shared/WindowsVersion.h29
-rw-r--r--src/libs/3rdparty/winpty/src/shared/WinptyAssert.cc55
-rw-r--r--src/libs/3rdparty/winpty/src/shared/WinptyAssert.h64
-rw-r--r--src/libs/3rdparty/winpty/src/shared/WinptyException.cc57
-rw-r--r--src/libs/3rdparty/winpty/src/shared/WinptyException.h43
-rw-r--r--src/libs/3rdparty/winpty/src/shared/WinptyVersion.cc42
-rw-r--r--src/libs/3rdparty/winpty/src/shared/WinptyVersion.h27
-rw-r--r--src/libs/3rdparty/winpty/src/shared/winpty_snprintf.h99
-rw-r--r--src/libs/3rdparty/winpty/src/subdir.mk5
-rw-r--r--src/libs/3rdparty/winpty/src/tests/subdir.mk28
-rw-r--r--src/libs/3rdparty/winpty/src/tests/trivial_test.cc158
-rw-r--r--src/libs/3rdparty/winpty/src/unix-adapter/InputHandler.cc114
-rw-r--r--src/libs/3rdparty/winpty/src/unix-adapter/InputHandler.h56
-rw-r--r--src/libs/3rdparty/winpty/src/unix-adapter/OutputHandler.cc80
-rw-r--r--src/libs/3rdparty/winpty/src/unix-adapter/OutputHandler.h53
-rw-r--r--src/libs/3rdparty/winpty/src/unix-adapter/Util.cc86
-rw-r--r--src/libs/3rdparty/winpty/src/unix-adapter/Util.h31
-rw-r--r--src/libs/3rdparty/winpty/src/unix-adapter/WakeupFd.cc70
-rw-r--r--src/libs/3rdparty/winpty/src/unix-adapter/WakeupFd.h42
-rw-r--r--src/libs/3rdparty/winpty/src/unix-adapter/main.cc729
-rw-r--r--src/libs/3rdparty/winpty/src/unix-adapter/subdir.mk41
-rw-r--r--src/libs/3rdparty/winpty/src/winpty.gyp206
-rw-r--r--src/libs/3rdparty/winpty/vcbuild.bat83
-rw-r--r--src/libs/3rdparty/winpty/winpty.qbs207
-rw-r--r--src/libs/CMakeLists.txt17
-rw-r--r--src/libs/advanceddockingsystem/ads_globals_p.h8
-rw-r--r--src/libs/advanceddockingsystem/advanceddockingsystem.qbs4
-rw-r--r--src/libs/advanceddockingsystem/dockareatabbar.cpp3
-rw-r--r--src/libs/advanceddockingsystem/dockareatitlebar.cpp3
-rw-r--r--src/libs/advanceddockingsystem/dockareawidget.cpp3
-rw-r--r--src/libs/advanceddockingsystem/dockcontainerwidget.cpp3
-rw-r--r--src/libs/advanceddockingsystem/dockmanager.cpp3
-rw-r--r--src/libs/advanceddockingsystem/docksplitter.cpp7
-rw-r--r--src/libs/advanceddockingsystem/dockwidget.cpp3
-rw-r--r--src/libs/advanceddockingsystem/dockwidgettab.cpp3
-rw-r--r--src/libs/advanceddockingsystem/floatingdockcontainer.cpp3
-rw-r--r--src/libs/advanceddockingsystem/floatingdragpreview.cpp3
-rw-r--r--src/libs/advanceddockingsystem/workspacedialog.cpp3
-rw-r--r--src/libs/advanceddockingsystem/workspaceinputdialog.cpp2
-rw-r--r--src/libs/advanceddockingsystem/workspaceview.cpp8
-rw-r--r--src/libs/cplusplus/CppDocument.cpp18
-rw-r--r--src/libs/cplusplus/CppDocument.h15
-rw-r--r--src/libs/cplusplus/DependencyTable.cpp18
-rw-r--r--src/libs/cplusplus/DependencyTable.h5
-rw-r--r--src/libs/cplusplus/cplusplus.qbs1
-rw-r--r--src/libs/cplusplus/pp-engine.cpp63
-rw-r--r--src/libs/cplusplus/pp-engine.h1
-rw-r--r--src/libs/extensionsystem/CMakeLists.txt2
-rw-r--r--src/libs/extensionsystem/iplugin.cpp60
-rw-r--r--src/libs/extensionsystem/iplugin.h25
-rw-r--r--src/libs/extensionsystem/plugindetailsview.cpp7
-rw-r--r--src/libs/extensionsystem/pluginerroroverview.cpp2
-rw-r--r--src/libs/extensionsystem/pluginerrorview.cpp7
-rw-r--r--src/libs/extensionsystem/pluginmanager.cpp18
-rw-r--r--src/libs/extensionsystem/pluginmanager.h5
-rw-r--r--src/libs/extensionsystem/pluginmanager_p.h3
-rw-r--r--src/libs/extensionsystem/pluginspec.cpp6
-rw-r--r--src/libs/languageserverprotocol/jsonrpcmessages.cpp3
-rw-r--r--src/libs/languageserverprotocol/jsonrpcmessages.h1
-rw-r--r--src/libs/languageserverprotocol/lsptypes.cpp20
-rw-r--r--src/libs/languageserverprotocol/lsptypes.h3
-rw-r--r--src/libs/languageserverprotocol/lsputils.h7
-rw-r--r--src/libs/languageserverprotocol/workspace.h2
-rw-r--r--src/libs/libs.qbs5
m---------src/libs/qlitehtml0
-rw-r--r--src/libs/qmleditorwidgets/contextpanetextwidget.cpp2
-rw-r--r--src/libs/qmleditorwidgets/contextpanewidget.cpp29
-rw-r--r--src/libs/qmleditorwidgets/contextpanewidgetimage.cpp4
-rw-r--r--src/libs/qmleditorwidgets/contextpanewidgetrectangle.cpp15
-rw-r--r--src/libs/qmleditorwidgets/easingpane/easingcontextpane.cpp2
-rw-r--r--src/libs/qmljs/qmljsbind.cpp2
-rw-r--r--src/libs/qmljs/qmljsbundle.cpp19
-rw-r--r--src/libs/qmljs/qmljsbundle.h5
-rw-r--r--src/libs/qmljs/qmljscheck.cpp80
-rw-r--r--src/libs/qmljs/qmljscheck.h8
-rw-r--r--src/libs/qmljs/qmljscodeformatter.cpp7
-rw-r--r--src/libs/qmljs/qmljscodeformatter.h1
-rw-r--r--src/libs/qmljs/qmljsmodelmanagerinterface.cpp95
-rw-r--r--src/libs/qmljs/qmljsmodelmanagerinterface.h19
-rw-r--r--src/libs/qmljs/qmljsplugindumper.cpp33
-rw-r--r--src/libs/qmljs/qmljsplugindumper.h6
-rw-r--r--src/libs/qmljs/qmljsreformatter.cpp24
-rw-r--r--src/libs/qmljs/qmljsutils.cpp5
-rw-r--r--src/libs/solutions/CMakeLists.txt1
-rw-r--r--src/libs/solutions/README.md48
-rw-r--r--src/libs/solutions/solutions.qbs7
-rw-r--r--src/libs/solutions/tasking/CMakeLists.txt9
-rw-r--r--src/libs/solutions/tasking/barrier.cpp52
-rw-r--r--src/libs/solutions/tasking/barrier.h97
-rw-r--r--src/libs/solutions/tasking/tasking.qbs14
-rw-r--r--src/libs/solutions/tasking/tasking_global.h14
-rw-r--r--src/libs/solutions/tasking/tasktree.cpp1980
-rw-r--r--src/libs/solutions/tasking/tasktree.h468
-rw-r--r--src/libs/sqlite/CMakeLists.txt2
-rw-r--r--src/libs/sqlite/sqliteexception.h5
-rw-r--r--src/libs/tracing/qml/ImageToolButton.qml6
-rw-r--r--src/libs/tracing/qml/MainView.qml2
-rw-r--r--src/libs/tracing/qml/RangeDetails.qml2
-rw-r--r--src/libs/tracing/qml/TimeDisplay.qml2
-rw-r--r--src/libs/tracing/timelinetheme.cpp13
-rw-r--r--src/libs/tracing/timelinetheme.h2
-rw-r--r--src/libs/tracing/timelinetracemanager.cpp36
-rw-r--r--src/libs/utils/CMakeLists.txt30
-rw-r--r--src/libs/utils/ansiescapecodehandler.cpp1
-rw-r--r--src/libs/utils/archive.cpp8
-rw-r--r--src/libs/utils/archive.h4
-rw-r--r--src/libs/utils/aspects.cpp429
-rw-r--r--src/libs/utils/aspects.h115
-rw-r--r--src/libs/utils/async.cpp55
-rw-r--r--src/libs/utils/async.h215
-rw-r--r--src/libs/utils/asynctask.cpp8
-rw-r--r--src/libs/utils/asynctask.h93
-rw-r--r--src/libs/utils/buildablehelperlibrary.cpp6
-rw-r--r--src/libs/utils/checkablemessagebox.cpp548
-rw-r--r--src/libs/utils/checkablemessagebox.h127
-rw-r--r--src/libs/utils/clangutils.cpp9
-rw-r--r--src/libs/utils/classnamevalidatinglineedit.cpp1
-rw-r--r--src/libs/utils/commandline.cpp46
-rw-r--r--src/libs/utils/commandline.h4
-rw-r--r--src/libs/utils/completingtextedit.cpp4
-rw-r--r--src/libs/utils/delegates.h8
-rw-r--r--src/libs/utils/detailswidget.cpp1
-rw-r--r--src/libs/utils/devicefileaccess.cpp234
-rw-r--r--src/libs/utils/devicefileaccess.h28
-rw-r--r--src/libs/utils/deviceshell.cpp28
-rw-r--r--src/libs/utils/deviceshell.h11
-rw-r--r--src/libs/utils/differ.cpp25
-rw-r--r--src/libs/utils/differ.h8
-rw-r--r--src/libs/utils/dropsupport.cpp4
-rw-r--r--src/libs/utils/elidinglabel.cpp1
-rw-r--r--src/libs/utils/environment.cpp455
-rw-r--r--src/libs/utils/environment.h127
-rw-r--r--src/libs/utils/externalterminalprocessimpl.cpp92
-rw-r--r--src/libs/utils/externalterminalprocessimpl.h29
-rw-r--r--src/libs/utils/faketooltip.cpp1
-rw-r--r--src/libs/utils/fancylineedit.cpp17
-rw-r--r--src/libs/utils/fancymainwindow.cpp4
-rw-r--r--src/libs/utils/fileinprojectfinder.cpp10
-rw-r--r--src/libs/utils/filenamevalidatinglineedit.cpp1
-rw-r--r--src/libs/utils/filepath.cpp802
-rw-r--r--src/libs/utils/filepath.h50
-rw-r--r--src/libs/utils/filesearch.cpp101
-rw-r--r--src/libs/utils/filesearch.h70
-rw-r--r--src/libs/utils/filestreamer.cpp491
-rw-r--r--src/libs/utils/filestreamer.h63
-rw-r--r--src/libs/utils/filestreamermanager.cpp201
-rw-r--r--src/libs/utils/filestreamermanager.h42
-rw-r--r--src/libs/utils/filesystemmodel.cpp17
-rw-r--r--src/libs/utils/filesystemwatcher.cpp64
-rw-r--r--src/libs/utils/fileutils.cpp46
-rw-r--r--src/libs/utils/fileutils.h7
-rw-r--r--src/libs/utils/fileutils_mac.h1
-rw-r--r--src/libs/utils/fileutils_mac.mm11
-rw-r--r--src/libs/utils/filewizardpage.cpp2
-rw-r--r--src/libs/utils/fsengine/diriterator.h3
-rw-r--r--src/libs/utils/fsengine/fileiconprovider.cpp55
-rw-r--r--src/libs/utils/fsengine/fileiteratordevicesappender.h5
-rw-r--r--src/libs/utils/fsengine/fixedlistfsengine.h2
-rw-r--r--src/libs/utils/fsengine/fsengine.cpp51
-rw-r--r--src/libs/utils/fsengine/fsengine.h4
-rw-r--r--src/libs/utils/fsengine/fsengine_impl.cpp2
-rw-r--r--src/libs/utils/fsengine/fsenginehandler.cpp35
-rw-r--r--src/libs/utils/futuresynchronizer.cpp4
-rw-r--r--src/libs/utils/futuresynchronizer.h7
-rw-r--r--src/libs/utils/guard.cpp4
-rw-r--r--src/libs/utils/headerviewstretcher.cpp1
-rw-r--r--src/libs/utils/images/iconoverlay_close_small.pngbin0 -> 173 bytes
-rw-r--r--src/libs/utils/images/iconoverlay_close_small@2x.pngbin0 -> 249 bytes
-rw-r--r--src/libs/utils/images/pinned_small.pngbin0 -> 145 bytes
-rw-r--r--src/libs/utils/images/pinned_small@2x.pngbin0 -> 233 bytes
-rw-r--r--src/libs/utils/itemviews.cpp4
-rw-r--r--src/libs/utils/launcherinterface.cpp24
-rw-r--r--src/libs/utils/launcherpackets.cpp6
-rw-r--r--src/libs/utils/launcherpackets.h1
-rw-r--r--src/libs/utils/launchersocket.cpp7
-rw-r--r--src/libs/utils/launchersocket.h2
-rw-r--r--src/libs/utils/layoutbuilder.cpp1061
-rw-r--r--src/libs/utils/layoutbuilder.h316
-rw-r--r--src/libs/utils/linecolumn.cpp43
-rw-r--r--src/libs/utils/linecolumn.h46
-rw-r--r--src/libs/utils/link.cpp8
-rw-r--r--src/libs/utils/link.h22
-rw-r--r--src/libs/utils/macroexpander.cpp99
-rw-r--r--src/libs/utils/mathutils.cpp34
-rw-r--r--src/libs/utils/multitextcursor.cpp271
-rw-r--r--src/libs/utils/multitextcursor.h70
-rw-r--r--src/libs/utils/namevalueitem.cpp12
-rw-r--r--src/libs/utils/navigationtreeview.cpp1
-rw-r--r--src/libs/utils/optionpushbutton.cpp1
-rw-r--r--src/libs/utils/osspecificaspects.h30
-rw-r--r--src/libs/utils/parameteraction.cpp1
-rw-r--r--src/libs/utils/pathchooser.cpp22
-rw-r--r--src/libs/utils/pathchooser.h2
-rw-r--r--src/libs/utils/pathlisteditor.cpp1
-rw-r--r--src/libs/utils/persistentsettings.cpp2
-rw-r--r--src/libs/utils/port.cpp28
-rw-r--r--src/libs/utils/port.h4
-rw-r--r--src/libs/utils/process.cpp (renamed from src/libs/utils/qtcprocess.cpp)460
-rw-r--r--src/libs/utils/process.h (renamed from src/libs/utils/qtcprocess.h)42
-rw-r--r--src/libs/utils/process_ctrlc_stub.cpp10
-rw-r--r--src/libs/utils/process_stub.qbs21
-rw-r--r--src/libs/utils/process_stub_unix.c340
-rw-r--r--src/libs/utils/process_stub_win.c204
-rw-r--r--src/libs/utils/processenums.h7
-rw-r--r--src/libs/utils/processhandle.cpp1
-rw-r--r--src/libs/utils/processinfo.cpp179
-rw-r--r--src/libs/utils/processinfo.h4
-rw-r--r--src/libs/utils/processinterface.cpp17
-rw-r--r--src/libs/utils/processinterface.h38
-rw-r--r--src/libs/utils/processreaper.cpp2
-rw-r--r--src/libs/utils/processutils.cpp62
-rw-r--r--src/libs/utils/processutils.h3
-rw-r--r--src/libs/utils/progressindicator.cpp10
-rw-r--r--src/libs/utils/projectintropage.cpp2
-rw-r--r--src/libs/utils/qrcparser.h3
-rw-r--r--src/libs/utils/qtcolorbutton.cpp90
-rw-r--r--src/libs/utils/runextensions.h109
-rw-r--r--src/libs/utils/scopedtimer.cpp67
-rw-r--r--src/libs/utils/scopedtimer.h52
-rw-r--r--src/libs/utils/searchresultitem.cpp64
-rw-r--r--src/libs/utils/searchresultitem.h102
-rw-r--r--src/libs/utils/settingsaccessor.cpp78
-rw-r--r--src/libs/utils/settingsaccessor.h28
-rw-r--r--src/libs/utils/statuslabel.cpp1
-rw-r--r--src/libs/utils/stringtable.cpp10
-rw-r--r--src/libs/utils/stringutils.cpp107
-rw-r--r--src/libs/utils/stringutils.h18
-rw-r--r--src/libs/utils/styledbar.cpp16
-rw-r--r--src/libs/utils/stylehelper.cpp83
-rw-r--r--src/libs/utils/stylehelper.h241
-rw-r--r--src/libs/utils/tasktree.cpp1401
-rw-r--r--src/libs/utils/tasktree.h385
-rw-r--r--src/libs/utils/terminalcommand.cpp8
-rw-r--r--src/libs/utils/terminalhooks.cpp129
-rw-r--r--src/libs/utils/terminalhooks.h91
-rw-r--r--src/libs/utils/terminalinterface.cpp433
-rw-r--r--src/libs/utils/terminalinterface.h61
-rw-r--r--src/libs/utils/terminalprocess.cpp721
-rw-r--r--src/libs/utils/terminalprocess_p.h54
-rw-r--r--src/libs/utils/textfieldcheckbox.cpp1
-rw-r--r--src/libs/utils/textfieldcombobox.cpp1
-rw-r--r--src/libs/utils/textfileformat.cpp28
-rw-r--r--src/libs/utils/textutils.cpp134
-rw-r--r--src/libs/utils/textutils.h46
-rw-r--r--src/libs/utils/theme/theme.h23
-rw-r--r--src/libs/utils/tooltip/tips.cpp12
-rw-r--r--src/libs/utils/treemodel.cpp14
-rw-r--r--src/libs/utils/utils.qbs33
-rw-r--r--src/libs/utils/utils.qdoc3
-rw-r--r--src/libs/utils/utils.qrc4
-rw-r--r--src/libs/utils/utilsicons.cpp4
-rw-r--r--src/libs/utils/utilsicons.h2
-rw-r--r--src/libs/utils/utiltypes.h14
-rw-r--r--src/libs/utils/variablechooser.h4
-rw-r--r--src/libs/utils/wizard.cpp4
-rw-r--r--src/libs/utils/wizardpage.cpp4
471 files changed, 44041 insertions, 6884 deletions
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/cplusplus/AST.cpp b/src/libs/3rdparty/cplusplus/AST.cpp
index 5d7c9e2a90..5c59bb683c 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;
@@ -1657,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;
@@ -1690,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;
@@ -1708,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;
@@ -3761,6 +3787,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)
@@ -3805,6 +3833,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;
@@ -4634,3 +4664,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..3f48e192b9 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; }
@@ -290,6 +291,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 +324,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 +332,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,9 +340,12 @@ public:
virtual TypenameTypeParameterAST *asTypenameTypeParameter() { return nullptr; }
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; }
+ virtual YieldExpressionAST *asYieldExpression() { return nullptr; }
protected:
virtual void accept0(ASTVisitor *visitor) = 0;
@@ -636,6 +643,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:
@@ -646,6 +696,7 @@ public:
SpecifierListAST *post_attribute_list = nullptr;
int equal_token = 0;
ExpressionAST *initializer = nullptr;
+ RequiresClauseAST *requiresClause = nullptr;
public:
DeclaratorAST *asDeclarator() override { return this; }
@@ -1728,6 +1779,7 @@ public:
int if_token = 0;
int constexpr_token = 0;
int lparen_token = 0;
+ StatementAST *initStmt = nullptr;
ExpressionAST *condition = nullptr;
int rparen_token = 0;
StatementAST *statement = nullptr;
@@ -2716,6 +2768,7 @@ public:
int less_token = 0;
DeclarationListAST *template_parameter_list = nullptr;
int greater_token = 0;
+ RequiresClauseAST *requiresClause = nullptr;
DeclarationAST *declaration = nullptr;
public: // annotations
@@ -2734,6 +2787,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:
@@ -2753,6 +2829,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:
@@ -2883,6 +2997,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;
@@ -2927,6 +3042,48 @@ 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 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:
@@ -3522,6 +3679,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;
@@ -3602,6 +3762,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 9e5a773bdb..c9c2fc8294 100644
--- a/src/libs/3rdparty/cplusplus/ASTClone.cpp
+++ b/src/libs/3rdparty/cplusplus/ASTClone.cpp
@@ -141,6 +141,34 @@ 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);
+ 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)
+ *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;
@@ -757,6 +785,8 @@ IfStatementAST *IfStatementAST::clone(MemoryPool *pool) const
ast->if_token = if_token;
ast->constexpr_token = constexpr_token;
ast->lparen_token = lparen_token;
+ if (initStmt)
+ ast->initStmt = initStmt->clone(pool);
if (condition)
ast->condition = condition->clone(pool);
ast->rparen_token = rparen_token;
@@ -1279,11 +1309,50 @@ 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;
}
+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;
+}
+
+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;
@@ -1293,6 +1362,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;
@@ -1364,6 +1451,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)
@@ -1729,6 +1818,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)
@@ -1780,6 +1877,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/ASTMatch0.cpp b/src/libs/3rdparty/cplusplus/ASTMatch0.cpp
index 4105ec3c52..b58bd59efe 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,28 @@ 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 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())
@@ -904,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 fcfe86ab57..b264098a96 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;
@@ -1299,6 +1318,11 @@ bool ASTMatcher::match(IfStatementAST *node, IfStatementAST *pattern)
pattern->lparen_token = node->lparen_token;
+ 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))
@@ -1749,6 +1773,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;
@@ -2173,6 +2210,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))
@@ -2181,6 +2223,50 @@ 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(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;
@@ -2196,6 +2282,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;
@@ -2317,6 +2423,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)
@@ -2950,6 +3061,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))
@@ -3041,6 +3167,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/ASTMatcher.h b/src/libs/3rdparty/cplusplus/ASTMatcher.h
index c243e18b7b..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);
@@ -54,6 +55,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 +141,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 +159,8 @@ 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);
virtual bool match(SimpleNameAST *node, SimpleNameAST *pattern);
@@ -173,6 +178,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);
@@ -183,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 2248f51a50..5b0ef3ce33 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)) {
@@ -546,6 +563,7 @@ void ForStatementAST::accept0(ASTVisitor *visitor)
void IfStatementAST::accept0(ASTVisitor *visitor)
{
if (visitor->visit(this)) {
+ accept(initStmt, visitor);
accept(condition, visitor);
accept(statement, visitor);
accept(else_statement, visitor);
@@ -941,11 +959,36 @@ void TemplateDeclarationAST::accept0(ASTVisitor *visitor)
{
if (visitor->visit(this)) {
accept(template_parameter_list, visitor);
+ accept(requiresClause, visitor);
accept(declaration, 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 RequiresClauseAST::accept0(ASTVisitor *visitor)
+{
+ if (visitor->visit(this))
+ accept(constraint, visitor);
+ visitor->endVisit(this);
+}
+
void ThrowExpressionAST::accept0(ASTVisitor *visitor)
{
if (visitor->visit(this)) {
@@ -954,6 +997,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)) {
@@ -1009,6 +1066,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);
@@ -1260,6 +1318,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);
}
@@ -1297,6 +1358,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/ASTVisitor.h b/src/libs/3rdparty/cplusplus/ASTVisitor.h
index 691b57e9ac..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; }
@@ -96,6 +97,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 +183,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 +201,8 @@ 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(RequiresClauseAST *) { return true; }
virtual bool visit(ReturnStatementAST *) { return true; }
virtual bool visit(SimpleDeclarationAST *) { return true; }
virtual bool visit(SimpleNameAST *) { return true; }
@@ -215,6 +220,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; }
@@ -225,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 *) {}
@@ -235,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 *) {}
@@ -250,6 +258,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 +344,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 +362,8 @@ public:
virtual void endVisit(QualifiedNameAST *) {}
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 *) {}
@@ -369,6 +381,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 *) {}
@@ -379,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 4b31aaf590..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;
@@ -146,6 +147,7 @@ class OperatorAST;
class OperatorFunctionIdAST;
class ParameterDeclarationAST;
class ParameterDeclarationClauseAST;
+class PlaceholderTypeSpecifierAST;
class PointerAST;
class PointerLiteralAST;
class PointerToMemberAST;
@@ -166,6 +168,8 @@ class QtPropertyDeclarationItemAST;
class QualifiedNameAST;
class RangeBasedForStatementAST;
class ReferenceAST;
+class RequiresClauseAST;
+class RequiresExpressionAST;
class ReturnStatementAST;
class SimpleDeclarationAST;
class SimpleNameAST;
@@ -178,6 +182,7 @@ class StdAttributeSpecifierAST;
class StringLiteralAST;
class SwitchStatementAST;
class TemplateDeclarationAST;
+class ConceptDeclarationAST;
class TemplateIdAST;
class TemplateTypeParameterAST;
class ThisExpressionAST;
@@ -185,6 +190,7 @@ class ThrowExpressionAST;
class TrailingReturnTypeAST;
class TranslationUnitAST;
class TryBlockStatementAST;
+class TypeConstraintAST;
class TypeConstructorCallAST;
class TypeIdAST;
class TypeidExpressionAST;
@@ -195,6 +201,7 @@ class UnaryExpressionAST;
class UsingAST;
class UsingDirectiveAST;
class WhileStatementAST;
+class YieldExpressionAST;
typedef List<ExpressionAST *> ExpressionListAST;
typedef List<DeclarationAST *> DeclarationListAST;
diff --git a/src/libs/3rdparty/cplusplus/Bind.cpp b/src/libs/3rdparty/cplusplus/Bind.cpp
index 7f10c79428..c85d401c49 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)
@@ -1523,6 +1528,8 @@ bool Bind::visit(IfStatementAST *ast)
ast->symbol = block;
Scope *previousScope = switchScope(block);
+ if (ast->initStmt)
+ this->statement(ast->initStmt);
/*ExpressionTy condition =*/ this->expression(ast->condition);
this->statement(ast->statement);
this->statement(ast->else_statement);
@@ -2527,6 +2534,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 3947e57026..867ed7baaa 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;
@@ -229,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/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/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/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()
diff --git a/src/libs/3rdparty/cplusplus/Parser.cpp b/src/libs/3rdparty/cplusplus/Parser.cpp
index cb7bfa3a1b..6bf19f5982 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:
@@ -1247,6 +1248,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()) {
@@ -1255,6 +1258,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 +1270,205 @@ 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)
+{
+ if (!_languageFeatures.cxx20Enabled)
+ return false;
+ NestedNameSpecifierListAST *nestedName = nullptr;
+ parseNestedNameSpecifierOpt(nestedName, true);
+ NameAST *conceptName = nullptr;
+ if (!parseUnqualifiedName(conceptName, false))
+ return false;
+ const auto typeConstraint = new (_pool) TypeConstraintAST;
+ typeConstraint->nestedName = nestedName;
+ typeConstraint->conceptName = conceptName;
+ if (LA() != T_LESS) {
+ node = typeConstraint;
+ 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::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 (!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;
+}
+
+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 +1704,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 +1762,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 +1907,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;
}
@@ -2012,8 +2227,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();
@@ -2022,20 +2237,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()
@@ -2071,10 +2287,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)
@@ -2819,6 +3034,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;
}
@@ -3358,6 +3575,7 @@ bool Parser::parseStatement(StatementAST *&node, bool blockLabeledStatement)
return parseGotoStatement(node);
case T_RETURN:
+ case T_CO_RETURN:
return parseReturnStatement(node);
case T_LBRACE:
@@ -3468,7 +3686,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)
@@ -3854,6 +4072,31 @@ 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);
+ 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);
+ }
+ _translationUnit->blockErrors(savedBlockErrors);
+ }
parseCondition(ast->condition);
match(T_RPAREN, &ast->rparen_token);
if (! parseStatement(ast->statement))
@@ -4731,6 +4974,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)) {
@@ -5436,6 +5682,11 @@ bool Parser::parseUnaryExpression(ExpressionAST *&node)
return parseNoExceptOperatorExpression(node);
}
+ case T_CO_AWAIT:
+ if (!_languageFeatures.cxx20Enabled)
+ break;
+ return parseAwaitExpression(node);
+
default:
break;
} // switch
@@ -5694,6 +5945,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)
}
@@ -5822,6 +6075,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();
@@ -6757,6 +7038,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;
@@ -6781,7 +7071,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;
}
@@ -6897,6 +7189,7 @@ bool Parser::parseLambdaDeclarator(LambdaDeclaratorAST *&node)
parseExceptionSpecification(ast->exception_specification);
parseTrailingReturnType(ast->trailing_return_type);
+ parseRequiresClauseOpt(ast->requiresClause);
node = ast;
return true;
diff --git a/src/libs/3rdparty/cplusplus/Parser.h b/src/libs/3rdparty/cplusplus/Parser.h
index ba101ccd46..e34a4ff522 100644
--- a/src/libs/3rdparty/cplusplus/Parser.h
+++ b/src/libs/3rdparty/cplusplus/Parser.h
@@ -143,9 +143,17 @@ 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 parseRequiresClauseOpt(RequiresClauseAST *&node);
+ bool parseRequiresExpression(ExpressionAST *&node);
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);
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/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..f853d29c77 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,
@@ -447,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;
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/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..cb18b33206
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/conptyprocess.cpp
@@ -0,0 +1,343 @@
+#include "conptyprocess.h"
+#include <QFile>
+#include <QFileInfo>
+#include <QThread>
+#include <sstream>
+#include <QTimer>
+#include <QMutexLocker>
+#include <QCoreApplication>
+#include <QWinEventNotifier>
+
+#include <qt_windows.h>
+
+#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<LPPROC_THREAD_ATTRIBUTE_LIST>(
+ 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;
+}
+
+Q_DECLARE_METATYPE(HANDLE)
+
+ConPtyProcess::ConPtyProcess()
+ : IPtyProcess()
+ , m_ptyHandler { INVALID_HANDLE_VALUE }
+ , m_hPipeIn { INVALID_HANDLE_VALUE }
+ , m_hPipeOut { INVALID_HANDLE_VALUE }
+ , m_readThread(nullptr)
+{
+ qRegisterMetaType<HANDLE>("HANDLE");
+}
+
+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_shellPath.replace('/', '\\');
+ m_size = QPair<qint16, qint16>(cols, rows);
+
+ //env
+ 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())};
+
+ 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 = 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) {
+ 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
+ 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);
+ }, Qt::QueuedConnection);
+
+ //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;
+ }
+
+ CancelIoEx(m_hPipeIn, nullptr);
+ });
+
+ //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<qint16, qint16>(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);
+
+ 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;
+
+ 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);
+
+ // 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..d4ffd62b7e
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/conptyprocess.h
@@ -0,0 +1,165 @@
+#ifndef CONPTYPROCESS_H
+#define CONPTYPROCESS_H
+
+#include "iptyprocess.h"
+#include <windows.h>
+#include <process.h>
+#include <stdio.h>
+
+#include <QIODevice>
+#include <QLibrary>
+#include <QMutex>
+#include <QTimer>
+#include <QThread>
+
+//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
+
+class QWinEventNotifier;
+
+template <typename T>
+std::vector<T> vectorFromString(const std::basic_string<T> &str)
+{
+ return std::vector<T>(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()
+ {
+ 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{INVALID_HANDLE_VALUE};
+ HANDLE m_hPipeIn{INVALID_HANDLE_VALUE}, m_hPipeOut{INVALID_HANDLE_VALUE};
+
+ QThread *m_readThread{nullptr};
+ QMutex m_bufferMutex;
+ PtyBuffer m_buffer;
+ bool m_aboutToDestruct{false};
+ PROCESS_INFORMATION m_shellProcessInformation{};
+ QWinEventNotifier *m_shellCloseWaitNotifier{nullptr};
+ 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 <QDebug>
+#include <QString>
+#include <QThread>
+
+#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<qint16, qint16> 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<qint16, qint16> 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..b3e7aa1b16
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/ptyqt.cpp
@@ -0,0 +1,45 @@
+#include "ptyqt.h"
+#include <utility>
+
+#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() && qgetenv("QTC_USE_WINPTY").isEmpty())
+ 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..8c018daf8c
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/unixptyprocess.cpp
@@ -0,0 +1,374 @@
+#include "unixptyprocess.h"
+#include <QStandardPaths>
+
+#include <errno.h>
+#include <termios.h>
+#if !defined(Q_OS_ANDROID) && !defined(Q_OS_FREEBSD)
+#include <utmpx.h>
+#endif
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <QCoreApplication>
+#include <QFileInfo>
+
+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<qint16, qint16>(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)
+
+ const size_t maxRead = 16 * 1024;
+ static std::array<char, maxRead> 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) {
+ m_exitCode = exitCode;
+ emit m_shellProcess.aboutToClose();
+ m_readMasterNotify->disconnect();
+ });
+
+ 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);
+ if (!m_shellProcess.waitForStarted())
+ return false;
+
+ 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<qint16, qint16>(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) - 1);
+
+ QString device(m_handleSlaveName);
+ if (device.startsWith("/dev/"))
+ device = device.mid(5);
+
+ const auto deviceAsLatin1 = device.toLatin1();
+ const char *d = deviceAsLatin1.constData();
+
+ 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));
+
+ 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 <QProcess>
+#include <QSocketNotifier>
+
+// support for build with MUSL on Alpine Linux
+#ifndef _PATH_UTMPX
+#include <sys/time.h>
+#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..0509bb77c3
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/winptyprocess.cpp
@@ -0,0 +1,278 @@
+#include "winptyprocess.h"
+#include <QFile>
+#include <QFileInfo>
+#include <sstream>
+#include <QCoreApplication>
+#include <QLocalSocket>
+#include <QWinEventNotifier>
+
+#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_shellPath.replace('/', '\\');
+ m_size = QPair<qint16, qint16>(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);
+
+ m_outSocket = new QLocalSocket();
+
+ // Notify when the shell process has been terminated
+ 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);
+ 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->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<qint16, qint16>(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);
+
+ delete m_shellCloseWaitNotifier;
+ m_shellCloseWaitNotifier = nullptr;
+
+ 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..0bfb27c02c
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/winptyprocess.h
@@ -0,0 +1,43 @@
+#ifndef WINPTYPROCESS_H
+#define WINPTYPROCESS_H
+
+#include "iptyprocess.h"
+#include "winpty.h"
+
+class QLocalSocket;
+class QWinEventNotifier;
+
+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};
+ QWinEventNotifier* m_shellCloseWaitNotifier;
+};
+
+#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..232217d9f5
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/CMakeLists.txt
@@ -0,0 +1,18 @@
+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
+ 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
+)
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 <leonerd@leonerd.org.uk>
+
+
+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 <leonerd@leonerd.org.uk>
+
+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..cb16ff2a04
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/include/vterm.h
@@ -0,0 +1,637 @@
+#ifndef __VTERM_H__
+#define __VTERM_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#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 : 3;
+ 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,
+ VTERM_UNDERLINE_DOTTED,
+ VTERM_UNDERLINE_DASHED
+};
+
+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 <stdio.h>
+
+#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 <stdio.h>
+#include <string.h>
+
+#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..891a45cec7
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/src/pen.c
@@ -0,0 +1,613 @@
+#include "vterm_internal.h"
+
+#include <stdio.h>
+
+/**
+ * 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;
+ 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);
+ 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..9d1028e67a
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/src/screen.c
@@ -0,0 +1,1183 @@
+#include "vterm_internal.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#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 : 3;
+ 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..313e746e77
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/src/state.c
@@ -0,0 +1,2315 @@
+#include "vterm_internal.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#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 = 2;
+ 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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+
+/*****************
+ * 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..ad61bff8b0
--- /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 <stdarg.h>
+
+#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:3;
+ 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..18ccb638aa
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/vterm.qbs
@@ -0,0 +1,34 @@
+Project {
+ QtcLibrary {
+ name: "vterm"
+ type: "staticlibrary"
+
+ Depends { name: "cpp" }
+ cpp.includePaths: base.concat("include")
+ cpp.warningLevel: "none"
+
+ 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/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);
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=<path>` 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=<suffix>` to make or `-D VERSION_SUFFIX=<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 <windows.h>
+#include <cassert>
+
+#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 <windows.h>
+#include <stdio.h>
+#include <conio.h>
+#include <io.h>
+#include <cassert>
+
+#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 <windows.h>
+
+#include <cassert>
+#include <cstdio>
+#include <cstdlib>
+
+#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 <windows.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <string>
+#include <vector>
+
+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<std::string> 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 <windows.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <string>
+#include <vector>
+
+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<std::string> 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 <windows.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <vector>
+
+#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<bool> condense(const std::vector<CHAR_INFO> &buf) {
+ std::vector<bool> 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<CHAR_INFO> readBuf(14);
+ const SMALL_RECT readRegion = {0, 0, static_cast<short>(readBuf.size() - 1), 0};
+ SMALL_RECT readRegion2 = readRegion;
+ success = ReadConsoleOutputW(
+ conout, readBuf.data(),
+ {static_cast<short>(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 <ctype.h>
+#include <stdio.h>
+#include <string.h>
+
+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 <windows.h>
+
+#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 <conio.h>
+#include <ctype.h>
+#include <stdio.h>
+
+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 <windows.h>
+
+#include <stdio.h>
+
+#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 <windows.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <wchar.h>
+
+#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<funcName##_t*>((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<std::pair<DWORD, COORD> > readFontTable(
+ XPFontAPI &api, HANDLE conout, DWORD maxCount) {
+ std::vector<std::pair<DWORD, COORD> > 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<std::pair<DWORD, COORD> > 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<unsigned>(first), static_cast<unsigned>(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<unsigned>(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<unsigned>(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 <path>\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 <windows.h>
+
+#include <stdio.h>
+#include <string.h>
+
+#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<short>(info.srWindow.Right - info.srWindow.Left + 1),
+ static_cast<short>(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<short>(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 <windows.h>
+
+#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 <path-to-srvany>\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=<full-path>\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 <windows.h>
+
+#include <assert.h>
+#include <locale.h>
+#include <stdio.h>
+
+#include <iostream>
+
+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 <windows.h>
+#include <stdio.h>
+
+#include <string>
+
+#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 <windows.h>
+#include <stdio.h>
+#include <string.h>
+
+#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<HANDLE>(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 <windows.h>
+
+#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<HANDLE>(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 <stdio.h>
+#include <windows.h>
+
+#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 <windows.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#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 <windows.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#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 <windows.h>
+
+#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 <windows.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string>
+
+#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 <index>\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<BOOL WINAPI(*)(HANDLE, DWORD)>(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<unsigned>(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 <windows.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#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 <stdio.h>
+#include <windows.h>
+
+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 <windows.h>
+#include <stdio.h>
+#include <ctype.h>
+
+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 <windows.h>
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <wchar.h>
+#include <vector>
+#include <string>
+
+#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<SHORT>(x), static_cast<SHORT>(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<SHORT>(x), static_cast<SHORT>(y),
+ static_cast<SHORT>(x + w - 1),
+ static_cast<SHORT>(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<SHORT>(x), static_cast<SHORT>(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<CHAR_INFO> buf(w * h, info);
+ HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE);
+ COORD bufSize = { static_cast<SHORT>(w), static_cast<SHORT>(h) };
+ COORD bufCoord = { 0, 0 };
+ SMALL_RECT writeRegion = {
+ static_cast<SHORT>(x),
+ static_cast<SHORT>(y),
+ static_cast<SHORT>(x + w - 1),
+ static_cast<SHORT>(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<SHORT>(x), static_cast<SHORT>(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<char> 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 <windows.h>
+#include <assert.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <windows.h>
+
+#include <assert.h>
+#include <vector>
+
+#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<SHORT>(pt.X + size.X - 1),
+ static_cast<SHORT>(pt.Y + size.Y - 1)
+ };
+}
+
+static void set(
+ const COORD pt,
+ const COORD size,
+ const std::vector<CHAR_INFO> &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<CHAR_INFO> &data) {
+ set(pt, {static_cast<SHORT>(data.size()), 1}, data);
+}
+
+static void writeAttrsAt(
+ const COORD pt,
+ const std::vector<WORD> &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<wchar_t> &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<wchar_t> &data) {
+ DWORD actual = 0;
+ BOOL ret = WriteConsoleW(
+ GetStdHandle(STD_OUTPUT_HANDLE),
+ data.data(), data.size(), &actual, NULL);
+ assert(ret && actual == data.size());
+}
+
+std::vector<CHAR_INFO> get(
+ const COORD pt,
+ const COORD size) {
+ std::vector<CHAR_INFO> 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<wchar_t> readCharsAt(
+ const COORD pt,
+ int size) {
+ std::vector<wchar_t> 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 <windows.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#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 <termios.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#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 <windows.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <vector>
+#include <string>
+
+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<CHAR_INFO> readBuffer(dataToWrite.size() * 2);
+ COORD bufSize = {static_cast<short>(readBuffer.size()), 1};
+ COORD bufCoord = {0, 0};
+ SMALL_RECT topLeft = {0, 0, static_cast<short>(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 <no server OS yet> enhanced console w/rewrapping
+//
+// (Presumably, Win2K, Vista, and Win2K3 behave the same as XP. conhost.exe
+// was announced as a Win7 feature.)
+//
+
+#include <windows.h>
+#include <assert.h>
+#include <vector>
+
+#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 <stdio.h>
+#include <stdlib.h>
+#include <windows.h>
+
+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 <windows.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wchar.h>
+
+#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 <windows.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#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 <windows.h>
+
+#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 <stdio.h>
+#include <conio.h>
+#include <windows.h>
+
+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 <stdio.h>
+#include <conio.h>
+
+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 <windows.h>
+#include <stdio.h>
+
+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 <windows.h>
+#include <stdio.h>
+
+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 <windows.h>
+#include <string.h>
+#include <stdio.h>
+
+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 <stdio.h>
+#include <windows.h>
+
+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 <windows.h>
+
+#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 <windows.h>
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <string>
+#include <vector>
+
+static std::wstring mbsToWcs(const std::string &s) {
+ const size_t len = mbstowcs(nullptr, s.c_str(), 0);
+ if (len == static_cast<size_t>(-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<std::wstring> 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 <windows.h>
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <wchar.h>
+
+#include <string>
+
+#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 },
+ &region));
+
+ 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..22b15111d4
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/CMakeLists.txt
@@ -0,0 +1,111 @@
+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
+ 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 $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>)
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 <windows.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <algorithm>
+#include <string>
+#include <utility>
+#include <vector>
+
+#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<uint64_t>(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<int64_t>(reinterpret_cast<intptr_t>(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<Terminal> 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<Terminal> 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<char> packetData;
+ packetData.resize(packetSize);
+ const auto amt2 = m_controlPipe->read(packetData.data(), packetSize);
+ ASSERT(amt2 == packetSize);
+ try {
+ ReadBuffer buffer(std::move(packetData));
+ buffer.getRawValue<uint64_t>(); // 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<uint64_t>(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<unsigned int>(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<int32_t>(StartProcessResult::ProcessCreated));
+ reply.putInt64(replyProcess);
+ reply.putInt64(replyThread);
+ } else {
+ reply.putInt32(static_cast<int32_t>(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<DWORD>(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<DWORD>(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<Win32ConsoleBuffer> 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 <windows.h>
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#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<Win32ConsoleBuffer> 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<Scraper> m_primaryScraper;
+ std::unique_ptr<Scraper> m_errorScraper;
+ std::unique_ptr<Win32ConsoleBuffer> 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<ConsoleInput> 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<uint64_t>(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<uint64_t>(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 <windows.h>
+
+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 <windows.h>
+#include <stdio.h>
+#include <string.h>
+#include <wchar.h>
+
+#include <algorithm>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#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<funcName##_t*>((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<std::pair<DWORD, COORD> > readFontTable(
+ XPFontAPI &api, HANDLE conout, DWORD maxCount) {
+ std::vector<std::pair<DWORD, COORD> > 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<std::pair<DWORD, COORD> > 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<unsigned>(first), static_cast<unsigned>(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<unsigned>(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<unsigned>(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<size_t>(-1);
+ std::tuple<int, int> 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<int, int> 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<size_t>(-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<DWORD, COORD> &obj1,
+ const std::pair<DWORD, COORD> &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<unsigned>(fontCount));
+ std::vector<std::pair<DWORD, COORD> > 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<unsigned>(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<unsigned>(GetConsoleCP()),
+ static_cast<unsigned>(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 <windows.h>
+
+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 <stdio.h>
+#include <string.h>
+
+#include <algorithm>
+#include <string>
+
+#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<INPUT_RECORD> 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<INPUT_RECORD> &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<INPUT_RECORD> &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-<character>.
+ //
+ // 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-<Char>");
+ 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<unsigned char>(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<INPUT_RECORD> &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<int>(record.coord.X,
+ m_mouseWindowRect.width() - 1));
+
+ mer.dwMousePosition.Y =
+ m_mouseWindowRect.Top +
+ std::max(0, std::min<int>(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<INPUT_RECORD> &records,
+ const char *charBuffer,
+ const int charLen,
+ const bool terminalAltEscape)
+{
+ const uint32_t codePoint = decodeUtf8(charBuffer);
+ if (codePoint == static_cast<uint32_t>(-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<true, uint8_t>(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-<Key> 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<INPUT_RECORD> &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<INPUT_RECORD> &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<INPUT_RECORD> &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 <windows.h>
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#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<INPUT_RECORD> &records);
+ int scanInput(std::vector<INPUT_RECORD> &records,
+ const char *input,
+ int inputSize,
+ bool isEof);
+ int scanMouseInput(std::vector<INPUT_RECORD> &records,
+ const char *input,
+ int inputSize);
+ void appendUtf8Char(std::vector<INPUT_RECORD> &records,
+ const char *charBuffer,
+ int charLen,
+ bool terminalAltEscape);
+ void appendKeyPress(std::vector<INPUT_RECORD> &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<INPUT_RECORD> &records,
+ BOOL keyDown,
+ uint16_t virtualKey,
+ uint32_t codePoint,
+ uint16_t keyState);
+ static void appendInputRecord(std::vector<INPUT_RECORD> &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<INPUT_RECORD> &out, wchar_t ch) {
+ ConsoleInput::appendInputRecord(out, TRUE, 0, ch, 0);
+}
+
+} // anonymous namespace
+
+void reencodeEscapedKeyPress(
+ std::vector<INPUT_RECORD> &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 <windows.h>
+
+#include <stdint.h>
+
+#include <vector>
+
+void reencodeEscapedKeyPress(
+ std::vector<INPUT_RECORD> &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 <algorithm>
+
+#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<int>(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<int>(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<int>(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 <windows.h>
+
+#include <vector>
+
+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<CHAR_INFO> 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 <windows.h>
+
+#include <string>
+
+#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 <windows.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <string>
+
+#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 <size_t n>
+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<unsigned int>(origConsoleMode),
+ static_cast<unsigned int>(newConsoleMode),
+ static_cast<unsigned int>(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<uint16_t>(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 <windows.h>
+
+#include <string>
+
+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 <windows.h>
+#include <string.h>
+
+#include <algorithm>
+
+#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 <prefix> <suffix>
+// - kSemiMod: expands to: ESC <prefix> <numid> ; <mod> <suffix>
+// - kBareMod: expands to: ESC <prefix> <mod> <suffix>
+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 <mod> <letter>` 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-<letter/digit> 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 <bool is_numeric>
+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 <bool is_numeric>
+static inline void expandEncoding(const ExpandContext &ctx) {
+ if (ctx.e.alt_prefix_allowed) {
+ // For better or for worse, this code expands all of:
+ // * ESC [ <key> -- <key>
+ // * ESC ESC [ <key> -- Alt-<key>
+ // * ESC [ 1 ; 3 <key> -- Alt-<key>
+ // * ESC ESC [ 1 ; 3 <key> -- Alt-<key> 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<is_numeric>(ctx, p, LEFT_ALT_PRESSED);
+ }
+ expandEncodingAfterAltPrefix<is_numeric>(ctx, ctx.buffer, 0);
+}
+
+template <bool is_numeric, size_t N>
+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<is_numeric>(ctx);
+ }
+}
+
+} // anonymous namespace
+
+void addDefaultEntriesToInputMap(InputMap &inputMap) {
+ addEscapes<false>(inputMap, escapeLetterEncodings);
+ addEscapes<true>(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 <algorithm>
+
+#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<HANDLE> 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 <vector>
+
+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<NamedPipe*> 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 <windows.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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<char>(virtualKey);
+ } else {
+ winpty_snprintf(buf, "%#x", virtualKey);
+ ret += buf;
+ }
+ if (unicodeChar >= 32 && unicodeChar <= 126) {
+ winpty_snprintf(buf, " ch='%c'",
+ static_cast<char>(unicodeChar));
+ } else {
+ winpty_snprintf(buf, " ch=%#x",
+ static_cast<unsigned int>(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<char>(ctrlChar));
+ } else if (i == ' ') {
+ encoding.append("' '");
+ } else {
+ encoding.push_back(static_cast<char>(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 <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <string>
+
+#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<Node, 256> m_nodePool;
+ SimplePool<Branch, 8> 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<Node*>(getChild(static_cast<const Node&>(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 <stdlib.h>
+
+#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<WORD>(~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 <windows.h>
+#include <stdlib.h>
+
+#include <vector>
+
+#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<CHAR_INFO> 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 <string.h>
+
+#include <algorithm>
+
+#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<HANDLE> *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<size_t>(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<const char*>(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<char*>(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 <windows.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#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<HANDLE> *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<InputWorker> m_inputWorker;
+ std::unique_ptr<OutputWorker> 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 <windows.h>
+
+#include <stdint.h>
+
+#include <algorithm>
+#include <utility>
+
+#include "../shared/WinptyAssert.h"
+#include "../shared/winpty_snprintf.h"
+
+#include "ConsoleFont.h"
+#include "Win32Console.h"
+#include "Win32ConsoleBuffer.h"
+
+namespace {
+
+template <typename T>
+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> 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<int>(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<short>(cols, largest.X);
+ const short visibleRows = std::min<short>(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<int>(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<int>(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<SHORT>(std::min(windowRect.width(), m_ptySize.X),
+ MAX_CONSOLE_WIDTH),
+ std::min<SHORT>(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<int>(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<int>(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<SHORT>(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 <windows.h>
+
+#include <stdint.h>
+
+#include <memory>
+#include <vector>
+
+#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> 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<Terminal> 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<ConsoleLine> 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 <stdlib.h>
+
+#include <vector>
+
+#include "../shared/WinptyAssert.h"
+
+template <typename T, size_t chunkSize>
+class SimplePool {
+public:
+ ~SimplePool();
+ T *alloc();
+ void clear();
+private:
+ struct Chunk {
+ size_t count;
+ T *data;
+ };
+ std::vector<Chunk> m_chunks;
+};
+
+template <typename T, size_t chunkSize>
+SimplePool<T, chunkSize>::~SimplePool() {
+ clear();
+}
+
+template <typename T, size_t chunkSize>
+void SimplePool<T, chunkSize>::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 <typename T, size_t chunkSize>
+T *SimplePool<T, chunkSize>::alloc() {
+ if (m_chunks.empty() || m_chunks.back().count == chunkSize) {
+ T *newData = reinterpret_cast<T*>(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 <windows.h>
+
+#include <algorithm>
+#include <string>
+
+#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 <windows.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <string>
+
+#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<size_t>(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<size_t>(width) >= m_lineData.size();
+ } else {
+ okWidth = static_cast<size_t>(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<unsigned int>(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 <windows.h>
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#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<CHAR_INFO> 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 <stdint.h>
+
+// 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<uint32_t>(-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 <windows.h>
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+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<uint8_t>(buf[0]),
+ static_cast<uint8_t>(buf[1]),
+ static_cast<uint8_t>(buf[2]),
+ static_cast<uint8_t>(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<uint32_t>(-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 <windows.h>
+#include <wchar.h>
+
+#include <string>
+
+#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 <windows.h>
+
+#include <string>
+#include <vector>
+
+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<wchar_t> 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 <windows.h>
+
+#include "../shared/DebugClient.h"
+#include "../shared/StringBuilder.h"
+#include "../shared/WinptyAssert.h"
+
+std::unique_ptr<Win32ConsoleBuffer> Win32ConsoleBuffer::openStdout() {
+ return std::unique_ptr<Win32ConsoleBuffer>(
+ new Win32ConsoleBuffer(GetStdHandle(STD_OUTPUT_HANDLE), false));
+}
+
+std::unique_ptr<Win32ConsoleBuffer> 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<Win32ConsoleBuffer>(
+ new Win32ConsoleBuffer(conout, true));
+}
+
+std::unique_ptr<Win32ConsoleBuffer> 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<Win32ConsoleBuffer>(
+ 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<int>(actual) != width * count) {
+ trace("FillConsoleOutputCharacterW failed");
+ }
+ if (!FillConsoleOutputAttribute(
+ m_conout, kDefaultAttributes, width * count, Coord(0, row),
+ &actual) || static_cast<int>(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 <windows.h>
+
+#include <string.h>
+
+#include <memory>
+
+#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<Win32ConsoleBuffer> openStdout();
+ static std::unique_ptr<Win32ConsoleBuffer> openConout();
+ static std::unique_ptr<Win32ConsoleBuffer> 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#include <wchar.h>
+#include <shellapi.h>
+
+#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 <cstdio>
+#include <cstdlib>
+
+#include <windows.h>
+
+#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<unsigned>(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 <windows.h>
+
+#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 <windows.h>
+
+#include <string>
+
+#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<LPCWSTR>(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 <string>
+
+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 <memory>
+#include <string>
+
+class LibWinptyException : public WinptyException {
+public:
+ LibWinptyException(winpty_result_t code, const wchar_t *what) :
+ m_code(code), m_what(std::make_shared<std::wstring>(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<std::wstring> 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<std::wstring> 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 <memory>
+#include <string>
+
+#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<std::wstring> *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 <windows.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <limits>
+#include <string>
+#include <vector>
+
+#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<winpty_error_ptr_t>(&kBadRpcPacket);
+ } catch (const LibWinptyException &e) {
+ std::unique_ptr<winpty_error_t> obj(new winpty_error_t);
+ obj->code = e.code();
+ obj->msgStatic = nullptr;
+ obj->msgDynamic =
+ new std::shared_ptr<std::wstring>(e.whatSharedStr());
+ ret = obj.release();
+ } catch (const WinptyException &e) {
+ std::unique_ptr<winpty_error_t> obj(new winpty_error_t);
+ std::shared_ptr<std::wstring> msg(new std::wstring(e.what()));
+ obj->code = WINPTY_ERROR_UNSPECIFIED;
+ obj->msgStatic = nullptr;
+ obj->msgDynamic = new std::shared_ptr<std::wstring>(msg);
+ ret = obj.release();
+ }
+ } catch (const std::bad_alloc&) {
+ ret = const_cast<winpty_error_ptr_t>(&kOutOfMemory);
+ } catch (...) {
+ ret = const_cast<winpty_error_ptr_t>(&kUncaughtException);
+ }
+ trace("libwinpty error: code=%u msg='%s'",
+ static_cast<unsigned>(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<winpty_config_t> 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<BOOL, DWORD> 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<BOOL, DWORD> 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<uint64_t>(0); // Reserve space for size.
+ return packet;
+}
+
+static void writePacket(winpty_t &wp, WriteBuffer &packet) {
+ const auto &buf = packet.buf();
+ packet.replaceRawValue<uint64_t>(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<char*>(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<char> 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 &params,
+ 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<unsigned int>(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<winpty_t>
+createAgentSession(const winpty_config_t *cfg,
+ const std::wstring &desktop,
+ const std::wstring &params,
+ DWORD creationFlags) {
+ std::unique_ptr<winpty_t> 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<winpty_t> &&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<winpty_t> m_wp;
+ std::wstring m_desktopName;
+};
+
+} // anonymous namespace
+
+std::unique_ptr<AgentDesktop>
+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<AgentDesktop>();
+ }
+
+ 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<AgentDesktop>();
+ } else {
+ return std::unique_ptr<AgentDesktop>(
+ new AgentDesktopIndirect(std::move(wp),
+ std::move(desktopName)));
+ }
+ } else {
+ try {
+ BackgroundDesktop desktop;
+ return std::unique_ptr<AgentDesktop>(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<AgentDesktop>();
+ }
+ }
+}
+
+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<winpty_spawn_config_t> 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<HANDLE>(static_cast<intptr_t>(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<Mutex> 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<StartProcessResult>(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<Mutex> 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<Mutex> 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 <memory>
+
+#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<wchar_t[]> 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 <windows.h>
+
+#include <string>
+
+#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 <stdint.h>
+
+#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<const char*>(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<const char*>(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<uint64_t>(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<char*>(data));
+ m_off += len;
+}
+
+int32_t ReadBuffer::getInt32() {
+ READ_BUFFER_CHECK(getRawValue<Piece>() == Piece::Int32);
+ return getRawValue<int32_t>();
+}
+
+int64_t ReadBuffer::getInt64() {
+ READ_BUFFER_CHECK(getRawValue<Piece>() == Piece::Int64);
+ return getRawValue<int64_t>();
+}
+
+std::wstring ReadBuffer::getWString() {
+ READ_BUFFER_CHECK(getRawValue<Piece>() == Piece::WString);
+ const uint64_t charLen = getRawValue<uint64_t>();
+ 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 <stdint.h>
+#include <string.h>
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+#include <string>
+
+#include "WinptyException.h"
+
+class WriteBuffer {
+private:
+ std::vector<char> m_buf;
+
+public:
+ WriteBuffer() {}
+
+ template <typename T> void putRawValue(const T &t) {
+ putRawData(&t, sizeof(t));
+ }
+ template <typename T> 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<char> &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<char> m_buf;
+ size_t m_off = 0;
+
+public:
+ explicit ReadBuffer(std::vector<char> &&buf) : m_buf(std::move(buf)) {}
+
+ template <typename T> 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 <windows.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <algorithm>
+#include <string>
+
+#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<char*>(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<DWORD>(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<const char*>(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 <stdint.h>
+#include <string.h>
+
+#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<RtlGenRandom_t*>(
+ 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<unsigned>(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<unsigned>(GetLastError()));
+ }
+ } else if (m_cryptProvIsValid) {
+ success =
+ CryptGenRandom(m_cryptProv, size,
+ reinterpret_cast<BYTE*>(buffer)) != 0;
+ if (!success) {
+ trace("GenRandom: CryptGenRandom failed, size=%d, lasterror=%u",
+ static_cast<int>(size),
+ static_cast<unsigned>(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<uint8_t>(bytes[i]) >> 4];
+ ret[i * 2 + 1] = hex[static_cast<uint8_t>(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<uint64_t>(monotonicTime.dwHighDateTime) << 32) |
+ static_cast<uint64_t>(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 <windows.h>
+#include <wincrypt.h>
+
+#include <string>
+
+#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 <windows.h>
+
+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 <typename T>
+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 <windows.h>
+
+#include <string>
+
+#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 <windows.h>
+
+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 <windows.h>
+
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+#include <array>
+#include <limits>
+#include <memory>
+#include <new>
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#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 <array>
+#include <string>
+#include <type_traits>
+
+#ifdef STRING_BUILDER_TESTING
+#include <assert.h>
+#define STRING_BUILDER_CHECK(cond) assert(cond)
+#else
+#define STRING_BUILDER_CHECK(cond)
+#endif // STRING_BUILDER_TESTING
+
+#include "WinptyAssert.h"
+
+template <typename C, size_t sz>
+struct ValueString {
+ std::array<C, sz> 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<C> str() const {
+ return std::basic_string<C>(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 <typename C, typename I>
+ValueString<C, sizeof(I) * 3 + 1 + 1> gdecOfInt(const I value) {
+ typedef typename std::make_unsigned<I>::type U;
+ auto unsValue = static_cast<U>(value);
+ const bool isNegative = (value < 0);
+ if (isNegative) {
+ unsValue = STRING_BUILDER_ALLOW_UNSIGNED_NEGATE(-unsValue);
+ }
+ decltype(gdecOfInt<C, I>(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 <typename I> decltype(gdecOfInt<char, I>(0)) decOfInt(I i) {
+ return gdecOfInt<char>(i);
+}
+
+template <typename I> decltype(gdecOfInt<wchar_t, I>(0)) wdecOfInt(I i) {
+ return gdecOfInt<wchar_t>(i);
+}
+
+// Formats an integer as hexadecimal, with or without leading zeros.
+template <typename C, bool leadingZeros=false, typename I>
+ValueString<C, sizeof(I) * 2 + 1> ghexOfInt(const I value) {
+ typedef typename std::make_unsigned<I>::type U;
+ const auto unsValue = static_cast<U>(value);
+ static const C hex[16] = {'0','1','2','3','4','5','6','7',
+ '8','9','a','b','c','d','e','f'};
+ decltype(ghexOfInt<C, leadingZeros, I>(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 <bool leadingZeros=false, typename I>
+decltype(ghexOfInt<char, leadingZeros, I>(0)) hexOfInt(I i) {
+ return ghexOfInt<char, leadingZeros, I>(i);
+}
+
+template <bool leadingZeros=false, typename I>
+decltype(ghexOfInt<wchar_t, leadingZeros, I>(0)) whexOfInt(I i) {
+ return ghexOfInt<wchar_t, leadingZeros, I>(i);
+}
+
+template <typename C>
+class GStringBuilder {
+public:
+ typedef std::basic_string<C> 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 <size_t sz>
+ GStringBuilder &operator<<(const ValueString<C, sz> &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 P>
+ typename std::enable_if<
+ (std::is_same<P, char>::value || std::is_same<P, wchar_t>::value) &&
+ !std::is_same<C, P>::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<C>(i); }
+ GStringBuilder &operator<<(unsigned short i) { return *this << gdecOfInt<C>(i); }
+ GStringBuilder &operator<<(int i) { return *this << gdecOfInt<C>(i); }
+ GStringBuilder &operator<<(unsigned int i) { return *this << gdecOfInt<C>(i); }
+ GStringBuilder &operator<<(long i) { return *this << gdecOfInt<C>(i); }
+ GStringBuilder &operator<<(unsigned long i) { return *this << gdecOfInt<C>(i); }
+ GStringBuilder &operator<<(long long i) { return *this << gdecOfInt<C>(i); }
+ GStringBuilder &operator<<(unsigned long long i) { return *this << gdecOfInt<C>(i); }
+
+ GStringBuilder &operator<<(const void *p) {
+ m_out.push_back(static_cast<C>('0'));
+ m_out.push_back(static_cast<C>('x'));
+ *this << ghexOfInt<C>(reinterpret_cast<uintptr_t>(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<char> StringBuilder;
+typedef GStringBuilder<wchar_t> 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 <stdio.h>
+#include <string.h>
+
+#include <iomanip>
+#include <sstream>
+
+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 <typename C, typename I>
+std::basic_string<C> decOfIntSS(const I value) {
+ // std::to_string and std::to_wstring are missing in Cygwin as of this
+ // writing (early 2016).
+ std::basic_stringstream<C> ss;
+ ss << +value; // We must promote char to print it as an integer.
+ return ss.str();
+}
+
+
+template <typename C, bool leadingZeros=false, typename I>
+std::basic_string<C> hexOfIntSS(const I value) {
+ typedef typename std::make_unsigned<I>::type U;
+ const unsigned long long u64Value = value & static_cast<U>(~0);
+ std::basic_stringstream<C> ss;
+ if (leadingZeros) {
+ ss << std::setfill(static_cast<C>('0')) << std::setw(sizeof(I) * 2);
+ }
+ ss << std::hex << u64Value;
+ return ss.str();
+}
+
+template <typename I>
+void testValue(I value) {
+ CHECK_EQ(decOfInt(value).str(), (decOfIntSS<char>(value)));
+ CHECK_EQ(wdecOfInt(value).str(), (decOfIntSS<wchar_t>(value)));
+ CHECK_EQ((hexOfInt<false>(value).str()), (hexOfIntSS<char, false>(value)));
+ CHECK_EQ((hexOfInt<true>(value).str()), (hexOfIntSS<char, true>(value)));
+ CHECK_EQ((whexOfInt<false>(value).str()), (hexOfIntSS<wchar_t, false>(value)));
+ CHECK_EQ((whexOfInt<true>(value).str()), (hexOfIntSS<wchar_t, true>(value)));
+}
+
+template <typename I>
+void testType() {
+ typedef typename std::make_unsigned<I>::type U;
+ const U quarter = static_cast<U>(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<U>(offset);
+ testValue(value);
+ }
+ }
+ testValue(static_cast<I>(42));
+ testValue(static_cast<I>(123456));
+ testValue(static_cast<I>(0xdeadfacecafebeefull));
+}
+
+int main() {
+ testType<char>();
+
+ testType<signed char>();
+ testType<signed short>();
+ testType<signed int>();
+ testType<signed long>();
+ testType<signed long long>();
+
+ testType<unsigned char>();
+ testType<unsigned short>();
+ testType<unsigned int>();
+ testType<unsigned long>();
+ testType<unsigned long long>();
+
+ StringBuilder() << static_cast<const void*>("TEST");
+ WStringBuilder() << static_cast<const void*>("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 <windows.h>
+
+#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<char> 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 <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#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 <typename T>
+std::vector<T> vectorFromString(const std::basic_string<T> &str) {
+ return std::vector<T>(str.begin(), str.end());
+}
+
+// Return a vector containing each character in the string, followed by a
+// NUL terminator.
+template <typename T>
+std::vector<T> vectorWithNulFromString(const std::basic_string<T> &str) {
+ std::vector<T> 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 <size_t N>
+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 <size_t N>
+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 <windows.h>
+#include <assert.h>
+#include <stdint.h>
+
+class TimeMeasurement {
+public:
+ TimeMeasurement() {
+ static double freq = static_cast<double>(getFrequency());
+ m_freq = freq;
+ m_start = value();
+ }
+
+ double elapsed() {
+ uint64_t elapsedTicks = value() - m_start;
+ return static_cast<double>(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 <array>
+
+#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<HLOCAL>(ptr));
+ }
+ }
+};
+
+typedef std::unique_ptr<void, LocalFreer> PointerLocal;
+
+template <typename T>
+SecurityItem<T> localItem(typename T::type v) {
+ typedef typename T::type P;
+ struct Impl : SecurityItem<T>::Impl {
+ P m_v;
+ Impl(P v) : m_v(v) {}
+ virtual ~Impl() {
+ LocalFree(reinterpret_cast<HLOCAL>(m_v));
+ }
+ };
+ return SecurityItem<T>(v, std::unique_ptr<Impl>(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<Impl>(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<char[]> 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> impl(new Impl);
+ impl->buffer = std::unique_ptr<char[]>(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<char*>(&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<AclTag>(aclRaw);
+ }
+
+ const PSECURITY_DESCRIPTOR sdRaw =
+ reinterpret_cast<PSECURITY_DESCRIPTOR>(
+ LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH));
+ if (sdRaw == nullptr) {
+ throwWinptyException(L"finishSecurityDescriptor: LocalAlloc failed");
+ }
+ SecurityDescriptor sd = localItem<SecurityDescriptorTag>(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<EXPLICIT_ACCESSW, 3> daclEntries = {};
+ Acl dacl;
+ SecurityDescriptor value;
+ };
+
+ std::unique_ptr<Impl> 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<LPWSTR>(impl->localSystem.get());
+ impl->daclEntries[1].Trustee.ptstrName =
+ reinterpret_cast<LPWSTR>(impl->builtinAdmins.get());
+ impl->daclEntries[2].Trustee.ptstrName =
+ reinterpret_cast<LPWSTR>(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<EXPLICIT_ACCESSW, 4> daclEntries = {};
+ Acl dacl;
+ SecurityDescriptor value;
+ };
+
+ std::unique_ptr<Impl> 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<LPWSTR>(impl->localSystem.get());
+ impl->daclEntries[1].Trustee.ptstrName =
+ reinterpret_cast<LPWSTR>(impl->builtinAdmins.get());
+ impl->daclEntries[2].Trustee.ptstrName =
+ reinterpret_cast<LPWSTR>(impl->owner.get());
+ impl->daclEntries[3].Trustee.ptstrName =
+ reinterpret_cast<LPWSTR>(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<SecurityDescriptorTag>(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<funcName##_t*>( \
+ 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<LPWSTR>(str.c_str()),
+ &psid);
+ if (!success) {
+ const auto err = GetLastError();
+ throwWindowsError(
+ (std::wstring(L"ConvertStringSidToSidW failed on \"") +
+ str + L'"').c_str(),
+ err);
+ }
+ return localItem<SidTag>(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<SecurityDescriptorTag>(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_Result, DWORD, DWORD>
+getNamedPipeClientProcessId(HANDLE serverPipe) {
+ OsModule kernel32(L"kernel32.dll");
+ const auto pGetNamedPipeClientProcessId =
+ reinterpret_cast<GetNamedPipeClientProcessId_t*>(
+ 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<DWORD>(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 <windows.h>
+#include <aclapi.h>
+
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+
+// 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 <typename T>
+class SecurityItem {
+public:
+ struct Impl {
+ virtual ~Impl() {}
+ };
+
+private:
+ typedef typename T::type P;
+ P m_v;
+ std::unique_ptr<Impl> 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<Impl> &&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<SidTag> Sid;
+typedef SecurityItem<AclTag> Acl;
+typedef SecurityItem<SecurityDescriptorTag> 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_Result, DWORD, DWORD>
+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 <windows.h>
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <tuple>
+
+#include "DebugClient.h"
+#include "OsModule.h"
+#include "StringBuilder.h"
+#include "StringUtil.h"
+#include "WinptyAssert.h"
+#include "WinptyException.h"
+
+namespace {
+
+typedef std::tuple<DWORD, DWORD> 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<OSVERSIONINFO*>(&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<decltype(name)*>( \
+ 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<char[]> 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<void**>(&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<uint64_t>(info.dwProductVersionMS) << 32) |
+ (static_cast<uint64_t>(info.dwProductVersionLS));
+}
+
+uint64_t fileVersionFromInfo(const VS_FIXEDFILEINFO &info) {
+ return (static_cast<uint64_t>(info.dwFileVersionMS) << 32) |
+ (static_cast<uint64_t>(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<IsWow64Process_t*>(
+ 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 <windows.h>
+#include <stdlib.h>
+
+#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 <assert.h>
+#include <stdlib.h>
+#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 <memory>
+#include <string>
+
+#include "StringBuilder.h"
+
+namespace {
+
+class ExceptionImpl : public WinptyException {
+public:
+ ExceptionImpl(const wchar_t *what) :
+ m_what(std::make_shared<std::wstring>(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<std::wstring> 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 <windows.h>
+
+#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 <stdio.h>
+#include <string.h>
+
+#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 <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#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<size_t>(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 <size_t size>
+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 <size_t size>
+int winpty_snprintf(char (&out)[size], const char *fmt, ...)
+ WINPTY_SNPRINTF_FORMAT(2, 3);
+template <size_t size>
+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 <windows.h>
+
+#include <cassert>
+#include <cctype>
+#include <cstdio>
+#include <cstdlib>
+#include <cwchar>
+#include <vector>
+
+#include "../include/winpty.h"
+#include "../shared/DebugClient.h"
+
+static std::vector<unsigned char> filterContent(
+ const std::vector<unsigned char> &content) {
+ std::vector<unsigned char> 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<unsigned char> readAll(HANDLE handle) {
+ unsigned char buf[1024];
+ std::vector<unsigned char> 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<unsigned char> 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 <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <sys/select.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <vector>
+
+#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<char> 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<DWORD>(numRead)) {
+ if (!ret && GetLastError() == ERROR_BROKEN_PIPE) {
+ trace("InputHandler: pipe closed: written=%u",
+ static_cast<unsigned int>(written));
+ } else {
+ trace("InputHandler: write failed: "
+ "ret=%d lastError=0x%x numRead=%d written=%u",
+ ret,
+ static_cast<unsigned int>(GetLastError()),
+ numRead,
+ static_cast<unsigned int>(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 <windows.h>
+#include <pthread.h>
+#include <signal.h>
+
+#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<InputHandler*>(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 <assert.h>
+#include <errno.h>
+#include <sys/select.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <vector>
+
+#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<char> 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<unsigned int>(numRead));
+ } else {
+ trace("OutputHandler: read failed: "
+ "ret=%d lastError=0x%x numRead=%u",
+ ret,
+ static_cast<unsigned int>(GetLastError()),
+ static_cast<unsigned int>(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 <windows.h>
+#include <pthread.h>
+#include <signal.h>
+
+#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<OutputHandler*>(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 <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#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<const char*>(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<unsigned int>(size),
+ static_cast<unsigned int>(written),
+ ret);
+ return false;
+ }
+ assert(static_cast<size_t>(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 <stdlib.h>
+#include <sys/select.h>
+
+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 <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+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 <windows.h>
+
+#include <assert.h>
+#include <cygwin/version.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
+#include <sys/cygwin.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <winpty.h>
+#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 <size_t N>
+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<std::string> &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<std::string, std::string> 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<std::string, std::string>::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<std::string> 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] : "<program>";
+ 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<wchar_t*>(&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<unsigned int>(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=<ver> to gyp. <ver> 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%': '<!(cmd /c "cd shared && GetCommitHash.bat")',
+ },
+ 'target_defaults' : {
+ 'defines' : [
+ 'UNICODE',
+ '_UNICODE',
+ '_WIN32_WINNT=0x0501',
+ 'NOMINMAX',
+ ],
+ 'include_dirs': [
+ # Add the 'src/gen' directory to the include path and force gyp to
+ # run the script (re)generating the version header.
+ '<!(cmd /c "cd shared && UpdateGenVersion.bat <(WINPTY_COMMIT_HASH)")',
+ ],
+ },
+ 'targets' : [
+ {
+ 'target_name' : 'winpty-agent',
+ 'type' : 'executable',
+ 'include_dirs' : [
+ 'include',
+ ],
+ 'defines' : [
+ 'WINPTY_AGENT_ASSERT',
+ ],
+ 'libraries' : [
+ '-ladvapi32',
+ '-lshell32',
+ '-luser32',
+ ],
+ 'msvs_settings': {
+ # Specify this setting here to override a setting from somewhere
+ # else, such as node's common.gypi.
+ 'VCCLCompilerTool': {
+ 'ExceptionHandling': '1', # /EHsc
+ },
+ },
+ '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/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',
+ ],
+ },
+ {
+ 'target_name' : 'winpty',
+ 'type' : 'shared_library',
+ 'include_dirs' : [
+ 'include',
+ ],
+ 'defines' : [
+ 'COMPILING_WINPTY_DLL',
+ ],
+ 'libraries' : [
+ '-ladvapi32',
+ '-luser32',
+ ],
+ 'msvs_settings': {
+ # Specify this setting here to override a setting from somewhere
+ # else, such as node's common.gypi.
+ 'VCCLCompilerTool': {
+ 'ExceptionHandling': '1', # /EHsc
+ },
+ },
+ 'sources' : [
+ 'include/winpty.h',
+ 'libwinpty/AgentLocation.cc',
+ 'libwinpty/AgentLocation.h',
+ 'libwinpty/winpty.cc',
+ '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/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',
+ ],
+ },
+ {
+ 'target_name' : 'winpty-debugserver',
+ 'type' : 'executable',
+ 'msvs_settings': {
+ # Specify this setting here to override a setting from somewhere
+ # else, such as node's common.gypi.
+ 'VCCLCompilerTool': {
+ 'ExceptionHandling': '1', # /EHsc
+ },
+ },
+ 'sources' : [
+ 'debugserver/DebugServer.cc',
+ 'shared/DebugClient.h',
+ 'shared/DebugClient.cc',
+ 'shared/OwnedHandle.h',
+ 'shared/OwnedHandle.cc',
+ 'shared/OsModule.h',
+ 'shared/StringBuilder.h',
+ 'shared/StringUtil.cc',
+ 'shared/StringUtil.h',
+ 'shared/WindowsSecurity.h',
+ 'shared/WindowsSecurity.cc',
+ 'shared/WindowsVersion.h',
+ 'shared/WindowsVersion.cc',
+ 'shared/WinptyAssert.h',
+ 'shared/WinptyAssert.cc',
+ 'shared/WinptyException.h',
+ 'shared/WinptyException.cc',
+ 'shared/winpty_snprintf.h',
+ ],
+ 'libraries' : [
+ '-ladvapi32',
+ ],
+ }
+ ],
+}
diff --git a/src/libs/3rdparty/winpty/vcbuild.bat b/src/libs/3rdparty/winpty/vcbuild.bat
new file mode 100644
index 0000000000..f3787a20f1
--- /dev/null
+++ b/src/libs/3rdparty/winpty/vcbuild.bat
@@ -0,0 +1,83 @@
+@echo off
+
+REM -- Script requirements:
+REM --
+REM -- * git This program must be in the Path to check out
+REM -- build-gyp. If that directory already exists, then
+REM -- git isn't necessary, but if it is missing, no
+REM -- commit hash will be embedded into binaries.
+REM --
+REM -- * python A non-Cygwin Python 2 python.exe must be in the
+REM -- Path to run gyp.
+REM --
+REM -- * msbuild msbuild must be in the Path. It is probably
+REM -- important to have msbuild from the correct MSVC
+REM -- release.
+REM --
+REM -- The script's output binaries are in the src/Release/{Win32,x64}
+REM -- directory.
+
+REM -------------------------------------------------------------------------
+REM -- Parse arguments
+
+setlocal
+cd %~dp0
+set GYP_ARGS=
+set MSVC_PLATFORM=x64
+
+:ParamLoop
+if "%1" == "" goto :ParamDone
+if "%1" == "--msvc-platform" (
+ REM -- One of Win32 or x64.
+ set MSVC_PLATFORM=%2
+ shift && shift
+ goto :ParamLoop
+)
+if "%1" == "--gyp-msvs-version" (
+ set GYP_ARGS=%GYP_ARGS% -G msvs_version=%2
+ shift && shift
+ goto :ParamLoop
+)
+if "%1" == "--toolset" (
+ set GYP_ARGS=%GYP_ARGS% -D WINPTY_MSBUILD_TOOLSET=%2
+ shift && shift
+ goto :ParamLoop
+)
+if "%1" == "--commit-hash" (
+ set GYP_ARGS=%GYP_ARGS% -D WINPTY_COMMIT_HASH=%2
+ shift && shift
+ goto :ParamLoop
+)
+echo error: Unrecognized argument: %1
+exit /b 1
+:ParamDone
+
+REM -------------------------------------------------------------------------
+REM -- Check out GYP. GYP doesn't seem to have releases, so just use the
+REM -- current master commit.
+
+if not exist build-gyp (
+ git clone https://chromium.googlesource.com/external/gyp build-gyp || (
+ echo error: GYP clone failed
+ exit /b 1
+ )
+)
+
+REM -------------------------------------------------------------------------
+REM -- Run gyp to generate MSVC project files.
+
+cd src
+
+call ..\build-gyp\gyp.bat winpty.gyp -I configurations.gypi %GYP_ARGS%
+if errorlevel 1 (
+ echo error: GYP failed
+ exit /b 1
+)
+
+REM -------------------------------------------------------------------------
+REM -- Compile the project.
+
+msbuild winpty.sln /m /p:Platform=%MSVC_PLATFORM% || (
+ echo error: msbuild failed
+ exit /b 1
+)
diff --git a/src/libs/3rdparty/winpty/winpty.qbs b/src/libs/3rdparty/winpty/winpty.qbs
new file mode 100644
index 0000000000..63d7611364
--- /dev/null
+++ b/src/libs/3rdparty/winpty/winpty.qbs
@@ -0,0 +1,207 @@
+import qbs
+import qbs.TextFile
+
+Project {
+ name: "Winpty"
+ condition: qbs.targetOS.contains("windows")
+
+
+ Product {
+ name: "winpty_genversion_header"
+ type: "hpp"
+
+ Group {
+ files: "VERSION.txt"
+ fileTags: "txt.in"
+ }
+
+ Rule {
+ inputs: "txt.in"
+ Artifact {
+ filePath: "GenVersion.h"
+ fileTags: "hpp"
+ }
+ prepare: {
+ var cmd = new JavaScriptCommand();
+ cmd.description = "generating GenVersion.h";
+ cmd.highlight = "codegen";
+ cmd.sourceCode = function() {
+ var inFile = new TextFile(input.filePath);
+ var versionTxt = inFile.readAll();
+ inFile.close();
+ // remove any line endings
+ versionTxt = versionTxt.replace(/[\r\n]/g, "");
+
+ var content = 'const char GenVersion_Version[] = "@VERSION@";\n'
+ + 'const char GenVersion_Commit[] = "@COMMIT_HASH@";\n';
+ content = content.replace(/@VERSION@/g, versionTxt);
+
+ var outFile = new TextFile(output.filePath, TextFile.WriteOnly);
+ outFile.truncate();
+ outFile.write(content);
+ outFile.close();
+ }
+ return cmd;
+ }
+ }
+
+ Export {
+ Depends { name: "cpp" }
+ cpp.includePaths: exportingProduct.buildDirectory
+ }
+ }
+
+ QtcTool {
+ name: "winpty-agent"
+ Depends { name: "winpty_genversion_header" }
+ Depends { name: "cpp" }
+
+ useNonGuiPchFile: false
+ useGuiPchFile: false
+
+ cpp.includePaths: base.concat([sourceDirectory + "/include", buildDirectory])
+ cpp.defines: base.concat(["WINPTY_AGENT_ASSERT",
+ "NOMINMAX", "UNICODE", "_UNICODE"
+ ])
+ cpp.dynamicLibraries: ["user32", "shell32", "advapi32"]
+
+ files: [
+ "src/agent/Agent.h",
+ "src/agent/Agent.cc",
+ "src/agent/AgentCreateDesktop.h",
+ "src/agent/AgentCreateDesktop.cc",
+ "src/agent/ConsoleFont.cc",
+ "src/agent/ConsoleFont.h",
+ "src/agent/ConsoleInput.cc",
+ "src/agent/ConsoleInput.h",
+ "src/agent/ConsoleInputReencoding.cc",
+ "src/agent/ConsoleInputReencoding.h",
+ "src/agent/ConsoleLine.cc",
+ "src/agent/ConsoleLine.h",
+ "src/agent/Coord.h",
+ "src/agent/DebugShowInput.h",
+ "src/agent/DebugShowInput.cc",
+ "src/agent/DefaultInputMap.h",
+ "src/agent/DefaultInputMap.cc",
+ "src/agent/DsrSender.h",
+ "src/agent/EventLoop.h",
+ "src/agent/EventLoop.cc",
+ "src/agent/InputMap.h",
+ "src/agent/InputMap.cc",
+ "src/agent/LargeConsoleRead.h",
+ "src/agent/LargeConsoleRead.cc",
+ "src/agent/NamedPipe.h",
+ "src/agent/NamedPipe.cc",
+ "src/agent/Scraper.h",
+ "src/agent/Scraper.cc",
+ "src/agent/SimplePool.h",
+ "src/agent/SmallRect.h",
+ "src/agent/Terminal.h",
+ "src/agent/Terminal.cc",
+ "src/agent/UnicodeEncoding.h",
+ "src/agent/Win32Console.cc",
+ "src/agent/Win32Console.h",
+ "src/agent/Win32ConsoleBuffer.cc",
+ "src/agent/Win32ConsoleBuffer.h",
+ "src/agent/main.cc",
+ ]
+
+ Group {
+ name: "Shared sources"
+ prefix: "src/shared/"
+ files: [
+ "AgentMsg.h",
+ "BackgroundDesktop.h",
+ "BackgroundDesktop.cc",
+ "Buffer.h",
+ "Buffer.cc",
+ "DebugClient.h",
+ "DebugClient.cc",
+ "GenRandom.h",
+ "GenRandom.cc",
+ "OsModule.h",
+ "OwnedHandle.h",
+ "OwnedHandle.cc",
+ "StringBuilder.h",
+ "StringUtil.cc",
+ "StringUtil.h",
+ "UnixCtrlChars.h",
+ "WindowsSecurity.cc",
+ "WindowsSecurity.h",
+ "WindowsVersion.h",
+ "WindowsVersion.cc",
+ "WinptyAssert.h",
+ "WinptyAssert.cc",
+ "WinptyException.h",
+ "WinptyException.cc",
+ "WinptyVersion.h",
+ "WinptyVersion.cc",
+ "winpty_snprintf.h",
+ ]
+ }
+ }
+
+ QtcLibrary {
+ name: "winpty"
+ type: "staticlibrary"
+
+ Depends { name: "winpty_genversion_header" }
+ Depends { name: "cpp" }
+
+ useNonGuiPchFile: false
+ useGuiPchFile: false
+
+ cpp.defines: base.concat(["COMPILING_WINPTY_DLL",
+ "NOMINMAX", "UNICODE", "_UNICODE"
+ ])
+ cpp.dynamicLibraries: ["user32", "shell32", "advapi32"]
+ cpp.includePaths: base.concat(sourceDirectory + "/src/include")
+
+
+ files: [
+ "src/libwinpty/AgentLocation.cc",
+ "src/libwinpty/AgentLocation.h",
+ "src/libwinpty/winpty.cc",
+ ]
+
+ Group {
+ name: "Shared sources" // FIXME duplication
+ prefix: "src/shared/"
+ files: [
+ "AgentMsg.h",
+ "BackgroundDesktop.h",
+ "BackgroundDesktop.cc",
+ "Buffer.h",
+ "Buffer.cc",
+ "DebugClient.h",
+ "DebugClient.cc",
+ "GenRandom.h",
+ "GenRandom.cc",
+ "OsModule.h",
+ "OwnedHandle.h",
+ "OwnedHandle.cc",
+ "StringBuilder.h",
+ "StringUtil.cc",
+ "StringUtil.h",
+ "UnixCtrlChars.h",
+ "WindowsSecurity.cc",
+ "WindowsSecurity.h",
+ "WindowsVersion.h",
+ "WindowsVersion.cc",
+ "WinptyAssert.h",
+ "WinptyAssert.cc",
+ "WinptyException.h",
+ "WinptyException.cc",
+ "WinptyVersion.h",
+ "WinptyVersion.cc",
+ "winpty_snprintf.h",
+ ]
+ }
+
+ Export {
+ Depends { name: "cpp" }
+ cpp.defines: base.concat("COMPILING_WINPTY_DLL")
+ cpp.includePaths: base.concat(exportingProduct.sourceDirectory + "/src/include")
+ }
+ }
+}
diff --git a/src/libs/CMakeLists.txt b/src/libs/CMakeLists.txt
index b5410655f2..de2d2b01ef 100644
--- a/src/libs/CMakeLists.txt
+++ b/src/libs/CMakeLists.txt
@@ -2,24 +2,25 @@ 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(qmljs)
+add_subdirectory(qtcreatorcdbext)
+add_subdirectory(utils)
+add_subdirectory(solutions)
+add_subdirectory(tracing)
+
if (WITH_QMLDESIGNER)
add_subdirectory(sqlite)
add_subdirectory(qmlpuppetcommunication)
endif()
-add_subdirectory(tracing)
-
-add_subdirectory(qtcreatorcdbext)
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/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 <QLoggingCategory>
+
+Q_DECLARE_LOGGING_CATEGORY(adsLog)
diff --git a/src/libs/advanceddockingsystem/advanceddockingsystem.qbs b/src/libs/advanceddockingsystem/advanceddockingsystem.qbs
index 9778f62949..8d758b3f7e 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,7 +31,9 @@ QtcLibrary {
"floatingdockcontainer.cpp", "floatingdockcontainer.h",
"floatingdragpreview.cpp", "floatingdragpreview.h",
"iconprovider.cpp", "iconprovider.h",
+ "workspace.cpp", "workspace.h",
"workspacedialog.cpp", "workspacedialog.h",
+ "workspaceinputdialog.cpp", "workspaceinputdialog.h",
"workspacemodel.cpp", "workspacemodel.h",
"workspaceview.cpp", "workspaceview.h",
]
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 <iostream>
-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 5e45c2cf90..02c83489fa 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"
@@ -28,8 +29,6 @@
#include <iostream>
-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 <iostream>
-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 2cf073a94f..51ced58096 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 <functional>
#include <iostream>
-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 44d3ce89f9..1b2a1c322a 100644
--- a/src/libs/advanceddockingsystem/dockmanager.cpp
+++ b/src/libs/advanceddockingsystem/dockmanager.cpp
@@ -5,6 +5,7 @@
#include "ads_globals.h"
#include "advanceddockingsystemtr.h"
+#include "ads_globals_p.h"
#include "dockareawidget.h"
#include "dockfocuscontroller.h"
#include "dockingstatereader.h"
@@ -41,7 +42,7 @@
#include <QVariant>
#include <QXmlStreamWriter>
-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..a10161da59 100644
--- a/src/libs/advanceddockingsystem/docksplitter.cpp
+++ b/src/libs/advanceddockingsystem/docksplitter.cpp
@@ -3,14 +3,15 @@
#include "docksplitter.h"
+#include "ads_globals_p.h"
#include "dockareawidget.h"
+#include <utils/stylehelper.h>
+
#include <QChildEvent>
#include <QLoggingCategory>
#include <QVariant>
-static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg)
-
namespace ADS
{
/**
@@ -31,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/advanceddockingsystem/dockwidget.cpp b/src/libs/advanceddockingsystem/dockwidget.cpp
index 34887e9163..ea68014966 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 <QXmlStreamWriter>
#include <QWindow>
-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 <iostream>
-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 <QMouseEvent>
#include <QPointer>
-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 <iostream>
-static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg)
-
namespace ADS
{
/**
diff --git a/src/libs/advanceddockingsystem/workspacedialog.cpp b/src/libs/advanceddockingsystem/workspacedialog.cpp
index fb636887db..06e74f310a 100644
--- a/src/libs/advanceddockingsystem/workspacedialog.cpp
+++ b/src/libs/advanceddockingsystem/workspacedialog.cpp
@@ -14,6 +14,7 @@
#include <QDialogButtonBox>
#include <QLabel>
#include <QPushButton>
+#include <QLineEdit>
namespace ADS {
@@ -47,7 +48,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{Column{m_workspaceView, m_autoLoadCheckBox},
Column{m_btCreateNew,
diff --git a/src/libs/advanceddockingsystem/workspaceinputdialog.cpp b/src/libs/advanceddockingsystem/workspaceinputdialog.cpp
index 56e7543323..15f36398c2 100644
--- a/src/libs/advanceddockingsystem/workspaceinputdialog.cpp
+++ b/src/libs/advanceddockingsystem/workspaceinputdialog.cpp
@@ -69,7 +69,7 @@ WorkspaceNameInputDialog::WorkspaceNameInputDialog(DockManager *manager, QWidget
m_switchToButton->setEnabled(m_newWorkspaceLineEdit->hasAcceptableInput());
});
- using namespace Utils::Layouting;
+ using namespace Layouting;
Column {
label,
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/cplusplus/CppDocument.cpp b/src/libs/cplusplus/CppDocument.cpp
index 6e241fb29b..07714c0fe2 100644
--- a/src/libs/cplusplus/CppDocument.cpp
+++ b/src/libs/cplusplus/CppDocument.cpp
@@ -26,7 +26,6 @@
#include <QByteArray>
#include <QDebug>
-#include <QFutureInterface>
#include <QStack>
/*!
@@ -297,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;
}
@@ -772,7 +772,7 @@ QSet<FilePath> 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);
@@ -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<QFuture<void>> &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..00bebf277d 100644
--- a/src/libs/cplusplus/CppDocument.h
+++ b/src/libs/cplusplus/CppDocument.h
@@ -15,12 +15,9 @@
#include <QDateTime>
#include <QHash>
#include <QFileInfo>
+#include <QFuture>
#include <QAtomicInt>
-QT_BEGIN_NAMESPACE
-class QFutureInterfaceBase;
-QT_END_NAMESPACE
-
namespace CPlusPlus {
class Macro;
@@ -300,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<Include> &resolvedIncludes() const
@@ -406,8 +408,7 @@ public:
Utils::FilePaths filesDependingOn(const Utils::FilePath &filePath) const;
- void updateDependencyTable() const;
- void updateDependencyTable(QFutureInterfaceBase &futureInterface) const;
+ void updateDependencyTable(const std::optional<QFuture<void>> &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 <QDebug>
-#include <QFutureInterface>
+#include <QFuture>
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<QFuture<void>> &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<int> 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 <QVector>
QT_BEGIN_NAMESPACE
-class QFutureInterfaceBase;
+template <typename T>
+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<QFuture<void>> &future, const Snapshot &snapshot);
Utils::FilePaths filesDependingOn(const Utils::FilePath &fileName) const;
QVector<Utils::FilePath> files;
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",
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 <QDebug>
#include <QList>
#include <QDate>
+#include <QLoggingCategory>
#include <QTime>
#include <QPair>
#include <cctype>
+#include <deque>
#include <list>
#include <algorithm>
-#define NO_DEBUG
-
-#ifndef NO_DEBUG
-# include <iostream>
-#endif // NO_DEBUG
-
-#include <deque>
+// 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 &poundToken)
{
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 &poundToken)
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 &poundToken)
{
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;
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/extensionsystem/iplugin.cpp b/src/libs/extensionsystem/iplugin.cpp
index 9f7ca221e5..2af4925573 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<TestCreator> testCreators;
+
+ QList<ObjectInitializer> objectInitializers;
+ QList<std::function<void()>> objectDestructors;
+
+ // For debugging purposes:
+ QList<void *> createdObjects; // Not owned.
};
+void IPluginPrivate::tryCreateObjects()
+{
+ QList<ObjectInitializer> 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<void()> &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)
@@ -195,7 +240,16 @@ bool IPlugin::initialize(const QStringList &arguments, QString *errorString)
}
/*!
- Registers a function object that creates a test object.
+ \internal
+*/
+void IPlugin::tryCreateObjects()
+{
+ d->tryCreateObjects();
+}
+
+/*!
+ 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().
@@ -211,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<QObject *> IPlugin::createTestObjects() const
{
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 <utils/id.h>
+
#include <QObject>
#include <functional>
@@ -15,6 +17,17 @@ namespace Internal { class IPluginPrivate; }
using TestCreator = std::function<QObject *()>;
+using ObjectCreator = std::function<void *()>;
+using ObjectDestructor = std::function<void(void *)>;
+
+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<Utils::Id> dependsOn;
+};
+
class EXTENSIONSYSTEM_EXPORT IPlugin : public QObject
{
Q_OBJECT
@@ -39,6 +52,7 @@ public:
// Deprecated in 10.0, use addTest()
virtual QVector<QObject *> 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 <typename Type>
+ void addManaged(const ObjectCreationPolicy &policy = {}) {
+ addManagedHelper([]() -> void * { return new Type(); },
+ [](void *p) { delete static_cast<Type *>(p); },
+ policy);
+ }
+
+ void addManagedHelper(const ObjectCreator &creator,
+ const ObjectDestructor &destructor,
+ const ObjectCreationPolicy &policy);
+
signals:
void asynchronousShutdownFinished();
diff --git a/src/libs/extensionsystem/plugindetailsview.cpp b/src/libs/extensionsystem/plugindetailsview.cpp
index 8242d8dac6..49ba0815a3 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 {
@@ -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/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..31c4334b6a 100644
--- a/src/libs/extensionsystem/pluginerrorview.cpp
+++ b/src/libs/extensionsystem/pluginerrorview.cpp
@@ -41,12 +41,13 @@ public:
errorString->setTabChangesFocus(true);
errorString->setReadOnly(true);
- using namespace Utils::Layouting;
+ using namespace Layouting;
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/extensionsystem/pluginmanager.cpp b/src/libs/extensionsystem/pluginmanager.cpp
index aec053b6a8..ae2dbf8903 100644
--- a/src/libs/extensionsystem/pluginmanager.cpp
+++ b/src/libs/extensionsystem/pluginmanager.cpp
@@ -32,10 +32,11 @@
#include <utils/benchmarker.h>
#include <utils/executeondestruction.h>
#include <utils/fileutils.h>
+#include <utils/futuresynchronizer.h>
#include <utils/hostosinfo.h>
#include <utils/mimeutils.h>
+#include <utils/process.h>
#include <utils/qtcassert.h>
-#include <utils/qtcprocess.h>
#include <utils/qtcsettings.h>
#include <utils/threadutils.h>
@@ -400,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)
@@ -422,6 +423,11 @@ QString PluginManager::systemInformation()
return result;
}
+FutureSynchronizer *PluginManager::futureSynchronizer()
+{
+ return d->m_futureSynchronizer.get();
+}
+
/*!
The list of paths were the plugin manager searches for plugins.
@@ -976,6 +982,7 @@ void PluginManagerPrivate::nextDelayedInitialize()
PluginManagerPrivate::PluginManagerPrivate(PluginManager *pluginManager) :
q(pluginManager)
{
+ m_futureSynchronizer.reset(new FutureSynchronizer);
}
@@ -1027,6 +1034,7 @@ void PluginManagerPrivate::readSettings()
*/
void PluginManagerPrivate::stopAll()
{
+ m_isShuttingDown = true;
if (delayedInitializeTimer && delayedInitializeTimer->isActive()) {
delayedInitializeTimer->stop();
delete delayedInitializeTimer;
@@ -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);
});
@@ -1831,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 8dac9544cc..ecd0ee70b7 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;
@@ -127,12 +129,15 @@ public:
static QString platformName();
static bool isInitializationDone();
+ static bool isShuttingDown();
static void remoteArguments(const QString &serializedArguments, QObject *socket);
static void shutdown();
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..c7a4291a6b 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;
}
@@ -123,6 +124,7 @@ public:
bool m_isInitializationDone = false;
bool enableCrashCheck = true;
+ bool m_isShuttingDown = false;
QHash<QString, std::function<bool()>> m_scenarios;
QString m_requestedScenario;
@@ -133,6 +135,7 @@ public:
QWaitCondition m_scenarioWaitCondition;
PluginManager::ProcessData m_creatorProcessData;
+ std::unique_ptr<Utils::FutureSynchronizer> m_futureSynchronizer;
private:
PluginManager *q;
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;
}
/*!
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));
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> params() const
{
const QJsonValue &params = m_jsonObject.value(paramsKey);
diff --git a/src/libs/languageserverprotocol/lsptypes.cpp b/src/libs/languageserverprotocol/lsptypes.cpp
index 4bf3f3746e..6f0b57c1e3 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,13 +288,13 @@ 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));
+ setStart(Position(line - 1, character));
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));
+ setEnd(Position(line - 1, character));
}
bool Range::contains(const Range &other) const
@@ -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 239d40f662..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); }
};
@@ -556,7 +558,6 @@ enum class SymbolKind {
TypeParameter = 26,
LastSymbolKind = TypeParameter,
};
-using SymbolStringifier = std::function<QString(SymbolKind, const QString &, const QString &)>;
namespace CompletionItemKind {
enum Kind {
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<T> toListOrEmpty() const
+ {
+ if (std::holds_alternative<QList<T>>(*this))
+ return std::get<QList<T>>(*this);
+ return {};
+ }
+
QList<T> toList() const
{
QTC_ASSERT(std::holds_alternative<QList<T>>(*this), return {});
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<SymbolInformation>, std::nullptr_t, WorkspaceSymbolParams>
{
public:
- WorkspaceSymbolRequest(const WorkspaceSymbolParams &params);
+ explicit WorkspaceSymbolRequest(const WorkspaceSymbolParams &params);
using Request::Request;
constexpr static const char methodName[] = "workspace/symbol";
};
diff --git a/src/libs/libs.qbs b/src/libs/libs.qbs
index 92f84fe4d1..ffc3017cba 100644
--- a/src/libs/libs.qbs
+++ b/src/libs/libs.qbs
@@ -19,12 +19,15 @@ Project {
"qmljs/qmljs.qbs",
"qmldebug/qmldebug.qbs",
"qtcreatorcdbext/qtcreatorcdbext.qbs",
+ "solutions/solutions.qbs",
"sqlite/sqlite.qbs",
"tracing/tracing.qbs",
- "utils/process_stub.qbs",
"utils/process_ctrlc_stub.qbs",
"utils/utils.qbs",
+ "3rdparty/libptyqt/ptyqt.qbs",
+ "3rdparty/libvterm/vterm.qbs",
"3rdparty/syntax-highlighting/syntax-highlighting.qbs",
+ "3rdparty/winpty/winpty.qbs",
"3rdparty/yaml-cpp/yaml-cpp.qbs",
].concat(qlitehtml).concat(project.additionalLibs)
}
diff --git a/src/libs/qlitehtml b/src/libs/qlitehtml
-Subproject f05f78ef33225823d348ee18f2fa464e95024dd
+Subproject 8e541a22b513432ed566fca824af207395ee0c9
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/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 <utils/hostosinfo.h>
+#include <utils/utilsicons.h>
#include <QToolButton>
#include <QFontComboBox>
@@ -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/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..c757f06802 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,15 +54,14 @@ 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);
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/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/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 <QString>
#include <QFile>
+#include <QRegularExpression>
#include <QTextStream>
#include <QHash>
@@ -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;
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 <utils/algorithm.h>
#include <utils/qtcassert.h>
+#include <utils/qtcsettings.h>
#include <QColor>
#include <QDir>
@@ -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<StaticAnalysis::Type> Check::defaultDisabledMessages()
+{
+ static const QList<StaticAnalysis::Type> disabled = Utils::sorted(QList<StaticAnalysis::Type>{
+ HintAnonymousFunctionSpacing,
+ HintDeclareVarsInOneLine,
+ HintDeclarationsShouldBeAtStartOfFunction,
+ HintBinaryOperatorSpacing,
+ HintOneStatementPerLine,
+ HintExtraParentheses,
+
+ // QmlDesigner related
+ WarnImperativeCodeNotEditableInVisualDesigner,
+ WarnUnsupportedTypeInVisualDesigner,
+ WarnReferenceToParentItemNotSupportedByVisualDesigner,
+ WarnUndefinedValueForVisualDesigner,
+ WarnStatesOnlyInRootItemForVisualDesigner,
+ ErrUnsupportedRootTypeInVisualDesigner,
+ ErrInvalidIdeInVisualDesigner,
+
+ });
+ return disabled;
+}
+
+QList<StaticAnalysis::Type> Check::defaultDisabledMessagesForNonQuickUi()
+{
+ static const QList<StaticAnalysis::Type> disabled = Utils::sorted(QList<StaticAnalysis::Type>{
+ // 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 <QSet>
#include <QStack>
+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<StaticAnalysis::Message> operator()();
@@ -31,11 +33,13 @@ public:
void disableMessage(StaticAnalysis::Type type);
void enableQmlDesignerChecks();
- void disableQmlDesignerChecks();
void enableQmlDesignerUiFileChecks();
void disableQmlDesignerUiFileChecks();
+ static QList<StaticAnalysis::Type> defaultDisabledMessages();
+ static QList<StaticAnalysis::Type> defaultDisabledMessagesForNonQuickUi();
+
protected:
bool preVisit(AST::Node *ast) override;
void postVisit(AST::Node *ast) override;
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
diff --git a/src/libs/qmljs/qmljsmodelmanagerinterface.cpp b/src/libs/qmljs/qmljsmodelmanagerinterface.cpp
index 3d2fc32120..6bcfe7ee25 100644
--- a/src/libs/qmljs/qmljsmodelmanagerinterface.cpp
+++ b/src/libs/qmljs/qmljsmodelmanagerinterface.cpp
@@ -15,8 +15,8 @@
#include <cplusplus/cppmodelmanagerbase.h>
#include <utils/algorithm.h>
+#include <utils/async.h>
#include <utils/hostosinfo.h>
-#include <utils/runextensions.h>
#include <utils/stringutils.h>
#ifdef WITH_TESTS
@@ -337,11 +337,9 @@ QFuture<void> ModelManagerInterface::refreshSourceFiles(const QList<Utils::FileP
if (sourceFiles.isEmpty())
return QFuture<void>();
- QFuture<void> result = Utils::runAsync(&m_threadPool,
- &ModelManagerInterface::parse,
- workingCopyInternal(), sourceFiles,
- this, Dialect(Dialect::Qml),
- emitDocumentOnDiskChanged);
+ QFuture<void> 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<void> ModelManagerInterface::refreshSourceFiles(const QList<Utils::FileP
void ModelManagerInterface::fileChangedOnDisk(const Utils::FilePath &path)
{
- addFuture(Utils::runAsync(&m_threadPool,
- &ModelManagerInterface::parse,
- workingCopyInternal(),
- FilePaths({path}),
- this,
- Dialect(Dialect::AnyLanguage),
- true));
+ addFuture(Utils::asyncRun(&m_threadPool, &ModelManagerInterface::parse, workingCopyInternal(),
+ FilePaths({path}), this, Dialect(Dialect::AnyLanguage), true));
}
void ModelManagerInterface::removeFiles(const QList<Utils::FilePath> &files)
@@ -1044,24 +1037,24 @@ void ModelManagerInterface::parseLoop(QSet<Utils::FilePath> &scannedPaths,
class FutureReporter
{
public:
- FutureReporter(QFutureInterface<void> &future, int multiplier, int base)
- : future(future), multiplier(multiplier), base(base)
+ FutureReporter(QPromise<void> &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<void> &future;
- int multiplier;
- int base;
+ QPromise<void> &m_promise;
+ int m_multiplier;
+ int m_base;
};
-void ModelManagerInterface::parse(QFutureInterface<void> &future,
+void ModelManagerInterface::parse(QPromise<void> &promise,
const WorkingCopy &workingCopy,
QList<Utils::FilePath> files,
ModelManagerInterface *modelManager,
@@ -1069,8 +1062,8 @@ void ModelManagerInterface::parse(QFutureInterface<void> &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<Utils::FilePath> scannedPaths;
@@ -1078,7 +1071,7 @@ void ModelManagerInterface::parse(QFutureInterface<void> &future,
QSet<Utils::FilePath> 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<void> &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<void> promise;
+ promise.start();
+ importScanAsync(promise, workingCopy, paths, modelManager, emitDocChanged, libOnly, forceRescan);
+}
+
+void ModelManagerInterface::importScanAsync(QPromise<void> &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<Utils::FilePath> scannedPaths;
@@ -1118,9 +1120,9 @@ void ModelManagerInterface::importScan(QFutureInterface<void> &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<void> &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<void> &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<void> result = Utils::runAsync(&m_threadPool,
- &ModelManagerInterface::importScan,
+ QFuture<void> 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<void> &futureInterface, ModelManagerInterface *qmlModelManager,
- const CPlusPlus::Snapshot &snapshot,
+void ModelManagerInterface::updateCppQmlTypes(QPromise<void> &promise,
+ ModelManagerInterface *qmlModelManager, const CPlusPlus::Snapshot &snapshot,
const QHash<QString, QPair<CPlusPlus::Document::Ptr, bool>> &documents)
{
- futureInterface.setProgressRange(0, documents.size());
- futureInterface.setProgressValue(0);
+ promise.setProgressRange(0, documents.size());
+ promise.setProgressValue(0);
CppDataHash newData;
QHash<QString, QList<CPlusPlus::Document::Ptr>> newDeclarations;
@@ -1436,9 +1437,9 @@ void ModelManagerInterface::updateCppQmlTypes(
bool hasNewInfo = false;
using DocScanPair = QPair<CPlusPlus::Document::Ptr, bool>;
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<void> &future);
QmlJS::Document::Ptr ensuredGetDocumentForPath(const Utils::FilePath &filePath);
- static void importScan(QFutureInterface<void> &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<void> &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<bool(qreal)> &reportProgress);
- static void parse(QFutureInterface<void> &future,
+ static void parse(QPromise<void> &promise,
const WorkingCopy &workingCopyInternal,
QList<Utils::FilePath> files,
ModelManagerInterface *modelManager,
QmlJS::Dialect mainLanguage,
bool emitDocChangedOnDisk);
- static void updateCppQmlTypes(
- QFutureInterface<void> &futureInterface, ModelManagerInterface *qmlModelManager,
- const CPlusPlus::Snapshot &snapshot,
- const QHash<QString, QPair<CPlusPlus::Document::Ptr, bool>> &documents);
+ static void updateCppQmlTypes(QPromise<void> &promise, ModelManagerInterface *qmlModelManager,
+ const CPlusPlus::Snapshot &snapshot,
+ const QHash<QString, QPair<CPlusPlus::Document::Ptr, bool>> &documents);
void maybeScan(const PathsAndLanguages &importPaths);
void updateImportPaths();
diff --git a/src/libs/qmljs/qmljsplugindumper.cpp b/src/libs/qmljs/qmljsplugindumper.cpp
index a7bafdde94..761df90ddc 100644
--- a/src/libs/qmljs/qmljsplugindumper.cpp
+++ b/src/libs/qmljs/qmljsplugindumper.cpp
@@ -7,14 +7,13 @@
#include "qmljsmodelmanagerinterface.h"
#include "qmljstr.h"
#include "qmljsutils.h"
-#include "qmljsviewercontext.h"
#include <utils/algorithm.h>
+#include <utils/async.h>
#include <utils/filesystemwatcher.h>
#include <utils/fileutils.h>
#include <utils/hostosinfo.h>
-#include <utils/qtcprocess.h>
-#include <utils/runextensions.h>
+#include <utils/process.h>
#include <QDir>
#include <QDirIterator>
@@ -204,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();
@@ -238,7 +237,7 @@ static QString qmlPluginDumpErrorMessage(QtcProcess *process)
return errorMessage;
}
-void PluginDumper::qmlPluginTypeDumpDone(QtcProcess *process)
+void PluginDumper::qmlPluginTypeDumpDone(Process *process)
{
process->deleteLater();
@@ -273,14 +272,13 @@ void PluginDumper::qmlPluginTypeDumpDone(QtcProcess *process)
QStringList dependencies;
};
- auto future = Utils::runAsync(m_modelManager->threadPool(),
- [output, libraryPath](QFutureInterface<CppQmlTypesInfo>& future)
- {
+ auto future = Utils::asyncRun(m_modelManager->threadPool(),
+ [output, libraryPath](QPromise<CppQmlTypesInfo> &promise) {
CppQmlTypesInfo infos;
- CppQmlTypesLoader::parseQmlTypeDescriptions(output, &infos.objectsList, &infos.moduleApis, &infos.dependencies,
- &infos.error, &infos.warning,
- "<dump of " + libraryPath.toUserOutput() + '>');
- future.reportFinished(&infos);
+ CppQmlTypesLoader::parseQmlTypeDescriptions(output, &infos.objectsList,
+ &infos.moduleApis, &infos.dependencies, &infos.error, &infos.warning,
+ "<dump of " + libraryPath.toUserOutput() + '>');
+ promise.addResult(infos);
});
m_modelManager->addFuture(future);
@@ -327,8 +325,8 @@ void PluginDumper::pluginChanged(const QString &pluginLibrary)
QFuture<PluginDumper::QmlTypeDescription> PluginDumper::loadQmlTypeDescription(const FilePaths &paths) const
{
- auto future = Utils::runAsync(m_modelManager->threadPool(), [=](QFutureInterface<PluginDumper::QmlTypeDescription> &future)
- {
+ auto future = Utils::asyncRun(m_modelManager->threadPool(),
+ [=](QPromise<PluginDumper::QmlTypeDescription> &promise) {
PluginDumper::QmlTypeDescription result;
for (const FilePath &p: paths) {
@@ -355,8 +353,7 @@ QFuture<PluginDumper::QmlTypeDescription> PluginDumper::loadQmlTypeDescription(c
if (!warning.isEmpty())
result.warnings += warning;
}
-
- future.reportFinished(&result);
+ promise.addResult(result);
});
m_modelManager->addFuture(future);
@@ -600,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<Utils::QtcProcess *, Utils::FilePath> m_runningQmldumps;
+ QHash<Utils::Process *, Utils::FilePath> m_runningQmldumps;
QList<Plugin> m_plugins;
QHash<Utils::FilePath, int> m_libraryToPluginIndex;
QHash<QString, QmlJS::ModelManagerInterface::ProjectInfo> m_qtToInfo;
diff --git a/src/libs/qmljs/qmljsreformatter.cpp b/src/libs/qmljs/qmljsreformatter.cpp
index 461c944afa..232c0361a1 100644
--- a/src/libs/qmljs/qmljsreformatter.cpp
+++ b/src/libs/qmljs/qmljsreformatter.cpp
@@ -101,22 +101,23 @@ public:
_hadEmptyLine = false;
_binaryExpDepth = 0;
-
+ const QString &source = _doc->source();
// emit directives
if (_doc->bind()->isJsLibrary()) {
- out(QLatin1String(".pragma library"));
+ const QString pragmaLine(".pragma library");
+ out(pragmaLine, SourceLocation(source.indexOf(".pragma"), pragmaLine.length()));
newLine();
}
const QList<SourceLocation> &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;
+ int i = -1;
+ while (++line < d.startLine)
+ i = source.indexOf(QChar('\n'), i + 1);
quint32 offset = static_cast<quint32>(i) + d.startColumn;
- int endline = _doc->source().indexOf('\n', static_cast<int>(offset) + 1);
- int end = endline == -1 ? _doc->source().length() : endline;
- quint32 length = static_cast<quint32>(end) - offset;
+ int endline = source.indexOf('\n', static_cast<int>(offset) + 1);
+ int end = endline == -1 ? source.length() : endline;
+ quint32 length = static_cast<quint32>(end) - offset + 1;
out(SourceLocation(offset, length, d.startLine, d.startColumn));
}
if (!directives.isEmpty())
@@ -1087,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);
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/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<bool> result() const { return m_result; }
+
+signals:
+ void done(bool success);
+
+private:
+ std::optional<bool> m_result = {};
+ int m_limit = 1;
+ int m_current = -1;
+};
+
+class TASKING_EXPORT BarrierTaskAdapter : public Tasking::TaskAdapter<Barrier>
+{
+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 <int Limit = 1>
+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<Barrier> m_barrier;
+};
+
+template <int Limit = 1>
+using MultiBarrier = TreeStorage<SharedBarrier<Limit>>;
+
+// 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 <int Limit>
+ WaitForBarrierTask(const MultiBarrier<Limit> &sharedBarrier)
+ : BarrierTask([sharedBarrier](Barrier &barrier) {
+ SharedBarrier<Limit> *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<bool> 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 <qglobal.h>
+
+#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..f9fe58375e
--- /dev/null
+++ b/src/libs/solutions/tasking/tasktree.cpp
@@ -0,0 +1,1980 @@
+// 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 <QEventLoop>
+#include <QFutureWatcher>
+#include <QPromise>
+#include <QSet>
+#include <QTimer>
+
+using namespace std::chrono;
+
+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;
+};
+
+/*!
+ \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::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.
+
+ 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
+*/
+
+/*!
+ \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
+
+ 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<TaskAction()>.
+
+ 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<void()>,
+ 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\<void()\>.
+
+ 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 <typename SetupHandler> 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<TaskAction()> or \c std::function<void()>
+ 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 finishAllAndDone 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);
+}
+
+/*!
+ 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));
+}
+
+/*!
+ 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);
+}
+
+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)
+{
+ 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;
+}
+
+void TaskItem::addChildren(const QList<TaskItem> &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::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::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;
+}
+
+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;
+
+class TaskTreePrivate
+{
+ Q_DISABLE_COPY_MOVE(TaskTreePrivate)
+
+public:
+ TaskTreePrivate(TaskTree *taskTree)
+ : q(taskTree) {}
+
+ void start();
+ void stop();
+ void advanceProgress(int byValue);
+ void emitStartedAndProgress();
+ void emitProgress();
+ void emitDone();
+ void emitError();
+ QList<TreeStorageBase> addStorages(const QList<TreeStorageBase> &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<TreeStorageBase> m_storages;
+ QHash<TreeStorageBase, StorageHandler> m_storageHandlers;
+ std::unique_ptr<TaskNode> m_root = nullptr; // Keep me last in order to destruct first
+};
+
+class TaskContainer
+{
+ Q_DISABLE_COPY_MOVE(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<TreeStorageBase> m_storageList;
+ const QList<TaskNode *> m_children;
+ const int m_taskCount = 0;
+ };
+
+ struct RuntimeData {
+ RuntimeData(const ConstData &constData);
+ ~RuntimeData();
+
+ static QList<int> createStorages(const TaskContainer::ConstData &constData);
+ void callStorageDoneHandlers();
+ bool updateSuccessBit(bool success);
+ int currentLimit() const;
+
+ const ConstData &m_constData;
+ const QList<int> m_storageIdList;
+ bool m_successBit = true;
+ int m_doneCount = 0;
+ Guard m_startGuard;
+ };
+
+ const ConstData m_constData;
+ std::optional<RuntimeData> m_runtimeData;
+};
+
+class TaskNode
+{
+ Q_DISABLE_COPY_MOVE(TaskNode)
+
+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; }
+ TaskTree *taskTree() const { return m_container.m_constData.m_taskTreePrivate->q; }
+
+private:
+ const TaskItem::TaskHandler m_taskHandler;
+ TaskContainer m_container;
+ std::unique_ptr<TaskInterface> 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<TreeStorageBase> TaskTreePrivate::addStorages(const QList<TreeStorageBase> &storages)
+{
+ QList<TreeStorageBase> 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 <typename Handler, typename ...Args,
+ typename ReturnType = typename std::invoke_result_t<Handler, Args...>>
+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>(handler), std::forward<Args>(args)...);
+}
+
+static QList<TaskNode *> createChildren(TaskTreePrivate *taskTreePrivate, TaskContainer *container,
+ const TaskItem &task)
+{
+ QList<TaskNode *> result;
+ const QList<TaskItem> &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.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,
+ [](int r, TaskNode *n) { return r + n->taskCount(); }))
+{}
+
+QList<int> TaskContainer::RuntimeData::createStorages(const TaskContainer::ConstData &constData)
+{
+ QList<int> 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);
+ }
+}
+
+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(initialSuccessBit(m_constData.m_workflowPolicy))
+{}
+
+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::FinishAllAndDone
+ || 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;
+ }
+
+ 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 = toTaskAction(m_runtimeData->m_successBit);
+ }
+ 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::StopOnFinished
+ || (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<TaskAction> unwindAction
+ = std::make_shared<TaskAction>(TaskAction::Continue);
+ QObject::connect(m_task.get(), &TaskInterface::done, taskTree(), [=](bool success) {
+ invokeEndHandler(success);
+ QObject::disconnect(m_task.get(), &TaskInterface::done, taskTree(), 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.m_runtimeData->updateSuccessBit(false);
+ 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);
+}
+
+/*!
+ \namespace Tasking
+ \inmodule QtCreator
+ \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.
+*/
+
+/*!
+ \class Tasking::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<ReturnType>. 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<ReturnType>:
+
+ \code
+ using namespace Tasking;
+
+ const Group root {
+ ProcessTask(...),
+ AsyncTask<int>(...),
+ 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<int>.
+ After taskTree->start() is called, the tasks are run in a chain, starting
+ with ProcessTask. When the ProcessTask finishes successfully, the AsyncTask<int> 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<int>(...)
+ },
+ FileTransferTask(...)
+ };
+ \endcode
+
+ The example above differs from the first example in that the root element has
+ a subgroup that contains the ProcessTask and AsyncTask<int>. 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<int> 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<int>), 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<int> starts
+ \li AsyncTask<int> starts
+ \row
+ \li ...
+ \li ...
+ \row
+ \li \b {ProcessTask finishes}
+ \li \b {AsyncTask<int> finishes}
+ \row
+ \li ...
+ \li ...
+ \row
+ \li \b {AsyncTask<int> 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<int> is still being executed,
+ the AsyncTask<int> 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<ReturnType>
+ \li Utils::Async<ReturnType>
+ \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'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
+ 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 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
+ possible values are:
+
+ \table
+ \header
+ \li TaskAction Value
+ \li Brief Description
+ \row
+ \li Continue
+ \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
+ \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. 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.
+
+ \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'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,
+ 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 onSetup = [] {
+ qDebug() << "Entering the group";
+ };
+ const Group root {
+ 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
+ is triggered at runtime that includes an error message.
+
+ 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
+ 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 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
+
+ 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. 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, 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 any of its \e direct child's tasks finish. For a detailed description of possible
+ policies, refer to WorkflowPolicy.
+
+ 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,
+ 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
+ static QByteArray load(const QString &fileName) { ... }
+ static void save(const QString &fileName, const QByteArray &array) { ... }
+
+ static TaskItem copyRecipe(const QString &source, const QString &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<CopyStorage> storage;
+
+ const auto onLoaderSetup = [source](Async<QByteArray> &async) {
+ async.setConcurrentCallData(&load, source);
+ };
+ // [4] runtime: task tree activates the instance from [7] before invoking handler
+ const auto onLoaderDone = [storage](const Async<QByteArray> &async) {
+ storage->content = async.result(); // [5] loader stores the result in storage
+ };
+
+ // [4] runtime: task tree activates the instance from [7] before invoking handler
+ const auto onSaverSetup = [storage, destination](Async<void> &async) {
+ const QByteArray content = storage->content; // [6] saver takes data from storage
+ async.setConcurrentCallData(&save, destination, content);
+ };
+ const auto onSaverDone = [](const Async<void> &async) {
+ qDebug() << "Save done successfully";
+ };
+
+ const Group root {
+ // [7] runtime: task tree creates an instance of CopyStorage when root is entered
+ Storage(storage),
+ AsyncTask<QByteArray>(onLoaderSetup, onLoaderDone),
+ AsyncTask<void>(onSaverSetup, onSaverDone)
+ };
+ 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
+ variable [2] enclosed in a CopyStorage custom struct [1]. If the loader
+ finishes successfully, it stores the data in a CopyStorage::content
+ 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<CopyStorage> is created [3]. If a copy of this object 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.
+
+ If several task trees that hold a copy of the common TreeStorage<CopyStorage>
+ 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<CopyStorage> object to the handler (for example, in
+ a lambda capture) [4].
+
+ 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<CopyStorage> object. Therefore, the CopyStorage struct may be
+ accessed only from within the handler body. To access the currently active
+ CopyStorage from within TreeStorage<CopyStorage>, use the TreeStorage::operator->(),
+ 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<MyStorage> storage [3]
+ \li Pass the TreeStorage<MyStorage> instance to handlers [4]
+ \li Access the MyStorage instance in handlers [5], [6]
+ \li Insert the TreeStorage<MyStorage> instance into a group [7]
+ \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<CopyStorage> 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<CopyStorage> 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 TimeoutTaskAdapter : public Tasking::TaskAdapter<QTimer>
+ {
+ public:
+ TimeoutTaskAdapter() {
+ task()->setSingleShot(true);
+ task()->setInterval(1000);
+ connect(task(), &QTimer::timeout, this, [this] { emit done(true); });
+ }
+ void start() final { task()->start(); }
+ };
+
+ QTC_DECLARE_CUSTOM_TASK(TimeoutTask, TimeoutTaskAdapter);
+ \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 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 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:
+
+ \code
+ const auto onTimeoutSetup = [](QTimer &task) {
+ task.setInterval(2000);
+ };
+ const auto onTimeoutDone = [](const QTimer &task) {
+ qDebug() << "timeout triggered";
+ };
+
+ const Group root {
+ TimeoutTask(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();
+}
+
+bool TaskTree::runBlocking()
+{
+ QPromise<void> dummy;
+ dummy.start();
+ return runBlocking(dummy.future());
+}
+
+bool TaskTree::runBlocking(const QFuture<void> &future)
+{
+ if (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<void> 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); });
+ QTimer::singleShot(0, this, &TaskTree::start);
+
+ loop.exec(QEventLoop::ExcludeUserInputEvents);
+ if (!ok) {
+ auto nonConstFuture = future;
+ nonConstFuture.cancel();
+ }
+ return ok;
+}
+
+bool TaskTree::runBlocking(const Group &recipe, milliseconds timeout)
+{
+ QPromise<void> dummy;
+ dummy.start();
+ return TaskTree::runBlocking(recipe, dummy.future(), timeout);
+}
+
+bool TaskTree::runBlocking(const Group &recipe, const QFuture<void> &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
+{
+ 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();
+}
+
+using TimeoutCallback = std::function<void()>;
+
+struct TimerData
+{
+ system_clock::time_point m_deadline;
+ QPointer<QObject> m_context;
+ TimeoutCallback m_callback;
+};
+
+QMutex s_mutex;
+std::atomic_int s_timerId = 0;
+QHash<int, TimerData> s_timerIdToTimerData = {};
+QMultiMap<system_clock::time_point, int> s_deadlineToTimerId = {};
+
+static QList<TimerData> 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<TimerData> 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<TimerData> 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()
+{
+ 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
new file mode 100644
index 0000000000..647c680b5b
--- /dev/null
+++ b/src/libs/solutions/tasking/tasktree.h
@@ -0,0 +1,468 @@
+// 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 <QHash>
+#include <QObject>
+#include <QSharedPointer>
+
+QT_BEGIN_NAMESPACE
+template <class T>
+class QFuture;
+QT_END_NAMESPACE
+
+namespace Tasking {
+
+Q_NAMESPACE_EXPORT(TASKING_EXPORT)
+
+class ExecutionContextActivator;
+class TaskContainer;
+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<void *(void)>;
+ using StorageDestructor = std::function<void(void *)>;
+
+ 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<int, void *> m_storageHash = {};
+ int m_activeStorage = 0; // 0 means no active storage
+ int m_storageCounter = 0;
+ };
+ QSharedPointer<StorageData> m_storageData;
+ friend ExecutionContextActivator;
+ friend TaskContainer;
+ friend TaskTreePrivate;
+};
+
+template <typename StorageStruct>
+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<StorageStruct *>(activeStorageVoid());
+ }
+
+private:
+ static StorageConstructor ctor() { return [] { return new StorageStruct; }; }
+ static StorageDestructor dtor() {
+ return [](void *storage) { delete static_cast<StorageStruct *>(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. 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).
+ 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.
+ FinishAllAndError // 5 - Reports error after all children finished.
+};
+Q_ENUM_NS(WorkflowPolicy);
+
+enum class TaskAction
+{
+ Continue,
+ StopWithDone,
+ StopWithError
+};
+Q_ENUM_NS(TaskAction);
+
+class TASKING_EXPORT TaskItem
+{
+public:
+ // Internal, provided by QTC_DECLARE_CUSTOM_TASK
+ using TaskCreateHandler = std::function<TaskInterface *(void)>;
+ // Called prior to task start, just after createHandler
+ using TaskSetupHandler = std::function<TaskAction(TaskInterface &)>;
+ // Called on task done / error
+ using TaskEndHandler = std::function<void(const TaskInterface &)>;
+ // Called when group entered
+ using GroupSetupHandler = std::function<TaskAction()>;
+ // Called when group done / error
+ using GroupEndHandler = std::function<void()>;
+
+ 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 = {};
+ };
+
+ struct GroupData {
+ GroupHandler m_groupHandler = {};
+ std::optional<int> m_parallelLimit = {};
+ std::optional<WorkflowPolicy> m_workflowPolicy = {};
+ };
+
+ QList<TaskItem> children() const { return m_children; }
+ GroupData groupData() const { return m_groupData; }
+ QList<TreeStorageBase> storageList() const { return m_storageList; }
+ TaskHandler taskHandler() const { return m_taskHandler; }
+
+protected:
+ enum class Type {
+ Group,
+ GroupData,
+ Storage,
+ TaskHandler
+ };
+
+ TaskItem() = default;
+ 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<TaskItem> &children);
+
+ void setTaskSetupHandler(const TaskSetupHandler &handler);
+ void setTaskDoneHandler(const TaskEndHandler &handler);
+ void setTaskErrorHandler(const TaskEndHandler &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}); }
+ static TaskItem withTimeout(const TaskItem &item, std::chrono::milliseconds timeout,
+ const GroupEndHandler &handler = {});
+
+private:
+ Type m_type = Type::Group;
+ QList<TaskItem> m_children;
+ GroupData m_groupData;
+ QList<TreeStorageBase> m_storageList;
+ TaskHandler m_taskHandler;
+};
+
+class TASKING_EXPORT Group : public TaskItem
+{
+public:
+ Group(const QList<TaskItem> &children) { addChildren(children); }
+ Group(std::initializer_list<TaskItem> children) { addChildren(children); }
+
+ // GroupData related:
+ template <typename SetupHandler>
+ static TaskItem onGroupSetup(SetupHandler &&handler) {
+ return groupHandler({wrapGroupSetup(std::forward<SetupHandler>(handler))});
+ }
+ static TaskItem onGroupDone(const GroupEndHandler &handler) {
+ return groupHandler({{}, handler});
+ }
+ static TaskItem onGroupError(const GroupEndHandler &handler) {
+ return groupHandler({{}, {}, handler});
+ }
+ 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<typename SetupHandler>
+ static GroupSetupHandler wrapGroupSetup(SetupHandler &&handler)
+ {
+ static constexpr bool isDynamic
+ = std::is_same_v<TaskAction, std::invoke_result_t<std::decay_t<SetupHandler>>>;
+ constexpr bool isVoid
+ = std::is_same_v<void, std::invoke_result_t<std::decay_t<SetupHandler>>>;
+ 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 <typename SetupHandler>
+static TaskItem onGroupSetup(SetupHandler &&handler)
+{
+ return Group::onGroupSetup(std::forward<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 finishAllAndDone;
+TASKING_EXPORT extern const TaskItem finishAllAndError;
+
+class TASKING_EXPORT Storage : public TaskItem
+{
+public:
+ Storage(const TreeStorageBase &storage) : TaskItem(storage) { }
+};
+
+// Synchronous invocation. Similarly to Group - isn't counted as a task inside taskCount()
+class TASKING_EXPORT Sync : public Group
+{
+
+public:
+ template<typename Function>
+ Sync(Function &&function) : Group(init(std::forward<Function>(function))) {}
+
+private:
+ template<typename Function>
+ static QList<TaskItem> init(Function &&function) {
+ constexpr bool isInvocable = std::is_invocable_v<std::decay_t<Function>>;
+ static_assert(isInvocable,
+ "Sync element: The synchronous function can't take any arguments.");
+ constexpr bool isBool = std::is_same_v<bool, std::invoke_result_t<std::decay_t<Function>>>;
+ constexpr bool isVoid = std::is_same_v<void, std::invoke_result_t<std::decay_t<Function>>>;
+ 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; })};
+ };
+};
+
+template <typename Task>
+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 <typename Adapter>
+class CustomTask : public TaskItem
+{
+public:
+ using Task = typename Adapter::Type;
+ using EndHandler = std::function<void(const Task &)>;
+ static Adapter *createAdapter() { return new Adapter; }
+ CustomTask() : TaskItem({&createAdapter}) {}
+ template <typename SetupFunction>
+ CustomTask(SetupFunction &&function, const EndHandler &done = {}, const EndHandler &error = {})
+ : TaskItem({&createAdapter, wrapSetup(std::forward<SetupFunction>(function)),
+ wrapEnd(done), wrapEnd(error)}) {}
+
+ template <typename SetupFunction>
+ CustomTask &onSetup(SetupFunction &&function) {
+ setTaskSetupHandler(wrapSetup(std::forward<SetupFunction>(function)));
+ return *this;
+ }
+ CustomTask &onDone(const EndHandler &handler) {
+ setTaskDoneHandler(wrapEnd(handler));
+ return *this;
+ }
+ CustomTask &onError(const EndHandler &handler) {
+ setTaskErrorHandler(wrapEnd(handler));
+ return *this;
+ }
+
+ TaskItem withTimeout(std::chrono::milliseconds timeout,
+ const GroupEndHandler &handler = {}) const {
+ return TaskItem::withTimeout(*this, timeout, handler);
+ }
+
+private:
+ template<typename SetupFunction>
+ static TaskItem::TaskSetupHandler wrapSetup(SetupFunction &&function) {
+ static constexpr bool isDynamic = std::is_same_v<TaskAction,
+ std::invoke_result_t<std::decay_t<SetupFunction>, typename Adapter::Type &>>;
+ constexpr bool isVoid = std::is_same_v<void,
+ std::invoke_result_t<std::decay_t<SetupFunction>, 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<Adapter &>(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<const Adapter &>(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;
+
+ // 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();
+ bool runBlocking(const QFuture<void> &future);
+ static bool runBlocking(const Group &recipe,
+ std::chrono::milliseconds timeout = std::chrono::milliseconds::max());
+ static bool runBlocking(const Group &recipe, const QFuture<void> &future,
+ std::chrono::milliseconds timeout = std::chrono::milliseconds::max());
+
+ int taskCount() const;
+ int progressMaximum() const { return taskCount(); }
+ int progressValue() const; // all finished / skipped / stopped tasks, groups itself excluded
+
+ template <typename StorageStruct, typename StorageHandler>
+ void onStorageSetup(const TreeStorage<StorageStruct> &storage, StorageHandler &&handler) {
+ setupStorageHandler(storage,
+ wrapHandler<StorageStruct>(std::forward<StorageHandler>(handler)), {});
+ }
+ template <typename StorageStruct, typename StorageHandler>
+ void onStorageDone(const TreeStorage<StorageStruct> &storage, StorageHandler &&handler) {
+ setupStorageHandler(storage,
+ {}, wrapHandler<StorageStruct>(std::forward<StorageHandler>(handler)));
+ }
+
+signals:
+ void started();
+ void done();
+ void errorOccurred();
+ void progressValueChanged(int value); // updated whenever task finished / skipped / stopped
+
+private:
+ using StorageVoidHandler = std::function<void(void *)>;
+ void setupStorageHandler(const TreeStorageBase &storage,
+ StorageVoidHandler setupHandler,
+ StorageVoidHandler doneHandler);
+ template <typename StorageStruct, typename StorageHandler>
+ StorageVoidHandler wrapHandler(StorageHandler &&handler) {
+ return [=](void *voidStruct) {
+ StorageStruct *storageStruct = static_cast<StorageStruct *>(voidStruct);
+ std::invoke(handler, storageStruct);
+ };
+ }
+
+ friend class TaskTreePrivate;
+ TaskTreePrivate *d;
+};
+
+class TASKING_EXPORT TaskTreeTaskAdapter : public TaskAdapter<TaskTree>
+{
+public:
+ TaskTreeTaskAdapter();
+ void start() final;
+};
+
+class TASKING_EXPORT TimeoutTaskAdapter : public TaskAdapter<std::chrono::milliseconds>
+{
+public:
+ TimeoutTaskAdapter();
+ ~TimeoutTaskAdapter();
+ void start() final;
+
+private:
+ std::optional<int> m_timerId;
+};
+
+} // namespace Tasking
+
+#define TASKING_DECLARE_TASK(CustomTaskName, TaskAdapterClass)\
+namespace Tasking { using CustomTaskName = CustomTask<TaskAdapterClass>; }
+
+#define TASKING_DECLARE_TEMPLATE_TASK(CustomTaskName, TaskAdapterClass)\
+namespace Tasking {\
+template <typename ...Args>\
+using CustomTaskName = CustomTask<TaskAdapterClass<Args...>>;\
+} // namespace Tasking
+
+TASKING_DECLARE_TASK(TaskTreeTask, TaskTreeTaskAdapter);
+TASKING_DECLARE_TASK(TimeoutTask, TimeoutTaskAdapter);
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 $<$<CONFIG:Debug>:SQLITE_DEBUG>
PROPERTIES COMPILE_OPTIONS $<IF:$<CXX_COMPILER_ID:MSVC>,/FIconfig.h,-includeconfig.h>
PUBLIC_INCLUDES
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 <utils/smallstring.h>
@@ -10,10 +11,6 @@
#include <exception>
#include <iostream>
-extern "C" {
-struct sqlite3;
-}
-
namespace Sqlite {
class SQLITE_EXPORT Exception : public std::exception
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 <utils/icon.h>
#include <utils/qtcassert.h>
-#include <utils/utilsicons.h>
+#include <utils/stylehelper.h>
#include <utils/theme/theme.h>
+#include <utils/utilsicons.h>
#include <QIcon>
#include <QQmlContext>
@@ -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
diff --git a/src/libs/tracing/timelinetracemanager.cpp b/src/libs/tracing/timelinetracemanager.cpp
index 96eb147519..98f333da91 100644
--- a/src/libs/tracing/timelinetracemanager.cpp
+++ b/src/libs/tracing/timelinetracemanager.cpp
@@ -6,12 +6,10 @@
#include "timelinetracemanager.h"
#include "tracingtr.h"
+#include <utils/async.h>
#include <utils/qtcassert.h>
-#include <utils/temporaryfile.h>
-#include <utils/runextensions.h>
#include <QFile>
-#include <QDataStream>
#include <memory>
@@ -223,8 +221,11 @@ QFuture<void> 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<void> &future) {
- writer->setFuture(future);
+ QFutureInterface<void> fi;
+ fi.reportStarted();
+ writer->setFuture(fi);
+
+ Utils::asyncRun([filename, writer, fi] {
QFile file(filename);
if (file.open(QIODevice::WriteOnly))
@@ -232,10 +233,13 @@ QFuture<void> 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<void> TimelineTraceManager::load(const QString &filename)
@@ -249,8 +253,10 @@ QFuture<void> TimelineTraceManager::load(const QString &filename)
connect(reader, &QObject::destroyed, this, &TimelineTraceManager::loadFinished);
connect(reader, &TimelineTraceFile::error, this, &TimelineTraceManager::error);
- QFuture<void> future = Utils::runAsync([filename, reader] (QFutureInterface<void> &future) {
- reader->setFuture(future);
+ QFutureInterface<void> fi;
+ fi.reportStarted();
+ reader->setFuture(fi);
+ Utils::asyncRun([filename, reader, fi] {
QFile file(filename);
if (file.open(QIODevice::ReadOnly))
@@ -259,11 +265,13 @@ QFuture<void> 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<void> *watcher = new QFutureWatcher<void>(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<void> 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<void> 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();
diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt
index 4362294341..aa6a3e199e 100644
--- a/src/libs/utils/CMakeLists.txt
+++ b/src/libs/utils/CMakeLists.txt
@@ -1,8 +1,8 @@
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
- Qt6Core5Compat
+ Qt::Core5Compat
SOURCES
../3rdparty/span/span.hpp
../3rdparty/tl_expected/include/tl/expected.hpp
@@ -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
basetreeview.cpp basetreeview.h
benchmarker.cpp benchmarker.h
buildablehelperlibrary.cpp buildablehelperlibrary.h
@@ -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
@@ -55,6 +56,8 @@ add_qtc_library(Utils
filepath.cpp filepath.h
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
@@ -86,7 +89,6 @@ add_qtc_library(Utils
launcherpackets.cpp launcherpackets.h
launchersocket.cpp launchersocket.h
layoutbuilder.cpp layoutbuilder.h
- linecolumn.cpp linecolumn.h
link.cpp link.h
listmodel.h
listutils.h
@@ -124,6 +126,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
@@ -136,7 +139,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
@@ -144,6 +146,8 @@ 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
settingsselector.cpp settingsselector.h
@@ -167,12 +171,12 @@ 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
terminalcommand.cpp terminalcommand.h
- terminalprocess.cpp terminalprocess_p.h
+ terminalhooks.cpp terminalhooks.h
+ terminalinterface.cpp terminalinterface.h
textfieldcheckbox.cpp textfieldcheckbox.h
textfieldcombobox.cpp textfieldcombobox.h
textfileformat.cpp textfileformat.h
@@ -194,6 +198,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
@@ -260,7 +265,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
@@ -274,19 +279,10 @@ extend_qtc_library(Utils
)
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/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/archive.cpp b/src/libs/utils/archive.cpp
index 408c75d7c5..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 <QSettings>
@@ -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<QtcProcess> m_process;
+ std::unique_ptr<Process> m_process;
};
} // namespace Utils
diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp
index 72e667d76a..57d358a7c1 100644
--- a/src/libs/utils/aspects.cpp
+++ b/src/libs/utils/aspects.cpp
@@ -4,11 +4,13 @@
#include "aspects.h"
#include "algorithm.h"
+#include "checkablemessagebox.h"
#include "environment.h"
#include "fancylineedit.h"
#include "layoutbuilder.h"
#include "pathchooser.h"
#include "qtcassert.h"
+#include "qtcolorbutton.h"
#include "qtcsettings.h"
#include "utilstr.h"
#include "variablechooser.h"
@@ -29,7 +31,7 @@
#include <QSpinBox>
#include <QTextEdit>
-using namespace Utils::Layouting;
+using namespace Layouting;
namespace Utils {
namespace Internal {
@@ -88,11 +90,15 @@ public:
*/
/*!
- Constructs a BaseAspect.
+ Constructs a base aspect.
+
+ If \a container is non-null, the aspect is made known to the container.
*/
-BaseAspect::BaseAspect()
+BaseAspect::BaseAspect(AspectContainer *container)
: d(new Internal::BaseAspectPrivate)
{
+ if (container)
+ container->registerAspect(this);
addDataExtractor(this, &BaseAspect::value, &Data::value);
}
@@ -120,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)
{
@@ -133,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.
*/
@@ -151,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
@@ -209,17 +215,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);
- LayoutItem item(widget);
- item.span = std::max(d->m_spanX - 1, 1);
- builder.addItem(item);
+ parent.addItem(l);
+ parent.addItem(Span(std::max(d->m_spanX - 1, 1), LayoutItem(widget)));
} else {
- builder.addItem(LayoutItem(widget));
+ parent.addItem(LayoutItem(widget));
}
}
@@ -337,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.
@@ -368,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()
*/
@@ -378,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()
*/
@@ -417,13 +421,24 @@ 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(Layouting::LayoutBuilder &)
+void BaseAspect::addToLayout(LayoutItem &)
{
}
+void createItem(Layouting::LayoutItem *item, const BaseAspect &aspect)
+{
+ const_cast<BaseAspect &>(aspect).addToLayout(*item);
+}
+
+void createItem(Layouting::LayoutItem *item, const BaseAspect *aspect)
+{
+ const_cast<BaseAspect *>(aspect)->addToLayout(*item);
+}
+
+
/*!
Updates this aspect's value from user-initiated changes in the widget.
@@ -515,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)
{
@@ -524,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
{
@@ -575,8 +590,15 @@ class BoolAspectPrivate
{
public:
BoolAspect::LabelPlacement m_labelPlacement = BoolAspect::LabelPlacement::AtCheckBox;
- QPointer<QCheckBox> m_checkBox; // Owned by configuration widget
+ QPointer<QAbstractButton> m_button; // Owned by configuration widget
QPointer<QGroupBox> m_groupBox; // For BoolAspects handling GroupBox check boxes
+ bool m_buttonIsAdopted = false;
+};
+
+class ColorAspectPrivate
+{
+public:
+ QPointer<QtColorButton> m_colorButton; // Owned by configuration widget
};
class SelectionAspectPrivate
@@ -623,9 +645,12 @@ 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;
- EnvironmentChange m_environmentChange;
+ Environment m_environment;
QPointer<ElidingLabel> m_labelDisplay;
QPointer<FancyLineEdit> m_lineEditDisplay;
QPointer<PathChooser> m_pathChooserDisplay;
@@ -645,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<class Widget> void updateWidgetFromCheckStatus(StringAspect *aspect, Widget *w)
{
@@ -742,11 +768,11 @@ public:
*/
/*!
- Constructs a StringAspect.
+ Constructs the string aspect \a container.
*/
-StringAspect::StringAspect()
- : d(new Internal::StringAspectPrivate)
+StringAspect::StringAspect(AspectContainer *container)
+ : BaseAspect(container), d(new Internal::StringAspectPrivate)
{
setDefaultValue(QString());
setSpan(2, 1); // Default: Label + something
@@ -777,7 +803,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)
{
@@ -931,6 +957,34 @@ 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);
+}
+
+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.
*/
@@ -968,16 +1022,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)
@@ -1050,11 +1099,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.finishRow();
+ d->m_checker->addToLayout(parent);
+ parent.addItem(br);
}
const auto useMacroExpander = [this](QWidget *w) {
@@ -1075,9 +1124,13 @@ 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);
+ 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
@@ -1086,7 +1139,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) {
@@ -1094,7 +1147,7 @@ void StringAspect::addToLayout(Layouting::LayoutBuilder &builder)
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);
@@ -1102,7 +1155,7 @@ void StringAspect::addToLayout(Layouting::LayoutBuilder &builder)
} else {
connect(d->m_pathChooserDisplay, &PathChooser::textChanged,
this, [this](const QString &path) {
- setValue(path);
+ setValueQuietly(path);
});
}
}
@@ -1117,7 +1170,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) {
@@ -1147,7 +1200,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:
@@ -1159,7 +1212,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] {
@@ -1173,19 +1226,18 @@ 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
{
- QTC_CHECK(!isAutoApply());
switch (d->m_displayStyle) {
case PathChooserDisplay:
QTC_ASSERT(d->m_pathChooserDisplay, return {});
@@ -1287,6 +1339,92 @@ 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(AspectContainer *container)
+ : StringAspect(container)
+{
+ setDisplayStyle(PathChooserDisplay);
+}
+
+/*!
+ \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(AspectContainer *container)
+ : BaseAspect(container), d(new Internal::ColorAspectPrivate)
+{
+ setDefaultValue(QColor::fromRgb(0, 0, 0));
+ setSpan(1, 1);
+
+ addDataExtractor(this, &ColorAspect::value, &Data::value);
+}
+
+ColorAspect::~ColorAspect() = default;
+
+void ColorAspect::addToLayout(Layouting::LayoutItem &parent)
+{
+ QTC_CHECK(!d->m_colorButton);
+ d->m_colorButton = createSubWidget<QtColorButton>();
+ parent.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<QColor>();
+}
+
+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<QColor>());
+}
+
/*!
\class Utils::BoolAspect
\inmodule QtCreator
@@ -1302,54 +1440,68 @@ void StringAspect::makeCheckable(CheckBoxPlacement checkBoxPlacement,
*/
-BoolAspect::BoolAspect(const QString &settingsKey)
- : d(new Internal::BoolAspectPrivate)
+BoolAspect::BoolAspect(AspectContainer *container)
+ : BaseAspect(container), d(new Internal::BoolAspectPrivate)
{
setDefaultValue(false);
- setSettingsKey(settingsKey);
setSpan(2, 1);
addDataExtractor(this, &BoolAspect::value, &Data::value);
}
/*!
- \reimp
+ \internal
*/
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<QCheckBox>();
+ if (!d->m_buttonIsAdopted) {
+ QTC_CHECK(!d->m_button);
+ d->m_button = createSubWidget<QCheckBox>();
+ }
switch (d->m_labelPlacement) {
- case LabelPlacement::AtCheckBoxWithoutDummyLabel:
- d->m_checkBox->setText(labelText());
- builder.addItem(d->m_checkBox.data());
+ case LabelPlacement::AtCheckBox:
+ d->m_button->setText(labelText());
+ parent.addItem(d->m_button.data());
break;
- case LabelPlacement::AtCheckBox: {
- d->m_checkBox->setText(labelText());
- Layouting::LayoutBuilder::LayoutType type = builder.layoutType();
- if (type == LayoutBuilder::FormLayout)
- builder.addItem(createSubWidget<QLabel>());
- builder.addItem(d->m_checkBox.data());
- break;
- }
case LabelPlacement::InExtraLabel:
- addLabeledItem(builder, 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);
}
+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<void (QObject *)> BoolAspect::groupChecker()
+{
+ return [this](QObject *target) {
+ auto groupBox = qobject_cast<QGroupBox *>(target);
+ QTC_ASSERT(groupBox, return);
+ registerSubWidget(groupBox);
+ groupBox->setCheckable(true);
+ groupBox->setChecked(value());
+ d->m_groupBox = groupBox;
+ };
+}
+
QAction *BoolAspect::action()
{
if (hasAction())
@@ -1371,9 +1523,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);
@@ -1382,9 +1533,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());
}
@@ -1396,7 +1546,7 @@ void BoolAspect::emitChangedValue()
/*!
- \reimp
+ Returns the value of the boolean aspect as a boolean value.
*/
bool BoolAspect::value() const
@@ -1407,8 +1557,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());
@@ -1442,10 +1592,12 @@ void BoolAspect::setLabelPlacement(BoolAspect::LabelPlacement labelPlacement)
d->m_labelPlacement = labelPlacement;
}
-void BoolAspect::setHandlesGroup(QGroupBox *box)
+CheckableDecider BoolAspect::checkableDecider()
{
- registerSubWidget(box);
- d->m_groupBox = box;
+ return CheckableDecider(
+ [this] { return !value(); },
+ [this] { setValue(true); }
+ );
}
/*!
@@ -1459,21 +1611,21 @@ void BoolAspect::setHandlesGroup(QGroupBox *box)
QRadioButtons in a QButtonGroup.
*/
-SelectionAspect::SelectionAspect()
- : d(new Internal::SelectionAspectPrivate)
+SelectionAspect::SelectionAspect(AspectContainer *container)
+ : BaseAspect(container), d(new Internal::SelectionAspectPrivate)
{
setSpan(2, 1);
}
/*!
- \reimp
+ \internal
*/
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);
@@ -1489,7 +1641,7 @@ void SelectionAspect::addToLayout(Layouting::LayoutBuilder &builder)
button->setChecked(i == value());
button->setEnabled(option.enabled);
button->setToolTip(option.tooltip);
- builder.addItems({Layouting::empty, button});
+ parent.addItem(button);
d->m_buttons.append(button);
d->m_buttonGroup->addButton(button, i);
if (isAutoApply()) {
@@ -1511,14 +1663,13 @@ 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;
}
}
QVariant SelectionAspect::volatileValue() const
{
- QTC_CHECK(!isAutoApply());
switch (d->m_displayStyle) {
case DisplayStyle::RadioButtons:
QTC_ASSERT(d->m_buttonGroup, return {});
@@ -1532,7 +1683,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) {
@@ -1603,12 +1753,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)
@@ -1662,22 +1814,22 @@ 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);
}
/*!
- \reimp
+ \internal
*/
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())
@@ -1769,8 +1921,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);
@@ -1779,14 +1931,14 @@ IntegerAspect::IntegerAspect()
}
/*!
- \reimp
+ \internal
*/
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<QSpinBox>();
@@ -1799,11 +1951,11 @@ 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,
- this, [this] { setValue(d->m_spinBox->value()); });
+ this, [this] { setValue(d->m_spinBox->value() * d->m_displayScaleFactor); });
}
}
@@ -1830,7 +1982,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());
@@ -1905,22 +2057,22 @@ 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);
}
/*!
- \reimp
+ \internal
*/
DoubleAspect::~DoubleAspect() = default;
/*!
\reimp
*/
-void DoubleAspect::addToLayout(Layouting::LayoutBuilder &builder)
+void DoubleAspect::addToLayout(LayoutItem &builder)
{
QTC_CHECK(!d->m_spinBox);
d->m_spinBox = createSubWidget<QDoubleSpinBox>();
@@ -2001,7 +2153,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
@@ -2010,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);
@@ -2060,23 +2215,23 @@ 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());
}
/*!
- \reimp
+ \internal
*/
StringListAspect::~StringListAspect() = default;
/*!
\reimp
*/
-void StringListAspect::addToLayout(Layouting::LayoutBuilder &builder)
+void StringListAspect::addToLayout(LayoutItem &parent)
{
- Q_UNUSED(builder)
+ Q_UNUSED(parent)
// TODO - when needed.
}
@@ -2125,28 +2280,30 @@ void StringListAspect::removeValues(const QStringList &values)
/*!
\class Utils::IntegerListAspect
+ \internal
\inmodule QtCreator
\brief A string list aspect represents a property of some object
that is a list of strings.
*/
-IntegersAspect::IntegersAspect()
+IntegersAspect::IntegersAspect(AspectContainer *container)
+ : BaseAspect(container)
{
setDefaultValue({});
}
/*!
- \reimp
+ \internal
*/
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.
}
@@ -2180,6 +2337,7 @@ void IntegersAspect::setDefaultValue(const QList<int> &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
@@ -2188,6 +2346,10 @@ void IntegersAspect::setDefaultValue(const QList<int> &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.
@@ -2200,14 +2362,14 @@ TextDisplay::TextDisplay(const QString &message, InfoLabel::InfoType type)
}
/*!
- \reimp
+ \internal
*/
TextDisplay::~TextDisplay() = default;
/*!
\reimp
*/
-void TextDisplay::addToLayout(LayoutBuilder &builder)
+void TextDisplay::addToLayout(LayoutItem &parent)
{
if (!d->m_label) {
d->m_label = createSubWidget<InfoLabel>(d->m_message, d->m_type);
@@ -2219,7 +2381,7 @@ void TextDisplay::addToLayout(LayoutBuilder &builder)
if (!isVisible())
d->m_label->setVisible(false);
}
- builder.addItem(d->m_label.data());
+ parent.addItem(d->m_label.data());
}
/*!
@@ -2253,9 +2415,9 @@ namespace Internal {
class AspectContainerPrivate
{
public:
- QList<BaseAspect *> m_items; // Not owned
+ QList<BaseAspect *> m_items; // Both owned and non-owned.
+ QList<BaseAspect *> m_ownedItems; // Owned only.
bool m_autoApply = true;
- bool m_ownsSubAspects = false;
QStringList m_settingsGroup;
};
@@ -2266,21 +2428,22 @@ AspectContainer::AspectContainer(QObject *parent)
{}
/*!
- \reimp
+ \internal
*/
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)
@@ -2292,7 +2455,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
{
@@ -2365,10 +2528,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()
@@ -2396,11 +2564,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 413a17e624..b99c28c0be 100644
--- a/src/libs/utils/aspects.h
+++ b/src/libs/utils/aspects.h
@@ -9,28 +9,28 @@
#include "macroexpander.h"
#include "pathchooser.h"
-#include <QCoreApplication>
-
#include <functional>
#include <memory>
#include <optional>
QT_BEGIN_NAMESPACE
class QAction;
-class QGroupBox;
class QSettings;
QT_END_NAMESPACE
+namespace Layouting { class LayoutItem; }
+
namespace Utils {
class AspectContainer;
class BoolAspect;
-namespace Layouting { class LayoutBuilder; }
+class CheckableDecider;
namespace Internal {
class AspectContainerPrivate;
class BaseAspectPrivate;
class BoolAspectPrivate;
+class ColorAspectPrivate;
class DoubleAspectPrivate;
class IntegerAspectPrivate;
class MultiSelectionAspectPrivate;
@@ -45,7 +45,7 @@ class QTCREATOR_UTILS_EXPORT BaseAspect : public QObject
Q_OBJECT
public:
- BaseAspect();
+ BaseAspect(AspectContainer *container = nullptr);
~BaseAspect() override;
Id id() const;
@@ -98,7 +98,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);
@@ -116,7 +116,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
@@ -169,7 +169,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;
@@ -205,12 +205,15 @@ private:
std::unique_ptr<Internal::BaseAspectPrivate> d;
};
+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
{
Q_OBJECT
public:
- explicit BoolAspect(const QString &settingsKey = QString());
+ BoolAspect(AspectContainer *container = nullptr);
~BoolAspect() override;
struct Data : BaseAspect::Data
@@ -218,7 +221,9 @@ public:
bool value;
};
- void addToLayout(Layouting::LayoutBuilder &builder) override;
+ void addToLayout(Layouting::LayoutItem &parent) override;
+ std::function<void(QObject *)> groupChecker();
+ Utils::CheckableDecider checkableDecider();
QAction *action() override;
@@ -226,16 +231,18 @@ 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;
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);
- void setHandlesGroup(QGroupBox *box);
+
+ void adoptButton(QAbstractButton *button);
signals:
void valueChanged(bool newValue);
@@ -245,19 +252,45 @@ private:
std::unique_ptr<Internal::BoolAspectPrivate> d;
};
+class QTCREATOR_UTILS_EXPORT ColorAspect : public BaseAspect
+{
+ Q_OBJECT
+
+public:
+ ColorAspect(AspectContainer *container = nullptr);
+ ~ColorAspect() override;
+
+ struct Data : BaseAspect::Data
+ {
+ QColor value;
+ };
+
+ void addToLayout(Layouting::LayoutItem &parent) override;
+
+ QColor value() const;
+ void setValue(const QColor &val);
+
+ QVariant volatileValue() const override;
+ void setVolatileValue(const QVariant &val) override;
+
+private:
+ std::unique_ptr<Internal::ColorAspectPrivate> d;
+};
+
class QTCREATOR_UTILS_EXPORT SelectionAspect : public BaseAspect
{
Q_OBJECT
public:
- SelectionAspect();
+ SelectionAspect(AspectContainer *container = nullptr);
~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;
+ int operator()() const { return value(); }
int value() const;
void setValue(int val);
@@ -304,10 +337,10 @@ class QTCREATOR_UTILS_EXPORT MultiSelectionAspect : public BaseAspect
Q_OBJECT
public:
- MultiSelectionAspect();
+ MultiSelectionAspect(AspectContainer *container = nullptr);
~MultiSelectionAspect() override;
- void addToLayout(Layouting::LayoutBuilder &builder) override;
+ void addToLayout(Layouting::LayoutItem &parent) override;
enum class DisplayStyle { ListView };
void setDisplayStyle(DisplayStyle style);
@@ -327,7 +360,7 @@ class QTCREATOR_UTILS_EXPORT StringAspect : public BaseAspect
Q_OBJECT
public:
- StringAspect();
+ StringAspect(AspectContainer *container = nullptr);
~StringAspect() override;
struct Data : BaseAspect::Data
@@ -336,7 +369,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;
@@ -345,6 +378,8 @@ public:
// Hook between UI and StringAspect:
using ValueAcceptor = std::function<std::optional<QString>(const QString &, const QString &)>;
void setValueAcceptor(ValueAcceptor &&acceptor);
+
+ QString operator()() const { return value(); }
QString value() const;
void setValue(const QString &val);
@@ -355,9 +390,11 @@ public:
void setDisplayFilter(const std::function<QString (const QString &)> &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 setEnvironmentChange(const EnvironmentChange &change);
void setEnvironment(const Environment &env);
void setBaseFileName(const FilePath &baseFileName);
void setUndoRedoEnabled(bool readOnly);
@@ -369,6 +406,7 @@ public:
void setOpenTerminalHandler(const std::function<void()> &openTerminal);
void setAutoApplyOnEditingFinished(bool applyOnEditingFinished);
void setElideMode(Qt::TextElideMode elideMode);
+ void setAllowPathFromDevice(bool allowPathFromDevice);
void validateInput();
@@ -407,19 +445,28 @@ protected:
std::unique_ptr<Internal::StringAspectPrivate> d;
};
+class QTCREATOR_UTILS_EXPORT FilePathAspect : public StringAspect
+{
+public:
+ FilePathAspect(AspectContainer *container = nullptr);
+
+ FilePath operator()() const { return filePath(); }
+};
+
class QTCREATOR_UTILS_EXPORT IntegerAspect : public BaseAspect
{
Q_OBJECT
public:
- IntegerAspect();
+ IntegerAspect(AspectContainer *container = nullptr);
~IntegerAspect() override;
- void addToLayout(Layouting::LayoutBuilder &builder) override;
+ void addToLayout(Layouting::LayoutItem &parent) override;
QVariant volatileValue() const override;
void setVolatileValue(const QVariant &val) override;
+ qint64 operator()() const { return value(); }
qint64 value() const;
void setValue(qint64 val);
@@ -449,14 +496,15 @@ class QTCREATOR_UTILS_EXPORT DoubleAspect : public BaseAspect
Q_OBJECT
public:
- DoubleAspect();
+ DoubleAspect(AspectContainer *container = nullptr);
~DoubleAspect() override;
- void addToLayout(Layouting::LayoutBuilder &builder) override;
+ void addToLayout(Layouting::LayoutItem &parent) override;
QVariant volatileValue() const override;
void setVolatileValue(const QVariant &val) override;
+ double operator()() const { return value(); }
double value() const;
void setValue(double val);
@@ -501,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 = {});
@@ -517,10 +566,10 @@ class QTCREATOR_UTILS_EXPORT StringListAspect : public BaseAspect
Q_OBJECT
public:
- StringListAspect();
+ StringListAspect(AspectContainer *container = nullptr);
~StringListAspect() override;
- void addToLayout(Layouting::LayoutBuilder &builder) override;
+ void addToLayout(Layouting::LayoutItem &parent) override;
QStringList value() const;
void setValue(const QStringList &val);
@@ -539,12 +588,13 @@ class QTCREATOR_UTILS_EXPORT IntegersAspect : public BaseAspect
Q_OBJECT
public:
- IntegersAspect();
+ IntegersAspect(AspectContainer *container = nullptr);
~IntegersAspect() override;
- void addToLayout(Layouting::LayoutBuilder &builder) override;
+ void addToLayout(Layouting::LayoutItem &parent) override;
void emitChangedValue() override;
+ QList<int> operator()() const { return value(); }
QList<int> value() const;
void setValue(const QList<int> &value);
@@ -560,11 +610,12 @@ 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;
- void addToLayout(Layouting::LayoutBuilder &builder) override;
+ void addToLayout(Layouting::LayoutItem &parent) override;
void setIconType(InfoLabel::InfoType t);
void setText(const QString &message);
@@ -603,14 +654,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 <class Aspect, typename ...Args>
Aspect *addAspect(Args && ...args)
{
auto aspect = new Aspect(args...);
- registerAspect(aspect);
+ registerAspect(aspect, true);
return aspect;
}
@@ -631,7 +682,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 <typename T> T *aspect() const
@@ -661,6 +711,7 @@ public:
signals:
void applied();
+ void changed();
void fromMapFinished();
private:
diff --git a/src/libs/utils/async.cpp b/src/libs/utils/async.cpp
new file mode 100644
index 0000000000..33487db73b
--- /dev/null
+++ b/src/libs/utils/async.cpp
@@ -0,0 +1,55 @@
+// 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"
+
+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..f8dc6813a7
--- /dev/null
+++ b/src/libs/utils/async.h
@@ -0,0 +1,215 @@
+// 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 <solutions/tasking/tasktree.h>
+
+#include <QFutureWatcher>
+#include <QtConcurrent>
+
+namespace Utils {
+
+QTCREATOR_UTILS_EXPORT QThreadPool *asyncThreadPool(QThread::Priority priority);
+
+template <typename Function, typename ...Args>
+auto asyncRun(QThreadPool *threadPool, QThread::Priority priority,
+ Function &&function, Args &&...args)
+{
+ QThreadPool *pool = threadPool ? threadPool : asyncThreadPool(priority);
+ return QtConcurrent::run(pool, std::forward<Function>(function), std::forward<Args>(args)...);
+}
+
+template <typename Function, typename ...Args>
+auto asyncRun(QThread::Priority priority, Function &&function, Args &&...args)
+{
+ return asyncRun<Function, Args...>(nullptr, priority,
+ std::forward<Function>(function), std::forward<Args>(args)...);
+}
+
+template <typename Function, typename ...Args>
+auto asyncRun(QThreadPool *threadPool, Function &&function, Args &&...args)
+{
+ return asyncRun<Function, Args...>(threadPool, QThread::InheritPriority,
+ std::forward<Function>(function), std::forward<Args>(args)...);
+}
+
+template <typename Function, typename ...Args>
+auto asyncRun(Function &&function, Args &&...args)
+{
+ return asyncRun<Function, Args...>(nullptr, QThread::InheritPriority,
+ std::forward<Function>(function), std::forward<Args>(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 <typename R, typename T>
+const QFuture<T> &onResultReady(const QFuture<T> &future, R *receiver, void(R::*member)(const T &))
+{
+ auto watcher = new QFutureWatcher<T>();
+ 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 <typename T, typename Function>
+const QFuture<T> &onResultReady(const QFuture<T> &future, QObject *guard, Function f)
+{
+ auto watcher = new QFutureWatcher<T>();
+ 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<typename R, typename T>
+const QFuture<T> &onFinished(const QFuture<T> &future,
+ R *receiver, void (R::*member)(const QFuture<T> &))
+{
+ auto watcher = new QFutureWatcher<T>();
+ 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<typename T, typename Function>
+const QFuture<T> &onFinished(const QFuture<T> &future, QObject *guard, Function f)
+{
+ auto watcher = new QFutureWatcher<T>();
+ 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 <typename ResultType>
+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 <typename Function, typename ...Args>
+ void setConcurrentCallData(Function &&function, Args &&...args)
+ {
+ return wrapConcurrent(std::forward<Function>(function), std::forward<Args>(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<ResultType> future() const { return m_watcher.future(); }
+ ResultType result() const { return m_watcher.result(); }
+ ResultType resultAt(int index) const { return m_watcher.resultAt(index); }
+ QList<ResultType> results() const { return future().results(); }
+ bool isResultAvailable() const { return future().resultCount(); }
+
+private:
+ template <typename Function, typename ...Args>
+ void wrapConcurrent(Function &&function, Args &&...args)
+ {
+ m_startHandler = [=] {
+ return asyncRun(m_threadPool, m_priority, function, args...);
+ };
+ }
+
+ template <typename Function, typename ...Args>
+ void wrapConcurrent(std::reference_wrapper<const Function> &&wrapper, Args &&...args)
+ {
+ m_startHandler = [=] {
+ return asyncRun(m_threadPool, m_priority, std::forward<const Function>(wrapper.get()),
+ args...);
+ };
+ }
+
+ using StartHandler = std::function<QFuture<ResultType>()>;
+ StartHandler m_startHandler;
+ FutureSynchronizer *m_synchronizer = nullptr;
+ QThreadPool *m_threadPool = nullptr;
+ QThread::Priority m_priority = QThread::InheritPriority;
+ QFutureWatcher<ResultType> m_watcher;
+};
+
+template <typename ResultType>
+class AsyncTaskAdapter : public Tasking::TaskAdapter<Async<ResultType>>
+{
+public:
+ AsyncTaskAdapter() {
+ this->connect(this->task(), &AsyncBase::done, this, [this] {
+ emit this->done(!this->task()->isCanceled());
+ });
+ }
+ void start() final { this->task()->start(); }
+};
+
+} // namespace Utils
+
+TASKING_DECLARE_TEMPLATE_TASK(AsyncTask, Utils::AsyncTaskAdapter);
diff --git a/src/libs/utils/asynctask.cpp b/src/libs/utils/asynctask.cpp
deleted file mode 100644
index 3576ad804c..0000000000
--- a/src/libs/utils/asynctask.cpp
+++ /dev/null
@@ -1,8 +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"
-
-namespace Utils {
-
-} // namespace Utils
diff --git a/src/libs/utils/asynctask.h b/src/libs/utils/asynctask.h
deleted file mode 100644
index 1a6ae7d1b6..0000000000
--- a/src/libs/utils/asynctask.h
+++ /dev/null
@@ -1,93 +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 "runextensions.h"
-#include "tasktree.h"
-
-#include <QFutureWatcher>
-
-namespace Utils {
-
-class QTCREATOR_UTILS_EXPORT AsyncTaskBase : public QObject
-{
- Q_OBJECT
-
-signals:
- void started();
- void done();
-};
-
-template <typename ResultType>
-class AsyncTask : public AsyncTaskBase
-{
-public:
- AsyncTask() { connect(&m_watcher, &QFutureWatcherBase::finished, this, &AsyncTaskBase::done); }
- ~AsyncTask()
- {
- if (isDone())
- return;
-
- m_watcher.cancel();
- if (!m_synchronizer)
- m_watcher.waitForFinished();
- }
-
- using StartHandler = std::function<QFuture<ResultType>()>;
-
- template <typename Function, typename ...Args>
- 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; }
-
- 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<ResultType> future() const { return m_watcher.future(); }
- ResultType result() const { return m_watcher.result(); }
- QList<ResultType> results() const { return future().results(); }
- bool isResultAvailable() const { return future().resultCount(); }
-
-private:
- StartHandler m_startHandler;
- FutureSynchronizer *m_synchronizer = nullptr;
- QThreadPool *m_threadPool = nullptr;
- QThread::Priority m_priority = QThread::InheritPriority;
- QFutureWatcher<ResultType> m_watcher;
-};
-
-template <typename ResultType>
-class AsyncTaskAdapter : public Tasking::TaskAdapter<AsyncTask<ResultType>>
-{
-public:
- AsyncTaskAdapter() {
- this->connect(this->task(), &AsyncTaskBase::done, this, [this] {
- emit this->done(!this->task()->isCanceled());
- });
- }
- void start() final { this->task()->start(); }
-};
-
-} // namespace Utils
-
-QTC_DECLARE_CUSTOM_TEMPLATE_TASK(Async, AsyncTaskAdapter);
diff --git a/src/libs/utils/buildablehelperlibrary.cpp b/src/libs/utils/buildablehelperlibrary.cpp
index eac24cfdba..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 <QDebug>
#include <QRegularExpression>
@@ -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/checkablemessagebox.cpp b/src/libs/utils/checkablemessagebox.cpp
index 4420fc66cb..d3ede7d4e7 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"
@@ -17,10 +18,11 @@
/*!
\class Utils::CheckableMessageBox
+ \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.
@@ -30,435 +32,155 @@ 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 QSettings *theSettings;
+
+static QMessageBox::StandardButton exec(
+ QWidget *parent,
+ QMessageBox::Icon icon,
+ const QString &title,
+ const QString &text,
+ const CheckableDecider &decider,
+ QMessageBox::StandardButtons buttons,
+ QMessageBox::StandardButton defaultButton,
+ QMessageBox::StandardButton acceptButton,
+ QMap<QMessageBox::StandardButton, QString> buttonTextOverrides,
+ const QString &msg)
+{
+ 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("<a ")) {
+#if QT_VERSION >= 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<QAbstractButton *> buttons = d->buttonBox->buttons();
- for (QAbstractButton *b : buttons)
- if (auto *pb = qobject_cast<QPushButton *>(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<QMessageBox::StandardButton>(int(db));
-}
+ if (decider.shouldAskAgain) {
+ if (!decider.shouldAskAgain())
+ return acceptButton;
-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;
+ msgBox.setCheckBox(new QCheckBox);
+ msgBox.checkBox()->setChecked(false);
+ msgBox.checkBox()->setText(msg);
}
- return true;
-}
-
-enum DoNotAskAgainType{Question, Information};
-
-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);
-}
-
-void CheckableMessageBox::doNotAskAgain(QSettings *settings, const QString &settingsSubKey)
-{
- if (!settings)
- return;
- settings->beginGroup(QLatin1String(kDoNotAskAgainKey));
- settings->setValue(settingsSubKey, true);
- settings->endGroup();
+ 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.doNotAskAgain && msgBox.checkBox()->isChecked()
+ && (acceptButton == QMessageBox::NoButton || clickedBtn == acceptButton))
+ 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,
+ const CheckableDecider &decider,
+ QMessageBox::StandardButtons buttons,
+ QMessageBox::StandardButton defaultButton,
+ QMessageBox::StandardButton acceptButton,
+ QMap<QMessageBox::StandardButton, QString> buttonTextOverrides,
+ const QString &msg)
+{
+ return exec(parent,
+ QMessageBox::Question,
+ title,
+ question,
+ decider,
+ buttons,
+ defaultButton,
+ acceptButton,
+ buttonTextOverrides,
+ msg.isEmpty() ? msgDoNotAskAgain() : msg);
+}
+
+QMessageBox::StandardButton CheckableMessageBox::information(
+ QWidget *parent,
+ const QString &title,
+ const QString &text,
+ const CheckableDecider &decider,
+ QMessageBox::StandardButtons buttons,
+ QMessageBox::StandardButton defaultButton,
+ QMap<QMessageBox::StandardButton, QString> buttonTextOverrides,
+ const QString &msg)
+{
+ return exec(parent,
+ QMessageBox::Information,
+ title,
+ text,
+ decider,
+ buttons,
+ defaultButton,
+ QMessageBox::NoButton,
+ buttonTextOverrides,
+ msg.isEmpty() ? msgDoNotShowAgain() : msg);
}
/*!
- 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)
-
-{
- 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();
-}
-
-/*!
- 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);
- 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;
- }
- }
- settings->endGroup();
+ QTC_ASSERT(theSettings, return false);
+ theSettings->beginGroup(QLatin1String(kDoNotAskAgainKey));
+ const bool hasSuppressed = !theSettings->childKeys().isEmpty()
+ || !theSettings->childGroups().isEmpty();
+ theSettings->endGroup();
return hasSuppressed;
}
/*!
- 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()
{
@@ -466,12 +188,16 @@ 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()
{
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 eb80e15eac..297ff22b62 100644
--- a/src/libs/utils/checkablemessagebox.h
+++ b/src/libs/utils/checkablemessagebox.h
@@ -5,7 +5,7 @@
#include "utils_global.h"
-#include <QDialogButtonBox>
+#include <QMap>
#include <QMessageBox>
QT_BEGIN_NAMESPACE
@@ -14,102 +14,51 @@ QT_END_NAMESPACE
namespace Utils {
-class CheckableMessageBoxPrivate;
-
-class QTCREATOR_UTILS_EXPORT CheckableMessageBox : public QDialog
+class QTCREATOR_UTILS_EXPORT CheckableDecider
{
- 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;
+ CheckableDecider() = default;
+ CheckableDecider(const QString &settingsSubKey);
+ CheckableDecider(bool *doNotAskAgain);
+ CheckableDecider(const std::function<bool()> &should, const std::function<void()> &doNot)
+ : shouldAskAgain(should), doNotAskAgain(doNot)
+ {}
+
+ std::function<bool()> shouldAskAgain;
+ std::function<void()> doNotAskAgain;
+};
- // check and set "ask again" status
- static bool shouldAskAgain(QSettings *settings, const QString &settingsSubKey);
- static void doNotAskAgain(QSettings *settings, const QString &settingsSubKey);
+class QTCREATOR_UTILS_EXPORT CheckableMessageBox
+{
+public:
+ static QMessageBox::StandardButton question(
+ QWidget *parent,
+ const QString &title,
+ const QString &question,
+ const CheckableDecider &decider,
+ QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No,
+ QMessageBox::StandardButton defaultButton = QMessageBox::No,
+ QMessageBox::StandardButton acceptButton = QMessageBox::Yes,
+ QMap<QMessageBox::StandardButton, QString> buttonTextOverrides = {},
+ const QString &msg = {});
+
+ static QMessageBox::StandardButton information(
+ QWidget *parent,
+ const QString &title,
+ const QString &text,
+ const CheckableDecider &decider,
+ QMessageBox::StandardButtons buttons = QMessageBox::Ok,
+ QMessageBox::StandardButton defaultButton = QMessageBox::NoButton,
+ QMap<QMessageBox::StandardButton, QString> buttonTextOverrides = {},
+ const QString &msg = {});
// Conversion convenience
- static QMessageBox::StandardButton dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton);
- static void resetAllDoNotAskAgainQuestions(QSettings *settings);
- static bool hasSuppressedQuestions(QSettings *settings);
+ static void resetAllDoNotAskAgainQuestions();
+ static bool hasSuppressedQuestions();
static QString msgDoNotAskAgain();
static QString msgDoNotShowAgain();
-private:
- CheckableMessageBoxPrivate *d;
+ static void initialize(QSettings *settings);
};
} // namespace Utils
diff --git a/src/libs/utils/clangutils.cpp b/src/libs/utils/clangutils.cpp
index 3f2fb69909..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 <QVersionNumber>
@@ -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)
@@ -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;
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 6f8d4a4f5d..ba1cc4edc4 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.
@@ -195,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
@@ -217,7 +219,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.
@@ -253,7 +255,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)
@@ -362,12 +363,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 +413,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++) {
@@ -893,7 +894,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:
@@ -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.
@@ -1409,6 +1411,8 @@ CommandLine::CommandLine(const FilePath &executable)
: m_executable(executable)
{}
+CommandLine::~CommandLine() = default;
+
CommandLine::CommandLine(const FilePath &exe, const QStringList &args)
: m_executable(exe)
{
@@ -1429,16 +1433,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;
}
diff --git a/src/libs/utils/commandline.h b/src/libs/utils/commandline.h
index 23585adb8a..d7fc0a066b 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
@@ -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);
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/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/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/devicefileaccess.cpp b/src/libs/utils/devicefileaccess.cpp
index 3d8354d7a0..bd139d9b16 100644
--- a/src/libs/utils/devicefileaccess.cpp
+++ b/src/libs/utils/devicefileaccess.cpp
@@ -8,11 +8,12 @@
#include "environment.h"
#include "expected.h"
#include "hostosinfo.h"
+#include "osspecificaspects.h"
#include "qtcassert.h"
#include "utilstr.h"
#ifndef UTILS_STATIC_LIBRARY
-#include "qtcprocess.h"
+#include "process.h"
#endif
#include <QCoreApplication>
@@ -105,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))
@@ -153,7 +161,7 @@ expected_str<void> 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<void> copyRecursively_fallback(const FilePath &src, const FilePath &target)
@@ -189,25 +197,22 @@ expected_str<void> 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
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(":/");
@@ -215,13 +220,13 @@ expected_str<void> 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());
@@ -268,12 +273,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)
@@ -306,7 +305,7 @@ expected_str<QByteArray> 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<qint64> DeviceFileAccess::writeFileContents(const FilePath &filePath,
@@ -318,7 +317,7 @@ expected_str<qint64> 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
@@ -379,35 +378,12 @@ std::optional<FilePath> DeviceFileAccess::refersToExecutableFile(
return {};
}
-void DeviceFileAccess::asyncFileContents(const FilePath &filePath,
- const Continuation<expected_str<QByteArray>> &cont,
- qint64 limit,
- qint64 offset) const
-{
- cont(fileContents(filePath, limit, offset));
-}
-
-void DeviceFileAccess::asyncWriteFileContents(const FilePath &filePath,
- const Continuation<expected_str<qint64>> &cont,
- const QByteArray &data,
- qint64 offset) const
-{
- cont(writeFileContents(filePath, data, offset));
-}
-
-void DeviceFileAccess::asyncCopyFile(const FilePath &filePath,
- const Continuation<expected_str<void>> &cont,
- const FilePath &target) const
-{
- cont(copyFile(filePath, target));
-}
-
expected_str<FilePath> 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()));
}
@@ -506,6 +482,37 @@ 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;
+ }
+#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
+ return false;
+}
+
bool DesktopDeviceFileAccess::ensureWritableDirectory(const FilePath &filePath) const
{
const QFileInfo fi(filePath.path());
@@ -587,10 +594,13 @@ bool DesktopDeviceFileAccess::removeRecursively(const FilePath &filePath, QStrin
expected_str<void> 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
@@ -662,10 +672,10 @@ expected_str<QByteArray> 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);
@@ -690,14 +700,14 @@ expected_str<qint64> 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()));
@@ -708,12 +718,14 @@ expected_str<FilePath> 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();
@@ -818,12 +830,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;
@@ -882,6 +888,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();
@@ -964,7 +977,7 @@ expected_str<QByteArray> 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) {
@@ -1015,7 +1028,7 @@ expected_str<FilePath> 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.
@@ -1039,9 +1052,9 @@ expected_str<FilePath> 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)")
+ return make_unexpected(Tr::tr("Failed creating temporary file \"%1\" (too many tries).")
.arg(filePath.toUserOutput()));
}
} while (newPath.exists());
@@ -1054,12 +1067,6 @@ expected_str<FilePath> UnixDeviceFileAccess::createTempFile(const FilePath &file
return newPath;
}
-OsType UnixDeviceFileAccess::osType(const FilePath &filePath) const
-{
- Q_UNUSED(filePath)
- return OsTypeLinux;
-}
-
QDateTime UnixDeviceFileAccess::lastModified(const FilePath &filePath) const
{
const RunResult result = runInShell(
@@ -1069,10 +1076,19 @@ QDateTime UnixDeviceFileAccess::lastModified(const FilePath &filePath) const
return dt;
}
+QStringList UnixDeviceFileAccess::statArgs(const FilePath &filePath,
+ const QString &linuxFormat,
+ const QString &macFormat) const
+{
+ return (filePath.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 +1116,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 +1129,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 +1153,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),
+ filePath.osType() == OsTypeMac ? 8 : 16);
}
// returns whether 'find' could be used.
@@ -1152,8 +1172,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 = filePath.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 +1200,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 = filePath.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"
@@ -1210,7 +1238,7 @@ void UnixDeviceFileAccess::findUsingLs(const QString &current,
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();
@@ -1272,8 +1300,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
diff --git a/src/libs/utils/devicefileaccess.h b/src/libs/utils/devicefileaccess.h
index 8fe8201e08..0f8282477f 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;
@@ -30,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;
@@ -41,7 +46,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;
@@ -68,20 +72,6 @@ protected:
const QByteArray &data,
qint64 offset) const;
- virtual void asyncFileContents(const FilePath &filePath,
- const Continuation<expected_str<QByteArray>> &cont,
- qint64 limit,
- qint64 offset) const;
-
- virtual void asyncWriteFileContents(const FilePath &filePath,
- const Continuation<expected_str<qint64>> &cont,
- const QByteArray &data,
- qint64 offset) const;
-
- virtual void asyncCopyFile(const FilePath &filePath,
- const Continuation<expected_str<void>> &cont,
- const FilePath &target) const;
-
virtual expected_str<FilePath> createTempFile(const FilePath &filePath);
};
@@ -101,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;
@@ -110,7 +101,6 @@ protected:
expected_str<void> 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;
@@ -160,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;
@@ -169,7 +160,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;
@@ -204,6 +194,10 @@ private:
const FileFilter &filter,
QStringList *found) const;
+ QStringList statArgs(const FilePath &filePath,
+ const QString &linuxFormat,
+ const QString &macFormat) const;
+
mutable bool m_tryUseFind = true;
mutable std::optional<bool> m_hasMkTemp;
};
diff --git a/src/libs/utils/deviceshell.cpp b/src/libs/utils/deviceshell.cpp
index 18e816d38b..f96c5da8f2 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 <QLoggingCategory>
#include <QScopeGuard>
@@ -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 "<cmd-id> "<base64-encoded-stdin-data>" <commandline>\n"
* To stop the script, simply send "exit\n" via stdin
*
@@ -73,15 +73,15 @@ 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) {
- QtcProcess proc;
+ Process proc;
const CommandLine fallbackCmd = createFallbackCommand(cmd);
qCDebug(deviceShellLog) << "Running fallback:" << fallbackCmd;
proc.setCommand(fallbackCmd);
@@ -97,13 +97,14 @@ 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);
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)
@@ -114,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);
@@ -135,7 +137,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"});
}
@@ -170,8 +172,8 @@ void DeviceShell::startupFailed(const CommandLine &cmdLine)
*/
bool DeviceShell::start()
{
- m_shellProcess = std::make_unique<QtcProcess>();
- connect(m_shellProcess.get(), &QtcProcess::done, m_shellProcess.get(),
+ m_shellProcess = std::make_unique<Process>();
+ 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);
@@ -198,11 +200,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();
@@ -215,7 +217,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..e5bc4ad7af 100644
--- a/src/libs/utils/deviceshell.h
+++ b/src/libs/utils/deviceshell.h
@@ -7,7 +7,7 @@
#include "fileutils.h"
-#include <QMap>
+#include <QHash>
#include <QMutex>
#include <QProcess>
#include <QThread>
@@ -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,13 +73,12 @@ private:
QWaitCondition *waiter;
};
- std::unique_ptr<QtcProcess> m_shellProcess;
+ std::unique_ptr<Process> m_shellProcess;
QThread m_thread;
int m_currentId{0};
QMutex m_commandMutex;
- // QMap is used here to preserve iterators
- QMap<quint64, CommandRun> m_commandOutput;
+ QHash<quint64, CommandRun> m_commandOutput;
QByteArray m_commandBuffer;
State m_shellScriptState = State::Unknown;
diff --git a/src/libs/utils/differ.cpp b/src/libs/utils/differ.cpp
index 538aa6657a..9dc2ce8454 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 <QList>
-#include <QRegularExpression>
-#include <QStringList>
#include <QMap>
#include <QPair>
-#include <QFutureInterfaceBase>
+#include <QRegularExpression>
+#include <QStringList>
namespace Utils {
@@ -937,10 +936,9 @@ QString Diff::toString() const
///////////////
-Differ::Differ(QFutureInterfaceBase *jobController)
- : m_jobController(jobController)
+Differ::Differ(const std::optional<QFuture<void>> &future)
+ : m_future(future)
{
-
}
QList<Diff> Differ::diff(const QString &text1, const QString &text2)
@@ -1075,7 +1073,7 @@ QList<Diff> 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 && m_future->isCanceled()) {
delete [] forwardV;
delete [] reverseV;
return QList<Diff>();
@@ -1193,17 +1191,10 @@ QList<Diff> Differ::diffNonCharMode(const QString &text1, const QString &text2)
QString lastDelete;
QString lastInsert;
QList<Diff> 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<Diff>();
- }
- m_jobController->setProgressValue(i + 1);
+ if (m_future && 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..dc6d74f30d 100644
--- a/src/libs/utils/differ.h
+++ b/src/libs/utils/differ.h
@@ -5,12 +5,14 @@
#include "utils_global.h"
+#include <QFuture>
#include <QString>
+#include <optional>
+
QT_BEGIN_NAMESPACE
template <class K, class T>
class QMap;
-class QFutureInterfaceBase;
QT_END_NAMESPACE
namespace Utils {
@@ -42,7 +44,7 @@ public:
WordMode,
LineMode
};
- Differ(QFutureInterfaceBase *jobController = nullptr);
+ Differ(const std::optional<QFuture<void>> &future = {});
QList<Diff> diff(const QString &text1, const QString &text2);
QList<Diff> 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;
- QFutureInterfaceBase *m_jobController = nullptr;
+ std::optional<QFuture<void>> m_future;
};
} // namespace Utils
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<DropSupport::FileSpec> *fil
const QList<QUrl>::const_iterator cend = urls.constEnd();
for (QList<QUrl>::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/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/environment.cpp b/src/libs/utils/environment.cpp
index c99293329c..b9af2dea92 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 <QDir>
@@ -11,6 +12,13 @@
#include <QReadWriteLock>
#include <QSet>
+/*!
+ \class Utils::Environment
+ \inmodule QtCreator
+
+ \brief The Environment class sets \QC's system environment.
+*/
+
namespace Utils {
static QReadWriteLock s_envMutex;
@@ -18,83 +26,149 @@ Q_GLOBAL_STATIC_WITH_ARGS(Environment, staticSystemEnvironment,
(QProcessEnvironment::systemEnvironment().toStringList()))
Q_GLOBAL_STATIC(QVector<EnvironmentProvider>, 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 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<void(const QString &, const QString &, bool)> &callBack) const
+{
+ 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(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<AppendOrSet>(), 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<PrependOrSet>(), key, value, sep});
}
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(), QString(sep));
+ prependOrSet("PATH", value.nativePath(), sep);
break;
}
case OsTypeMac: {
- const QString sep = ":";
const QString nativeValue = value.nativePath();
prependOrSet("DYLD_LIBRARY_PATH", nativeValue, sep);
prependOrSet("DYLD_FRAMEWORK_PATH", nativeValue, sep);
@@ -102,8 +176,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:
@@ -118,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);
@@ -126,144 +207,24 @@ 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<SetupEnglishOutput>()});
}
-static FilePath searchInDirectory(const QStringList &execs,
- const FilePath &directory,
- QSet<FilePath> &alreadyChecked)
-{
- const int checkedCount = alreadyChecked.count();
- alreadyChecked.insert(directory);
-
- if (directory.isEmpty() || alreadyChecked.count() == checkedCount)
- return FilePath();
-
- for (const QString &exec : execs) {
- const FilePath filePath = directory.pathAppended(exec);
- if (filePath.isExecutableFile())
- return filePath;
- }
- return FilePath();
-}
-
-static QStringList appendExeExtensions(const Environment &env, const QString &executable)
-{
- QStringList 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()) {
- const QStringList extensions = env.expandedValueForKey("PATHEXT").split(';');
-
- for (const QString &ext : extensions)
- execs << executable + ext.toLower();
- }
- }
- return execs;
-}
+using SearchResultCallback = std::function<IterationPolicy(const FilePath &)>;
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)
-{
- if (executable.isEmpty())
- return FilePath();
-
- const QString exec = QDir::cleanPath(env.expandVariables(executable));
- const QFileInfo fi(exec);
-
- const QStringList 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);
- }
- return FilePath::fromString(exec);
- }
-
- QSet<FilePath> alreadyChecked;
- for (const FilePath &dir : dirs) {
- FilePath tmp = searchInDirectory(execs, dir, alreadyChecked);
- if (!tmp.isEmpty() && (!func || func(tmp)))
- return tmp;
- }
-
- if (usePath) {
- if (executable.contains('/'))
- return FilePath();
-
- for (const FilePath &p : env.path()) {
- FilePath tmp = searchInDirectory(execs, p, alreadyChecked);
- if (!tmp.isEmpty() && (!func || func(tmp)))
- return tmp;
- }
- }
- return FilePath();
-}
-
-FilePath Environment::searchInDirectories(const QString &executable,
- const FilePaths &dirs,
- const PathFilter &func) const
-{
- return searchInDirectoriesHelper(*this, executable, dirs, func, false);
+ const NameValueDictionary &dict = resolved();
+ return expandVariables(dict.value(key));
}
FilePath Environment::searchInPath(const QString &executable,
const FilePaths &additionalDirs,
- const PathFilter &func) const
-{
- return searchInDirectoriesHelper(*this, executable, additionalDirs, func, true);
-}
-
-FilePaths Environment::findAllInPath(const QString &executable,
- const FilePaths &additionalDirs,
- const Environment::PathFilter &func) const
+ const FilePathPredicate &filter) 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<FilePath> result;
- QSet<FilePath> alreadyChecked;
- for (const FilePath &dir : additionalDirs) {
- FilePath tmp = searchInDirectory(execs, dir, alreadyChecked);
- if (!tmp.isEmpty() && (!func || func(tmp)))
- result << tmp;
- }
-
- 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();
+ const FilePath exec = FilePath::fromUserInput(expandVariables(executable));
+ const FilePaths dirs = path() + additionalDirs;
+ return exec.searchInDirectories(dirs, filter, FilePath::WithAnySuffix);
}
FilePaths Environment::path() const
@@ -299,14 +260,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;
@@ -339,28 +302,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;
@@ -376,6 +341,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));
@@ -394,63 +365,145 @@ std::optional<EnvironmentProvider> 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<SetValue>(), QPair<QString, QString>{key, value}});
+ addItem(Item{std::in_place_index_t<SetValue>(),
+ std::tuple<QString, QString, bool>{key, value, enabled}});
}
-void EnvironmentChange::addUnsetValue(const QString &key)
+void Environment::setFallback(const QString &key, const QString &value)
{
- m_changeItems.append(Item{std::in_place_index_t<UnsetValue>(), key});
+ addItem(Item{std::in_place_index_t<SetFallbackValue>(),
+ std::tuple<QString, QString>{key, value}});
}
-void EnvironmentChange::addPrependToPath(const FilePaths &values)
+void Environment::unset(const QString &key)
{
+ addItem(Item{std::in_place_index_t<UnsetValue>(), key});
+}
+
+void Environment::modify(const NameValueItems &items)
+{
+ addItem(Item{std::in_place_index_t<Modify>(), 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<PrependToPath>(), value});
+ m_changeItems.append(Item{
+ std::in_place_index_t<PrependOrSet>(),
+ 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<AppendToPath>(), value});
+ m_dict.clear();
+ for (const FilePath &value : values) {
+ m_changeItems.append(Item{
+ std::in_place_index_t<AppendOrSet>(),
+ 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<SetFixedDictionary>(), dict});
- return change;
-}
+ if (m_dict.size() != 0)
+ return m_dict;
-void EnvironmentChange::applyToEnvironment(Environment &env) const
-{
+ m_fullDict = false;
for (const Item &item : m_changeItems) {
switch (item.index()) {
case SetSystemEnvironment:
- env = Environment::systemEnvironment();
+ m_dict = Environment::systemEnvironment().toDictionary();
+ m_fullDict = true;
break;
case SetFixedDictionary:
- env = Environment(std::get<SetFixedDictionary>(item));
+ m_dict = std::get<SetFixedDictionary>(item);
+ m_fullDict = true;
break;
case SetValue: {
- const QPair<QString, QString> data = std::get<SetValue>(item);
- env.set(data.first, data.second);
+ auto [key, value, enabled] = std::get<SetValue>(item);
+ m_dict.set(key, value, enabled);
+ break;
+ }
+ case SetFallbackValue: {
+ auto [key, value] = std::get<SetFallbackValue>(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:
- env.unset(std::get<UnsetValue>(item));
+ m_dict.unset(std::get<UnsetValue>(item));
break;
- case PrependToPath:
- env.prependOrSetPath(std::get<PrependToPath>(item));
+ case PrependOrSet: {
+ auto [key, value, sep] = std::get<PrependOrSet>(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<AppendToPath>(item));
+ }
+ case AppendOrSet: {
+ auto [key, value, sep] = std::get<AppendOrSet>(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<Modify>(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 2671e972cf..63fe697bd6 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 <functional>
#include <optional>
@@ -21,28 +22,25 @@ 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 setFallback(const QString &key, const QString &value);
+ void unset(const QString &key);
+ void modify(const NameValueItems &items);
bool hasChanges() const;
- void clear() { return m_dict.clear(); }
- 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,18 +52,15 @@ public:
void prependOrSetLibrarySearchPath(const FilePath &value);
void prependOrSetLibrarySearchPaths(const FilePaths &values);
+ void prependToPath(const FilePaths &values);
+ void appendToPath(const FilePaths &values);
+
void setupEnglishOutput();
+ void setupSudoAskPass(const FilePath &askPass);
- using PathFilter = std::function<bool(const FilePath &)>;
FilePath searchInPath(const QString &executable,
const FilePaths &additionalDirs = FilePaths(),
- const PathFilter &func = PathFilter()) const;
- FilePath searchInDirectories(const QString &executable,
- const FilePaths &dirs,
- const PathFilter &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;
@@ -75,80 +70,62 @@ public:
FilePath expandVariables(const FilePath &input) const;
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
+ NameValueDictionary toDictionary() const; // 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
+ struct Entry { QString key; QString value; bool enabled; };
+ using FindResult = std::optional<Entry>;
+ FindResult find(const QString &name) const; // Note res->key may differ in case from name.
- void setCombineWithDeviceEnvironment(bool combine) { m_combineWithDeviceEnvironment = combine; }
- bool combineWithDeviceEnvironment() const { return m_combineWithDeviceEnvironment; }
+ void forEachEntry(const std::function<void (const QString &, const QString &, bool)> &callBack) const;
- 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
-
- 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,
+ SetFallbackValue,
UnsetValue,
- PrependToPath,
- AppendToPath,
+ PrependOrSet,
+ AppendOrSet,
+ Modify,
+ SetupEnglishOutput
};
using Item = std::variant<
- std::monostate, // SetSystemEnvironment dummy
- NameValueDictionary, // SetFixedDictionary
- QPair<QString, QString>, // SetValue
- QString, // UnsetValue
- FilePath, // PrependToPath
- FilePath // AppendToPath
+ std::monostate, // SetSystemEnvironment dummy
+ NameValueDictionary, // SetFixedDictionary
+ std::tuple<QString, QString, bool>, // SetValue (key, value, enabled)
+ std::tuple<QString, QString>, // SetFallbackValue (key, value)
+ QString, // UnsetValue (key)
+ std::tuple<QString, QString, QString>, // PrependOrSet (key, value, separator)
+ std::tuple<QString, QString, QString>, // AppendOrSet (key, value, separator)
+ NameValueItems, // Modify
+ std::monostate, // SetupEnglishOutput
+ FilePath // SetupSudoAskPass (file path of qtc-askpass or ssh-askpass)
>;
- 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<Item> m_changeItems;
+ mutable QList<Item> m_changeItems;
+ mutable NameValueDictionary m_dict; // Latest resolved.
+ mutable bool m_fullDict = false;
};
+using EnviromentChange = Environment;
+
class QTCREATOR_UTILS_EXPORT EnvironmentProvider
{
public:
diff --git a/src/libs/utils/externalterminalprocessimpl.cpp b/src/libs/utils/externalterminalprocessimpl.cpp
new file mode 100644
index 0000000000..efdbc9aca3
--- /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 <QTemporaryFile>
+
+namespace Utils {
+
+ExternalTerminalProcessImpl::ExternalTerminalProcessImpl()
+{
+ setStubCreator(new ProcessStubCreator(this));
+}
+
+ProcessStubCreator::ProcessStubCreator(TerminalInterface *interface)
+ : m_interface(interface)
+{}
+
+expected_str<qint64> 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 = 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<qint64> startStubProcess(const ProcessSetupData &setupData) override;
+
+ TerminalInterface *m_interface;
+};
+
+} // namespace Utils
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 a0d1fd9b8a..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 <QApplication>
#include <QKeyEvent>
#include <QKeySequence>
#include <QMenu>
@@ -26,6 +27,7 @@
/*!
\class Utils::FancyLineEdit
+ \inmodule QtCreator
\brief The FancyLineEdit class is an enhanced line edit with several
opt-in features.
@@ -115,6 +117,7 @@ public:
const QColor m_okTextColor;
const QColor m_errorTextColor;
+ const QColor m_placeholderTextColor;
QString m_errorMessage;
};
@@ -123,7 +126,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(QApplication::palette().color(QPalette::PlaceholderText))
+
{
m_completionShortcut.setContext(Qt::WidgetShortcut);
connect(completionShortcut(), &CompletionShortcut::keyChanged,
@@ -172,7 +177,6 @@ FancyLineEdit::FancyLineEdit(QWidget *parent) :
CompletingLineEdit(parent),
d(new FancyLineEditPrivate(this))
{
- ensurePolished();
updateMargins();
connect(d->m_iconbutton[Left], &QAbstractButton::clicked, this, [this] {
@@ -486,15 +490,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)
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..957f694179 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.
@@ -109,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
{
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 88c239a2ee..9059a37ecc 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"
@@ -36,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
@@ -90,7 +93,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()
@@ -116,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 <device>" 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()
@@ -154,7 +157,7 @@ FilePath::FilePath()
}
/*!
- Constructs a FilePath from \a info
+ Constructs a FilePath from \a info.
*/
FilePath FilePath::fromFileInfo(const QFileInfo &info)
{
@@ -162,13 +165,18 @@ FilePath FilePath::fromFileInfo(const QFileInfo &info)
}
/*!
- \returns a QFileInfo
+ Returns a QFileInfo.
*/
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<FilePath>()
@@ -214,7 +222,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().
@@ -233,13 +241,16 @@ QString FilePath::toString() const
if (!needsDevice())
return path();
+ if (pathView().isEmpty())
+ return scheme() + "://" + encodedHost();
+
if (isRelativePath())
return scheme() + "://" + encodedHost() + "/./" + pathView();
return scheme() + "://" + encodedHost() + pathView();
}
/*!
- \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.
@@ -255,6 +266,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();
@@ -272,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.
@@ -289,7 +303,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.
@@ -343,7 +357,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.
@@ -355,7 +369,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"
@@ -370,7 +384,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
@@ -393,7 +407,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 '.'.
@@ -431,9 +445,10 @@ 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_hash = 0;
m_data = path.toString() + scheme.toString() + host.toString();
m_schemeLen = scheme.size();
m_hostLen = host.size();
@@ -441,7 +456,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
{
@@ -449,7 +464,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
{
@@ -457,13 +472,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);
@@ -474,17 +496,21 @@ 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);
}
/*!
- \returns a bool indicating on whether a process with this FilePath's
- .nativePath() is likely to start.
+ Returns a bool indicating on whether a process with this FilePath's
+ 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> FilePath::refersToExecutableFile(MatchScope matchScope) const
{
@@ -496,14 +522,14 @@ expected_str<FilePath> 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()));
}
@@ -550,6 +576,19 @@ bool FilePath::isSymLink() const
return fileAccess()->isSymLink(*this);
}
+bool FilePath::hasHardLinks() const
+{
+ return fileAccess()->hasHardLinks(*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 ensureWritableDir()
+*/
bool FilePath::createDir() const
{
return fileAccess()->createDirectory(*this);
@@ -586,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
@@ -620,13 +657,6 @@ bool FilePath::ensureReachable(const FilePath &other) const
return false;
}
-void FilePath::asyncFileContents(const Continuation<const expected_str<QByteArray> &> &cont,
- qint64 maxSize,
- qint64 offset) const
-{
- return fileAccess()->asyncFileContents(*this, cont, maxSize, offset);
-}
-
expected_str<qint64> FilePath::writeFileContents(const QByteArray &data, qint64 offset) const
{
return fileAccess()->writeFileContents(*this, data, offset);
@@ -637,11 +667,21 @@ FilePathInfo FilePath::filePathInfo() const
return fileAccess()->filePathInfo(*this);
}
-void FilePath::asyncWriteFileContents(const Continuation<const expected_str<qint64> &> &cont,
- const QByteArray &data,
- qint64 offset) const
+FileStreamHandle FilePath::asyncCopy(const FilePath &target, QObject *context,
+ const CopyContinuation &cont) const
+{
+ return FileStreamerManager::copy(*this, target, context, cont);
+}
+
+FileStreamHandle FilePath::asyncRead(QObject *context, const ReadContinuation &cont) const
+{
+ return FileStreamerManager::read(*this, context, cont);
+}
+
+FileStreamHandle FilePath::asyncWrite(const QByteArray &data, QObject *context,
+ const WriteContinuation &cont) const
{
- return fileAccess()->asyncWriteFileContents(*this, cont, data, offset);
+ return FileStreamerManager::write(*this, data, context, cont);
}
bool FilePath::needsDevice() const
@@ -679,20 +719,39 @@ bool FilePath::isSameFile(const FilePath &other) const
return false;
}
-static FilePaths appendExeExtensions(const Environment &env, const FilePath &executable)
+static FilePaths appendExeExtensions(const FilePath &executable,
+ FilePath::MatchScope matchScope)
{
- 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());
+ 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
@@ -703,9 +762,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))
@@ -716,7 +774,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
{
@@ -774,13 +832,154 @@ 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;
-/*! Find the parent directory of a given directory.
+ int i = len - 1;
+ QVarLengthArray<char16_t> outVector(len);
+ int used = len;
+ char16_t *out = outVector.data();
+ const ushort *p = reinterpret_cast<const ushort *>(name.data());
+ const ushort *prefix = p;
+ int up = 0;
+
+ const int prefixLength = name.at(0) == u'/' ? 1 : 0;
- Returns an empty FilePath if the current directory is already
+ 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;
+}
+
+/*!
+ Finds the parent directory of the file path.
+
+ 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
{
@@ -848,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 <device>" 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:
+ "<path> <args> on <device>".
+*/
QString FilePath::displayName(const QString &args) const
{
QString deviceName;
@@ -1010,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)
{
@@ -1034,20 +1242,21 @@ 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)
{
- 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));
}
/*!
- 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)
{
@@ -1068,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
@@ -1075,7 +1290,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
{
@@ -1097,7 +1312,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
{
@@ -1105,7 +1320,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
{
@@ -1113,7 +1328,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
{
@@ -1124,7 +1339,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
{
@@ -1135,10 +1351,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
{
@@ -1153,7 +1370,7 @@ FilePath FilePath::relativeChildPath(const FilePath &parent) const
}
/*!
- \returns the relative path of FilePath from a given \a anchor.
+ Returns the relative path of FilePath from a given \a anchor.
Both, FilePath and anchor may be files or directories.
Example usage:
@@ -1198,8 +1415,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 relative path 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");
@@ -1207,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)
{
@@ -1247,33 +1466,31 @@ 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 \a newPath object on the
+ same device as the current object.
- same device as \a deviceTemplate. The FilePath needs to be local.
+ 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.
-
- \returns A path on the same device as \a deviceTemplate.
*/
-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;
}
/*!
@@ -1295,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
@@ -1305,32 +1523,121 @@ 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,
+ 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<FilePath> 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 PathFilter &filter) const
+FilePaths FilePath::searchAllInDirectories(const FilePaths &dirs,
+ const FilePathPredicate &filter,
+ MatchScope matchScope) const
{
- if (isAbsolutePath())
- return *this;
- FilePaths directories = deviceEnvironment().path();
- if (needsDevice()) {
- directories = Utils::transform(directories, [this](const FilePath &path) {
- return path.onDevice(*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;
}
+
+ QSet<FilePath> 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 searchInDirectories(directories, filter);
+
+ 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
@@ -1342,6 +1649,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);
@@ -1359,11 +1676,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<QStringList>(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)
@@ -1416,7 +1739,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
@@ -1429,7 +1756,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
{
@@ -1468,26 +1795,6 @@ expected_str<void> FilePath::copyFile(const FilePath &target) const
return fileAccess()->copyFile(*this, target);
}
-void FilePath::asyncCopyFile(const Continuation<const expected_str<void> &> &cont,
- const FilePath &target) const
-{
- if (host() != target.host()) {
- asyncFileContents([cont, target](const expected_str<QByteArray> &contents) {
- if (contents)
- target.asyncWriteFileContents(
- [cont](const expected_str<qint64> &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);
@@ -1504,16 +1811,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.
+ Returns whether at least one file in the file path has a newer date than
+ \a timeStamp.
*/
bool FilePath::isNewerThan(const QDateTime &timeStamp) const
{
@@ -1532,7 +1839,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.
*/
@@ -1551,7 +1857,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
{
@@ -1561,7 +1867,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
{
@@ -1577,7 +1883,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
{
@@ -1593,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
{
@@ -1607,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;
@@ -1655,7 +1962,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
@@ -1669,7 +1976,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
{
@@ -1684,9 +1991,9 @@ 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.
+ Returns true if the path is relative.
*/
bool FilePath::isRelativePath() const
{
@@ -1701,10 +2008,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 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
{
@@ -1716,10 +2022,9 @@ FilePath FilePath::resolvePath(const FilePath &tail) 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 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
{
@@ -1737,7 +2042,7 @@ expected_str<FilePath> 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
@@ -1782,150 +2087,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<char16_t> outVector(len);
- int used = len;
- char16_t *out = outVector.data();
- const ushort *p = reinterpret_cast<const ushort *>(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)
@@ -2021,6 +2183,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();
@@ -2058,9 +2223,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::CaseInsensitive)
- return qHash(filePath.path().toCaseFolded(), seed);
- return qHash(filePath.path(), seed);
+ 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 4c61786eba..f313ba5ff9 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 <QDir>
#include <QDirIterator>
@@ -31,9 +32,12 @@ namespace Utils {
class DeviceFileAccess;
class Environment;
-class EnvironmentChange;
+enum class FileStreamHandle;
template <class ...Args> using Continuation = std::function<void(Args...)>;
+using CopyContinuation = Continuation<const expected_str<void> &>;
+using ReadContinuation = Continuation<const expected_str<QByteArray> &>;
+using WriteContinuation = Continuation<const expected_str<qint64> &>;
class QTCREATOR_UTILS_EXPORT FileFilter
{
@@ -51,8 +55,6 @@ public:
using FilePaths = QList<class FilePath>;
-enum class IterationPolicy { Stop, Continue };
-
class QTCREATOR_UTILS_EXPORT FilePath
{
public:
@@ -116,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;
@@ -158,11 +161,10 @@ 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 PathFilter &filter = {}) const;
[[nodiscard]] Environment deviceEnvironment() const;
- [[nodiscard]] FilePath onDevice(const FilePath &deviceTemplate) const;
+ [[nodiscard]] FilePaths devicePathEnvironmentVariable() const;
[[nodiscard]] FilePath withNewPath(const QString &newPath) const;
+ [[nodiscard]] FilePath withNewMappedPath(const FilePath &newPath) const;
using IterateDirCallback
= std::variant<
@@ -180,12 +182,24 @@ public:
const FileFilter &filter);
enum PathAmending { AppendToPath, PrependToPath };
+ enum MatchScope { ExactMatchOnly, WithExeSuffix, WithBatSuffix,
+ WithExeOrBatSuffix, WithAnySuffix };
+
+ [[nodiscard]] FilePath searchInDirectories(const FilePaths &dirs,
+ const FilePathPredicate &filter = {},
+ MatchScope matchScope = WithAnySuffix) const;
+ [[nodiscard]] FilePaths searchAllInDirectories(const FilePaths &dirs,
+ const FilePathPredicate &filter = {},
+ MatchScope matchScope = WithAnySuffix) const;
[[nodiscard]] FilePath searchInPath(const FilePaths &additionalDirs = {},
PathAmending = AppendToPath,
- const PathFilter &filter = {}) const;
+ const FilePathPredicate &filter = {},
+ MatchScope matchScope = WithAnySuffix) const;
+ [[nodiscard]] FilePaths searchAllInPath(const FilePaths &additionalDirs = {},
+ PathAmending = AppendToPath,
+ const FilePathPredicate &filter = {},
+ MatchScope matchScope = WithAnySuffix) const;
- enum MatchScope { ExactMatchOnly, WithExeSuffix, WithBatSuffix,
- WithExeOrBatSuffix, WithAnySuffix };
std::optional<FilePath> refersToExecutableFile(MatchScope considerScript) const;
[[nodiscard]] expected_str<FilePath> tmpDir() const;
@@ -207,14 +221,11 @@ public:
static void sort(FilePaths &files);
// Asynchronous interface
- void asyncCopyFile(const Continuation<const expected_str<void> &> &cont,
- const FilePath &target) const;
- void asyncFileContents(const Continuation<const expected_str<QByteArray> &> &cont,
- qint64 maxSize = -1,
- qint64 offset = 0) const;
- void asyncWriteFileContents(const Continuation<const expected_str<qint64> &> &cont,
- const QByteArray &data,
- qint64 offset = 0) const;
+ FileStreamHandle asyncCopy(const FilePath &target, QObject *context,
+ const CopyContinuation &cont = {}) const;
+ FileStreamHandle asyncRead(QObject *context, const ReadContinuation &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
@@ -277,6 +288,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
@@ -291,8 +303,12 @@ public:
std::function<bool(const FilePath &left, const FilePath &right)> isSameDevice;
std::function<expected_str<FilePath>(const FilePath &)> localSource;
std::function<void(const FilePath &, const Environment &)> openTerminal;
+ std::function<OsType(const FilePath &)> osType;
};
+// For testing
+QTCREATOR_UTILS_EXPORT QString doCleanPath(const QString &input);
+
} // Utils
Q_DECLARE_METATYPE(Utils::FilePath)
diff --git a/src/libs/utils/filesearch.cpp b/src/libs/utils/filesearch.cpp
index ccbcfbfb9f..a72ddc6ab0 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"
@@ -68,7 +69,7 @@ public:
FileSearch(const QString &searchTerm,
QTextDocument::FindFlags flags,
const QMap<FilePath, QString> &fileToContentsMap);
- void operator()(QFutureInterface<FileSearchResultList> &futureInterface,
+ void operator()(QFutureInterface<SearchResultItems> &futureInterface,
const FileIterator::Item &item) const;
private:
@@ -90,7 +91,7 @@ public:
QTextDocument::FindFlags flags,
const QMap<FilePath, QString> &fileToContentsMap);
FileSearchRegExp(const FileSearchRegExp &other);
- void operator()(QFutureInterface<FileSearchResultList> &futureInterface,
+ void operator()(QFutureInterface<SearchResultItems> &futureInterface,
const FileIterator::Item &item) const;
private:
@@ -116,7 +117,7 @@ FileSearch::FileSearch(const QString &searchTerm,
termDataUpper = searchTermUpper.constData();
}
-void FileSearch::operator()(QFutureInterface<FileSearchResultList> &futureInterface,
+void FileSearch::operator()(QFutureInterface<SearchResultItems> &futureInterface,
const FileIterator::Item &item) const
{
if (futureInterface.isCanceled())
@@ -124,7 +125,7 @@ void FileSearch::operator()(QFutureInterface<FileSearchResultList> &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;
@@ -189,13 +190,13 @@ void FileSearch::operator()(QFutureInterface<FileSearchResultList> &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
}
}
@@ -237,7 +238,7 @@ QRegularExpressionMatch FileSearchRegExp::doGuardedMatch(const QString &line, in
return expression.match(line, offset);
}
-void FileSearchRegExp::operator()(QFutureInterface<FileSearchResultList> &futureInterface,
+void FileSearchRegExp::operator()(QFutureInterface<SearchResultItems> &futureInterface,
const FileIterator::Item &item) const
{
if (!expression.isValid()) {
@@ -249,7 +250,7 @@ void FileSearchRegExp::operator()(QFutureInterface<FileSearchResultList> &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;
@@ -269,12 +270,13 @@ void FileSearchRegExp::operator()(QFutureInterface<FileSearchResultList> &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();
@@ -298,12 +300,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<FileSearchResultList> &futureInterface,
+SearchState initFileSearch(QFutureInterface<SearchResultItems> &futureInterface,
const QString &searchTerm, FileIterator *files)
{
futureInterface.setProgressRange(0, files->maxProgress());
@@ -311,9 +313,9 @@ SearchState initFileSearch(QFutureInterface<FileSearchResultList> &futureInterfa
return SearchState(searchTerm, files);
}
-void collectSearchResults(QFutureInterface<FileSearchResultList> &futureInterface,
+void collectSearchResults(QFutureInterface<SearchResultItems> &futureInterface,
SearchState &state,
- const FileSearchResultList &results)
+ const SearchResultItems &results)
{
state.numMatches += results.size();
state.cachedResults << results;
@@ -332,7 +334,7 @@ void collectSearchResults(QFutureInterface<FileSearchResultList> &futureInterfac
}
}
-void cleanUpFileSearch(QFutureInterface<FileSearchResultList> &futureInterface,
+void cleanUpFileSearch(QFutureInterface<SearchResultItems> &futureInterface,
SearchState &state)
{
if (!state.cachedResults.isEmpty()) {
@@ -355,13 +357,13 @@ void cleanUpFileSearch(QFutureInterface<FileSearchResultList> &futureInterface,
} // namespace
-QFuture<FileSearchResultList> Utils::findInFiles(const QString &searchTerm,
- FileIterator *files,
- QTextDocument::FindFlags flags,
- const QMap<FilePath, QString> &fileToContentsMap)
+QFuture<SearchResultItems> Utils::findInFiles(const QString &searchTerm,
+ FileIterator *files,
+ QTextDocument::FindFlags flags,
+ const QMap<FilePath, QString> &fileToContentsMap)
{
return mapReduce(files->begin(), files->end(),
- [searchTerm, files](QFutureInterface<FileSearchResultList> &futureInterface) {
+ [searchTerm, files](QFutureInterface<SearchResultItems> &futureInterface) {
return initFileSearch(futureInterface, searchTerm, files);
},
FileSearch(searchTerm, flags, fileToContentsMap),
@@ -369,14 +371,14 @@ QFuture<FileSearchResultList> Utils::findInFiles(const QString &searchTerm,
&cleanUpFileSearch);
}
-QFuture<FileSearchResultList> Utils::findInFilesRegExp(
+QFuture<SearchResultItems> Utils::findInFilesRegExp(
const QString &searchTerm,
FileIterator *files,
QTextDocument::FindFlags flags,
const QMap<FilePath, QString> &fileToContentsMap)
{
return mapReduce(files->begin(), files->end(),
- [searchTerm, files](QFutureInterface<FileSearchResultList> &futureInterface) {
+ [searchTerm, files](QFutureInterface<SearchResultItems> &futureInterface) {
return initFileSearch(futureInterface, searchTerm, files);
},
FileSearchRegExp(searchTerm, flags, fileToContentsMap),
@@ -496,16 +498,6 @@ static bool isFileIncluded(const QList<QRegularExpression> &filterRegs,
return isIncluded && (exclusionRegs.isEmpty() || !matches(exclusionRegs, filePath));
}
-std::function<bool(const FilePath &)> filterFileFunction(const QStringList &filters,
- const QStringList &exclusionFilters)
-{
- const QList<QRegularExpression> filterRegs = filtersToRegExps(filters);
- const QList<QRegularExpression> exclusionRegs = filtersToRegExps(exclusionFilters);
- return [filterRegs, exclusionRegs](const FilePath &filePath) {
- return isFileIncluded(filterRegs, exclusionRegs, filePath);
- };
-}
-
std::function<FilePaths(const FilePaths &)> filterFilesFunction(const QStringList &filters,
const QStringList &exclusionFilters)
{
@@ -538,10 +530,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)
@@ -598,19 +592,20 @@ FileIterator::const_iterator FileIterator::end() const
// #pragma mark -- FileListIterator
-QTextCodec *encodingAt(const QList<QTextCodec *> &encodings, int index)
+QList<FileIterator::Item> constructItems(const FilePaths &fileList,
+ const QList<QTextCodec *> &encodings)
{
- if (index >= 0 && index < encodings.size())
- return encodings.at(index);
- return QTextCodec::codecForLocale();
+ QList<FileIterator::Item> 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<QTextCodec *> encodings)
- : m_maxIndex(-1)
+FileListIterator::FileListIterator(const FilePaths &fileList, const QList<QTextCodec *> &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..fc6ce62ab0 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 <QDir>
#include <QMap>
#include <QSet>
#include <QStack>
@@ -24,10 +24,6 @@ QT_END_NAMESPACE
namespace Utils {
QTCREATOR_UTILS_EXPORT
-std::function<bool(const FilePath &)> filterFileFunction(const QStringList &filterRegs,
- const QStringList &exclusionRegs);
-
-QTCREATOR_UTILS_EXPORT
std::function<FilePaths(const FilePaths &)> filterFilesFunction(const QStringList &filters,
const QStringList &exclusionFilters);
@@ -40,8 +36,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
{
@@ -107,7 +108,8 @@ protected:
class QTCREATOR_UTILS_EXPORT FileListIterator : public FileIterator
{
public:
- explicit FileListIterator(const FilePaths &fileList, const QList<QTextCodec *> encodings);
+ explicit FileListIterator(const FilePaths &fileList = {},
+ const QList<QTextCodec *> &encodings = {});
int maxProgress() const override;
int currentProgress() const override;
@@ -118,8 +120,8 @@ protected:
const Item &itemAt(int index) const override;
private:
- QVector<Item> m_items;
- int m_maxIndex;
+ const QList<Item> m_items;
+ int m_maxIndex = -1;
};
class QTCREATOR_UTILS_EXPORT SubDirFileIterator : public FileIterator
@@ -151,54 +153,20 @@ private:
QList<Item *> 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 &regexpCapturedTexts)
- : 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<FileSearchResult>;
-
-QTCREATOR_UTILS_EXPORT QFuture<FileSearchResultList> findInFiles(
- const QString &searchTerm,
+QTCREATOR_UTILS_EXPORT QFuture<SearchResultItems> findInFiles(const QString &searchTerm,
FileIterator *files,
QTextDocument::FindFlags flags,
- const QMap<FilePath, QString> &fileToContentsMap = QMap<FilePath, QString>());
+ const QMap<FilePath, QString> &fileToContentsMap = {});
-QTCREATOR_UTILS_EXPORT QFuture<FileSearchResultList> findInFilesRegExp(
+QTCREATOR_UTILS_EXPORT QFuture<SearchResultItems> findInFilesRegExp(
const QString &searchTerm,
FileIterator *files,
QTextDocument::FindFlags flags,
- const QMap<FilePath, QString> &fileToContentsMap = QMap<FilePath, QString>());
+ const QMap<FilePath, QString> &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/filestreamer.cpp b/src/libs/utils/filestreamer.cpp
new file mode 100644
index 0000000000..693849b7c5
--- /dev/null
+++ b/src/libs/utils/filestreamer.cpp
@@ -0,0 +1,491 @@
+// 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 "async.h"
+#include "process.h"
+
+#include <solutions/tasking/barrier.h>
+
+#include <QFile>
+#include <QMutex>
+#include <QMutexLocker>
+#include <QWaitCondition>
+
+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<TaskTree> m_taskTree;
+
+private:
+ virtual TaskItem remoteTask() = 0;
+ virtual TaskItem localTask() = 0;
+};
+
+static void localRead(QPromise<QByteArray> &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](Process &process) {
+ const QStringList args = {"if=" + m_filePath.path()};
+ const FilePath dd = m_filePath.withNewPath("dd");
+ process.setCommand({dd, args, OsType::OsTypeLinux});
+ Process *processPtr = &process;
+ connect(processPtr, &Process::readyReadStandardOutput, this, [this, processPtr] {
+ emit readyRead(processPtr->readAllRawStandardOutput());
+ });
+ };
+ return ProcessTask(setup);
+ }
+ TaskItem localTask() final {
+ const auto setup = [this](Async<QByteArray> &async) {
+ async.setConcurrentCallData(localRead, m_filePath);
+ Async<QByteArray> *asyncPtr = &async;
+ connect(asyncPtr, &AsyncBase::resultReadyAt, this, [=](int index) {
+ emit readyRead(asyncPtr->resultAt(index));
+ });
+ };
+ return AsyncTask<QByteArray>(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<void> &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();
+ // m_writeBuffer is a child of either Process or Async<void>. 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<void>, coming from
+ // localTask(), the d'tor of Async<void>, run by FileStreamBase, busy waits for the
+ // already canceled here WriteBuffer to finish before deleting WriteBuffer child.
+ }
+
+ 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](Process &process) {
+ m_writeBuffer = new WriteBuffer(false, &process);
+ connect(m_writeBuffer, &WriteBuffer::writeRequested, &process, &Process::writeRaw);
+ connect(m_writeBuffer, &WriteBuffer::closeWriteChannelRequested,
+ &process, &Process::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, &Process::started, this, [this] { emit started(); });
+ };
+ const auto finalize = [this](const Process &) {
+ delete m_writeBuffer;
+ m_writeBuffer = nullptr;
+ };
+ return ProcessTask(setup, finalize, finalize);
+ }
+ TaskItem localTask() final {
+ const auto setup = [this](Async<void> &async) {
+ m_writeBuffer = new WriteBuffer(isBuffered(), &async);
+ async.setConcurrentCallData(localWrite, m_filePath, m_writeData, m_writeBuffer);
+ emit started();
+ };
+ const auto finalize = [this](const Async<void> &) {
+ delete m_writeBuffer;
+ m_writeBuffer = nullptr;
+ };
+ return AsyncTask<void>(setup, finalize, finalize);
+ }
+
+ bool isBuffered() const { return m_writeData.isEmpty(); }
+ QByteArray m_writeData;
+ WriteBuffer *m_writeBuffer = nullptr;
+};
+
+class FileStreamReaderAdapter : public TaskAdapter<FileStreamReader>
+{
+public:
+ FileStreamReaderAdapter() { connect(task(), &FileStreamBase::done, this, &TaskInterface::done); }
+ void start() override { task()->start(); }
+};
+
+class FileStreamWriterAdapter : public TaskAdapter<FileStreamWriter>
+{
+public:
+ FileStreamWriterAdapter() { connect(task(), &FileStreamBase::done, this, &TaskInterface::done); }
+ void start() override { task()->start(); }
+};
+
+} // namespace Utils
+
+TASKING_DECLARE_TASK(FileStreamReaderTask, Utils::FileStreamReaderAdapter);
+TASKING_DECLARE_TASK(FileStreamWriterTask, Utils::FileStreamWriterAdapter);
+
+namespace Utils {
+
+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](Process &process) {
+ const QStringList args = {source.path(), destination.path()};
+ const FilePath cp = source.withNewPath("cp");
+ process.setCommand({cp, args, OsType::OsTypeLinux});
+ };
+ return {ProcessTask(setup)};
+}
+
+static Group interDeviceTransferTask(const FilePath &source, const FilePath &destination)
+{
+ struct TransferStorage { QPointer<FileStreamWriter> writer; };
+ SingleBarrier writerReadyBarrier;
+ TreeStorage<TransferStorage> 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 &) {
+ if (storage->writer) // writer may be deleted before the reader on TaskTree::stop().
+ storage->writer->closeWriteChannel();
+ };
+ const auto setupWriter = [=](FileStreamWriter &writer) {
+ writer.setFilePath(destination);
+ QObject::connect(&writer, &FileStreamWriter::started,
+ writerReadyBarrier->barrier(), &Barrier::advance);
+ QTC_CHECK(storage->writer == nullptr);
+ storage->writer = &writer;
+ };
+
+ const Group root {
+ Storage(writerReadyBarrier),
+ parallel,
+ Storage(storage),
+ FileStreamWriterTask(setupWriter),
+ Group {
+ WaitForBarrierTask(writerReadyBarrier),
+ FileStreamReaderTask(setupReader, finalizeReader, finalizeReader)
+ }
+ };
+
+ 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<void> &promise, const FilePath &source, const FilePath &destination)
+{
+ if (promise.isCanceled())
+ return;
+
+ if (!TaskTree::runBlocking(transferTask(source, destination), promise.future()))
+ 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<TaskTree> 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 FileStreamReaderTask(setup);
+ }
+ TaskItem writerTask() {
+ const auto setup = [this](FileStreamWriter &writer) {
+ writer.setFilePath(m_destination);
+ writer.setWriteData(m_writeBuffer);
+ };
+ return FileStreamWriterTask(setup);
+ }
+ TaskItem transferTask() {
+ const auto setup = [this](Async<void> &async) {
+ async.setConcurrentCallData(transfer, m_source, m_destination);
+ };
+ return AsyncTask<void>(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..a5b6ff4f25
--- /dev/null
+++ b/src/libs/utils/filestreamer.h
@@ -0,0 +1,63 @@
+// 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 <solutions/tasking/tasktree.h>
+
+#include <QObject>
+
+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 FileStreamerTaskAdapter : public Tasking::TaskAdapter<FileStreamer>
+{
+public:
+ FileStreamerTaskAdapter() { connect(task(), &FileStreamer::done, this,
+ [this] { emit done(task()->result() == StreamResult::FinishedWithSuccess); }); }
+ void start() override { task()->start(); }
+};
+
+} // namespace Utils
+
+TASKING_DECLARE_TASK(FileStreamerTask, Utils::FileStreamerTaskAdapter);
diff --git a/src/libs/utils/filestreamermanager.cpp b/src/libs/utils/filestreamermanager.cpp
new file mode 100644
index 0000000000..11d05faee5
--- /dev/null
+++ b/src/libs/utils/filestreamermanager.cpp
@@ -0,0 +1,201 @@
+// 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 <QMutex>
+#include <QMutexLocker>
+#include <QThread>
+#include <QWaitCondition>
+
+#include <unordered_map>
+
+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<FileStreamHandle, FileStreamer *> 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<void(FileStreamer *)> &onSetup,
+ const std::function<void(FileStreamer *)> &onDone,
+ QObject *context)
+{
+ FileStreamer *streamer = new FileStreamer;
+ onSetup(streamer);
+ const FileStreamHandle handle = generateUniqueHandle();
+ QTC_CHECK(context == nullptr || context->thread() == QThread::currentThread());
+ 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();
+ });
+ 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..f86a3db480
--- /dev/null
+++ b/src/libs/utils/filestreamermanager.h
@@ -0,0 +1,42 @@
+// 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 <QObject>
+
+QT_BEGIN_NAMESPACE
+class QByteArray;
+QT_END_NAMESPACE
+
+namespace Utils {
+
+enum class FileStreamHandle : int {};
+
+class QTCREATOR_UTILS_EXPORT FileStreamerManager
+{
+public:
+ 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/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<int, QByteArray> 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/filesystemwatcher.cpp b/src/libs/utils/filesystemwatcher.cpp
index e0cef5aed9..155f83fba0 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.
@@ -187,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) :
@@ -197,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) :
@@ -276,15 +277,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())
@@ -312,15 +317,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())
@@ -419,13 +428,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)) {
qCDebug(fileSystemWatcherLog)
<< this << "triggers on file" << it.key()
<< 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)
@@ -451,9 +474,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);
}
}
diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp
index 1f6d89c43a..2cecc2810b 100644
--- a/src/libs/utils/fileutils.cpp
+++ b/src/libs/utils/fileutils.cpp
@@ -5,7 +5,7 @@
#include "savefile.h"
#include "algorithm.h"
-#include "hostosinfo.h"
+#include "devicefileaccess.h"
#include "qtcassert.h"
#include "utilstr.h"
@@ -194,12 +194,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() ?
@@ -258,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.
@@ -590,8 +593,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 {
@@ -621,18 +623,24 @@ 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 {});
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)
@@ -661,13 +669,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();
@@ -806,14 +814,14 @@ 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) {
- 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'),
diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h
index f34206d8b7..a1a7ffef97 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<QWidget *()> &getter);
@@ -121,7 +121,6 @@ public:
QString *selectedFilter = nullptr,
QFileDialog::Options options = {});
#endif
-
};
// for actually finding out if e.g. directories are writable on Windows
@@ -232,9 +231,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
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;
diff --git a/src/libs/utils/filewizardpage.cpp b/src/libs/utils/filewizardpage.cpp
index aae79b1a49..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
@@ -45,7 +46,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/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, '/');
}
diff --git a/src/libs/utils/fsengine/fileiconprovider.cpp b/src/libs/utils/fsengine/fileiconprovider.cpp
index e5ed4a1281..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.
@@ -216,49 +217,11 @@ 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<QIcon> icon = getIcon(m_filenameCache, filename);
- if (icon)
- return *icon;
- }
-
- const QString suffix = !isDir ? filePath.suffix() : QString();
- if (!suffix.isEmpty()) {
- const std::optional<QIcon> 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()));
}
/*!
+ \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.
*/
@@ -269,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)
@@ -288,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)
@@ -296,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.
*/
@@ -305,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.
*/
@@ -314,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/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.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 <QMutex>
+
#include <memory>
namespace Utils {
@@ -29,46 +31,65 @@ bool FSEngine::isAvailable()
#endif
}
-FilePaths FSEngine::registeredDeviceRoots()
+template<class T>
+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<QMutex> m_locker;
+};
+
+static Locked<Utils::FilePaths> deviceRoots()
{
- return FSEngine::deviceRoots();
+ static FilePaths g_deviceRoots;
+ static QMutex mutex;
+ return {&mutex, g_deviceRoots};
}
-void FSEngine::addDevice(const FilePath &deviceRoot)
+static Locked<QStringList> 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
@@ -34,10 +34,6 @@ public:
static QStringList registeredDeviceSchemes();
private:
- static Utils::FilePaths &deviceRoots();
- static QStringList &deviceSchemes();
-
-private:
std::unique_ptr<Internal::FSEngineHandler> m_engineHandler;
};
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..e020eebdda 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,29 +49,30 @@ QAbstractFileEngine *FSEngineHandler::create(const QString &fileName) const
return rootFilePath.pathAppended(scheme);
});
- return new FixedListFSEngine(rootFilePath, paths);
+ return new FixedListFSEngine(removeDoubleSlash(fileName), paths);
}
if (fixedFileName.startsWith(rootPath)) {
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;
});
- return new FixedListFSEngine(rootFilePath.pathAppended(scheme), filteredRoots);
+ return new FixedListFSEngine(removeDoubleSlash(fileName), filteredRoots);
}
}
- FilePath filePath = FilePath::fromString(fixedFileName);
- 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)
- return new RootInjectFSEngine(fixedFileName);
+ return new RootInjectFSEngine(fileName);
return nullptr;
}
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/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<QFuture<void>> 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
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/images/iconoverlay_close_small.png b/src/libs/utils/images/iconoverlay_close_small.png
new file mode 100644
index 0000000000..e39b67cfbb
--- /dev/null
+++ b/src/libs/utils/images/iconoverlay_close_small.png
Binary files 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
--- /dev/null
+++ b/src/libs/utils/images/iconoverlay_close_small@2x.png
Binary files differ
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
--- /dev/null
+++ b/src/libs/utils/images/pinned_small.png
Binary files 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
--- /dev/null
+++ b/src/libs/utils/images/pinned_small@2x.png
Binary files differ
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/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<pid_t>(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/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..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);
@@ -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;
@@ -257,6 +259,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);
}
@@ -619,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/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp
index e7136c5a90..bea7faafd3 100644
--- a/src/libs/utils/layoutbuilder.cpp
+++ b/src/libs/utils/layoutbuilder.cpp
@@ -3,36 +3,188 @@
#include "layoutbuilder.h"
-#include "aspects.h"
-#include "qtcassert.h"
-
+#include <QDebug>
#include <QFormLayout>
#include <QGridLayout>
#include <QGroupBox>
+#include <QLabel>
#include <QPushButton>
#include <QStackedLayout>
+#include <QSpacerItem>
+#include <QSpinBox>
#include <QSplitter>
#include <QStyle>
#include <QTabWidget>
-#include <QWidget>
+#include <QTextEdit>
+#include <QApplication>
+
+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)
+
+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<QWidget *>(parent);
+ return pw->style()->pixelMetric(pm, nullptr, pw);
+ } else {
+ return static_cast<QLayout *>(parent)->spacing();
+ }
+ }
-namespace Utils::Layouting {
+ QList<QLayoutItem *> itemList;
+ int m_hSpace;
+ int m_vSpace;
+};
/*!
- \enum Utils::LayoutBuilder::LayoutType
+ \namespace Layouting
\inmodule QtCreator
- The LayoutType enum describes the type of \c QLayout a layout builder
- operates on.
-
- \value Form
- \value Grid
- \value HBox
- \value VBox
+ \brief The Layouting namespace contains classes for use with layout builders.
*/
+
/*!
- \class Utils::LayoutBuilder::LayoutItem
+ \class Layouting::LayoutItem
\inmodule QtCreator
\brief The LayoutItem class represents widgets, layouts, and aggregate
@@ -46,83 +198,59 @@ namespace Utils::Layouting {
/*!
Constructs a layout item instance representing an empty cell.
*/
-LayoutItem::LayoutItem()
-{}
+LayoutItem::LayoutItem() = default;
+LayoutItem::~LayoutItem() = default;
-/*!
- 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)
-{}
+ \fn template <class T> LayoutItem(const T &t)
+ \internal
-/*!
- Constructs a layout item representing a \c BaseAspect.
-
- 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)
-{}
+struct ResultItem
+{
+ ResultItem() = default;
+ explicit ResultItem(QLayout *l) : layout(l) {}
+ explicit ResultItem(QWidget *w) : widget(w) {}
-/*!
- Constructs a layout item containing some static \a text.
- */
-LayoutItem::LayoutItem(const QString &text)
- : text(text)
-{}
+ QString text;
+ QLayout *layout = nullptr;
+ QWidget *widget = nullptr;
+ int space = -1;
+ int stretch = -1;
+ int span = 1;
+};
-QLayout *LayoutBuilder::createLayout() const
+struct Slice
{
+ Slice() = default;
+ Slice(QLayout *l) : layout(l) {}
+ Slice(QWidget *w) : widget(w) {}
+
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();
+
+ // Grid-specific
+ int currentGridColumn = 0;
+ int currentGridRow = 0;
+ bool isFormAlignment = false;
+ Qt::Alignment align = {}; // Can be changed to
+
+ // Grid or Form
+ QList<ResultItem> pendingItems;
+};
static QWidget *widgetForItem(QLayoutItem *item)
{
@@ -147,18 +275,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 {
@@ -166,138 +292,205 @@ static void addItemToBoxLayout(QBoxLayout *layout, const LayoutItem &item)
}
}
-static void flushPendingFormItems(QFormLayout *formLayout,
- LayoutBuilder::LayoutItems &pendingFormItems)
+static void addItemToFlowLayout(FlowLayout *layout, const ResultItem &item)
{
- QTC_ASSERT(formLayout, return);
-
- if (pendingFormItems.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 (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 (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())
+ return;
- // 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<QLabel *>(l->widget())) {
- if (QWidget *widget = widgetForItem(f))
- label->setBuddy(widget);
+ if (auto formLayout = qobject_cast<QFormLayout *>(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));
}
- }
- pendingFormItems.clear();
-}
+ 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);
+ }
-static void doLayoutHelper(QLayout *layout,
- const LayoutBuilder::LayoutItems &items,
- const Layouting::AttachType attachType,
- int currentGridRow = 0)
-{
- int currentGridColumn = 0;
- LayoutBuilder::LayoutItems pendingFormItems;
-
- auto formLayout = qobject_cast<QFormLayout *>(layout);
- auto gridLayout = qobject_cast<QGridLayout *>(layout);
- auto boxLayout = qobject_cast<QBoxLayout *>(layout);
- auto stackLayout = qobject_cast<QStackedLayout *>(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;
- }
+ // 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<QLabel *>(l->widget())) {
+ if (QWidget *widget = widgetForItem(f))
+ label->setBuddy(widget);
}
- continue;
}
- QWidget *widget = item.widget;
+ } else if (auto gridLayout = qobject_cast<QGridLayout *>(layout)) {
- 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);
+ for (const ResultItem &item : std::as_const(pendingItems)) {
+ 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, align);
+ gridLayout->addLayout(item.layout, currentGridRow, currentGridColumn, 1, item.span, a);
else if (!item.text.isEmpty())
- gridLayout->addWidget(createLabel(item.text), currentGridRow, currentGridColumn, 1, 1, align);
+ gridLayout->addWidget(createLabel(item.text), currentGridRow, currentGridColumn, 1, 1, a);
currentGridColumn += item.span;
- } else if (boxLayout) {
+ }
+ ++currentGridRow;
+ currentGridColumn = 0;
+
+ } else if (auto boxLayout = qobject_cast<QBoxLayout *>(layout)) {
+
+ for (const ResultItem &item : std::as_const(pendingItems))
addItemToBoxLayout(boxLayout, item);
- } else if (stackLayout) {
- stackLayout->addWidget(item.widget);
- } else {
- pendingFormItems.append(item);
+
+ } else if (auto flowLayout = qobject_cast<FlowLayout *>(layout)) {
+
+ for (const ResultItem &item : std::as_const(pendingItems))
+ addItemToFlowLayout(flowLayout, item);
+
+ } else if (auto stackLayout = qobject_cast<QStackedLayout *>(layout)) {
+ for (const ResultItem &item : std::as_const(pendingItems)) {
+ if (item.widget)
+ stackLayout->addWidget(item.widget);
+ else
+ QTC_CHECK(false);
}
+
+ } else {
+ QTC_CHECK(false);
}
- if (formLayout)
- flushPendingFormItems(formLayout, pendingFormItems);
+ pendingItems.clear();
}
+// LayoutBuilder
-/*!
- Constructs a layout item from the contents of another LayoutBuilder
- */
-LayoutItem::LayoutItem(const LayoutBuilder &builder)
+class LayoutBuilder
{
- layout = builder.createLayout();
- doLayoutHelper(layout, builder.m_items, Layouting::WithoutMargins);
+ Q_DISABLE_COPY_MOVE(LayoutBuilder)
+
+public:
+ LayoutBuilder();
+ ~LayoutBuilder();
+
+ void addItem(const LayoutItem &item);
+ void addItems(const LayoutItems &items);
+
+ QList<Slice> stack;
+};
+
+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);
+ }
+
+ for (const LayoutItem &subItem : item.subItems)
+ addItemHelper(builder, subItem);
+
+ if (item.onExit)
+ item.onExit(builder);
}
+void doAddText(LayoutBuilder &builder, const QString &text)
+{
+ ResultItem fi;
+ fi.text = text;
+ builder.stack.last().pendingItems.append(fi);
+}
+
+void doAddSpace(LayoutBuilder &builder, const Space &space)
+{
+ ResultItem fi;
+ fi.space = space.space;
+ builder.stack.last().pendingItems.append(fi);
+}
+
+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));
+}
+
+void doAddWidget(LayoutBuilder &builder, QWidget *widget)
+{
+ 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 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 Stretch class represents some stretch in a layout.
*/
/*!
- \class Utils::LayoutBuilder
+ \class Layouting::LayoutBuilder
+ \internal
\inmodule QtCreator
\brief The LayoutBuilder class provides a convenient way to fill \c QFormLayout
@@ -307,241 +500,409 @@ LayoutItem::LayoutItem(const LayoutBuilder &builder)
A LayoutBuilder instance is typically used locally within a function and never stored.
- \sa addItem(), addItems(), addRow(), finishRow()
+ \sa addItem(), addItems()
*/
-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;
/*!
+ \internal
Destructs a layout builder.
*/
LayoutBuilder::~LayoutBuilder() = default;
-/*!
- Instructs a layout builder to finish the current row.
- This is implicitly called by LayoutBuilder's destructor.
- */
-LayoutBuilder &LayoutBuilder::finishRow()
+void LayoutBuilder::addItem(const LayoutItem &item)
{
- addItem(Break());
- return *this;
+ addItemHelper(*this, item);
}
-/*!
- 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)
+void LayoutBuilder::addItems(const LayoutItems &items)
{
- return finishRow().addItem(item);
+ for (const LayoutItem &item : items)
+ addItemHelper(*this, item);
}
/*!
- 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 finishRow(), addItem(), addItems()
+ \sa addItem(), addItems()
*/
-LayoutBuilder &LayoutBuilder::addRow(const LayoutItems &items)
+void LayoutItem::addRow(const LayoutItems &items)
{
- return finishRow().addItems(items);
+ addItem(br);
+ 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.aspect) {
- item.aspect->addToLayout(*this);
- if (m_layoutType == FormLayout || m_layoutType == VBoxLayout)
- finishRow();
- } 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);
}
/*!
- 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.
*/
-void LayoutBuilder::attachTo(QWidget *w, Layouting::AttachType attachType) const
+
+void LayoutItem::attachTo(QWidget *w) const
{
- doLayout(w, attachType);
+ LayoutBuilder builder;
+
+ builder.stack.append(w);
+ addItemHelper(builder, *this);
}
-QWidget *LayoutBuilder::emerge(Layouting::AttachType attachType)
+QWidget *LayoutItem::emerge()
{
auto w = new QWidget;
- doLayout(w, attachType);
+ attachTo(w);
return w;
}
-/*!
- Constructs a layout extender to extend an existing \a layout.
+static void layoutExit(LayoutBuilder &builder)
+{
+ builder.stack.last().flush();
+ QLayout *layout = builder.stack.last().layout;
+ builder.stack.pop_back();
- 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.
- */
+ if (QWidget *widget = builder.stack.last().widget)
+ widget->setLayout(layout);
+ else
+ builder.stack.last().pendingItems.append(ResultItem(layout));
+}
-LayoutExtender::LayoutExtender(QLayout *layout, Layouting::AttachType attachType)
- : m_layout(layout), m_attachType(attachType)
-{}
+static void widgetExit(LayoutBuilder &builder)
+{
+ QWidget *widget = builder.stack.last().widget;
+ builder.stack.pop_back();
+ builder.stack.last().pendingItems.append(ResultItem(widget));
+}
-LayoutExtender::~LayoutExtender()
+Column::Column(std::initializer_list<LayoutItem> items)
{
- QTC_ASSERT(m_layout, return);
- int currentGridRow = 0;
- if (auto gridLayout = qobject_cast<QGridLayout *>(m_layout))
- currentGridRow = gridLayout->rowCount();
- doLayoutHelper(m_layout, m_items, m_attachType, currentGridRow);
+ subItems = items;
+ onAdd = [](LayoutBuilder &builder) { builder.stack.append(new QVBoxLayout); };
+ onExit = layoutExit;
}
-// Special items
+Row::Row(std::initializer_list<LayoutItem> items)
+{
+ subItems = items;
+ onAdd = [](LayoutBuilder &builder) { builder.stack.append(new QHBoxLayout); };
+ onExit = layoutExit;
+}
-Break::Break()
+Flow::Flow(std::initializer_list<LayoutItem> items)
{
- specialType = SpecialType::Break;
+ subItems = items;
+ onAdd = [](LayoutBuilder &builder) { builder.stack.append(new FlowLayout); };
+ onExit = layoutExit;
}
-Stretch::Stretch(int stretch)
+Grid::Grid(std::initializer_list<LayoutItem> items)
{
- specialType = SpecialType::Stretch;
- specialValue = stretch;
+ subItems = items;
+ onAdd = [](LayoutBuilder &builder) { builder.stack.append(new QGridLayout); };
+ onExit = layoutExit;
}
-Space::Space(int space)
+static QFormLayout *newFormLayout()
{
- specialType = SpecialType::Space;
- specialValue = space;
+ auto formLayout = new QFormLayout;
+ formLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
+ return formLayout;
}
-Span::Span(int span_, const LayoutItem &item)
+Form::Form(std::initializer_list<LayoutItem> items)
{
- LayoutItem::operator=(item);
- span = span_;
+ subItems = items;
+ onAdd = [](LayoutBuilder &builder) { builder.stack.append(newFormLayout()); };
+ onExit = layoutExit;
}
-Tab::Tab(const QString &tabName, const LayoutBuilder &item)
+Stack::Stack(std::initializer_list<LayoutItem> items)
{
- text = tabName;
- widget = new QWidget;
- item.attachTo(widget);
+ subItems = items;
+ onAdd = [](LayoutBuilder &builder) { builder.stack.append(new QStackedLayout); };
+ onExit = layoutExit;
}
-HorizontalRule::HorizontalRule()
+LayoutItem br()
{
- specialType = SpecialType::HorizontalRule;
+ LayoutItem item;
+ item.onAdd = [](LayoutBuilder &builder) {
+ builder.stack.last().flush();
+ };
+ return item;
}
-// "Widgets"
+LayoutItem empty()
+{
+ LayoutItem item;
+ item.onAdd = [](LayoutBuilder &builder) {
+ ResultItem ri;
+ ri.span = 1;
+ builder.stack.last().pendingItems.append(ResultItem());
+ };
+ return item;
+}
-static void applyItems(QWidget *widget, const QList<LayoutItem> &items)
+LayoutItem hr()
{
- bool hadLayout = false;
- for (const LayoutItem &item : items) {
- if (item.setter) {
- item.setter(widget);
- } else if (item.layout && !hadLayout) {
- hadLayout = true;
- widget->setLayout(item.layout);
- } else {
- QTC_CHECK(false);
+ 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;
+}
+
+// "Widgets"
+
+template <class T>
+void setupWidget(LayoutItem *item)
+{
+ item->onAdd = [](LayoutBuilder &builder) { builder.stack.append(new T); };
+ item->onExit = widgetExit;
+};
+
+Widget::Widget(std::initializer_list<LayoutItem> items)
+{
+ this->subItems = items;
+ setupWidget<QWidget>(this);
}
Group::Group(std::initializer_list<LayoutItem> items)
{
- widget = new QGroupBox;
- applyItems(widget, items);
+ this->subItems = items;
+ setupWidget<QGroupBox>(this);
}
PushButton::PushButton(std::initializer_list<LayoutItem> items)
{
- widget = new QPushButton;
- applyItems(widget, items);
+ this->subItems = items;
+ setupWidget<QPushButton>(this);
+}
+
+SpinBox::SpinBox(std::initializer_list<LayoutItem> items)
+{
+ this->subItems = items;
+ setupWidget<QSpinBox>(this);
+}
+
+TextEdit::TextEdit(std::initializer_list<LayoutItem> items)
+{
+ this->subItems = items;
+ setupWidget<QTextEdit>(this);
}
Splitter::Splitter(std::initializer_list<LayoutItem> items)
- : Splitter(new QSplitter(Qt::Vertical), items) {}
+{
+ 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<QSplitter *>(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));
+ };
+}
-Splitter::Splitter(QSplitter *splitter, std::initializer_list<LayoutItem> items)
+TabWidget::TabWidget(std::initializer_list<LayoutItem> items)
{
- widget = splitter;
- for (const LayoutItem &item : items)
- splitter->addWidget(item.widget);
+ this->subItems = items;
+ setupWidget<QTabWidget>(this);
+}
+
+// Special Tab
+
+Tab::Tab(const QString &tabName, const LayoutItem &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<QTabWidget *>(builder.stack.last().widget);
+ QTC_ASSERT(tabWidget, return);
+ tabWidget->addTab(inner, tabName);
+ };
}
-TabWidget::TabWidget(std::initializer_list<Tab> tabs)
- : TabWidget(new QTabWidget, tabs) {}
+// Special Application
+
+Application::Application(std::initializer_list<LayoutItem> items)
+{
+ subItems = items;
+ setupWidget<QWidget>(this);
+ onExit = {}; // Hack: Don't dropp the last slice, we need the resulting widget.
+}
-TabWidget::TabWidget(QTabWidget *tabWidget, std::initializer_list<Tab> tabs)
+int Application::exec(int &argc, char *argv[])
{
- widget = tabWidget;
- for (const Tab &tab : tabs)
- tabWidget->addTab(tab.widget, tab.text);
+ QApplication app(argc, argv);
+ LayoutBuilder builder;
+ addItemHelper(builder, *this);
+ if (QWidget *widget = builder.stack.last().widget)
+ widget->show();
+ return app.exec();
}
// "Properties"
-LayoutItem::Setter title(const QString &title, BoolAspect *checker)
+LayoutItem title(const QString &title)
{
- return [title, checker](QObject *target) {
+ return [title](QObject *target) {
if (auto groupBox = qobject_cast<QGroupBox *>(target)) {
groupBox->setTitle(title);
groupBox->setObjectName(title);
- if (checker) {
- groupBox->setCheckable(true);
- groupBox->setChecked(checker->value());
- checker->setHandlesGroup(groupBox);
- }
+ } else if (auto widget = qobject_cast<QWidget *>(target)) {
+ widget->setWindowTitle(title);
} else {
QTC_CHECK(false);
}
};
}
-LayoutItem::Setter onClicked(const std::function<void ()> &func, QObject *guard)
+LayoutItem text(const QString &text)
+{
+ return [text](QObject *target) {
+ if (auto button = qobject_cast<QAbstractButton *>(target)) {
+ button->setText(text);
+ } else if (auto textEdit = qobject_cast<QTextEdit *>(target)) {
+ textEdit->setText(text);
+ } else {
+ QTC_CHECK(false);
+ }
+ };
+}
+
+LayoutItem tooltip(const QString &toolTip)
+{
+ return [toolTip](QObject *target) {
+ if (auto widget = qobject_cast<QWidget *>(target)) {
+ widget->setToolTip(toolTip);
+ } else {
+ QTC_CHECK(false);
+ }
+ };
+}
+
+LayoutItem spacing(int spacing)
+{
+ return [spacing](QObject *target) {
+ if (auto layout = qobject_cast<QLayout *>(target)) {
+ layout->setSpacing(spacing);
+ } else {
+ QTC_CHECK(false);
+ }
+ };
+}
+
+LayoutItem resize(int w, int h)
+{
+ return [w, h](QObject *target) {
+ if (auto widget = qobject_cast<QWidget *>(target)) {
+ widget->resize(w, h);
+ } else {
+ QTC_CHECK(false);
+ }
+ };
+}
+
+LayoutItem columnStretch(int column, int stretch)
+{
+ return [column, stretch](QObject *target) {
+ if (auto grid = qobject_cast<QGridLayout *>(target)) {
+ grid->setColumnStretch(column, stretch);
+ } else {
+ QTC_CHECK(false);
+ }
+ };
+}
+
+// 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<QTextEdit *>(id.ob))
+ textEdit->setText(text);
+}
+
+// Signals
+
+LayoutItem onClicked(const std::function<void ()> &func, QObject *guard)
{
return [func, guard](QObject *target) {
if (auto button = qobject_cast<QAbstractButton *>(target)) {
@@ -552,28 +913,30 @@ LayoutItem::Setter onClicked(const std::function<void ()> &func, QObject *guard)
};
}
-LayoutItem::Setter text(const QString &text)
+LayoutItem onTextChanged(const std::function<void (const QString &)> &func, QObject *guard)
{
- return [text](QObject *target) {
- if (auto button = qobject_cast<QAbstractButton *>(target)) {
- button->setText(text);
+ return [func, guard](QObject *target) {
+ if (auto button = qobject_cast<QSpinBox *>(target)) {
+ QObject::connect(button, &QSpinBox::textChanged, guard ? guard : target, func);
} else {
QTC_CHECK(false);
}
};
}
-LayoutItem::Setter tooltip(const QString &toolTip)
+LayoutItem onValueChanged(const std::function<void (int)> &func, QObject *guard)
{
- return [toolTip](QObject *target) {
- if (auto widget = qobject_cast<QWidget *>(target)) {
- widget->setToolTip(toolTip);
+ return [func, guard](QObject *target) {
+ if (auto button = qobject_cast<QSpinBox *>(target)) {
+ QObject::connect(button, &QSpinBox::valueChanged, guard ? guard : target, func);
} else {
QTC_CHECK(false);
}
};
}
+// Convenience
+
QWidget *createHr(QWidget *parent)
{
auto frame = new QFrame(parent);
@@ -583,9 +946,55 @@ QWidget *createHr(QWidget *parent)
}
// Singletons.
-Break br;
-Stretch st;
-Space empty(0);
-HorizontalRule hr;
-} // Utils::Layouting
+LayoutItem::LayoutItem(const LayoutItem &t)
+{
+ operator=(t);
+}
+
+void createItem(LayoutItem *item, LayoutItem(*t)())
+{
+ *item = t();
+}
+
+void createItem(LayoutItem *item, const std::function<void(QObject *target)> &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;
+ };
+}
+
+} // Layouting
+
+#include "layoutbuilder.moc"
diff --git a/src/libs/utils/layoutbuilder.h b/src/libs/utils/layoutbuilder.h
index 2f534cb488..1f774ba146 100644
--- a/src/libs/utils/layoutbuilder.h
+++ b/src/libs/utils/layoutbuilder.h
@@ -3,256 +3,250 @@
#pragma once
-#include "utils_global.h"
-
#include <QList>
#include <QString>
-#include <QVariant>
+#include <QtGlobal>
#include <optional>
+#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
+
QT_BEGIN_NAMESPACE
class QLayout;
-class QSplitter;
-class QTabWidget;
+class QObject;
class QWidget;
+template <class T> T qobject_cast(QObject *object);
QT_END_NAMESPACE
-namespace Utils {
-class BaseAspect;
-class BoolAspect;
-} // Utils
-
-namespace Utils::Layouting {
+namespace Layouting {
-enum AttachType {
- WithMargins,
- WithoutMargins,
- WithFormAlignment, // Handle Grid similar to QFormLayout, i.e. use special alignment for the first column on Mac
-};
+// LayoutItem
class LayoutBuilder;
-
-// LayoutItem
+class LayoutItem;
+using LayoutItems = QList<LayoutItem>;
class QTCREATOR_UTILS_EXPORT LayoutItem
{
public:
- enum class AlignmentType {
- DefaultAlignment,
- AlignAsFormLabel,
- };
-
- enum class SpecialType {
- NotSpecial,
- Space,
- Stretch,
- Break,
- HorizontalRule,
- };
-
using Setter = std::function<void(QObject *target)>;
+
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; }
-
- QLayout *layout = nullptr;
- QWidget *widget = nullptr;
- BaseAspect *aspect = nullptr;
-
- QString text; // FIXME: Use specialValue for that
- int span = 1;
- AlignmentType align = AlignmentType::DefaultAlignment;
- Setter setter;
- SpecialType specialType = SpecialType::NotSpecial;
- QVariant specialValue;
+ ~LayoutItem();
+
+ LayoutItem(const LayoutItem &t);
+ LayoutItem &operator=(const LayoutItem &t) = default;
+
+ template <class T> LayoutItem(const T &t)
+ {
+ if constexpr (std::is_base_of_v<LayoutItem, T>)
+ LayoutItem::operator=(t);
+ else
+ createItem(this, t);
+ }
+
+ void attachTo(QWidget *w) const;
+ QWidget *emerge();
+
+ void addItem(const LayoutItem &item);
+ void addItems(const LayoutItems &items);
+ void addRow(const LayoutItems &items);
+
+ std::function<void(LayoutBuilder &)> onAdd;
+ std::function<void(LayoutBuilder &)> onExit;
+ std::function<void(QObject *target)> setter;
+ LayoutItems subItems;
};
-class QTCREATOR_UTILS_EXPORT Space : public LayoutItem
+// Special items
+
+class QTCREATOR_UTILS_EXPORT Space
{
public:
- explicit Space(int space);
+ explicit Space(int space) : space(space) {}
+ const int space;
};
-class QTCREATOR_UTILS_EXPORT Span : public LayoutItem
+class QTCREATOR_UTILS_EXPORT Stretch
{
public:
- Span(int span, const LayoutItem &item);
+ explicit Stretch(int stretch = 1) : stretch(stretch) {}
+ const int stretch;
};
-class QTCREATOR_UTILS_EXPORT Stretch : public LayoutItem
+class QTCREATOR_UTILS_EXPORT Span
{
public:
- explicit Stretch(int stretch = 1);
+ Span(int span, const LayoutItem &item) : span(span), item(item) {}
+ const int span;
+ LayoutItem item;
};
-class QTCREATOR_UTILS_EXPORT Tab : public LayoutItem
+class QTCREATOR_UTILS_EXPORT Column : public LayoutItem
{
public:
- Tab(const QString &tabName, const LayoutBuilder &item);
+ Column(std::initializer_list<LayoutItem> items);
};
-class QTCREATOR_UTILS_EXPORT Break : public LayoutItem
+class QTCREATOR_UTILS_EXPORT Row : public LayoutItem
{
public:
- Break();
+ Row(std::initializer_list<LayoutItem> items);
};
-class QTCREATOR_UTILS_EXPORT HorizontalRule : public LayoutItem
+class QTCREATOR_UTILS_EXPORT Flow : public LayoutItem
{
public:
- HorizontalRule();
+ Flow(std::initializer_list<LayoutItem> items);
};
-class QTCREATOR_UTILS_EXPORT Group : public LayoutItem
+class QTCREATOR_UTILS_EXPORT Grid : public LayoutItem
{
public:
- Group(std::initializer_list<LayoutItem> items);
+ Grid() : Grid({}) {}
+ Grid(std::initializer_list<LayoutItem> items);
};
-class QTCREATOR_UTILS_EXPORT PushButton : public LayoutItem
+class QTCREATOR_UTILS_EXPORT Form : public LayoutItem
{
public:
- PushButton(std::initializer_list<LayoutItem> items);
+ Form() : Form({}) {}
+ Form(std::initializer_list<LayoutItem> items);
};
-class QTCREATOR_UTILS_EXPORT Splitter : public LayoutItem
+class QTCREATOR_UTILS_EXPORT Widget : public LayoutItem
{
public:
- Splitter(std::initializer_list<LayoutItem> items);
- Splitter(QSplitter *splitter, std::initializer_list<LayoutItem> items);
+ Widget(std::initializer_list<LayoutItem> items);
};
-class QTCREATOR_UTILS_EXPORT TabWidget : public LayoutItem
+class QTCREATOR_UTILS_EXPORT Stack : public LayoutItem
{
public:
- TabWidget(std::initializer_list<Tab> tabs);
- TabWidget(QTabWidget *tabWidget, std::initializer_list<Tab> tabs);
+ Stack() : Stack({}) {}
+ Stack(std::initializer_list<LayoutItem> items);
};
-// Singleton 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;
-
-// "Properties"
-
-QTCREATOR_UTILS_EXPORT LayoutItem::Setter title(const QString &title,
- BoolAspect *checker = nullptr);
+class QTCREATOR_UTILS_EXPORT Tab : public LayoutItem
+{
+public:
+ Tab(const QString &tabName, const LayoutItem &item);
+};
-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<void()> &func,
- QObject *guard = nullptr);
+class QTCREATOR_UTILS_EXPORT Group : public LayoutItem
+{
+public:
+ Group(std::initializer_list<LayoutItem> items);
+};
+class QTCREATOR_UTILS_EXPORT TextEdit : public LayoutItem
+{
+public:
+ TextEdit(std::initializer_list<LayoutItem> items);
+};
-// Convenience
+class QTCREATOR_UTILS_EXPORT PushButton : public LayoutItem
+{
+public:
+ PushButton(std::initializer_list<LayoutItem> items);
+};
-QTCREATOR_UTILS_EXPORT QWidget *createHr(QWidget *parent = nullptr);
+class QTCREATOR_UTILS_EXPORT SpinBox : public LayoutItem
+{
+public:
+ SpinBox(std::initializer_list<LayoutItem> items);
+};
+class QTCREATOR_UTILS_EXPORT Splitter : public LayoutItem
+{
+public:
+ Splitter(std::initializer_list<LayoutItem> items);
+};
-// LayoutBuilder
+class QTCREATOR_UTILS_EXPORT TabWidget : public LayoutItem
+{
+public:
+ TabWidget(std::initializer_list<LayoutItem> items);
+};
-class QTCREATOR_UTILS_EXPORT LayoutBuilder
+class QTCREATOR_UTILS_EXPORT Application : public LayoutItem
{
public:
- enum LayoutType {
- HBoxLayout,
- VBoxLayout,
- FormLayout,
- GridLayout,
- StackLayout,
- };
+ Application(std::initializer_list<LayoutItem> items);
- using LayoutItems = QList<LayoutItem>;
+ int exec(int &argc, char *argv[]);
+};
- explicit LayoutBuilder(LayoutType layoutType, const LayoutItems &items = {});
- LayoutBuilder(const LayoutBuilder &) = delete;
- LayoutBuilder(LayoutBuilder &&) = default;
- LayoutBuilder &operator=(const LayoutBuilder &) = delete;
- LayoutBuilder &operator=(LayoutBuilder &&) = default;
+void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const std::function<void(QObject *target)> &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);
- ~LayoutBuilder();
- LayoutBuilder &setSpacing(int spacing);
+// "Singletons"
- LayoutBuilder &addItem(const LayoutItem &item);
- LayoutBuilder &addItems(const LayoutItems &items);
+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();
- LayoutBuilder &finishRow();
- LayoutBuilder &addRow(const LayoutItem &item);
- LayoutBuilder &addRow(const LayoutItems &items);
+// "Setters"
- LayoutType layoutType() const { return m_layoutType; }
+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);
- void attachTo(QWidget *w, Layouting::AttachType attachType = Layouting::WithMargins) const;
- QWidget *emerge(Layouting::AttachType attachType = Layouting::WithMargins);
+// "Getters"
-protected:
- friend class LayoutItem;
+class ID
+{
+public:
+ QObject *ob = nullptr;
+};
- explicit LayoutBuilder(); // Adds to existing layout.
+QTCREATOR_UTILS_EXPORT LayoutItem id(ID &out);
- QLayout *createLayout() const;
- void doLayout(QWidget *parent, Layouting::AttachType attachType) const;
+QTCREATOR_UTILS_EXPORT void setText(ID id, const QString &text);
- LayoutItems m_items;
- LayoutType m_layoutType;
- std::optional<int> m_spacing;
-};
-class QTCREATOR_UTILS_EXPORT LayoutExtender : public LayoutBuilder
-{
-public:
- explicit LayoutExtender(QLayout *layout, Layouting::AttachType attachType);
- ~LayoutExtender();
+// "Signals"
-private:
- QLayout *m_layout = nullptr;
- Layouting::AttachType m_attachType = {};
-};
+QTCREATOR_UTILS_EXPORT LayoutItem onClicked(const std::function<void()> &,
+ QObject *guard = nullptr);
+QTCREATOR_UTILS_EXPORT LayoutItem onTextChanged(const std::function<void(const QString &)> &,
+ QObject *guard = nullptr);
+QTCREATOR_UTILS_EXPORT LayoutItem onValueChanged(const std::function<void(int)> &,
+ QObject *guard = nullptr);
-class QTCREATOR_UTILS_EXPORT Column : public LayoutBuilder
-{
-public:
- Column() : LayoutBuilder(VBoxLayout) {}
- Column(std::initializer_list<LayoutItem> items) : LayoutBuilder(VBoxLayout, items) {}
-};
+QTCREATOR_UTILS_EXPORT LayoutItem onTextChanged(ID &id, QVariant(*sig)(QObject *));
-class QTCREATOR_UTILS_EXPORT Row : public LayoutBuilder
-{
-public:
- Row() : LayoutBuilder(HBoxLayout) {}
- Row(std::initializer_list<LayoutItem> items) : LayoutBuilder(HBoxLayout, items) {}
-};
+// Convenience
-class QTCREATOR_UTILS_EXPORT Grid : public LayoutBuilder
-{
-public:
- Grid() : LayoutBuilder(GridLayout) {}
- Grid(std::initializer_list<LayoutItem> items) : LayoutBuilder(GridLayout, items) {}
-};
+QTCREATOR_UTILS_EXPORT QWidget *createHr(QWidget *parent = nullptr);
-class QTCREATOR_UTILS_EXPORT Form : public LayoutBuilder
+template <class T>
+LayoutItem bindTo(T **out)
{
-public:
- Form() : LayoutBuilder(FormLayout) {}
- Form(std::initializer_list<LayoutItem> items) : LayoutBuilder(FormLayout, items) {}
-};
+ return [out](QObject *target) { *out = qobject_cast<T *>(target); };
+}
-class QTCREATOR_UTILS_EXPORT Stack : public LayoutBuilder
-{
-public:
- Stack() : LayoutBuilder(StackLayout) {}
- Stack(std::initializer_list<LayoutItem> items) : LayoutBuilder(StackLayout, items) {}
-};
-} // Utils::Layouting
+} // Layouting
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 <QRegularExpression>
-
-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
deleted file mode 100644
index 78a881d7f4..0000000000
--- a/src/libs/utils/linecolumn.h
+++ /dev/null
@@ -1,46 +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 <QMetaType>
-
-#include <optional>
-
-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;
- }
-
- 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);
- }
-
- static LineColumn extractFromFileName(QStringView fileName, int &postfixPos);
-
-public:
- int line = -1;
- int column = -1;
-};
-
-using OptionalLineColumn = std::optional<LineColumn>;
-
-} // namespace Utils
-
-Q_DECLARE_METATYPE(Utils::LineColumn)
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/link.h b/src/libs/utils/link.h
index 6f01b48434..00194654c9 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)
@@ -26,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; }
@@ -48,8 +53,8 @@ public:
int linkTextEnd = -1;
FilePath targetFilePath;
- int targetLine;
- int targetColumn;
+ int targetLine = 0;
+ int targetColumn = 0;
};
using LinkHandler = std::function<void(const Link &)>;
@@ -58,3 +63,12 @@ using Links = QList<Link>;
} // namespace Utils
Q_DECLARE_METATYPE(Utils::Link)
+
+namespace std {
+
+template<> struct hash<Utils::Link>
+{
+ size_t operator()(const Utils::Link &fn) const { return qHash(fn); }
+};
+
+} // std
diff --git a/src/libs/utils/macroexpander.cpp b/src/libs/utils/macroexpander.cpp
index 4e24247833..4dc18b23f6 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.
@@ -140,7 +141,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
[...]
@@ -196,7 +197,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.
@@ -246,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
{
@@ -274,7 +274,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
@@ -322,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)
@@ -340,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,
@@ -354,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,
@@ -372,46 +379,54 @@ 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,
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] { return base().path(); },
+ visibleInChooser);
+
+ registerVariable(
+ prefix + kPathPostfix,
+ Tr::tr("%1: Full path excluding file name.").arg(heading),
+ [base] { return base().parentDir().path(); },
+ visibleInChooser);
+
+ registerVariable(
+ prefix + kNativeFilePathPostfix,
+ Tr::tr(
+ "%1: Full path including file name, with native path separator (backslash on Windows).")
+ .arg(heading),
+ [base] { 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] { return base().parentDir().nativePath(); },
+ visibleInChooser);
+
+ registerVariable(
+ prefix + kFileNamePostfix,
+ Tr::tr("%1: File name without path.").arg(heading),
+ [base] { return base().fileName(); },
+ visibleInChooser);
+
+ registerVariable(
+ prefix + kFileBaseNamePostfix,
+ Tr::tr("%1: File base name without path and suffix.").arg(heading),
+ [base] { return base().baseName(); },
+ visibleInChooser);
}
void MacroExpander::registerExtraResolver(const MacroExpander::ResolverFunction &value)
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 <QtMath>
+/*!
+ \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)
{
diff --git a/src/libs/utils/multitextcursor.cpp b/src/libs/utils/multitextcursor.cpp
index bb2e38f5eb..0c2e5b0792 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<QTextCursor> &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<QTextCursor> &cursors)
{
- m_cursors.append(cursors);
- mergeCursors();
+ for (const QTextCursor &c : cursors)
+ addCursor(c);
}
void MultiTextCursor::setCursors(const QList<QTextCursor> &cursors)
{
- m_cursors = cursors;
- mergeCursors();
+ m_cursorList.clear();
+ m_cursorMap.clear();
+ addCursors(cursors);
}
const QList<QTextCursor> MultiTextCursor::cursors() const
{
- return m_cursors;
+ return QList<QTextCursor>(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 static_cast<int>(m_cursors.size());
+ return static_cast<int>(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<QTextCursor> 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<QTextCursor> cursors = Utils::sorted(m_cursors);
- for (QTextCursor &cursor : cursors)
+ if (static_cast<long unsigned int>(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<QTextCursor> thisCursors = m_cursors;
- QList<QTextCursor> 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<QTextCursor> 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<QTextCursor>(cursors.begin(), cursors.end());
+ QList<QTextCursor> 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<QTextCursor> cursors = m_cursors;
+ const std::list<QTextCursor> 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..dc554dd227 100644
--- a/src/libs/utils/multitextcursor.h
+++ b/src/libs/utils/multitextcursor.h
@@ -21,15 +21,22 @@ public:
MultiTextCursor();
explicit MultiTextCursor(const QList<QTextCursor> &cursors);
- /// replace all cursors with \param cursors and the last one will be the new main cursors
+ MultiTextCursor(const MultiTextCursor &multiCursor);
+ MultiTextCursor &operator=(const MultiTextCursor &multiCursor);
+ MultiTextCursor(const MultiTextCursor &&multiCursor);
+ MultiTextCursor &operator=(const MultiTextCursor &&multiCursor);
+
+ ~MultiTextCursor();
+
+ /// Replaces all cursors with \param cursors and the last one will be the new main cursors.
void setCursors(const QList<QTextCursor> &cursors);
const QList<QTextCursor> 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
@@ -39,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();
@@ -55,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();
@@ -69,20 +76,45 @@ public:
bool operator==(const MultiTextCursor &other) const;
bool operator!=(const MultiTextCursor &other) const;
- using iterator = QList<QTextCursor>::iterator;
- using const_iterator = QList<QTextCursor>::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(); }
+ template <typename T, typename mapit>
+ 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<QTextCursor, std::map<int, std::list<QTextCursor>::iterator>::iterator>;
+ using const_iterator
+ = BaseIterator<const QTextCursor,
+ std::map<int, std::list<QTextCursor>::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);
private:
- QList<QTextCursor> m_cursors;
+ std::list<QTextCursor> m_cursorList;
+ std::map<int, std::list<QTextCursor>::iterator> m_cursorMap;
+
+ void fillMapWithList();
};
} // namespace Utils
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;
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/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)
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/pathchooser.cpp b/src/libs/utils/pathchooser.cpp
index 53825931b9..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 <QFileDialog>
@@ -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();
@@ -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<QAbstractButton *> 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();
@@ -619,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;
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/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 3f9166a1a9..7ddd3ea0da 100644
--- a/src/libs/utils/port.cpp
+++ b/src/libs/utils/port.cpp
@@ -6,9 +6,13 @@
#include "qtcassert.h"
#include "stringutils.h"
+#include <QRegularExpression>
+
#include <limits>
-/*! \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.
@@ -31,27 +35,7 @@ quint16 Port::number() const
QTC_ASSERT(isValid(), return -1); return quint16(m_port);
}
-QList<Port> Port::parseFromSedOutput(const QByteArray &output)
-{
- QList<Port> ports;
- const QList<QByteArray> 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> Port::parseFromNetstatOutput(const QByteArray &output)
+QList<Port> Port::parseFromCommandOutput(const QByteArray &output)
{
QList<Port> ports;
const QList<QByteArray> lines = output.split('\n');
diff --git a/src/libs/utils/port.h b/src/libs/utils/port.h
index 851b41ceaf..c4b46631de 100644
--- a/src/libs/utils/port.h
+++ b/src/libs/utils/port.h
@@ -24,8 +24,8 @@ public:
QString toString() const { return QString::number(m_port); }
- static QList<Port> parseFromSedOutput(const QByteArray &output);
- static QList<Port> parseFromNetstatOutput(const QByteArray &output);
+ // Parses the output of "netstat -an" and "cat /proc/net/tcp"
+ static QList<Port> 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(); }
diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/process.cpp
index 74bd28560b..85d3fa54ed 100644
--- a/src/libs/utils/qtcprocess.cpp
+++ b/src/libs/utils/process.cpp
@@ -1,7 +1,7 @@
// 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 "process.h"
#include "algorithm.h"
#include "environment.h"
@@ -12,10 +12,13 @@
#include "processreaper.h"
#include "processutils.h"
#include "stringutils.h"
-#include "terminalprocess_p.h"
+#include "terminalhooks.h"
#include "threadutils.h"
#include "utilstr.h"
+#include <iptyprocess.h>
+#include <ptyqt.h>
+
#include <QCoreApplication>
#include <QDebug>
#include <QDir>
@@ -168,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;
@@ -304,6 +307,107 @@ private:
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<IPtyProcess> m_ptyProcess;
+};
+
class QProcessImpl final : public DefaultImpl
{
public:
@@ -350,15 +454,18 @@ 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);
handler->setWriteData(m_setup.m_writeData);
- if (m_setup.m_belowNormalPriority)
- handler->setBelowNormalPriority();
handler->setNativeArguments(m_setup.m_nativeArguments);
- m_process->setProcessEnvironment(m_setup.m_environment.toProcessEnvironment());
+ 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);
@@ -577,7 +684,7 @@ private:
class GeneralProcessBlockingImpl : public ProcessBlockingInterface
{
public:
- GeneralProcessBlockingImpl(QtcProcessPrivate *parent);
+ GeneralProcessBlockingImpl(ProcessPrivate *parent);
void flush() { flushSignals(takeAllSignals()); }
bool flushFor(ProcessSignalType signalType) {
@@ -601,21 +708,20 @@ private:
void handleReadyReadSignal(const ReadyReadSignal *launcherSignal);
void handleDoneSignal(const DoneSignal *launcherSignal);
- QtcProcessPrivate *m_caller = nullptr;
+ ProcessPrivate *m_caller = nullptr;
std::unique_ptr<ProcessInterfaceHandler> m_processHandler;
mutable QMutex m_mutex;
QList<ProcessInterfaceSignal *> m_signals;
};
-class QtcProcessPrivate : public QObject
+class ProcessPrivate : public QObject
{
public:
- explicit QtcProcessPrivate(QtcProcess *parent)
+ explicit ProcessPrivate(Process *parent)
: QObject(parent)
, 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();
@@ -629,14 +735,16 @@ public:
ProcessInterface *createProcessInterface()
{
+ if (m_setup.m_ptyData)
+ return new PtyProcessImpl;
if (m_setup.m_terminalMode != TerminalMode::Off)
- return new TerminalImpl();
+ 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();
+ return new QProcessImpl;
+ return new ProcessLauncherImpl;
}
void setProcessInterface(ProcessInterface *process)
@@ -646,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)
@@ -667,23 +775,7 @@ 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;
+ Process *q;
std::unique_ptr<ProcessBlockingInterface> m_blockingInterface;
std::unique_ptr<ProcessInterface> m_process;
ProcessSetupData m_setup;
@@ -694,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)();
}
@@ -807,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()))
{
@@ -815,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
// |
@@ -929,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());
@@ -945,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))
@@ -965,7 +1057,7 @@ void QtcProcessPrivate::sendControlSignal(ControlSignal controlSignal)
}, connectionType());
}
-void QtcProcessPrivate::clearForRun()
+void ProcessPrivate::clearForRun()
{
m_hangTimerCount = 0;
m_stdOut.clearForRun();
@@ -981,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);
@@ -993,16 +1085,17 @@ ProcessResult QtcProcessPrivate::interpretExitCode(int exitCode)
} // Internal
/*!
- \class Utils::QtcProcess
+ \class Utils::Process
+ \inmodule QtCreator
- \brief The QtcProcess class provides functionality for with processes.
+ \brief The Process class provides functionality for with processes.
\sa Utils::ProcessArgs
*/
-QtcProcess::QtcProcess(QObject *parent)
+Process::Process(QObject *parent)
: QObject(parent),
- d(new QtcProcessPrivate(this))
+ d(new ProcessPrivate(this))
{
qRegisterMetaType<ProcessResultData>("ProcessResultData");
static int qProcessExitStatusMeta = qRegisterMetaType<QProcess::ExitStatus>();
@@ -1011,61 +1104,71 @@ QtcProcess::QtcProcess(QObject *parent)
Q_UNUSED(qProcessProcessErrorMeta)
}
-QtcProcess::~QtcProcess()
+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();
delete d;
}
-void QtcProcess::setProcessImpl(ProcessImpl processImpl)
+void Process::setProcessImpl(ProcessImpl processImpl)
{
d->m_setup.m_processImpl = processImpl;
}
-ProcessMode QtcProcess::processMode() const
+void Process::setPtyData(const std::optional<Pty::Data> &data)
+{
+ d->m_setup.m_ptyData = data;
+}
+
+std::optional<Pty::Data> Process::ptyData() const
+{
+ return d->m_setup.m_ptyData;
+}
+
+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());
@@ -1073,17 +1176,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());
@@ -1091,16 +1194,16 @@ 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()),
- 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;
@@ -1115,37 +1218,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->m_process->m_setup.m_environment = d->fullEnvironment();
- 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(),
@@ -1153,37 +1255,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");
@@ -1202,37 +1304,47 @@ QString QtcProcess::toStandaloneCommandLine() const
return parts.join(" ");
}
-void QtcProcess::setExtraData(const QString &key, const QVariant &value)
+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 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;
}
@@ -1267,7 +1379,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)
@@ -1307,39 +1419,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 {});
@@ -1349,48 +1461,48 @@ 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)
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);
}
-bool QtcProcess::waitForReadyRead(int msecs)
+bool Process::waitForReadyRead(int msecs)
{
QTC_ASSERT(d->m_process, return false);
if (d->m_state == QProcess::NotRunning)
@@ -1398,7 +1510,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)
@@ -1406,17 +1518,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)
@@ -1431,7 +1543,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);
@@ -1444,7 +1556,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) {
@@ -1464,7 +1576,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;
@@ -1473,47 +1585,17 @@ 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());
}
-/*!
- \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 QtcProcess::exitMessage() const
+QString Process::exitMessage() const
{
const QString fullCmd = commandLine().toUserOutput();
switch (result()) {
@@ -1533,7 +1615,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);
@@ -1547,7 +1629,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);
@@ -1564,30 +1646,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());
}
@@ -1602,20 +1684,20 @@ 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="
+ 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;
@@ -1691,7 +1773,7 @@ void ChannelBuffer::handleRest()
}
}
-void QtcProcess::setTimeoutS(int timeoutS)
+void Process::setTimeoutS(int timeoutS)
{
if (timeoutS > 0)
d->m_maxHangTimerCount = qMax(2, timeoutS);
@@ -1699,33 +1781,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));
@@ -1734,7 +1816,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({});
@@ -1745,7 +1827,7 @@ void QtcProcess::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
@@ -1786,33 +1868,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);
@@ -1825,7 +1907,7 @@ void QtcProcess::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;
@@ -1848,13 +1930,13 @@ 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;
}
-void QtcProcessPrivate::slotTimeout()
+void ProcessPrivate::slotTimeout()
{
if (!m_waitingForUser && (++m_hangTimerCount > m_maxHangTimerCount)) {
if (debug)
@@ -1875,17 +1957,17 @@ 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;
m_processId = processId;
m_applicationMainThreadId = applicationMainThreadId;
- emitGuardedSignal(&QtcProcess::started);
+ 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);
@@ -1900,7 +1982,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()) {
@@ -1909,12 +1991,12 @@ 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);
}
}
}
-void QtcProcessPrivate::handleDone(const ProcessResultData &data)
+void ProcessPrivate::handleDone(const ProcessResultData &data)
{
m_killTimer.stop();
const bool wasCanceled = m_resultData.m_canceledByUser;
@@ -1966,7 +2048,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;
}
@@ -1980,7 +2062,7 @@ static QString blockingMessage(const QVariant &variant)
return "blocking without event loop";
}
-void QtcProcessPrivate::setupDebugLog()
+void ProcessPrivate::setupDebugLog()
{
if (!processLog().isDebugEnabled())
return;
@@ -1990,7 +2072,7 @@ void QtcProcessPrivate::setupDebugLog()
return duration_cast<milliseconds>(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);
@@ -2003,7 +2085,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);
@@ -2029,24 +2111,24 @@ void QtcProcessPrivate::setupDebugLog()
});
}
-void QtcProcessPrivate::storeEventLoopDebugInfo(const QVariant &value)
+void ProcessPrivate::storeEventLoopDebugInfo(const QVariant &value)
{
if (processLog().isDebugEnabled())
setProperty(QTC_PROCESS_BLOCKING_TYPE, value);
}
-QtcProcessAdapter::QtcProcessAdapter()
+ProcessTaskAdapter::ProcessTaskAdapter()
{
- connect(task(), &QtcProcess::done, this, [this] {
+ connect(task(), &Process::done, this, [this] {
emit done(task()->result() == ProcessResult::FinishedWithSuccess);
});
}
-void QtcProcessAdapter::start()
+void ProcessTaskAdapter::start()
{
task()->start();
}
} // namespace Utils
-#include "qtcprocess.moc"
+#include "process.moc"
diff --git a/src/libs/utils/qtcprocess.h b/src/libs/utils/process.h
index 67898ba855..83f1bb990b 100644
--- a/src/libs/utils/qtcprocess.h
+++ b/src/libs/utils/process.h
@@ -1,13 +1,18 @@
// 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 <pthread.h> which wants to include <process.h>
+ #include_next <process.h>
+#elif !defined(UTILS_PROCESS_H)
+#define UTILS_PROCESS_H
#include "utils_global.h"
#include "commandline.h"
#include "processenums.h"
-#include "tasktree.h"
+
+#include <solutions/tasking/tasktree.h>
#include <QProcess>
@@ -16,24 +21,23 @@ class QDebug;
class QTextCodec;
QT_END_NAMESPACE
-class tst_QtcProcess;
-
namespace Utils {
-namespace Internal { class QtcProcessPrivate; }
+namespace Internal { class ProcessPrivate; }
+namespace Pty { class Data; }
class Environment;
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
@@ -76,6 +80,9 @@ public:
void setProcessImpl(ProcessImpl processImpl);
+ void setPtyData(const std::optional<Pty::Data> &data);
+ std::optional<Pty::Data> ptyData() const;
+
void setTerminalMode(TerminalMode mode);
TerminalMode terminalMode() const;
bool usesTerminal() const { return terminalMode() != TerminalMode::Off; }
@@ -125,7 +132,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);
@@ -177,6 +184,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
@@ -187,10 +197,10 @@ 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;
+ friend class Internal::ProcessPrivate;
+ Internal::ProcessPrivate *d = nullptr;
};
class DeviceProcessHooks
@@ -200,13 +210,15 @@ public:
std::function<Environment(const FilePath &)> systemEnvironmentForBinary;
};
-class QTCREATOR_UTILS_EXPORT QtcProcessAdapter : public Tasking::TaskAdapter<QtcProcess>
+class QTCREATOR_UTILS_EXPORT ProcessTaskAdapter : public Tasking::TaskAdapter<Process>
{
public:
- QtcProcessAdapter();
+ ProcessTaskAdapter();
void start() final;
};
} // namespace Utils
-QTC_DECLARE_CUSTOM_TASK(Process, Utils::QtcProcessAdapter);
+TASKING_DECLARE_TASK(ProcessTask, Utils::ProcessTaskAdapter);
+
+#endif // UTILS_PROCESS_H
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) {
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 <sys/types.h>
-#include <sys/stat.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <sys/wait.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <signal.h>
-#include <string.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <errno.h>
-#include <assert.h>
-
-#ifdef __linux__
-#include <sys/prctl.h>
-
-// 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"} <pid-socket> <continuation-msg> <workdir> <env-file> <exe> <args...> */
-/* 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 <windows.h>
-#include <shellapi.h>
-#include <string.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <direct.h>
-
-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"} <pid-socket> <workdir> <env-file> <cmdline> <continuation-msg> */
-/* 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..84019bae53 100644
--- a/src/libs/utils/processenums.h
+++ b/src/libs/utils/processenums.h
@@ -24,10 +24,9 @@ enum class ProcessImpl {
enum class TerminalMode {
Off,
- Run,
- Debug,
- Suspend,
- 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/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/processinfo.cpp b/src/libs/utils/processinfo.cpp
index 366800d316..0d71e380fb 100644
--- a/src/libs/utils/processinfo.cpp
+++ b/src/libs/utils/processinfo.cpp
@@ -3,14 +3,13 @@
#include "processinfo.h"
-#include "qtcprocess.h"
+#include "algorithm.h"
+#include "process.h"
-#if defined(Q_OS_UNIX)
#include <QDir>
-#include <signal.h>
-#include <errno.h>
-#include <string.h>
-#include <unistd.h>
+#include <QRegularExpression>
+
+#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<ProcessInfo> 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);
+
+ Process procProcess;
+ procProcess.setCommand(cmd);
+ procProcess.runBlocking();
-static QList<ProcessInfo> getLocalProcessesUsingProc()
-{
QList<ProcessInfo> 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<QByteArray> 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<qint64, QString> getLocalProcessDataUsingPs(const QString &column)
+static QMap<qint64, QString> getLocalProcessDataUsingPs(const FilePath &deviceRoot,
+ const QString &column)
{
- QtcProcess process;
- process.setCommand({"ps", {"-e", "-o", "pid," + column}});
- process.start();
- if (!process.waitForFinished())
- return {};
+ Process process;
+ 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<qint64, QString> result;
for (int i = 1; i < lines.size(); ++i) { // Skip header
const QString line = lines.at(i).trimmed();
@@ -118,14 +99,14 @@ static QMap<qint64, QString> getLocalProcessDataUsingPs(const QString &column)
return result;
}
-static QList<ProcessInfo> getLocalProcessesUsingPs()
+static QList<ProcessInfo> getLocalProcessesUsingPs(const FilePath &deviceRoot)
{
QList<ProcessInfo> processes;
// cmdLines are full command lines, usually with absolute path,
// exeNames only the file part of the executable's path.
- const QMap<qint64, QString> exeNames = getLocalProcessDataUsingPs("comm");
- const QMap<qint64, QString> cmdLines = getLocalProcessDataUsingPs("args");
+ const QMap<qint64, QString> exeNames = getLocalProcessDataUsingPs(deviceRoot, "comm");
+ const QMap<qint64, QString> 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<ProcessInfo> getLocalProcessesUsingPs()
return processes;
}
-QList<ProcessInfo> ProcessInfo::processInfoList()
+static QList<ProcessInfo> getProcessesUsingPidin(const FilePath &pidin)
+{
+ Process process;
+ process.setCommand({pidin, {"-F", "%a %A {/%n}"}});
+ process.runBlocking();
+
+ QList<ProcessInfo> 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<ProcessInfo> processInfoListUnix(const FilePath &deviceRoot)
{
- const QDir procDir = QDir(QLatin1String(procDirC));
- return procDir.exists() ? getLocalProcessesUsingProc() : getLocalProcessesUsingPs();
+ 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> ProcessInfo::processInfoList(const FilePath &deviceRoot)
+{
+ return processInfoListUnix(deviceRoot);
}
#elif defined(Q_OS_WIN)
-QList<ProcessInfo> ProcessInfo::processInfoList()
+QList<ProcessInfo> ProcessInfo::processInfoList(const FilePath &deviceRoot)
{
+ if (deviceRoot.needsDevice())
+ return processInfoListUnix(deviceRoot);
+
QList<ProcessInfo> 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 <QList>
#include <QString>
@@ -19,7 +21,7 @@ public:
bool operator<(const ProcessInfo &other) const;
- static QList<ProcessInfo> processInfoList();
+ static QList<ProcessInfo> processInfoList(const Utils::FilePath &deviceRoot = Utils::FilePath());
};
} // namespace Utils
diff --git a/src/libs/utils/processinterface.cpp b/src/libs/utils/processinterface.cpp
index 9e13494cda..a2dc746905 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
@@ -18,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/processinterface.h b/src/libs/utils/processinterface.h
index d398d8fe13..a0460c5af3 100644
--- a/src/libs/utils/processinterface.h
+++ b/src/libs/utils/processinterface.h
@@ -10,10 +10,38 @@
#include "processenums.h"
#include <QProcess>
+#include <QSize>
namespace Utils {
-namespace Internal { class QtcProcessPrivate; }
+namespace Internal { class ProcessPrivate; }
+
+namespace Pty {
+
+using ResizeHandler = std::function<void(const QSize &)>;
+
+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<SharedData> m_data;
+};
+
+} // namespace Pty
class QTCREATOR_UTILS_EXPORT ProcessSetupData
{
@@ -22,6 +50,7 @@ public:
ProcessMode m_processMode = ProcessMode::Reader;
TerminalMode m_terminalMode = TerminalMode::Off;
+ std::optional<Pty::Data> m_ptyData;
CommandLine m_commandLine;
FilePath m_workingDirectory;
Environment m_environment;
@@ -39,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
@@ -74,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
@@ -112,8 +142,8 @@ private:
virtual ProcessBlockingInterface *processBlockingInterface() const { return nullptr; }
- friend class QtcProcess;
- friend class Internal::QtcProcessPrivate;
+ friend class Process;
+ 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.
diff --git a/src/libs/utils/processutils.cpp b/src/libs/utils/processutils.cpp
index c8394e37dd..62ea514b65 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)
{
@@ -94,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
}
@@ -140,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 60161f8398..89202c0daf 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;
@@ -50,7 +50,6 @@ public:
private:
void terminateProcess();
- void setupChildProcess_impl();
bool m_lowPriority = false;
bool m_unixTerminalDisabled = false;
diff --git a/src/libs/utils/progressindicator.cpp b/src/libs/utils/progressindicator.cpp
index 5e09191765..d1ce125dbe 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)
@@ -204,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)
{
@@ -215,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
{
diff --git a/src/libs/utils/projectintropage.cpp b/src/libs/utils/projectintropage.cpp
index 8df64b97f0..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
@@ -66,7 +67,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);
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 <QSharedPointer>
#include <QString>
diff --git a/src/libs/utils/qtcolorbutton.cpp b/src/libs/utils/qtcolorbutton.cpp
index ca0aa631ba..6a8537bd5f 100644
--- a/src/libs/utils/qtcolorbutton.cpp
+++ b/src/libs/utils/qtcolorbutton.cpp
@@ -3,12 +3,15 @@
#include "qtcolorbutton.h"
-#include <QMimeData>
+#include "theme/theme.h"
+
#include <QApplication>
#include <QColorDialog>
+#include <QDrag>
#include <QDragEnterEvent>
+#include <QMimeData>
#include <QPainter>
-#include <QDrag>
+#include <QStyleOption>
namespace Utils {
@@ -157,51 +160,52 @@ bool QtColorButton::isDialogOpen() const
void QtColorButton::paintEvent(QPaintEvent *event)
{
- QToolButton::paintEvent(event);
- if (!isEnabled())
- return;
+ Q_UNUSED(event)
- 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);
- }
+ constexpr Theme::Color overlayColor = Theme::TextColorNormal;
+ constexpr qreal overlayOpacity = 0.25;
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);
+ 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);
+ 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(), color);
+ 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(creatorTheme()->color(overlayColor));
+ p.setOpacity(overlayOpacity);
+ }
+ p.drawRect(rect().adjusted(0, 0, -1, -1));
}
void QtColorButton::mousePressEvent(QMouseEvent *event)
diff --git a/src/libs/utils/runextensions.h b/src/libs/utils/runextensions.h
index f81eb56699..6505b12a9c 100644
--- a/src/libs/utils/runextensions.h
+++ b/src/libs/utils/runextensions.h
@@ -476,113 +476,4 @@ runAsync(QThreadPool *pool, Function &&function, Args&&... args)
std::forward<Args>(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 <typename R, typename T>
-const QFuture<T> &onResultReady(const QFuture<T> &future, R *receiver, void(R::*member)(const T &))
-{
- auto watcher = new QFutureWatcher<T>();
- 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 <typename T, typename Function>
-const QFuture<T> &onResultReady(const QFuture<T> &future, QObject *guard, Function f)
-{
- auto watcher = new QFutureWatcher<T>();
- 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 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 <typename T, typename Function>
-const QFuture<T> &onResultReady(const QFuture<T> &future, Function f)
-{
- auto watcher = new QFutureWatcher<T>();
- 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
- or create a QFutureWatcher already for other reasons.
-*/
-template<typename R, typename T>
-const QFuture<T> &onFinished(const QFuture<T> &future,
- R *receiver,
- void (R::*member)(const QFuture<T> &))
-{
- auto watcher = new QFutureWatcher<T>();
- 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<typename T, typename Function>
-const QFuture<T> &onFinished(const QFuture<T> &future, QObject *guard, Function f)
-{
- auto watcher = new QFutureWatcher<T>();
- QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater);
- QObject::connect(watcher, &QFutureWatcherBase::finished, guard, [f, watcher] {
- f(watcher->future());
- });
- 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<typename T, typename Function>
-const QFuture<T> &onFinished(const QFuture<T> &future, Function f)
-{
- auto watcher = new QFutureWatcher<T>();
- 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
diff --git a/src/libs/utils/scopedtimer.cpp b/src/libs/utils/scopedtimer.cpp
new file mode 100644
index 0000000000..6a4522e28d
--- /dev/null
+++ b/src/libs/utils/scopedtimer.cpp
@@ -0,0 +1,67 @@
+// 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 <QDebug>
+#include <QTime>
+
+#include <chrono>
+
+namespace Utils {
+
+static QString currentTime() { return QTime::currentTime().toString(Qt::ISODateWithMs); }
+
+using namespace std::chrono;
+
+static const char s_scoped[] = "SCOPED TIMER";
+static const char s_scopedCumulative[] = "STATIC SCOPED TIMER";
+
+class ScopedTimerPrivate
+{
+public:
+ 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<system_clock, nanoseconds> m_start = system_clock::now();
+};
+
+ScopedTimer::ScopedTimer(const ScopedTimerData &data)
+ : d(new ScopedTimerPrivate{data})
+{
+ if (d->m_data.m_cumulative)
+ return;
+ qDebug().noquote().nospace() << d->header() << "started";
+}
+
+static int64_t toMs(int64_t ns) { return ns / 1000000; }
+
+ScopedTimer::~ScopedTimer()
+{
+ const auto elapsed = duration_cast<nanoseconds>(system_clock::now() - d->m_start);
+ QString suffix;
+ 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);
+ // 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";
+ }
+ qDebug().noquote().nospace() << d->header() << suffix;
+}
+
+} // namespace Utils
diff --git a/src/libs/utils/scopedtimer.h b/src/libs/utils/scopedtimer.h
new file mode 100644
index 0000000000..d66ef15841
--- /dev/null
+++ b/src/libs/utils/scopedtimer.h
@@ -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
+
+#pragma once
+
+#include "utils_global.h"
+
+#include <QString>
+
+#include <atomic>
+#include <memory>
+
+namespace Utils {
+
+class ScopedTimerPrivate;
+
+class QTCREATOR_UTILS_EXPORT ScopedTimerData
+{
+public:
+ QString m_message;
+ const char *m_fileName = nullptr;
+ int m_line = 0;
+ std::atomic<int64_t> *m_cumulative = nullptr;
+};
+
+class QTCREATOR_UTILS_EXPORT ScopedTimer
+{
+public:
+ ScopedTimer(const ScopedTimerData &data);
+ ~ScopedTimer();
+
+private:
+ std::unique_ptr<ScopedTimerPrivate> d;
+};
+
+} // 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(message) ::Utils::ScopedTimer QTC_CONCAT(_qtc_scoped_timer_, __LINE__)\
+({{message}, __FILE__, __LINE__})
+
+// The macro below expands as follows (in one line):
+// static std::atomic<int64_t> _qtc_static_scoped_timer___LINE__ = 0;
+// ScopedTimer _qtc_scoped_timer___LINE__(__FILE__, __LINE__, &_qtc_static_scoped_timer___LINE__)
+#define QTC_STATIC_SCOPED_TIMER(message) static std::atomic<int64_t> \
+QTC_CONCAT(_qtc_static_scoped_timer_, __LINE__) = 0; \
+::Utils::ScopedTimer QTC_CONCAT(_qtc_scoped_timer_, __LINE__)\
+({{message}, __FILE__, __LINE__, &QTC_CONCAT(_qtc_static_scoped_timer_, __LINE__)})
diff --git a/src/libs/utils/searchresultitem.cpp b/src/libs/utils/searchresultitem.cpp
new file mode 100644
index 0000000000..2574473c93
--- /dev/null
+++ b/src/libs/utils/searchresultitem.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 "searchresultitem.h"
+
+namespace Utils {
+
+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;
+}
+
+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);
+}
+
+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
new file mode 100644
index 0000000000..ea9d0332d5
--- /dev/null
+++ b/src/libs/utils/searchresultitem.h
@@ -0,0 +1,102 @@
+// 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 <utils/filepath.h>
+#include <utils/hostosinfo.h>
+#include <utils/textutils.h>
+
+#include <QColor>
+#include <QHash>
+#include <QIcon>
+#include <QStringList>
+#include <QVariant>
+
+#include <optional>
+
+namespace Utils {
+
+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<SearchResultColor::Style, SearchResultColor>;
+
+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; }
+ void setDisplayText(const QString &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; }
+
+ 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; }
+ 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<QString> containingFunctionName() const { return m_containingFunctionName; }
+
+ void setContainingFunctionName(const std::optional<QString> &containingFunctionName)
+ {
+ 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
+ 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
+ Text::Range m_mainRange;
+ bool m_useTextEditorFont = false;
+ bool m_selectForReplacement = true;
+ SearchResultColor::Style m_style = SearchResultColor::Style::Default;
+ std::optional<QString> m_containingFunctionName;
+};
+
+using SearchResultItems = QList<SearchResultItem>;
+
+} // namespace Utils
+
+Q_DECLARE_METATYPE(Utils::SearchResultItem)
+Q_DECLARE_METATYPE(Utils::SearchResultItems)
diff --git a/src/libs/utils/settingsaccessor.cpp b/src/libs/utils/settingsaccessor.cpp
index dc3cd0711e..275290674f 100644
--- a/src/libs/utils/settingsaccessor.cpp
+++ b/src/libs/utils/settingsaccessor.cpp
@@ -39,17 +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 &displayName,
- const QString &applicationDisplayName) :
-docType(docType),
-displayName(displayName),
-applicationDisplayName(applicationDisplayName)
-{
- QTC_CHECK(!docType.isEmpty());
- QTC_CHECK(!displayName.isEmpty());
- QTC_CHECK(!applicationDisplayName.isEmpty());
-}
+SettingsAccessor::SettingsAccessor() = default;
SettingsAccessor::~SettingsAccessor() = default;
@@ -68,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<Issue> result = writeData(m_baseFilePath, data, parent);
const ProceedInfo pi = result ? reportIssues(result.value(), m_baseFilePath, parent) : ProceedInfo::Continue;
@@ -99,6 +92,9 @@ std::optional<SettingsAccessor::Issue> 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)
@@ -123,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<PersistentSettingsWriter>(m_baseFilePath, docType);
+ m_writer = std::make_unique<PersistentSettingsWriter>(m_baseFilePath, m_docType);
m_writer->setContents(data);
}
@@ -146,7 +142,7 @@ std::optional<SettingsAccessor::Issue> SettingsAccessor::writeFile(const FilePat
QString errorMessage;
if (!m_readOnly && (!m_writer || m_writer->fileName() != path))
- m_writer = std::make_unique<PersistentSettingsWriter>(path, docType);
+ m_writer = std::make_unique<PersistentSettingsWriter>(path, m_docType);
if (!m_writer->save(data, &errorMessage)) {
return Issue(Tr::tr("Failed to Write File"),
@@ -156,8 +152,7 @@ std::optional<SettingsAccessor::Issue> 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;
@@ -226,18 +221,8 @@ std::optional<FilePath> BackUpStrategy::backupName(const QVariantMap &oldData,
return path.stringAppended(".bak");
}
-BackingUpSettingsAccessor::BackingUpSettingsAccessor(const QString &docType,
- const QString &displayName,
- const QString &applicationDisplayName) :
- BackingUpSettingsAccessor(std::make_unique<BackUpStrategy>(), docType, displayName, applicationDisplayName)
-{ }
-
-BackingUpSettingsAccessor::BackingUpSettingsAccessor(std::unique_ptr<BackUpStrategy> &&strategy,
- const QString &docType,
- const QString &displayName,
- const QString &applicationDisplayName) :
- SettingsAccessor(docType, displayName, applicationDisplayName),
- m_strategy(std::move(strategy))
+BackingUpSettingsAccessor::BackingUpSettingsAccessor()
+ : m_strategy(std::make_unique<BackUpStrategy>())
{ }
SettingsAccessor::RestoreData
@@ -259,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.</p>")
- .arg(path.toUserOutput(), applicationDisplayName), Issue::Type::ERROR);
+ .arg(path.toUserOutput(), m_applicationDisplayName), Issue::Type::ERROR);
i.buttons.insert(QMessageBox::Ok, DiscardAndContinue);
result.issue = i;
}
@@ -279,6 +264,11 @@ std::optional<SettingsAccessor::Issue> BackingUpSettingsAccessor::writeData(cons
return SettingsAccessor::writeData(path, data, parent);
}
+void BackingUpSettingsAccessor::setStrategy(std::unique_ptr<BackUpStrategy> &&strategy)
+{
+ m_strategy = std::move(strategy);
+}
+
FilePaths BackingUpSettingsAccessor::readFileCandidates(const FilePath &path) const
{
FilePaths result = filteredUnique(m_strategy->readFileCandidates(path));
@@ -410,19 +400,10 @@ QVariantMap VersionUpgrader::renameKeys(const QList<Change> &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 &displayName,
- const QString &applicationDisplayName) :
- UpgradingSettingsAccessor(std::make_unique<VersionedBackUpStrategy>(this), docType,
- displayName, applicationDisplayName)
-{ }
-
-UpgradingSettingsAccessor::UpgradingSettingsAccessor(std::unique_ptr<BackUpStrategy> &&strategy,
- const QString &docType,
- const QString &displayName,
- const QString &applicationDisplayName) :
- BackingUpSettingsAccessor(std::move(strategy), docType, displayName, applicationDisplayName)
-{ }
+UpgradingSettingsAccessor::UpgradingSettingsAccessor()
+{
+ setStrategy(std::make_unique<VersionedBackUpStrategy>(this));
+}
int UpgradingSettingsAccessor::currentVersion() const
{
@@ -541,7 +522,7 @@ UpgradingSettingsAccessor::validateVersionRange(const RestoreData &data) const
"version of %2 was used are ignored, and "
"changes made now will <b>not</b> be propagated to "
"the newer version.</p>")
- .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;
@@ -550,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("<p>No settings file created by this instance "
"of %1 was found.</p>"
"<p>Did you work with this project on another machine or "
"using a different settings path before?</p>"
"<p>Do you still want to load the settings file \"%2\"?</p>")
- .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();
@@ -577,12 +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(std::unique_ptr<BackUpStrategy> &&strategy,
- const QString &docType,
- const QString &displayName,
- const QString &applicationDisplayName) :
- UpgradingSettingsAccessor(std::move(strategy), docType, displayName, applicationDisplayName)
-{ }
+MergingSettingsAccessor::MergingSettingsAccessor() = default;
SettingsAccessor::RestoreData MergingSettingsAccessor::readData(const FilePath &path,
QWidget *parent) const
@@ -614,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 f1bbb3594e..683743ac62 100644
--- a/src/libs/utils/settingsaccessor.h
+++ b/src/libs/utils/settingsaccessor.h
@@ -51,8 +51,7 @@ using SettingsMergeResult = std::optional<QPair<QString, QVariant>>;
class QTCREATOR_UTILS_EXPORT SettingsAccessor
{
public:
- SettingsAccessor(const QString &docType, const QString &displayName,
- const QString &applicationDisplayName);
+ SettingsAccessor();
virtual ~SettingsAccessor();
enum ProceedInfo { Continue, DiscardAndContinue };
@@ -95,10 +94,6 @@ public:
QVariantMap restoreSettings(QWidget *parent) const;
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; }
void setReadOnly() { m_readOnly = true; }
FilePath baseFilePath() const { return m_baseFilePath; }
@@ -108,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;
@@ -119,6 +117,9 @@ protected:
virtual RestoreData readFile(const FilePath &path) const;
virtual std::optional<Issue> writeFile(const FilePath &path, const QVariantMap &data) const;
+ QString m_docType;
+ QString m_applicationDisplayName;
+
private:
FilePath m_baseFilePath;
mutable std::unique_ptr<PersistentSettingsWriter> m_writer;
@@ -148,10 +149,7 @@ public:
class QTCREATOR_UTILS_EXPORT BackingUpSettingsAccessor : public SettingsAccessor
{
public:
- BackingUpSettingsAccessor(const QString &docType, const QString &displayName,
- const QString &applicationDisplayName);
- BackingUpSettingsAccessor(std::unique_ptr<BackUpStrategy> &&strategy, const QString &docType,
- const QString &displayName, const QString &applicationDisplayName);
+ BackingUpSettingsAccessor();
RestoreData readData(const FilePath &path, QWidget *parent) const override;
std::optional<Issue> writeData(const FilePath &path,
@@ -159,6 +157,7 @@ public:
QWidget *parent) const override;
BackUpStrategy *strategy() const { return m_strategy.get(); }
+ void setStrategy(std::unique_ptr<BackUpStrategy> &&strategy);
private:
FilePaths readFileCandidates(const FilePath &path) const;
@@ -220,10 +219,7 @@ class MergingSettingsAccessor;
class QTCREATOR_UTILS_EXPORT UpgradingSettingsAccessor : public BackingUpSettingsAccessor
{
public:
- UpgradingSettingsAccessor(const QString &docType,
- const QString &displayName, const QString &applicationDisplayName);
- UpgradingSettingsAccessor(std::unique_ptr<BackUpStrategy> &&strategy, const QString &docType,
- const QString &displayName, const QString &appDisplayName);
+ UpgradingSettingsAccessor();
int currentVersion() const;
int firstSupportedVersion() const;
@@ -263,9 +259,7 @@ public:
QString key;
};
- MergingSettingsAccessor(std::unique_ptr<BackUpStrategy> &&strategy,
- const QString &docType, const QString &displayName,
- const QString &applicationDisplayName);
+ MergingSettingsAccessor();
RestoreData readData(const FilePath &path, QWidget *parent) const final;
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/stringtable.cpp b/src/libs/utils/stringtable.cpp
index 6345b80c32..73cad02588 100644
--- a/src/libs/utils/stringtable.cpp
+++ b/src/libs/utils/stringtable.cpp
@@ -3,7 +3,7 @@
#include "stringtable.h"
-#include "runextensions.h"
+#include "async.h"
#include <QDebug>
#include <QElapsedTimer>
@@ -34,7 +34,7 @@ public:
void cancelAndWait();
QString insert(const QString &string);
void startGC();
- void GC(QFutureInterface<void> &futureInterface);
+ void GC(QPromise<void> &promise);
QFuture<void> 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<void> &futureInterface)
+void StringTablePrivate::GC(QPromise<void> &promise)
{
int initialSize = 0;
bytesSaved = 0;
@@ -125,7 +125,7 @@ void StringTablePrivate::GC(QFutureInterface<void> &futureInterface)
// Collect all QStrings which have refcount 1. (One reference in m_strings and nowhere else.)
for (QSet<QString>::iterator i = m_strings.begin(); i != m_strings.end();) {
- if (futureInterface.isCanceled())
+ if (promise.isCanceled())
return;
if (!isQStringInUse(*i))
diff --git a/src/libs/utils/stringutils.cpp b/src/libs/utils/stringutils.cpp
index 3a1afe1fa1..a33a08e3d9 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,14 @@
#endif
#include <QDir>
+#include <QFontMetrics>
#include <QJsonArray>
#include <QJsonValue>
#include <QLocale>
#include <QRegularExpression>
#include <QSet>
+#include <QTextDocument>
+#include <QTextList>
#include <QTime>
#include <limits.h>
@@ -456,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)
{
@@ -527,4 +529,101 @@ QTCREATOR_UTILS_EXPORT FilePath appendHelper(const FilePath &base, int n)
return base.stringAppended(QString::number(n));
}
+QTCREATOR_UTILS_EXPORT QPair<QStringView, QStringView> 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<QStringView, QStringView> splitAtFirst(const QString &string, QChar ch)
+{
+ QStringView view = string;
+ 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);
+}
+
+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 5cf2978c07..3bab6110cf 100644
--- a/src/libs/utils/stringutils.h
+++ b/src/libs/utils/stringutils.h
@@ -5,8 +5,10 @@
#include "utils_global.h"
+#include <QBrush>
#include <QList>
#include <QString>
+#include <QSyntaxHighlighter>
#include <functional>
@@ -115,4 +117,20 @@ 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<QStringView, QStringView> splitAtFirst(const QString &string, QChar ch);
+QTCREATOR_UTILS_EXPORT QPair<QStringView, QStringView> splitAtFirst(const QStringView &stringView,
+ QChar ch);
+
+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
diff --git a/src/libs/utils/styledbar.cpp b/src/libs/utils/styledbar.cpp
index a20aba6e1a..4e7ec489fb 100644
--- a/src/libs/utils/styledbar.cpp
+++ b/src/libs/utils/styledbar.cpp
@@ -3,6 +3,8 @@
#include "styledbar.h"
+#include "stylehelper.h"
+
#include <QPainter>
#include <QStyleOption>
@@ -11,26 +13,26 @@ using namespace Utils;
StyledBar::StyledBar(QWidget *parent)
: QWidget(parent)
{
- setProperty("panelwidget", true);
- setProperty("panelwidget_singlerow", true);
- setProperty("lightColored", false);
+ StyleHelper::setPanelWidget(this);
+ StyleHelper::setPanelWidgetSingleRow(this);
+ setProperty(StyleHelper::C_LIGHT_COLORED, false);
}
void StyledBar::setSingleRow(bool singleRow)
{
- setProperty("panelwidget_singlerow", singleRow);
+ StyleHelper::setPanelWidgetSingleRow(this, 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<QWidget *> children = findChildren<QWidget *>();
for (QWidget *childWidget : children)
childWidget->style()->polish(childWidget);
@@ -38,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 9f849b9a61..cc860e715a 100644
--- a/src/libs/utils/stylehelper.cpp
+++ b/src/libs/utils/stylehelper.cpp
@@ -12,6 +12,7 @@
#include <QFileInfo>
#include <QFontDatabase>
#include <QPainter>
+#include <QPainterPath>
#include <QPixmapCache>
#include <QStyleOption>
#include <QWindow>
@@ -36,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;
@@ -58,6 +64,36 @@ 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;
+}
+
+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,10 +125,6 @@ QColor StyleHelper::panelTextColor(bool lightColored)
return Qt::black;
}
-// 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);
@@ -101,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))
@@ -149,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.
@@ -173,7 +215,7 @@ void StyleHelper::setBaseColor(const QColor &newcolor)
if (color.isValid() && color != m_baseColor) {
m_baseColor = color;
- const QList<QWidget *> widgets = QApplication::topLevelWidgets();
+ const QWidgetList widgets = QApplication::allWidgets();
for (QWidget *w : widgets)
w->update();
}
@@ -439,6 +481,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()) {
@@ -462,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);
@@ -634,6 +697,16 @@ QLinearGradient StyleHelper::statusBarGradient(const QRect &statusBarRect)
return grad;
}
+void StyleHelper::setPanelWidget(QWidget *widget, bool value)
+{
+ widget->setProperty(C_PANEL_WIDGET, value);
+}
+
+void StyleHelper::setPanelWidgetSingleRow(QWidget *widget, bool value)
+{
+ widget->setProperty(C_PANEL_WIDGET_SINGLE_ROW, 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..2b898cb104 100644
--- a/src/libs/utils/stylehelper.h
+++ b/src/libs/utils/stylehelper.h
@@ -13,115 +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;
-
- // Height of the project explorer navigation bar
- static int navigationWidgetHeight() { return 24; }
- 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);
-
- // 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 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<IconFontHelper> &parameters);
- 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<int> 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 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<IconFontHelper> &parameters);
+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<int> 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
diff --git a/src/libs/utils/tasktree.cpp b/src/libs/utils/tasktree.cpp
deleted file mode 100644
index 90e4073e03..0000000000
--- a/src/libs/utils/tasktree.cpp
+++ /dev/null
@@ -1,1401 +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 "guard.h"
-#include "qtcassert.h"
-
-#include <QSet>
-
-namespace Utils {
-namespace Tasking {
-
-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, 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<TaskItem> &children)
-{
- QTC_ASSERT(m_type == Type::Group, qWarning("Only Task 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);
- 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);
- 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);
- 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:
- QTC_ASSERT(m_type == Type::Group, qWarning("Group Handler may only be a "
- "child of 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;
- }
- }
-}
-
-} // namespace Tasking
-
-using namespace Tasking;
-
-class TaskTreePrivate;
-class TaskNode;
-
-class TaskContainer
-{
-public:
- TaskContainer(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
- TaskContainer *parentContainer)
- : m_constData(taskTreePrivate, task, 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,
- TaskContainer *parentContainer, TaskContainer *thisContainer);
- ~ConstData() { qDeleteAll(m_children); }
- TaskTreePrivate * const m_taskTreePrivate = nullptr;
- TaskContainer * const m_parentContainer = nullptr;
-
- const int m_parallelLimit = 1;
- const WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError;
- const TaskItem::GroupHandler m_groupHandler;
- const QList<TreeStorageBase> m_storageList;
- const QList<TaskNode *> m_children;
- const int m_taskCount = 0;
- };
-
- struct RuntimeData {
- RuntimeData(const ConstData &constData);
- ~RuntimeData();
-
- static QList<int> createStorages(const TaskContainer::ConstData &constData);
- void callStorageDoneHandlers();
- bool updateSuccessBit(bool success);
- int currentLimit() const;
-
- const ConstData &m_constData;
- const QList<int> m_storageIdList;
- int m_doneCount = 0;
- bool m_successBit = true;
- Guard m_startGuard;
- };
-
- const ConstData m_constData;
- std::optional<RuntimeData> m_runtimeData;
-};
-
-class TaskNode : public QObject
-{
-public:
- TaskNode(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
- TaskContainer *parentContainer)
- : m_taskHandler(task.taskHandler())
- , m_container(taskTreePrivate, task, 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 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; }
-
-private:
- const TaskItem::TaskHandler m_taskHandler;
- TaskContainer m_container;
- std::unique_ptr<TaskInterface> m_task;
-};
-
-class TaskTreePrivate
-{
-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);
- }
- 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<TreeStorageBase> addStorages(const QList<TreeStorageBase> &storages) {
- QList<TreeStorageBase> 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);
- }
- 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<TreeStorageBase> m_storages;
- QHash<TreeStorageBase, StorageHandler> m_storageHandlers;
- std::unique_ptr<TaskNode> m_root = nullptr; // Keep me last in order to destruct first
-};
-
-class StorageActivator
-{
-public:
- StorageActivator(TaskContainer *container)
- : m_container(container) { activateStorages(m_container); }
- ~StorageActivator() { deactivateStorages(m_container); }
-
-private:
- static void activateStorages(TaskContainer *container)
- {
- QTC_ASSERT(container && container->isRunning(), return);
- const TaskContainer::ConstData &constData = container->m_constData;
- if (constData.m_parentContainer)
- activateStorages(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 deactivateStorages(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);
- }
- TaskContainer *m_container = nullptr;
-};
-
-template <typename Handler, typename ...Args,
- typename ReturnType = typename std::invoke_result_t<Handler, Args...>>
-ReturnType invokeHandler(TaskContainer *container, Handler &&handler, Args &&...args)
-{
- StorageActivator activator(container);
- GuardLocker locker(container->m_constData.m_taskTreePrivate->m_guard);
- return std::invoke(std::forward<Handler>(handler), std::forward<Args>(args)...);
-}
-
-static QList<TaskNode *> createChildren(TaskTreePrivate *taskTreePrivate, TaskContainer *container,
- const TaskItem &task)
-{
- QList<TaskNode *> result;
- const QList<TaskItem> &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,
- TaskContainer *parentContainer, TaskContainer *thisContainer)
- : m_taskTreePrivate(taskTreePrivate)
- , 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<int> TaskContainer::RuntimeData::createStorages(const TaskContainer::ConstData &constData)
-{
- QList<int> 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 (m_constData.m_children.isEmpty() && startAction == TaskAction::Continue)
- 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 = invokeHandler(parentContainer(), m_taskHandler.m_setupHandler,
- *m_task.get());
- if (startAction != TaskAction::Continue) {
- m_container.m_constData.m_taskTreePrivate->advanceProgress(1);
- m_task.reset();
- return startAction;
- }
- const std::shared_ptr<TaskAction> unwindAction
- = std::make_shared<TaskAction>(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 Utils::TaskTree
- \inheaderfile utils/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 QtcProcess,
- FileTransfer, or AsyncTask<ReturnType>. 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,
- or AsyncTask<ReturnType>:
-
- \code
- using namespace Utils;
- using namespace Tasking;
-
- const Group root {
- Process(...),
- Async<int>(...),
- Transfer(...)
- };
-
- 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 QtcProcess, FileTransfer, and AsyncTask<int>.
- After taskTree->start() is called, the tasks are run in a chain, starting
- with Process. When Process finishes successfully, the Async<int> task is
- started. Finally, when the asynchronous task finishes successfully, the
- FileTransfer 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,
- Process(...),
- Async<int>(...)
- },
- Transfer(...)
- };
- \endcode
-
- The example above differs from the first example in that the root element has
- a subgroup that contains the Process and Async<int> tasks. The subgroup is a
- sibling element of the Transfer task 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<int> tasks 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
- 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<int>), 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 Process starts
- \li Process starts
- \row
- \li Async<int> starts
- \li Async<int> starts
- \row
- \li ...
- \li ...
- \row
- \li \b {Process finishes}
- \li \b {Async<int> finishes}
- \row
- \li ...
- \li ...
- \row
- \li \b {Async<int> finishes}
- \li \b {Process finishes}
- \row
- \li Sub Group finishes
- \li Sub Group finishes
- \row
- \li Transfer starts
- \li Transfer starts
- \row
- \li ...
- \li ...
- \row
- \li Transfer finishes
- \li Transfer 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 Process finishes with an error while Async<int> is still being executed,
- Async<int> is automatically stopped, the subgroup finishes with an error,
- Transfer 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
- 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
- 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 Process
- \li Utils::QtcProcess
- \li Starts processes.
- \row
- \li Async<ReturnType>
- \li Utils::AsyncTask<ReturnType>
- \li Starts asynchronous tasks; run in separate thread.
- \row
- \li Tree
- \li Utils::TaskTree
- \li Starts a nested task tree.
- \row
- \li Transfer
- \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 = [](QtcProcess &process) {
- process.setCommand({"sleep", {"3"}});
- };
- const Group root {
- Process(onSetup)
- };
- \endcode
-
- You can modify the passed QtcProcess 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 = [](QtcProcess &process) {
- process.setCommand({"sleep", {"3"}});
- };
- const auto onDone = [](const QtcProcess &process) {
- qDebug() << "Success" << process.cleanedStdOut();
- };
- const auto onError = [](const QtcProcess &process) {
- qDebug() << "Failure" << process.cleanedStdErr();
- };
- const Group root {
- Process(onSetup, onDone, onError)
- };
- \endcode
-
- The done and error handlers may collect output data from QtcProcess, 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),
- Process(...)
- };
- \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; }),
- Process(...) // Process 1
- },
- Group {
- OnGroupSetup([] { qDebug() << "Group 2 setup"; return TaskAction::StopWithDone; }),
- Process(...) // Process 2
- },
- Group {
- OnGroupSetup([] { qDebug() << "Group 3 setup"; return TaskAction::StopWithError; }),
- Process(...) // Process 3
- },
- Process(...) // 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"; }),
- Process(...),
- 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<CopyStorage> storage;
-
- const auto onLoaderSetup = [source](Async<QByteArray> &async) {
- async.setAsyncCallData(&load, source);
- };
- // [4] runtime: task tree activates the instance from [5] before invoking handler
- const auto onLoaderDone = [storage](const Async<QByteArray> &async) {
- storage->content = async.result();
- };
-
- // [4] runtime: task tree activates the instance from [5] before invoking handler
- const auto onSaverSetup = [storage, destination](Async<void> &async) {
- async.setAsyncCallData(&save, destination, storage->content);
- };
- const auto onSaverDone = [](const Async<void> &async) {
- qDebug() << "Save done successfully";
- };
-
- const Group root {
- // [5] runtime: task tree creates an instance of CopyStorage when root is entered
- Storage(storage),
- Async<QByteArray>(onLoaderSetup, onLoaderDone),
- Async<void>(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<CopyStorage> 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<CopyStorage>
- 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<CopyStorage> 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<CopyStorage> object. Therefore, the CopyStorage struct may be
- accessed only from within the handler body. To access the currently active
- CopyStorage from within TreeStorage<CopyStorage>, 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<MyStorage> storage [3]
- \li Pass the TreeStorage<MyStorage> instance to handlers [4]
- \li Insert the TreeStorage<MyStorage> 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 Tasking::Tree
- 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<CopyStorage> 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<CopyStorage> 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 Utils::Tasking::TaskAdapter<QTimer>
- {
- 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 Utils::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!"));
- delete d;
-}
-
-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_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 Tasking::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;
- }
-}
-
-TaskTreeAdapter::TaskTreeAdapter()
-{
- connect(task(), &TaskTree::done, this, [this] { emit done(true); });
- connect(task(), &TaskTree::errorOccurred, this, [this] { emit done(false); });
-}
-
-void TaskTreeAdapter::start()
-{
- task()->start();
-}
-
-} // namespace Utils
diff --git a/src/libs/utils/tasktree.h b/src/libs/utils/tasktree.h
deleted file mode 100644
index 54a0cfda53..0000000000
--- a/src/libs/utils/tasktree.h
+++ /dev/null
@@ -1,385 +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 <QHash>
-#include <QObject>
-#include <QSharedPointer>
-
-namespace Utils {
-
-class StorageActivator;
-class TaskContainer;
-class TaskTreePrivate;
-
-namespace Tasking {
-
-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<void *(void)>;
- using StorageDestructor = std::function<void(void *)>;
-
- 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<int, void *> m_storageHash = {};
- int m_activeStorage = 0; // 0 means no active storage
- int m_storageCounter = 0;
- };
- QSharedPointer<StorageData> m_storageData;
- friend TaskContainer;
- friend TaskTreePrivate;
- friend StorageActivator;
-};
-
-template <typename StorageStruct>
-class TreeStorage : public TreeStorageBase
-{
-public:
- TreeStorage() : TreeStorageBase(TreeStorage::ctor(), TreeStorage::dtor()) {}
- StorageStruct *operator->() const noexcept { return activeStorage(); }
- StorageStruct *activeStorage() const {
- return static_cast<StorageStruct *>(activeStorageVoid());
- }
-
-private:
- static StorageConstructor ctor() { return [] { return new StorageStruct; }; }
- static StorageDestructor dtor() {
- return [](void *storage) { delete static_cast<StorageStruct *>(storage); };
- }
-};
-
-// 4 policies:
-// 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
-// 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
-
-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
-};
-
-enum class TaskAction
-{
- Continue,
- StopWithDone,
- StopWithError
-};
-
-class QTCREATOR_UTILS_EXPORT TaskItem
-{
-public:
- // Internal, provided by QTC_DECLARE_CUSTOM_TASK
- using TaskCreateHandler = std::function<TaskInterface *(void)>;
- // Called prior to task start, just after createHandler
- using TaskSetupHandler = std::function<TaskAction(TaskInterface &)>;
- // Called on task done / error
- using TaskEndHandler = std::function<void(const TaskInterface &)>;
- // Called when group entered
- using GroupSetupHandler = std::function<TaskAction()>;
- // Called when group done / error
- using GroupEndHandler = std::function<void()>;
-
- 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<TaskItem> children() const { return m_children; }
- QList<TreeStorageBase> 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<TaskItem> &children);
-
-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<TreeStorageBase> m_storageList;
- QList<TaskItem> m_children;
-};
-
-class QTCREATOR_UTILS_EXPORT Group : public TaskItem
-{
-public:
- Group(const QList<TaskItem> &children) { addChildren(children); }
- Group(std::initializer_list<TaskItem> 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 <typename SetupFunction>
- OnGroupSetup(SetupFunction &&function)
- : TaskItem({wrapSetup(std::forward<SetupFunction>(function))}) {}
-
-private:
- template<typename SetupFunction>
- static TaskItem::GroupSetupHandler wrapSetup(SetupFunction &&function) {
- static constexpr bool isDynamic = std::is_same_v<TaskAction,
- std::invoke_result_t<std::decay_t<SetupFunction>>>;
- constexpr bool isVoid = std::is_same_v<void,
- std::invoke_result_t<std::decay_t<SetupFunction>>>;
- 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}) {}
-};
-
-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 <typename Task>
-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 <typename Adapter>
-class CustomTask : public TaskItem
-{
-public:
- using Task = typename Adapter::Type;
- using EndHandler = std::function<void(const Task &)>;
- static Adapter *createAdapter() { return new Adapter; }
- template <typename SetupFunction>
- CustomTask(SetupFunction &&function, const EndHandler &done = {}, const EndHandler &error = {})
- : TaskItem({&createAdapter, wrapSetup(std::forward<SetupFunction>(function)),
- wrapEnd(done), wrapEnd(error)}) {}
-
-private:
- template<typename SetupFunction>
- static TaskItem::TaskSetupHandler wrapSetup(SetupFunction &&function) {
- static constexpr bool isDynamic = std::is_same_v<TaskAction,
- std::invoke_result_t<std::decay_t<SetupFunction>, typename Adapter::Type &>>;
- constexpr bool isVoid = std::is_same_v<void,
- std::invoke_result_t<std::decay_t<SetupFunction>, 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<Adapter &>(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<const Adapter &>(taskInterface);
- handler(*adapter.task());
- };
- };
-};
-
-} // namespace Tasking
-
-class TaskTreePrivate;
-
-class QTCREATOR_UTILS_EXPORT TaskTree : public QObject
-{
- Q_OBJECT
-
-public:
- TaskTree();
- TaskTree(const Tasking::Group &root);
- ~TaskTree();
-
- void setupRoot(const Tasking::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 <typename StorageStruct, typename StorageHandler>
- void onStorageSetup(const Tasking::TreeStorage<StorageStruct> &storage,
- StorageHandler &&handler) {
- setupStorageHandler(storage,
- wrapHandler<StorageStruct>(std::forward<StorageHandler>(handler)), {});
- }
- template <typename StorageStruct, typename StorageHandler>
- void onStorageDone(const Tasking::TreeStorage<StorageStruct> &storage,
- StorageHandler &&handler) {
- setupStorageHandler(storage,
- {}, wrapHandler<StorageStruct>(std::forward<StorageHandler>(handler)));
- }
-
-signals:
- void started();
- void done();
- void errorOccurred();
- void progressValueChanged(int value); // updated whenever task finished / skipped / stopped
-
-private:
- using StorageVoidHandler = std::function<void(void *)>;
- void setupStorageHandler(const Tasking::TreeStorageBase &storage,
- StorageVoidHandler setupHandler,
- StorageVoidHandler doneHandler);
- template <typename StorageStruct, typename StorageHandler>
- StorageVoidHandler wrapHandler(StorageHandler &&handler) {
- return [=](void *voidStruct) {
- StorageStruct *storageStruct = static_cast<StorageStruct *>(voidStruct);
- std::invoke(handler, storageStruct);
- };
- }
-
- friend class TaskTreePrivate;
- TaskTreePrivate *d;
-};
-
-class QTCREATOR_UTILS_EXPORT TaskTreeAdapter : public Tasking::TaskAdapter<TaskTree>
-{
-public:
- TaskTreeAdapter();
- void start() final;
-};
-
-} // namespace Utils
-
-#define QTC_DECLARE_CUSTOM_TASK(CustomTaskName, TaskAdapterClass)\
-namespace Utils::Tasking { using CustomTaskName = CustomTask<TaskAdapterClass>; }
-
-#define QTC_DECLARE_CUSTOM_TEMPLATE_TASK(CustomTaskName, TaskAdapterClass)\
-namespace Utils::Tasking {\
-template <typename ...Args>\
-using CustomTaskName = CustomTask<TaskAdapterClass<Args...>>;\
-} // namespace Utils::Tasking
-
-QTC_DECLARE_CUSTOM_TASK(Tree, Utils::TaskTreeAdapter);
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
new file mode 100644
index 0000000000..3bda25b109
--- /dev/null
+++ b/src/libs/utils/terminalhooks.cpp
@@ -0,0 +1,129 @@
+// 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 "externalterminalprocessimpl.h"
+#include "filepath.h"
+#include "process.h"
+
+#include <QMutex>
+
+namespace Utils::Terminal {
+
+FilePath defaultShellForDevice(const FilePath &deviceRoot)
+{
+ if (deviceRoot.osType() == OsTypeWindows)
+ return deviceRoot.withNewPath("cmd.exe").searchInPath();
+
+ 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 deviceRoot.withNewMappedPath(shell);
+}
+
+class HooksPrivate
+{
+public:
+ HooksPrivate()
+ : m_getTerminalCommandsForDevicesHook([] { return QList<NameAndCommandLine>{}; })
+ {
+ auto openTerminal = [](const OpenTerminalParameters &parameters) {
+ DeviceFileHooks::instance().openTerminal(parameters.workingDirectory.value_or(
+ FilePath{}),
+ parameters.environment.value_or(Environment{}));
+ };
+ 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::GetTerminalCommandsForDevicesHook m_getTerminalCommandsForDevicesHook;
+
+private:
+ Hooks::OpenTerminal m_openTerminal;
+ Hooks::CreateTerminalProcessInterface m_createTerminalProcessInterface;
+
+ QMutex m_mutex;
+ QList<QPair<QString, Hooks::CallbackSet>> m_callbackSets;
+};
+
+Hooks &Hooks::instance()
+{
+ static Hooks manager;
+ return manager;
+}
+
+Hooks::Hooks()
+ : d(new HooksPrivate())
+{}
+
+Hooks::~Hooks() = default;
+
+void Hooks::openTerminal(const OpenTerminalParameters &parameters) const
+{
+ d->openTerminal()(parameters);
+}
+
+ProcessInterface *Hooks::createTerminalProcessInterface() const
+{
+ return d->createTerminalProcessInterface()();
+}
+
+Hooks::GetTerminalCommandsForDevicesHook &Hooks::getTerminalCommandsForDevicesHook()
+{
+ 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
new file mode 100644
index 0000000000..b37c51517c
--- /dev/null
+++ b/src/libs/utils/terminalhooks.h
@@ -0,0 +1,91 @@
+// 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 "id.h"
+
+#include <functional>
+#include <memory>
+
+namespace Utils {
+class ProcessInterface;
+
+template<typename R, typename... Params>
+class Hook
+{
+ Q_DISABLE_COPY_MOVE(Hook)
+
+public:
+ using Callback = std::function<R(Params...)>;
+
+public:
+ Hook() = delete;
+
+ explicit Hook(Callback defaultCallback) { set(defaultCallback); }
+
+ void set(Callback cb) { m_callback = cb; }
+ R operator()(Params &&...params) { return m_callback(std::forward<Params>(params)...); }
+
+private:
+ Callback m_callback;
+};
+
+namespace Terminal {
+class HooksPrivate;
+
+enum class ExitBehavior { Close, Restart, Keep };
+
+struct OpenTerminalParameters
+{
+ std::optional<CommandLine> shellCommand;
+ std::optional<FilePath> workingDirectory;
+ std::optional<Environment> environment;
+ ExitBehavior m_exitBehavior{ExitBehavior::Close};
+ std::optional<Id> identifier{std::nullopt};
+};
+
+struct NameAndCommandLine
+{
+ QString name;
+ CommandLine commandLine;
+};
+
+QTCREATOR_UTILS_EXPORT FilePath defaultShellForDevice(const FilePath &deviceRoot);
+
+class QTCREATOR_UTILS_EXPORT Hooks
+{
+public:
+ using OpenTerminal = std::function<void(const OpenTerminalParameters &)>;
+ using CreateTerminalProcessInterface = std::function<ProcessInterface *()>;
+
+ struct CallbackSet
+ {
+ OpenTerminal openTerminal;
+ CreateTerminalProcessInterface createTerminalProcessInterface;
+ };
+
+ using GetTerminalCommandsForDevicesHook = Hook<QList<NameAndCommandLine>>;
+
+public:
+ static Hooks &instance();
+ ~Hooks();
+
+ GetTerminalCommandsForDevicesHook &getTerminalCommandsForDevicesHook();
+
+ void openTerminal(const OpenTerminalParameters &parameters) const;
+ ProcessInterface *createTerminalProcessInterface() const;
+
+ void addCallbackSet(const QString &name, const CallbackSet &callbackSet);
+ void removeCallbackSet(const QString &name);
+
+private:
+ Hooks();
+ std::unique_ptr<HooksPrivate> d;
+};
+
+} // namespace Terminal
+} // namespace Utils
diff --git a/src/libs/utils/terminalinterface.cpp b/src/libs/utils/terminalinterface.cpp
new file mode 100644
index 0000000000..6767c16c61
--- /dev/null
+++ b/src/libs/utils/terminalinterface.cpp
@@ -0,0 +1,433 @@
+// 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 <QLocalServer>
+#include <QLocalSocket>
+#include <QLoggingCategory>
+#include <QTemporaryDir>
+#include <QTemporaryFile>
+#include <QTextStream>
+#include <QThread>
+#include <QTimer>
+
+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);
+}
+
+static QString msgPromptToClose()
+{
+ // Shown in a terminal which might have a different character set on Windows.
+ return Tr::tr("Press <RETURN> to close this window...");
+}
+
+class TerminalInterfacePrivate : public QObject
+{
+ Q_OBJECT
+public:
+ TerminalInterfacePrivate(TerminalInterface *p, bool waitOnExit)
+ : q(p)
+ , waitOnExit(waitOnExit)
+ {
+ 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<QTemporaryFile> envListFile;
+ QTemporaryDir tempDir;
+
+ std::unique_ptr<QTimer> stubConnectTimeoutTimer;
+
+ ProcessResultData processResultData;
+ TerminalInterface *q;
+
+ StubCreator *stubCreator{nullptr};
+
+ const bool waitOnExit;
+};
+
+TerminalInterface::TerminalInterface(bool waitOnExit)
+ : d(new TerminalInterfacePrivate(this, waitOnExit))
+{}
+
+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<void> 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;
+
+ if (m_setup.m_terminalMode == TerminalMode::Detached) {
+ expected_str<qint64> result;
+ QMetaObject::invokeMethod(
+ d->stubCreator,
+ [this, &result] { result = d->stubCreator->startStubProcess(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<void> result = startStubServer();
+ if (!result) {
+ emitError(QProcess::FailedToStart, msgCommChannelFailed(result.error()));
+ return;
+ }
+
+ 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);
+ }
+ } else if (HostOsInfo::isMacHost()) {
+ finalEnv.set("TERM", "xterm-256color");
+ }
+
+ if (finalEnv.hasChanges()) {
+ d->envListFile = std::make_unique<QTemporaryFile>(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({"--wait", d->waitOnExit ? msgPromptToClose() : ""});
+
+ cmd.addArgs({"--", m_setup.m_commandLine.executable().nativePath()});
+ cmd.addArgs(m_setup.m_commandLine.arguments(), CommandLine::Raw);
+
+ QTC_ASSERT(d->stubCreator, return);
+
+ ProcessSetupData stubSetupData = m_setup;
+ stubSetupData.m_commandLine = cmd;
+
+ QMetaObject::invokeMethod(
+ d->stubCreator,
+ [stubSetupData, this] { d->stubCreator->startStubProcess(stubSetupData); },
+ d->stubCreator->thread() == QThread::currentThread() ? Qt::DirectConnection
+ : Qt::BlockingQueuedConnection);
+
+ d->stubConnectTimeoutTimer = std::make_unique<QTimer>();
+
+ 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)
+{
+ QTC_ASSERT(m_setup.m_terminalMode != TerminalMode::Detached, return);
+
+ 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..a1960e7b96
--- /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 expected_str<qint64> startStubProcess(const ProcessSetupData &setup) = 0;
+};
+
+class QTCREATOR_UTILS_EXPORT TerminalInterface : public ProcessInterface
+{
+ friend class TerminalInterfacePrivate;
+ friend class StubCreator;
+
+public:
+ TerminalInterface(bool waitOnExit = true);
+ ~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<void> 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 bd0333c9d7..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 <QCoreApplication>
-#include <QLocalServer>
-#include <QLocalSocket>
-#include <QRegularExpression>
-#include <QTemporaryFile>
-#include <QTextCodec>
-#include <QTimer>
-#include <QWinEventNotifier>
-
-#ifdef Q_OS_WIN
-
-#include "winutils.h"
-
-#include <cstring>
-#include <stdlib.h>
-#include <windows.h>
-
-#else
-
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <errno.h>
-#include <string.h>
-#include <unistd.h>
-
-#endif
-
-namespace Utils {
-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:
- 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 <RETURN> 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 <QProcess>
-
-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/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..264c6c4351 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.
@@ -51,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)
@@ -84,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)
@@ -152,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
@@ -162,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.
*/
@@ -211,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
@@ -229,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
@@ -278,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
diff --git a/src/libs/utils/textutils.cpp b/src/libs/utils/textutils.cpp
index 1222038039..28a9e12e1b 100644
--- a/src/libs/utils/textutils.cpp
+++ b/src/libs/utils/textutils.cpp
@@ -2,40 +2,116 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "textutils.h"
+#include "qtcassert.h"
-#include <QTextDocument>
+#include <QRegularExpression>
#include <QTextBlock>
+#include <QTextDocument>
+
+namespace Utils::Text {
+
+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);
+ 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();
+ }
+ 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;
+}
+
+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()};
+
+ 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)
+ return -1;
-namespace Utils {
-namespace Text {
+ if (begin.line == end.line)
+ return end.column - begin.column;
+
+ int index = 0;
+ int currentLine = 1;
+ 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;
+ }
+ return index + end.column - beginIndex;
+}
+
+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)
{
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;
}
}
-OptionalLineColumn convertPosition(const QTextDocument *document, int pos)
-{
- OptionalLineColumn optional;
-
- QTextBlock block = document->findBlock(pos);
-
- if (block.isValid())
- optional.emplace(block.blockNumber() + 1, pos - block.position() + 1);
-
- return optional;
-}
-
int positionInText(const QTextDocument *textDocument, int line, int column)
{
// Deduct 1 from line and column since they are 1-based.
@@ -141,21 +217,6 @@ int utf8NthLineOffset(const QTextDocument *textDocument, const QByteArray &buffe
return utf8Offset;
}
-LineColumn utf16LineColumn(const QByteArray &utf8Buffer, int utf8Offset)
-{
- LineColumn lineColumn;
- lineColumn.line = static_cast<int>(
- 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()
- + 1;
- return lineColumn;
-}
-
QString utf16LineTextInUtf8Buffer(const QByteArray &utf8Buffer, int currentUtf8Offset)
{
const int lineStartUtf8Offset = currentUtf8Offset
@@ -211,5 +272,10 @@ void applyReplacements(QTextDocument *doc, const Replacements &replacements)
editCursor.endEditBlock();
}
-} // Text
-} // Utils
+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 e214f74659..80e4150c1d 100644
--- a/src/libs/utils/textutils.h
+++ b/src/libs/utils/textutils.h
@@ -5,8 +5,7 @@
#include "utils_global.h"
-#include "linecolumn.h"
-
+#include <QMetaType>
#include <QString>
QT_BEGIN_NAMESPACE
@@ -17,6 +16,39 @@ 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)); }
+
+ bool isValid() const { return line > 0 && column >= 0; }
+
+ 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
+{
+public:
+ 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;
@@ -36,12 +68,10 @@ using Replacements = std::vector<Replacement>;
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);
-QTCREATOR_UTILS_EXPORT
-OptionalLineColumn convertPosition(const QTextDocument *document, int pos);
// line and column are 1-based
QTCREATOR_UTILS_EXPORT int positionInText(const QTextDocument *textDocument, int line, int column);
@@ -60,9 +90,13 @@ 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);
+QTCREATOR_UTILS_EXPORT QDebug &operator<<(QDebug &stream, const Position &pos);
+
} // Text
} // Utils
+
+Q_DECLARE_METATYPE(Utils::Text::Position)
+Q_DECLARE_METATYPE(Utils::Text::Range)
diff --git a/src/libs/utils/theme/theme.h b/src/libs/utils/theme/theme.h
index 46acd2cf06..2604780a2d 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,
@@ -438,6 +437,28 @@ public:
DSstatePanelBackground,
DSstateHighlight,
+
+ TerminalForeground,
+ TerminalBackground,
+ TerminalSelection,
+ TerminalFindMatch,
+
+ TerminalAnsi0,
+ TerminalAnsi1,
+ TerminalAnsi2,
+ TerminalAnsi3,
+ TerminalAnsi4,
+ TerminalAnsi5,
+ TerminalAnsi6,
+ TerminalAnsi7,
+ TerminalAnsi8,
+ TerminalAnsi9,
+ TerminalAnsi10,
+ TerminalAnsi11,
+ TerminalAnsi12,
+ TerminalAnsi13,
+ TerminalAnsi14,
+ TerminalAnsi15,
};
enum ImageFile {
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)
diff --git a/src/libs/utils/treemodel.cpp b/src/libs/utils/treemodel.cpp
index 344a77d837..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()
@@ -895,6 +908,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.qbs b/src/libs/utils/utils.qbs
index 451a9f3494..df23fc3ba2 100644
--- a/src/libs/utils/utils.qbs
+++ b/src/libs/utils/utils.qbs
@@ -35,7 +35,9 @@ 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" }
files: [
"QtConcurrentTools",
@@ -48,8 +50,8 @@ Project {
"archive.h",
"aspects.cpp",
"aspects.h",
- "asynctask.cpp",
- "asynctask.h",
+ "async.cpp",
+ "async.h",
"basetreeview.cpp",
"basetreeview.h",
"benchmarker.cpp",
@@ -108,6 +110,8 @@ Project {
"execmenu.cpp",
"execmenu.h",
"executeondestruction.h",
+ "externalterminalprocessimpl.cpp",
+ "externalterminalprocessimpl.h",
"fadingindicator.cpp",
"fadingindicator.h",
"faketooltip.cpp",
@@ -126,6 +130,10 @@ Project {
"filepath.h",
"filesearch.cpp",
"filesearch.h",
+ "filestreamer.cpp",
+ "filestreamer.h",
+ "filestreamermanager.cpp",
+ "filestreamermanager.h",
"filesystemmodel.cpp",
"filesystemmodel.h",
"filesystemwatcher.cpp",
@@ -178,8 +186,6 @@ Project {
"launchersocket.h",
"layoutbuilder.cpp",
"layoutbuilder.h",
- "linecolumn.cpp",
- "linecolumn.h",
"link.cpp",
"link.h",
"listmodel.h",
@@ -232,6 +238,8 @@ Project {
"port.h",
"portlist.cpp",
"portlist.h",
+ "process.cpp",
+ "process.h",
"processenums.h",
"processhandle.cpp",
"processhandle.h",
@@ -255,8 +263,6 @@ Project {
"qtcassert.h",
"qtcolorbutton.cpp",
"qtcolorbutton.h",
- "qtcprocess.cpp",
- "qtcprocess.h",
"qtcsettings.cpp",
"qtcsettings.h",
"reloadpromptutils.cpp",
@@ -268,6 +274,10 @@ Project {
"savefile.cpp",
"savefile.h",
"scopedswap.h",
+ "scopedtimer.cpp",
+ "scopedtimer.h",
+ "searchresultitem.cpp",
+ "searchresultitem.h",
"set_algorithm.h",
"settingsaccessor.cpp",
"settingsaccessor.h",
@@ -299,8 +309,6 @@ Project {
"styledbar.h",
"stylehelper.cpp",
"stylehelper.h",
- "tasktree.cpp",
- "tasktree.h",
"templateengine.cpp",
"templateengine.h",
"temporarydirectory.cpp",
@@ -309,8 +317,10 @@ Project {
"temporaryfile.h",
"terminalcommand.cpp",
"terminalcommand.h",
- "terminalprocess.cpp",
- "terminalprocess_p.h",
+ "terminalhooks.cpp",
+ "terminalhooks.h",
+ "terminalinterface.cpp",
+ "terminalinterface.h",
"textfieldcheckbox.cpp",
"textfieldcheckbox.h",
"textfieldcombobox.cpp",
@@ -329,7 +339,7 @@ Project {
"headerviewstretcher.h",
"uncommentselection.cpp",
"uncommentselection.h",
- "uniqueobjectptr.h"
+ "uniqueobjectptr.h",
"unixutils.cpp",
"unixutils.h",
"url.cpp",
@@ -464,6 +474,7 @@ Project {
Export {
Depends { name: "Qt"; submodules: ["concurrent", "widgets" ] }
+ Depends { name: "Tasking" }
cpp.includePaths: base.concat("mimetypes2")
}
}
diff --git a/src/libs/utils/utils.qdoc b/src/libs/utils/utils.qdoc
index c071704331..bc9f843498 100644
--- a/src/libs/utils/utils.qdoc
+++ b/src/libs/utils/utils.qdoc
@@ -3,7 +3,8 @@
/*!
\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.
*/
diff --git a/src/libs/utils/utils.qrc b/src/libs/utils/utils.qrc
index 1f2ef64898..c0f2d2559a 100644
--- a/src/libs/utils/utils.qrc
+++ b/src/libs/utils/utils.qrc
@@ -36,6 +36,8 @@
<file>images/unlocked@2x.png</file>
<file>images/pinned.png</file>
<file>images/pinned@2x.png</file>
+ <file>images/pinned_small.png</file>
+ <file>images/pinned_small@2x.png</file>
<file>images/broken.png</file>
<file>images/broken@2x.png</file>
<file>images/notloaded.png</file>
@@ -173,6 +175,8 @@
<file>images/iconoverlay_add@2x.png</file>
<file>images/iconoverlay_add_background.png</file>
<file>images/iconoverlay_add_background@2x.png</file>
+ <file>images/iconoverlay_close_small.png</file>
+ <file>images/iconoverlay_close_small@2x.png</file>
<file>images/iconoverlay_error.png</file>
<file>images/iconoverlay_error@2x.png</file>
<file>images/iconoverlay_error_background.png</file>
diff --git a/src/libs/utils/utilsicons.cpp b/src/libs/utils/utilsicons.cpp
index 7263b28594..01b1f0c1db 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({
@@ -225,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 b8fda0c53f..5a75267a36 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;
@@ -120,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;
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 <functional>
+
+namespace Utils {
+class FilePath;
+
+enum class IterationPolicy { Stop, Continue };
+
+using FilePathPredicate = std::function<bool(const FilePath &)>;
+} // namespace Utils
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<MacroExpander *()>;
+
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<MacroExpander *()> &provider);
+ void addMacroExpanderProvider(const MacroExpanderProvider &provider);
void addSupportedWidget(QWidget *textcontrol, const QByteArray &ownName = QByteArray());
static void addSupportForChildWidgets(QWidget *parent, MacroExpander *expander);
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 <QVBoxLayout>
-/*! \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.