diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index f063ed7596..1b0f799f7d 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -10,6 +10,7 @@ on: env: AUTOHOTKEY_VERSION: 2.0.19 WT_VERSION: 1.22.11141.0 + WIN32_OPENSSH_VERSION: 9.8.3.0p2-Preview jobs: ui-tests: @@ -76,6 +77,27 @@ jobs: "$WINDIR/system32/tar.exe" -C "$RUNNER_TEMP/ahk" -xf "$RUNNER_TEMP/ahk.zip" && cygpath -aw "$RUNNER_TEMP/ahk" >>$GITHUB_PATH - uses: actions/setup-node@v4 # the hook uses node for the background process + - uses: actions/cache/restore@v4 + id: restore-win32-openssh + with: + key: win32-openssh-${{ env.WIN32_OPENSSH_VERSION }} + path: ${{ runner.temp }}/win32-openssh.zip + - name: Download Win32-OpenSSH + if: steps.restore-win32-openssh.outputs.cache-hit != 'true' + shell: bash + run: | + curl -fLo "$RUNNER_TEMP/win32-openssh.zip" \ + https://github.com/PowerShell/Win32-OpenSSH/releases/download/v$WIN32_OPENSSH_VERSION/OpenSSH-Win64.zip + - uses: actions/cache/save@v4 + if: steps.restore-win32-openssh.outputs.cache-hit != 'true' + with: + key: win32-openssh-${{ env.WIN32_OPENSSH_VERSION }} + path: ${{ runner.temp }}/win32-openssh.zip + - name: Unpack Win32-OpenSSH + shell: bash + run: | + "$WINDIR/system32/tar.exe" -C "$RUNNER_TEMP" -xvf "$RUNNER_TEMP/win32-openssh.zip" && + echo "OPENSSH_FOR_WINDOWS_DIRECTORY=$(cygpath -aw "$RUNNER_TEMP/OpenSSH-Win64")" >>$GITHUB_ENV - uses: actions/checkout@v4 with: @@ -83,12 +105,84 @@ jobs: ui-tests - name: Run UI tests id: ui-tests + timeout-minutes: 10 + working-directory: ui-tests run: | - $p = Start-Process -PassThru -FilePath "${env:RUNNER_TEMP}\ahk\AutoHotKey64.exe" -ArgumentList ui-tests\background-hook.ahk, "$PWD\bg-hook" - $p.WaitForExit() - if ($p.ExitCode -ne 0) { echo "::error::Test failed!" } else { echo "::notice::Test log" } + $exitCode = 0 + & "${env:RUNNER_TEMP}\ahk\AutoHotKey64.exe" /ErrorStdOut /force background-hook.ahk "$PWD\bg-hook" 2>&1 | Out-Default + if (!$?) { $exitCode = 1; echo "::error::Test failed!" } else { echo "::notice::Test log" } type bg-hook.log - if ($p.ExitCode -ne 0) { exit 1 } - - name: Show logs, if canceled - if: cancelled() - run: type bg-hook.log + $env:LARGE_FILES_DIRECTORY = "${env:RUNNER_TEMP}\large" + & "${env:RUNNER_TEMP}\ahk\AutoHotKey64.exe" /ErrorStdOut /force ctrl-c.ahk "$PWD\ctrl-c" 2>&1 | Out-Default + if (!$?) { $exitCode = 1; echo "::error::Ctrl+C Test failed!" } else { echo "::notice::Ctrl+C Test log" } + type ctrl-c.log + exit $exitCode + - name: Show logs + if: always() + working-directory: ui-tests + run: | + type bg-hook.log + type ctrl-c.log + - name: Take screenshot, if canceled + id: take-screenshot + if: cancelled() || failure() + shell: powershell + run: | + Add-Type -TypeDefinition @" + using System; + using System.Runtime.InteropServices; + + public class DpiHelper { + [DllImport("user32.dll")] + public static extern bool SetProcessDpiAwarenessContext(IntPtr dpiContext); + + [DllImport("Shcore.dll")] + public static extern int GetDpiForMonitor(IntPtr hmonitor, int dpiType, out uint dpiX, out uint dpiY); + + [DllImport("User32.dll")] + public static extern IntPtr MonitorFromPoint(System.Drawing.Point pt, uint dwFlags); + + [DllImport("user32.dll")] + public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + + public static uint GetDPI() { + // DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4 + SetProcessDpiAwarenessContext((IntPtr)(-4)); + + uint dpiX, dpiY; + IntPtr monitor = MonitorFromPoint(new System.Drawing.Point(0, 0), 2); // MONITOR_DEFAULTTONEAREST + GetDpiForMonitor(monitor, 0, out dpiX, out dpiY); // MDT_EFFECTIVE_DPI + return (dpiX + dpiY) / 2; + } + } + "@ -ReferencedAssemblies "System.Drawing.dll" + + # First, minimize the Console window in which this script is running + $hwnd = (Get-Process -Id $PID).MainWindowHandle + $SW_MINIMIZE = 6 + + [DpiHelper]::ShowWindow($hwnd, $SW_MINIMIZE) + + # Now, get the DPI + $dpi = [DpiHelper]::GetDPI() + + # This function takes a screenshot and saves it as a PNG file + [Reflection.Assembly]::LoadWithPartialName("System.Drawing") + function screenshot([Drawing.Rectangle]$bounds, $path) { + $bmp = New-Object Drawing.Bitmap $bounds.width, $bounds.height + $graphics = [Drawing.Graphics]::FromImage($bmp) + $graphics.CopyFromScreen($bounds.Location, [Drawing.Point]::Empty, $bounds.size) + $bmp.Save($path) + $graphics.Dispose() + $bmp.Dispose() + } + Add-Type -AssemblyName System.Windows.Forms + $screen = [System.Windows.Forms.Screen]::PrimaryScreen + $bounds = [Drawing.Rectangle]::FromLTRB(0, 0, $screen.Bounds.Width * $dpi / 96, $screen.Bounds.Height * $dpi / 96) + screenshot $bounds "ui-tests/screenshot.png" + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: ui-tests-${{ matrix.os }} + path: ui-tests diff --git a/ui-tests/.gitattributes b/ui-tests/.gitattributes new file mode 100644 index 0000000000..4dd1b9375b --- /dev/null +++ b/ui-tests/.gitattributes @@ -0,0 +1 @@ +*.ahk eol=lf diff --git a/ui-tests/background-hook.ahk b/ui-tests/background-hook.ahk index 43f723b56d..af7c27d313 100755 --- a/ui-tests/background-hook.ahk +++ b/ui-tests/background-hook.ahk @@ -1,129 +1,54 @@ -#Requires AutoHotkey v2.0 - -; This script is an integration test for the following scenario: -; A Git hook spawns a background process that outputs some text -; to the console even after Git has exited. - -; At some point in time, the Cygwin/MSYS2 runtime left the console -; in a state where it was not possible to navigate the history via -; CursorUp/Down, as reported in https://github.com/microsoft/git/issues/730. -; This was fixed in the Cygwin/MSYS2 runtime, but then regressed again. -; This test is meant to verify that the issue is fixed and remains so. - -; First, set the worktree path; This path will be reused -; for the `.log` file). -if A_Args.Length > 0 - workTree := A_Args[1] -else -{ - ; Create a unique worktree path in the TEMP directory. - workTree := EnvGet('TEMP') . '\git-test-background-hook' - if FileExist(workTree) - { - counter := 0 - while FileExist(workTree '-' counter) - counter++ - workTree := workTree '-' counter - } -} - -Info(text) { - FileAppend text '`n', workTree '.log' -} - -closeWindow := false -childPid := 0 -ExitWithError(error) { - Info 'Error: ' error - if closeWindow - WinClose "A" - else if childPid != 0 - ProcessClose childPid - ExitApp 1 -} - -RunWaitOne(command) { - shell := ComObject("WScript.Shell") - ; Execute a single command via cmd.exe - exec := shell.Exec(A_ComSpec " /C " command) - ; Read and return the command's output - return exec.StdOut.ReadAll() -} - -SetWorkingDir(EnvGet('TEMP')) -Info 'uname: ' RunWaitOne('uname -a') -Info RunWaitOne('git version --build-options') - -RunWait('git init "' workTree '"', '', 'Hide') -if A_LastError - ExitWithError 'Could not initialize Git worktree at: ' workTree - -SetWorkingDir(workTree) -if A_LastError - ExitWithError 'Could not set working directory to: ' workTree - -if not FileExist('.git/hooks') and not DirCreate('.git/hooks') - ExitWithError 'Could not create hooks directory: ' workTree - -FileAppend("#!/bin/sh`npowershell -command 'for ($i = 0; $i -lt 50; $i++) { echo $i; sleep -milliseconds 10 }' &`n", '.git/hooks/pre-commit') -if A_LastError - ExitWithError 'Could not create pre-commit hook: ' A_LastError - -Run 'wt.exe -d . ' A_ComSpec ' /d', , , &childPid -if A_LastError - ExitWithError 'Error launching CMD: ' A_LastError -Info 'Launched CMD: ' childPid -if not WinWait(A_ComSpec, , 9) - ExitWithError 'CMD window did not appear' -Info 'Got window' -WinActivate -CloseWindow := true -WinMove 0, 0 -Info 'Moved window to top left (so that the bottom is not cut off)' - -CaptureText() { - ControlGetPos &cx, &cy, &cw, &ch, 'Windows.UI.Composition.DesktopWindowContentBridge1', "A" - titleBarHeight := 54 - scrollBarWidth := 28 - pad := 8 - - SavedClipboard := ClipboardAll - A_Clipboard := '' - SendMode('Event') - MouseMove cx + pad, cy + titleBarHeight + pad - MouseClickDrag 'Left', , , cx + cw - scrollBarWidth, cy + ch - pad, , '' - MouseClick 'Right' - ClipWait() - Result := A_Clipboard - Clipboard := SavedClipboard - return Result -} - -Info('Setting committer identity') -Send('git config user.name Test{Enter}git config user.email t@e.st{Enter}') - -Info('Committing') -Send('git commit --allow-empty -m zOMG{Enter}') -; Wait for the hook to finish printing -While not RegExMatch(CaptureText(), '`n49$') -{ - Sleep 100 - if A_Index > 1000 - ExitWithError 'Timed out waiting for commit to finish' - MouseClick 'WheelDown', , , 20 -} -Info('Hook finished') - -; Verify that CursorUp shows the previous command -Send('{Up}') -Sleep 150 -Text := CaptureText() -if not RegExMatch(Text, 'git commit --allow-empty -m zOMG *$') - ExitWithError 'Cursor Up did not work: ' Text -Info('Match!') - -Send('^C') -Send('exit{Enter}') -Sleep 50 -SetWorkingDir(EnvGet('TEMP')) -DirDelete(workTree, true) \ No newline at end of file +#Requires AutoHotkey v2.0 +#Include ui-test-library.ahk + +; This script is an integration test for the following scenario: +; A Git hook spawns a background process that outputs some text +; to the console even after Git has exited. + +; At some point in time, the Cygwin/MSYS2 runtime left the console +; in a state where it was not possible to navigate the history via +; CursorUp/Down, as reported in https://github.com/microsoft/git/issues/730. +; This was fixed in the Cygwin/MSYS2 runtime, but then regressed again. +; This test is meant to verify that the issue is fixed and remains so. + +SetWorkTree('git-test-background-hook') + +if not FileExist('.git/hooks') and not DirCreate('.git/hooks') + ExitWithError 'Could not create hooks directory: ' workTree + +FileAppend("#!/bin/sh`npowershell -command 'for ($i = 0; $i -lt 50; $i++) { echo $i; sleep -milliseconds 10 }' &`n", '.git/hooks/pre-commit') +if A_LastError + ExitWithError 'Could not create pre-commit hook: ' A_LastError + +Run 'wt.exe -d . ' A_ComSpec ' /d', , , &childPid +if A_LastError + ExitWithError 'Error launching CMD: ' A_LastError +Info 'Launched CMD: ' childPid +if not WinWait(A_ComSpec, , 9) + ExitWithError 'CMD window did not appear' +Info 'Got window' +WinActivate +CloseWindow := true +WinMove 0, 0 +Info 'Moved window to top left (so that the bottom is not cut off)' + +Info('Setting committer identity') +Send('git config user.name Test{Enter}git config user.email t@e.st{Enter}') + +Info('Committing') +Send('git commit --allow-empty -m zOMG{Enter}') +; Wait for the hook to finish printing +WaitForRegExInWindowsTerminal('`n49$', 'Timed out waiting for commit to finish', 'Hook finished', 100000) + +; Verify that CursorUp shows the previous command +Send('{Up}') +Sleep 150 +Text := CaptureTextFromWindowsTerminal() +if not RegExMatch(Text, 'git commit --allow-empty -m zOMG *$') + ExitWithError 'Cursor Up did not work: ' Text +Info('Match!') + +Send('^C') +Send('exit{Enter}') +Sleep 50 +CleanUpWorkTree() \ No newline at end of file diff --git a/ui-tests/ctrl-c.ahk b/ui-tests/ctrl-c.ahk new file mode 100644 index 0000000000..3ca8873de7 --- /dev/null +++ b/ui-tests/ctrl-c.ahk @@ -0,0 +1,166 @@ +#Requires AutoHotkey v2.0 +#Include ui-test-library.ahk + +SetWorkTree('git-test-ctrl-c') + +powerShellPath := EnvGet('SystemRoot') . '\System32\WindowsPowerShell\v1.0\powershell.exe' +Run 'wt.exe -d . "' powerShellPath '"', , , &childPid +if A_LastError + ExitWithError 'Error launching PowerShell: ' A_LastError +Info 'Launched PowerShell: ' childPid +; Sadly, `WinWait('ahk_pid ' childPid)` does not work because the Windows Terminal window seems +; to be owned by the `wt.exe` process that launched. +; +; Probably should use the trick mentioned in +; https://www.autohotkey.com/boards/viewtopic.php?p=580081&sid=a40d0ce73efff728ffa6b4573dff07b9#p580081 +; where the `before` variable is assigned `WinGetList(winTitle).Length` before the `Run` command, +; and a `Loop` is used to wait until [`WinGetList()`](https://www.autohotkey.com/docs/v2/lib/WinGetList.htm) +; returns a different length, in which case the first array element is the new window. +; +; Also: This is crying out loud to be refactored into a function and then also used in `background-hook.ahk`! +hwnd := WinWait(powerShellPath, , 9) +if not hwnd + ExitWithError 'PowerShell window did not appear' +Info 'Got window' +WinActivate +CloseWindow := true +WinMove 0, 0 +Info 'Moved window to top left (so that the bottom is not cut off)' + +; sleep test +Sleep 1500 +; The `:;` is needed to force Git to call this via the shell, otherwise `/usr/bin/` would not resolve. +Send('git -c alias.sleep="{!}:;/usr/bin/sleep" sleep 15{Enter}') +Sleep 500 +; interrupt sleep; Ideally we'd call `Send('^C')` but that would too quick on GitHub Actions' runners. +; The idea for this work-around comes from https://www.reddit.com/r/AutoHotkey/comments/aok10s/comment/eg57e81/. +Send '{Ctrl down}{c down}' +Sleep 50 +Send '{c up}{Ctrl up}' +Sleep 150 +; Wait for the `^C` tell-tale that is the PowerShell prompt to appear +WaitForRegExInWindowsTerminal('>[ `n`r]*$', 'Timed out waiting for interrupt', 'Sleep was interrupted as desired') + +; ping test (`cat.exe` should be interrupted, too) +Send('git -c alias.c="{!}cat | /c/windows/system32/ping -t localhost" c{Enter}') +Sleep 500 +WaitForRegExInWindowsTerminal('Pinging ', 'Timed out waiting for pinging to start', 'Pinging started') +Send('^C') ; interrupt ping and cat +Sleep 150 +; Wait for the `^C` tell-tale to appear +WaitForRegExInWindowsTerminal('Control-C', 'Timed out waiting for pinging to be interrupted', 'Pinging was interrupted as desired') +; Wait for the `^C` tell-tale that is the PowerShell prompt to appear +WaitForRegExInWindowsTerminal('>[ `n`r]*$', 'Timed out waiting for `cat.exe` to be interrupted', '`cat.exe` was interrupted as desired') + +; Clone via SSH test; Requires an OpenSSH for Windows `sshd.exe` whose directory needs to be specified via +; the environment variable `OPENSSH_FOR_WINDOWS_DIRECTORY`. The clone will still be performed via Git's +; included `ssh.exe`, to exercise the MSYS2 runtime (which these UI tests are all about). + +openSSHPath := EnvGet('OPENSSH_FOR_WINDOWS_DIRECTORY') +if (openSSHPath != '' and FileExist(openSSHPath . '\sshd.exe')) { + Info('Generate 26M of data') + largeFilesDirectory := EnvGet('LARGE_FILES_DIRECTORY') + if largeFilesDirectory == '' + largeFilesDirectory := workTree . '-large-files' + largeGitRepoPath := largeFilesDirectory . '\large.git' + largeGitClonePath := largeFilesDirectory . '\large-clone' + RunWait('git init --bare -b main "' . largeGitRepoPath . '"', '', 'Hide') + RunWait('git --git-dir="' . largeGitRepoPath . '" -c alias.c="!(' . + 'printf \"reset refs/heads/main\\n\"; ' . + 'seq 100000 | ' . + 'sed \"s|.*|blob\\nmark :&\\ndata < 1234& +0000\\ndata <[ `n`r]*$', 'Timed out waiting for clone to be interrupted', 'clone was interrupted as desired') + + if DirExist(largeGitClonePath) + ExitWithError('`large-clone` was unexpectedly not deleted on interrupt') + + ; Now verify that the SSH-based clone actually works and does not hang + Info('Re-starting SSH server') + Run(openSSHPath . '\sshd.exe ' . sshdOptions, '', 'Hide', &sshdPID) + if A_LastError + ExitWithError 'Error starting SSH server: ' A_LastError + Info('Started SSH server: ' sshdPID) + + Info('Starting clone') + Send('git -c core.sshCommand="ssh ' . sshOptions . '" clone ' . cloneOptions . '{Enter}') + Sleep 500 + Info('Waiting for clone to finish') + WinActivate('ahk_id ' . hwnd) + WaitForRegExInWindowsTerminal('Receiving objects: .*, done\.`r?`nPS .*>[ `n`r]*$', 'Timed out waiting for clone to finish', 'Clone finished', 15000, 'ahk_id ' . hwnd) + + if not DirExist(largeGitClonePath) + ExitWithError('`large-clone` did not work?!?') +} + +Send('exit{Enter}') +Sleep 50 +CleanUpWorkTree() \ No newline at end of file diff --git a/ui-tests/ui-test-library.ahk b/ui-tests/ui-test-library.ahk new file mode 100644 index 0000000000..120e85ed76 --- /dev/null +++ b/ui-tests/ui-test-library.ahk @@ -0,0 +1,125 @@ +; Reusable library functions for the UI tests. + +SetWorkTree(defaultName) { + global workTree + ; First, set the worktree path; This path will be reused + ; for the `.log` file). + if A_Args.Length > 0 + workTree := A_Args[1] + else + { + ; Create a unique worktree path in the TEMP directory. + workTree := EnvGet('TEMP') . '\' . defaultName + if FileExist(workTree) + { + counter := 0 + while FileExist(workTree '-' counter) + counter++ + workTree := workTree '-' counter + } + } + + SetWorkingDir(EnvGet('TEMP')) + Info 'uname: ' RunWaitOne('git -c alias.uname="!uname" uname -a') + Info RunWaitOne('git version --build-options') + + RunWait('git init "' workTree '"', '', 'Hide') + if A_LastError + ExitWithError 'Could not initialize Git worktree at: ' workTree + + SetWorkingDir(workTree) + if A_LastError + ExitWithError 'Could not set working directory to: ' workTree +} + +CleanUpWorkTree() { + global workTree + SetWorkingDir(EnvGet('TEMP')) + Info 'Cleaning up worktree: ' workTree + DirDelete(workTree, true) +} + +Info(text) { + FileAppend text '`n', workTree '.log' +} + +closeWindow := false +childPid := 0 +ExitWithError(error) { + Info 'Error: ' error + if closeWindow + WinClose "A" + else if childPid != 0 + ProcessClose childPid + ExitApp 1 +} + +RunWaitOne(command) { + SavedClipboard := ClipboardAll + shell := ComObject("WScript.Shell") + ; Execute a single command via cmd.exe + exec := shell.Run(A_ComSpec " /C " command " | clip", 0, true) + if exec != 0 + ExitWithError 'Error executing command: ' command + ; Read and return the command's output, trimming trailing newlines. + Result := RegExReplace(A_Clipboard, '`r?`n$', '') + Clipboard := SavedClipboard + return Result +} + +; This function is quite the hack. It assumes that the Windows Terminal is the active window, +; then drags the mouse diagonally across the window to select all text and then copies it. +; +; This is fragile! If any other window becomes active, or if the mouse is moved, +; the function will not work as intended. +; +; An alternative would be to use `ControlSend`, e.g. +; `ControlSend '+^a', 'Windows.UI.Input.InputSite.WindowClass1', 'ahk_id ' . hwnd +; This _kinda_ works, the text is selected (all text, in fact), but the PowerShell itself +; _also_ processes the keyboard events and therefore they leave ugly and unintended +; `^Ac` characters in the prompt. So that alternative is not really usable. +CaptureTextFromWindowsTerminal(winTitle := '') { + if winTitle != '' + WinActivate winTitle + ControlGetPos &cx, &cy, &cw, &ch, 'Windows.UI.Composition.DesktopWindowContentBridge1', "A" + titleBarHeight := 54 + scrollBarWidth := 28 + pad := 8 + + SavedClipboard := ClipboardAll + A_Clipboard := '' + SendMode('Event') + if winTitle != '' + WinActivate winTitle + MouseMove cx + pad, cy + titleBarHeight + pad + if winTitle != '' + WinActivate winTitle + MouseClickDrag 'Left', , , cx + cw - scrollBarWidth, cy + ch - pad, , '' + if winTitle != '' + WinActivate winTitle + MouseClick 'Right' + ClipWait() + Result := A_Clipboard + Clipboard := SavedClipboard + return Result +} + +WaitForRegExInWindowsTerminal(regex, errorMessage, successMessage, timeout := 5000, winTitle := '') { + timeout := timeout + A_TickCount + ; Wait for the regex to match in the terminal output + while true + { + capturedText := CaptureTextFromWindowsTerminal(winTitle) + if RegExMatch(capturedText, regex) + break + Sleep 100 + if A_TickCount > timeout { + Info('Captured text:`n' . capturedText) + ExitWithError errorMessage + } + if winTitle != '' + WinActivate winTitle + MouseClick 'WheelDown', , , 20 + } + Info(successMessage) +} \ No newline at end of file diff --git a/winsup/cygwin/fhandler/console.cc b/winsup/cygwin/fhandler/console.cc index 48c932647f..831df4f2cb 100644 --- a/winsup/cygwin/fhandler/console.cc +++ b/winsup/cygwin/fhandler/console.cc @@ -923,12 +923,12 @@ fhandler_console::cleanup_for_non_cygwin_app (handle_set_t *p) termios *ti = shared_console_info[unit] ? &(shared_console_info[unit]->tty_min_state.ti) : &dummy; /* Cleaning-up console mode for non-cygwin app. */ + set_disable_master_thread (con.owner == GetCurrentProcessId ()); /* conmode can be tty::restore when non-cygwin app is exec'ed from login shell. */ tty::cons_mode conmode = cons_mode_on_close (p); set_output_mode (conmode, ti, p); set_input_mode (conmode, ti, p); - set_disable_master_thread (con.owner == GetCurrentProcessId ()); } /* Return the tty structure associated with a given tty number. If the @@ -1121,8 +1121,8 @@ fhandler_console::bg_check (int sig, bool dontsignal) in the same process group. */ if (sig == SIGTTIN && con.curr_input_mode != tty::cygwin) { - set_input_mode (tty::cygwin, &tc ()->ti, get_handle_set ()); set_disable_master_thread (false, this); + set_input_mode (tty::cygwin, &tc ()->ti, get_handle_set ()); } if (sig == SIGTTOU && con.curr_output_mode != tty::cygwin) set_output_mode (tty::cygwin, &tc ()->ti, get_handle_set ()); @@ -1987,8 +1987,8 @@ fhandler_console::post_open_setup (int fd) /* Setting-up console mode for cygwin app started from non-cygwin app. */ if (fd == 0) { - set_input_mode (tty::cygwin, &get_ttyp ()->ti, &handle_set); set_disable_master_thread (false, this); + set_input_mode (tty::cygwin, &get_ttyp ()->ti, &handle_set); } else if (fd == 1 || fd == 2) set_output_mode (tty::cygwin, &get_ttyp ()->ti, &handle_set); @@ -2995,7 +2995,12 @@ fhandler_console::char_command (char c) if (con.args[i] == 1) /* DECCKM */ con.cursor_key_app_mode = (c == 'h'); if (con.args[i] == 9001) /* win32-input-mode (https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md) */ - set_disable_master_thread (c == 'h', this); + { + set_disable_master_thread (c == 'h', this); + if (con.curr_input_mode == tty::cygwin) + set_input_mode (tty::cygwin, + &tc ()->ti, get_handle_set ()); + } } /* Call fix_tab_position() if screen has been alternated. */ if (need_fix_tab_position)