How to write to the alternate screen buffer on Win

2019-06-27 17:05发布

问题:

I am working on an alternate screen feature for my cross-platform terminal manipulating crate.

Situation

When I want to write something to console I can use the following code to write to the current standard output. This works on both UNIX and Windows systems:

write!(::std::fmt::io::stdout(), "Some text").

When I switch to the alternate screen, I don't want to write to the current standard output but to the alternate screen. This works differently depending on which platform you are on. The basics are the same: I need to store the handle somewhere globally so that I could put the stored output handle in alternate screen mode and write all my output, commands and actions to that stored output handle.

When I am in alternate mode, my code writes to the alternate screen and when in main screen modes my code writes to the main screen.

  • Unix

    For UNIX systems, I can use ANSI escape codes to switch to the alternate screen and back. I store the ::std::io::stdout() somewhere and all my UNIX code uses that handle for access to the terminal output. When in alternate screen mode, all the writes I do are done on the alternate screen and when on the main screen all the writes are done on the main screen.

  • Windows

    For Windows systems, I can use WinAPI to switch to the alternate screen buffer. I'll create a new screen buffer with CreateConsoleScreenBuffer then I'll use SetConsoleActiveScreenBuffer to change the active buffer. At last, I need to store the handle gotten from CreateConsoleScreenBuffer. Through this output handle, I can write output to the alternate screen buffer.

If I would not have used the above-described way and switched to alternate screen and just called this write!(::std::fmt::io::stdout(), "Some text"), I would write to the main screen instead of the alternate screen on both Windows and Unix systems because stdout() is a handle to the standard output.

The Question

The way described above works to a certain point; when I want to write to the stored handle.

For Unix I can do the following:

// (...) some logic to get the handle to the current screen.
let stored_handle: Write = ...;
write!(stored_handle, "Some text);

But for Windows I could not do this:

// (...) some logic to get the handle to the current screen for windows.
let stored_handle: HANDLE = ...;
write!(stored_handle, "Some text);

I could implement std::io::Write for the struct where I store the stdout so that for Windows I create my own logic for writing to the console with WinAPI. If I would do that I would be able to write to that struct like the following:

#[cfg(target_os = "windows")]
let storage = WindowsScreenManager::new();
#[cfg(not(target_os = "windows"))]
let storage = UnixScreenManager::new();

write!(storage, "Some text");

This is not ideal for my situation because I can not use the Rust string escape characters like \n \t my string will not contain any newlines or tabs when doing it this way. Think because WinAPI does not know these formatting options. Also I don't want to manage all the writing to the console for Windows manually on my side.

I really want to use the WinAPI HANDLE as std::io::Write so that it can be used in the write! macro, just like I do in UNIX. Just store the stdout() and write to that stdout() using the write! macro, but storing the HANDLE and writing to that.

I suppose that this should work since when calling println!() or write!(stdout()) on Windows it will write the to the standard output handle of the current process. But now I want to write to the alternate handle and not only to the default handle. Or am I wrong with this?

If the above cannot be done, how would I write to the alternate screen HANDLE without using my own implementation for writing to the Console using WinAPI?