ncurses 05 – 윈도우에서의 문자

창이라는 개념에 대해서는 hello world를 출력하면서 간단하게 설명하고 넘겼는데, 이젠 이걸 좀 제대로 설명해야겠다.

curses 시스템에 의한 창이라는 개념은 가상의 화면이다. 우리가 일반적으로 아는 윈도우 시스템과 같이 경계선(보더) 쳐저있는 그런 윈도우를 의미하지 않는다는 것입니다. curses가 초기화 되면, 80×25 사이즈 혹은 현재 실행중인 크기의 화면을 나타내는 stdscr이라는 기본 창 구조체를 만든다. 그냥 적은 수의 문자열을 인쇄(출력)하거나 입력을 읽어내는 등의 간단한 작업을 수행하는 경우 이 단일 창을 모든 용도로 안전하게 사용할 수 있다. 또한 지정된 창에서 명시적으로 작동하는 창을 만들고 이에 대한 함수를 호출할 수도 있다.

예를 들어,

printw(“hi there!”);
refresh();

이런 코드를 작성하면, stdscr에 해당되는 문자열을 출력하는데, 출력하는 것이 현재의 커서 포지션에서 출력을 하게 된다. 현재 커서 포지션은 stdscr에 있는 커서 포지션이 기준이 된다. 비슷하게 refresh() 함수를 부르면, stdscr 구조체에 대해서만 작동을 하게 된다.

이걸 윈도우를 만들어서 처리를 하게 되는 함수가 있는데, 함수 앞에 주로 w가 붙어있다. 또한 만들어진 윈도우 객체와 함께 호출된다. 이 부분은 윈도우에 대한 이해가 좀 되어야 하기 때문에 윈도우에서 좀 더 자세히 다루겠다.

이 외에도 stdscr에 출력하는 데 있어서 위치를 이동하여 처리할 수 있는 함수가 있다. mvprintw 함수를 이용하는데, 아래에 예제를 만들었다. 일전에 이용하였던 hello world를 일부 수정하였다.

스크린샷 2017-10-28 오후 4.03.57.png

수정한 부분이라고 해봐야, 문자열을 밖으로 뺐고, printw 함수 뒤에 mvprintw 함수를 썼다, (x, y) 좌표는 (5, 5) 로 해서 설정하였다. 이렇게 해서 refresh를 하면 화면에 해당 글씨가 두번 써진 상태로 출력이 될 것이다.

실제로 출력이 되었다. 커서 기준으로 (5,5) 이동이기 때문에 줄바꿈 5칸, 스페이스바 5칸 만큼 이동하였다.

이걸로 이제 단순한 수준의 기본 윈도우가 어떤 것인지 알아보고 출력 가능한 화면이 이동할 수 있다는 것들을 볼 수 있었다. 설명이 좀 부실한 부분에 대해서는 양해를 바란다. 이 부분을 아예 자세히 다루게 되면 엄청난 양을 다루게 되는데 그 부분을 최대한 좀 없앴다. 기본적인 화면에 대해서는 가상의 화면이고, stdscr이라는 구조체가 그 기본이 되며, 윈도우라는 별도의 창과 같은 개념을 이용하여 처리할 수 있는 부분이 존재하고, 화면 위치를 내가 설정하여 출력할 수 있다는 것이 된다는 걸 알아두었으면 한다.

ncurses 04 – 초기화

hello world를 출력하면서, 일단 초기화를 위해서 initscr() 함수를 이용하는 것에 대해서 다룬 적이 있다. 이러한 초기화 함수들이 ncurses에서는 여러가지가 존재한다. 화면 클리어화부터 시작해서 키보드 입력 초기화, 마우스 입력 초기화 등 다양한 함수들이 존재한다. 이 중에서도 일단 초기화에 필요한 함수들을 몇 가지 보고 실제로 조금씩 써보는 예제까지 만들어 보겠다.

  • raw() 함수와 cbreak() 함수

일반적으로 터미널 드라이버는 새 줄이나 캐리지 리턴이 발생할 때까지 사용자가 입력하는 문자를 버퍼링한다. 그러나, 대부분의 프로그램에서는 사용자가 입력하는 즉시 문자를 사용할 수 있어야 한다. 그래야 사용자가 사용을 하고 있다는 것을 알 수 있다. 그래서 이 두 함수를 이용하여 라인 버퍼링을 비활성화하는 데 이용한다. 두 함수의 차이는 일시 중단(ctrl + z), 인터럽트 및 종료(ctrl + c)와 같은 제어 문자가 프로그램에 전달되는 방식의 차이가 있다. raw() 함수 모드에서는 이러한 문자는 신호를 생성하지 않고 프로그램에 직접 전달한다. 반면에 cbreak() 함수 모드에서는 이러한 제어 문자는 터미널 드라이버에 의해 다른 문자로 해석된다. 어떤 함수를 이용하던 상관은 없지만, 주로 raw() 함수를 사용하는 것을 추천한다. 사용자가 하는 일을 보다 더 잘 제어할 수 있기 때문에 추천한다. (제어를 보다 단순하고 직관적으로 할 수 있다. 중요한 사항이다.)

  • echo() 함수와 noecho() 함수

이 함수는 사용자가 터미널에 입력한 문자에 대해 반응을 한다. noecho() 함수는 반응을 하지 않는다.  사용자가 getch() 함수와 같은 함수를 통해 입력을 가져오는 동안 반응을 제어하거나 불필요한 반응을 하지 않도록 하기 위해서는 noecho() 함수를 이용하는 것이 좋다. 그러나 대부분의 대화형 프로그램은 초기에 초기화를 할 때에는 noecho()를 호출하고 나중에 제어된 반응을 보여주기 위해 echo() 함수를 이용한다. 반응에 대해서는 좌표 업데이트와 상관 없이 창의 모든 위치에서 문자를 출력 반응을 하도록 유연하게 동작할 수 있다.

  • keypad() 함수

이 함수는 F1, F2, 화살표 키 등의 기능키를 읽을 수 있는 함수이다. 거의 모든 대화형 프로그램에서 이용하는 주요 부분에 이용된다. 사용자 인터페이스의 제어에도 이용하기 때문에 상당히 자주 이용된다. keypad(stdscr, TRUE) 라고 사용하여 일반 화면에서의 제어 기능을 활성화 할 수 있다. 이것은 실제 예제를 보고 알면 될 것이다.

  • halfdelay() 함수

자주 사용하는 함수는 아니지만 유용한 기능이다. 이름 그대로 반 지연 모드를 활성화하기 위해 호출하는 함수이다. 이 함수 모드는 입력 된 문자를 즉시 프로그램에서 사용할 수 있다는 점에서 cbreak() 함수와 유사하다. 그러나 입력을 위해 몇 초 동안 대기 한 다음 사용 가능한 입력이 없으면 ERR를 리턴한다. 몇 초라는 것은 halfdelay() 함수에 전달 된 시간 초과 값이다. 이 함수를 이용하여 사용자에게 입력을 요청할 때 특정 시간에 응답하지 않으면 다른 작업을 수행할 수 있도록 할 수 있다.

이 외에도 여러모로 초기화 함수가 존재하는데, 함수가 너무 많기 때문에 일단 자주 보이는 함수에 대해서만 작성을 하였다. 그리고 이를 통해 예제도 작성해보았다. 입력된 키가 F1 키일 경우에는 F1이 눌렸다는 문장을 출력하고, 그냥 일반 키가 입력되면 입력된 키를 출력하는데, 출력을 볼드(두껍게)하도록 출력하였다. 이를 위해 keypad 함수를 이용하였고, F1 기능키를 확인할 수 있는 조건문을 통해서 특수키가 어떻게 제어되는지 간단히 보여줬다.

이를 실행하기 위해 간단하게 빌드를 진행하였다.

실행을 우선 두 가지로 진행하였다. 첫 번째는 F1을 눌렀을 때 어떻게 되는지 보여준다.

그리고 나서 다른 키를 또 누르면 실행이 끝난다. 계속 실행되도록 하질 않아서 그런다.

두 번째로는 A를 입력하였을 때, A가 볼드 처리되어 출력되는 것을 볼 수 있다.

볼드 처리를 위해서 attron, attroff를 통해 문자 처리도 어떻게 되는지 볼 수 있다.

초기화에 대해서는 어떻게 되는 지 보고 간단한 예제만 보더라도 어떻게 실행되는지를 먼저 알아두는 게 좋다. 이제 이걸 나중에 계속 짜봐야 좀 알게된다.

ncurses 03 – Hello World!

어떠한 환경이던 일단 기본적으로 했던 것이 바로 Hello World입니다. ncurses에서도 마찬가지인데요, 어떻게 구성되어 있는지 우선 코드를 작성하여 보여드리겠습니다.

프로그램 구조는 C와 동일합니다. 아니, C 코드 그 자체입니다. 근데 보면 뭔가 다른 코드들이 막 있습니다. 이게 어떤 함수들을 이용한 것인지 설명하겠습니다.

