1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
|
# terminal.tcl --
#
# Implements a virtual terminal with basic read/write procs.
#
namespace eval Terminal {
# From `man console_codes`
variable keymap [dict create \
BACKSPACE "\x08" \
TAB "\x09" \
ENTER "\x0d" \
DELETE "\x7f" \
ESC "\x1b" \
UP "\x1b\[A" \
DOWN "\x1b\[B" \
RIGHT "\x1b\[C" \
LEFT "\x1b\[D" \
]
proc _remap {key ctrlPressed} {
variable keymap
if {[string length $key] == 1} {
# Convert ctrl-A through ctrl-Z and others to terminal control characters
if {$ctrlPressed} {
set charCode [scan [string toupper $key] %c]
if {$charCode >= 64 && $charCode <= 95} {
set charCode [expr {$charCode - 64}]
return [format %c $charCode]
}
}
# All other single char keys can be passed through
return $key
}
if {[dict exists $keymap $key]} {
return [dict get $keymap $key]
}
return ""
}
proc create {rows cols cmd} {
termCreate $rows $cols [list bash -c $cmd ""]
}
proc destroy {term} {
termDestroy $term
}
# Writes a keyboard key to the terminal, handling control codes
proc write {term key ctrlPressed} {
set key [_remap $key $ctrlPressed]
if {[string length $key] > 0} {
termWrite $term $key
}
}
# Returns a newline separated string of terminal lines
proc read {term} {
termRead $term
}
}
set cc [c create]
$cc cflags -I./vendor/libtmt ./vendor/libtmt/tmt.c
if {$::tcl_platform(os) ne "Darwin"} {
c loadlibLd libutil
}
$cc cflags -lutil
$cc include <sys/types.h>
$cc include <stdlib.h>
$cc include <unistd.h>
if {$::tcl_platform(os) eq "Darwin"} {
$cc include <util.h>
} else {
$cc include <pty.h>
}
$cc include <fcntl.h>
$cc include <string.h>
$cc include <sys/time.h>
$cc include <signal.h>
$cc include "tmt.h"
$cc struct VTerminal {
TMT* tmt;
int pty_fd;
int pid;
// Note: display has 1 more column than tmt screen to hold newlines between each line
char* display;
int curs_r;
int curs_c;
int ncols;
};
$cc code {
#define PTYBUF 4096
char iobuf[PTYBUF];
char* charAt(VTerminal *vt, int r, int c) {
int i = r * (vt->ncols + 1) + c;
return &vt->display[i];
}
void tmtEvent(tmt_msg_t m, TMT *tmt, const void *a, void *p) {
VTerminal *vt = (VTerminal*)p;
const TMTSCREEN *s = tmt_screen(tmt);
if (m == TMT_MSG_UPDATE) {
for (size_t r = 0; r < s->nline; r++){
if (s->lines[r]->dirty){
for (size_t c = 0; c < s->ncol; c++){
*charAt(vt, r, c) = s->lines[r]->chars[c].c;
}
}
}
tmt_clean(tmt);
}
}
void blinkCursor(VTerminal *vt) {
// Restore char under old cursor
const TMTSCREEN *s = tmt_screen(vt->tmt);
*charAt(vt, vt->curs_r, vt->curs_c) = s->lines[vt->curs_r]->chars[vt->curs_c].c;
// Update new cursor
const TMTPOINT *c = tmt_cursor(vt->tmt);
vt->curs_r = c->r;
vt->curs_c = c->c;
// Replace char with cursor every other second
struct timeval tv;
gettimeofday(&tv, NULL);
if (tv.tv_sec % 2 == 0) {
*charAt(vt, vt->curs_r, vt->curs_c) = 0xDB; // block char: █
}
}
}
$cc proc termCreate {int rows int cols char* cmd[]} VTerminal* {
int i = 0;
while (true) {
// execvp requires cmd array to be terminated by null pointer
if (strlen(cmd[i]) == 0) { cmd[i] = NULL; break; }
i++;
}
VTerminal *vt = malloc(sizeof(VTerminal));
vt->curs_r = 0;
vt->curs_c = 0;
vt->ncols = cols;
vt->display = malloc(sizeof(char[rows][cols + 1]));
for (int r = 0; r < rows - 1; r++) {
*charAt(vt, r, cols) = '\n';
}
*charAt(vt, rows - 1, cols) = '\0';
vt->tmt = tmt_open(rows, cols, tmtEvent, vt, NULL);
struct winsize ws = {.ws_row = rows, .ws_col = cols};
pid_t pid = forkpty(&vt->pty_fd, NULL, NULL, &ws);
if (pid < 0){
return NULL;
} else if (pid == 0){
setenv("TERM", "ansi", 1);
if (execvp(cmd[0], cmd) == -1) {
fprintf(stderr, "execvp(%s, ...) failed: %m\n", cmd[0]);
}
return NULL;
}
vt->pid = pid;
fcntl(vt->pty_fd, F_SETFL, O_NONBLOCK);
return vt;
}
$cc proc termDestroy {VTerminal* vt} void {
kill(vt->pid, SIGTERM);
close(vt->pty_fd);
free(vt->display);
free(vt);
}
$cc proc termRead {VTerminal* vt} char* {
ssize_t r = read(vt->pty_fd, iobuf, PTYBUF);
if (r > 0) {
tmt_write(vt->tmt, iobuf, r);
}
blinkCursor(vt);
return vt->display;
}
$cc proc termWrite {VTerminal* vt char* key} void {
write(vt->pty_fd, key, strlen(key));
}
$cc compile
|