Compare commits

..

1 Commits

Author SHA1 Message Date
Ryan Prichard
f653e163f2 Prototype a change where winpty reuses Cygwin fork/exec to spawn a child
I didn't see a noticeable slowdown in winpty invocation times.

This change *did* break IronPython's help() command, as expected, because
when winpty reinvokes itself, it sets TERM to cygwin.  I think the adapter
could unset TERM, but then it wouldn't be set if winpty were used to
invoke a Cygwin program.  (https://github.com/rprichard/winpty/issues/23)
2017-02-01 17:58:43 -06:00
25 changed files with 256 additions and 873 deletions

17
.drone.yml Normal file
View File

@ -0,0 +1,17 @@
# 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
View File

@ -10,7 +10,6 @@ winpty.opensdf
/build-gyp
/build-libpty
/ship/packages
/ship/tmp
/src/Default
/src/Release
/src/gen

View File

@ -1,6 +1,6 @@
# winpty
[![Build Status](https://ci.appveyor.com/api/projects/status/69tb9gylsph1ee1x/branch/master?svg=true)](https://ci.appveyor.com/project/rprichard/winpty/branch/master)
[![Build Status](https://tea-ci.org/api/badges/rprichard/winpty/status.svg)](https://tea-ci.org/rprichard/winpty)
winpty is a Windows software package providing an interface similar to a Unix
pty-master for communicating with Windows console programs. The package

View File

@ -1,57 +1,3 @@
# 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):

View File

@ -1 +1 @@
0.4.4-dev
0.4.3-dev

View File

@ -1,16 +0,0 @@
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

View File

@ -1,90 +0,0 @@
#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;
}

View File

@ -1,201 +0,0 @@
/*
Test program demonstrating a problem in Windows 15048's ReadConsoleOutput API.
To compile:
cl /nologo /EHsc winbug-15048.cc shell32.lib
Example of regressed input:
Case 1:
> chcp 932
> winbug-15048 -face-gothic 3044
Correct output:
1**34 (nb: U+3044 replaced with '**' to avoid MSVC encoding warning)
5678
ReadConsoleOutputW (both rows, 3 cols)
row 0: U+0031(0007) U+3044(0107) U+3044(0207) U+0033(0007)
row 1: U+0035(0007) U+0036(0007) U+0037(0007) U+0038(0007)
ReadConsoleOutputW (both rows, 4 cols)
row 0: U+0031(0007) U+3044(0107) U+3044(0207) U+0033(0007) U+0034(0007)
row 1: U+0035(0007) U+0036(0007) U+0037(0007) U+0038(0007) U+0020(0007)
ReadConsoleOutputW (second row)
row 1: U+0035(0007) U+0036(0007) U+0037(0007) U+0038(0007) U+0020(0007)
...
Win10 15048 bad output:
1**34
5678
ReadConsoleOutputW (both rows, 3 cols)
row 0: U+0031(0007) U+3044(0007) U+0033(0007) U+0035(0007)
row 1: U+0036(0007) U+0037(0007) U+0038(0007) U+0000(0000)
ReadConsoleOutputW (both rows, 4 cols)
row 0: U+0031(0007) U+3044(0007) U+0033(0007) U+0034(0007) U+0035(0007)
row 1: U+0036(0007) U+0037(0007) U+0038(0007) U+0020(0007) U+0000(0000)
ReadConsoleOutputW (second row)
row 1: U+0035(0007) U+0036(0007) U+0037(0007) U+0038(0007) U+0020(0007)
...
The U+3044 character (HIRAGANA LETTER I) occupies two columns, but it only
fills one record in the ReadConsoleOutput output buffer, which has the
effect of shifting the first cell of the second row into the last cell of
the first row. Ordinarily, the first and second cells would also have the
COMMON_LVB_LEADING_BYTE and COMMON_LVB_TRAILING_BYTE attributes set, which
allows winpty to detect the double-column character.
Case 2:
> chcp 437
> winbug-15048 -face "Lucida Console" -h 4 221A
The same issue happens with U+221A (SQUARE ROOT), but only in certain
fonts. The console seems to think this character occupies two columns
if the font is sufficiently small. The Windows console properties dialog
doesn't allow fonts below 5 pt, but winpty tries to use 2pt and 4pt Lucida
Console to allow very large console windows.
Case 3:
> chcp 437
> winbug-15048 -face "Lucida Console" -h 12 FF12
The console selection system thinks U+FF12 (FULLWIDTH DIGIT TWO) occupies
two columns, which happens to be correct, but it's displayed as a single
column unrecognized character. It otherwise behaves the same as the other
cases.
*/
#include <windows.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <wchar.h>
#include <string>
#define COUNT_OF(array) (sizeof(array) / sizeof((array)[0]))
// See https://en.wikipedia.org/wiki/List_of_CJK_fonts
const wchar_t kMSGothic[] = { 0xff2d, 0xff33, 0x0020, 0x30b4, 0x30b7, 0x30c3, 0x30af, 0 }; // Japanese
const wchar_t kNSimSun[] = { 0x65b0, 0x5b8b, 0x4f53, 0 }; // Simplified Chinese
const wchar_t kMingLight[] = { 0x7d30, 0x660e, 0x9ad4, 0 }; // Traditional Chinese
const wchar_t kGulimChe[] = { 0xad74, 0xb9bc, 0xccb4, 0 }; // Korean
static void set_font(const wchar_t *name, int size) {
const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_FONT_INFOEX fontex {};
fontex.cbSize = sizeof(fontex);
fontex.dwFontSize.Y = size;
fontex.FontWeight = 400;
fontex.FontFamily = 0x36;
wcsncpy(fontex.FaceName, name, COUNT_OF(fontex.FaceName));
assert(SetCurrentConsoleFontEx(conout, FALSE, &fontex));
}
static void usage(const wchar_t *prog) {
printf("Usage: %ls [options]\n", prog);
printf(" -h HEIGHT\n");
printf(" -face FACENAME\n");
printf(" -face-{gothic|simsun|minglight|gulimche) [JP,CN-sim,CN-tra,KR]\n");
printf(" hhhh -- print U+hhhh\n");
exit(1);
}
static void dump_region(SMALL_RECT region, const char *name) {
const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE);
CHAR_INFO buf[1000];
memset(buf, 0xcc, sizeof(buf));
const int w = region.Right - region.Left + 1;
const int h = region.Bottom - region.Top + 1;
assert(ReadConsoleOutputW(
conout, buf, { (short)w, (short)h }, { 0, 0 },
&region));
printf("\n");
printf("ReadConsoleOutputW (%s)\n", name);
for (int y = 0; y < h; ++y) {
printf("row %d: ", region.Top + y);
for (int i = 0; i < region.Left * 13; ++i) {
printf(" ");
}
for (int x = 0; x < w; ++x) {
const int i = y * w + x;
printf("U+%04x(%04x) ", buf[i].Char.UnicodeChar, buf[i].Attributes);
}
printf("\n");
}
}
int main() {
wchar_t *cmdline = GetCommandLineW();
int argc = 0;
wchar_t **argv = CommandLineToArgvW(cmdline, &argc);
const wchar_t *font_name = L"Lucida Console";
int font_height = 8;
int test_ch = 0xff12; // U+FF12 FULLWIDTH DIGIT TWO
for (int i = 1; i < argc; ++i) {
const std::wstring arg = argv[i];
const std::wstring next = i + 1 < argc ? argv[i + 1] : L"";
if (arg == L"-face" && i + 1 < argc) {
font_name = argv[i + 1];
i++;
} else if (arg == L"-face-gothic") {
font_name = kMSGothic;
} else if (arg == L"-face-simsun") {
font_name = kNSimSun;
} else if (arg == L"-face-minglight") {
font_name = kMingLight;
} else if (arg == L"-face-gulimche") {
font_name = kGulimChe;
} else if (arg == L"-h" && i + 1 < argc) {
font_height = _wtoi(next.c_str());
i++;
} else if (arg.c_str()[0] != '-') {
test_ch = wcstol(arg.c_str(), NULL, 16);
} else {
printf("Unrecognized argument: %ls\n", arg.c_str());
usage(argv[0]);
}
}
const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE);
set_font(font_name, font_height);
system("cls");
DWORD actual = 0;
wchar_t output[] = L"1234\n5678\n";
output[1] = test_ch;
WriteConsoleW(conout, output, 10, &actual, nullptr);
dump_region({ 0, 0, 3, 1 }, "both rows, 3 cols");
dump_region({ 0, 0, 4, 1 }, "both rows, 4 cols");
dump_region({ 0, 1, 4, 1 }, "second row");
dump_region({ 0, 0, 4, 0 }, "first row");
dump_region({ 1, 0, 4, 0 }, "first row, skip 1");
dump_region({ 2, 0, 4, 0 }, "first row, skip 2");
dump_region({ 3, 0, 4, 0 }, "first row, skip 3");
set_font(font_name, 14);
return 0;
}

View File

@ -1,17 +1,12 @@
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
@ -25,11 +20,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)
@ -42,48 +37,17 @@ 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"]).strip()
commitHash = subprocess.check_output(["git.exe", "rev-parse", "HEAD"]).decode().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", [])

View File

@ -36,6 +36,10 @@ 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" : {
@ -96,16 +100,14 @@ 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 +
toolsetArgument
" --commit-hash " + common_ship.commitHash
)
subprocess.check_call(commandLine, shell=True, env=newEnv)
@ -157,7 +159,7 @@ def buildPackage():
shutil.copy(topDir + "/README.md", packageDir)
shutil.copy(topDir + "/RELEASES.md", packageDir)
subprocess.check_call([common_ship.ZIP_TOOL, "a", packageFile, "."], cwd=packageDir)
subprocess.check_call([ZIP_TOOL, "a", packageFile, "."], cwd=packageDir)
if __name__ == "__main__":
buildPackage()

View File

@ -23,16 +23,20 @@
#
# 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(
@ -40,65 +44,65 @@ def dllVersion(path):
"[System.Diagnostics.FileVersionInfo]::GetVersionInfo(\"" + path + "\").FileVersion"])
return version.strip()
os.chdir(common_ship.topDir)
# Determine other build parameters.
BUILD_KINDS = {
"cygwin": {
"path": ["bin"],
"dll": "bin\\cygwin1.dll",
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",
},
"msys2": {
"path": ["opt\\bin", "usr\\bin"],
"dll": "usr\\bin\\msys-2.0.dll",
{
"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(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)
def buildTarget(target):
packageName = "winpty-" + common_ship.winptyVersion + "-" + target["name"]
if os.path.exists("ship\\packages\\" + packageName):
shutil.rmtree("ship\\packages\\" + packageName)
print("+ Setting PATH to: {}".format(newPath))
with common_ship.ModifyEnv(PATH=newPath):
subprocess.check_call(["sh.exe", "configure"])
subprocess.check_call(["make.exe", "clean"])
makeBaseCmd = [
"make.exe",
"COMMIT_HASH=" + common_ship.commitHash,
"PREFIX=ship/packages/" + packageName
]
subprocess.check_call(makeBaseCmd + ["all", "tests", "-j%d" % multiprocessing.cpu_count()])
subprocess.check_call(["build\\trivial_test.exe"])
subprocess.check_call(makeBaseCmd + ["install"])
subprocess.check_call(["tar.exe", "cvfz",
packageName + ".tar.gz",
packageName], cwd=os.path.join(os.getcwd(), "ship", "packages"))
oldPath = os.environ["PATH"]
os.environ["PATH"] = target["path"] + ";" + common_ship.defaultPathEnviron
subprocess.check_call(["sh.exe", "configure"])
makeBinary = target.get("make_binary", "make.exe")
subprocess.check_call([makeBinary, "clean"])
makeBaseCmd = [
makeBinary,
"USE_PCH=0",
"COMMIT_HASH=" + common_ship.commitHash,
"PREFIX=ship/packages/" + packageName
]
subprocess.check_call(makeBaseCmd + ["all", "tests", "-j%d" % multiprocessing.cpu_count()])
subprocess.check_call(["build\\trivial_test.exe"])
subprocess.check_call(makeBaseCmd + ["install"])
subprocess.check_call(["tar.exe", "cvfz",
packageName + ".tar.gz",
packageName], cwd=os.path.join(os.getcwd(), "ship", "packages"))
os.environ["PATH"] = oldPath
def main():
parser = OptionParser()
parser.add_option("--kind", type="choice", choices=["cygwin", "msys2"])
parser.add_option("--syspath")
parser.add_option("--arch", type="choice", choices=["ia32", "x64"])
(args, extra) = parser.parse_args()
args.kind or parser.error("--kind must be specified")
args.arch or parser.error("--arch must be specified")
args.syspath or parser.error("--syspath must be specified")
extra and parser.error("unexpected positional argument(s)")
buildTarget(args.kind, args.syspath, args.arch)
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)
if __name__ == "__main__":
main()

View File

@ -27,7 +27,6 @@
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
@ -333,9 +332,6 @@ 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);
}
@ -430,33 +426,6 @@ 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();
@ -560,31 +529,11 @@ void Agent::resizeWindow(int cols, int rows)
Win32Console::FreezeGuard guard(m_console, m_console.frozen());
const Coord newSize(cols, rows);
ConsoleScreenBufferInfo info;
auto primaryBuffer = openPrimaryBuffer();
m_primaryScraper->resizeWindow(*primaryBuffer, newSize, info);
m_primaryScraper->resizeWindow(*openPrimaryBuffer(), 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()
@ -602,11 +551,9 @@ 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());
}
std::string command = std::string("\x1b]0;") +
utf8FromWide(newTitle) + "\x07";
m_conoutPipe->write(command.c_str());
m_currentTitle = newTitle;
}
}

