Introduction
Introduction Statistics Contact Development Disclaimer Help
lchat.c - lchat - A line oriented chat front end for ii.
git clone git://git.suckless.org/lchat
Log
Files
Refs
README
---
lchat.c (9376B)
---
1 /*
2 * Copyright (c) 2015-2023 Jan Klemkow <[email protected]>
3 * Copyright (c) 2022-2023 Tom Schwindl <[email protected]>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANT…
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE F…
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT …
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18 #include <sys/ioctl.h>
19
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <libgen.h>
23 #include <limits.h>
24 #include <poll.h>
25 #include <signal.h>
26 #include <stdbool.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <termios.h>
31 #include <unistd.h>
32
33 #include "slackline.h"
34 #include "util.h"
35
36 #ifndef INFTIM
37 #define INFTIM -1
38 #endif
39
40 static struct termios origin_term;
41 static struct winsize winsize;
42 static char *TERM;
43
44 static void
45 sigwinch(int sig)
46 {
47 if (sig == SIGWINCH)
48 ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize);
49 }
50
51 static void
52 exit_handler(void)
53 {
54 /* reset terminal's window name */
55 set_title(TERM, TERM);
56
57 if (tcsetattr(STDIN_FILENO, TCSANOW, &origin_term) == -1)
58 die("tcsetattr:");
59 }
60
61 static char *
62 read_file_line(const char *file)
63 {
64 FILE *fh;
65 char buf[BUFSIZ];
66 char *line = NULL;
67 char *nl = NULL;
68
69 if (access(file, R_OK) == -1)
70 return NULL;
71
72 if ((fh = fopen(file, "r")) == NULL)
73 die("fopen:");
74
75 if (fgets(buf, sizeof buf, fh) == NULL)
76 die("fgets:");
77
78 if (fclose(fh) == EOF)
79 die("fclose:");
80
81 if ((nl = strchr(buf, '\n')) != NULL) /* delete new line …
82 *nl = '\0';
83
84 if ((line = strdup(buf)) == NULL)
85 die("strdup:");
86
87 return line;
88 }
89
90 static void
91 line_output(struct slackline *sl, char *file)
92 {
93 int fd;
94
95 if ((fd = open(file, O_WRONLY|O_APPEND)) == -1)
96 die("open: %s:", file);
97
98 if (write(fd, sl->buf, sl->blen) == -1)
99 die("write:");
100
101 if (close(fd) == -1)
102 die("close:");
103 }
104
105 static void
106 fork_filter(int *read, int *write)
107 {
108 int fds_read[2]; /* .filter -> lchat */
109 int fds_write[2]; /* lchat -> .filter */
110
111 if (pipe(fds_read) == -1)
112 die("pipe:");
113 if (pipe(fds_write) == -1)
114 die("pipe:");
115
116 switch (fork()) {
117 case -1:
118 die("fork of .filter");
119 break;
120 case 0: /* child */
121 if (dup2(fds_read[1], STDOUT_FILENO) == -1)
122 die("dup2:");
123 if (dup2(fds_write[0], STDIN_FILENO) == -1)
124 die("dup2:");
125
126 if (close(fds_read[0]) == -1)
127 die("close:");
128 if (close(fds_write[1]) == -1)
129 die("close:");
130
131 execl("./.filter", "./.filter", NULL);
132 die("exec of .filter");
133 }
134
135 /* parent */
136 if (close(fds_read[1]) == -1)
137 die("close:");
138 if (close(fds_write[0]) == -1)
139 die("close:");
140
141 *read = fds_read[0];
142 *write = fds_write[1];
143 }
144
145 static void
146 usage(void)
147 {
148 die("lchat [-aeh] [-n lines] [-p prompt] [-t title] [-i in] [-o …
149 " [directory]");
150 }
151
152 int
153 main(int argc, char *argv[])
154 {
155 #ifdef __OpenBSD__
156 if (pledge("stdio rpath wpath tty proc exec", NULL) == -1)
157 die("pledge:");
158 #endif
159 struct pollfd pfd[3];
160 struct termios term;
161 struct slackline *sl = sl_init();
162 int fd = STDIN_FILENO;
163 int read_fd = 6;
164 int read_filter = -1;
165 int backend_sink = STDOUT_FILENO;
166 char c;
167 int ch;
168 bool empty_line = false;
169 bool bell_flag = true;
170 bool ucspi = false;
171 char *bell_file = ".bellmatch";
172 size_t history_len = 5;
173 char *prompt = read_file_line(".prompt");
174 char *title = read_file_line(".title");
175
176 if ((TERM = getenv("TERM")) == NULL)
177 TERM = "";
178
179 if (sl == NULL)
180 die("Failed to initialize slackline");
181
182 if (prompt == NULL) /* set default prompt */
183 prompt = "> ";
184
185 size_t prompt_len = strlen(prompt);
186 size_t loverhang = 0;
187 char *dir = ".";
188 char *in_file = NULL;
189 char *out_file = NULL;
190
191 while ((ch = getopt(argc, argv, "an:i:eo:p:t:uhm:")) != -1) {
192 switch (ch) {
193 case 'a':
194 bell_flag = false;
195 break;
196 case 'n':
197 errno = 0;
198 history_len = strtoull(optarg, NULL, 0);
199 if (errno != 0)
200 die("strtoull:");
201 break;
202 case 'i':
203 in_file = optarg;
204 break;
205 case 'e':
206 empty_line = true;
207 break;
208 case 'o':
209 out_file = optarg;
210 break;
211 case 'p':
212 prompt = optarg;
213 prompt_len = strlen(prompt);
214 break;
215 case 't':
216 title = optarg;
217 break;
218 case 'u':
219 ucspi = true;
220 break;
221 case 'm':
222 if (strcmp(optarg, "emacs") == 0)
223 sl_mode(sl, SL_EMACS);
224 else
225 die("lchat: invalid mode");
226 break;
227 case 'h':
228 default:
229 usage();
230 /* NOTREACHED */
231 }
232 }
233 argc -= optind;
234 argv += optind;
235
236 if (argc > 1)
237 usage();
238
239 if (argc == 1)
240 if ((dir = strdup(argv[0])) == NULL)
241 die("strdup:");
242
243 if (in_file == NULL)
244 if (asprintf(&in_file, "%s/in", dir) == -1)
245 die("asprintf:");
246
247 if (out_file == NULL)
248 if (asprintf(&out_file, "%s/out", dir) == -1)
249 die("asprintf:");
250
251 if (isatty(fd) == 0)
252 die("isatty:");
253
254 /* set terminal's window title */
255 if (title == NULL) {
256 char path[PATH_MAX];
257 if (getcwd(path, sizeof path) == NULL)
258 die("getcwd:");
259 if ((title = basename(path)) == NULL)
260 die("basename:");
261 }
262 set_title(TERM, title);
263
264 /* prepare terminal reset on exit */
265 if (tcgetattr(fd, &origin_term) == -1)
266 die("tcgetattr:");
267
268 term = origin_term;
269
270 if (atexit(exit_handler) == -1)
271 die("atexit:");
272
273 term.c_iflag &= ~(BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
274 term.c_lflag &= ~(ECHO|ICANON|IEXTEN);
275 term.c_cflag &= ~(CSIZE|PARENB);
276 term.c_cflag |= CS8;
277 term.c_cc[VMIN] = 1;
278 term.c_cc[VTIME] = 0;
279
280 if (tcsetattr(fd, TCSANOW, &term) == -1)
281 die("tcsetattr:");
282
283 /* get the terminal size */
284 sigwinch(SIGWINCH);
285 signal(SIGWINCH, sigwinch);
286
287 setbuf(stdin, NULL);
288 setbuf(stdout, NULL);
289
290 if (!ucspi) {
291 char tail_cmd[BUFSIZ];
292 FILE *fh;
293
294 /* open external source */
295 snprintf(tail_cmd, sizeof tail_cmd, "exec tail -n %zu -f…
296 history_len, out_file);
297
298 if ((fh = popen(tail_cmd, "r")) == NULL)
299 die("unable to open pipe to tail:");
300
301 read_fd = fileno(fh);
302 }
303
304 int nfds = 2;
305
306 pfd[0].fd = fd;
307 pfd[0].events = POLLIN;
308
309 pfd[1].fd = read_fd;
310 pfd[1].events = POLLIN;
311
312 if (access(".filter", X_OK) == 0) {
313 fork_filter(&read_filter, &backend_sink);
314
315 pfd[2].fd = read_filter;
316 pfd[2].events = POLLIN;
317
318 nfds = 3;
319 }
320
321 /* print initial prompt */
322 fputs(prompt, stdout);
323
324 for (;;) {
325 if (fflush(stdout) == EOF)
326 die("fflush:");
327
328 errno = 0;
329 if (poll(pfd, nfds, INFTIM) == -1 && errno != EINTR)
330 die("poll:");
331
332 /* moves cursor back after linewrap */
333 if (loverhang > 0) {
334 fputs("\r\033[2K", stdout); /* cr + ... */
335 printf("\033[%zuA", loverhang); /* x time…
336 }
337
338 /* carriage return and erase the whole line */
339 fputs("\r\033[2K", stdout);
340
341 /* handle keyboard intput */
342 if (pfd[0].revents & POLLIN) {
343 ssize_t ret = read(fd, &c, sizeof c);
344
345 if (ret == -1)
346 die("read:");
347
348 if (ret == 0)
349 return EXIT_SUCCESS;
350
351 switch (c) {
352 case 13: /* return */
353 if (sl->rlen == 0 && empty_line == false)
354 goto out;
355 /* replace NUL-terminator with newline */
356 sl->buf[sl->blen++] = '\n';
357 if (ucspi) {
358 if (write(7, sl->buf, sl->blen) …
359 die("write:");
360 } else {
361 line_output(sl, in_file);
362 }
363 sl_reset(sl);
364 break;
365 case 12: /* ctrl+l -- clear screen, same as clea…
366 fputs("\x1b[2J\x1b[H", stdout);
367 break;
368 default:
369 if (sl_keystroke(sl, c) == -1)
370 die("sl_keystroke");
371 }
372 }
373
374 /* handle backend error and its broken pipe */
375 if (pfd[1].revents & POLLHUP)
376 break;
377 if (pfd[1].revents & POLLERR || pfd[1].revents & POLLNVA…
378 die("backend error");
379
380 /* handle backend input */
381 if (pfd[1].revents & POLLIN) {
382 char buf[BUFSIZ];
383 ssize_t n = read(pfd[1].fd, buf, sizeof buf);
384 if (n == 0)
385 die("backend exited");
386 if (n == -1)
387 die("read:");
388 if (write(backend_sink, buf, n) == -1)
389 die("write:");
390
391 /* terminate the input buffer with NUL */
392 buf[n == BUFSIZ ? n - 1 : n] = '\0';
393
394 /* ring the bell on external input */
395 if (bell_flag && bell_match(buf, bell_file))
396 putchar('\a');
397 }
398
399 /* handel optional .filter i/o */
400 if (nfds > 2) {
401 /* handle .filter error and its broken pipe */
402 if (pfd[2].revents & POLLHUP)
403 break;
404 if (pfd[2].revents & POLLERR ||
405 pfd[2].revents & POLLNVAL)
406 die(".filter error");
407
408 /* handle .filter output */
409 if (pfd[2].revents & POLLIN) {
410 char buf[BUFSIZ];
411 ssize_t n = read(pfd[2].fd, buf, sizeof …
412 if (n == 0)
413 die(".filter exited");
414 if (n == -1)
415 die("read:");
416 if (write(STDOUT_FILENO, buf, n) == -1)
417 die("write:");
418 }
419 }
420 out:
421 /* show current input line */
422 fputs(prompt, stdout);
423 fputs(sl->buf, stdout);
424
425 /* save amount of overhanging lines */
426 loverhang = (prompt_len + sl->rlen) / winsize.ws_col;
427
428 /* correct line wrap handling */
429 if ((prompt_len + sl->rlen) > 0 &&
430 (prompt_len + sl->rlen) % winsize.ws_col == 0)
431 fputs("\n", stdout);
432
433 if (sl->rcur < sl->rlen) { /* move the cursor */
434 putchar('\r');
435 /* HACK: because \033[0C does the same as \033[1…
436 if (sl->rcur + prompt_len > 0)
437 printf("\033[%zuC", sl->rcur + prompt_le…
438 }
439 }
440 return EXIT_SUCCESS;
441 }
You are viewing proxied material from suckless.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.