SetConsoleScreenBufferInfoEx works different from SetConsoleTextAttribute - c

Recently I tried to print text in C with underscore. My console doesn't support ANSI escape character so I tried using DBCS, which my console does support. To do so, I had to change the console text attributes. At the beginning I used SetConsoleTextAttribute to change it but later when I wanted to remember the color and ONLY change the underscore I started using GetConsoleScreenBufferInfoEx and SetConsoleScreenBufferInfoEx to also get the previous attributes. That's when I noticed that when I use the former, it only affects the text which I print after the call, and in the case of the latter, I also change the attributes of the previous text.
For example, I wrote 2 short codes and compiled them.
Code 1:
#include <Windows.h>
#include <stdio.h>
int main()
{
printf("Code 1:\n");
HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD mode = 0;
int flag = 1;
flag &= GetConsoleMode(out, &mode);
flag &= SetConsoleMode(out, mode | ENABLE_LVB_GRID_WORLDWIDE);
//7 is the default foreground - gray
SetConsoleTextAttribute(out, 7 | COMMON_LVB_UNDERSCORE);
printf("Hello World! 1==%d", flag);
getchar();
SetConsoleTextAttribute(out, 7);
printf("Goodbye World! 1==%d", flag);
getchar();
return 0;
}
And code 2:
#include <Windows.h>
#include <stdio.h>
typedef CONSOLE_SCREEN_BUFFER_INFOEX CSBI;
int main()
{
printf("Code 2:\n");
HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD mode = 0;
int flag = 1;
flag &= GetConsoleMode(out, &mode);
flag &= SetConsoleMode(out, mode | ENABLE_LVB_GRID_WORLDWIDE);
CSBI csbi = { 0 };
csbi.cbSize = sizeof(csbi);
flag &= GetConsoleScreenBufferInfoEx(out, &csbi);
csbi.wAttributes |= COMMON_LVB_UNDERSCORE;
flag &= SetConsoleScreenBufferInfoEx(out, &csbi);
printf("Hello World! 1==%d", flag);
getchar();
csbi.wAttributes &= ~COMMON_LVB_UNDERSCORE;
flag &= SetConsoleScreenBufferInfoEx(out, &csbi);
printf("Goodbye World! 1==%d", flag);
getchar();
return 0;
}
the flag is to make sure that all function return TRUE
In the first code, 'Code 1' will remain without underscore, 'Hello World!' will have underscore, and 'Goodbye World!' won't have an underscore.
In the second code, everything will have underscore until I enter a new line, and then everything will lose their underscore.
Does anyone have an idea why is it like that? I though that they will do the same about console text attributes.
Thanks, Roy

In the second code, everything will have underscore until I enter a new line, and then everything will lose their underscore.
After my test, the final effect of the two pieces of code is the same.
Does anyone have an idea why is it like that? I though that they will do the same about console text attributes.
SetConsoleTextAttribute: Sets the attributes of characters written to
the console screen buffer by the WriteFile or WriteConsole function,
or echoed by the ReadFile or ReadConsole function. This function
affects text written after the function call.
SetConsoleScreenBufferInfoEx: Sets extended information about the
specified console screen buffer.
For the comment, on the properties of the console text, SetConsoleTextAttribute and SetConsoleScreenBufferInfoEx can achieve the same effect, such as changing the color of the text or add underscore.

Related

Get terminal size using ANSI escape sequences?

