Compare commits

..

35 Commits

Author SHA1 Message Date
Ryan Prichard
7e59fe2d09 Remove tea-ci drone.yml and switch status badge to Appveyor 2018-12-19 00:40:19 -08:00
Ryan Prichard
a6174e8a4d
Merge pull request #163 from jackyzy823/master
Disable outputing title escape sequences when WINPTY_FLAG_PLAIN_OUTPUT sets
2018-12-19 00:28:51 -08:00
jackyzy823
b60ded1cf2 Do not output title escape sequences when WINPTY_FLAG_PLAIN_OUTPUT sets 2018-12-14 14:16:31 +08:00
Ryan Prichard
afacf7792f Use Appveyor's MSYS2/Cygwin installation rather than my own snapshots
It's simpler to just rely on what Appveyor already provides in its system
images. Appveyor doesn't provide a 32-bit msys2 build, but AFAIK, anyone
needing that can compile it themselves.

Avoid listing gcc-g++ in the Cygwin setup commands because it apparently
breaks the Cygwin toolchain (i.e. missing stddef.h error):
https://ci.appveyor.com/project/rprichard/winpty/builds/19875792
2018-10-29 04:20:06 -07:00
Ryan Prichard
d49712c548 Switch to GitHub prebuilts and add SHA256 checking 2018-10-14 23:53:23 -07:00
Ryan Prichard
784b812663 Add an appveyor config file 2018-10-14 23:08:04 -07:00
Ryan Prichard
ceecad1551 Actually use an XP toolset for XP builds 2018-10-14 22:43:15 -07:00
Ryan Prichard
dcb2df4a4e ship.py: build packages using cygwin/msys2 prebuilts 2018-10-14 22:43:03 -07:00
Ryan Prichard
6aae341b74 ship.py: remove obsolete msys 1.0 mingw32-make code 2018-10-14 19:25:46 -07:00
Ryan Prichard
498a9b1a7f Add util exercising [GS]etConsoleScreenBufferInfoEx
Specifically, allow getting and setting window/buffer size/position.
2018-09-16 20:59:52 -07:00
Ryan Prichard
850661d02b Merge pull request #130 from the-ress/get-console-process-list
Add winpty_get_console_process_list
2017-10-07 20:34:18 -07:00
Tereza Tomcova
f678472b51 Replaced overflow check 2017-10-07 20:33:06 +02:00
Tereza Tomcova
39fe32b15d Fix for edge case when process list changes between GetConsoleProcessList calls 2017-10-04 00:28:05 +02:00
Tereza Tomcova
1424696a6d Add winpty_get_console_process_list 2017-09-16 14:29:23 +02:00
Ryan Prichard
ce9239af5d Update release notes 2017-05-25 20:01:39 -05:00
Ryan Prichard
7d16530ea7 For IntelliJ/JediTerm, allow kBareMod input escapes for navigation keys
Fixes https://github.com/rprichard/winpty/issues/118
2017-05-25 19:49:36 -05:00
Ryan Prichard
4978cf94b6 Go back to using GenerateConsoleCtrlEvent for processed-mode Ctrl-C
This change reverts the first part of the GH-116 fix.

See https://github.com/rprichard/winpty/issues/116
2017-05-19 03:14:13 -05:00
Ryan Prichard
0c86e4f78e Add an entry for Ctrl-C in DefaultInputMap's SimpleEncoding table
Fixes https://github.com/rprichard/winpty/issues/116
2017-05-18 05:04:59 -05:00
Ryan Prichard
c9ce3ad1e3 Use Window messages rather than GenerateConsoleCtrlEvent for Ctrl-C
Fixes https://github.com/rprichard/winpty/issues/116
2017-05-17 06:03:17 -05:00
Ryan Prichard
096f978c7a Flush INPUT_RECORD objects prior to SendMessage/GenerateConsoleCtrlEvent
Fixes https://github.com/rprichard/winpty/issues/117
2017-05-17 05:29:07 -05:00
Ryan Prichard
faf75f74b7 Bump version to 0.4.4-dev 2017-05-17 02:01:42 -05:00
Ryan Prichard
3e1ab962d5 Version 0.4.3 2017-05-17 01:47:20 -05:00
Ryan Prichard
e75bd9cbdd Explicitly use the Unicode function WriteConsoleInputW 2017-05-17 01:47:19 -05:00
Ryan Prichard
6765a41c5b Change the modified-Home/End escape sequence from H/F to 1/4
For unmodified Home/End, winpty instead sends WM_KEYDOWN / WM_KEYUP
messages to the console window so that the console can decide what escape
to use based on the VT terminal "alternate mode".

Fixes https://github.com/rprichard/winpty/issues/114
2017-04-27 21:17:03 -07:00
Ryan Prichard
5814705546 Update release notes 2017-04-24 00:47:14 -07:00
Ryan Prichard
b4db322010 Stop using fonts smaller than 5px in height
The console must decide whether characters are half-vs-full width, and it
seems to make poorer decisions with fonts that are atypically small.  The
console properties dialog doesn't allow fonts smaller than 5px.

For example, (U+2026 HORIZONTAL ELLIPSIS) is half-width in Lucida Console
and Consolas, but with Lucida Console, 2px or 4px height, the console
thinks it's full-width.

Disallowing smaller fonts will force winpty to use larger windows, which
makes it more likely to hit the maximum window size constraint.

See https://github.com/Microsoft/vscode/issues/19665.
2017-04-24 00:38:05 -07:00
Ryan Prichard
af4bc8b78a Update release notes 2017-04-22 23:51:06 -07:00
Ryan Prichard
5b99238fca Don't show the cursor if it is outside the console window
Fixes https://github.com/rprichard/winpty/issues/113
2017-04-22 23:41:22 -07:00
Ryan Prichard
bae43674b1 Update release notes 2017-04-22 22:09:36 -07:00
Ryan Prichard
ac33b66172 Direct mode: invalidate m_bufferData on resize
Also, for efficieny in direct mode, only output the lines that have
changed, rather than reprinting all of the lines after the first changed
line.

Fixes https://github.com/rprichard/winpty/issues/112
2017-04-22 22:05:16 -07:00
Ryan Prichard
d636a1de86 Synthesize a WINDOW_BUFFER_SIZE_EVENT event after winpty is resized
Fixes https://github.com/rprichard/winpty/issues/110.
2017-04-22 14:57:35 -07:00
Ryan Prichard
838d959579 Update the release notes 2017-04-22 03:11:58 -05:00
Ryan Prichard
201d8aeb3a Fix handling of AltGr keys
* Respect the result of VkKeyScan when it returns multiple modifiers.  I
   *think* the old if-else behavior was a typo?

 * Refactor the code to restrict the scope of the Alt-implies-NUL behavior.
   On my US keyboard, if I press either Alt-P or Alt-Ctrl-P, the console
   reports a VirtualKey of 'P', and a NUL codepoint for KeyUp.  For KeyDown,
   though, Alt-P reports a 'P' codepoint, but Alt-Ctrl-P reports a NUL
   codepoint.  winpty previously implemented this logic in appendKeyPress, but
   now it's factored out.

   If the terminal sends us U+20AC (EURO SIGN), VkKeyScan decides that it must
   have been typed using both Ctrl and Alt.  (Pressing Ctrl-LeftAlt-E on my US
   keyboard produces a Euro, as does RightAlt-E; AltGr/RightAlt seems to imply
   Ctrl.)  If the Alt keypress only comes from VkKeyScan, then continue using
   the proper codepoint.

 * Avoid changing the VT/reencoding behavior.  In appendUtf8Char, pass the
   unadjusted codepoint and Alt-escape to appendKeyPress, which will pass it
   to reencodeEscapedKeyPress if the console is using VT input.

Fixes https://github.com/rprichard/winpty/issues/109
2017-04-22 03:11:58 -05:00
Ryan Prichard
6360ec6a5b Add test case demonstrating a Win10 15048 ReadConsoleOutput bug 2017-03-16 20:57:35 -05:00
Ryan Prichard
47e7e005e8 Change program execution: resolve symlinks and do PATH search explicitly
Fixes https://github.com/rprichard/winpty/issues/81
Fixes https://github.com/rprichard/winpty/issues/98
2017-02-01 20:48:43 -06:00
25 changed files with 877 additions and 260 deletions

