fbpad: split the screen at row and column boundaries
[fbpad.git] / fbpad.c
blobd44106a0c0e781dc675b96eca960b091bc8cc3be
1 /*
2 * FBPAD FRAMEBUFFER VIRTUAL TERMINAL
4 * Copyright (C) 2009-2021 Ali Gholami Rudi <ali at rudi dot ir>
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #include <ctype.h>
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <poll.h>
22 #include <stdio.h>
23 #include <signal.h>
24 #include <string.h>
25 #include <sys/ioctl.h>
26 #include <sys/wait.h>
27 #include <termios.h>
28 #include <unistd.h>
29 #include <linux/vt.h>
30 #include "conf.h"
31 #include "fbpad.h"
32 #include "draw.h"
34 #define CTRLKEY(x) ((x) - 96)
35 #define POLLFLAGS (POLLIN | POLLHUP | POLLERR | POLLNVAL)
36 #define NTAGS (sizeof(tags) - 1)
37 #define NTERMS (NTAGS * 2)
38 #define TERMOPEN(i) (terms[i].fd)
39 #define TERMSNAP(i) (strchr(TAGS_SAVED, tags[(i) % NTAGS]))
41 static char tags[] = TAGS;
42 static struct term terms[NTERMS];
43 static int tops[NTAGS]; /* top terms of tags */
44 static int split[NTAGS]; /* terms are shown together */
45 static int ctag; /* current tag */
46 static int ltag; /* the last tag */
47 static int exitit;
48 static int hidden; /* do not touch the framebuffer */
49 static int locked;
50 static int taglock; /* disable tag switching */
51 static char pass[1024];
52 static int passlen;
53 static int cmdmode; /* execute a command and exit */
55 static int readchar(void)
57 char b;
58 if (read(0, &b, 1) > 0)
59 return (unsigned char) b;
60 return -1;
63 /* the current terminal */
64 static int cterm(void)
66 return tops[ctag] * NTAGS + ctag;
69 /* the other terminal in the same tag */
70 static int aterm(int n)
72 return n < NTAGS ? n + NTAGS : n - NTAGS;
75 /* the next terminal */
76 static int nterm(void)
78 int n = (cterm() + 1) % NTERMS;
79 while (n != cterm()) {
80 if (TERMOPEN(n))
81 break;
82 n = (n + 1) % NTERMS;
84 return n;
87 static struct term *fterm_main(void)
89 return TERMOPEN(cterm()) ? &terms[cterm()] : NULL;
92 #define BRWID 2
93 #define BRCLR 0xff0000
95 static void fterm_conf(int idx)
97 int h1 = fb_rows() / 2 / pad_crows() * pad_crows();
98 int h2 = fb_rows() - h1 - 4 * BRWID;
99 int w1 = fb_cols() / 2 / pad_ccols() * pad_ccols();
100 int w2 = fb_cols() - w1 - 4 * BRWID;
101 int tag = idx % NTAGS;
102 int top = idx < NTAGS;
103 if (split[tag] == 0)
104 pad_conf(0, 0, fb_rows(), fb_cols());
105 if (split[tag] == 1)
106 pad_conf(top ? BRWID : h1 + 3 * BRWID, BRWID,
107 top ? h1 : h2, fb_cols() - 2 * BRWID);
108 if (split[tag] == 2)
109 pad_conf(BRWID, top ? BRWID : w1 + 3 * BRWID,
110 fb_rows() - 2 * BRWID, top ? w1 : w2);
113 static void fterm_switch(int oidx, int nidx, int show, int save, int load)
115 int otag = oidx % NTAGS;
116 int ntag = nidx % NTAGS;
117 int bothvisible = otag == ntag && split[otag];
118 if (save && TERMOPEN(oidx) && TERMSNAP(oidx) && !bothvisible)
119 scr_snap(split[otag] ? otag : oidx);
120 term_save(&terms[oidx]);
121 if (show && split[otag] && otag == ntag)
122 pad_border(0, BRWID);
123 fterm_conf(nidx);
124 term_load(&terms[nidx], show);
125 if (show)
126 term_redraw(load && (load < 0 || !TERMOPEN(nidx) || !TERMSNAP(nidx) ||
127 (!bothvisible && scr_load(split[ntag] ? ntag : nidx))));
128 if (show && split[ntag])
129 pad_border(BRCLR, BRWID);
132 static void fterm_show(int n)
134 if (cterm() == n || cmdmode)
135 return;
136 if (taglock && ctag != n % NTAGS)
137 return;
138 if (ctag != n % NTAGS)
139 ltag = ctag;
140 if (ctag == n % NTAGS) {
141 if (split[n % NTAGS])
142 fterm_switch(cterm(), n, !hidden, 0, 0);
143 else
144 fterm_switch(cterm(), n, !hidden, !hidden, !hidden);
145 } else {
146 fterm_switch(cterm(), n, !hidden, !hidden, !hidden);
147 if (split[n % NTAGS]) {
148 fterm_switch(n, aterm(n), !hidden, 0, !hidden);
149 fterm_switch(aterm(n), n, !hidden, 0, 0);
152 ctag = n % NTAGS;
153 tops[ctag] = n / NTAGS;
156 static void tag_split(int n)
158 split[ctag] = n;
159 fterm_switch(cterm(), aterm(cterm()), !hidden, 0, -!hidden);
160 fterm_switch(aterm(cterm()), cterm(), !hidden, !hidden, -!hidden);
163 static void tag_show(int n)
165 fterm_show(tops[n] * NTAGS + n);
168 static void fterm_exec(char **args)
170 if (!fterm_main())
171 term_exec(args);
174 static void tag_list(void)
176 /* colors for tags based on their number of terminals */
177 int fg = 0x96cb5c, bg = 0x516f7b;
178 int colors[] = {0x173f4f, fg, 0x68cbc0 | FN_B};
179 int c = 0;
180 int r = pad_rows() - 1;
181 int i;
182 pad_put('T', r, c++, fg | FN_B, bg);
183 pad_put('A', r, c++, fg | FN_B, bg);
184 pad_put('G', r, c++, fg | FN_B, bg);
185 pad_put('S', r, c++, fg | FN_B, bg);
186 pad_put(':', r, c++, fg | FN_B, bg);
187 pad_put(' ', r, c++, fg | FN_B, bg);
188 for (i = 0; i < NTAGS && c + 2 < pad_cols(); i++) {
189 int nt = 0;
190 if (TERMOPEN(i))
191 nt++;
192 if (TERMOPEN(aterm(i)))
193 nt++;
194 pad_put(i == ctag ? '(' : ' ', r, c++, fg, bg);
195 if (TERMSNAP(i))
196 pad_put(tags[i], r, c++, !nt ? bg : colors[nt], colors[0]);
197 else
198 pad_put(tags[i], r, c++, colors[nt], bg);
199 pad_put(i == ctag ? ')' : ' ', r, c++, fg, bg);
201 for (; c < pad_cols(); c++)
202 pad_put(' ', r, c, fg, bg);
205 static void directkey(void)
207 char *shell[32] = SHELL;
208 char *mail[32] = MAIL;
209 char *editor[32] = EDITOR;
210 int c = readchar();
211 if (PASS && locked) {
212 if (c == '\r') {
213 pass[passlen] = '\0';
214 if (!strcmp(PASS, pass))
215 locked = 0;
216 passlen = 0;
217 return;
219 if (isprint(c) && passlen + 1 < sizeof(pass))
220 pass[passlen++] = c;
221 return;
223 if (c == ESC) {
224 switch ((c = readchar())) {
225 case 'c':
226 fterm_exec(shell);
227 return;
228 case 'm':
229 fterm_exec(mail);
230 return;
231 case 'e':
232 fterm_exec(editor);
233 return;
234 case 'j':
235 case 'k':
236 fterm_show(aterm(cterm()));
237 return;
238 case 'o':
239 tag_show(ltag);
240 return;
241 case 'p':
242 tag_list();
243 return;
244 case '\t':
245 if (nterm() != cterm())
246 fterm_show(nterm());
247 return;
248 case CTRLKEY('q'):
249 exitit = 1;
250 return;
251 case 's':
252 term_screenshot();
253 return;
254 case 'y':
255 term_redraw(1);
256 return;
257 case CTRLKEY('l'):
258 locked = 1;
259 passlen = 0;
260 return;
261 case CTRLKEY('o'):
262 taglock = 1 - taglock;
263 return;
264 case ',':
265 term_scrl(pad_rows() / 2);
266 return;
267 case '.':
268 term_scrl(-pad_rows() / 2);
269 return;
270 case '=':
271 tag_split(split[ctag] == 1 ? 2 : 1);
272 return;
273 case '-':
274 tag_split(0);
275 return;
276 default:
277 if (strchr(tags, c)) {
278 tag_show(strchr(tags, c) - tags);
279 return;
281 if (fterm_main())
282 term_send(ESC);
285 if (c != -1 && fterm_main())
286 term_send(c);
289 static void peepterm(int termid)
291 int visible = !hidden && ctag == (termid % NTAGS) && split[ctag];
292 if (termid != cterm())
293 fterm_switch(cterm(), termid, visible, 0, 0);
296 static void peepback(int termid)
298 if (termid != cterm())
299 fterm_switch(termid, cterm(), !hidden, 0, 0);
302 static int pollterms(void)
304 struct pollfd ufds[NTERMS + 1];
305 int term_idx[NTERMS + 1];
306 int i;
307 int n = 1;
308 ufds[0].fd = 0;
309 ufds[0].events = POLLIN;
310 for (i = 0; i < NTERMS; i++) {
311 if (TERMOPEN(i)) {
312 ufds[n].fd = terms[i].fd;
313 ufds[n].events = POLLIN;
314 term_idx[n++] = i;
317 if (poll(ufds, n, 1000) < 1)
318 return 0;
319 if (ufds[0].revents & (POLLFLAGS & ~POLLIN))
320 return 1;
321 if (ufds[0].revents & POLLIN)
322 directkey();
323 for (i = 1; i < n; i++) {
324 if (!(ufds[i].revents & POLLFLAGS))
325 continue;
326 peepterm(term_idx[i]);
327 if (ufds[i].revents & POLLIN) {
328 term_read();
329 } else {
330 scr_free(term_idx[i]);
331 term_end();
332 if (cmdmode)
333 exitit = 1;
335 peepback(term_idx[i]);
337 return 0;
340 static void mainloop(char **args)
342 struct termios oldtermios, termios;
343 tcgetattr(0, &termios);
344 oldtermios = termios;
345 cfmakeraw(&termios);
346 tcsetattr(0, TCSAFLUSH, &termios);
347 term_load(&terms[cterm()], 1);
348 term_redraw(1);
349 if (args) {
350 cmdmode = 1;
351 fterm_exec(args);
353 while (!exitit)
354 if (pollterms())
355 break;
356 tcsetattr(0, 0, &oldtermios);
359 static void signalreceived(int n)
361 if (exitit)
362 return;
363 switch (n) {
364 case SIGUSR1:
365 hidden = 1;
366 fterm_switch(cterm(), cterm(), 0, 1, 0);
367 ioctl(0, VT_RELDISP, 1);
368 break;
369 case SIGUSR2:
370 hidden = 0;
371 fb_cmap();
372 fterm_switch(cterm(), cterm(), 1, 0, 1);
373 break;
374 case SIGCHLD:
375 while (waitpid(-1, NULL, WNOHANG) > 0)
377 break;
381 static void signalsetup(void)
383 struct vt_mode vtm;
384 vtm.mode = VT_PROCESS;
385 vtm.waitv = 0;
386 vtm.relsig = SIGUSR1;
387 vtm.acqsig = SIGUSR2;
388 vtm.frsig = 0;
389 signal(SIGUSR1, signalreceived);
390 signal(SIGUSR2, signalreceived);
391 signal(SIGCHLD, signalreceived);
392 ioctl(0, VT_SETMODE, &vtm);
395 int main(int argc, char **argv)
397 char *hide = "\x1b[2J\x1b[H\x1b[?25l";
398 char *show = "\x1b[?25h";
399 char **args = argv + 1;
400 if (fb_init(FBDEV)) {
401 fprintf(stderr, "fbpad: failed to initialize the framebuffer\n");
402 return 1;
404 if (pad_init()) {
405 fprintf(stderr, "fbpad: cannot find fonts\n");
406 return 1;
408 write(1, hide, strlen(hide));
409 signalsetup();
410 fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
411 while (args[0] && args[0][0] == '-')
412 args++;
413 mainloop(args[0] ? args : NULL);
414 write(1, show, strlen(show));
415 pad_free();
416 scr_done();
417 fb_free();
418 return 0;