Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to write to the alternate screen buffer on Windows using only the standard libary? #17

Closed
TimonPost opened this issue Jun 28, 2018 · 4 comments

Comments

@TimonPost
Copy link
Member

TimonPost commented Jun 28, 2018

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?

@retep998
Copy link

Write a wrapper around HANDLE which implements the necessary trait for write! and converts the text to UTF-16 before calling WriteConsoleW.

@TimonPost
Copy link
Member Author

TimonPost commented Jun 30, 2018

Oke, I have already tried that. If I used escape characters in the string, these were shown as weird symbols.

Example: https://ibb.co/dLTw1d

I use this code. . Any idea why this could be?

@retep998
Copy link

In order for newlines and such to be interpreted correctly, the ENABLE_PROCESSED_OUTPUT mode must be enabled on the console screen buffer using SetConsoleMode.

ANSI escape sequences require Windows 10 with the ENABLE_VIRTUAL_TERMINAL_PROCESSING mode enabled, and will never work on older Windows. For older Windows you have to use the API to do things like changing the color.

@TimonPost
Copy link
Member Author

TimonPost commented Jun 30, 2018

Thanks, I will give it a try. I know that only Windows 10 supports ANSI escape codes. In this crate this option is turned on by default. If the current system does not support this it will fall back to WinAPI. And with this implementation, I got the problem with alternate screen and writing to that alternate screen. But I will try what you have suggested.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants