Skip to content

Commit 2df44e1

Browse files
committed
First version of a terminal emulator example using PDCursesMod.
1 parent d267065 commit 2df44e1

4 files changed

Lines changed: 283 additions & 3 deletions

File tree

fb/Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ all: libs
9292
libs: $(LIBCURSES)
9393

9494
clean:
95-
-$(RM) *.o trace $(LIBCURSES) $(DEMOS) $(TESTS)
95+
-$(RM) *.o trace $(LIBCURSES) $(DEMOS) $(TESTS) vt0$(E)
9696

9797
demos: libs $(DEMOS)
9898
ifneq ($(DEBUG),Y)
@@ -113,6 +113,7 @@ endif
113113
$(LIBOBJS) $(PDCOBJS) : $(PDCURSES_HEADERS)
114114
$(DEMOS) : $(PDCURSES_CURSES_H) $(LIBCURSES)
115115
$(TESTS) : $(PDCURSES_CURSES_H) $(LIBCURSES)
116+
vt0$(E) : $(PDCURSES_CURSES_H) $(LIBCURSES)
116117
tui.o tuidemo.o : $(PDCURSES_CURSES_H)
117118
terminfo.o : $(TERM_HEADER)
118119
panel.o ptest: $(PANEL_HEADER)
@@ -129,6 +130,9 @@ $(DEMOS_EXCEPT_TUIDEMO): %: $(demodir)/%.c
129130
$(TESTS): %: $(testdir)/%.c
130131
$(BUILD) $(DEMOFLAGS) -o$@ $< $(LDFLAGS)
131132

133+
vt0$(E): %: $(testdir)/%.c
134+
$(BUILD) $(DEMOFLAGS) -o$@ $< $(LDFLAGS)
135+
132136
tuidemo$(E) : tuidemo.o tui.o
133137
$(LINK) tui.o tuidemo.o -o $@ $(LDFLAGS)
134138

sdl2/Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ all: libs
138138
libs: $(LIBCURSES)
139139

140140
clean:
141-
-$(RM) *.o trace $(CLEAN) $(DEMOS) $(TESTS)
141+
-$(RM) *.o trace $(CLEAN) $(DEMOS) $(TESTS) vt0$(E)
142142

143143
demos: $(DEMOS)
144144
ifneq ($(DEBUG),Y)
@@ -172,6 +172,7 @@ $(LIBOBJS) $(PDCOBJS) : $(PDCURSES_HEADERS)
172172
$(PDCOBJS) : $(PDCURSES_SDL_H)
173173
$(DEMOS) : $(PDCURSES_CURSES_H) $(LIBCURSES)
174174
$(TESTS) : $(PDCURSES_CURSES_H) $(LIBCURSES)
175+
vt0$(E) : $(PDCURSES_CURSES_H) $(LIBCURSES)
175176
tui.o tuidemo.o : $(PDCURSES_CURSES_H)
176177
terminfo.o : $(TERM_HEADER)
177178
panel.o ptest$(E) test_pan$(E): $(PANEL_HEADER)
@@ -188,6 +189,9 @@ $(TESTS): %: $(testdir)/%.c
188189
$(DEMOS_EXCEPT_TUIDEMO): %: $(demodir)/%.c
189190
$(BUILD) $(DEMOFLAGS) -o$@ $< $(LDFLAGS)
190191

192+
vt0$(E): %: $(testdir)/%.c
193+
$(BUILD) $(DEMOFLAGS) -o$@ $< $(LDFLAGS)
194+
191195
tuidemo$(E): tuidemo.o tui.o
192196
$(LINK) tui.o tuidemo.o -o $@ $(LDFLAGS)
193197