우선 stdio를 삽입 안하고 바로 ncurses를 삽입하였습니다. 그렇습니다. 화면에만 뿌리는 작업, 기본적인 ncurses 작업에는 stdio는 필요하지 않습니다. printf 함수는 stdio에 정의되어 있던 함수이기 때문에 우리는 첨에 hello world를 C에서 출력할 때에는 그걸 그대로 이용했습니다만, ncurses에서는 ncurses용 함수를 이용했기 때문에 안쓴 것이죠.

이제 함수들을 하나 하나 살펴보겠습니다. 단순한 설명을 우선적으로 진행합니다.

  • initscr: 화면을 초기화 하는 것입니다. 터미널의 쉘 환경 화면에서 새로 그리기 위해 화면을 초기화 합니다. 그리고 빈 화면을 출력합니다.
  • printw: print window. 화면에 프린트 하는 함수입니다. 사용법은 printf와 비슷합니다.
  • refresh: 화면을 리프래시 해준다. 이 때에 화면에 printw에 있는 문자열이 출력된다. (중요!)
  • getch: 사용자에게 문자 하나를 입력받을 준비를 한다.
  • endwin: ncurses 윈도우를 종료한다.

함수들이 어떤 기능을 하는지를 확인하니 윈도우를 그리는 과정을 알 수 있겠죠? 화면 초기화 -> 데이터 입력 -> 화면 그리기 -> 화면 종료 이런 식으로 간단하게 보여줬습니다. 단, getch로 사용자 입력을 기다리게 해서 화면이 지속적으로 떠 있도록 해줬던 것이 차이점입니다.

여기서 지금 중요한 이야기를 하나 진행합니다. 바로 refresh에 대한 내용인데, 이게 정말 중요합니다. 왜 refresh, 즉 새로 그리기를 해야지만 다시 보여줄 수 있는 것인지?

터미널에서 보여주는 화면은 우리가 메모리에서 저장된 내용을 보여주게 됩니다. 그런데, 메모리에 저장된 내용이 단순한 문자열 한 줄인 경우에는 그냥 기존처럼 출력하게 되지만, 윈도우 화면으로 만들려고 한다면 윈도우 화면을 수동적으로 만들어서 보여줄 수 있어야 합니다. 그런 윈도우 화면을 자료구조 형태로 만들어서 메모리에 적재하는 과정이 바로 initscr 함수에서 진행합니다. 그리고 해당되는 빈 자료구조 중에 기본적인 윈도우의 역할을 하는 자료구조는 stdscr이라는 내부 자료구조가 있어서, 이 자료구조가 초기화되고 뿌려진 상태가 바로 아무것도 없는 검은 화면이 되는 것입니다.

여기에 이제 printw로 문자열을 화면 출력용 자료구조에 삽입하여도 일단 출력된 자료구조와 현재 메모리에 저장되어 있는 자료구조와는 다른 데이터를 갖게 되는 것이죠. 이 것을 refresh 함수가 다시 읽어오게 됩니다. 다시 읽어온 메모리 덤프 데이터가 그대로 출력되어야지만 비로소 화면에 출력이 되는 것인데, 이 과정 때문에 refresh 함수가 반드시 필요합니다.

처음에 ncurses를 개발하다보면 자꾸 refresh를 까먹을 수 있는데, 화면이 변경되어 새로 그리게 되면 반드시 쓰게 되는 함수라고 생각하고 있어야 합니다.

실제로 실행을 하게 되면 어떻게 되는지 보여드리겠습니다. 저장하고 나서 컴파일을 진행합니다.

그리고 컴파일 결과물인 hello를 직접 실행해 보겠습니다.

빈 화면에 kyuling: hello world라고 떠 있습니다. 왼쪽 상단에 위치하는데, 왼쪽 상단 좌표를 0,0으로 설정하고 출력하기 때문에 그쪽부터 출력되는 것입니다. 그 뒤에 커서는 입력 대기를 하는 것이고요. 여기서 아무 키나 누르게 되면 다시 원래 화면으로 돌아갑니다.

다시 이전 화면으로 돌아갑니다. ncurses 내부를 파해치는 것이 아니라 더 이상의 자세한 설명은 생략하겠습니다만, 프로그래밍을 하다보면 “아, 이거 그거다.”라고 금방 이해하고 알 수 있는 날이 올 것입니다. 나중에 다세히 쓰기도 하겠지만요.

hello world를 출력해보면서 간단한 구조를 살펴봤습니다. 이제 조금씩 더 다뤄가면서 그래픽 프로그래밍 같아 보이는 작업들을 보여주도록 하겠습니다.

ncurses 02 – 우분투에 ncurses 개발 환경 설치하기

왜 우분투인지는 더 이상 설명하지 않겠습니다. 제가 쓰는 환경이 주분투(우분투 + xfce)일 뿐입니다.

