이전 게시글 ( C++ 코드로 터미널 크기 구하기 ) 의 코드를 일부 재사용합니다.
#include <stdio.h>
#include <iostream>
#include <sstream>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
using namespace std;
/* ------------------------------------- DEFINE -------------------------------------- */
constexpr int CMD_MAX_LEN = 256;
// 좌표 저장 구조체 및 비교 연산자 정의
typedef struct _tCoord_ {
int xPos;
int yPos;
bool operator == ( const _tCoord_& A ) {
return (xPos == A.xPos) && (yPos == A.yPos);
}
bool operator != ( const _tCoord_& A ) {
return (xPos != A.xPos) || (yPos != A.yPos);
}
} COORD, *PCOORD;
// getCoord() 오류값 지정
constexpr COORD ERROR_GET_COORD = { -1, -1 };
// setCoord() 좌표값 이동 시 절대좌표 이동 or 상대좌표 이동 타입 체크
enum class MOVE_TYPE {
ABSOLUTE_MOVEMENT,
RELATIVE_MOVEMENT,
};
/* ------------------------------------- DEFINE -------------------------------------- */
/* --------------------------------- Console Control --------------------------------- */
string getStringResultFromCommand( const char* cmd ) noexcept
{
string result;
char buffer[CMD_MAX_LEN] = {'\0',};
// popen : 주어진 cmd 를 shell 로 실행하고 파이프 fd 반환
FILE* stream = popen( cmd, "r" );
if( stream ) {
// popen 및 shell cmd 성공적으로 수행했다면, 결과 출력값을 string 으로 반환
while( fgets( buffer, CMD_MAX_LEN, stream ) != NULL ) result.append( buffer );
pclose( stream );
}
return result;
}
// if Terminal Size 50(Lines) x 100(Columns) -> 'stty size' return : 50 100
inline void getConsoleSize( int& width, int& height )
{
// getStringResultFromCommand 이용해서 터미널 사이즈 받은 다음 토큰화
istringstream iss( getStringResultFromCommand( "stty size" ) );
string tokenString;
getline( iss, tokenString, ' ' );
height = stoi( tokenString );
getline( iss, tokenString, ' ' );
width = stoi( tokenString );
}
int getConsoleWidth()
{
try { return stoi( getStringResultFromCommand( "stty size | awk '{print $2}'" ) ); }
catch (...) { return 0; }
}
int getConsoleHeight()
{
try { return stoi( getStringResultFromCommand( "stty size | awk '{print $1}'" ) ); }
catch (...) { return 0; }
}
/* --------------------------------- Console Control --------------------------------- */
/* --------------------- Static Function for get terminal coord ---------------------- */
constexpr int RD_EOF = -1;
constexpr int RD_EIO = -2;
static inline int rd( const int fd )
{
unsigned char buffer[4];
ssize_t n;
while( true ) {
n = read( fd, buffer, 1 );
if( n > (ssize_t) 0 )
return buffer[0];
else
if( n == (ssize_t) 0 )
return RD_EOF;
else
if( n != (ssize_t)-1 )
return RD_EIO;
else
if( errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK )
return RD_EIO;
}
}
static inline int wr(const int fd, const char* const data, const size_t bytes )
{
const char* head = data;
const char* const tail = data + bytes;
ssize_t n;
while( head < tail ) {
n = write( fd, head, (size_t)( tail - head ) );
if( n > (ssize_t) 0 )
head += n;
else
if( n != (ssize_t)-1 )
return EIO;
else
if( errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK )
return errno;
}
return 0;
}
/* Return a new file descriptor to the current tty */
int current_tty(void)
{
const char* dev = ttyname(STDIN_FILENO);
if( !dev ) dev = ttyname(STDOUT_FILENO);
if( !dev ) dev = ttyname(STDERR_FILENO);
if( !dev ) {
errno = ENOTTY;
return -1;
}
int fd;
do {
fd = open( dev, O_RDWR | O_NOCTTY );
} while( fd == -1 && errno == EINTR );
return fd;
}
/* --------------------- Static Function for get terminal coord ---------------------- */
COORD getCoord()
{
struct termios save_attr, new_attr;
int result, rows, cols;
COORD co = ERROR_GET_COORD;
// fflush last buffer
fflush(stdout);
int tty = current_tty();
if( tty == -1 )
return ERROR_GET_COORD;
// Save current terminal setting
do {
result = tcgetattr( tty, &save_attr );
} while( result == -1 && errno == EINTR );
if( result == -1 )
return ERROR_GET_COORD;
// Get current terminal settings for basis, too
do {
result = tcgetattr( tty, &new_attr );
} while( result == -1 && errno == EINTR );
if( result == -1 )
return ERROR_GET_COORD;
// Disable ICANON, ECHO, CREAD
new_attr.c_lflag &= ~ICANON;
new_attr.c_lflag &= ~ECHO;
new_attr.c_lflag &= ~CREAD;
do {
// Set modified setting
do {
result = tcsetattr( tty, TCSANOW, &new_attr );
} while( result == -1 && errno == EINTR );
if( result == -1 ) {
break;
}
// Request cursor coord from the teminal
result = wr( tty, "\033[6n", 4 );
if( result ) {
break;
}
// Expect an ESC
result = rd(tty);
if( result != 27 ) break;
// Expect [ after ESC
result = rd(tty);
if( result != '[' ) break;
// Parse rows(yPos)
rows = 0;
result = rd(tty);
while( result >= '0' && result <= '9' ) {
rows = 10 * rows + result - '0';
result = rd(tty);
}
if( result != ';' ) break;
// Parse cols(xPos)
cols = 0;
result = rd(tty);
while( result >= '0' && result <= '9' ) {
cols = 10 * cols + result - '0';
result = rd(tty);
}
if( result != 'R' ) break;
// Success
co.xPos = cols - 1;
co.yPos = rows - 1;
} while( 0 );
// Restore saved terminal setting
do {
result = tcsetattr( tty, TCSANOW, &save_attr );
} while( result == -1 && errno == EINTR );
return co;
}
/*
ABSOLUTE_MOVEMENT :
최상단좌측을 (0,0) 이라 했을 때, 길이 방향 'x', 높이 방향 'y' 아래로 절대좌표 이동
RELATIVE_MOVEMENT :
현 위치에서 길이 방향 'x', 높이 방향 'y'만큼 상대좌표 이동
*/
inline void setCoord( const COORD& coord, MOVE_TYPE type = MOVE_TYPE::ABSOLUTE_MOVEMENT )
{
int maxConsoleX = getConsoleWidth() - 1;
int maxConsoleY = getConsoleHeight() - 1;
switch (type)
{
case MOVE_TYPE::ABSOLUTE_MOVEMENT:
{
// setCoord : zero-start / getConsoleSize : 1 start
// getConsoleHeight() or getConsoleWidth() 값으로 setCoord 할 때를 위한 값 조절
COORD curPos = {
.xPos = ( coord.xPos == getConsoleWidth() ) ? maxConsoleX : coord.xPos,
.yPos = ( coord.yPos == getConsoleHeight() ) ? maxConsoleY : coord.yPos,
};
if( curPos.xPos >= 0 && curPos.xPos <= maxConsoleX &&
curPos.yPos >= 0 && curPos.yPos <= maxConsoleY ){
printf("\033[%d;%df", curPos.yPos+1, curPos.xPos+1);
}
}
break;
case MOVE_TYPE::RELATIVE_MOVEMENT:
{
COORD curPos = getCoord();
if( curPos != ERROR_GET_COORD ){
curPos.xPos += coord.xPos;
curPos.yPos += coord.yPos;
if( curPos.xPos >= 0 && curPos.xPos <= maxConsoleX &&
curPos.yPos >= 0 && curPos.yPos <= maxConsoleY ){
printf("\033[%d;%df", curPos.yPos+1, curPos.xPos+1);
}
}
}
break;
}
}
inline void setCoord( const int xPos, const int yPos, MOVE_TYPE type = MOVE_TYPE::ABSOLUTE_MOVEMENT )
{
setCoord( {xPos, yPos}, type );
}
int main()
{
COORD startPos = { 4, 4 };
COORD curPos;
setCoord( startPos ); // startPos 위치로 이동
printf("Move Cursor : (%d, %d)", startPos.xPos, startPos.yPos );
startPos = { 6, 6 };
setCoord( startPos ); // startPos 위치로 이동
curPos = getCoord(); // 이동한 위치 읽어옴
printf("[ %d, %d ] setCoord (%d, %d) (absolute)", curPos.xPos, curPos.yPos, startPos.xPos, startPos.yPos);
/* [ 4, 4 ] setCoord (4, 4) (absolute) */
// 출력이 종료된 지점을 기준으로 상대 좌표 이동도 가능
setCoord( {0, 1}, MOVE_TYPE::RELATIVE_MOVEMENT ); // 이전 출력 후 마지막 커서 지점에서 x 방향으로는 그대로, y축 방향으로는 1칸 이동
curPos = getCoord(); // 이동한 위치 읽어옴
printf("[ %d, %d ] setCoord (0, 1) (relative)", curPos.xPos, curPos.yPos);
// 터미널의 오른쪽 가장자리로 이동해서, 높이만큼 글자 입력하기
for( size_t i = 0; i < getConsoleHeight(); ++i ) {
if( i == 0 ) setCoord( { getConsoleWidth(), 0 }, MOVE_TYPE::ABSOLUTE_MOVEMENT ); // 오른쪽 맨 위 모서리로 이동
else setCoord( { 0, 1 }, MOVE_TYPE::RELATIVE_MOVEMENT ); // 아래 칸 이동
printf("%c", 'A'+(int)i);
}
setCoord({0, getConsoleHeight()});
return 0;
}