tests/vt0.c

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
#define _XOPEN_SOURCE 600
2+
#include <stdio.h>
3+
#include <stdlib.h>
4+
#include <assert.h>
5+
#include <string.h>
6+
#include <unistd.h>
7+
#include <errno.h>
8+
#include <fcntl.h>
9+
#include <termios.h>
10+
#include <sys/types.h>
11+
#include <sys/ioctl.h>
12+
#include <sys/wait.h>
13+
#include "curses.h"
14+
#include "../common/xlates.h"
15+
16+
/* (Very) primitive terminal emulator using PDCursesMod. Could use
17+
PDCurses instead. Or probably other implementations of curses.
18+
19+
We set up a pseudterminal (pty). Much of this comes from
20+
21+
https://learning.oreilly.com/library/view/the-linux-programming/9781593272203/xhtml/ch64.xhtml
22+
23+
In this program, we start a child process (by default, /usr/bin/bash -i).
24+
The parent process connects to its stdin and stdout, initializes curses,
25+
and enters a loop. If a character is received via getch( ), it's
26+
written to the child's stdin. If characters are read from the child's
27+
stdout, we add them to the screen via addch( ).
28+
29+
Note, too, that this is _not_ a full-fledged terminal emulator
30+
(though I think it could be turned into one). It handles the escape
31+
sequence for setting the window title and a couple of other escape
32+
sequences to delete characters; that was just enough to avoid having
33+
a window full of junk. You could call this a VT0 emulator... hence the
34+
file name.
35+
36+
It works decently on SDLn, x11new, and Linux framebuffer (DRM).
37+
38+
I could imagine something similar allowing us to build a Windows console.
39+
*/
40+
41+
int PDC_mbtowc(wchar_t *, const char *, size_t);
42+
43+
int create_term( const char* szCommand, const char **args,
44+
const char **environ)
45+
{
46+
const int master_fd = posix_openpt( O_RDWR | O_NOCTTY);
47+
int pid_child;
48+
49+
if( master_fd < 0)
50+
{
51+
perror( "couldn't open pseudoterminal");
52+
return -1;
53+
}
54+
55+
if( -1 == grantpt( master_fd) || -1 == unlockpt( master_fd))
56+
{
57+
close( master_fd);
58+
return -1;
59+
}
60+
61+
pid_child = fork();
62+
if( !pid_child) /* child continues here */
63+
{
64+
char *name = ptsname( master_fd);
65+
int term_fd;
66+
struct termios term;
67+
struct winsize ws;
68+
69+
if (setsid() == -1)
70+
return( -1);
71+
term_fd = open( name, O_RDWR);
72+
if( -1 == term_fd)
73+
return( -2);
74+
#ifdef TIOCSCTTY /* acquire controlling tty on BSD */
75+
if (ioctl( term_fd, TIOCSCTTY, 0) == -1)
76+
return( -3);
77+
#endif
78+
79+
tcgetattr( term_fd, &term);
80+
term.c_oflag &= ~OPOST;
81+
term.c_iflag &= ~ICRNL;
82+
tcsetattr( term_fd, TCSANOW, &term);
83+
84+
ws.ws_row = 25;
85+
ws.ws_col = 80;
86+
ioctl( term_fd, TIOCSWINSZ, &ws);
87+
88+
/* redirect stdin, stdout, stderr */
89+
if( -1 == dup2( term_fd, STDIN_FILENO)
90+
|| -1 == dup2( term_fd, STDOUT_FILENO)
91+
|| -1 == dup2( term_fd, STDERR_FILENO))
92+
exit(errno);
93+
94+
execve(szCommand, (char * const *) args, (char * const *)environ);
95+
96+
/* if we get here at all, an error occurred, but we are */
97+
/* in the child process, so just exit */
98+
perror( "Child process failed");
99+
exit( -1);
100+
}
101+
else if( pid_child > 0) /* parent continues here */
102+
{
103+
int wait_status;
104+
SCREEN *screen_pointer = newterm(NULL, stdout, stdin);
105+
106+
/* close unused file descriptors, these are for child only : */
107+
fcntl( master_fd, F_SETFL, O_NONBLOCK);
108+
109+
nocbreak( );
110+
noecho( );
111+
clear( );
112+
keypad(stdscr, TRUE);
113+
start_color( );
114+
addstr( "Welcome to PDCursesModTerm!\n");
115+
setbuf( stdout, NULL);
116+
raw( );
117+
scrollok( stdscr, TRUE);
118+
refresh( );
119+
timeout( 20);
120+
121+
while( waitpid( pid_child, &wait_status, WNOHANG) >= 0)
122+
{
123+
char nChar;
124+
int ch;
125+
126+
while( 1 == read( master_fd, &nChar, 1))
127+
{
128+
if( nChar != 27)
129+
{
130+
static int buffsize = 0;
131+
132+
if( !(nChar & 0x80))
133+
{
134+
addch( nChar);
135+
buffsize = 0;
136+
}
137+
else
138+
{
139+
static char utf8_bytes[5];
140+
wchar_t result;
141+
142+
assert( buffsize < 5);
143+
utf8_bytes[buffsize++] = nChar;
144+
if( PDC_mbtowc( &result, utf8_bytes, buffsize) > 0)
145+
{
146+
addch( (chtype)result);
147+
buffsize = 0;
148+
}
149+
}
150+
}
151+
else if( 1 == read( master_fd, &nChar, 1))
152+
{
153+
char buff[200];
154+
size_t i = 0;
155+
156+
switch( nChar)
157+
{
158+
case '[' : /* Control Sequence Introducer (CSI) */
159+
{
160+
bool valid_csi_sequence = false;
161+
162+
while( i < sizeof( buff) - 1
163+
&& 1 == read( master_fd, buff + i, 1))
164+
{
165+
i++;
166+
if( buff[i - 1] >= '@')
167+
{
168+
buff[i] = '\0';
169+
valid_csi_sequence = true;
170+
break;
171+
}
172+
}
173+
if( valid_csi_sequence) /* valid CSI sequence */
174+
{
175+
switch( buff[i - 1])
176+
{
177+
case 'K':
178+
delch( );
179+
break;
180+
case 'P':
181+
delch( );
182+
break;
183+
default:
184+
break;
185+
}
186+
}
187+
}
188+
break;
189+
case ']' : /* Operating System Command (OSC) */
190+
if( 1 == read( master_fd, &nChar, 1))
191+
{
192+
switch( nChar)
193+
{
194+
case '0': /* set window/icon title */
195+
case '1': /* set icon label */
196+
case '2': /* set window title */
197+
if( 1 == read( master_fd, &nChar, 1) && nChar == ';')
198+
{
199+
char title[255];
200+
201+
while( i < sizeof( title) &&
202+
1 == read( master_fd, title + i, 1))
203+
if( '\a' == title[i])
204+
{
205+
title[i] = '\0';
206+
PDC_set_title( title);
207+
break;
208+
}
209+
else
210+
i++;
211+
}
212+
break;
213+
default:
214+
break;
215+
}
216+
}
217+
break;
218+
default:
219+
break;
220+
}
221+
}
222+
}
223+
refresh( );
224+
while( ERR != (ch = getch( )))
225+
{
226+
if( ch > 0 && ch < 127)
227+
{
228+
nChar = (char)ch;
229+
if( !write( master_fd, &nChar, 1))
230+
exit( -1);
231+
}
232+
else for( size_t i = 0; i < sizeof( xlates) / sizeof( xlates[0]); i++)
233+
if( xlates[i].key_code == ch)
234+
{
235+
nChar = 27;
236+
if( !write( master_fd, &nChar, 1))
237+
exit( -1);
238+
if( !write( master_fd, xlates[i].xlation, strlen( xlates[i].xlation)))
239+
exit( -1);
240+
}
241+
}
242+
}
243+
244+
endwin();
245+
/* Not really needed, but ensures Valgrind */
246+
delscreen( screen_pointer); /* says all memory was freed */
247+
}
248+
else /* pid_child < 0; the fork failed */
249+
{
250+
}
251+
return pid_child;
252+
}
253+
254+
255+
int main( const int argc, const char **argv)
256+
{
257+
extern const char **environ;
258+
259+
if( argc < 2)
260+
{
261+
static const char *arguments[4] = { NULL, "/usr/bin/bash", "-i", NULL };
262+
263+
argv = arguments;
264+
}
265+
create_term( argv[1], &argv[1], environ);
266+
267+
return( 0);
268+
}

