OpenConsole: Cursor visibility prevents programmatic scrolling #23652

Open
opened 2026-01-31 08:48:16 +00:00 by claunia · 2 comments
Owner

Originally created by @alabuzhev on GitHub (Sep 27, 2025).

Windows Terminal version

1.25.2509.25001 or the latest source

Windows build number

10.0.19045.6218

Other Software

No response

Steps to reproduce

  1. Get the latest openconsole (compile or from the WT package)

Compile the code:

Code
#include <iostream>

#include <windows.h>

static bool Scroll(int Lines)
{
	const auto Out = GetStdHandle(STD_OUTPUT_HANDLE);

	CONSOLE_SCREEN_BUFFER_INFO Info;
	if (!GetConsoleScreenBufferInfo(Out, &Info))
		return false;

	if (!(Lines < 0 && Info.srWindow.Top) && !(Lines > 0 && Info.srWindow.Bottom != Info.dwSize.Y - 1))
		return false;

	Info.srWindow.Top += Lines;
	Info.srWindow.Bottom += Lines;

	if (Info.srWindow.Top < 0)
	{
		Info.srWindow.Bottom -= Info.srWindow.Top;
		Info.srWindow.Top = 0;
	}

	if (Info.srWindow.Bottom >= Info.dwSize.Y)
	{
		Info.srWindow.Top -= (Info.srWindow.Bottom - (Info.dwSize.Y - 1));
		Info.srWindow.Bottom = Info.dwSize.Y - 1;
	}

	if (!SetConsoleWindowInfo(Out, true, &Info.srWindow))
		return false;

	return true;
}

int main()
{
	const auto In = GetStdHandle(STD_INPUT_HANDLE);
	const auto Out = GetStdHandle(STD_OUTPUT_HANDLE);

	{
		CONSOLE_SCREEN_BUFFER_INFO Info;
		GetConsoleScreenBufferInfo(Out, &Info);
		if (Info.dwSize.Y < 1000)
		{
			Info.dwSize.Y = 1000;
			SetConsoleScreenBufferSize(Out, { 80, 1000 });
		}
	}

	for (size_t i = 0; i != 1000; ++i)
		std::wcout << i << '\n';

	std::wcout << "\nPress Up or Down to scroll\nPress C to toggle cursor";
	std::wcout.flush();

	for (;;)
	{
		INPUT_RECORD InputRecord;
		DWORD Read;
		if (!ReadConsoleInput(In, &InputRecord, 1, &Read) || !Read)
			return EXIT_FAILURE;

		if (InputRecord.EventType != KEY_EVENT)
			continue;

		const auto& KeyEvent = InputRecord.Event.KeyEvent;

		if (!KeyEvent.bKeyDown)
			continue;


		switch (KeyEvent.wVirtualKeyCode)
		{
		case VK_UP:
		case VK_DOWN:
			Scroll(KeyEvent.wVirtualKeyCode == VK_UP ? -1 : 1);
			break;

		case 'C':
			{
				CONSOLE_CURSOR_INFO Info;
				GetConsoleCursorInfo(Out, &Info);
				Info.bVisible = !Info.bVisible;
				SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &Info);
			}
			break;

		default:
			break;
		}
	}
}

  1. Run it in openconsole.
  2. Follow the instructions on the screen.
  3. Try to move it far enough, so that the cursor is outside of the viewport.

Expected Behavior

The code is a simple program that moves the viewport up or down as you press the corresponding buttons.

It's basically an isolated/simplified feature of another app (Far) that allows the user to inspect the scrollback without reaching for the mouse.

It worked as expected for years in legacy, conhost, and openconsole until recently.

Actual Behavior

After some relatively recent changes it is only possible to move it while the cursor position stays in the viewport.
As soon as you reach the edge it blinks and reverts.

It is still possible to move it freely if the cursor is not visible (press 'C' to goggle).

