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
|
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include "framebuffer.h"
#include "uint8toa.h"
size_t term_width = 0,
term_height = 0;
struct termios initial_terminal_state = {0};
Framebuffer fb_new(size_t width, size_t height) {
Framebuffer fb;
/* In case malloc() fails and we return early */
fb.width = 0;
fb.height = 0;
fb.fb = malloc(sizeof(Pixel *) * height);
if (fb.fb == NULL) return fb;
for (size_t i = 0; i < height; i++) {
fb.fb[i] = malloc(sizeof(Pixel) * width);
if (fb.fb[i] == NULL) {
/* FIXME: Memory leak; should free all prev. malloced rows */
fb.fb = NULL;
return fb;
}
}
fb.print_buf = malloc(42 * width * height);
if (fb.print_buf == NULL) {
/* FIXME: see above */
fb.fb = NULL;
return fb;
}
fb.width = width;
fb.height = height;
return fb;
}
void fb_free(Framebuffer *fb) {
if (fb->fb != NULL) {
for (size_t i = 0; i < fb->height; i++) {
free(fb->fb[i]);
}
free(fb->fb);
}
if (fb->print_buf != NULL)
free(fb->print_buf);
fb->width = 0;
fb->height = 0;
return;
}
void fb_fill(Framebuffer fb, Pixel pixel) {
for (size_t row = 0; row < fb.height; row++) {
for (size_t col = 0; col < fb.width; col++) {
fb.fb[row][col] = pixel;
}
}
}
void fb_put(Framebuffer fb, size_t row, size_t col, Pixel pixel) {
fb.fb[row][col] = pixel;
}
void fb_vert_line(Framebuffer fb, size_t x, size_t y1, size_t y2, Pixel pixel) {
assert(y2 >= y1);
for (size_t y = y1; y <= y2; y++) {
fb.fb[y][x] = pixel;
}
}
void fb_text(Framebuffer fb, long x, size_t y, char *s, Colour fg, Colour bg) {
if (x >= (long)term_width || y >= term_height) return; /* OOB */
while (x < 0 && *s != '\0') { x++, s++; }
Pixel p = {fg, bg, '\0'};
for(; *s != '\0' && x < (long)term_width; s++, x++) {
p.ch = *s;
fb.fb[y][x] = p;
}
}
void fb_text_no_bg(Framebuffer fb, long x, size_t y, char *s, Colour fg) {
if (x >= (long)term_width || y >= term_height) return; /* OOB */
while (x < 0 && *s != '\0') { x++, s++; }
for(; *s != '\0' && x < (long)term_width; s++, x++) {
fb.fb[y][x].ch = *s;
fb.fb[y][x].fg = fg;
}
}
void term_init(void) {
tcgetattr(0, &initial_terminal_state);
struct termios new = initial_terminal_state;
new.c_lflag &= ~ICANON; /* Turn off canonical mode */
new.c_lflag &= ~ECHO; /* Turn off echoing */
new.c_cc[VMIN] = 0; /* Return read(STDIN_FILENO, ...) even if no input */
new.c_cc[VTIME] = 0; /* ^, make it return immediately, without waiting */
tcsetattr(0, TCSANOW, &new);
write(STDOUT_FILENO, "\x1b[?1049h", 8); /* Alternative screen buffer */
write(STDOUT_FILENO, "\x1b[H", 3); /* Move cursor to top-left */
write(STDOUT_FILENO, "\x1b[?25l", 6); /* Hide the cursor */
term_update_size();
}
void term_cleanup(void) {
tcsetattr(0, TCSANOW, &initial_terminal_state);
write(STDOUT_FILENO, "\x1b[?25h", 6); /* Show the cursor */
write(STDOUT_FILENO, "\x1b[?1049l", 8); /* Alternative screen buffer */
write(STDOUT_FILENO, "\x1b[0m", 4); /* Reset styling (colour) */
}
void term_update_size(void) {
#if defined(TIOCGWINSZ)
struct winsize ws;
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) {
term_height = ws.ws_row;
term_width = ws.ws_col;
return;
}
#elif defined(TIOCGSIZE)
struct ttysize ts;
if (ioctl(STDOUT_FILENO, TIOCGSIZE, &ts) == 0) {
term_height = ts.ts_row;
term_width = ts.ts_col;
return;
}
#else
#error "Neither TIOCGWINSZ or TIOCGSIZE are defined, can't proceed"
#endif
}
/* Converts pixel to ascii and puts it in buf, used in fb_print().
* This is faster than sprinf().
* TODO: maybe less memcpy()s will be better? */
static inline void pixel_to_ascii(char *buf, Pixel p) {
memcpy(buf, "\x1b[38;2;XXX;XXX;XXXm\x1b[48;2;XXX;XXX;XXXmXXXX", 42);
memcpy(buf + 7, uint8toa[p.fg.r], 3);
memcpy(buf + 11, uint8toa[p.fg.g], 3);
memcpy(buf + 15, uint8toa[p.fg.b], 3);
memcpy(buf + 26, uint8toa[p.bg.r], 3);
memcpy(buf + 30, uint8toa[p.bg.g], 3);
memcpy(buf + 34, uint8toa[p.bg.b], 3);
memcpy(buf + 38, &p.ch, 4);
}
void fb_print(Framebuffer fb) {
/* TODO: move this to buffer, maybe? to avoid an extra write every 'frame' */
write(STDOUT_FILENO, "\x1b[H", 3); /* Move cursor to top-left */
/* 'cursor' of print_buf, used to build it */
char *cur = fb.print_buf;
/* build the buffer */
for (size_t row = 0; row < fb.height; row++) {
for (size_t col = 0; col < fb.width; col++) {
pixel_to_ascii(cur, fb.fb[row][col]);
cur += 42;
}
}
/* flush the buffer */
write(STDOUT_FILENO, fb.print_buf, 42 * fb.width * fb.height);
}
__attribute__((unused))
static inline void set_fg(Colour col) {
fprintf(stdout, "\x1b[38;2;%u;%u;%um", col.r, col.g, col.g);
fflush(stdout);
}
__attribute__((unused))
static inline void set_bg(Colour col) {
fprintf(stdout, "\x1b[48;2;%u;%u;%um", col.r, col.g, col.g);
fflush(stdout);
}
|