Compare commits
35 Commits
reuse-cygw
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
7e59fe2d09 | ||
|
a6174e8a4d | ||
|
b60ded1cf2 | ||
|
afacf7792f | ||
|
d49712c548 | ||
|
784b812663 | ||
|
ceecad1551 | ||
|
dcb2df4a4e | ||
|
6aae341b74 | ||
|
498a9b1a7f | ||
|
850661d02b | ||
|
f678472b51 | ||
|
39fe32b15d | ||
|
1424696a6d | ||
|
ce9239af5d | ||
|
7d16530ea7 | ||
|
4978cf94b6 | ||
|
0c86e4f78e | ||
|
c9ce3ad1e3 | ||
|
096f978c7a | ||
|
faf75f74b7 | ||
|
3e1ab962d5 | ||
|
e75bd9cbdd | ||
|
6765a41c5b | ||
|
5814705546 | ||
|
b4db322010 | ||
|
af4bc8b78a | ||
|
5b99238fca | ||
|
bae43674b1 | ||
|
ac33b66172 | ||
|
d636a1de86 | ||
|
838d959579 | ||
|
201d8aeb3a | ||
|
6360ec6a5b | ||
|
47e7e005e8 |
17
.drone.yml
17
.drone.yml
@ -1,17 +0,0 @@
|
|||||||
# Build configure for https://www.tea-ci.org (fork of Drone CI with Msys2 support)
|
|
||||||
build:
|
|
||||||
image: teaci/msys$$arch
|
|
||||||
pull: true
|
|
||||||
shell: msys$$arch
|
|
||||||
commands:
|
|
||||||
- pacman -S --needed --noconfirm --noprogressbar mingw-w64-cross-gcc mingw-w64-cross-crt-git
|
|
||||||
- ./configure
|
|
||||||
- make
|
|
||||||
- make tests
|
|
||||||
- build/trivial_test.exe
|
|
||||||
- mintty --log - --exec build/winpty.exe cmd /c ver | grep Windows
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
arch:
|
|
||||||
- 64
|
|
||||||
- 32
|
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,6 +10,7 @@ winpty.opensdf
|
|||||||
/build-gyp
|
/build-gyp
|
||||||
/build-libpty
|
/build-libpty
|
||||||
/ship/packages
|
/ship/packages
|
||||||
|
/ship/tmp
|
||||||
/src/Default
|
/src/Default
|
||||||
/src/Release
|
/src/Release
|
||||||
/src/gen
|
/src/gen
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# winpty
|
# winpty
|
||||||
|
|
||||||
[![Build Status](https://tea-ci.org/api/badges/rprichard/winpty/status.svg)](https://tea-ci.org/rprichard/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
|
winpty is a Windows software package providing an interface similar to a Unix
|
||||||
pty-master for communicating with Windows console programs. The package
|
pty-master for communicating with Windows console programs. The package
|
||||||
|
54
RELEASES.md
54
RELEASES.md
@ -1,3 +1,57 @@
|
|||||||
|
# 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)
|
# Version 0.4.2 (2017-01-18)
|
||||||
|
|
||||||
This release improves WSL support (i.e. Bash-on-Windows):
|
This release improves WSL support (i.e. Bash-on-Windows):
|
||||||
|
@ -1 +1 @@
|
|||||||
0.4.3-dev
|
0.4.4-dev
|
||||||
|
16
appveyor.yml
Executable file
16
appveyor.yml
Executable file
@ -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
|
90
misc/SetBufInfo.cc
Executable file
90
misc/SetBufInfo.cc
Executable file
@ -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;
|
||||||
|
}
|
201
misc/winbug-15048.cc
Normal file
201
misc/winbug-15048.cc
Normal file
@ -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 },
|
||||||
|
®ion));
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
printf("ReadConsoleOutputW (%s)\n", name);
|
||||||
|
for (int y = 0; y < h; ++y) {
|
||||||
|
printf("row %d: ", region.Top + y);
|
||||||
|
for (int i = 0; i < region.Left * 13; ++i) {
|
||||||
|
printf(" ");
|
||||||
|
}
|
||||||
|
for (int x = 0; x < w; ++x) {
|
||||||
|
const int i = y * w + x;
|
||||||
|
printf("U+%04x(%04x) ", buf[i].Char.UnicodeChar, buf[i].Attributes);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
wchar_t *cmdline = GetCommandLineW();
|
||||||
|
int argc = 0;
|
||||||
|
wchar_t **argv = CommandLineToArgvW(cmdline, &argc);
|
||||||
|
const wchar_t *font_name = L"Lucida Console";
|
||||||
|
int font_height = 8;
|
||||||
|
int test_ch = 0xff12; // U+FF12 FULLWIDTH DIGIT TWO
|
||||||
|
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
const std::wstring arg = argv[i];
|
||||||
|
const std::wstring next = i + 1 < argc ? argv[i + 1] : L"";
|
||||||
|
if (arg == L"-face" && i + 1 < argc) {
|
||||||
|
font_name = argv[i + 1];
|
||||||
|
i++;
|
||||||
|
} else if (arg == L"-face-gothic") {
|
||||||
|
font_name = kMSGothic;
|
||||||
|
} else if (arg == L"-face-simsun") {
|
||||||
|
font_name = kNSimSun;
|
||||||
|
} else if (arg == L"-face-minglight") {
|
||||||
|
font_name = kMingLight;
|
||||||
|
} else if (arg == L"-face-gulimche") {
|
||||||
|
font_name = kGulimChe;
|
||||||
|
} else if (arg == L"-h" && i + 1 < argc) {
|
||||||
|
font_height = _wtoi(next.c_str());
|
||||||
|
i++;
|
||||||
|
} else if (arg.c_str()[0] != '-') {
|
||||||
|
test_ch = wcstol(arg.c_str(), NULL, 16);
|
||||||
|
} else {
|
||||||
|
printf("Unrecognized argument: %ls\n", arg.c_str());
|
||||||
|
usage(argv[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||||
|
|
||||||
|
set_font(font_name, font_height);
|
||||||
|
|
||||||
|
system("cls");
|
||||||
|
DWORD actual = 0;
|
||||||
|
wchar_t output[] = L"1234\n5678\n";
|
||||||
|
output[1] = test_ch;
|
||||||
|
WriteConsoleW(conout, output, 10, &actual, nullptr);
|
||||||
|
|
||||||
|
dump_region({ 0, 0, 3, 1 }, "both rows, 3 cols");
|
||||||
|
dump_region({ 0, 0, 4, 1 }, "both rows, 4 cols");
|
||||||
|
dump_region({ 0, 1, 4, 1 }, "second row");
|
||||||
|
dump_region({ 0, 0, 4, 0 }, "first row");
|
||||||
|
dump_region({ 1, 0, 4, 0 }, "first row, skip 1");
|
||||||
|
dump_region({ 2, 0, 4, 0 }, "first row, skip 2");
|
||||||
|
dump_region({ 3, 0, 4, 0 }, "first row, skip 3");
|
||||||
|
|
||||||
|
set_font(font_name, 14);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@ -1,12 +1,17 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
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":
|
if os.name != "nt":
|
||||||
sys.exit("Error: ship scripts require native Python 2.7. (wrong os.name)")
|
sys.exit("Error: ship scripts require native Python 2.7. (wrong os.name)")
|
||||||
if sys.version_info[0:2] != (2,7):
|
if sys.version_info[0:2] != (2,7):
|
||||||
sys.exit("Error: ship scripts require native Python 2.7. (wrong version)")
|
sys.exit("Error: ship scripts require native Python 2.7. (wrong version)")
|
||||||
|
|
||||||
import glob
|
import glob
|
||||||
|
import hashlib
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
from distutils.spawn import find_executable
|
from distutils.spawn import find_executable
|
||||||
@ -20,11 +25,11 @@ def rmrf(patterns):
|
|||||||
for pattern in patterns:
|
for pattern in patterns:
|
||||||
for path in glob.glob(pattern):
|
for path in glob.glob(pattern):
|
||||||
if os.path.isdir(path) and not os.path.islink(path):
|
if os.path.isdir(path) and not os.path.islink(path):
|
||||||
print "+ rm -r " + path
|
print("+ rm -r " + path)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
shutil.rmtree(path)
|
shutil.rmtree(path)
|
||||||
elif os.path.isfile(path):
|
elif os.path.isfile(path):
|
||||||
print "+ rm " + path
|
print("+ rm " + path)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
os.remove(path)
|
os.remove(path)
|
||||||
|
|
||||||
@ -37,17 +42,48 @@ def requireExe(name, guesses):
|
|||||||
for guess in guesses:
|
for guess in guesses:
|
||||||
if os.path.exists(guess):
|
if os.path.exists(guess):
|
||||||
newDir = os.path.dirname(guess)
|
newDir = os.path.dirname(guess)
|
||||||
print "Adding " + newDir + " to Path to provide " + name
|
print("Adding " + newDir + " to Path to provide " + name)
|
||||||
os.environ["Path"] = newDir + ";" + os.environ["Path"]
|
os.environ["Path"] = newDir + ";" + os.environ["Path"]
|
||||||
ret = find_executable(name)
|
ret = find_executable(name)
|
||||||
if ret is None:
|
if ret is None:
|
||||||
sys.exit("Error: required EXE is missing from Path: " + name)
|
sys.exit("Error: required EXE is missing from Path: " + name)
|
||||||
return ret
|
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", [
|
requireExe("git.exe", [
|
||||||
"C:\\Program Files\\Git\\cmd\\git.exe",
|
"C:\\Program Files\\Git\\cmd\\git.exe",
|
||||||
"C:\\Program Files (x86)\\Git\\cmd\\git.exe"
|
"C:\\Program Files (x86)\\Git\\cmd\\git.exe"
|
||||||
])
|
])
|
||||||
|
|
||||||
commitHash = subprocess.check_output(["git.exe", "rev-parse", "HEAD"]).decode().strip()
|
commitHash = subprocess.check_output(["git.exe", "rev-parse", "HEAD"]).strip()
|
||||||
defaultPathEnviron = "C:\\Windows\\System32;C:\\Windows"
|
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", [])
|
||||||
|
@ -36,10 +36,6 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
os.chdir(common_ship.topDir)
|
os.chdir(common_ship.topDir)
|
||||||
ZIP_TOOL = common_ship.requireExe("7z.exe", [
|
|
||||||
"C:\\Program Files\\7-Zip\\7z.exe",
|
|
||||||
"C:\\Program Files (x86)\\7-Zip\\7z.exe",
|
|
||||||
])
|
|
||||||
|
|
||||||
MSVC_VERSION_TABLE = {
|
MSVC_VERSION_TABLE = {
|
||||||
"2015" : {
|
"2015" : {
|
||||||
@ -100,14 +96,16 @@ def build(arch, packageDir, xp=False):
|
|||||||
if not os.path.isfile(devCmdPath):
|
if not os.path.isfile(devCmdPath):
|
||||||
sys.exit("Error: MSVC environment script missing: " + devCmdPath)
|
sys.exit("Error: MSVC environment script missing: " + devCmdPath)
|
||||||
|
|
||||||
|
toolsetArgument = " --toolset {}".format(versionInfo["xp_toolset"]) if xp else ""
|
||||||
newEnv = os.environ.copy()
|
newEnv = os.environ.copy()
|
||||||
newEnv["PATH"] = os.path.dirname(sys.executable) + ";" + common_ship.defaultPathEnviron
|
newEnv["PATH"] = os.path.dirname(sys.executable) + ";" + common_ship.defaultPathEnviron
|
||||||
commandLine = (
|
commandLine = (
|
||||||
'"' + devCmdPath + '" && '
|
'"' + devCmdPath + '" && ' +
|
||||||
" vcbuild.bat" +
|
" vcbuild.bat" +
|
||||||
" --gyp-msvs-version " + versionInfo["gyp_version"] +
|
" --gyp-msvs-version " + versionInfo["gyp_version"] +
|
||||||
" --msvc-platform " + archInfo["msvc_platform"] +
|
" --msvc-platform " + archInfo["msvc_platform"] +
|
||||||
" --commit-hash " + common_ship.commitHash
|
" --commit-hash " + common_ship.commitHash +
|
||||||
|
toolsetArgument
|
||||||
)
|
)
|
||||||
|
|
||||||
subprocess.check_call(commandLine, shell=True, env=newEnv)
|
subprocess.check_call(commandLine, shell=True, env=newEnv)
|
||||||
@ -159,7 +157,7 @@ def buildPackage():
|
|||||||
shutil.copy(topDir + "/README.md", packageDir)
|
shutil.copy(topDir + "/README.md", packageDir)
|
||||||
shutil.copy(topDir + "/RELEASES.md", packageDir)
|
shutil.copy(topDir + "/RELEASES.md", packageDir)
|
||||||
|
|
||||||
subprocess.check_call([ZIP_TOOL, "a", packageFile, "."], cwd=packageDir)
|
subprocess.check_call([common_ship.ZIP_TOOL, "a", packageFile, "."], cwd=packageDir)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
buildPackage()
|
buildPackage()
|
||||||
|
92
ship/ship.py
92
ship/ship.py
@ -23,20 +23,16 @@
|
|||||||
#
|
#
|
||||||
# Run with native CPython 2.7 on a 64-bit computer.
|
# Run with native CPython 2.7 on a 64-bit computer.
|
||||||
#
|
#
|
||||||
# Each of the targets in BUILD_TARGETS must be installed to the default
|
|
||||||
# location. Each target must have the appropriate MinGW and non-MinGW
|
|
||||||
# compilers installed, as well as make and tar.
|
|
||||||
#
|
|
||||||
|
|
||||||
import common_ship
|
import common_ship
|
||||||
|
|
||||||
|
from optparse import OptionParser
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
os.chdir(common_ship.topDir)
|
|
||||||
|
|
||||||
def dllVersion(path):
|
def dllVersion(path):
|
||||||
version = subprocess.check_output(
|
version = subprocess.check_output(
|
||||||
@ -44,46 +40,40 @@ def dllVersion(path):
|
|||||||
"[System.Diagnostics.FileVersionInfo]::GetVersionInfo(\"" + path + "\").FileVersion"])
|
"[System.Diagnostics.FileVersionInfo]::GetVersionInfo(\"" + path + "\").FileVersion"])
|
||||||
return version.strip()
|
return version.strip()
|
||||||
|
|
||||||
# Determine other build parameters.
|
|
||||||
print "Determining Cygwin/MSYS2 DLL versions..."
|
|
||||||
sys.stdout.flush()
|
|
||||||
BUILD_TARGETS = [
|
|
||||||
# {
|
|
||||||
# "name": "msys",
|
|
||||||
# "path": "C:\\MinGW\\bin;C:\\MinGW\\msys\\1.0\\bin",
|
|
||||||
# # The parallel make.exe in the original MSYS/MinGW project hangs.
|
|
||||||
# "make_binary": "mingw32-make.exe",
|
|
||||||
# },
|
|
||||||
{
|
|
||||||
"name": "msys2-" + dllVersion("C:\\msys32\\usr\\bin\\msys-2.0.dll") + "-ia32",
|
|
||||||
"path": "C:\\msys32\\mingw32\\bin;C:\\msys32\\usr\\bin",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "msys2-" + dllVersion("C:\\msys64\\usr\\bin\\msys-2.0.dll") + "-x64",
|
|
||||||
"path": "C:\\msys64\\mingw64\\bin;C:\\msys64\\usr\\bin",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "cygwin-" + dllVersion("C:\\cygwin\\bin\\cygwin1.dll") + "-ia32",
|
|
||||||
"path": "C:\\cygwin\\bin",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "cygwin-" + dllVersion("C:\\cygwin64\\bin\\cygwin1.dll") + "-x64",
|
|
||||||
"path": "C:\\cygwin64\\bin",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
def buildTarget(target):
|
os.chdir(common_ship.topDir)
|
||||||
packageName = "winpty-" + common_ship.winptyVersion + "-" + target["name"]
|
|
||||||
|
|
||||||
|
# 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):
|
if os.path.exists("ship\\packages\\" + packageName):
|
||||||
shutil.rmtree("ship\\packages\\" + packageName)
|
shutil.rmtree("ship\\packages\\" + packageName)
|
||||||
oldPath = os.environ["PATH"]
|
|
||||||
os.environ["PATH"] = target["path"] + ";" + common_ship.defaultPathEnviron
|
print("+ Setting PATH to: {}".format(newPath))
|
||||||
|
with common_ship.ModifyEnv(PATH=newPath):
|
||||||
subprocess.check_call(["sh.exe", "configure"])
|
subprocess.check_call(["sh.exe", "configure"])
|
||||||
makeBinary = target.get("make_binary", "make.exe")
|
subprocess.check_call(["make.exe", "clean"])
|
||||||
subprocess.check_call([makeBinary, "clean"])
|
|
||||||
makeBaseCmd = [
|
makeBaseCmd = [
|
||||||
makeBinary,
|
"make.exe",
|
||||||
"USE_PCH=0",
|
|
||||||
"COMMIT_HASH=" + common_ship.commitHash,
|
"COMMIT_HASH=" + common_ship.commitHash,
|
||||||
"PREFIX=ship/packages/" + packageName
|
"PREFIX=ship/packages/" + packageName
|
||||||
]
|
]
|
||||||
@ -93,16 +83,22 @@ def buildTarget(target):
|
|||||||
subprocess.check_call(["tar.exe", "cvfz",
|
subprocess.check_call(["tar.exe", "cvfz",
|
||||||
packageName + ".tar.gz",
|
packageName + ".tar.gz",
|
||||||
packageName], cwd=os.path.join(os.getcwd(), "ship", "packages"))
|
packageName], cwd=os.path.join(os.getcwd(), "ship", "packages"))
|
||||||
os.environ["PATH"] = oldPath
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
oldPath = os.environ["PATH"]
|
parser = OptionParser()
|
||||||
for t in BUILD_TARGETS:
|
parser.add_option("--kind", type="choice", choices=["cygwin", "msys2"])
|
||||||
os.environ["PATH"] = t["path"] + ";" + common_ship.defaultPathEnviron
|
parser.add_option("--syspath")
|
||||||
subprocess.check_output(["tar.exe", "--help"])
|
parser.add_option("--arch", type="choice", choices=["ia32", "x64"])
|
||||||
subprocess.check_output(["make.exe", "--help"])
|
(args, extra) = parser.parse_args()
|
||||||
for t in BUILD_TARGETS:
|
|
||||||
buildTarget(t)
|
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__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@ -332,6 +333,9 @@ void Agent::handlePacket(ReadBuffer &packet)
|
|||||||
// at once, we can ignore the early ones.
|
// at once, we can ignore the early ones.
|
||||||
handleSetSizePacket(packet);
|
handleSetSizePacket(packet);
|
||||||
break;
|
break;
|
||||||
|
case AgentMsg::GetConsoleProcessList:
|
||||||
|
handleGetConsoleProcessListPacket(packet);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
trace("Unrecognized message, id:%d", type);
|
trace("Unrecognized message, id:%d", type);
|
||||||
}
|
}
|
||||||
@ -426,6 +430,33 @@ void Agent::handleSetSizePacket(ReadBuffer &packet)
|
|||||||
writePacket(reply);
|
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()
|
void Agent::pollConinPipe()
|
||||||
{
|
{
|
||||||
const std::string newData = m_coninPipe->readAllToString();
|
const std::string newData = m_coninPipe->readAllToString();
|
||||||
@ -529,11 +560,31 @@ void Agent::resizeWindow(int cols, int rows)
|
|||||||
Win32Console::FreezeGuard guard(m_console, m_console.frozen());
|
Win32Console::FreezeGuard guard(m_console, m_console.frozen());
|
||||||
const Coord newSize(cols, rows);
|
const Coord newSize(cols, rows);
|
||||||
ConsoleScreenBufferInfo info;
|
ConsoleScreenBufferInfo info;
|
||||||
m_primaryScraper->resizeWindow(*openPrimaryBuffer(), newSize, info);
|
auto primaryBuffer = openPrimaryBuffer();
|
||||||
|
m_primaryScraper->resizeWindow(*primaryBuffer, newSize, info);
|
||||||
m_consoleInput->setMouseWindowRect(info.windowRect());
|
m_consoleInput->setMouseWindowRect(info.windowRect());
|
||||||
if (m_errorScraper) {
|
if (m_errorScraper) {
|
||||||
m_errorScraper->resizeWindow(*m_errorBuffer, newSize, info);
|
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()
|
void Agent::scrapeBuffers()
|
||||||
@ -551,9 +602,11 @@ void Agent::syncConsoleTitle()
|
|||||||
{
|
{
|
||||||
std::wstring newTitle = m_console.title();
|
std::wstring newTitle = m_console.title();
|
||||||
if (newTitle != m_currentTitle) {
|
if (newTitle != m_currentTitle) {
|
||||||
|
if (!m_plainMode && !m_conoutPipe->isClosed()) {
|
||||||
std::string command = std::string("\x1b]0;") +
|
std::string command = std::string("\x1b]0;") +
|
||||||
utf8FromWide(newTitle) + "\x07";
|
utf8FromWide(newTitle) + "\x07";
|
||||||
m_conoutPipe->write(command.c_str());
|
m_conoutPipe->write(command.c_str());
|
||||||
|
}
|
||||||
m_currentTitle = newTitle;
|
m_currentTitle = newTitle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,7 @@ private:
|
|||||||
void writePacket(WriteBuffer &packet);
|
void writePacket(WriteBuffer &packet);
|
||||||
void handleStartProcessPacket(ReadBuffer &packet);
|
void handleStartProcessPacket(ReadBuffer &packet);
|
||||||
void handleSetSizePacket(ReadBuffer &packet);
|
void handleSetSizePacket(ReadBuffer &packet);
|
||||||
|
void handleGetConsoleProcessListPacket(ReadBuffer &packet);
|
||||||
void pollConinPipe();
|
void pollConinPipe();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -113,8 +113,6 @@ struct Font {
|
|||||||
// - misc/Utf16Echo.cc, misc/FontSurvey.cc, misc/SetFont.cc, misc/GetFont.cc
|
// - misc/Utf16Echo.cc, misc/FontSurvey.cc, misc/SetFont.cc, misc/GetFont.cc
|
||||||
|
|
||||||
const FontSize kLucidaFontSizes[] = {
|
const FontSize kLucidaFontSizes[] = {
|
||||||
{ 2, 1 },
|
|
||||||
{ 4, 2 },
|
|
||||||
{ 5, 3 },
|
{ 5, 3 },
|
||||||
{ 6, 4 },
|
{ 6, 4 },
|
||||||
{ 8, 5 },
|
{ 8, 5 },
|
||||||
@ -132,8 +130,6 @@ const FontSize kLucidaFontSizes[] = {
|
|||||||
|
|
||||||
// Japanese. Used on Vista and Windows 7.
|
// Japanese. Used on Vista and Windows 7.
|
||||||
const FontSize k932GothicVista[] = {
|
const FontSize k932GothicVista[] = {
|
||||||
{ 2, 1 },
|
|
||||||
{ 4, 2 },
|
|
||||||
{ 6, 3 },
|
{ 6, 3 },
|
||||||
{ 8, 4 },
|
{ 8, 4 },
|
||||||
{ 10, 5 },
|
{ 10, 5 },
|
||||||
@ -150,7 +146,6 @@ const FontSize k932GothicVista[] = {
|
|||||||
const FontSize k932GothicWin8[] = {
|
const FontSize k932GothicWin8[] = {
|
||||||
// All of these characters are broken w.r.t. full-size East Asian
|
// All of these characters are broken w.r.t. full-size East Asian
|
||||||
// characters, but they're equally broken.
|
// characters, but they're equally broken.
|
||||||
{ 3, 2 },
|
|
||||||
{ 5, 3 },
|
{ 5, 3 },
|
||||||
{ 7, 4 },
|
{ 7, 4 },
|
||||||
{ 9, 5 },
|
{ 9, 5 },
|
||||||
@ -170,8 +165,6 @@ const FontSize k932GothicWin8[] = {
|
|||||||
|
|
||||||
// Japanese. Used on the new Windows 10 console.
|
// Japanese. Used on the new Windows 10 console.
|
||||||
const FontSize k932GothicWin10[] = {
|
const FontSize k932GothicWin10[] = {
|
||||||
{ 2, 1 },
|
|
||||||
{ 4, 2 },
|
|
||||||
{ 6, 3 },
|
{ 6, 3 },
|
||||||
{ 8, 4 },
|
{ 8, 4 },
|
||||||
{ 10, 5 },
|
{ 10, 5 },
|
||||||
@ -191,8 +184,6 @@ const FontSize k932GothicWin10[] = {
|
|||||||
|
|
||||||
// Chinese Simplified.
|
// Chinese Simplified.
|
||||||
const FontSize k936SimSun[] = {
|
const FontSize k936SimSun[] = {
|
||||||
{ 2, 1 },
|
|
||||||
{ 4, 2 },
|
|
||||||
{ 6, 3 },
|
{ 6, 3 },
|
||||||
{ 8, 4 },
|
{ 8, 4 },
|
||||||
{ 10, 5 },
|
{ 10, 5 },
|
||||||
@ -212,8 +203,6 @@ const FontSize k936SimSun[] = {
|
|||||||
|
|
||||||
// Korean.
|
// Korean.
|
||||||
const FontSize k949GulimChe[] = {
|
const FontSize k949GulimChe[] = {
|
||||||
{ 2, 1 },
|
|
||||||
{ 4, 2 },
|
|
||||||
{ 6, 3 },
|
{ 6, 3 },
|
||||||
{ 8, 4 },
|
{ 8, 4 },
|
||||||
{ 10, 5 },
|
{ 10, 5 },
|
||||||
@ -233,8 +222,6 @@ const FontSize k949GulimChe[] = {
|
|||||||
|
|
||||||
// Chinese Traditional.
|
// Chinese Traditional.
|
||||||
const FontSize k950MingLight[] = {
|
const FontSize k950MingLight[] = {
|
||||||
{ 2, 1 },
|
|
||||||
{ 4, 2 },
|
|
||||||
{ 6, 3 },
|
{ 6, 3 },
|
||||||
{ 8, 4 },
|
{ 8, 4 },
|
||||||
{ 10, 5 },
|
{ 10, 5 },
|
||||||
|
@ -343,12 +343,38 @@ void ConsoleInput::doWrite(bool isEof)
|
|||||||
idx += charSize;
|
idx += charSize;
|
||||||
}
|
}
|
||||||
m_byteQueue.erase(0, idx);
|
m_byteQueue.erase(0, idx);
|
||||||
|
flushInputRecords(records);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConsoleInput::flushInputRecords(std::vector<INPUT_RECORD> &records)
|
||||||
|
{
|
||||||
|
if (records.size() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
DWORD actual = 0;
|
DWORD actual = 0;
|
||||||
if (records.size() > 0) {
|
|
||||||
if (!WriteConsoleInputW(m_conin, records.data(), records.size(), &actual)) {
|
if (!WriteConsoleInputW(m_conin, records.data(), records.size(), &actual)) {
|
||||||
trace("WriteConsoleInputW failed");
|
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,
|
int ConsoleInput::scanInput(std::vector<INPUT_RECORD> &records,
|
||||||
@ -359,9 +385,20 @@ int ConsoleInput::scanInput(std::vector<INPUT_RECORD> &records,
|
|||||||
ASSERT(inputSize >= 1);
|
ASSERT(inputSize >= 1);
|
||||||
|
|
||||||
// Ctrl-C.
|
// 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)) {
|
if (input[0] == '\x03' && (inputConsoleMode() & ENABLE_PROCESSED_INPUT)) {
|
||||||
|
flushInputRecords(records);
|
||||||
trace("Ctrl-C");
|
trace("Ctrl-C");
|
||||||
BOOL ret = GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
|
const BOOL ret = GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
|
||||||
trace("GenerateConsoleCtrlEvent: %d", ret);
|
trace("GenerateConsoleCtrlEvent: %d", ret);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -395,10 +432,17 @@ int ConsoleInput::scanInput(std::vector<INPUT_RECORD> &records,
|
|||||||
trace("Incomplete escape sequence");
|
trace("Incomplete escape sequence");
|
||||||
return -1;
|
return -1;
|
||||||
} else if (matchLen > 0) {
|
} else if (matchLen > 0) {
|
||||||
appendKeyPress(records,
|
uint32_t winCodePointDn = match.unicodeChar;
|
||||||
match.virtualKey,
|
if ((match.keyState & LEFT_CTRL_PRESSED) && (match.keyState & LEFT_ALT_PRESSED)) {
|
||||||
match.unicodeChar,
|
winCodePointDn = '\0';
|
||||||
match.keyState);
|
}
|
||||||
|
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;
|
return matchLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,7 +461,7 @@ int ConsoleInput::scanInput(std::vector<INPUT_RECORD> &records,
|
|||||||
trace("Incomplete UTF-8 character in Alt-<Char>");
|
trace("Incomplete UTF-8 character in Alt-<Char>");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
appendUtf8Char(records, &input[1], len, LEFT_ALT_PRESSED);
|
appendUtf8Char(records, &input[1], len, true);
|
||||||
return 1 + len;
|
return 1 + len;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -437,7 +481,7 @@ int ConsoleInput::scanInput(std::vector<INPUT_RECORD> &records,
|
|||||||
trace("Incomplete UTF-8 character");
|
trace("Incomplete UTF-8 character");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
appendUtf8Char(records, &input[0], len, 0);
|
appendUtf8Char(records, &input[0], len, false);
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -557,10 +601,10 @@ int ConsoleInput::scanMouseInput(std::vector<INPUT_RECORD> &records,
|
|||||||
void ConsoleInput::appendUtf8Char(std::vector<INPUT_RECORD> &records,
|
void ConsoleInput::appendUtf8Char(std::vector<INPUT_RECORD> &records,
|
||||||
const char *charBuffer,
|
const char *charBuffer,
|
||||||
const int charLen,
|
const int charLen,
|
||||||
const uint16_t keyState)
|
const bool terminalAltEscape)
|
||||||
{
|
{
|
||||||
const uint32_t code = decodeUtf8(charBuffer);
|
const uint32_t codePoint = decodeUtf8(charBuffer);
|
||||||
if (code == static_cast<uint32_t>(-1)) {
|
if (codePoint == static_cast<uint32_t>(-1)) {
|
||||||
static bool debugInput = isTracingEnabled() && hasDebugFlag("input");
|
static bool debugInput = isTracingEnabled() && hasDebugFlag("input");
|
||||||
if (debugInput) {
|
if (debugInput) {
|
||||||
StringBuilder error(64);
|
StringBuilder error(64);
|
||||||
@ -574,37 +618,65 @@ void ConsoleInput::appendUtf8Char(std::vector<INPUT_RECORD> &records,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const short charScan = code > 0xFFFF ? -1 : VkKeyScan(code);
|
const short charScan = codePoint > 0xFFFF ? -1 : VkKeyScan(codePoint);
|
||||||
uint16_t virtualKey = 0;
|
uint16_t virtualKey = 0;
|
||||||
uint16_t charKeyState = keyState;
|
uint16_t winKeyState = 0;
|
||||||
|
uint32_t winCodePointDn = codePoint;
|
||||||
|
uint32_t winCodePointUp = codePoint;
|
||||||
|
uint16_t vtKeyState = 0;
|
||||||
|
|
||||||
if (charScan != -1) {
|
if (charScan != -1) {
|
||||||
virtualKey = charScan & 0xFF;
|
virtualKey = charScan & 0xFF;
|
||||||
if (charScan & 0x100)
|
if (charScan & 0x100) {
|
||||||
charKeyState |= SHIFT_PRESSED;
|
winKeyState |= SHIFT_PRESSED;
|
||||||
else if (charScan & 0x200)
|
|
||||||
charKeyState |= LEFT_CTRL_PRESSED;
|
|
||||||
else if (charScan & 0x400)
|
|
||||||
charKeyState |= LEFT_ALT_PRESSED;
|
|
||||||
}
|
}
|
||||||
appendKeyPress(records, virtualKey, code, charKeyState);
|
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,
|
void ConsoleInput::appendKeyPress(std::vector<INPUT_RECORD> &records,
|
||||||
uint16_t virtualKey,
|
const uint16_t virtualKey,
|
||||||
uint32_t codePoint,
|
const uint32_t winCodePointDn,
|
||||||
uint16_t keyState)
|
const uint32_t winCodePointUp,
|
||||||
|
const uint16_t winKeyState,
|
||||||
|
const uint32_t vtCodePoint,
|
||||||
|
const uint16_t vtKeyState)
|
||||||
{
|
{
|
||||||
const bool ctrl = (keyState & LEFT_CTRL_PRESSED) != 0;
|
const bool ctrl = (winKeyState & LEFT_CTRL_PRESSED) != 0;
|
||||||
const bool alt = (keyState & LEFT_ALT_PRESSED) != 0;
|
const bool leftAlt = (winKeyState & LEFT_ALT_PRESSED) != 0;
|
||||||
const bool shift = (keyState & SHIFT_PRESSED) != 0;
|
const bool rightAlt = (winKeyState & RIGHT_ALT_PRESSED) != 0;
|
||||||
const bool enhanced = (keyState & ENHANCED_KEY) != 0;
|
const bool shift = (winKeyState & SHIFT_PRESSED) != 0;
|
||||||
|
const bool enhanced = (winKeyState & ENHANCED_KEY) != 0;
|
||||||
bool hasDebugInput = false;
|
bool hasDebugInput = false;
|
||||||
|
|
||||||
if (isTracingEnabled()) {
|
if (isTracingEnabled()) {
|
||||||
static bool debugInput = hasDebugFlag("input");
|
static bool debugInput = hasDebugFlag("input");
|
||||||
if (debugInput) {
|
if (debugInput) {
|
||||||
hasDebugInput = true;
|
hasDebugInput = true;
|
||||||
InputMap::Key key = { virtualKey, codePoint, keyState };
|
InputMap::Key key = { virtualKey, winCodePointDn, winKeyState };
|
||||||
trace("keypress: %s", key.toString().c_str());
|
trace("keypress: %s", key.toString().c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -616,18 +688,13 @@ void ConsoleInput::appendKeyPress(std::vector<INPUT_RECORD> &records,
|
|||||||
virtualKey == VK_RIGHT ||
|
virtualKey == VK_RIGHT ||
|
||||||
virtualKey == VK_HOME ||
|
virtualKey == VK_HOME ||
|
||||||
virtualKey == VK_END) &&
|
virtualKey == VK_END) &&
|
||||||
!ctrl && !alt && !shift) {
|
!ctrl && !leftAlt && !rightAlt && !shift) {
|
||||||
|
flushInputRecords(records);
|
||||||
if (hasDebugInput) {
|
if (hasDebugInput) {
|
||||||
trace("sending keypress to console HWND");
|
trace("sending keypress to console HWND");
|
||||||
}
|
}
|
||||||
uint32_t scanCode = MapVirtualKey(virtualKey, MAPVK_VK_TO_VSC);
|
sendKeyMessage(m_console.hwnd(), true, virtualKey);
|
||||||
if (scanCode > 255) {
|
sendKeyMessage(m_console.hwnd(), false, virtualKey);
|
||||||
scanCode = 0;
|
|
||||||
}
|
|
||||||
SendMessage(m_console.hwnd(), WM_KEYDOWN, virtualKey,
|
|
||||||
(scanCode << 16) | 1u);
|
|
||||||
SendMessage(m_console.hwnd(), WM_KEYUP, virtualKey,
|
|
||||||
(scanCode << 16) | (1u | (1u << 30) | (1u << 31)));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -636,10 +703,14 @@ void ConsoleInput::appendKeyPress(std::vector<INPUT_RECORD> &records,
|
|||||||
stepKeyState |= LEFT_CTRL_PRESSED;
|
stepKeyState |= LEFT_CTRL_PRESSED;
|
||||||
appendInputRecord(records, TRUE, VK_CONTROL, 0, stepKeyState);
|
appendInputRecord(records, TRUE, VK_CONTROL, 0, stepKeyState);
|
||||||
}
|
}
|
||||||
if (alt) {
|
if (leftAlt) {
|
||||||
stepKeyState |= LEFT_ALT_PRESSED;
|
stepKeyState |= LEFT_ALT_PRESSED;
|
||||||
appendInputRecord(records, TRUE, VK_MENU, 0, stepKeyState);
|
appendInputRecord(records, TRUE, VK_MENU, 0, stepKeyState);
|
||||||
}
|
}
|
||||||
|
if (rightAlt) {
|
||||||
|
stepKeyState |= RIGHT_ALT_PRESSED;
|
||||||
|
appendInputRecord(records, TRUE, VK_MENU, 0, stepKeyState | ENHANCED_KEY);
|
||||||
|
}
|
||||||
if (shift) {
|
if (shift) {
|
||||||
stepKeyState |= SHIFT_PRESSED;
|
stepKeyState |= SHIFT_PRESSED;
|
||||||
appendInputRecord(records, TRUE, VK_SHIFT, 0, stepKeyState);
|
appendInputRecord(records, TRUE, VK_SHIFT, 0, stepKeyState);
|
||||||
@ -648,21 +719,11 @@ void ConsoleInput::appendKeyPress(std::vector<INPUT_RECORD> &records,
|
|||||||
stepKeyState |= ENHANCED_KEY;
|
stepKeyState |= ENHANCED_KEY;
|
||||||
}
|
}
|
||||||
if (m_escapeInputEnabled) {
|
if (m_escapeInputEnabled) {
|
||||||
reencodeEscapedKeyPress(records, virtualKey, codePoint, stepKeyState);
|
reencodeEscapedKeyPress(records, virtualKey, vtCodePoint, vtKeyState);
|
||||||
} else {
|
} else {
|
||||||
if (ctrl && alt) {
|
appendCPInputRecords(records, TRUE, virtualKey, winCodePointDn, stepKeyState);
|
||||||
// This behavior seems arbitrary, but it's what I see in the
|
|
||||||
// Windows 7 console.
|
|
||||||
codePoint = 0;
|
|
||||||
}
|
}
|
||||||
appendCPInputRecords(records, TRUE, virtualKey, codePoint, stepKeyState);
|
appendCPInputRecords(records, FALSE, virtualKey, winCodePointUp, stepKeyState);
|
||||||
}
|
|
||||||
if (alt) {
|
|
||||||
// This behavior seems arbitrary, but it's what I see in the Windows 7
|
|
||||||
// console.
|
|
||||||
codePoint = 0;
|
|
||||||
}
|
|
||||||
appendCPInputRecords(records, FALSE, virtualKey, codePoint, stepKeyState);
|
|
||||||
if (enhanced) {
|
if (enhanced) {
|
||||||
stepKeyState &= ~ENHANCED_KEY;
|
stepKeyState &= ~ENHANCED_KEY;
|
||||||
}
|
}
|
||||||
@ -670,7 +731,11 @@ void ConsoleInput::appendKeyPress(std::vector<INPUT_RECORD> &records,
|
|||||||
stepKeyState &= ~SHIFT_PRESSED;
|
stepKeyState &= ~SHIFT_PRESSED;
|
||||||
appendInputRecord(records, FALSE, VK_SHIFT, 0, stepKeyState);
|
appendInputRecord(records, FALSE, VK_SHIFT, 0, stepKeyState);
|
||||||
}
|
}
|
||||||
if (alt) {
|
if (rightAlt) {
|
||||||
|
stepKeyState &= ~RIGHT_ALT_PRESSED;
|
||||||
|
appendInputRecord(records, FALSE, VK_MENU, 0, stepKeyState | ENHANCED_KEY);
|
||||||
|
}
|
||||||
|
if (leftAlt) {
|
||||||
stepKeyState &= ~LEFT_ALT_PRESSED;
|
stepKeyState &= ~LEFT_ALT_PRESSED;
|
||||||
appendInputRecord(records, FALSE, VK_MENU, 0, stepKeyState);
|
appendInputRecord(records, FALSE, VK_MENU, 0, stepKeyState);
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void doWrite(bool isEof);
|
void doWrite(bool isEof);
|
||||||
|
void flushInputRecords(std::vector<INPUT_RECORD> &records);
|
||||||
int scanInput(std::vector<INPUT_RECORD> &records,
|
int scanInput(std::vector<INPUT_RECORD> &records,
|
||||||
const char *input,
|
const char *input,
|
||||||
int inputSize,
|
int inputSize,
|
||||||
@ -58,11 +59,14 @@ private:
|
|||||||
void appendUtf8Char(std::vector<INPUT_RECORD> &records,
|
void appendUtf8Char(std::vector<INPUT_RECORD> &records,
|
||||||
const char *charBuffer,
|
const char *charBuffer,
|
||||||
int charLen,
|
int charLen,
|
||||||
uint16_t keyState);
|
bool terminalAltEscape);
|
||||||
void appendKeyPress(std::vector<INPUT_RECORD> &records,
|
void appendKeyPress(std::vector<INPUT_RECORD> &records,
|
||||||
uint16_t virtualKey,
|
uint16_t virtualKey,
|
||||||
uint32_t codePoint,
|
uint32_t winCodePointDn,
|
||||||
uint16_t keyState);
|
uint32_t winCodePointUp,
|
||||||
|
uint16_t winKeyState,
|
||||||
|
uint32_t vtCodePoint,
|
||||||
|
uint16_t vtKeyState);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static void appendCPInputRecords(std::vector<INPUT_RECORD> &records,
|
static void appendCPInputRecords(std::vector<INPUT_RECORD> &records,
|
||||||
|
@ -60,10 +60,10 @@ void reencodeEscapedKeyPress(
|
|||||||
case VK_F10: escapeCode = { EscapedKey::Numeric, {'2', '1'} }; break;
|
case VK_F10: escapeCode = { EscapedKey::Numeric, {'2', '1'} }; break;
|
||||||
case VK_F11: escapeCode = { EscapedKey::Numeric, {'2', '3'} }; break;
|
case VK_F11: escapeCode = { EscapedKey::Numeric, {'2', '3'} }; break;
|
||||||
case VK_F12: escapeCode = { EscapedKey::Numeric, {'2', '4'} }; break;
|
case VK_F12: escapeCode = { EscapedKey::Numeric, {'2', '4'} }; break;
|
||||||
case VK_HOME: escapeCode = { EscapedKey::Numeric, {'1'} }; break;
|
case VK_HOME: escapeCode = { EscapedKey::Letter, {'H'} }; break;
|
||||||
case VK_INSERT: escapeCode = { EscapedKey::Numeric, {'2'} }; break;
|
case VK_INSERT: escapeCode = { EscapedKey::Numeric, {'2'} }; break;
|
||||||
case VK_DELETE: escapeCode = { EscapedKey::Numeric, {'3'} }; break;
|
case VK_DELETE: escapeCode = { EscapedKey::Numeric, {'3'} }; break;
|
||||||
case VK_END: escapeCode = { EscapedKey::Numeric, {'4'} }; break;
|
case VK_END: escapeCode = { EscapedKey::Letter, {'F'} }; break;
|
||||||
case VK_PRIOR: escapeCode = { EscapedKey::Numeric, {'5'} }; break;
|
case VK_PRIOR: escapeCode = { EscapedKey::Numeric, {'5'} }; break;
|
||||||
case VK_NEXT: escapeCode = { EscapedKey::Numeric, {'6'} }; break;
|
case VK_NEXT: escapeCode = { EscapedKey::Numeric, {'6'} }; break;
|
||||||
}
|
}
|
||||||
|
@ -65,10 +65,11 @@ const int kSuffixBoth = kSuffixCtrl | kSuffixShift;
|
|||||||
|
|
||||||
static const EscapeEncoding escapeLetterEncodings[] = {
|
static const EscapeEncoding escapeLetterEncodings[] = {
|
||||||
// Conventional arrow keys
|
// Conventional arrow keys
|
||||||
{ true, '[', 'A', kBare | kSemiMod, { VK_UP, '\0', 0 } },
|
// kBareMod: Ubuntu /etc/inputrc and IntelliJ/JediTerm use escapes like: ESC [ n ABCD
|
||||||
{ true, '[', 'B', kBare | kSemiMod, { VK_DOWN, '\0', 0 } },
|
{ true, '[', 'A', kBare | kBareMod | kSemiMod, { VK_UP, '\0', 0 } },
|
||||||
{ true, '[', 'C', kBare | kSemiMod, { VK_RIGHT, '\0', 0 } },
|
{ true, '[', 'B', kBare | kBareMod | kSemiMod, { VK_DOWN, '\0', 0 } },
|
||||||
{ true, '[', 'D', kBare | kSemiMod, { VK_LEFT, '\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
|
// 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
|
// Ctrl-Shift-Arrow, but I can only decode to one choice, so I'm just
|
||||||
@ -102,8 +103,9 @@ static const EscapeEncoding escapeLetterEncodings[] = {
|
|||||||
|
|
||||||
// Home/End, letter version
|
// Home/End, letter version
|
||||||
// * gnome-terminal uses `ESC O [HF]`. I never saw it modified.
|
// * gnome-terminal uses `ESC O [HF]`. I never saw it modified.
|
||||||
{ true, '[', 'H', kBare | kSemiMod, { VK_HOME, '\0', 0 } },
|
// kBareMod: IntelliJ/JediTerm uses escapes like: ESC [ n HF
|
||||||
{ true, '[', 'F', kBare | kSemiMod, { VK_END, '\0', 0 } },
|
{ 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', 'H', kBare, { VK_HOME, '\0', 0 } },
|
||||||
{ true, 'O', 'F', kBare, { VK_END, '\0', 0 } },
|
{ true, 'O', 'F', kBare, { VK_END, '\0', 0 } },
|
||||||
|
|
||||||
@ -235,6 +237,7 @@ static void addSimpleEntries(InputMap &inputMap) {
|
|||||||
|
|
||||||
{ "\x7F", { VK_BACK, '\x08', 0, } },
|
{ "\x7F", { VK_BACK, '\x08', 0, } },
|
||||||
{ ESC "\x7F", { VK_BACK, '\x08', LEFT_ALT_PRESSED, } },
|
{ ESC "\x7F", { VK_BACK, '\x08', LEFT_ALT_PRESSED, } },
|
||||||
|
{ "\x03", { 'C', '\x03', LEFT_CTRL_PRESSED, } },
|
||||||
|
|
||||||
// Handle special F1-F5 for TERM=linux and TERM=cygwin.
|
// Handle special F1-F5 for TERM=linux and TERM=cygwin.
|
||||||
{ ESC "[[A", { VK_F1, '\0', 0 } },
|
{ ESC "[[A", { VK_F1, '\0', 0 } },
|
||||||
|
@ -55,7 +55,7 @@ Scraper::Scraper(
|
|||||||
{
|
{
|
||||||
m_consoleBuffer = &buffer;
|
m_consoleBuffer = &buffer;
|
||||||
|
|
||||||
resetConsoleTracking(Terminal::OmitClear, buffer.windowRect());
|
resetConsoleTracking(Terminal::OmitClear, buffer.windowRect().top());
|
||||||
|
|
||||||
m_bufferData.resize(BUFFER_LINE_COUNT);
|
m_bufferData.resize(BUFFER_LINE_COUNT);
|
||||||
|
|
||||||
@ -114,13 +114,13 @@ void Scraper::scrapeBuffer(Win32ConsoleBuffer &buffer,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Scraper::resetConsoleTracking(
|
void Scraper::resetConsoleTracking(
|
||||||
Terminal::SendClearFlag sendClear, const SmallRect &windowRect)
|
Terminal::SendClearFlag sendClear, int64_t scrapedLineCount)
|
||||||
{
|
{
|
||||||
for (ConsoleLine &line : m_bufferData) {
|
for (ConsoleLine &line : m_bufferData) {
|
||||||
line.reset();
|
line.reset();
|
||||||
}
|
}
|
||||||
m_syncRow = -1;
|
m_syncRow = -1;
|
||||||
m_scrapedLineCount = windowRect.top();
|
m_scrapedLineCount = scrapedLineCount;
|
||||||
m_scrolledCount = 0;
|
m_scrolledCount = 0;
|
||||||
m_maxBufferedLine = -1;
|
m_maxBufferedLine = -1;
|
||||||
m_dirtyWindowTop = -1;
|
m_dirtyWindowTop = -1;
|
||||||
@ -205,7 +205,11 @@ void Scraper::resizeImpl(const ConsoleScreenBufferInfo &origInfo)
|
|||||||
const Coord origBufferSize = origInfo.bufferSize();
|
const Coord origBufferSize = origInfo.bufferSize();
|
||||||
const SmallRect origWindowRect = origInfo.windowRect();
|
const SmallRect origWindowRect = origInfo.windowRect();
|
||||||
|
|
||||||
if (!m_directMode) {
|
if (m_directMode) {
|
||||||
|
for (ConsoleLine &line : m_bufferData) {
|
||||||
|
line.reset();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
m_consoleBuffer->clearLines(0, origWindowRect.Top, origInfo);
|
m_consoleBuffer->clearLines(0, origWindowRect.Top, origInfo);
|
||||||
clearBufferLines(0, origWindowRect.Top);
|
clearBufferLines(0, origWindowRect.Top);
|
||||||
if (m_syncRow != -1) {
|
if (m_syncRow != -1) {
|
||||||
@ -343,7 +347,8 @@ void Scraper::syncConsoleContentAndSize(
|
|||||||
const bool newDirectMode = (info.bufferSize().Y != BUFFER_LINE_COUNT);
|
const bool newDirectMode = (info.bufferSize().Y != BUFFER_LINE_COUNT);
|
||||||
if (newDirectMode != m_directMode) {
|
if (newDirectMode != m_directMode) {
|
||||||
trace("Entering %s mode", newDirectMode ? "direct" : "scrolling");
|
trace("Entering %s mode", newDirectMode ? "direct" : "scrolling");
|
||||||
resetConsoleTracking(Terminal::SendClear, info.windowRect());
|
resetConsoleTracking(Terminal::SendClear,
|
||||||
|
newDirectMode ? 0 : info.windowRect().top());
|
||||||
m_directMode = newDirectMode;
|
m_directMode = newDirectMode;
|
||||||
|
|
||||||
// When we switch from direct->scrolling mode, make sure the console is
|
// When we switch from direct->scrolling mode, make sure the console is
|
||||||
@ -355,6 +360,11 @@ void Scraper::syncConsoleContentAndSize(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (m_directMode) {
|
if (m_directMode) {
|
||||||
|
// In direct-mode, resizing the console redraws the terminal, so do it
|
||||||
|
// before scraping.
|
||||||
|
if (forceResize) {
|
||||||
|
resizeImpl(info);
|
||||||
|
}
|
||||||
directScrapeOutput(info, cursorVisible);
|
directScrapeOutput(info, cursorVisible);
|
||||||
} else {
|
} else {
|
||||||
if (!m_console.frozen()) {
|
if (!m_console.frozen()) {
|
||||||
@ -365,16 +375,17 @@ void Scraper::syncConsoleContentAndSize(
|
|||||||
if (m_console.frozen()) {
|
if (m_console.frozen()) {
|
||||||
scrollingScrapeOutput(info, cursorVisible, false);
|
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) {
|
if (forceResize) {
|
||||||
resizeImpl(info);
|
resizeImpl(info);
|
||||||
finalInfoOut = m_consoleBuffer->bufferInfo();
|
|
||||||
} else {
|
|
||||||
finalInfoOut = info;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finalInfoOut = forceResize ? m_consoleBuffer->bufferInfo() : info;
|
||||||
|
}
|
||||||
|
|
||||||
// Try to match Windows' behavior w.r.t. to the LVB attribute flags. In some
|
// 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
|
// situations, Windows ignores the LVB flags on a character cell because of
|
||||||
// backwards compatibility -- apparently some programs set the flags without
|
// backwards compatibility -- apparently some programs set the flags without
|
||||||
@ -421,7 +432,7 @@ WORD Scraper::attributesMask()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Scraper::directScrapeOutput(const ConsoleScreenBufferInfo &info,
|
void Scraper::directScrapeOutput(const ConsoleScreenBufferInfo &info,
|
||||||
bool cursorVisible)
|
bool consoleCursorVisible)
|
||||||
{
|
{
|
||||||
const SmallRect windowRect = info.windowRect();
|
const SmallRect windowRect = info.windowRect();
|
||||||
|
|
||||||
@ -435,40 +446,35 @@ void Scraper::directScrapeOutput(const ConsoleScreenBufferInfo &info,
|
|||||||
const int h = scrapeRect.height();
|
const int h = scrapeRect.height();
|
||||||
|
|
||||||
const Coord cursor = info.cursorPosition();
|
const Coord cursor = info.cursorPosition();
|
||||||
const int cursorColumn = !cursorVisible ? -1 :
|
const bool showTerminalCursor =
|
||||||
constrained(0, cursor.X - scrapeRect.Left, w - 1);
|
consoleCursorVisible && scrapeRect.contains(cursor);
|
||||||
const int cursorLine = !cursorVisible ? -1 :
|
const int cursorColumn = !showTerminalCursor ? -1 : cursor.X - scrapeRect.Left;
|
||||||
constrained(0, cursor.Y - scrapeRect.Top, h - 1);
|
const int cursorLine = !showTerminalCursor ? -1 : cursor.Y - scrapeRect.Top;
|
||||||
if (!cursorVisible) {
|
|
||||||
|
if (!showTerminalCursor) {
|
||||||
m_terminal->hideTerminalCursor();
|
m_terminal->hideTerminalCursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
largeConsoleRead(m_readBuffer, *m_consoleBuffer, scrapeRect, attributesMask());
|
largeConsoleRead(m_readBuffer, *m_consoleBuffer, scrapeRect, attributesMask());
|
||||||
|
|
||||||
bool sawModifiedLine = false;
|
|
||||||
for (int line = 0; line < h; ++line) {
|
for (int line = 0; line < h; ++line) {
|
||||||
const CHAR_INFO *curLine =
|
const CHAR_INFO *const curLine =
|
||||||
m_readBuffer.lineData(scrapeRect.top() + line);
|
m_readBuffer.lineData(scrapeRect.top() + line);
|
||||||
ConsoleLine &bufLine = m_bufferData[line];
|
ConsoleLine &bufLine = m_bufferData[line];
|
||||||
if (sawModifiedLine) {
|
if (bufLine.detectChangeAndSetLine(curLine, w)) {
|
||||||
bufLine.setLine(curLine, w);
|
|
||||||
} else {
|
|
||||||
sawModifiedLine = bufLine.detectChangeAndSetLine(curLine, w);
|
|
||||||
}
|
|
||||||
if (sawModifiedLine) {
|
|
||||||
const int lineCursorColumn =
|
const int lineCursorColumn =
|
||||||
line == cursorLine ? cursorColumn : -1;
|
line == cursorLine ? cursorColumn : -1;
|
||||||
m_terminal->sendLine(line, curLine, w, lineCursorColumn);
|
m_terminal->sendLine(line, curLine, w, lineCursorColumn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cursorVisible) {
|
if (showTerminalCursor) {
|
||||||
m_terminal->showTerminalCursor(cursorColumn, cursorLine);
|
m_terminal->showTerminalCursor(cursorColumn, cursorLine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Scraper::scrollingScrapeOutput(const ConsoleScreenBufferInfo &info,
|
bool Scraper::scrollingScrapeOutput(const ConsoleScreenBufferInfo &info,
|
||||||
bool cursorVisible,
|
bool consoleCursorVisible,
|
||||||
bool tentative)
|
bool tentative)
|
||||||
{
|
{
|
||||||
const Coord cursor = info.cursorPosition();
|
const Coord cursor = info.cursorPosition();
|
||||||
@ -488,7 +494,7 @@ bool Scraper::scrollingScrapeOutput(const ConsoleScreenBufferInfo &info,
|
|||||||
trace("Sync marker has disappeared -- resetting the terminal"
|
trace("Sync marker has disappeared -- resetting the terminal"
|
||||||
" (m_syncCounter=%u)",
|
" (m_syncCounter=%u)",
|
||||||
m_syncCounter);
|
m_syncCounter);
|
||||||
resetConsoleTracking(Terminal::SendClear, windowRect);
|
resetConsoleTracking(Terminal::SendClear, windowRect.top());
|
||||||
} else if (markerRow != m_syncRow) {
|
} else if (markerRow != m_syncRow) {
|
||||||
ASSERT(markerRow < m_syncRow);
|
ASSERT(markerRow < m_syncRow);
|
||||||
m_scrolledCount += (m_syncRow - markerRow);
|
m_scrolledCount += (m_syncRow - markerRow);
|
||||||
@ -531,7 +537,7 @@ bool Scraper::scrollingScrapeOutput(const ConsoleScreenBufferInfo &info,
|
|||||||
trace("Window moved upward -- resetting the terminal"
|
trace("Window moved upward -- resetting the terminal"
|
||||||
" (m_syncCounter=%u)",
|
" (m_syncCounter=%u)",
|
||||||
m_syncCounter);
|
m_syncCounter);
|
||||||
resetConsoleTracking(Terminal::SendClear, windowRect);
|
resetConsoleTracking(Terminal::SendClear, windowRect.top());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_dirtyWindowTop = windowRect.top();
|
m_dirtyWindowTop = windowRect.top();
|
||||||
@ -600,9 +606,12 @@ bool Scraper::scrollingScrapeOutput(const ConsoleScreenBufferInfo &info,
|
|||||||
std::min(m_dirtyLineCount, windowRect.top() + windowRect.height()) +
|
std::min(m_dirtyLineCount, windowRect.top() + windowRect.height()) +
|
||||||
m_scrolledCount;
|
m_scrolledCount;
|
||||||
|
|
||||||
const int64_t cursorLine = !cursorVisible ? -1 : cursor.Y + m_scrolledCount;
|
const bool showTerminalCursor =
|
||||||
const int cursorColumn = !cursorVisible ? -1 : cursor.X;
|
consoleCursorVisible && windowRect.contains(cursor);
|
||||||
if (!cursorVisible) {
|
const int64_t cursorLine = !showTerminalCursor ? -1 : cursor.Y + m_scrolledCount;
|
||||||
|
const int cursorColumn = !showTerminalCursor ? -1 : cursor.X;
|
||||||
|
|
||||||
|
if (!showTerminalCursor) {
|
||||||
m_terminal->hideTerminalCursor();
|
m_terminal->hideTerminalCursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -631,7 +640,7 @@ bool Scraper::scrollingScrapeOutput(const ConsoleScreenBufferInfo &info,
|
|||||||
|
|
||||||
m_scrapedLineCount = windowRect.top() + m_scrolledCount;
|
m_scrapedLineCount = windowRect.top() + m_scrolledCount;
|
||||||
|
|
||||||
if (cursorVisible) {
|
if (showTerminalCursor) {
|
||||||
m_terminal->showTerminalCursor(cursorColumn, cursorLine);
|
m_terminal->showTerminalCursor(cursorColumn, cursorLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void resetConsoleTracking(
|
void resetConsoleTracking(
|
||||||
Terminal::SendClearFlag sendClear, const SmallRect &windowRect);
|
Terminal::SendClearFlag sendClear, int64_t scrapedLineCount);
|
||||||
void markEntireWindowDirty(const SmallRect &windowRect);
|
void markEntireWindowDirty(const SmallRect &windowRect);
|
||||||
void scanForDirtyLines(const SmallRect &windowRect);
|
void scanForDirtyLines(const SmallRect &windowRect);
|
||||||
void clearBufferLines(int firstRow, int count);
|
void clearBufferLines(int firstRow, int count);
|
||||||
@ -73,9 +73,9 @@ private:
|
|||||||
ConsoleScreenBufferInfo &finalInfoOut);
|
ConsoleScreenBufferInfo &finalInfoOut);
|
||||||
WORD attributesMask();
|
WORD attributesMask();
|
||||||
void directScrapeOutput(const ConsoleScreenBufferInfo &info,
|
void directScrapeOutput(const ConsoleScreenBufferInfo &info,
|
||||||
bool cursorVisible);
|
bool consoleCursorVisible);
|
||||||
bool scrollingScrapeOutput(const ConsoleScreenBufferInfo &info,
|
bool scrollingScrapeOutput(const ConsoleScreenBufferInfo &info,
|
||||||
bool cursorVisible,
|
bool consoleCursorVisible,
|
||||||
bool tentative);
|
bool tentative);
|
||||||
void syncMarkerText(CHAR_INFO (&output)[SYNC_MARKER_LEN]);
|
void syncMarkerText(CHAR_INFO (&output)[SYNC_MARKER_LEN]);
|
||||||
int findSyncMarker();
|
int findSyncMarker();
|
||||||
|
@ -76,6 +76,14 @@ struct SmallRect : SMALL_RECT
|
|||||||
other.Bottom <= Bottom;
|
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
|
SmallRect intersected(const SmallRect &other) const
|
||||||
{
|
{
|
||||||
int x1 = std::max(Left, other.Left);
|
int x1 = std::max(Left, other.Left);
|
||||||
|
@ -218,6 +218,11 @@ WINPTY_API BOOL
|
|||||||
winpty_set_size(winpty_t *wp, int cols, int rows,
|
winpty_set_size(winpty_t *wp, int cols, int rows,
|
||||||
winpty_error_ptr_t *err /*OPTIONAL*/);
|
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
|
/* 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
|
* call breaks the connection with the agent, which should then close its
|
||||||
* console, terminating the processes attached to it.
|
* console, terminating the processes attached to it.
|
||||||
|
@ -935,6 +935,33 @@ winpty_set_size(winpty_t *wp, int cols, int rows,
|
|||||||
} API_CATCH(FALSE)
|
} 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) {
|
WINPTY_API void winpty_free(winpty_t *wp) {
|
||||||
// At least in principle, CloseHandle can fail, so this deletion can
|
// 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
|
// fail. It won't throw an exception, but maybe there's an error that
|
||||||
|
@ -26,6 +26,7 @@ struct AgentMsg
|
|||||||
enum Type {
|
enum Type {
|
||||||
StartProcess,
|
StartProcess,
|
||||||
SetSize,
|
SetSize,
|
||||||
|
GetConsoleProcessList,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -34,7 +34,6 @@
|
|||||||
#include <sys/cygwin.h>
|
#include <sys/cygwin.h>
|
||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <wchar.h>
|
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -199,6 +198,126 @@ static void registerResizeSignalHandler()
|
|||||||
sigaction(SIGWINCH, &resizeSigAct, NULL);
|
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
|
// Convert argc/argv into a Win32 command-line following the escaping convention
|
||||||
// documented on MSDN. (e.g. see CommandLineToArgvW documentation)
|
// documented on MSDN. (e.g. see CommandLineToArgvW documentation)
|
||||||
static std::string argvToCommandLine(const std::vector<std::string> &argv)
|
static std::string argvToCommandLine(const std::vector<std::string> &argv)
|
||||||
@ -238,20 +357,6 @@ static std::string argvToCommandLine(const std::vector<std::string> &argv)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The original MSYS lacks wcscpy.
|
|
||||||
static wchar_t *appWcsCpy(wchar_t *dst, const wchar_t *src)
|
|
||||||
{
|
|
||||||
memcpy(dst, src, (wcslen(src) + 1) * sizeof(wchar_t));
|
|
||||||
return dst;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The original MSYS lacks wcscat.
|
|
||||||
static wchar_t *appWscCat(wchar_t *dst, const wchar_t *src)
|
|
||||||
{
|
|
||||||
appWcsCpy(dst + wcslen(dst), src);
|
|
||||||
return dst;
|
|
||||||
}
|
|
||||||
|
|
||||||
static wchar_t *heapMbsToWcs(const char *text)
|
static wchar_t *heapMbsToWcs(const char *text)
|
||||||
{
|
{
|
||||||
// Calling mbstowcs with a NULL first argument seems to be broken on MSYS.
|
// Calling mbstowcs with a NULL first argument seems to be broken on MSYS.
|
||||||
@ -477,12 +582,6 @@ int main(int argc, char *argv[])
|
|||||||
{
|
{
|
||||||
setlocale(LC_ALL, "");
|
setlocale(LC_ALL, "");
|
||||||
|
|
||||||
if (argc >= 3 && !strcmp(argv[1], "--child-exec")) {
|
|
||||||
execvp(argv[2], &argv[2]);
|
|
||||||
perror("error: exec failed");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_mainWakeup = new WakeupFd();
|
g_mainWakeup = new WakeupFd();
|
||||||
|
|
||||||
Arguments args;
|
Arguments args;
|
||||||
@ -532,35 +631,14 @@ int main(int argc, char *argv[])
|
|||||||
HANDLE childHandle = NULL;
|
HANDLE childHandle = NULL;
|
||||||
|
|
||||||
{
|
{
|
||||||
wchar_t selfPath[1024];
|
|
||||||
{
|
|
||||||
selfPath[0] = L'\0';
|
|
||||||
HMODULE selfModule = NULL;
|
|
||||||
BOOL success = GetModuleHandleExW(
|
|
||||||
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
|
|
||||||
(LPCWSTR)(void*)&main, &selfModule);
|
|
||||||
assert(success && "GetModuleHandleExW failed");
|
|
||||||
DWORD modPathLen = GetModuleFileNameW(selfModule, selfPath, 1024);
|
|
||||||
assert(modPathLen > 0 && modPathLen < 1024 && "GetModuleFileNameW failed");
|
|
||||||
FreeLibrary(selfModule);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the child process under the console.
|
// Start the child process under the console.
|
||||||
//args.childArgv[0] = convertPosixPathToWin(args.childArgv[0]);
|
args.childArgv[0] = findProgram(argv[0], args.childArgv[0]);
|
||||||
std::string childCmdLine = argvToCommandLine(args.childArgv);
|
std::string cmdLine = argvToCommandLine(args.childArgv);
|
||||||
wchar_t *const childCmdLineW = heapMbsToWcs(childCmdLine.c_str());
|
wchar_t *cmdLineW = heapMbsToWcs(cmdLine.c_str());
|
||||||
|
|
||||||
wchar_t *const cmdLineW = new wchar_t[wcslen(selfPath) + 32 + wcslen(childCmdLineW)];
|
|
||||||
cmdLineW[0] = L'\0';
|
|
||||||
appWscCat(cmdLineW, L"\"");
|
|
||||||
appWscCat(cmdLineW, selfPath);
|
|
||||||
appWscCat(cmdLineW, L"\" --child-exec ");
|
|
||||||
appWscCat(cmdLineW, childCmdLineW);
|
|
||||||
|
|
||||||
winpty_spawn_config_t *spawnCfg = winpty_spawn_config_new(
|
winpty_spawn_config_t *spawnCfg = winpty_spawn_config_new(
|
||||||
WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN,
|
WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN,
|
||||||
NULL, // heapMbsToWcs(args.childArgv[0].c_str()),
|
NULL, cmdLineW, NULL, NULL, NULL);
|
||||||
cmdLineW, NULL, NULL, NULL);
|
|
||||||
assert(spawnCfg != NULL);
|
assert(spawnCfg != NULL);
|
||||||
|
|
||||||
winpty_error_ptr_t spawnErr = NULL;
|
winpty_error_ptr_t spawnErr = NULL;
|
||||||
@ -572,19 +650,20 @@ int main(int argc, char *argv[])
|
|||||||
if (!spawnRet) {
|
if (!spawnRet) {
|
||||||
winpty_result_t spawnCode = winpty_error_code(spawnErr);
|
winpty_result_t spawnCode = winpty_error_code(spawnErr);
|
||||||
if (spawnCode == WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED) {
|
if (spawnCode == WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED) {
|
||||||
fprintf(stderr, "Could not start '%s': %s\n",
|
fprintf(stderr, "%s: error: cannot start '%s': %s\n",
|
||||||
childCmdLine.c_str(),
|
argv[0],
|
||||||
|
cmdLine.c_str(),
|
||||||
formatErrorMessage(lastError).c_str());
|
formatErrorMessage(lastError).c_str());
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "Could not start '%s': internal error: %s\n",
|
fprintf(stderr, "%s: error: cannot start '%s': internal error: %s\n",
|
||||||
childCmdLine.c_str(),
|
argv[0],
|
||||||
|
cmdLine.c_str(),
|
||||||
wcsToMbs(winpty_error_msg(spawnErr)).c_str());
|
wcsToMbs(winpty_error_msg(spawnErr)).c_str());
|
||||||
}
|
}
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
winpty_error_free(spawnErr);
|
winpty_error_free(spawnErr);
|
||||||
delete [] cmdLineW;
|
delete [] cmdLineW;
|
||||||
delete [] childCmdLineW;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
registerResizeSignalHandler();
|
registerResizeSignalHandler();
|
||||||
|
Loading…
Reference in New Issue
Block a user