x11new/Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ all: libs
8888
libs: $(LIBCURSES)
8989

9090
clean:
91-
-$(RM) *.o trace $(LIBCURSES) $(DEMOS) $(TESTS)
91+
-$(RM) *.o trace $(LIBCURSES) $(DEMOS) $(TESTS) vt0$(E)
9292

9393
demos: libs $(DEMOS)
9494
ifneq ($(DEBUG),Y)
@@ -109,6 +109,7 @@ endif
109109
$(LIBOBJS) $(PDCOBJS) : $(PDCURSES_HEADERS)
110110
$(DEMOS) : $(PDCURSES_CURSES_H) $(LIBCURSES)
111111
$(TESTS) : $(PDCURSES_CURSES_H) $(LIBCURSES)
112+
vt0$(E) : $(PDCURSES_CURSES_H) $(LIBCURSES)
112113
tui.o tuidemo.o : $(PDCURSES_CURSES_H)
113114
terminfo.o : $(TERM_HEADER)
114115
panel.o ptest: $(PANEL_HEADER)
@@ -125,6 +126,9 @@ $(DEMOS_EXCEPT_TUIDEMO): %: $(demodir)/%.c
125126
$(TESTS): %: $(testdir)/%.c
126127
$(BUILD) $(DEMOFLAGS) -o$@ $< $(LDFLAGS)
127128

129+
vt0$(E): %: $(testdir)/%.c
130+
$(BUILD) $(DEMOFLAGS) -o$@ $< $(LDFLAGS)
131+
128132
tuidemo$(E) : tuidemo.o tui.o
129133
$(LINK) tui.o tuidemo.o -o $@ $(LDFLAGS)
130134

0 commit comments

Comments
 (0)