While researching this problem, in the comments I found someone mentioning ANSI escape codes to get the terminal size. Since I will be using ANSI escape sequences, I thought this would be a much more elegant way to get the terminal size than ioctl() or getenv().
Here is a very good post about ioctl(), and this is the comment that piqued my interest:
Just stumbled upon this answer, and my jaw dropped when I realized that getenv("COLUMNS") works perfectly when running under watch(1). So now I have a set of fallbacks, all from the TIOCWINSZ ioctl, to getenv if not a tty, down to the classic ANSI escape "move cursor to 9999,9999 and the query cursor pos. The last one works on serial consoles for embedded systems :)
Now, I did find some posts (1, 2) about ANSI, but none answer the specific question I have - how to actually do it?
Using this chart, I deduced that in order to move the cursor to the extreme right and down position, I would need the CUP (CSI n ; m H) "Cursor Position" command, which would probably translate to something like \x1b[9999;9999H. This is the easy part since it is a command.
The second part is where I have a big problem. Even if I could deduce that I need the DSR(CSI 6n) "Device Status Report" command, and that it goes \x1b[6n, how does this querying work, i.e., how can I take the data and store it in a variable, preferrably without disrupting the other data that is being displayed on the terminal?
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <ctype.h>
#define SIZE 100
int main ( void) {
char in[SIZE] = "";
int each = 0;
int ch = 0;
int rows = 0;
int cols = 0;
struct termios original, changed;
// change terminal settings
tcgetattr( STDIN_FILENO, &original);
changed = original;
changed.c_lflag &= ~( ICANON | ECHO);
changed.c_cc[VMIN] = 1;
changed.c_cc[VTIME] = 0;
tcsetattr( STDIN_FILENO, TCSANOW, &changed);
printf ( "\033[2J"); //clear screen
printf ( "\033[9999;9999H"); // cursor should move as far as it can
printf ( "\033[6n"); // ask for cursor position
while ( ( ch = getchar ()) != 'R') { // R terminates the response
if ( EOF == ch) {
break;
}
if ( isprint ( ch)) {
if ( each + 1 < SIZE) {
in[each] = ch;
each++;
in[each] = '\0';
}
}
}
printf ( "\033[1;1H"); // move to upper left corner
if ( 2 == sscanf ( in, "[%d;%d", &rows, &cols)) {
printf ( "%d rows\n", rows);
printf ( "%d cols\n", cols);
}
// restore terminal settings
tcsetattr( STDIN_FILENO, TCSANOW, &original);
return 0;
}

Non-spacing characters in curses

I was trying to write a basic program to print ā (a with overline) in C using curses and non-spacing characters. I have set the locale to en_US.UTF-8 and I am able to print international language characters using that. This code only prints a without overline. I am getting similar results with ncurses too. What else do I need to do to get ā on screen?
#include <curses.h>
#include <locale.h>
#include <wchar.h>
#include <assert.h>
int main() {
setlocale(LC_ALL, "");
initscr();
int s = 0x41; // represents 'a'
int ns = 0x0305; // represents COMBINING OVERLINE (a non-spacing character)
assert(wcwidth(ns) == 0);
wchar_t wstr[] = { s, ns, L'\0'};
cchar_t *cc;
int x = setcchar(cc, wstr, 0x00, 0, NULL);
assert(x == 0);
add_wch(cc);
refresh();
getch();
endwin();
return 0;
}
The curses calls need a pointer to data, not just a pointer.
It's okay to pass a null-terminated array for the wide-characters, but the pointer for the cchar_t data needs some repair.
Here's a fix for the program:
> diff -u foo.c.orig foo.c
--- foo.c.orig 2020-05-21 19:50:48.000000000 -0400
+++ foo.c 2020-05-21 19:51:46.799849136 -0400
## -3,7 +3,7 ##
#include <wchar.h>
#include <assert.h>
-int main() {
+int main(void) {
setlocale(LC_ALL, "");
initscr();
int s = 0x41; // represents 'a'
## -12,11 +12,11 ##
assert(wcwidth(ns) == 0);
wchar_t wstr[] = { s, ns, L'\0'};
- cchar_t *cc;
- int x = setcchar(cc, wstr, 0x00, 0, NULL);
+ cchar_t cc;
+ int x = setcchar(&cc, wstr, 0x00, 0, NULL);
assert(x == 0);
- add_wch(cc);
+ add_wch(&cc);
refresh();
getch();
That produces (on xterm) a "A" with an overbar:
(For what it's worth, 0x61 is "a", while 0x41 is "A").
Your code is basically correct aside from the declaration of cc. You'd be well-advised to hide the cursor, though; I think it is preventing you from seeing the overbar incorrectly rendered in the following character position.
I modified your code as follows:
#include <curses.h>
#include <locale.h>
#include <wchar.h>
#include <assert.h>
int main() {
setlocale(LC_ALL, "");
initscr();
int s = 0x41; // represents 'A'
int ns = 0x0305; // represents COMBINING OVERLINE (a non-spacing character)
assert(wcwidth(ns) == 0);
wchar_t wstr[] = { s, ns, L'\0'};
cchar_t cc; /* Changed *cc to cc */
int x = setcchar(&cc, wstr, 0x00, 0, NULL); /* Changed cc to &cc */
assert(x == 0);
set_curs(0); /* Added to hide the cursor */
add_wch(&cc); /* Changed cc to &cc */
refresh();
getch();
endwin();
return 0;
}
I tested on a kubuntu system, since that's what I have handy. The resulting program worked perfectly on xterm (which has ugly fonts) but not on konsole. On konsole, it rendered the overbar in the following character position, which is clearly a rendering bug since the overbar appears on top of the following character if there is one. After changing konsole's font to Liberation Mono, the test program worked perfectly.
The rendering bug is not going to be easy to track down because it is hard to reproduce, although from my experiments it seems to show up reliably when the font is DejaVu Sans Mono. Curiously, my system is set up to use non-spacing characters from DejaVu Sans Mono as substitutes in other fonts, such as Ubuntu Mono, and when these characters are used as substitutes, the spacing appears to be correct. However, Unicode rendering is sufficiently intricate that I cannot actually prove that the substitute characters really come from the configured font, and the rendering bug seems to come and go. It may depend on the font cache, although I can't prove that either.
If I had more to go on I'd file a bug report, and if I get motivated to look at this some more tomorrow, I might find something. Meanwhile, any information that other people can provide will undoubtedly be useful; at a minimum, that should include operating system and console emulator, with precise version numbers, and a list of fonts tried along with an indication whether they succeeded or not.
It's not necessary to use ncurses to see this bug, by the way. It's sufficient to test in your shell:
printf '\u0041\u0305\u000a'
will suffice. I found it interesting to test
printf '\u0041\u0305\u0321\u000a'
as well.
The system I tested it on:
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 18.04.4 LTS
Release: 18.04
Codename: bionic
$ konsole --version
konsole 17.12.3
$ # Fonts showing bug
$ otfinfo -v /usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf
Version 2.37
$ # Fonts not showing bug
$ otfinfo -v /usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf
Version 1.07.4
There are multiple issues here. First, you're storing the result of setcchar to random memory at an uninitialized pointer, cc. Whenever a function takes a pointer for output, you need to pass the address of an object where the result will be stored, not an uninitialized pointer variable. The output must be an array of sufficient length to store the number of characters in the input. I'm not sure what the null termination convention is so to be safe I'd use:
cchar_t cc[3];
int x = setcchar(cc, wstr, 0x00, 0, NULL);
Then, the add_wch function takes only a single character to add, and replaces or appends based on whether it's a spacing or non-spacing character. So you need to call it once for each character.

Strange output when using system("clear") command in C program

I have the following code
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <stdbool.h>
#define dimensions 5
int RandomNumInRange(int M, int N)
{
return M + rand() / (RAND_MAX / (N - M + 1) + 1);
}
char ** CreateWorld(int dim)
{
int i,j;
char **world = malloc(dim *sizeof(char*));
for(i=0;i<dim;i++)
world[i]=malloc(dim*sizeof(char));
for(i=0;i<dim;i++)
for(j=0;j<dim;j++)
world[i][j]=42;
return world;
}
void CreateCastle(char **world)
{
//assuming world is big enough
//to hold a match of 2
int randRow,randCol;
//1 to dimension -2 so we can spawn a 3x3 castle
randRow = RandomNumInRange(1,dimensions-2);
randCol = RandomNumInRange(1,dimensions-2);
printf("position: %d %d\n", randRow, randCol);
world[randRow][randCol]='c';
//fill the rest so castle is 3x3
//assuming there is enough space for that
world[randRow-1][randCol-1]=35;
world[randRow-1][randCol]=35;
world[randRow-1][randCol+1]=35;
world[randRow][randCol-1]=35;
world[randRow][randCol+1]=35;
world[randRow+1][randCol-1]=35;
world[randRow+1][randCol]=35;
world[randRow+1][randCol+1]=35;
}
void DisplayWorld(char** world)
{
int i,j;
for(i=0;i<dimensions;i++)
{
for(j=0;j<dimensions;j++)
{
printf("%c",world[i][j]);
}
printf("\n");
}
}
int main(void){
system("clear");
int i,j;
srand (time(NULL));
char **world = CreateWorld(dimensions);
DisplayWorld(world);
CreateCastle(world);
printf("Castle Positions:\n");
DisplayWorld(world);
//free allocated memory
free(world);
//3 star strats
char ***world1 = malloc(3 *sizeof(char**));
for(i=0;i<3;i++)
world1[i]=malloc(3*sizeof(char*));
for(i=0;i<3;i++)
for(j=0;j<3;j++)
world1[i][j]="\u254B";
for(i=0;i<3;i++){
for(j=0;j<3;j++)
printf("%s",world1[i][j]);
puts("");
}
free(world1);
//end
return 0 ;
}
If I use the system("clear") command, I get a line consisting of "[3;J"
followed by an expected output. If I run the program again, I get the same gibberish, then many blank newlines, then the expected output. If I put the system("clear") command in comments then both the "[3;J" and the blank newlines don't show and the output is expected.
Edit: it seems the error is not in the code, but rather in the way the terminal on my system is (not) set. Thank you all for your input, I definitely have a lot of interesting stuff to read and learn now.
The codes being sent by your clear command from don't seem to be compatible with the Gnome terminal emulator, which I believe is what you would be using.
The normal control codes to clear a console are CSI H CSI J. (CSI is the Control Sequence Initializer: an escape character \033 followed by a [). CSI H sends the cursor to the home position, and CSI J clears from the cursor position to the end of the screen. You could also use CSI 2 J which clears the entire screen.
On Linux consoles and some terminal emulators, you can use CSI 3 J to clear both the entire screen and the scrollback. I would consider it unfriendly to do this (and the clear command installed on my system doesn't.)
CSI sequences can typically contain semicolons to separate numeric arguments. However, the J command doesn't accept more than one numeric argument and the semicolon seems to cause Gnome terminal to fail to recognize the control sequence. In any event, I don't believe Gnome terminal supports CSI 3 J.
The clear command normally uses the terminfo database to find the correct control sequences for the terminal. It identifies the terminal by using the value of the TERM environment variable, which suggests that you have to wrong value for that variable. Try setting export TERM=xterm and see if you get different results. If that works, you'll have to figure out where Linux Mint configures environment variables and fix it.
On the whole, you shouldn't need to use system("clear") to clear your screen; it's entirely too much overhead for such a simple task. You would be better off using tputs from the ncurses package. However, that also uses the terminfo database, so you will have to fix your TERM setting in any case.

Is it possible to make a loading animation in a Console Application using C?

I would like to know if it is possible to make a loading animation in a Console Application that would always appear in the same line, like a flashing dot or a more complex ASCII animation.
Perhaps like this
#include <stdio.h>
#include <time.h>
#define INTERVAL (0.1 * CLOCKS_PER_SEC) // tenth second
int main(void) {
int i = 0;
clock_t target;
char spin[] = "\\|/-"; // '\' needs escape seq
printf(" ");
while(1) {
printf("\b%c", spin[i]);
fflush(stdout);
i = (i + 1) % 4;
target = clock() + (clock_t)INTERVAL;
while (clock() < target);
}
return 0;
}
The more portable way would be to use termcap/terminfo or (n)curses.
If you send ANSI escape sequences you assume the terminal to be capable of interpreting them (and if it isn't it'll result in a big mess.)
It's essentially a system that describes the capabilities of the terminal (if there's one connected at all).
In these days one tends to forget but the original tty didn't have a way to remove ink from the paper it typed the output on ...
Termcap tutorials are easy enough to find on Google. Just one in the GNU flavor here: https://www.gnu.org/software/termutils/manual/termcap-1.3/html_mono/termcap.html (old, but should still be good)
(n)curses is a library that will allow you control and build entire text based user interfaces if you want to.
Yes it is.
One line
At first if you want to make animation only at one line, you could use putchar('\b') to remove last character and putchar('\r') to return to line beginning and then rewrite it.
Example:
#include
#include
int main() {
int num;
while (1) {
for (num = 1; num <= 3; num++) {
putchar('.');
fflush(stdout);
sleep(1);
}
printf("\r \r"); // or printf("\b\b\b");
}
return 0;
}
But if you want to place it at specified line, you can clear and re-draw every frame, or use libs.
Clearing method
You can do this with system("clear") or with printf("\e[1;1H\e[2J").
After that you'll need to re-draw your frame. I don't recommend this method.
But this is really unportable.
Other libraries
You can use ncurses.h or conio.h depending on system type.
Ncurses example:
#include <stdio.h>
#include <unistd.h>
#include <ncurses.h>
int main() {
int row, col;
initscr();
getmaxyx(stdscr, row, col);
char loading[] = "-\\|/";
while (1) {
for (int i = 0; i < 8; i++) {
mvaddch(row/2, col/2, loading[i%4]);
refresh();
sleep(1);
mvaddch(row/2, col/2, '\b');
}
}
endwin();
return 0;
}

Weird key values printed by ncurses

I am doing a little program in C with the ncurses library on Linux.
I decided to check the input I received with the getch() function, more specifically, the backspace key.
The backspace ASCII decimal value is 127, link: here
I decided to print the numerical decimal value of the keys I pressed, for example:
a -> 97
A -> 65
] -> 93
...
The latter are correct.
However, the following values are not correct:
Backspace -> 7 (which is BELL)
Supr -> 74 (which is 'J')
Here is the test code:
#include <curses.h>
int main(int argc, char **argv)
{
char ch;
int column,line;
int s_column,s_line;
initscr();
clear();
noecho();
raw();
keypad(stdscr,TRUE);
printw("Type: \n> ");
refresh();
getyx(stdscr,s_line,s_column);
while((ch=getch())!='\n')
{
printw("%d",ch);
addch(ch);
refresh();
}
endwin();
return 0;
}
NOTE: changing raw() to cbreak() generates the same output
Output test: (note: I type: 'a','A',(Backspace),(Supr),'J')
Type:
> 97a65A7^G74J74J
I don't understand why this is happening, can somebody explain why the Backspace key outputs 7 instead of 127, and Supr outputs 74, which is the same sa 'J'?
For special function keys, getch() doesn't necessarily return the ASCII character, it returns one of the KEY_xxx codes in <curses.h>. In the case of Backspace, this is:
#define KEY_BACKSPACE 0407 /* backspace key */
Since you declare ch as char rather than int, the value 0407 is being truncated to 07.
Change the declaration to:
int ch;
and then it will display 263 when you press Backspace. addch() will still display ^G, though, because it doesn't use the KEY_xxx macros. You need to handle these characters in your code.
I believe the "special" keys are generating multi-character readings, which explains the ^ in the output.
See caret notation for more.

Resources