게다가 제가 이전 글에서 말했듯, 좋은 환경 필요 없습니다. 터미널 접속만 잘 되면 됩니다. 전 당연히 ssh로 작성할 것입니다.

그래서 제 개발 환경을 보여드리고 시작해야 할 거 같군요.

맥에서 ssh로 접속하였습니다. 제 바이오는 여러모로 활약하는 좋은 개발머신입니다. 성능도 그렇게 좋은 것은 아닙니다만, 아무 문제 없는 작업 성능을 보여주고 있습니다.

이제 설치할 것들이 셋 있는데, 바로 build-essential, libncurses-dev, libncursesw5-dev 입니다. 왜 이것들이 필요한지 알려드리겠습니다.

  • build-essential: ncurses 라이브러리들을 빌드하여 설치할 때 필요함.
  • libncurses-dev: ncurses 라이브러리들. 시스템 전역에서 이용할 수 있도록 되어 있는 라이브러리다.
  • libncursesw5-dev: 확장 라이브러리. 이걸 이용하면 한국어와 같은 멀티바이트 환경을 이용할 수 있다. (한국어 입출력이 가능함)

전 리눅스 커널을 주로 만지다보니 커널 설정할 때 이미 ncurses를 쓰고 있어서 위에 두 라이브러리가 이미 설치가 되어 있습니다만, 아래의 라이브러리는 설치가 되어 있지 않습니다. 그래서 설치를 진행하면 이미 설치되어 있기도 합니다만, 혹시 모르니 저 셋을 다 설치해줍니다.

설치할 때, 뒤에 5 버전을 따로 안적어주면 기본으로 5로 설치해줍니다. 패키지 관리자에 매핑되어 있는 버전이 저 버전인지라 그냥 그대로 설치해주는데, 아무 문제 없이 이용할 수 있습니다.

공식문서나 HOWTO에는 보면 소스코드 받아서 빌드하고 뭐 하고 하는데, 일단은 편하게 이용할 수 있는 환경에서 써보는 걸 우선적으로 해보겠습니다.

ncurses 01 – ncurses 프로그래밍이란?

전에 작업하던 C언어 내용과는 별개로 재미를 붙이기 위해서라도 좀 해야 할 거 같아서 추가로 시작해봅니다. ㅇㅂㅇ

일전에 DOS를 사용하던 분들은 아시겠지만, MC(Midnight Commander)라는 것을 이용하여 콘솔 화면에서도 GUI 처럼 화면을 이리저리 보여주고 방향키로 선택 커서를 옮겨 다니고, 화면에 단축키 처리를 할 수 있던 녀석이 기억 나실지 모르겠습니다. 도스 환경에서는 MDrill, 좀 근래에 썼던 사람들은 mdir이라고 아실텐지… (나이 인증 아닙니다.)

이런 비슷한 녀석이요.

당연히 터미널 시스템에서 텍스트 기반으로 쓰던 시스템 환경에서도 화면에 보이는 기술이 중요하다는 것을 알고 있기 때문에, 이런 기반의 기술을 내놓게 됩니다. 바로 Unix System V ver 4.0에 있던 curses라는 기능입니다. 이 기능을 GNU에서 공개해서 어디서든 쓸 수 있도록 한 것이 바로 ncurses입니다.

즉, ncurses 는 콘솔 화면에서 사용자가 이용하기 쉽도록 사용자 친화적인 콘솔 윈도우 화면을 만드는 데 이용되는 라이브러리입니다. 메뉴 바를 제어하거나, 키보드 입력을 돕거나, 윈도우를 띄워주는 그런 기능들을 합니다.

당연히 이런 기능들을 쉽게 이용할 수 있도록 라이브러리화 되어 있고, 함수로도 구현되어 있기 때문에 적절하게 사용하면 됩니다. 그렇다고 해서 뭐 그렇게 큰 환경이 필요한 게 아닙니다. 터미널로 접속 가능한 리눅스만 있으면 충분히 가능합니다.

C언어를 진행하다 보면 지속적으로 물어보는 것이 “이거 어떻게 써요?” 하는 겁니다. 리눅스 시스템 프로그래밍 하는 건 어느 정도 알겠고, 입출력 하는 프로그램이야 당연히 알겠고, 장치 드라이버 짜는 것까지는 알겠는데, 더 이상 어떻게 쓸 곳 없냐고 물어보더군요. 그래서 C 언어를 리눅스에서 프로그래밍하는 것과는 별개의 과정으로 이 과정 또한 조금씩 다뤄보려 합니다. C를 할 줄 안다면 어렵지 않습니다. 오히려 더 좋은 길잡이가 될 수 있으면 좋겠군요.