Build
* cross build switches to pkgsCross.ucrt64 (shell-win.nix); curl (Schannel),
winpthreads and ncurses 6.6 are linked statically, so the release is
cst-agent.exe + busybox.exe (busybox-w32 Unicode) with no extra DLLs
* windres embeds a manifest with activeCodePage=UTF-8 and a version resource
* ncurses 6.6 Windows console driver patches (mouse button state, A_DIM,
wide-char declarations): scripts/patches/
* scripts/bootstrap_msys2.sh builds the same release natively in MSYS2
UCRT64; PR #1's MINGW64 notes now point there
* vendor/tre-regex (musl's TRE) provides <regex.h> on MinGW
UTF-8
* paths cross the system call boundary as UTF-16 (*W APIs); console code
pages set to 65001 and restored on exit; files are read and written in
binary mode
* child output that is not valid UTF-8 is decoded from the console, OEM or
ANSI system code page
* read_file hides CR; edit_file, apply_patch and write_file keep CRLF line
endings and a UTF-8 BOM
OS layer and processes
* os_dir_* and cst_fnmatch replace the POSIX-only directory walks in
subagent, plugins, skills, undo and fs_tools; realpath / path_under and
symlink rejection work on Windows
* every child goes through CreateProcessW with its own environment block
inside a Job Object, so timeouts and Esc end the whole process tree
Sandbox (auto mode, "auto · lowil")
* run_shell runs with a restricted Low integrity token; the workspace gets
an inheritable Low label once, the sandbox TEMP is labeled Low, and the
credentials file gets a no-read-up label
* drive roots, the profile and system folders are never labeled
* network commands still ask, since Low IL does not block the network;
Access denied and MSYS2's 0xc0000142 offer a rerun outside the sandbox
* /sandbox lists labeled folders, /sandbox reset-labels restores them
TUI
* raster console fonts are switched to Consolas / NSimSun while running
* the home logo falls back to text when the console font lacks U+2580
* mouse drag copy goes to the Windows clipboard
Distribution
* scripts/install_windows.ps1 installs a Gitea release into
%LOCALAPPDATA%\Programs\cst-agent and adds it to the user PATH;
cst-agent-update.cmd reruns it; scripts/release_windows.sh uploads the
zip with the token taken from the environment
* scripts/run_windows.bat is gone; docs/README-windows.md is rewritten
Tests
* new test_lowil, test_win_utf8 and test_os_dir; tests made portable
(cst_test_util.h)
* scripts/win_test.sh and scripts/winvm/ run the suite on English and
Chinese Windows Server VMs as Administrator, a standard user and a
Chinese user name: 29/29 on all six
351 lines
9.9 KiB
Diff
351 lines
9.9 KiB
Diff
--- a/ncurses/curses.priv.h
|
|
+++ b/ncurses/curses.priv.h
|
|
@@ -2294,7 +2294,10 @@
|
|
#if defined(_NC_WINDOWS_NATIVE) && !defined(_NC_MSC) && !USE_NAMED_PIPES
|
|
/*
|
|
* MinGW has wide-character functions, but they do not work correctly.
|
|
+ * Declare the originals first so a later <wchar.h> is not macro-expanded.
|
|
*/
|
|
+#include <stdlib.h>
|
|
+#include <wchar.h>
|
|
extern int __MINGW_NOTHROW _nc_wctomb(char *, wchar_t);
|
|
#define wctomb(s,wc) _nc_wctomb(s,wc)
|
|
#define wcrtomb(s,wc,n) _nc_wctomb(s,wc)
|
|
--- a/ncurses/tinfo/lib_win32con.c
|
|
+++ b/ncurses/tinfo/lib_win32con.c
|
|
@@ -653,6 +653,21 @@
|
|
return result;
|
|
}
|
|
|
|
+/* Whether handle_mouse() turns this record into an event: wheel, a button
|
|
+ * change (press or release), or a drag. */
|
|
+static bool
|
|
+mouse_wanted(const SCREEN *sp, const MOUSE_EVENT_RECORD *mer)
|
|
+{
|
|
+ int held = (int) (mer->dwButtonState & BUTTON_MASK);
|
|
+
|
|
+ if (mer->dwEventFlags & MOUSE_WHEELED)
|
|
+ return TRUE;
|
|
+ if (held != sp->_drv_mouse_new_buttons)
|
|
+ return TRUE;
|
|
+ return (mer->dwEventFlags & MOUSE_MOVED) && held
|
|
+ && (sp->_mouse_mask & REPORT_MOUSE_POSITION);
|
|
+}
|
|
+
|
|
#define AdjustY() (WINCONSOLE.buffered \
|
|
? 0 \
|
|
: (int) WINCONSOLE.SBI.srWindow.Top)
|
|
@@ -661,36 +676,47 @@
|
|
handle_mouse(SCREEN *sp, MOUSE_EVENT_RECORD mer)
|
|
{
|
|
MEVENT work;
|
|
- bool result = FALSE;
|
|
+ int old_buttons;
|
|
+ int new_buttons;
|
|
|
|
assert(sp);
|
|
|
|
- sp->_drv_mouse_old_buttons = sp->_drv_mouse_new_buttons;
|
|
- sp->_drv_mouse_new_buttons = mer.dwButtonState & BUTTON_MASK;
|
|
+ if (sp->_drv_mouse_tail >= FIFO_SIZE)
|
|
+ return FALSE;
|
|
|
|
- /*
|
|
- * We're only interested if the button is pressed or released.
|
|
- * FIXME: implement continuous event-tracking.
|
|
- */
|
|
- if (sp->_drv_mouse_new_buttons != sp->_drv_mouse_old_buttons) {
|
|
- memset(&work, 0, sizeof(work));
|
|
+ memset(&work, 0, sizeof(work));
|
|
+ work.x = mer.dwMousePosition.X;
|
|
+ work.y = mer.dwMousePosition.Y - AdjustY();
|
|
+
|
|
+ /* the wheel reports like xterm: button 4 up, button 5 down */
|
|
+ if (mer.dwEventFlags & MOUSE_WHEELED) {
|
|
+ work.bstate = ((SHORT) HIWORD(mer.dwButtonState) > 0)
|
|
+ ? BUTTON4_PRESSED
|
|
+ : BUTTON5_PRESSED;
|
|
+ sp->_drv_mouse_fifo[sp->_drv_mouse_tail++] = work;
|
|
+ return TRUE;
|
|
+ }
|
|
|
|
- if (sp->_drv_mouse_new_buttons) {
|
|
- work.bstate |= decode_mouse(sp, sp->_drv_mouse_new_buttons);
|
|
+ old_buttons = sp->_drv_mouse_new_buttons;
|
|
+ new_buttons = (int) (mer.dwButtonState & BUTTON_MASK);
|
|
+ sp->_drv_mouse_old_buttons = old_buttons;
|
|
+ sp->_drv_mouse_new_buttons = new_buttons;
|
|
+
|
|
+ if (new_buttons != old_buttons) {
|
|
+ if (new_buttons & ~old_buttons) {
|
|
+ work.bstate = decode_mouse(sp, new_buttons & ~old_buttons);
|
|
} else {
|
|
/* cf: BUTTON_PRESSED, BUTTON_RELEASED */
|
|
- work.bstate |= (decode_mouse(sp, sp->_drv_mouse_old_buttons)
|
|
- >> 1);
|
|
- result = TRUE;
|
|
+ work.bstate = decode_mouse(sp, old_buttons & ~new_buttons) >> 1;
|
|
}
|
|
-
|
|
- work.x = mer.dwMousePosition.X;
|
|
- work.y = mer.dwMousePosition.Y - AdjustY();
|
|
-
|
|
- sp->_drv_mouse_fifo[sp->_drv_mouse_tail] = work;
|
|
- sp->_drv_mouse_tail += 1;
|
|
+ } else if ((mer.dwEventFlags & MOUSE_MOVED) && new_buttons
|
|
+ && (sp->_mouse_mask & REPORT_MOUSE_POSITION)) {
|
|
+ work.bstate = REPORT_MOUSE_POSITION; /* drag */
|
|
+ } else {
|
|
+ return FALSE;
|
|
}
|
|
- return result;
|
|
+ sp->_drv_mouse_fifo[sp->_drv_mouse_tail++] = work;
|
|
+ return TRUE;
|
|
}
|
|
|
|
static int
|
|
@@ -813,6 +839,13 @@
|
|
returnCode(found);
|
|
}
|
|
|
|
+#if USE_WIDEC_SUPPORT
|
|
+/* UTF-8 bytes of the last non-ASCII key event not yet read */
|
|
+static int utf8_pend[4];
|
|
+static int utf8_len, utf8_pos;
|
|
+static unsigned utf8_high;
|
|
+#endif
|
|
+
|
|
NCURSES_EXPORT(int)
|
|
_nc_console_twait(
|
|
const SCREEN *sp,
|
|
@@ -845,6 +878,14 @@
|
|
TR(TRACE_IEVENT, ("start twait: hdl=%p, %d milliseconds, mode: %d",
|
|
hdl, milliseconds, mode));
|
|
|
|
+#if USE_WIDEC_SUPPORT
|
|
+ if ((mode & TW_INPUT) && utf8_pos < utf8_len) {
|
|
+ if (timeleft)
|
|
+ *timeleft = milliseconds;
|
|
+ return TW_INPUT;
|
|
+ }
|
|
+#endif
|
|
+
|
|
if (milliseconds < 0)
|
|
milliseconds = NC_INFINITY;
|
|
|
|
@@ -940,9 +981,7 @@
|
|
continue;
|
|
case MOUSE_EVENT:
|
|
T(("twait:event MOUSE_EVENT"));
|
|
- if (decode_mouse(sp,
|
|
- (inp_rec.Event.MouseEvent.dwButtonState
|
|
- & BUTTON_MASK)) == 0) {
|
|
+ if (!mouse_wanted(sp, &inp_rec.Event.MouseEvent)) {
|
|
CONSUME();
|
|
} else if (mode & TW_MOUSE) {
|
|
code = TW_MOUSE;
|
|
@@ -1024,6 +1063,16 @@
|
|
|
|
T((T_CALLED("lib_win32con::_nc_console_read(%p)"), sp));
|
|
|
|
+#if USE_WIDEC_SUPPORT
|
|
+ /* wget_wch() decodes the input as a byte stream in the locale's
|
|
+ * multibyte encoding (UTF-8), so non-ASCII UTF-16 key events are
|
|
+ * handed over one UTF-8 byte per call. */
|
|
+ if (utf8_pos < utf8_len) {
|
|
+ *buf = utf8_pend[utf8_pos++];
|
|
+ returnCode(1);
|
|
+ }
|
|
+#endif
|
|
+
|
|
while ((b = read_keycode(hdl, &inp_rec, 1, &nRead))) {
|
|
if (b && nRead > 0) {
|
|
if (rc < 0)
|
|
@@ -1034,6 +1083,36 @@
|
|
continue;
|
|
*buf = (int) inp_rec.Event.KeyEventChar;
|
|
vk = inp_rec.Event.KeyEvent.wVirtualKeyCode;
|
|
+#if USE_WIDEC_SUPPORT
|
|
+ if (*buf >= 0x80) {
|
|
+ unsigned cp = (unsigned) *buf;
|
|
+ if (cp >= 0xD800 && cp < 0xDC00) {
|
|
+ utf8_high = cp;
|
|
+ continue;
|
|
+ }
|
|
+ if (cp >= 0xDC00 && cp < 0xE000) {
|
|
+ if (!utf8_high)
|
|
+ continue;
|
|
+ cp = 0x10000 + ((utf8_high - 0xD800) << 10) + (cp - 0xDC00);
|
|
+ }
|
|
+ utf8_high = 0;
|
|
+ utf8_len = 0;
|
|
+ if (cp < 0x800) {
|
|
+ utf8_pend[utf8_len++] = (int) (0xC0 | (cp >> 6));
|
|
+ } else if (cp < 0x10000) {
|
|
+ utf8_pend[utf8_len++] = (int) (0xE0 | (cp >> 12));
|
|
+ utf8_pend[utf8_len++] = (int) (0x80 | ((cp >> 6) & 0x3F));
|
|
+ } else {
|
|
+ utf8_pend[utf8_len++] = (int) (0xF0 | (cp >> 18));
|
|
+ utf8_pend[utf8_len++] = (int) (0x80 | ((cp >> 12) & 0x3F));
|
|
+ utf8_pend[utf8_len++] = (int) (0x80 | ((cp >> 6) & 0x3F));
|
|
+ }
|
|
+ utf8_pend[utf8_len++] = (int) (0x80 | (cp & 0x3F));
|
|
+ *buf = utf8_pend[0];
|
|
+ utf8_pos = 1;
|
|
+ break;
|
|
+ }
|
|
+#endif
|
|
/*
|
|
* There are 24 virtual function-keys, and typically
|
|
* 12 function-keys on a keyboard. Use the shift-modifier
|
|
--- a/ncurses/widechar/widechars.c
|
|
+++ b/ncurses/widechar/widechars.c
|
|
@@ -97,20 +97,10 @@
|
|
wchar_t temp;
|
|
|
|
if (s != NULL && n != 0) {
|
|
+ /* _nc_mbtowc() returns the bytes used, not a character count */
|
|
count = _nc_mbtowc(&temp, s, n);
|
|
- if (count == 1) {
|
|
- int check = WideCharToMultiByte(CP_UTF8,
|
|
- 0,
|
|
- &temp,
|
|
- 1,
|
|
- NULL,
|
|
- 0, /* compute length only */
|
|
- NULL,
|
|
- NULL);
|
|
- TR(TRACE_BITS, ("\tcheck ->%d\n", check));
|
|
- if (check > 0 && (size_t) check <= n) {
|
|
- result = check;
|
|
- }
|
|
+ if (count > 0 && (size_t) count <= n) {
|
|
+ result = count;
|
|
}
|
|
} else {
|
|
result = 0;
|
|
--- a/ncurses/win32con/win32_driver.c
|
|
+++ b/ncurses/win32con/win32_driver.c
|
|
@@ -87,8 +87,13 @@
|
|
if (ch & A_BOLD)
|
|
res |= FOREGROUND_INTENSITY;
|
|
|
|
- if (ch & A_DIM)
|
|
- res |= BACKGROUND_INTENSITY;
|
|
+ /* dim lowers the foreground: bright -> normal, light gray -> dark gray */
|
|
+ if ((ch & A_DIM) && !(ch & A_BOLD)) {
|
|
+ if (res & FOREGROUND_INTENSITY)
|
|
+ res &= ~FOREGROUND_INTENSITY;
|
|
+ else if ((res & 7) == 7)
|
|
+ res = (res & ~7) | FOREGROUND_INTENSITY;
|
|
+ }
|
|
|
|
return res;
|
|
}
|
|
@@ -159,7 +164,6 @@
|
|
int y, int x, cchar_t *str, int limit)
|
|
{
|
|
int actual = 0;
|
|
- MakeArray(ci, CHAR_INFO, limit);
|
|
COORD loc, siz;
|
|
SMALL_RECT rec;
|
|
int i;
|
|
@@ -169,39 +173,71 @@
|
|
AssertTCB();
|
|
SetSP();
|
|
|
|
- for (i = actual = 0; i < limit; i++) {
|
|
- ch = str[i];
|
|
- if (isWidecExt(ch))
|
|
- continue;
|
|
- ci[actual].CharInfoChar = CharOf(ch);
|
|
- ci[actual].Attributes = MapAttr(WINCONSOLE.SBI.wAttributes,
|
|
- AttrOf(ch));
|
|
- if (AttrOf(ch) & A_ALTCHARSET) {
|
|
- if (_nc_wacs) {
|
|
- int which = CharOf(ch);
|
|
- if (which > 0
|
|
- && which < ACS_LEN
|
|
- && CharOf(_nc_wacs[which]) != 0) {
|
|
- ci[actual].CharInfoChar = CharOf(_nc_wacs[which]);
|
|
+ /*
|
|
+ * The console stores a double-width character as two cells flagged
|
|
+ * leading/trailing byte. Never start or end a write inside one, and
|
|
+ * emit both halves so the columns after it stay put.
|
|
+ */
|
|
+ if (limit > 0 && x > 0 && isWidecExt(str[0])) {
|
|
+ --str;
|
|
+ --x;
|
|
+ ++limit;
|
|
+ }
|
|
+ if (limit > 0 && x + limit < screen_columns(sp) && isWidecExt(str[limit]))
|
|
+ ++limit;
|
|
+
|
|
+ {
|
|
+ MakeArray(ci, CHAR_INFO, limit);
|
|
+
|
|
+ for (i = actual = 0; i < limit; i++) {
|
|
+ ch = str[i];
|
|
+ if (isWidecExt(ch)) {
|
|
+ if (actual > 0
|
|
+ && (ci[actual - 1].Attributes & COMMON_LVB_LEADING_BYTE)) {
|
|
+ ci[actual] = ci[actual - 1];
|
|
+ ci[actual].Attributes = (WORD)
|
|
+ ((ci[actual].Attributes & ~COMMON_LVB_LEADING_BYTE)
|
|
+ | COMMON_LVB_TRAILING_BYTE);
|
|
} else {
|
|
ci[actual].CharInfoChar = ' ';
|
|
+ ci[actual].Attributes = MapAttr(WINCONSOLE.SBI.wAttributes,
|
|
+ AttrOf(ch));
|
|
}
|
|
+ ++actual;
|
|
+ continue;
|
|
}
|
|
+ ci[actual].CharInfoChar = CharOf(ch);
|
|
+ ci[actual].Attributes = MapAttr(WINCONSOLE.SBI.wAttributes,
|
|
+ AttrOf(ch));
|
|
+ if (AttrOf(ch) & A_ALTCHARSET) {
|
|
+ if (_nc_wacs) {
|
|
+ int which = CharOf(ch);
|
|
+ if (which > 0
|
|
+ && which < ACS_LEN
|
|
+ && CharOf(_nc_wacs[which]) != 0) {
|
|
+ ci[actual].CharInfoChar = CharOf(_nc_wacs[which]);
|
|
+ } else {
|
|
+ ci[actual].CharInfoChar = ' ';
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ if (i + 1 < limit && isWidecExt(str[i + 1]))
|
|
+ ci[actual].Attributes |= COMMON_LVB_LEADING_BYTE;
|
|
+ ++actual;
|
|
}
|
|
- ++actual;
|
|
- }
|
|
|
|
- loc.X = (SHORT) 0;
|
|
- loc.Y = (SHORT) 0;
|
|
- siz.X = (SHORT) actual;
|
|
- siz.Y = 1;
|
|
-
|
|
- rec.Left = (SHORT) x;
|
|
- rec.Top = (SHORT) (y + AdjustY());
|
|
- rec.Right = (SHORT) (x + limit - 1);
|
|
- rec.Bottom = rec.Top;
|
|
+ loc.X = (SHORT) 0;
|
|
+ loc.Y = (SHORT) 0;
|
|
+ siz.X = (SHORT) actual;
|
|
+ siz.Y = 1;
|
|
+
|
|
+ rec.Left = (SHORT) x;
|
|
+ rec.Top = (SHORT) (y + AdjustY());
|
|
+ rec.Right = (SHORT) (x + actual - 1);
|
|
+ rec.Bottom = rec.Top;
|
|
|
|
- return write_screen(WINCONSOLE.hdl, ci, siz, loc, &rec);
|
|
+ return write_screen(WINCONSOLE.hdl, ci, siz, loc, &rec);
|
|
+ }
|
|
}
|
|
#define con_write(tcb, y, x, str, n) con_write16(tcb, y, x, str, n)
|
|
#else
|