Originally created by @alabuzhev on GitHub (Sep 27, 2025). ### Windows Terminal version 1.25.2509.25001 or the latest source ### Windows build number 10.0.19045.6218 ### Other Software _No response_ ### Steps to reproduce 1. Get the latest openconsole (compile or from the WT package) Compile the code: <details> <summary>Code</summary> ```C++ #include <iostream> #include <windows.h> static bool Scroll(int Lines) { const auto Out = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO Info; if (!GetConsoleScreenBufferInfo(Out, &Info)) return false; if (!(Lines < 0 && Info.srWindow.Top) && !(Lines > 0 && Info.srWindow.Bottom != Info.dwSize.Y - 1)) return false; Info.srWindow.Top += Lines; Info.srWindow.Bottom += Lines; if (Info.srWindow.Top < 0) { Info.srWindow.Bottom -= Info.srWindow.Top; Info.srWindow.Top = 0; } if (Info.srWindow.Bottom >= Info.dwSize.Y) { Info.srWindow.Top -= (Info.srWindow.Bottom - (Info.dwSize.Y - 1)); Info.srWindow.Bottom = Info.dwSize.Y - 1; } if (!SetConsoleWindowInfo(Out, true, &Info.srWindow)) return false; return true; } int main() { const auto In = GetStdHandle(STD_INPUT_HANDLE); const auto Out = GetStdHandle(STD_OUTPUT_HANDLE); { CONSOLE_SCREEN_BUFFER_INFO Info; GetConsoleScreenBufferInfo(Out, &Info); if (Info.dwSize.Y < 1000) { Info.dwSize.Y = 1000; SetConsoleScreenBufferSize(Out, { 80, 1000 }); } } for (size_t i = 0; i != 1000; ++i) std::wcout << i << '\n'; std::wcout << "\nPress Up or Down to scroll\nPress C to toggle cursor"; std::wcout.flush(); for (;;) { INPUT_RECORD InputRecord; DWORD Read; if (!ReadConsoleInput(In, &InputRecord, 1, &Read) || !Read) return EXIT_FAILURE; if (InputRecord.EventType != KEY_EVENT) continue; const auto& KeyEvent = InputRecord.Event.KeyEvent; if (!KeyEvent.bKeyDown) continue; switch (KeyEvent.wVirtualKeyCode) { case VK_UP: case VK_DOWN: Scroll(KeyEvent.wVirtualKeyCode == VK_UP ? -1 : 1); break; case 'C': { CONSOLE_CURSOR_INFO Info; GetConsoleCursorInfo(Out, &Info); Info.bVisible = !Info.bVisible; SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &Info); } break; default: break; } } } ``` </details> 2. Run it in openconsole. 3. Follow the instructions on the screen. 4. Try to move it far enough, so that the cursor is outside of the viewport. ### Expected Behavior The code is a simple program that moves the viewport up or down as you press the corresponding buttons. It's basically an isolated/simplified feature of another app (Far) that allows the user to inspect the scrollback without reaching for the mouse. It worked as expected for years in legacy, conhost, and openconsole until recently. ### Actual Behavior After some relatively recent changes it is only possible to move it while the cursor position stays in the viewport. As soon as you reach the edge it blinks and reverts. It is still possible to move it freely if the cursor is not visible (press 'C' to goggle).
claunia added the Product-ConhostIssue-BugNeeds-AttentionArea-ServerPriority-2 labels 2026-01-31 08:48:17 +00:00
Author
Owner

@DHowett commented on GitHub (Oct 1, 2025):

I suspect this regressed in #17453... or maybe not? Leonard will need to have a look.

@DHowett commented on GitHub (Oct 1, 2025): I suspect this regressed in #17453... or maybe not? Leonard will need to have a look.
Author
Owner

@alabuzhev commented on GitHub (Nov 20, 2025):

Thanks for looking into this, but, unfortunately, that's not all.
The initial example now works as expected, but it's just a minimal example.
In the real world scenario the app also outputs something after scrolling, which might require cursor repositioning, and if that happens, the scrolling breaks again.
To reproduce, add this after the

Scroll(KeyEvent.wVirtualKeyCode == VK_UP ? -1 : 1);

in the example above:

			{
				CONSOLE_SCREEN_BUFFER_INFO Info;
				GetConsoleScreenBufferInfo(Out, &Info);
				const auto Where = Info.dwCursorPosition;
				DWORD n;
				WriteConsole(Out, KeyEvent.wVirtualKeyCode == VK_UP ? L" ^" : L" v", 2, &n, {});
				SetConsoleCursorPosition(Out, Where); // <-- this resets the viewport position back
			}
@alabuzhev commented on GitHub (Nov 20, 2025): Thanks for looking into this, but, unfortunately, that's not all. The initial example now works as expected, but it's just a minimal example. In the real world scenario the app also outputs something after scrolling, which might require cursor repositioning, and if that happens, the scrolling breaks again. To reproduce, add this after the ```C++ Scroll(KeyEvent.wVirtualKeyCode == VK_UP ? -1 : 1); ``` in the example above: ```C++ { CONSOLE_SCREEN_BUFFER_INFO Info; GetConsoleScreenBufferInfo(Out, &Info); const auto Where = Info.dwCursorPosition; DWORD n; WriteConsole(Out, KeyEvent.wVirtualKeyCode == VK_UP ? L" ^" : L" v", 2, &n, {}); SetConsoleCursorPosition(Out, Where); // <-- this resets the viewport position back } ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#23652