View File

@ -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
View File

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

View File

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

View File

@ -1,3 +1,57 @@
# Next Version
Input handling changes:
* Improve Ctrl-C handling with programs that use unprocessed input. (e.g.
Ctrl-C now cancels input with PowerShell on Windows 10.)
[#116](https://github.com/rprichard/winpty/issues/116)
* Fix a theoretical issue with input event ordering.
[#117](https://github.com/rprichard/winpty/issues/117)
* Ctrl/Shift+{Arrow,Home,End} keys now work with IntelliJ.
[#118](https://github.com/rprichard/winpty/issues/118)
# Version 0.4.3 (2017-05-17)
Input handling changes:
* winpty sets `ENHANCED_KEY` for arrow and navigation keys. This fixes an
issue with the Ruby REPL.
[#99](https://github.com/rprichard/winpty/issues/99)
* AltGr keys are handled better now.
[#109](https://github.com/rprichard/winpty/issues/109)
* In `ENABLE_VIRTUAL_TERMINAL_INPUT` mode, when typing Home/End with a
modifier (e.g. Ctrl), winpty now generates an H/F escape sequence like
`^[[1;5F` rather than a 1/4 escape like `^[[4;5~`.
[#114](https://github.com/rprichard/winpty/issues/114)
Resizing and scraping fixes:
* winpty now synthesizes a `WINDOW_BUFFER_SIZE_EVENT` event after resizing
the console to better propagate window size changes to console programs.
In particular, this affects WSL and Cygwin.
[#110](https://github.com/rprichard/winpty/issues/110)
* Better handling of resizing for certain full-screen programs, like
WSL less.
[#112](https://github.com/rprichard/winpty/issues/112)
* Hide the cursor if it's currently outside the console window. This change
fixes an issue with Far Manager.
[#113](https://github.com/rprichard/winpty/issues/113)
* winpty now avoids using console fonts smaller than 5px high to improve
half-vs-full-width character handling. See
https://github.com/Microsoft/vscode/issues/19665.
[b4db322010](https://github.com/rprichard/winpty/commit/b4db322010d2d897e6c496fefc4f0ecc9b84c2f3)
Cygwin/MSYS adapter fix:
* The way the `winpty` Cygwin/MSYS2 adapter searches for the program to
launch changed. It now resolves symlinks and searches the PATH explicitly.
[#81](https://github.com/rprichard/winpty/issues/81)
[#98](https://github.com/rprichard/winpty/issues/98)
This release does not include binaries for the old MSYS1 project anymore.
MSYS2 will continue to be supported. See
https://github.com/rprichard/winpty/issues/97.
# Version 0.4.2 (2017-01-18) # Version 0.4.2 (2017-01-18)
This release improves WSL support (i.e. Bash-on-Windows): This release improves WSL support (i.e. Bash-on-Windows):

View File

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

16
appveyor.yml Executable file
View 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
View 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
View 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 },
&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,12 +1,17 @@
import os import os
import sys import sys
# These scripts need to continue using Python 2 rather than 3, because
# make_msvc_package.py puts the current Python interpreter on the PATH for the
# sake of gyp, and gyp doesn't work with Python 3 yet.
# https://bugs.chromium.org/p/gyp/issues/detail?id=36
if os.name != "nt": if os.name != "nt":
sys.exit("Error: ship scripts require native Python 2.7. (wrong os.name)") sys.exit("Error: ship scripts require native Python 2.7. (wrong os.name)")
if sys.version_info[0:2] != (2,7): if sys.version_info[0:2] != (2,7):
sys.exit("Error: ship scripts require native Python 2.7. (wrong version)") sys.exit("Error: ship scripts require native Python 2.7. (wrong version)")
import glob import glob
import hashlib
import shutil import shutil
import subprocess import subprocess
from distutils.spawn import find_executable from distutils.spawn import find_executable
@ -20,11 +25,11 @@ def rmrf(patterns):
for pattern in patterns: for pattern in patterns:
for path in glob.glob(pattern): for path in glob.glob(pattern):
if os.path.isdir(path) and not os.path.islink(path): if os.path.isdir(path) and not os.path.islink(path):
print "+ rm -r " + path print("+ rm -r " + path)
sys.stdout.flush() sys.stdout.flush()
shutil.rmtree(path) shutil.rmtree(path)
elif os.path.isfile(path): elif os.path.isfile(path):
print "+ rm " + path print("+ rm " + path)
sys.stdout.flush() sys.stdout.flush()
os.remove(path) os.remove(path)
@ -37,17 +42,48 @@ def requireExe(name, guesses):
for guess in guesses: for guess in guesses:
if os.path.exists(guess): if os.path.exists(guess):
newDir = os.path.dirname(guess) newDir = os.path.dirname(guess)
print "Adding " + newDir + " to Path to provide " + name print("Adding " + newDir + " to Path to provide " + name)
os.environ["Path"] = newDir + ";" + os.environ["Path"] os.environ["Path"] = newDir + ";" + os.environ["Path"]
ret = find_executable(name) ret = find_executable(name)
if ret is None: if ret is None:
sys.exit("Error: required EXE is missing from Path: " + name) sys.exit("Error: required EXE is missing from Path: " + name)
return ret return ret
class ModifyEnv:
def __init__(self, **kwargs):
self._changes = dict(kwargs)
self._original = dict()
def __enter__(self):
for var, val in self._changes.items():
self._original[var] = os.environ[var]
os.environ[var] = val
def __exit__(self, type, value, traceback):
for var, val in self._original.items():
os.environ[var] = val
def sha256(path):
with open(path, "rb") as fp:
return hashlib.sha256(fp.read()).hexdigest()
def checkSha256(path, expected):
actual = sha256(path)
if actual != expected:
sys.exit("error: sha256 hash mismatch on {}: expected {}, found {}".format(
path, expected, actual))
requireExe("git.exe", [ requireExe("git.exe", [
"C:\\Program Files\\Git\\cmd\\git.exe", "C:\\Program Files\\Git\\cmd\\git.exe",
"C:\\Program Files (x86)\\Git\\cmd\\git.exe" "C:\\Program Files (x86)\\Git\\cmd\\git.exe"
]) ])
commitHash = subprocess.check_output(["git.exe", "rev-parse", "HEAD"]).decode().strip() commitHash = subprocess.check_output(["git.exe", "rev-parse", "HEAD"]).strip()
defaultPathEnviron = "C:\\Windows\\System32;C:\\Windows" defaultPathEnviron = "C:\\Windows\\System32;C:\\Windows"
ZIP_TOOL = requireExe("7z.exe", [
"C:\\Program Files\\7-Zip\\7z.exe",
"C:\\Program Files (x86)\\7-Zip\\7z.exe",
])
requireExe("curl.exe", [])

View File

@ -36,10 +36,6 @@ import subprocess
import sys import sys
os.chdir(common_ship.topDir) os.chdir(common_ship.topDir)
ZIP_TOOL = common_ship.requireExe("7z.exe", [
"C:\\Program Files\\7-Zip\\7z.exe",
"C:\\Program Files (x86)\\7-Zip\\7z.exe",
])
MSVC_VERSION_TABLE = { MSVC_VERSION_TABLE = {
"2015" : { "2015" : {
@ -100,14 +96,16 @@ def build(arch, packageDir, xp=False):
if not os.path.isfile(devCmdPath): if not os.path.isfile(devCmdPath):
sys.exit("Error: MSVC environment script missing: " + devCmdPath) sys.exit("Error: MSVC environment script missing: " + devCmdPath)
toolsetArgument = " --toolset {}".format(versionInfo["xp_toolset"]) if xp else ""
newEnv = os.environ.copy() newEnv = os.environ.copy()
newEnv["PATH"] = os.path.dirname(sys.executable) + ";" + common_ship.defaultPathEnviron newEnv["PATH"] = os.path.dirname(sys.executable) + ";" + common_ship.defaultPathEnviron
commandLine = ( commandLine = (
'"' + devCmdPath + '" && ' '"' + devCmdPath + '" && ' +
" vcbuild.bat" + " vcbuild.bat" +
" --gyp-msvs-version " + versionInfo["gyp_version"] + " --gyp-msvs-version " + versionInfo["gyp_version"] +
" --msvc-platform " + archInfo["msvc_platform"] + " --msvc-platform " + archInfo["msvc_platform"] +
" --commit-hash " + common_ship.commitHash " --commit-hash " + common_ship.commitHash +
toolsetArgument
) )
subprocess.check_call(commandLine, shell=True, env=newEnv) subprocess.check_call(commandLine, shell=True, env=newEnv)
@ -159,7 +157,7 @@ def buildPackage():
shutil.copy(topDir + "/README.md", packageDir) shutil.copy(topDir + "/README.md", packageDir)
shutil.copy(topDir + "/RELEASES.md", packageDir) shutil.copy(topDir + "/RELEASES.md", packageDir)
subprocess.check_call([ZIP_TOOL, "a", packageFile, "."], cwd=packageDir) subprocess.check_call([common_ship.ZIP_TOOL, "a", packageFile, "."], cwd=packageDir)
if __name__ == "__main__": if __name__ == "__main__":
buildPackage() buildPackage()

View File

@ -23,20 +23,16 @@
# #
# Run with native CPython 2.7 on a 64-bit computer. # Run with native CPython 2.7 on a 64-bit computer.
# #
# Each of the targets in BUILD_TARGETS must be installed to the default
# location. Each target must have the appropriate MinGW and non-MinGW
# compilers installed, as well as make and tar.
#
import common_ship import common_ship
from optparse import OptionParser
import multiprocessing import multiprocessing
import os import os
import shutil import shutil
import subprocess import subprocess
import sys import sys
os.chdir(common_ship.topDir)
def dllVersion(path): def dllVersion(path):
version = subprocess.check_output( version = subprocess.check_output(
@ -44,46 +40,40 @@ def dllVersion(path):
"[System.Diagnostics.FileVersionInfo]::GetVersionInfo(\"" + path + "\").FileVersion"]) "[System.Diagnostics.FileVersionInfo]::GetVersionInfo(\"" + path + "\").FileVersion"])
return version.strip() return version.strip()
# Determine other build parameters.
print "Determining Cygwin/MSYS2 DLL versions..."
sys.stdout.flush()
BUILD_TARGETS = [
# {
# "name": "msys",
# "path": "C:\\MinGW\\bin;C:\\MinGW\\msys\\1.0\\bin",
# # The parallel make.exe in the original MSYS/MinGW project hangs.
# "make_binary": "mingw32-make.exe",
# },
{
"name": "msys2-" + dllVersion("C:\\msys32\\usr\\bin\\msys-2.0.dll") + "-ia32",
"path": "C:\\msys32\\mingw32\\bin;C:\\msys32\\usr\\bin",
},
{
"name": "msys2-" + dllVersion("C:\\msys64\\usr\\bin\\msys-2.0.dll") + "-x64",
"path": "C:\\msys64\\mingw64\\bin;C:\\msys64\\usr\\bin",
},
{
"name": "cygwin-" + dllVersion("C:\\cygwin\\bin\\cygwin1.dll") + "-ia32",
"path": "C:\\cygwin\\bin",
},
{
"name": "cygwin-" + dllVersion("C:\\cygwin64\\bin\\cygwin1.dll") + "-x64",
"path": "C:\\cygwin64\\bin",
},
]
def buildTarget(target): os.chdir(common_ship.topDir)
packageName = "winpty-" + common_ship.winptyVersion + "-" + target["name"]
# Determine other build parameters.
BUILD_KINDS = {
"cygwin": {
"path": ["bin"],
"dll": "bin\\cygwin1.dll",
},
"msys2": {
"path": ["opt\\bin", "usr\\bin"],
"dll": "usr\\bin\\msys-2.0.dll",
},
}
def buildTarget(kind, syspath, arch):
binPaths = [os.path.join(syspath, p) for p in BUILD_KINDS[kind]["path"]]
binPaths += common_ship.defaultPathEnviron.split(";")
newPath = ";".join(binPaths)
dllver = dllVersion(os.path.join(syspath, BUILD_KINDS[kind]["dll"]))
packageName = "winpty-{}-{}-{}-{}".format(common_ship.winptyVersion, kind, dllver, arch)
if os.path.exists("ship\\packages\\" + packageName): if os.path.exists("ship\\packages\\" + packageName):
shutil.rmtree("ship\\packages\\" + packageName) shutil.rmtree("ship\\packages\\" + packageName)
oldPath = os.environ["PATH"]
os.environ["PATH"] = target["path"] + ";" + common_ship.defaultPathEnviron print("+ Setting PATH to: {}".format(newPath))
with common_ship.ModifyEnv(PATH=newPath):
subprocess.check_call(["sh.exe", "configure"]) subprocess.check_call(["sh.exe", "configure"])
makeBinary = target.get("make_binary", "make.exe") subprocess.check_call(["make.exe", "clean"])
subprocess.check_call([makeBinary, "clean"])
makeBaseCmd = [ makeBaseCmd = [
makeBinary, "make.exe",
"USE_PCH=0",
"COMMIT_HASH=" + common_ship.commitHash, "COMMIT_HASH=" + common_ship.commitHash,
"PREFIX=ship/packages/" + packageName "PREFIX=ship/packages/" + packageName
] ]
@ -93,16 +83,22 @@ def buildTarget(target):
subprocess.check_call(["tar.exe", "cvfz", subprocess.check_call(["tar.exe", "cvfz",
packageName + ".tar.gz", packageName + ".tar.gz",
packageName], cwd=os.path.join(os.getcwd(), "ship", "packages")) packageName], cwd=os.path.join(os.getcwd(), "ship", "packages"))
os.environ["PATH"] = oldPath
def main(): def main():
oldPath = os.environ["PATH"] parser = OptionParser()
for t in BUILD_TARGETS: parser.add_option("--kind", type="choice", choices=["cygwin", "msys2"])
os.environ["PATH"] = t["path"] + ";" + common_ship.defaultPathEnviron parser.add_option("--syspath")
subprocess.check_output(["tar.exe", "--help"]) parser.add_option("--arch", type="choice", choices=["ia32", "x64"])
subprocess.check_output(["make.exe", "--help"]) (args, extra) = parser.parse_args()
for t in BUILD_TARGETS:
buildTarget(t) args.kind or parser.error("--kind must be specified")
args.arch or parser.error("--arch must be specified")
args.syspath or parser.error("--syspath must be specified")
extra and parser.error("unexpected positional argument(s)")
buildTarget(args.kind, args.syspath, args.arch)
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -27,6 +27,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <algorithm>
#include <string> #include <string>
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -332,6 +333,9 @@ void Agent::handlePacket(ReadBuffer &packet)
// at once, we can ignore the early ones. // at once, we can ignore the early ones.
handleSetSizePacket(packet); handleSetSizePacket(packet);
break; break;
case AgentMsg::GetConsoleProcessList:
handleGetConsoleProcessListPacket(packet);
break;
default: default:
trace("Unrecognized message, id:%d", type); trace("Unrecognized message, id:%d", type);
} }
@ -426,6 +430,33 @@ void Agent::handleSetSizePacket(ReadBuffer &packet)
writePacket(reply); writePacket(reply);
} }
void Agent::handleGetConsoleProcessListPacket(ReadBuffer &packet)
{
packet.assertEof();
auto processList = std::vector<DWORD>(64);
auto processCount = GetConsoleProcessList(&processList[0], processList.size());
// The process list can change while we're trying to read it
while (processList.size() < processCount) {
// Multiplying by two caps the number of iterations
const auto newSize = std::max<DWORD>(processList.size() * 2, processCount);
processList.resize(newSize);
processCount = GetConsoleProcessList(&processList[0], processList.size());
}
if (processCount == 0) {
trace("GetConsoleProcessList failed");
}
auto reply = newPacket();
reply.putInt32(processCount);
for (DWORD i = 0; i < processCount; i++) {
reply.putInt32(processList[i]);
}
writePacket(reply);
}
void Agent::pollConinPipe() void Agent::pollConinPipe()
{ {
const std::string newData = m_coninPipe->readAllToString(); const std::string newData = m_coninPipe->readAllToString();
@ -529,11 +560,31 @@ void Agent::resizeWindow(int cols, int rows)
Win32Console::FreezeGuard guard(m_console, m_console.frozen()); Win32Console::FreezeGuard guard(m_console, m_console.frozen());
const Coord newSize(cols, rows); const Coord newSize(cols, rows);
ConsoleScreenBufferInfo info; ConsoleScreenBufferInfo info;
m_primaryScraper->resizeWindow(*openPrimaryBuffer(), newSize, info); auto primaryBuffer = openPrimaryBuffer();
m_primaryScraper->resizeWindow(*primaryBuffer, newSize, info);
m_consoleInput->setMouseWindowRect(info.windowRect()); m_consoleInput->setMouseWindowRect(info.windowRect());
if (m_errorScraper) { if (m_errorScraper) {
m_errorScraper->resizeWindow(*m_errorBuffer, newSize, info); m_errorScraper->resizeWindow(*m_errorBuffer, newSize, info);
} }
// Synthesize a WINDOW_BUFFER_SIZE_EVENT event. Normally, Windows
// generates this event only when the buffer size changes, not when the
// window size changes. This behavior is undesirable in two ways:
// - When winpty expands the window horizontally, it must expand the
// buffer first, then the window. At least some programs (e.g. the WSL
// bash.exe wrapper) use the window width rather than the buffer width,
// so there is a short timespan during which they can read the wrong
// value.
// - If the window's vertical size is changed, no event is generated,
// even though a typical well-behaved console program cares about the
// *window* height, not the *buffer* height.
// This synthesization works around a design flaw in the console. It's probably
// harmless. See https://github.com/rprichard/winpty/issues/110.
INPUT_RECORD sizeEvent {};
sizeEvent.EventType = WINDOW_BUFFER_SIZE_EVENT;
sizeEvent.Event.WindowBufferSizeEvent.dwSize = primaryBuffer->bufferSize();
DWORD actual {};
WriteConsoleInputW(GetStdHandle(STD_INPUT_HANDLE), &sizeEvent, 1, &actual);
} }
void Agent::scrapeBuffers() void Agent::scrapeBuffers()
@ -551,9 +602,11 @@ void Agent::syncConsoleTitle()
{ {
std::wstring newTitle = m_console.title(); std::wstring newTitle = m_console.title();
if (newTitle != m_currentTitle) { if (newTitle != m_currentTitle) {
if (!m_plainMode && !m_conoutPipe->isClosed()) {
std::string command = std::string("\x1b]0;") + std::string command = std::string("\x1b]0;") +
utf8FromWide(newTitle) + "\x07"; utf8FromWide(newTitle) + "\x07";
m_conoutPipe->write(command.c_str()); m_conoutPipe->write(command.c_str());
}
m_currentTitle = newTitle; m_currentTitle = newTitle;
} }
} }

View File

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

View File

@ -113,8 +113,6 @@ struct Font {
// - misc/Utf16Echo.cc, misc/FontSurvey.cc, misc/SetFont.cc, misc/GetFont.cc // - misc/Utf16Echo.cc, misc/FontSurvey.cc, misc/SetFont.cc, misc/GetFont.cc
const FontSize kLucidaFontSizes[] = { const FontSize kLucidaFontSizes[] = {
{ 2, 1 },
{ 4, 2 },
{ 5, 3 }, { 5, 3 },
{ 6, 4 }, { 6, 4 },
{ 8, 5 }, { 8, 5 },
@ -132,8 +130,6 @@ const FontSize kLucidaFontSizes[] = {
// Japanese. Used on Vista and Windows 7. // Japanese. Used on Vista and Windows 7.
const FontSize k932GothicVista[] = { const FontSize k932GothicVista[] = {
{ 2, 1 },
{ 4, 2 },
{ 6, 3 }, { 6, 3 },
{ 8, 4 }, { 8, 4 },
{ 10, 5 }, { 10, 5 },
@ -150,7 +146,6 @@ const FontSize k932GothicVista[] = {
const FontSize k932GothicWin8[] = { const FontSize k932GothicWin8[] = {
// All of these characters are broken w.r.t. full-size East Asian // All of these characters are broken w.r.t. full-size East Asian
// characters, but they're equally broken. // characters, but they're equally broken.
{ 3, 2 },
{ 5, 3 }, { 5, 3 },
{ 7, 4 }, { 7, 4 },
{ 9, 5 }, { 9, 5 },
@ -170,8 +165,6 @@ const FontSize k932GothicWin8[] = {
// Japanese. Used on the new Windows 10 console. // Japanese. Used on the new Windows 10 console.
const FontSize k932GothicWin10[] = { const FontSize k932GothicWin10[] = {
{ 2, 1 },
{ 4, 2 },
{ 6, 3 }, { 6, 3 },
{ 8, 4 }, { 8, 4 },
{ 10, 5 }, { 10, 5 },
@ -191,8 +184,6 @@ const FontSize k932GothicWin10[] = {
// Chinese Simplified. // Chinese Simplified.
const FontSize k936SimSun[] = { const FontSize k936SimSun[] = {
{ 2, 1 },
{ 4, 2 },
{ 6, 3 }, { 6, 3 },
{ 8, 4 }, { 8, 4 },
{ 10, 5 }, { 10, 5 },
@ -212,8 +203,6 @@ const FontSize k936SimSun[] = {
// Korean. // Korean.
const FontSize k949GulimChe[] = { const FontSize k949GulimChe[] = {
{ 2, 1 },
{ 4, 2 },
{ 6, 3 }, { 6, 3 },
{ 8, 4 }, { 8, 4 },
{ 10, 5 }, { 10, 5 },
@ -233,8 +222,6 @@ const FontSize k949GulimChe[] = {
// Chinese Traditional. // Chinese Traditional.
const FontSize k950MingLight[] = { const FontSize k950MingLight[] = {
{ 2, 1 },
{ 4, 2 },
{ 6, 3 }, { 6, 3 },
{ 8, 4 }, { 8, 4 },
{ 10, 5 }, { 10, 5 },

View File

@ -343,12 +343,38 @@ void ConsoleInput::doWrite(bool isEof)
idx += charSize; idx += charSize;
} }
m_byteQueue.erase(0, idx); m_byteQueue.erase(0, idx);
flushInputRecords(records);
}
void ConsoleInput::flushInputRecords(std::vector<INPUT_RECORD> &records)
{
if (records.size() == 0) {
return;
}
DWORD actual = 0; DWORD actual = 0;
if (records.size() > 0) {
if (!WriteConsoleInputW(m_conin, records.data(), records.size(), &actual)) { if (!WriteConsoleInputW(m_conin, records.data(), records.size(), &actual)) {
trace("WriteConsoleInputW failed"); trace("WriteConsoleInputW failed");
} }
records.clear();
} }
// This behavior isn't strictly correct, because the keypresses (probably?)
// adopt the keyboard state (e.g. Ctrl/Alt/Shift modifiers) of the current
// window station's keyboard, which has no necessary relationship to the winpty
// instance. It's unlikely to be an issue in practice, but it's conceivable.
// (Imagine a foreground SSH server, where the local user holds down Ctrl,
// while the remote user tries to use WSL navigation keys.) I suspect using
// the BackgroundDesktop mechanism in winpty would fix the problem.
//
// https://github.com/rprichard/winpty/issues/116
static void sendKeyMessage(HWND hwnd, bool isKeyDown, uint16_t virtualKey)
{
uint32_t scanCode = MapVirtualKey(virtualKey, MAPVK_VK_TO_VSC);
if (scanCode > 255) {
scanCode = 0;
}
SendMessage(hwnd, isKeyDown ? WM_KEYDOWN : WM_KEYUP, virtualKey,
(scanCode << 16) | 1u | (isKeyDown ? 0u : 0xc0000000u));
} }
int ConsoleInput::scanInput(std::vector<INPUT_RECORD> &records, int ConsoleInput::scanInput(std::vector<INPUT_RECORD> &records,
@ -359,9 +385,20 @@ int ConsoleInput::scanInput(std::vector<INPUT_RECORD> &records,
ASSERT(inputSize >= 1); ASSERT(inputSize >= 1);
// Ctrl-C. // Ctrl-C.
//
// In processed mode, use GenerateConsoleCtrlEvent so that Ctrl-C handlers
// are called. GenerateConsoleCtrlEvent unfortunately doesn't interrupt
// ReadConsole calls[1]. Using WM_KEYDOWN/UP fixes the ReadConsole
// problem, but breaks in background window stations/desktops.
//
// In unprocessed mode, there's an entry for Ctrl-C in the SimpleEncoding
// table in DefaultInputMap.
//
// [1] https://github.com/rprichard/winpty/issues/116
if (input[0] == '\x03' && (inputConsoleMode() & ENABLE_PROCESSED_INPUT)) { if (input[0] == '\x03' && (inputConsoleMode() & ENABLE_PROCESSED_INPUT)) {
flushInputRecords(records);
trace("Ctrl-C"); trace("Ctrl-C");
BOOL ret = GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); const BOOL ret = GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
trace("GenerateConsoleCtrlEvent: %d", ret); trace("GenerateConsoleCtrlEvent: %d", ret);
return 1; return 1;
} }
@ -395,10 +432,17 @@ int ConsoleInput::scanInput(std::vector<INPUT_RECORD> &records,
trace("Incomplete escape sequence"); trace("Incomplete escape sequence");
return -1; return -1;
} else if (matchLen > 0) { } else if (matchLen > 0) {
appendKeyPress(records, uint32_t winCodePointDn = match.unicodeChar;
match.virtualKey, if ((match.keyState & LEFT_CTRL_PRESSED) && (match.keyState & LEFT_ALT_PRESSED)) {
match.unicodeChar, winCodePointDn = '\0';
match.keyState); }
uint32_t winCodePointUp = winCodePointDn;
if (match.keyState & LEFT_ALT_PRESSED) {
winCodePointUp = '\0';
}
appendKeyPress(records, match.virtualKey,
winCodePointDn, winCodePointUp, match.keyState,
match.unicodeChar, match.keyState);
return matchLen; return matchLen;
} }
@ -417,7 +461,7 @@ int ConsoleInput::scanInput(std::vector<INPUT_RECORD> &records,
trace("Incomplete UTF-8 character in Alt-<Char>"); trace("Incomplete UTF-8 character in Alt-<Char>");
return -1; return -1;
} }
appendUtf8Char(records, &input[1], len, LEFT_ALT_PRESSED); appendUtf8Char(records, &input[1], len, true);
return 1 + len; return 1 + len;
} }
} }
@ -437,7 +481,7 @@ int ConsoleInput::scanInput(std::vector<INPUT_RECORD> &records,
trace("Incomplete UTF-8 character"); trace("Incomplete UTF-8 character");
return -1; return -1;
} }
appendUtf8Char(records, &input[0], len, 0); appendUtf8Char(records, &input[0], len, false);
return len; return len;
} }
@ -557,10 +601,10 @@ int ConsoleInput::scanMouseInput(std::vector<INPUT_RECORD> &records,
void ConsoleInput::appendUtf8Char(std::vector<INPUT_RECORD> &records, void ConsoleInput::appendUtf8Char(std::vector<INPUT_RECORD> &records,
const char *charBuffer, const char *charBuffer,
const int charLen, const int charLen,
const uint16_t keyState) const bool terminalAltEscape)
{ {
const uint32_t code = decodeUtf8(charBuffer); const uint32_t codePoint = decodeUtf8(charBuffer);
if (code == static_cast<uint32_t>(-1)) { if (codePoint == static_cast<uint32_t>(-1)) {
static bool debugInput = isTracingEnabled() && hasDebugFlag("input"); static bool debugInput = isTracingEnabled() && hasDebugFlag("input");
if (debugInput) { if (debugInput) {
StringBuilder error(64); StringBuilder error(64);
@ -574,37 +618,65 @@ void ConsoleInput::appendUtf8Char(std::vector<INPUT_RECORD> &records,
return; return;
} }
const short charScan = code > 0xFFFF ? -1 : VkKeyScan(code); const short charScan = codePoint > 0xFFFF ? -1 : VkKeyScan(codePoint);
uint16_t virtualKey = 0; uint16_t virtualKey = 0;
uint16_t charKeyState = keyState; uint16_t winKeyState = 0;
uint32_t winCodePointDn = codePoint;
uint32_t winCodePointUp = codePoint;
uint16_t vtKeyState = 0;
if (charScan != -1) { if (charScan != -1) {
virtualKey = charScan & 0xFF; virtualKey = charScan & 0xFF;
if (charScan & 0x100) if (charScan & 0x100) {
charKeyState |= SHIFT_PRESSED; winKeyState |= SHIFT_PRESSED;
else if (charScan & 0x200)
charKeyState |= LEFT_CTRL_PRESSED;
else if (charScan & 0x400)
charKeyState |= LEFT_ALT_PRESSED;
} }
appendKeyPress(records, virtualKey, code, charKeyState); if (charScan & 0x200) {
winKeyState |= LEFT_CTRL_PRESSED;
}
if (charScan & 0x400) {
winKeyState |= RIGHT_ALT_PRESSED;
}
if (terminalAltEscape && (winKeyState & LEFT_CTRL_PRESSED)) {
// If the terminal escapes a Ctrl-<Key> with Alt, then set the
// codepoint to 0. On the other hand, if a character requires
// AltGr (like U+00B2 on a German layout), then VkKeyScan will
// report both Ctrl and Alt pressed, and we should keep the
// codepoint. See https://github.com/rprichard/winpty/issues/109.
winCodePointDn = 0;
winCodePointUp = 0;
}
}
if (terminalAltEscape) {
winCodePointUp = 0;
winKeyState |= LEFT_ALT_PRESSED;
vtKeyState |= LEFT_ALT_PRESSED;
}
appendKeyPress(records, virtualKey,
winCodePointDn, winCodePointUp, winKeyState,
codePoint, vtKeyState);
} }
void ConsoleInput::appendKeyPress(std::vector<INPUT_RECORD> &records, void ConsoleInput::appendKeyPress(std::vector<INPUT_RECORD> &records,
uint16_t virtualKey, const uint16_t virtualKey,
uint32_t codePoint, const uint32_t winCodePointDn,
uint16_t keyState) const uint32_t winCodePointUp,
const uint16_t winKeyState,
const uint32_t vtCodePoint,
const uint16_t vtKeyState)
{ {
const bool ctrl = (keyState & LEFT_CTRL_PRESSED) != 0; const bool ctrl = (winKeyState & LEFT_CTRL_PRESSED) != 0;
const bool alt = (keyState & LEFT_ALT_PRESSED) != 0; const bool leftAlt = (winKeyState & LEFT_ALT_PRESSED) != 0;
const bool shift = (keyState & SHIFT_PRESSED) != 0; const bool rightAlt = (winKeyState & RIGHT_ALT_PRESSED) != 0;
const bool enhanced = (keyState & ENHANCED_KEY) != 0; const bool shift = (winKeyState & SHIFT_PRESSED) != 0;
const bool enhanced = (winKeyState & ENHANCED_KEY) != 0;
bool hasDebugInput = false; bool hasDebugInput = false;
if (isTracingEnabled()) { if (isTracingEnabled()) {
static bool debugInput = hasDebugFlag("input"); static bool debugInput = hasDebugFlag("input");
if (debugInput) { if (debugInput) {
hasDebugInput = true; hasDebugInput = true;
InputMap::Key key = { virtualKey, codePoint, keyState }; InputMap::Key key = { virtualKey, winCodePointDn, winKeyState };
trace("keypress: %s", key.toString().c_str()); trace("keypress: %s", key.toString().c_str());
} }
} }
@ -616,18 +688,13 @@ void ConsoleInput::appendKeyPress(std::vector<INPUT_RECORD> &records,
virtualKey == VK_RIGHT || virtualKey == VK_RIGHT ||
virtualKey == VK_HOME || virtualKey == VK_HOME ||
virtualKey == VK_END) && virtualKey == VK_END) &&
!ctrl && !alt && !shift) { !ctrl && !leftAlt && !rightAlt && !shift) {
flushInputRecords(records);
if (hasDebugInput) { if (hasDebugInput) {
trace("sending keypress to console HWND"); trace("sending keypress to console HWND");
} }
uint32_t scanCode = MapVirtualKey(virtualKey, MAPVK_VK_TO_VSC); sendKeyMessage(m_console.hwnd(), true, virtualKey);
if (scanCode > 255) { sendKeyMessage(m_console.hwnd(), false, virtualKey);
scanCode = 0;
}
SendMessage(m_console.hwnd(), WM_KEYDOWN, virtualKey,
(scanCode << 16) | 1u);
SendMessage(m_console.hwnd(), WM_KEYUP, virtualKey,
(scanCode << 16) | (1u | (1u << 30) | (1u << 31)));
return; return;
} }
@ -636,10 +703,14 @@ void ConsoleInput::appendKeyPress(std::vector<INPUT_RECORD> &records,
stepKeyState |= LEFT_CTRL_PRESSED; stepKeyState |= LEFT_CTRL_PRESSED;
appendInputRecord(records, TRUE, VK_CONTROL, 0, stepKeyState); appendInputRecord(records, TRUE, VK_CONTROL, 0, stepKeyState);
} }
if (alt) { if (leftAlt) {
stepKeyState |= LEFT_ALT_PRESSED; stepKeyState |= LEFT_ALT_PRESSED;
appendInputRecord(records, TRUE, VK_MENU, 0, stepKeyState); appendInputRecord(records, TRUE, VK_MENU, 0, stepKeyState);
} }
if (rightAlt) {
stepKeyState |= RIGHT_ALT_PRESSED;
appendInputRecord(records, TRUE, VK_MENU, 0, stepKeyState | ENHANCED_KEY);
}
if (shift) { if (shift) {
stepKeyState |= SHIFT_PRESSED; stepKeyState |= SHIFT_PRESSED;
appendInputRecord(records, TRUE, VK_SHIFT, 0, stepKeyState); appendInputRecord(records, TRUE, VK_SHIFT, 0, stepKeyState);
@ -648,21 +719,11 @@ void ConsoleInput::appendKeyPress(std::vector<INPUT_RECORD> &records,
stepKeyState |= ENHANCED_KEY; stepKeyState |= ENHANCED_KEY;
} }
if (m_escapeInputEnabled) { if (m_escapeInputEnabled) {
reencodeEscapedKeyPress(records, virtualKey, codePoint, stepKeyState); reencodeEscapedKeyPress(records, virtualKey, vtCodePoint, vtKeyState);
} else { } else {
if (ctrl && alt) { appendCPInputRecords(records, TRUE, virtualKey, winCodePointDn, stepKeyState);
// This behavior seems arbitrary, but it's what I see in the
// Windows 7 console.
codePoint = 0;
} }
appendCPInputRecords(records, TRUE, virtualKey, codePoint, stepKeyState); appendCPInputRecords(records, FALSE, virtualKey, winCodePointUp, stepKeyState);
}
if (alt) {
// This behavior seems arbitrary, but it's what I see in the Windows 7
// console.
codePoint = 0;
}
appendCPInputRecords(records, FALSE, virtualKey, codePoint, stepKeyState);
if (enhanced) { if (enhanced) {
stepKeyState &= ~ENHANCED_KEY; stepKeyState &= ~ENHANCED_KEY;
} }
@ -670,7 +731,11 @@ void ConsoleInput::appendKeyPress(std::vector<INPUT_RECORD> &records,
stepKeyState &= ~SHIFT_PRESSED; stepKeyState &= ~SHIFT_PRESSED;
appendInputRecord(records, FALSE, VK_SHIFT, 0, stepKeyState); appendInputRecord(records, FALSE, VK_SHIFT, 0, stepKeyState);
} }
if (alt) { if (rightAlt) {
stepKeyState &= ~RIGHT_ALT_PRESSED;
appendInputRecord(records, FALSE, VK_MENU, 0, stepKeyState | ENHANCED_KEY);
}
if (leftAlt) {
stepKeyState &= ~LEFT_ALT_PRESSED; stepKeyState &= ~LEFT_ALT_PRESSED;
appendInputRecord(records, FALSE, VK_MENU, 0, stepKeyState); appendInputRecord(records, FALSE, VK_MENU, 0, stepKeyState);
} }

View File

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

View File

@ -60,10 +60,10 @@ void reencodeEscapedKeyPress(
case VK_F10: escapeCode = { EscapedKey::Numeric, {'2', '1'} }; break; case VK_F10: escapeCode = { EscapedKey::Numeric, {'2', '1'} }; break;
case VK_F11: escapeCode = { EscapedKey::Numeric, {'2', '3'} }; break; case VK_F11: escapeCode = { EscapedKey::Numeric, {'2', '3'} }; break;
case VK_F12: escapeCode = { EscapedKey::Numeric, {'2', '4'} }; break; case VK_F12: escapeCode = { EscapedKey::Numeric, {'2', '4'} }; break;
case VK_HOME: escapeCode = { EscapedKey::Numeric, {'1'} }; break; case VK_HOME: escapeCode = { EscapedKey::Letter, {'H'} }; break;
case VK_INSERT: escapeCode = { EscapedKey::Numeric, {'2'} }; break; case VK_INSERT: escapeCode = { EscapedKey::Numeric, {'2'} }; break;
case VK_DELETE: escapeCode = { EscapedKey::Numeric, {'3'} }; break; case VK_DELETE: escapeCode = { EscapedKey::Numeric, {'3'} }; break;
case VK_END: escapeCode = { EscapedKey::Numeric, {'4'} }; break; case VK_END: escapeCode = { EscapedKey::Letter, {'F'} }; break;
case VK_PRIOR: escapeCode = { EscapedKey::Numeric, {'5'} }; break; case VK_PRIOR: escapeCode = { EscapedKey::Numeric, {'5'} }; break;
case VK_NEXT: escapeCode = { EscapedKey::Numeric, {'6'} }; break; case VK_NEXT: escapeCode = { EscapedKey::Numeric, {'6'} }; break;
} }

View File

@ -65,10 +65,11 @@ const int kSuffixBoth = kSuffixCtrl | kSuffixShift;
static const EscapeEncoding escapeLetterEncodings[] = { static const EscapeEncoding escapeLetterEncodings[] = {
// Conventional arrow keys // Conventional arrow keys
{ true, '[', 'A', kBare | kSemiMod, { VK_UP, '\0', 0 } }, // kBareMod: Ubuntu /etc/inputrc and IntelliJ/JediTerm use escapes like: ESC [ n ABCD
{ true, '[', 'B', kBare | kSemiMod, { VK_DOWN, '\0', 0 } }, { true, '[', 'A', kBare | kBareMod | kSemiMod, { VK_UP, '\0', 0 } },
{ true, '[', 'C', kBare | kSemiMod, { VK_RIGHT, '\0', 0 } }, { true, '[', 'B', kBare | kBareMod | kSemiMod, { VK_DOWN, '\0', 0 } },
{ true, '[', 'D', kBare | kSemiMod, { VK_LEFT, '\0', 0 } }, { true, '[', 'C', kBare | kBareMod | kSemiMod, { VK_RIGHT, '\0', 0 } },
{ true, '[', 'D', kBare | kBareMod | kSemiMod, { VK_LEFT, '\0', 0 } },
// putty. putty uses this sequence for Ctrl-Arrow, Shift-Arrow, and // putty. putty uses this sequence for Ctrl-Arrow, Shift-Arrow, and
// Ctrl-Shift-Arrow, but I can only decode to one choice, so I'm just // Ctrl-Shift-Arrow, but I can only decode to one choice, so I'm just
@ -102,8 +103,9 @@ static const EscapeEncoding escapeLetterEncodings[] = {
// Home/End, letter version // Home/End, letter version
// * gnome-terminal uses `ESC O [HF]`. I never saw it modified. // * gnome-terminal uses `ESC O [HF]`. I never saw it modified.
{ true, '[', 'H', kBare | kSemiMod, { VK_HOME, '\0', 0 } }, // kBareMod: IntelliJ/JediTerm uses escapes like: ESC [ n HF
{ true, '[', 'F', kBare | kSemiMod, { VK_END, '\0', 0 } }, { true, '[', 'H', kBare | kBareMod | kSemiMod, { VK_HOME, '\0', 0 } },
{ true, '[', 'F', kBare | kBareMod | kSemiMod, { VK_END, '\0', 0 } },
{ true, 'O', 'H', kBare, { VK_HOME, '\0', 0 } }, { true, 'O', 'H', kBare, { VK_HOME, '\0', 0 } },
{ true, 'O', 'F', kBare, { VK_END, '\0', 0 } }, { true, 'O', 'F', kBare, { VK_END, '\0', 0 } },
@ -235,6 +237,7 @@ static void addSimpleEntries(InputMap &inputMap) {
{ "\x7F", { VK_BACK, '\x08', 0, } }, { "\x7F", { VK_BACK, '\x08', 0, } },
{ ESC "\x7F", { VK_BACK, '\x08', LEFT_ALT_PRESSED, } }, { ESC "\x7F", { VK_BACK, '\x08', LEFT_ALT_PRESSED, } },
{ "\x03", { 'C', '\x03', LEFT_CTRL_PRESSED, } },
// Handle special F1-F5 for TERM=linux and TERM=cygwin. // Handle special F1-F5 for TERM=linux and TERM=cygwin.
{ ESC "[[A", { VK_F1, '\0', 0 } }, { ESC "[[A", { VK_F1, '\0', 0 } },

View File

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

View File

@ -64,7 +64,7 @@ public:
private: private:
void resetConsoleTracking( void resetConsoleTracking(
Terminal::SendClearFlag sendClear, const SmallRect &windowRect); Terminal::SendClearFlag sendClear, int64_t scrapedLineCount);
void markEntireWindowDirty(const SmallRect &windowRect); void markEntireWindowDirty(const SmallRect &windowRect);
void scanForDirtyLines(const SmallRect &windowRect); void scanForDirtyLines(const SmallRect &windowRect);
void clearBufferLines(int firstRow, int count); void clearBufferLines(int firstRow, int count);
@ -73,9 +73,9 @@ private:
ConsoleScreenBufferInfo &finalInfoOut); ConsoleScreenBufferInfo &finalInfoOut);
WORD attributesMask(); WORD attributesMask();
void directScrapeOutput(const ConsoleScreenBufferInfo &info, void directScrapeOutput(const ConsoleScreenBufferInfo &info,
bool cursorVisible); bool consoleCursorVisible);
bool scrollingScrapeOutput(const ConsoleScreenBufferInfo &info, bool scrollingScrapeOutput(const ConsoleScreenBufferInfo &info,
bool cursorVisible, bool consoleCursorVisible,
bool tentative); bool tentative);
void syncMarkerText(CHAR_INFO (&output)[SYNC_MARKER_LEN]); void syncMarkerText(CHAR_INFO (&output)[SYNC_MARKER_LEN]);
int findSyncMarker(); int findSyncMarker();

View File

@ -76,6 +76,14 @@ struct SmallRect : SMALL_RECT
other.Bottom <= Bottom; other.Bottom <= Bottom;
} }
bool contains(const Coord &other) const
{
return other.X >= Left &&
other.X <= Right &&
other.Y >= Top &&
other.Y <= Bottom;
}
SmallRect intersected(const SmallRect &other) const SmallRect intersected(const SmallRect &other) const
{ {
int x1 = std::max(Left, other.Left); int x1 = std::max(Left, other.Left);

View File

@ -218,6 +218,11 @@ WINPTY_API BOOL
winpty_set_size(winpty_t *wp, int cols, int rows, winpty_set_size(winpty_t *wp, int cols, int rows,
winpty_error_ptr_t *err /*OPTIONAL*/); winpty_error_ptr_t *err /*OPTIONAL*/);
/* Gets a list of processes attached to the console. */
WINPTY_API int
winpty_get_console_process_list(winpty_t *wp, int *processList, const int processCount,
winpty_error_ptr_t *err /*OPTIONAL*/);
/* Frees the winpty_t object and the OS resources contained in it. This /* Frees the winpty_t object and the OS resources contained in it. This
* call breaks the connection with the agent, which should then close its * call breaks the connection with the agent, which should then close its
* console, terminating the processes attached to it. * console, terminating the processes attached to it.

View File

@ -935,6 +935,33 @@ winpty_set_size(winpty_t *wp, int cols, int rows,
} API_CATCH(FALSE) } API_CATCH(FALSE)
} }
WINPTY_API int
winpty_get_console_process_list(winpty_t *wp, int *processList, const int processCount,
winpty_error_ptr_t *err /*OPTIONAL*/) {
API_TRY {
ASSERT(wp != nullptr);
ASSERT(processList != nullptr);
LockGuard<Mutex> lock(wp->mutex);
RpcOperation rpc(*wp);
auto packet = newPacket();
packet.putInt32(AgentMsg::GetConsoleProcessList);
writePacket(*wp, packet);
auto reply = readPacket(*wp);
auto actualProcessCount = reply.getInt32();
if (actualProcessCount <= processCount) {
for (auto i = 0; i < actualProcessCount; i++) {
processList[i] = reply.getInt32();
}
}
reply.assertEof();
rpc.success();
return actualProcessCount;
} API_CATCH(0)
}
WINPTY_API void winpty_free(winpty_t *wp) { WINPTY_API void winpty_free(winpty_t *wp) {
// At least in principle, CloseHandle can fail, so this deletion can // At least in principle, CloseHandle can fail, so this deletion can
// fail. It won't throw an exception, but maybe there's an error that // fail. It won't throw an exception, but maybe there's an error that

View File

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

View File

@ -34,7 +34,6 @@
#include <sys/cygwin.h> #include <sys/cygwin.h>
#include <termios.h> #include <termios.h>
#include <unistd.h> #include <unistd.h>
#include <wchar.h>
#include <map> #include <map>
#include <string> #include <string>
@ -199,6 +198,126 @@ static void registerResizeSignalHandler()
sigaction(SIGWINCH, &resizeSigAct, NULL); sigaction(SIGWINCH, &resizeSigAct, NULL);
} }
// Convert the path to a Win32 path if it is a POSIX path, and convert slashes
// to backslashes.
static std::string convertPosixPathToWin(const std::string &path)
{
char *tmp;
#if defined(CYGWIN_VERSION_CYGWIN_CONV) && \
CYGWIN_VERSION_API_MINOR >= CYGWIN_VERSION_CYGWIN_CONV
// MSYS2 and versions of Cygwin released after 2009 or so use this API.
// The original MSYS still lacks this API.
ssize_t newSize = cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE,
path.c_str(), NULL, 0);
assert(newSize >= 0);
tmp = new char[newSize + 1];
ssize_t success = cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE,
path.c_str(), tmp, newSize + 1);
assert(success == 0);
#else
// In the current Cygwin header file, this API is documented as deprecated
// because it's restricted to paths of MAX_PATH length. In the CVS version
// of MSYS, the newer API doesn't exist, and this older API is implemented
// using msys_p2w, which seems like it would handle paths larger than
// MAX_PATH, but there's no way to query how large the new path is.
// Hopefully, this is large enough.
tmp = new char[MAX_PATH + path.size()];
cygwin_conv_to_win32_path(path.c_str(), tmp);
#endif
for (int i = 0; tmp[i] != '\0'; ++i) {
if (tmp[i] == '/')
tmp[i] = '\\';
}
std::string ret(tmp);
delete [] tmp;
return ret;
}
static std::string resolvePath(const std::string &path)
{
char ret[PATH_MAX];
ret[0] = '\0';
if (realpath(path.c_str(), ret) != ret) {
return std::string();
}
return ret;
}
template <size_t N>
static bool endsWith(const std::string &path, const char (&suf)[N])
{
const size_t suffixLen = N - 1;
char actualSuf[N];
if (path.size() < suffixLen) {
return false;
}
strcpy(actualSuf, &path.c_str()[path.size() - suffixLen]);
for (size_t i = 0; i < suffixLen; ++i) {
actualSuf[i] = tolower(actualSuf[i]);
}
return !strcmp(actualSuf, suf);
}
static std::string findProgram(
const char *winptyProgName,
const std::string &prog)
{
std::string candidate;
if (prog.find('/') == std::string::npos &&
prog.find('\\') == std::string::npos) {
// XXX: It would be nice to use a lambda here (once/if old MSYS support
// is dropped).
// Search the PATH.
const char *const pathVar = getenv("PATH");
const std::string pathList(pathVar ? pathVar : "");
size_t elpos = 0;
while (true) {
const size_t elend = pathList.find(':', elpos);
candidate = pathList.substr(elpos, elend - elpos);
if (!candidate.empty() && *(candidate.end() - 1) != '/') {
candidate += '/';
}
candidate += prog;
candidate = resolvePath(candidate);
if (!candidate.empty()) {
int perm = X_OK;
if (endsWith(candidate, ".bat") || endsWith(candidate, ".cmd")) {
#ifdef __MSYS__
// In MSYS/MSYS2, batch files don't have the execute bit
// set, so just check that they're readable.
perm = R_OK;
#endif
} else if (endsWith(candidate, ".com") || endsWith(candidate, ".exe")) {
// Do nothing.
} else {
// Make the exe extension explicit so that we don't try to
// run shell scripts with CreateProcess/winpty_spawn.
candidate += ".exe";
}
if (!access(candidate.c_str(), perm)) {
break;
}
}
if (elend == std::string::npos) {
fprintf(stderr, "%s: error: cannot start '%s': Not found in PATH\n",
winptyProgName, prog.c_str());
exit(1);
} else {
elpos = elend + 1;
}
}
} else {
candidate = resolvePath(prog);
if (candidate.empty()) {
std::string errstr(strerror(errno));
fprintf(stderr, "%s: error: cannot start '%s': %s\n",
winptyProgName, prog.c_str(), errstr.c_str());
exit(1);
}
}
return convertPosixPathToWin(candidate);
}
// Convert argc/argv into a Win32 command-line following the escaping convention // Convert argc/argv into a Win32 command-line following the escaping convention
// documented on MSDN. (e.g. see CommandLineToArgvW documentation) // documented on MSDN. (e.g. see CommandLineToArgvW documentation)
static std::string argvToCommandLine(const std::vector<std::string> &argv) static std::string argvToCommandLine(const std::vector<std::string> &argv)
@ -238,20 +357,6 @@ static std::string argvToCommandLine(const std::vector<std::string> &argv)
return result; return result;
} }
// The original MSYS lacks wcscpy.
static wchar_t *appWcsCpy(wchar_t *dst, const wchar_t *src)
{
memcpy(dst, src, (wcslen(src) + 1) * sizeof(wchar_t));
return dst;
}
// The original MSYS lacks wcscat.
static wchar_t *appWscCat(wchar_t *dst, const wchar_t *src)
{
appWcsCpy(dst + wcslen(dst), src);
return dst;
}
static wchar_t *heapMbsToWcs(const char *text) static wchar_t *heapMbsToWcs(const char *text)
{ {
// Calling mbstowcs with a NULL first argument seems to be broken on MSYS. // Calling mbstowcs with a NULL first argument seems to be broken on MSYS.
@ -477,12 +582,6 @@ int main(int argc, char *argv[])
{ {
setlocale(LC_ALL, ""); setlocale(LC_ALL, "");
if (argc >= 3 && !strcmp(argv[1], "--child-exec")) {
execvp(argv[2], &argv[2]);
perror("error: exec failed");
exit(1);
}
g_mainWakeup = new WakeupFd(); g_mainWakeup = new WakeupFd();
Arguments args; Arguments args;
@ -532,35 +631,14 @@ int main(int argc, char *argv[])
HANDLE childHandle = NULL; HANDLE childHandle = NULL;
{ {
wchar_t selfPath[1024];
{
selfPath[0] = L'\0';
HMODULE selfModule = NULL;
BOOL success = GetModuleHandleExW(
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
(LPCWSTR)(void*)&main, &selfModule);
assert(success && "GetModuleHandleExW failed");
DWORD modPathLen = GetModuleFileNameW(selfModule, selfPath, 1024);
assert(modPathLen > 0 && modPathLen < 1024 && "GetModuleFileNameW failed");
FreeLibrary(selfModule);
}
// Start the child process under the console. // Start the child process under the console.
//args.childArgv[0] = convertPosixPathToWin(args.childArgv[0]); args.childArgv[0] = findProgram(argv[0], args.childArgv[0]);
std::string childCmdLine = argvToCommandLine(args.childArgv); std::string cmdLine = argvToCommandLine(args.childArgv);
wchar_t *const childCmdLineW = heapMbsToWcs(childCmdLine.c_str()); wchar_t *cmdLineW = heapMbsToWcs(cmdLine.c_str());
wchar_t *const cmdLineW = new wchar_t[wcslen(selfPath) + 32 + wcslen(childCmdLineW)];
cmdLineW[0] = L'\0';
appWscCat(cmdLineW, L"\"");
appWscCat(cmdLineW, selfPath);
appWscCat(cmdLineW, L"\" --child-exec ");
appWscCat(cmdLineW, childCmdLineW);
winpty_spawn_config_t *spawnCfg = winpty_spawn_config_new( winpty_spawn_config_t *spawnCfg = winpty_spawn_config_new(
WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN,
NULL, // heapMbsToWcs(args.childArgv[0].c_str()), NULL, cmdLineW, NULL, NULL, NULL);
cmdLineW, NULL, NULL, NULL);
assert(spawnCfg != NULL); assert(spawnCfg != NULL);
winpty_error_ptr_t spawnErr = NULL; winpty_error_ptr_t spawnErr = NULL;
@ -572,19 +650,20 @@ int main(int argc, char *argv[])
if (!spawnRet) { if (!spawnRet) {
winpty_result_t spawnCode = winpty_error_code(spawnErr); winpty_result_t spawnCode = winpty_error_code(spawnErr);
if (spawnCode == WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED) { if (spawnCode == WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED) {
fprintf(stderr, "Could not start '%s': %s\n", fprintf(stderr, "%s: error: cannot start '%s': %s\n",
childCmdLine.c_str(), argv[0],
cmdLine.c_str(),
formatErrorMessage(lastError).c_str()); formatErrorMessage(lastError).c_str());
} else { } else {
fprintf(stderr, "Could not start '%s': internal error: %s\n", fprintf(stderr, "%s: error: cannot start '%s': internal error: %s\n",
childCmdLine.c_str(), argv[0],
cmdLine.c_str(),
wcsToMbs(winpty_error_msg(spawnErr)).c_str()); wcsToMbs(winpty_error_msg(spawnErr)).c_str());
} }
exit(1); exit(1);
} }
winpty_error_free(spawnErr); winpty_error_free(spawnErr);
delete [] cmdLineW; delete [] cmdLineW;
delete [] childCmdLineW;
} }
registerResizeSignalHandler(); registerResizeSignalHandler();