View File

@ -59,7 +59,6 @@ private:
void writePacket(WriteBuffer &packet);
void handleStartProcessPacket(ReadBuffer &packet);
void handleSetSizePacket(ReadBuffer &packet);
void handleGetConsoleProcessListPacket(ReadBuffer &packet);
void pollConinPipe();
protected:

View File

@ -113,6 +113,8 @@ 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 },
@ -130,6 +132,8 @@ const FontSize kLucidaFontSizes[] = {
// Japanese. Used on Vista and Windows 7.
const FontSize k932GothicVista[] = {
{ 2, 1 },
{ 4, 2 },
{ 6, 3 },
{ 8, 4 },
{ 10, 5 },
@ -146,6 +150,7 @@ 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 },
@ -165,6 +170,8 @@ const FontSize k932GothicWin8[] = {
// Japanese. Used on the new Windows 10 console.
const FontSize k932GothicWin10[] = {
{ 2, 1 },
{ 4, 2 },
{ 6, 3 },
{ 8, 4 },
{ 10, 5 },
@ -184,6 +191,8 @@ const FontSize k932GothicWin10[] = {
// Chinese Simplified.
const FontSize k936SimSun[] = {
{ 2, 1 },
{ 4, 2 },
{ 6, 3 },
{ 8, 4 },
{ 10, 5 },
@ -203,6 +212,8 @@ const FontSize k936SimSun[] = {
// Korean.
const FontSize k949GulimChe[] = {
{ 2, 1 },
{ 4, 2 },
{ 6, 3 },
{ 8, 4 },
{ 10, 5 },
@ -222,6 +233,8 @@ const FontSize k949GulimChe[] = {
// Chinese Traditional.
const FontSize k950MingLight[] = {
{ 2, 1 },
{ 4, 2 },
{ 6, 3 },
{ 8, 4 },
{ 10, 5 },

View File

@ -343,38 +343,12 @@ 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 (!WriteConsoleInputW(m_conin, records.data(), records.size(), &actual)) {
trace("WriteConsoleInputW failed");
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,
@ -385,20 +359,9 @@ 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");
const BOOL ret = GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
BOOL ret = GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
trace("GenerateConsoleCtrlEvent: %d", ret);
return 1;
}
@ -432,17 +395,10 @@ int ConsoleInput::scanInput(std::vector<INPUT_RECORD> &records,
trace("Incomplete escape sequence");
return -1;
} else if (matchLen > 0) {
uint32_t winCodePointDn = match.unicodeChar;
if ((match.keyState & LEFT_CTRL_PRESSED) && (match.keyState & LEFT_ALT_PRESSED)) {
winCodePointDn = '\0';
}
uint32_t winCodePointUp = winCodePointDn;
if (match.keyState & LEFT_ALT_PRESSED) {
winCodePointUp = '\0';
}
appendKeyPress(records, match.virtualKey,
winCodePointDn, winCodePointUp, match.keyState,
match.unicodeChar, match.keyState);
appendKeyPress(records,
match.virtualKey,
match.unicodeChar,
match.keyState);
return matchLen;
}
@ -461,7 +417,7 @@ int ConsoleInput::scanInput(std::vector<INPUT_RECORD> &records,
trace("Incomplete UTF-8 character in Alt-<Char>");
return -1;
}
appendUtf8Char(records, &input[1], len, true);
appendUtf8Char(records, &input[1], len, LEFT_ALT_PRESSED);
return 1 + len;
}
}
@ -481,7 +437,7 @@ int ConsoleInput::scanInput(std::vector<INPUT_RECORD> &records,
trace("Incomplete UTF-8 character");
return -1;
}
appendUtf8Char(records, &input[0], len, false);
appendUtf8Char(records, &input[0], len, 0);
return len;
}
@ -601,10 +557,10 @@ int ConsoleInput::scanMouseInput(std::vector<INPUT_RECORD> &records,
void ConsoleInput::appendUtf8Char(std::vector<INPUT_RECORD> &records,
const char *charBuffer,
const int charLen,
const bool terminalAltEscape)
const uint16_t keyState)
{
const uint32_t codePoint = decodeUtf8(charBuffer);
if (codePoint == static_cast<uint32_t>(-1)) {
const uint32_t code = decodeUtf8(charBuffer);
if (code == static_cast<uint32_t>(-1)) {
static bool debugInput = isTracingEnabled() && hasDebugFlag("input");
if (debugInput) {
StringBuilder error(64);
@ -618,65 +574,37 @@ void ConsoleInput::appendUtf8Char(std::vector<INPUT_RECORD> &records,
return;
}
const short charScan = codePoint > 0xFFFF ? -1 : VkKeyScan(codePoint);
const short charScan = code > 0xFFFF ? -1 : VkKeyScan(code);
uint16_t virtualKey = 0;
uint16_t winKeyState = 0;
uint32_t winCodePointDn = codePoint;
uint32_t winCodePointUp = codePoint;
uint16_t vtKeyState = 0;
uint16_t charKeyState = keyState;
if (charScan != -1) {
virtualKey = charScan & 0xFF;
if (charScan & 0x100) {
winKeyState |= SHIFT_PRESSED;
}
if (charScan & 0x200) {
winKeyState |= LEFT_CTRL_PRESSED;
}
if (charScan & 0x400) {
winKeyState |= RIGHT_ALT_PRESSED;
}
if (terminalAltEscape && (winKeyState & LEFT_CTRL_PRESSED)) {
// If the terminal escapes a Ctrl-<Key> with Alt, then set the
// codepoint to 0. On the other hand, if a character requires
// AltGr (like U+00B2 on a German layout), then VkKeyScan will
// report both Ctrl and Alt pressed, and we should keep the
// codepoint. See https://github.com/rprichard/winpty/issues/109.
winCodePointDn = 0;
winCodePointUp = 0;
}
if (charScan & 0x100)
charKeyState |= SHIFT_PRESSED;
else if (charScan & 0x200)
charKeyState |= LEFT_CTRL_PRESSED;
else if (charScan & 0x400)
charKeyState |= LEFT_ALT_PRESSED;
}
if (terminalAltEscape) {
winCodePointUp = 0;
winKeyState |= LEFT_ALT_PRESSED;
vtKeyState |= LEFT_ALT_PRESSED;
}
appendKeyPress(records, virtualKey,
winCodePointDn, winCodePointUp, winKeyState,
codePoint, vtKeyState);
appendKeyPress(records, virtualKey, code, charKeyState);
}
void ConsoleInput::appendKeyPress(std::vector<INPUT_RECORD> &records,
const uint16_t virtualKey,
const uint32_t winCodePointDn,
const uint32_t winCodePointUp,
const uint16_t winKeyState,
const uint32_t vtCodePoint,
const uint16_t vtKeyState)
uint16_t virtualKey,
uint32_t codePoint,
uint16_t keyState)
{
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;
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;
bool hasDebugInput = false;
if (isTracingEnabled()) {
static bool debugInput = hasDebugFlag("input");
if (debugInput) {
hasDebugInput = true;
InputMap::Key key = { virtualKey, winCodePointDn, winKeyState };
InputMap::Key key = { virtualKey, codePoint, keyState };
trace("keypress: %s", key.toString().c_str());
}
}
@ -688,13 +616,18 @@ void ConsoleInput::appendKeyPress(std::vector<INPUT_RECORD> &records,
virtualKey == VK_RIGHT ||
virtualKey == VK_HOME ||
virtualKey == VK_END) &&
!ctrl && !leftAlt && !rightAlt && !shift) {
flushInputRecords(records);
!ctrl && !alt && !shift) {
if (hasDebugInput) {
trace("sending keypress to console HWND");
}
sendKeyMessage(m_console.hwnd(), true, virtualKey);
sendKeyMessage(m_console.hwnd(), false, virtualKey);
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)));
return;
}
@ -703,14 +636,10 @@ void ConsoleInput::appendKeyPress(std::vector<INPUT_RECORD> &records,
stepKeyState |= LEFT_CTRL_PRESSED;
appendInputRecord(records, TRUE, VK_CONTROL, 0, stepKeyState);
}
if (leftAlt) {
if (alt) {
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);
@ -719,11 +648,21 @@ void ConsoleInput::appendKeyPress(std::vector<INPUT_RECORD> &records,
stepKeyState |= ENHANCED_KEY;
}
if (m_escapeInputEnabled) {
reencodeEscapedKeyPress(records, virtualKey, vtCodePoint, vtKeyState);
reencodeEscapedKeyPress(records, virtualKey, codePoint, stepKeyState);
} else {
appendCPInputRecords(records, TRUE, virtualKey, winCodePointDn, stepKeyState);
if (ctrl && alt) {
// 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) {
stepKeyState &= ~ENHANCED_KEY;
}
@ -731,11 +670,7 @@ void ConsoleInput::appendKeyPress(std::vector<INPUT_RECORD> &records,
stepKeyState &= ~SHIFT_PRESSED;
appendInputRecord(records, FALSE, VK_SHIFT, 0, stepKeyState);
}
if (rightAlt) {
stepKeyState &= ~RIGHT_ALT_PRESSED;
appendInputRecord(records, FALSE, VK_MENU, 0, stepKeyState | ENHANCED_KEY);
}
if (leftAlt) {
if (alt) {
stepKeyState &= ~LEFT_ALT_PRESSED;
appendInputRecord(records, FALSE, VK_MENU, 0, stepKeyState);
}

View File

@ -48,7 +48,6 @@ 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,
@ -59,14 +58,11 @@ private:
void appendUtf8Char(std::vector<INPUT_RECORD> &records,
const char *charBuffer,
int charLen,
bool terminalAltEscape);
uint16_t keyState);
void appendKeyPress(std::vector<INPUT_RECORD> &records,
uint16_t virtualKey,
uint32_t winCodePointDn,
uint32_t winCodePointUp,
uint16_t winKeyState,
uint32_t vtCodePoint,
uint16_t vtKeyState);
uint32_t codePoint,
uint16_t keyState);
public:
static void appendCPInputRecords(std::vector<INPUT_RECORD> &records,

View File

@ -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::Letter, {'H'} }; break;
case VK_HOME: escapeCode = { EscapedKey::Numeric, {'1'} }; break;
case VK_INSERT: escapeCode = { EscapedKey::Numeric, {'2'} }; break;
case VK_DELETE: escapeCode = { EscapedKey::Numeric, {'3'} }; break;
case VK_END: escapeCode = { EscapedKey::Letter, {'F'} }; break;
case VK_END: escapeCode = { EscapedKey::Numeric, {'4'} }; break;
case VK_PRIOR: escapeCode = { EscapedKey::Numeric, {'5'} }; break;
case VK_NEXT: escapeCode = { EscapedKey::Numeric, {'6'} }; break;
}

View File

@ -65,11 +65,10 @@ const int kSuffixBoth = kSuffixCtrl | kSuffixShift;
static const EscapeEncoding escapeLetterEncodings[] = {
// Conventional arrow keys
// kBareMod: Ubuntu /etc/inputrc and IntelliJ/JediTerm use escapes like: ESC [ n ABCD
{ true, '[', 'A', kBare | kBareMod | kSemiMod, { VK_UP, '\0', 0 } },
{ true, '[', 'B', kBare | kBareMod | kSemiMod, { VK_DOWN, '\0', 0 } },
{ true, '[', 'C', kBare | kBareMod | kSemiMod, { VK_RIGHT, '\0', 0 } },
{ true, '[', 'D', kBare | kBareMod | kSemiMod, { VK_LEFT, '\0', 0 } },
{ 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 } },
// 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
@ -103,9 +102,8 @@ static const EscapeEncoding escapeLetterEncodings[] = {
// Home/End, letter version
// * gnome-terminal uses `ESC O [HF]`. I never saw it modified.
// kBareMod: IntelliJ/JediTerm uses escapes like: ESC [ n HF
{ true, '[', 'H', kBare | kBareMod | kSemiMod, { VK_HOME, '\0', 0 } },
{ true, '[', 'F', kBare | kBareMod | kSemiMod, { VK_END, '\0', 0 } },
{ true, '[', 'H', kBare | kSemiMod, { VK_HOME, '\0', 0 } },
{ true, '[', 'F', kBare | kSemiMod, { VK_END, '\0', 0 } },
{ true, 'O', 'H', kBare, { VK_HOME, '\0', 0 } },
{ true, 'O', 'F', kBare, { VK_END, '\0', 0 } },
@ -237,7 +235,6 @@ 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 } },

View File

@ -55,7 +55,7 @@ Scraper::Scraper(
{
m_consoleBuffer = &buffer;
resetConsoleTracking(Terminal::OmitClear, buffer.windowRect().top());
resetConsoleTracking(Terminal::OmitClear, buffer.windowRect());
m_bufferData.resize(BUFFER_LINE_COUNT);
@ -114,13 +114,13 @@ void Scraper::scrapeBuffer(Win32ConsoleBuffer &buffer,
}
void Scraper::resetConsoleTracking(
Terminal::SendClearFlag sendClear, int64_t scrapedLineCount)
Terminal::SendClearFlag sendClear, const SmallRect &windowRect)
{
for (ConsoleLine &line : m_bufferData) {
line.reset();
}
m_syncRow = -1;
m_scrapedLineCount = scrapedLineCount;
m_scrapedLineCount = windowRect.top();
m_scrolledCount = 0;
m_maxBufferedLine = -1;
m_dirtyWindowTop = -1;
@ -205,11 +205,7 @@ void Scraper::resizeImpl(const ConsoleScreenBufferInfo &origInfo)
const Coord origBufferSize = origInfo.bufferSize();
const SmallRect origWindowRect = origInfo.windowRect();
if (m_directMode) {
for (ConsoleLine &line : m_bufferData) {
line.reset();
}
} else {
if (!m_directMode) {
m_consoleBuffer->clearLines(0, origWindowRect.Top, origInfo);
clearBufferLines(0, origWindowRect.Top);
if (m_syncRow != -1) {
@ -347,8 +343,7 @@ 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,
newDirectMode ? 0 : info.windowRect().top());
resetConsoleTracking(Terminal::SendClear, info.windowRect());
m_directMode = newDirectMode;
// When we switch from direct->scrolling mode, make sure the console is
@ -360,11 +355,6 @@ 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()) {
@ -375,15 +365,14 @@ 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 = forceResize ? m_consoleBuffer->bufferInfo() : info;
if (forceResize) {
resizeImpl(info);
finalInfoOut = m_consoleBuffer->bufferInfo();
} else {
finalInfoOut = info;
}
}
// Try to match Windows' behavior w.r.t. to the LVB attribute flags. In some
@ -432,7 +421,7 @@ WORD Scraper::attributesMask()
}
void Scraper::directScrapeOutput(const ConsoleScreenBufferInfo &info,
bool consoleCursorVisible)
bool cursorVisible)
{
const SmallRect windowRect = info.windowRect();
@ -446,35 +435,40 @@ void Scraper::directScrapeOutput(const ConsoleScreenBufferInfo &info,
const int h = scrapeRect.height();
const Coord cursor = info.cursorPosition();
const bool showTerminalCursor =
consoleCursorVisible && scrapeRect.contains(cursor);
const int cursorColumn = !showTerminalCursor ? -1 : cursor.X - scrapeRect.Left;
const int cursorLine = !showTerminalCursor ? -1 : cursor.Y - scrapeRect.Top;
if (!showTerminalCursor) {
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) {
m_terminal->hideTerminalCursor();
}
largeConsoleRead(m_readBuffer, *m_consoleBuffer, scrapeRect, attributesMask());
bool sawModifiedLine = false;
for (int line = 0; line < h; ++line) {
const CHAR_INFO *const curLine =
const CHAR_INFO *curLine =
m_readBuffer.lineData(scrapeRect.top() + line);
ConsoleLine &bufLine = m_bufferData[line];
if (bufLine.detectChangeAndSetLine(curLine, w)) {
if (sawModifiedLine) {
bufLine.setLine(curLine, w);
} else {
sawModifiedLine = bufLine.detectChangeAndSetLine(curLine, w);
}
if (sawModifiedLine) {
const int lineCursorColumn =
line == cursorLine ? cursorColumn : -1;
m_terminal->sendLine(line, curLine, w, lineCursorColumn);
}
}
if (showTerminalCursor) {
if (cursorVisible) {
m_terminal->showTerminalCursor(cursorColumn, cursorLine);
}
}
bool Scraper::scrollingScrapeOutput(const ConsoleScreenBufferInfo &info,
bool consoleCursorVisible,
bool cursorVisible,
bool tentative)
{
const Coord cursor = info.cursorPosition();
@ -494,7 +488,7 @@ bool Scraper::scrollingScrapeOutput(const ConsoleScreenBufferInfo &info,
trace("Sync marker has disappeared -- resetting the terminal"
" (m_syncCounter=%u)",
m_syncCounter);
resetConsoleTracking(Terminal::SendClear, windowRect.top());
resetConsoleTracking(Terminal::SendClear, windowRect);
} else if (markerRow != m_syncRow) {
ASSERT(markerRow < m_syncRow);
m_scrolledCount += (m_syncRow - markerRow);
@ -537,7 +531,7 @@ bool Scraper::scrollingScrapeOutput(const ConsoleScreenBufferInfo &info,
trace("Window moved upward -- resetting the terminal"
" (m_syncCounter=%u)",
m_syncCounter);
resetConsoleTracking(Terminal::SendClear, windowRect.top());
resetConsoleTracking(Terminal::SendClear, windowRect);
}
}
m_dirtyWindowTop = windowRect.top();
@ -606,12 +600,9 @@ bool Scraper::scrollingScrapeOutput(const ConsoleScreenBufferInfo &info,
std::min(m_dirtyLineCount, windowRect.top() + windowRect.height()) +
m_scrolledCount;
const bool showTerminalCursor =
consoleCursorVisible && windowRect.contains(cursor);
const int64_t cursorLine = !showTerminalCursor ? -1 : cursor.Y + m_scrolledCount;
const int cursorColumn = !showTerminalCursor ? -1 : cursor.X;
if (!showTerminalCursor) {
const int64_t cursorLine = !cursorVisible ? -1 : cursor.Y + m_scrolledCount;
const int cursorColumn = !cursorVisible ? -1 : cursor.X;
if (!cursorVisible) {
m_terminal->hideTerminalCursor();
}
@ -640,7 +631,7 @@ bool Scraper::scrollingScrapeOutput(const ConsoleScreenBufferInfo &info,
m_scrapedLineCount = windowRect.top() + m_scrolledCount;
if (showTerminalCursor) {
if (cursorVisible) {
m_terminal->showTerminalCursor(cursorColumn, cursorLine);
}

View File

@ -64,7 +64,7 @@ public:
private:
void resetConsoleTracking(
Terminal::SendClearFlag sendClear, int64_t scrapedLineCount);
Terminal::SendClearFlag sendClear, const SmallRect &windowRect);
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 consoleCursorVisible);
bool cursorVisible);
bool scrollingScrapeOutput(const ConsoleScreenBufferInfo &info,
bool consoleCursorVisible,
bool cursorVisible,
bool tentative);
void syncMarkerText(CHAR_INFO (&output)[SYNC_MARKER_LEN]);
int findSyncMarker();

View File

@ -76,14 +76,6 @@ 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);

View File

@ -218,11 +218,6 @@ 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.

View File

@ -935,33 +935,6 @@ 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

View File

@ -26,7 +26,6 @@ struct AgentMsg
enum Type {
StartProcess,
SetSize,
GetConsoleProcessList,
};
};

View File

@ -34,6 +34,7 @@
#include <sys/cygwin.h>
#include <termios.h>
#include <unistd.h>
#include <wchar.h>
#include <map>
#include <string>
@ -198,126 +199,6 @@ 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)
@ -357,6 +238,20 @@ 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.
@ -582,6 +477,12 @@ 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;
@ -631,14 +532,35 @@ 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] = findProgram(argv[0], args.childArgv[0]);
std::string cmdLine = argvToCommandLine(args.childArgv);
wchar_t *cmdLineW = heapMbsToWcs(cmdLine.c_str());
//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);
winpty_spawn_config_t *spawnCfg = winpty_spawn_config_new(
WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN,
NULL, cmdLineW, NULL, NULL, NULL);
NULL, // heapMbsToWcs(args.childArgv[0].c_str()),
cmdLineW, NULL, NULL, NULL);
assert(spawnCfg != NULL);
winpty_error_ptr_t spawnErr = NULL;
@ -650,20 +572,19 @@ 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, "%s: error: cannot start '%s': %s\n",
argv[0],
cmdLine.c_str(),
fprintf(stderr, "Could not start '%s': %s\n",
childCmdLine.c_str(),
formatErrorMessage(lastError).c_str());
} else {
fprintf(stderr, "%s: error: cannot start '%s': internal error: %s\n",
argv[0],
cmdLine.c_str(),
fprintf(stderr, "Could not start '%s': internal error: %s\n",
childCmdLine.c_str(),
wcsToMbs(winpty_error_msg(spawnErr)).c_str());
}
exit(1);
}
winpty_error_free(spawnErr);
delete [] cmdLineW;
delete [] childCmdLineW;
}
registerResizeSignalHandler();