ncurses 라이브러리의 개요와 Window의 개념을 알아보고, “Hello, World!”를 출력하는 첫 프로그램을 작성해봅니다.
Introduction
ncurses는 리눅스 터미널 상에서 GUI를 구현하기 위하여 만들어진 라이브러리입니다. ncurses는 new curses 라는 뜻으로, Unix System V에서 GUI 구현을 위해 존재했던 curses라는 라이브러리를 리눅스를 위해 재작성한 버전입니다.
ncurses의 대표적인 특징은, GUI의 특징 중 하나인 ‘창(Window)’의 개념을 그대로 사용한다는 것입니다. 윈도우 GUI 응용프로그램은 여러 개의 ‘창’들로 구성됩니다. 이 창에는 일반 창부터 경고창, 메시지 창, 사용자의 의도를 묻는 질문 창까지 다양한 종류가 있죠. ncurses는 이런 ‘Window’의 개념을 차용하여 터미널 상에서 사용자가 직관적으로 어플리케이션을 사용할 수 있도록 프로그램을 개발할 수 있게 도와줍니다.
위에서 창의 개념을 사용한다고 적어 놓았는데, 사실 ncurses에서 Window는 하나의 ‘Component’의 개념으로 생각하시면 편할 것 같습니다. 각기 다른 내용을 포함하고 있는 Window들이 모여서 하나의 ncurses 활용 어플리케이션이 만들어지는 거니까요.
이 사진은 제가 학교애서 진행하는 프로젝트로 잠깐 개발했었던 2048 게임입니다. 보시는 것과 같이, 터미널 상에서 바로 실행하고 플레이할 수 있습니다. ncurses를 이용하면, 터미널에서 이렇게 컬러풀한 GUI 응용 프로그램을 제작할 수 있습니다.
위 사진의 2048 게임에서, ncurses의 핵심이라고 했던 Window를 찾아볼까요?
이것들이 각각 다 하나의 Window입니다. 구현한 Window들을 하나하나 보고 나니 Window를 조금 더 명확히 이해할 수 있겠네요. Window는 ‘GUI상에서 분리하여 표현할 내용의 단위’ 라고 볼 수 있겠습니다.
실제 프로그래밍을 진행할 때도, 프로그램에 사용될 Window들의 위치를 정해준 뒤 그것들의 내부를 만들어주는 형태로 진행됩니다. 이렇게 만든 여러 Window들을 조합하여 프로그램을 완성하게 됩니다.
ncurses 설치
ncurses를 이용한 프로그램은 우분투 환경에서 개발됩니다. 또 색깔 변경 등의 작업이 지원되는 터미널이 필요한데, 대표적으로 gnome-terminal이 있습니다.
쉘에서 아래의 명령어를 입력하여 ncurses를 설치합니다. 혹시 모르니 gnome-terminal을 설치하기 위한 명령어도 포함되어 있습니다.
$ sudo apt-get install build-essential
$ sudo apt-get install libncurses5-dev libncursesw5-dev
$ sudo apt-get install gnome-terminal
Hello, World!
이제 ncurses를 이용해 간단한 window를 만들어 첫 프로그램을 만들어보겠습니다.
ncurses 라이브러리를 사용하려면 ncurses.h
헤더 파일이 필요합니다.
#include <ncurses.h>
ncurses가 터미널을 자유자재로 제어하기 위한 스크린 초기화는 initscr()
함수가 수행하고, 제어를 끝내고 모든 메모리를 반환한 후 터미널을 원래 상태로 돌려 놓는 과정을 endwin()
함수가 수행합니다. ncurses를 이용한 터미널 제어는 무조건 initscr()
과 endwin()
의 호출 사이에서 이루어져야 합니다. 그래서 많은 경우 아래와 같은 형태로 코드를 작성합니다.
int main(void){
initscr();
// Screen Control
endwin();
}
윈도우를 만들어 제어하기 앞서 stdscr
부터 건드려봅시다. ncurses에서 stdscr
은 모든 윈도우의 조상 윈도우 입니다. ncurses는 터미널 화면 전체를 stdscr
이라는 윈도우로 관리합니다.
ncurses에서 가장 기본적인 출력 함수는 printw()
입니다. printw
류 함수들의 기본적인 사용법은 printf
함수와 동일합니다. 문자열을 넣은 후, 서식 지정에 따라 추가적으로 인자들을 넣어 줄 수 있습니다.
printw
함수를 이용하여 stdscr
에 문자열을 찍어 봅니다.
printw("Hello by printw ");
문자열 출력에 아무 속성도 지정해주지 않았으므로, stdscr
의 우상단 위치에 문자열을 출력할 것입니다. 하지만 이 문장만 가지고는 정상적인 출력이 이루어지지 않습니다. 윈도우 내용의 수정을 끝내고, 실제 터미널의 출력에 반영하기 위해서는 refresh()
함수가 필요합니다. refresh()
함수는 stdscr
의 변경 사항을 모두 받아들여 출력에 반영합니다. 쉽게 말해 stdscr
윈도우를 새로 고침 합니다.
그리고 프로그램이 종료되지 않게 하기 위해, getch()
함수를 사용하여 프로그램의 진행을 중단해줍니다. 이제 비로소 출력을 볼 수 있습니다.
#include <ncurses.h>
int main(void){
initscr();
printw("Hello by printw");
refresh();
getch();
endwin();
}
Window 만들어 그 위에 출력하기
ncurses에서 가장 중요한 Window를 만들어봅시다. 윈도우는 WINDOW
라는 구조체로 관리됩니다.
새 윈도우를 만들기 위해서는 newwin()
이라는 함수가 필요합니다. newwin
에는 네 가지 인자가 들어가는데, 순서대로 newwin ( 윈도우 높이, 윈도우 너비, Y 좌표, X 좌표)
를 의미합니다. Y 좌표는 1당 한 줄이고, X 좌표는 1당 한 칸(글자 1개) 입니다.
WINDOW * win = newwin(5, 30, 10, 20);
좌표는 0부터 시작하므로, 위의 코드는 11번째 줄 21번째 글자 위치에 가로 30칸, 세로 5줄로 win
이라는 이름의 윈도우를 생성합니다.
ncurses의 함수는 이름 앞에 w
가 붙은 것과 붙지 않은 것으로 나뉩니다. w
가 붙은 함수는 윈도우 단위로 제어하는 함수이고, 붙지 않은 함수는 stdscr
을 제어하는 함수입니다.
ncurses에서의 가장 기본적인 출력 함수는 printw
입니다. 당연히 앞에 w
가 붙은 wprintw()
도 세트로 존재합니다. 대부분의 w
가 붙은 함수들은, w
가 붙지 않은 함수의 인자들을 포함하고 맨 앞에 윈도우가 인자로 들어갑니다. wprintw
를 사용해서 아까 만든 win
윈도우에 문자열을 출력해봅시다.
wprintw(win, "Hello by wprintw : win");
그리고, 윈도우 단위로 refresh하기 위해서 wrefresh()
함수를 사용합니다. 인자는 윈도우입니다.
wrefresh(win);
윈도우 내용 변경을 끝내고 refresh할 때 주의하실 부분이 있는데, wrefresh()
함수는 가급적 refresh()
함수 뒤에 오는 것이 좋습니다. refresh()
함수는 stdscr
에 대한 변경 사항을 처리하기 때문에, 모든 윈도우의 위치를 포함한 전체 화면을 변경하게 됩니다. 따라서 각각 윈도우 출력이 무시될 수 있으므로 wrefresh()
는 가급적이면 refresh()
뒤에 써 주는 것이 좋습니다.
#include <ncurses.h>
int main(void){
initscr();
WINDOW * win = newwin(5, 30, 10, 20);
printw("Hello by printw ");
wprintw(win, "Hello by wprintw : win);
refresh(); //stdscr refresh
wrefresh(win);
getch();
endwin();
}
처음에 윈도우를 생성할 때 설정해준 위치에 윈도우가 생성되어 출력된 것을 볼 수 있습니다.
stdscr
과 win
윈도우에 하나씩 더 출력해볼까요?
printw("\nprint in stdscr!");
wprintw(window, "print in win!");
자, 위의 코드와 이 사진에서 한 가지 사실을 알아낼 수 있습니다. 각 윈도우는 하나의 터미널 스크린처럼 출력한다는 사실입니다. win
윈도우 안에서 윈도우의 너비를 넘은 출력이 발생했을 때 win
내의 다음 줄로 개행된 것을 보면 알 수 있습니다.