-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathterm.c
161 lines (130 loc) · 4.68 KB
/
term.c
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
#undef _POSIX_C_SOURCE
#undef _XOPEN_SOURCE
#define _POSIX_C_SOURCE 2008'19L
#define _XOPEN_SOURCE 700
#include "term.h"
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
#include "io.h"
#include "sequences.h"
static bool parse_long(const char s[static 1], int base, long val[static 1])
{
char *endptr;
errno = 0;
const long i = strtol(s, &endptr, base);
if (endptr == s || *endptr != '\0' || errno != 0) {
return false;
}
*val = i;
return true;
}
static TermCodes get_cursor_pos(WinInfo wi[static 1])
{
/* The cursor is positioned at the bottom right of the window. We can learn
* how many rows and columns there must be on the screen by querying the
* position of the cursor. */
ssize_t len = (ssize_t) strlen(GET_CUR_POS);
if (write_eintr(STDOUT_FILENO, GET_CUR_POS, len) != len) {
return TERM_FAILURE;
}
/* The reply is an escape sequence of the form '\x1b[24;80R', we will
* read it into a buffer until read() returns EOF or until we get to
* the 'R' character. */
char buf[32]; /* Should be more than enough. */
for (size_t i = 0; i < sizeof buf - 1; ++i) {
if (read_eintr(STDIN_FILENO, &buf[i], 1) != 1 || buf[i] == 'R') {
break;
}
}
*buf = '\0';
/* Skip the escape character and the left square brace. */
return memcmp(buf, "\x1b[", 2) != 0
|| sscanf(&buf[2], "%u;%u", &wi->rows, &wi->cols) != 2
? TERM_FAILURE
: TERM_SUCCESS;
}
TermCodes term_get_winsize(WinInfo wi[static 1])
{
#if defined(TIOCGWINSZ)
struct winsize ws;
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) {
wi->rows = ws.ws_row;
wi->cols = ws.ws_col;
return TERM_SUCCESS;
}
#elif defined(TIOCGSIZE)
struct ttysize ts;
if (ioctl(STDOUT_FILENO, TIOCGSIZE, &ts) == 0) {
wi->rows = ts.ts_row;
wi->cols = ts.ts_col;
return TERM_SUCCESS;
}
#endif /* defined(TIOCGWINSZ) */
/* ioctl() failed. Fallback to VT100/ANSI escape sequences. */
ssize_t len = (ssize_t) strlen(POS_CUR_BOTTOM_RIGHT);
if (write_eintr(STDOUT_FILENO, POS_CUR_BOTTOM_RIGHT, len) == len) {
if (get_cursor_pos(wi) == TERM_SUCCESS) {
return TERM_SUCCESS;
}
}
/* write() or get_cursor_pos() failed as well. Now as a last resort, check
* LINES and COLUMNS environment variables.
* Though note that these variables are not reliable, are not guaranteed
* to exist, and might not be up to date if the user changes the terminal
* size. If set, the sh, ash, dash, csh shells do not update LINES and
* COLUMNS, but bash, fish, zsh, ksh, ksh93u+m, and tcsh handle SIGWINCH to
* update these variables. */
const char *const rows = getenv("LINES");
const char *const cols = getenv("COLUMNS");
if (rows != nullptr && cols != nullptr) {
long r;
long c;
bool res = parse_long(rows, 10, &r) && parse_long(cols, 10, &c);
if (!res || r > UINT_MAX || c > UINT_MAX) {
return TERM_FAILURE;
}
wi->rows = (unsigned int) r;
wi->cols = (unsigned int) c;
return TERM_SUCCESS;
}
return TERM_FAILURE;
}
TermCodes term_enable_raw_mode(struct termios old[static 1])
{
/* Save the old terminal attributes. */
if (tcgetattr(STDIN_FILENO, old) == -1) {
return TERM_TCGETATTR_FAILED;
}
struct termios new = *old;
/* input modes - no break, no CR to NL, no parity check, no strip char,
* no start/stop output control. */
new.c_iflag &= ~(0u | IGNBRK | BRKINT | PARMRK | INPCK | ISTRIP | ICRNL |
INLCR | IGNCR | IXON);
/* local modes - echoing off, canonical off, no extended functions,
* no signal chars (^Z, ^C) */
new.c_lflag &= ~(0u | ISIG | IEXTEN | ECHO | ICANON);
/* control modes - set 8 bit chars. */
new.c_cflag |= (0u | CS8);
/* output modes - disable post processing. */
new.c_oflag &= ~(0u | OPOST);
/* control chars - set return condition: min number of bytes and timer.
* We want read(2) to return every single byte, without timeout. */
new.c_cc[VMIN] = 1; /* 1 byte */
new.c_cc[VTIME] = 0; /* No timer */
/* Change attributes when output has drained; also flush pending input. */
return tcsetattr(STDIN_FILENO, TCSAFLUSH, &new) == -1
? TERM_TCSETATTR_FAILED
: TERM_SUCCESS;
}
TermCodes term_disable_raw_mode(struct termios old[static 1])
{
return tcsetattr(STDIN_FILENO, TCSAFLUSH, old) == -1
? TERM_TCSETATTR_FAILED
: TERM_SUCCESS;
}