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-libpty
|
||||
/ship/packages
|
||||
/ship/tmp
|
||||
/src/Default
|
||||
/src/Release
|
||||
/src/gen
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 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
|
||||
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)
|
||||
|
||||
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 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
|
||||
@ -20,11 +25,11 @@ 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
|
||||
print("+ rm -r " + path)
|
||||
sys.stdout.flush()
|
||||
shutil.rmtree(path)
|
||||
elif os.path.isfile(path):
|
||||
print "+ rm " + path
|
||||
print("+ rm " + path)
|
||||
sys.stdout.flush()
|
||||
os.remove(path)
|
||||
|
||||
@ -37,17 +42,48 @@ def requireExe(name, guesses):
|
||||
for guess in guesses:
|
||||
if os.path.exists(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"]
|
||||
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"]).decode().strip()
|
||||
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", [])
|
||||
|
@ -36,10 +36,6 @@ import subprocess
|
||||
import sys
|
||||
|
||||
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 = {
|
||||
"2015" : {
|
||||
@ -100,14 +96,16 @@ def build(arch, packageDir, xp=False):
|
||||
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 + '" && '
|
||||
'"' + devCmdPath + '" && ' +
|
||||
" vcbuild.bat" +
|
||||
" --gyp-msvs-version " + versionInfo["gyp_version"] +
|
||||
" --msvc-platform " + archInfo["msvc_platform"] +
|
||||
" --commit-hash " + common_ship.commitHash
|
||||
" --commit-hash " + common_ship.commitHash +
|
||||
toolsetArgument
|
||||
)
|
||||
|
||||
subprocess.check_call(commandLine, shell=True, env=newEnv)
|
||||
@ -159,7 +157,7 @@ def buildPackage():
|
||||
shutil.copy(topDir + "/README.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__":
|
||||
buildPackage()
|
||||
|
92
ship/ship.py
92
ship/ship.py
@ -23,20 +23,16 @@
|
||||
#
|
||||
# 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
|
||||
|
||||
from optparse import OptionParser
|
||||
import multiprocessing
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
os.chdir(common_ship.topDir)
|
||||
|
||||
def dllVersion(path):
|
||||
version = subprocess.check_output(
|
||||
@ -44,46 +40,40 @@ def dllVersion(path):
|
||||
"[System.Diagnostics.FileVersionInfo]::GetVersionInfo(\"" + path + "\").FileVersion"])
|
||||
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):
|
||||
packageName = "winpty-" + common_ship.winptyVersion + "-" + target["name"]
|
||||
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)
|
||||
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"])
|
||||
makeBinary = target.get("make_binary", "make.exe")
|
||||
subprocess.check_call([makeBinary, "clean"])
|
||||
subprocess.check_call(["make.exe", "clean"])
|
||||
makeBaseCmd = [
|
||||
makeBinary,
|
||||
"USE_PCH=0",
|
||||
"make.exe",
|
||||
"COMMIT_HASH=" + common_ship.commitHash,
|
||||
"PREFIX=ship/packages/" + packageName
|
||||
]
|
||||
@ -93,16 +83,22 @@ def buildTarget(target):
|
||||
subprocess.check_call(["tar.exe", "cvfz",
|
||||
packageName + ".tar.gz",
|
||||
packageName], cwd=os.path.join(os.getcwd(), "ship", "packages"))
|
||||
os.environ["PATH"] = oldPath
|
||||
|
||||
|
||||
def main():
|
||||
oldPath = os.environ["PATH"]
|
||||
for t in BUILD_TARGETS:
|
||||
os.environ["PATH"] = t["path"] + ";" + common_ship.defaultPathEnviron
|
||||
subprocess.check_output(["tar.exe", "--help"])
|
||||
subprocess.check_output(["make.exe", "--help"])
|
||||
for t in BUILD_TARGETS:
|
||||
buildTarget(t)
|
||||
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()
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@ -332,6 +333,9 @@ void Agent::handlePacket(ReadBuffer &packet)
|
||||
// at once, we can ignore the early ones.
|
||||
handleSetSizePacket(packet);
|
||||
break;
|
||||
case AgentMsg::GetConsoleProcessList:
|
||||
handleGetConsoleProcessListPacket(packet);
|
||||
break;
|
||||
default:
|
||||
trace("Unrecognized message, id:%d", type);
|
||||
}
|
||||
@ -426,6 +430,33 @@ void Agent::handleSetSizePacket(ReadBuffer &packet)
|
||||
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();
|
||||
@ -529,11 +560,31 @@ void Agent::resizeWindow(int cols, int rows)
|
||||
Win32Console::FreezeGuard guard(m_console, m_console.frozen());
|
||||
const Coord newSize(cols, rows);
|
||||
ConsoleScreenBufferInfo info;
|
||||
m_primaryScraper->resizeWindow(*openPrimaryBuffer(), newSize, 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()
|
||||
@ -551,9 +602,11 @@ 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;
|
||||
}
|
||||
}
|
||||
|
@ -59,6 +59,7 @@ private:
|
||||
void writePacket(WriteBuffer &packet);
|
||||
void handleStartProcessPacket(ReadBuffer &packet);
|
||||
void handleSetSizePacket(ReadBuffer &packet);
|
||||
void handleGetConsoleProcessListPacket(ReadBuffer &packet);
|
||||
void pollConinPipe();
|
||||
|
||||
protected:
|
||||
|
@ -113,8 +113,6 @@ struct Font {
|
||||
// - misc/Utf16Echo.cc, misc/FontSurvey.cc, misc/SetFont.cc, misc/GetFont.cc
|
||||
|
||||
const FontSize kLucidaFontSizes[] = {
|
||||
{ 2, 1 },
|
||||
{ 4, 2 },
|
||||
{ 5, 3 },
|
||||
{ 6, 4 },
|
||||
{ 8, 5 },
|
||||
@ -132,8 +130,6 @@ const FontSize kLucidaFontSizes[] = {
|
||||
|
||||
// Japanese. Used on Vista and Windows 7.
|
||||
const FontSize k932GothicVista[] = {
|
||||
{ 2, 1 },
|
||||
{ 4, 2 },
|
||||
{ 6, 3 },
|
||||
{ 8, 4 },
|
||||
{ 10, 5 },
|
||||
@ -150,7 +146,6 @@ const FontSize k932GothicVista[] = {
|
||||
const FontSize k932GothicWin8[] = {
|
||||
// All of these characters are broken w.r.t. full-size East Asian
|
||||
// characters, but they're equally broken.
|
||||
{ 3, 2 },
|
||||
{ 5, 3 },
|
||||
{ 7, 4 },
|
||||
{ 9, 5 },
|
||||
@ -170,8 +165,6 @@ const FontSize k932GothicWin8[] = {
|
||||
|
||||
// Japanese. Used on the new Windows 10 console.
|
||||
const FontSize k932GothicWin10[] = {
|
||||
{ 2, 1 },
|
||||
{ 4, 2 },
|
||||
{ 6, 3 },
|
||||
{ 8, 4 },
|
||||
{ 10, 5 },
|
||||
@ -191,8 +184,6 @@ const FontSize k932GothicWin10[] = {
|
||||
|
||||
// Chinese Simplified.
|
||||
const FontSize k936SimSun[] = {
|
||||
{ 2, 1 },
|
||||
{ 4, 2 },
|
||||
{ 6, 3 },
|
||||
{ 8, 4 },
|
||||
{ 10, 5 },
|
||||
@ -212,8 +203,6 @@ const FontSize k936SimSun[] = {
|
||||
|
||||
// Korean.
|
||||
const FontSize k949GulimChe[] = {
|
||||
{ 2, 1 },
|
||||
{ 4, 2 },
|
||||
{ 6, 3 },
|
||||
{ 8, 4 },
|
||||
{ 10, 5 },
|
||||
@ -233,8 +222,6 @@ const FontSize k949GulimChe[] = {
|
||||
|
||||
// Chinese Traditional.
|
||||
const FontSize k950MingLight[] = {
|
||||
{ 2, 1 },
|
||||
{ 4, 2 },
|
||||
{ 6, 3 },
|
||||
{ 8, 4 },
|
||||
{ 10, 5 },
|
||||
|
@ -343,12 +343,38 @@ void ConsoleInput::doWrite(bool isEof)
|
||||
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 (records.size() > 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,
|
||||
@ -359,9 +385,20 @@ int ConsoleInput::scanInput(std::vector<INPUT_RECORD> &records,
|
||||
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");
|
||||
BOOL ret = GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
|
||||
const BOOL ret = GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
|
||||
trace("GenerateConsoleCtrlEvent: %d", ret);
|
||||
return 1;
|
||||
}
|
||||
@ -395,10 +432,17 @@ int ConsoleInput::scanInput(std::vector<INPUT_RECORD> &records,
|
||||
trace("Incomplete escape sequence");
|
||||
return -1;
|
||||
} else if (matchLen > 0) {
|
||||
appendKeyPress(records,
|
||||
match.virtualKey,
|
||||
match.unicodeChar,
|
||||
match.keyState);
|
||||
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;
|
||||
}
|
||||
|
||||
@ -417,7 +461,7 @@ int ConsoleInput::scanInput(std::vector<INPUT_RECORD> &records,
|
||||
trace("Incomplete UTF-8 character in Alt-<Char>");
|
||||
return -1;
|
||||
}
|
||||
appendUtf8Char(records, &input[1], len, LEFT_ALT_PRESSED);
|
||||
appendUtf8Char(records, &input[1], len, true);
|
||||
return 1 + len;
|
||||
}
|
||||
}
|
||||
@ -437,7 +481,7 @@ int ConsoleInput::scanInput(std::vector<INPUT_RECORD> &records,
|
||||
trace("Incomplete UTF-8 character");
|
||||
return -1;
|
||||
}
|
||||
appendUtf8Char(records, &input[0], len, 0);
|
||||
appendUtf8Char(records, &input[0], len, false);
|
||||
return len;
|
||||
}
|
||||
|
||||
@ -557,10 +601,10 @@ int ConsoleInput::scanMouseInput(std::vector<INPUT_RECORD> &records,
|
||||
void ConsoleInput::appendUtf8Char(std::vector<INPUT_RECORD> &records,
|
||||
const char *charBuffer,
|
||||
const int charLen,
|
||||
const uint16_t keyState)
|
||||
const bool terminalAltEscape)
|
||||
{
|
||||
const uint32_t code = decodeUtf8(charBuffer);
|
||||
if (code == static_cast<uint32_t>(-1)) {
|
||||
const uint32_t codePoint = decodeUtf8(charBuffer);
|
||||
if (codePoint == static_cast<uint32_t>(-1)) {
|
||||
static bool debugInput = isTracingEnabled() && hasDebugFlag("input");
|
||||
if (debugInput) {
|
||||
StringBuilder error(64);
|
||||
@ -574,37 +618,65 @@ void ConsoleInput::appendUtf8Char(std::vector<INPUT_RECORD> &records,
|
||||
return;
|
||||
}
|
||||
|
||||
const short charScan = code > 0xFFFF ? -1 : VkKeyScan(code);
|
||||
const short charScan = codePoint > 0xFFFF ? -1 : VkKeyScan(codePoint);
|
||||
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) {
|
||||
virtualKey = charScan & 0xFF;
|
||||
if (charScan & 0x100)
|
||||
charKeyState |= SHIFT_PRESSED;
|
||||
else if (charScan & 0x200)
|
||||
charKeyState |= LEFT_CTRL_PRESSED;
|
||||
else if (charScan & 0x400)
|
||||
charKeyState |= LEFT_ALT_PRESSED;
|
||||
if (charScan & 0x100) {
|
||||
winKeyState |= SHIFT_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,
|
||||
uint16_t virtualKey,
|
||||
uint32_t codePoint,
|
||||
uint16_t keyState)
|
||||
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 = (keyState & LEFT_CTRL_PRESSED) != 0;
|
||||
const bool alt = (keyState & LEFT_ALT_PRESSED) != 0;
|
||||
const bool shift = (keyState & SHIFT_PRESSED) != 0;
|
||||
const bool enhanced = (keyState & ENHANCED_KEY) != 0;
|
||||
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, codePoint, keyState };
|
||||
InputMap::Key key = { virtualKey, winCodePointDn, winKeyState };
|
||||
trace("keypress: %s", key.toString().c_str());
|
||||
}
|
||||
}
|
||||
@ -616,18 +688,13 @@ void ConsoleInput::appendKeyPress(std::vector<INPUT_RECORD> &records,
|
||||
virtualKey == VK_RIGHT ||
|
||||
virtualKey == VK_HOME ||
|
||||
virtualKey == VK_END) &&
|
||||
!ctrl && !alt && !shift) {
|
||||
!ctrl && !leftAlt && !rightAlt && !shift) {
|
||||
flushInputRecords(records);
|
||||
if (hasDebugInput) {
|
||||
trace("sending keypress to console HWND");
|
||||
}
|
||||
uint32_t scanCode = MapVirtualKey(virtualKey, MAPVK_VK_TO_VSC);
|
||||
if (scanCode > 255) {
|
||||
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)));
|
||||
sendKeyMessage(m_console.hwnd(), true, virtualKey);
|
||||
sendKeyMessage(m_console.hwnd(), false, virtualKey);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -636,10 +703,14 @@ void ConsoleInput::appendKeyPress(std::vector<INPUT_RECORD> &records,
|
||||
stepKeyState |= LEFT_CTRL_PRESSED;
|
||||
appendInputRecord(records, TRUE, VK_CONTROL, 0, stepKeyState);
|
||||
}
|
||||
if (alt) {
|
||||
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);
|
||||
@ -648,21 +719,11 @@ void ConsoleInput::appendKeyPress(std::vector<INPUT_RECORD> &records,
|
||||
stepKeyState |= ENHANCED_KEY;
|
||||
}
|
||||
if (m_escapeInputEnabled) {
|
||||
reencodeEscapedKeyPress(records, virtualKey, codePoint, stepKeyState);
|
||||
reencodeEscapedKeyPress(records, virtualKey, vtCodePoint, vtKeyState);
|
||||
} else {
|
||||
if (ctrl && alt) {
|
||||
// This behavior seems arbitrary, but it's what I see in the
|
||||
// Windows 7 console.
|
||||
codePoint = 0;
|
||||
appendCPInputRecords(records, TRUE, virtualKey, winCodePointDn, stepKeyState);
|
||||
}
|
||||
appendCPInputRecords(records, TRUE, virtualKey, codePoint, 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);
|
||||
appendCPInputRecords(records, FALSE, virtualKey, winCodePointUp, stepKeyState);
|
||||
if (enhanced) {
|
||||
stepKeyState &= ~ENHANCED_KEY;
|
||||
}
|
||||
@ -670,7 +731,11 @@ void ConsoleInput::appendKeyPress(std::vector<INPUT_RECORD> &records,
|
||||
stepKeyState &= ~SHIFT_PRESSED;
|
||||
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;
|
||||
appendInputRecord(records, FALSE, VK_MENU, 0, stepKeyState);
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ public:
|
||||
|
||||
private:
|
||||
void doWrite(bool isEof);
|
||||
void flushInputRecords(std::vector<INPUT_RECORD> &records);
|
||||
int scanInput(std::vector<INPUT_RECORD> &records,
|
||||
const char *input,
|
||||
int inputSize,
|
||||
@ -58,11 +59,14 @@ private:
|
||||
void appendUtf8Char(std::vector<INPUT_RECORD> &records,
|
||||
const char *charBuffer,
|
||||
int charLen,
|
||||
uint16_t keyState);
|
||||
bool terminalAltEscape);
|
||||
void appendKeyPress(std::vector<INPUT_RECORD> &records,
|
||||
uint16_t virtualKey,
|
||||
uint32_t codePoint,
|
||||
uint16_t keyState);
|
||||
uint32_t winCodePointDn,
|
||||
uint32_t winCodePointUp,
|
||||
uint16_t winKeyState,
|
||||
uint32_t vtCodePoint,
|
||||
uint16_t vtKeyState);
|
||||
|
||||
public:
|
||||
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_F11: escapeCode = { EscapedKey::Numeric, {'2', '3'} }; 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_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_NEXT: escapeCode = { EscapedKey::Numeric, {'6'} }; break;
|
||||
}
|
||||
|
@ -65,10 +65,11 @@ const int kSuffixBoth = kSuffixCtrl | kSuffixShift;
|
||||
|
||||
static const EscapeEncoding escapeLetterEncodings[] = {
|
||||
// Conventional arrow keys
|
||||
{ true, '[', 'A', kBare | kSemiMod, { VK_UP, '\0', 0 } },
|
||||
{ true, '[', 'B', kBare | kSemiMod, { VK_DOWN, '\0', 0 } },
|
||||
{ true, '[', 'C', kBare | kSemiMod, { VK_RIGHT, '\0', 0 } },
|
||||
{ true, '[', 'D', kBare | kSemiMod, { VK_LEFT, '\0', 0 } },
|
||||
// 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
|
||||
@ -102,8 +103,9 @@ static const EscapeEncoding escapeLetterEncodings[] = {
|
||||
|
||||
// Home/End, letter version
|
||||
// * gnome-terminal uses `ESC O [HF]`. I never saw it modified.
|
||||
{ true, '[', 'H', kBare | kSemiMod, { VK_HOME, '\0', 0 } },
|
||||
{ true, '[', 'F', kBare | kSemiMod, { VK_END, '\0', 0 } },
|
||||
// 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 } },
|
||||
|
||||
@ -235,6 +237,7 @@ static void addSimpleEntries(InputMap &inputMap) {
|
||||
|
||||
{ "\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 } },
|
||||
|
@ -55,7 +55,7 @@ Scraper::Scraper(
|
||||
{
|
||||
m_consoleBuffer = &buffer;
|
||||
|
||||
resetConsoleTracking(Terminal::OmitClear, buffer.windowRect());
|
||||
resetConsoleTracking(Terminal::OmitClear, buffer.windowRect().top());
|
||||
|
||||
m_bufferData.resize(BUFFER_LINE_COUNT);
|
||||
|
||||
@ -114,13 +114,13 @@ void Scraper::scrapeBuffer(Win32ConsoleBuffer &buffer,
|
||||
}
|
||||
|
||||
void Scraper::resetConsoleTracking(
|
||||
Terminal::SendClearFlag sendClear, const SmallRect &windowRect)
|
||||
Terminal::SendClearFlag sendClear, int64_t scrapedLineCount)
|
||||
{
|
||||
for (ConsoleLine &line : m_bufferData) {
|
||||
line.reset();
|
||||
}
|
||||
m_syncRow = -1;
|
||||
m_scrapedLineCount = windowRect.top();
|
||||
m_scrapedLineCount = scrapedLineCount;
|
||||
m_scrolledCount = 0;
|
||||
m_maxBufferedLine = -1;
|
||||
m_dirtyWindowTop = -1;
|
||||
@ -205,7 +205,11 @@ void Scraper::resizeImpl(const ConsoleScreenBufferInfo &origInfo)
|
||||
const Coord origBufferSize = origInfo.bufferSize();
|
||||
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);
|
||||
clearBufferLines(0, origWindowRect.Top);
|
||||
if (m_syncRow != -1) {
|
||||
@ -343,7 +347,8 @@ void Scraper::syncConsoleContentAndSize(
|
||||
const bool newDirectMode = (info.bufferSize().Y != BUFFER_LINE_COUNT);
|
||||
if (newDirectMode != m_directMode) {
|
||||
trace("Entering %s mode", newDirectMode ? "direct" : "scrolling");
|
||||
resetConsoleTracking(Terminal::SendClear, info.windowRect());
|
||||
resetConsoleTracking(Terminal::SendClear,
|
||||
newDirectMode ? 0 : info.windowRect().top());
|
||||
m_directMode = newDirectMode;
|
||||
|
||||
// When we switch from direct->scrolling mode, make sure the console is
|
||||
@ -355,6 +360,11 @@ void Scraper::syncConsoleContentAndSize(
|
||||
}
|
||||
|
||||
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()) {
|
||||
@ -365,16 +375,17 @@ void Scraper::syncConsoleContentAndSize(
|
||||
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 = 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
|
||||
// situations, Windows ignores the LVB flags on a character cell because of
|
||||
// backwards compatibility -- apparently some programs set the flags without
|
||||
@ -421,7 +432,7 @@ WORD Scraper::attributesMask()
|
||||
}
|
||||
|
||||
void Scraper::directScrapeOutput(const ConsoleScreenBufferInfo &info,
|
||||
bool cursorVisible)
|
||||
bool consoleCursorVisible)
|
||||
{
|
||||
const SmallRect windowRect = info.windowRect();
|
||||
|
||||
@ -435,40 +446,35 @@ void Scraper::directScrapeOutput(const ConsoleScreenBufferInfo &info,
|
||||
const int h = scrapeRect.height();
|
||||
|
||||
const Coord cursor = info.cursorPosition();
|
||||
const int cursorColumn = !cursorVisible ? -1 :
|
||||
constrained(0, cursor.X - scrapeRect.Left, w - 1);
|
||||
const int cursorLine = !cursorVisible ? -1 :
|
||||
constrained(0, cursor.Y - scrapeRect.Top, h - 1);
|
||||
if (!cursorVisible) {
|
||||
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());
|
||||
|
||||
bool sawModifiedLine = false;
|
||||
for (int line = 0; line < h; ++line) {
|
||||
const CHAR_INFO *curLine =
|
||||
const CHAR_INFO *const curLine =
|
||||
m_readBuffer.lineData(scrapeRect.top() + line);
|
||||
ConsoleLine &bufLine = m_bufferData[line];
|
||||
if (sawModifiedLine) {
|
||||
bufLine.setLine(curLine, w);
|
||||
} else {
|
||||
sawModifiedLine = bufLine.detectChangeAndSetLine(curLine, w);
|
||||
}
|
||||
if (sawModifiedLine) {
|
||||
if (bufLine.detectChangeAndSetLine(curLine, w)) {
|
||||
const int lineCursorColumn =
|
||||
line == cursorLine ? cursorColumn : -1;
|
||||
m_terminal->sendLine(line, curLine, w, lineCursorColumn);
|
||||
}
|
||||
}
|
||||
|
||||
if (cursorVisible) {
|
||||
if (showTerminalCursor) {
|
||||
m_terminal->showTerminalCursor(cursorColumn, cursorLine);
|
||||
}
|
||||
}
|
||||
|
||||
bool Scraper::scrollingScrapeOutput(const ConsoleScreenBufferInfo &info,
|
||||
bool cursorVisible,
|
||||
bool consoleCursorVisible,
|
||||
bool tentative)
|
||||
{
|
||||
const Coord cursor = info.cursorPosition();
|
||||
@ -488,7 +494,7 @@ bool Scraper::scrollingScrapeOutput(const ConsoleScreenBufferInfo &info,
|
||||
trace("Sync marker has disappeared -- resetting the terminal"
|
||||
" (m_syncCounter=%u)",
|
||||
m_syncCounter);
|
||||
resetConsoleTracking(Terminal::SendClear, windowRect);
|
||||
resetConsoleTracking(Terminal::SendClear, windowRect.top());
|
||||
} else if (markerRow != m_syncRow) {
|
||||
ASSERT(markerRow < m_syncRow);
|
||||
m_scrolledCount += (m_syncRow - markerRow);
|
||||
@ -531,7 +537,7 @@ bool Scraper::scrollingScrapeOutput(const ConsoleScreenBufferInfo &info,
|
||||
trace("Window moved upward -- resetting the terminal"
|
||||
" (m_syncCounter=%u)",
|
||||
m_syncCounter);
|
||||
resetConsoleTracking(Terminal::SendClear, windowRect);
|
||||
resetConsoleTracking(Terminal::SendClear, windowRect.top());
|
||||
}
|
||||
}
|
||||
m_dirtyWindowTop = windowRect.top();
|
||||
@ -600,9 +606,12 @@ bool Scraper::scrollingScrapeOutput(const ConsoleScreenBufferInfo &info,
|
||||
std::min(m_dirtyLineCount, windowRect.top() + windowRect.height()) +
|
||||
m_scrolledCount;
|
||||
|
||||
const int64_t cursorLine = !cursorVisible ? -1 : cursor.Y + m_scrolledCount;
|
||||
const int cursorColumn = !cursorVisible ? -1 : cursor.X;
|
||||
if (!cursorVisible) {
|
||||
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();
|
||||
}
|
||||
|
||||
@ -631,7 +640,7 @@ bool Scraper::scrollingScrapeOutput(const ConsoleScreenBufferInfo &info,
|
||||
|
||||
m_scrapedLineCount = windowRect.top() + m_scrolledCount;
|
||||
|
||||
if (cursorVisible) {
|
||||
if (showTerminalCursor) {
|
||||
m_terminal->showTerminalCursor(cursorColumn, cursorLine);
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ public:
|
||||
|
||||
private:
|
||||
void resetConsoleTracking(
|
||||
Terminal::SendClearFlag sendClear, const SmallRect &windowRect);
|
||||
Terminal::SendClearFlag sendClear, int64_t scrapedLineCount);
|
||||
void markEntireWindowDirty(const SmallRect &windowRect);
|
||||
void scanForDirtyLines(const SmallRect &windowRect);
|
||||
void clearBufferLines(int firstRow, int count);
|
||||
@ -73,9 +73,9 @@ private:
|
||||
ConsoleScreenBufferInfo &finalInfoOut);
|
||||
WORD attributesMask();
|
||||
void directScrapeOutput(const ConsoleScreenBufferInfo &info,
|
||||
bool cursorVisible);
|
||||
bool consoleCursorVisible);
|
||||
bool scrollingScrapeOutput(const ConsoleScreenBufferInfo &info,
|
||||
bool cursorVisible,
|
||||
bool consoleCursorVisible,
|
||||
bool tentative);
|
||||
void syncMarkerText(CHAR_INFO (&output)[SYNC_MARKER_LEN]);
|
||||
int findSyncMarker();
|
||||
|
@ -76,6 +76,14 @@ struct SmallRect : SMALL_RECT
|
||||
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);
|
||||
|
@ -218,6 +218,11 @@ 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.
|
||||
|
@ -935,6 +935,33 @@ winpty_set_size(winpty_t *wp, int cols, int rows,
|
||||
} 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
|
||||
|
@ -26,6 +26,7 @@ struct AgentMsg
|
||||
enum Type {
|
||||
StartProcess,
|
||||
SetSize,
|
||||
GetConsoleProcessList,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -34,7 +34,6 @@
|
||||
#include <sys/cygwin.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
@ -199,6 +198,126 @@ static void registerResizeSignalHandler()
|
||||
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)
|
||||
@ -238,20 +357,6 @@ static std::string argvToCommandLine(const std::vector<std::string> &argv)
|
||||
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)
|
||||
{
|
||||
// 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, "");
|
||||
|
||||
if (argc >= 3 && !strcmp(argv[1], "--child-exec")) {
|
||||
execvp(argv[2], &argv[2]);
|
||||
perror("error: exec failed");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
g_mainWakeup = new WakeupFd();
|
||||
|
||||
Arguments args;
|
||||
@ -532,35 +631,14 @@ int main(int argc, char *argv[])
|
||||
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.
|
||||
//args.childArgv[0] = convertPosixPathToWin(args.childArgv[0]);
|
||||
std::string childCmdLine = argvToCommandLine(args.childArgv);
|
||||
wchar_t *const childCmdLineW = heapMbsToWcs(childCmdLine.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);
|
||||
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, // heapMbsToWcs(args.childArgv[0].c_str()),
|
||||
cmdLineW, NULL, NULL, NULL);
|
||||
NULL, cmdLineW, NULL, NULL, NULL);
|
||||
assert(spawnCfg != NULL);
|
||||
|
||||
winpty_error_ptr_t spawnErr = NULL;
|
||||
@ -572,19 +650,20 @@ int main(int argc, char *argv[])
|
||||
if (!spawnRet) {
|
||||
winpty_result_t spawnCode = winpty_error_code(spawnErr);
|
||||
if (spawnCode == WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED) {
|
||||
fprintf(stderr, "Could not start '%s': %s\n",
|
||||
childCmdLine.c_str(),
|
||||
fprintf(stderr, "%s: error: cannot start '%s': %s\n",
|
||||
argv[0],
|
||||
cmdLine.c_str(),
|
||||
formatErrorMessage(lastError).c_str());
|
||||
} else {
|
||||
fprintf(stderr, "Could not start '%s': internal error: %s\n",
|
||||
childCmdLine.c_str(),
|
||||
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;
|
||||
delete [] childCmdLineW;
|
||||
}
|
||||
|
||||
registerResizeSignalHandler();
|
||||
|
Loading…
Reference in New Issue
Block a user