diff options
Diffstat (limited to 'src/dumbsh.c')
-rw-r--r-- | src/dumbsh.c | 283 |
1 files changed, 283 insertions, 0 deletions
diff --git a/src/dumbsh.c b/src/dumbsh.c new file mode 100644 index 0000000..9ad0204 --- /dev/null +++ b/src/dumbsh.c | |||
@@ -0,0 +1,283 @@ | |||
1 | /* dumbsh.c - A really dumb shell, to demonstrate handle_keys usage. | ||
2 | * | ||
3 | * Copyright 2014 David Seikel <won_fang@yahoo.com.au> | ||
4 | * | ||
5 | * Not a real shell, so doesn't follow any standards, | ||
6 | * coz it wont implement them anyway. | ||
7 | |||
8 | USE_DUMBSH(NEWTOY(dumbsh, "", TOYFLAG_USR|TOYFLAG_BIN)) | ||
9 | |||
10 | config DUMBSH | ||
11 | bool "dumbsh" | ||
12 | default n | ||
13 | help | ||
14 | usage: dumbsh | ||
15 | |||
16 | A really dumb shell. | ||
17 | */ | ||
18 | |||
19 | #include "toys.h" | ||
20 | #include "lib/handlekeys.h" | ||
21 | |||
22 | typedef void (*eventHandler) (void); | ||
23 | |||
24 | struct keyCommand | ||
25 | { | ||
26 | char *key; | ||
27 | eventHandler handler; | ||
28 | }; | ||
29 | |||
30 | GLOBALS( | ||
31 | unsigned h, w; | ||
32 | int x, y; | ||
33 | struct double_list *history; | ||
34 | ) | ||
35 | |||
36 | #define TT this.dumbsh | ||
37 | |||
38 | // Sanity check cursor location and update the current line. | ||
39 | static void updateLine() | ||
40 | { | ||
41 | if (0 > TT.x) TT.x = 0; | ||
42 | if (0 > TT.y) TT.y = 0; | ||
43 | if (TT.w < TT.x) TT.x = TT.w; | ||
44 | if (TT.h < TT.y) TT.y = TT.h; | ||
45 | if (strlen(toybuf) < TT.x) TT.x = strlen(toybuf); | ||
46 | printf("\x1B[%d;0H%-*s\x1B[%d;%dH", | ||
47 | TT.y + 1, TT.w, toybuf, TT.y + 1, TT.x + 1); | ||
48 | fflush(stdout); | ||
49 | } | ||
50 | |||
51 | // The various commands. | ||
52 | static void deleteChar() | ||
53 | { | ||
54 | int j; | ||
55 | |||
56 | for (j = TT.x; toybuf[j]; j++) | ||
57 | toybuf[j] = toybuf[j + 1]; | ||
58 | updateLine(); | ||
59 | } | ||
60 | |||
61 | static void backSpaceChar() | ||
62 | { | ||
63 | if (TT.x) | ||
64 | { | ||
65 | TT.x--; | ||
66 | deleteChar(); | ||
67 | } | ||
68 | } | ||
69 | |||
70 | // This is where we would actually deal with | ||
71 | // what ever command the user had typed in. | ||
72 | // For now we just move on to the next line. | ||
73 | // TODO - We would want to redirect I/O, capture some keys (^C), | ||
74 | // but pass the rest on. | ||
75 | // Dunno yet how to deal with that. | ||
76 | // We still want handle_keys to be doing it's thing, | ||
77 | // so maybe handing it another fd, and a callback. | ||
78 | // A function to add and remove fd and callback pairs for | ||
79 | // handle_keys to check? | ||
80 | static void doCommand() | ||
81 | { | ||
82 | toybuf[0] = 0; | ||
83 | TT.x = 0; | ||
84 | TT.y++; | ||
85 | printf("\n"); | ||
86 | fflush(stdout); | ||
87 | updateLine(); | ||
88 | } | ||
89 | |||
90 | static void endOfLine() | ||
91 | { | ||
92 | TT.x = strlen(toybuf); | ||
93 | updateLine(); | ||
94 | } | ||
95 | |||
96 | static void leftChar() | ||
97 | { | ||
98 | TT.x--; | ||
99 | updateLine(); | ||
100 | } | ||
101 | |||
102 | static void nextHistory() | ||
103 | { | ||
104 | TT.history = TT.history->next; | ||
105 | strcpy(toybuf, TT.history->data); | ||
106 | TT.x = strlen(toybuf); | ||
107 | updateLine(); | ||
108 | } | ||
109 | |||
110 | static void prevHistory() | ||
111 | { | ||
112 | TT.history = TT.history->prev; | ||
113 | strcpy(toybuf, TT.history->data); | ||
114 | TT.x = strlen(toybuf); | ||
115 | updateLine(); | ||
116 | } | ||
117 | |||
118 | static void quit() | ||
119 | { | ||
120 | handle_keys_quit(); | ||
121 | } | ||
122 | |||
123 | static void rightChar() | ||
124 | { | ||
125 | TT.x++; | ||
126 | updateLine(); | ||
127 | } | ||
128 | |||
129 | static void startOfLine() | ||
130 | { | ||
131 | TT.x = 0; | ||
132 | updateLine(); | ||
133 | } | ||
134 | |||
135 | // The key to command mappings, Emacs style. | ||
136 | static const struct keyCommand simpleEmacsKeys[] = | ||
137 | { | ||
138 | {"BS", backSpaceChar}, | ||
139 | {"Del", deleteChar}, | ||
140 | {"^D", deleteChar}, | ||
141 | {"Return", doCommand}, | ||
142 | {"Enter", doCommand}, | ||
143 | {"Down", nextHistory}, | ||
144 | {"^N", nextHistory}, | ||
145 | {"End", endOfLine}, | ||
146 | {"^E", endOfLine}, | ||
147 | {"Left", leftChar}, | ||
148 | {"^B", leftChar}, | ||
149 | {"^X^C", quit}, | ||
150 | {"^C", quit}, | ||
151 | {"Right", rightChar}, | ||
152 | {"^F", rightChar}, | ||
153 | {"Home", startOfLine}, | ||
154 | {"^A", startOfLine}, | ||
155 | {"Up", prevHistory}, | ||
156 | {"^P", prevHistory} | ||
157 | }; | ||
158 | |||
159 | // Callback for incoming sequences from the terminal. | ||
160 | static int handleEvent(long extra, struct keyevent *event) | ||
161 | { | ||
162 | switch (event->type) | ||
163 | { | ||
164 | case HK_KEYS : | ||
165 | { | ||
166 | int j, l = strlen(event->sequence); | ||
167 | |||
168 | // Search for a key sequence bound to a command. | ||
169 | for (j = 0; j < ARRAY_LEN(simpleEmacsKeys); j++) | ||
170 | { | ||
171 | if (strncmp(simpleEmacsKeys[j].key, event->sequence, l) == 0) | ||
172 | { | ||
173 | // If it's a partial match, keep accumulating them. | ||
174 | if (strlen(simpleEmacsKeys[j].key) != l) | ||
175 | return 0; | ||
176 | else | ||
177 | { | ||
178 | if (simpleEmacsKeys[j].handler) simpleEmacsKeys[j].handler(); | ||
179 | return 1; | ||
180 | } | ||
181 | } | ||
182 | } | ||
183 | |||
184 | // See if it's ordinary keys. | ||
185 | // NOTE - with vi style ordinary keys can be commands, | ||
186 | // but they would be found by the command check above first. | ||
187 | if (!event->isTranslated) | ||
188 | { | ||
189 | if (TT.x < sizeof(toybuf)) | ||
190 | { | ||
191 | int j, l = strlen(event->sequence); | ||
192 | |||
193 | for (j = strlen(toybuf); j >= TT.x; j--) | ||
194 | toybuf[j + l] = toybuf[j]; | ||
195 | for (j = 0; j < l; j++) | ||
196 | toybuf[TT.x + j] = event->sequence[j]; | ||
197 | TT.x += l; | ||
198 | updateLine(); | ||
199 | } | ||
200 | } | ||
201 | break; | ||
202 | } | ||
203 | |||
204 | case HK_CSI : | ||
205 | { | ||
206 | // Is it a cursor location report? | ||
207 | if (strcmp("R", event->sequence) == 0) | ||
208 | { | ||
209 | // Parameters are cursor line and column. | ||
210 | // NOTE - This may be sent at other times, not just during terminal resize. | ||
211 | // We are assuming here that it's a resize. | ||
212 | // The defaults are 1, which get ignored by the heuristic below. | ||
213 | int r = event->params[0], c = event->params[1]; | ||
214 | |||
215 | // Check it's not an F3 key variation, coz some of them use | ||
216 | // the same CSI function command. | ||
217 | // This is a heuristic, we are checking against an unusable terminal size. | ||
218 | if ((2 == event->count) && (8 < r) && (8 < c)) | ||
219 | { | ||
220 | TT.h = r; | ||
221 | TT.w = c; | ||
222 | updateLine(); | ||
223 | } | ||
224 | break; | ||
225 | } | ||
226 | } | ||
227 | |||
228 | default : break; | ||
229 | } | ||
230 | |||
231 | // Tell handle_keys to drop it, coz we dealt with it, or it's not one of ours. | ||
232 | return 1; | ||
233 | } | ||
234 | |||
235 | void dumbsh_main(void) | ||
236 | { | ||
237 | struct termios termIo, oldTermIo; | ||
238 | char *t = getenv("HOME"); | ||
239 | int fd; | ||
240 | |||
241 | // Load bash history. | ||
242 | t = xmprintf("%s/%s", t ? t : "", ".bash_history"); | ||
243 | if (-1 != (fd = open(t, O_RDONLY))) | ||
244 | { | ||
245 | while ((t = get_line(fd))) TT.history = dlist_add(&TT.history, t); | ||
246 | close(fd); | ||
247 | } | ||
248 | if (!TT.history) | ||
249 | TT.history = dlist_add(&TT.history, ""); | ||
250 | |||
251 | // Grab the old terminal settings and save it. | ||
252 | tcgetattr(0, &oldTermIo); | ||
253 | tcflush(0, TCIFLUSH); | ||
254 | termIo = oldTermIo; | ||
255 | |||
256 | // Mould the terminal to our will. | ||
257 | // In this example we are turning off all the terminal smarts, but real code | ||
258 | // might not want that. | ||
259 | termIo.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | ||
260 | | IUCLC | IXON | IXOFF | IXANY); | ||
261 | termIo.c_oflag &= ~OPOST; | ||
262 | termIo.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | TOSTOP | ICANON | ISIG | ||
263 | | IEXTEN); | ||
264 | termIo.c_cflag &= ~(CSIZE | PARENB); | ||
265 | termIo.c_cflag |= CS8; | ||
266 | termIo.c_cc[VTIME]=0; // deciseconds. | ||
267 | termIo.c_cc[VMIN]=1; | ||
268 | tcsetattr(0, TCSANOW, &termIo); | ||
269 | |||
270 | // Let the mouldy old terminal mold us. | ||
271 | TT.w = 80; | ||
272 | TT.h = 24; | ||
273 | terminal_size(&TT.w, &TT.h); | ||
274 | |||
275 | // Let's rock! | ||
276 | updateLine(); | ||
277 | handle_keys(0, handleEvent); | ||
278 | |||
279 | // Clean up. | ||
280 | tcsetattr(0, TCSANOW, &oldTermIo); | ||
281 | puts(""); | ||
282 | fflush(stdout); | ||
283 | } | ||