'W' and 'H' args: width and height
[k8sterm.git] / src / sterm.c
blobb903c9bf950cea021fb507bd1c82bd5d62045ef3
1 /* See LICENSE for licence details. */
2 #ifndef GIT_VERSION
3 # define VERSION "0.4.0.beta4"
4 #else
5 # define VERSION GIT_VERSION
6 #endif
8 #ifdef _XOPEN_SOURCE
9 # undef _XOPEN_SOURCE
10 #endif
11 #define _XOPEN_SOURCE 600
13 #ifndef _GNU_SOURCE
14 # define _GNU_SOURCE
15 #endif
18 #include <alloca.h>
19 #include <ctype.h>
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <iconv.h>
23 #include <limits.h>
24 #include <locale.h>
26 #ifdef __MACH__
27 #include <util.h>
28 #else
29 #include <pty.h>
30 #endif
32 #include <stdarg.h>
33 #include <stdint.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <strings.h>
38 #include <signal.h>
39 #include <sys/ioctl.h>
40 #include <sys/select.h>
41 #include <sys/stat.h>
42 #include <sys/time.h>
43 #include <sys/types.h>
44 #include <sys/wait.h>
45 #include <time.h>
46 #include <unistd.h>
47 #include <X11/Xatom.h>
48 #include <X11/Xlib.h>
49 #include <X11/Xutil.h>
50 #include <X11/cursorfont.h>
51 #include <X11/keysym.h>
53 ////////////////////////////////////////////////////////////////////////////////
54 #include "libk8sterm/k8sterm.h"
57 ////////////////////////////////////////////////////////////////////////////////
58 #include "defaults.c"
61 ////////////////////////////////////////////////////////////////////////////////
62 #define USAGE_VERSION "k8sterm " VERSION "\n(c) 2010-2012 st engineers and Ketmar // Vampire Avalon\n"
63 #define USAGE \
64 "usage: sterm [options]\n" \
65 "options:\n" \
66 "-v show version and exit\n" \
67 "-h show this help and exit\n" \
68 "-S disable tabs\n" \
69 "-t title set window title\n" \
70 "-c class set window class\n" \
71 "-w windowid embed in window id given id\n" \
72 "-T termname set TERM name\n" \
73 "-C config_file use custom config file\n" \
74 "-l langiconv use given locale\n" \
75 "-R stcmd run this INTERNAL k8sterm command\n" \
76 "-e command... run given command and pass all following args to it\n"
79 ////////////////////////////////////////////////////////////////////////////////
80 /* masks for key translation */
81 #define XK_NO_MOD (UINT_MAX)
82 #define XK_ANY_MOD (0)
85 ////////////////////////////////////////////////////////////////////////////////
86 // internal command line mode
87 enum {
88 K8T_CMDMODE_NONE,
89 K8T_CMDMODE_INPUT,
90 K8T_CMDMODE_MESSAGE
94 // X11 window state flags
95 enum {
96 K8T_WIN_VISIBLE = 0x01,
97 K8T_WIN_REDRAW = 0x02,
98 K8T_WIN_FOCUSED = 0x04
102 ////////////////////////////////////////////////////////////////////////////////
103 /* Purely graphic info */
104 typedef struct {
105 Display *dpy;
106 Colormap cmap;
107 Window win;
108 Cursor cursor; // text cursor
109 Cursor defcursor; // 'default' cursor
110 Cursor lastcursor; // last used cursor before blanking
111 Cursor blankPtr;
112 Atom xembed;
113 XIM xim;
114 XIC xic;
115 int scr;
116 int w; /* window width */
117 int h; /* window height */
118 int bufw; /* pixmap width */
119 int bufh; /* pixmap height */
120 int ch; /* char height */
121 int cw; /* char width */
122 char state; /* focus, redraw, visible */
124 int tch; /* tab text char height */
125 Pixmap pictab;
126 int picscrhere;
127 Pixmap picscr;
128 int tabheight;
129 //struct timeval lastdraw;
130 } K8TXWindow;
133 /* Drawing Context */
134 typedef struct {
135 uint32_t *ncol; // normal colors
136 uint32_t *bcol; // b/w colors
137 uint32_t *gcol; // green colors
138 uint32_t *clrs[3];
139 GC gc;
140 struct {
141 int ascent;
142 int descent;
143 short lbearing;
144 short rbearing;
145 XFontSet set;
146 Font fid;
147 } font[3];
148 } K8TXDC;
151 typedef struct K8TCmdLine K8TCmdLine;
153 typedef void (*CmdLineExecFn) (K8Term *term, K8TCmdLine *cmdline, int cancelled);
155 struct K8TCmdLine {
156 int cmdMode; // K8T_CMDMODE_xxx
157 char cmdline[K8T_UTF_SIZ*CMDLINE_SIZE];
158 int cmdreslen; // byte length of 'reserved' (read-only) part
159 int cmdofs; // byte offset of the first visible UTF-8 char in cmdline
160 char cmdc[K8T_UTF_SIZ+1]; // buffer to collect UTF-8 char
161 int cmdcl; // index in cmdc, used to collect UTF-8 chars
162 int cmdtabpos; // # of bytes (not UTF-8 chars!) used in autocompletion or -1
163 const char *cmdcurtabc; // current autocompleted command
164 CmdLineExecFn cmdexecfn;
165 void *udata;
169 /* internal terminal data */
170 typedef struct {
171 pid_t pid;
172 int exitcode;
174 int waitkeypress; /* child is dead, awaiting for keypress */
175 char *exitmsg; /* message for waitkeypress */
177 char *execcmd;
179 char *lastpname;
180 char *lastppath;
181 int titleset;
182 int mc_hack_active;
183 int old_scroll_mode;
185 K8TCmdLine cmdline;
186 } K8TermData;
188 #define K8T_DATA(_term) ((K8TermData *)(_term->udata))
191 ////////////////////////////////////////////////////////////////////////////////
192 #include "globals.c"
193 #include "getticks.c"
196 ////////////////////////////////////////////////////////////////////////////////
197 #include "utils.c"
198 #include "keymaps.c"
201 ////////////////////////////////////////////////////////////////////////////////
202 static void executeCommands (const char *str);
203 static const char *findCommandCompletion (const char *str, int slen, const char *prev);
205 static void tdrawfatalmsg (K8Term *term, const char *msg);
207 static void tcmdput (K8Term *term, K8TCmdLine *cmdline, const char *s, int len);
209 static void xclearunused (void);
210 static void xseturgency (int add);
211 static void xfixsel (void);
212 static void xblankPointer (void);
213 static void xunblankPointer (void);
214 static void xdrawTabBar (void);
215 static void xdrawcmdline (K8Term *term, K8TCmdLine *cmdline, int scry);
217 static void termCreateXPixmap (K8Term *term);
218 static void termSetCallbacks (K8Term *term);
219 static void termSetDefaults (K8Term *term);
221 static void getButtonInfo (K8Term *term, XEvent *e, int *b, int *x, int *y);
223 static void fixWindowTitle (const K8Term *t);
225 static void scrollHistory (K8Term *term, int delta);
227 static int termsDoIO (int xfd);
228 //static void doTermWrFlush (K8Term *term);
231 ////////////////////////////////////////////////////////////////////////////////
232 static inline uint32_t getColor (int idx) {
233 if (globalBW && (curterm == NULL || !curterm->blackandwhite)) return dc.clrs[globalBW][idx];
234 if (curterm != NULL) return dc.clrs[curterm->blackandwhite%3][idx];
235 return dc.clrs[0][idx];
239 ////////////////////////////////////////////////////////////////////////////////
240 #include "iniparse.c"
241 #include "locales.c"
244 ////////////////////////////////////////////////////////////////////////////////
245 #include "termswitch.c"
246 #include "ttyinit.c"
249 ////////////////////////////////////////////////////////////////////////////////
250 #include "tcmdline.c"
251 #include "tfatalbox.c"
254 ////////////////////////////////////////////////////////////////////////////////
255 #include "x11init.c"
256 #include "x11misc.c"
257 #include "x11drawtabs.c"
258 #include "x11drawcmdline.c"
261 ////////////////////////////////////////////////////////////////////////////////
262 #include "x11evtvis.c"
263 #include "x11evtkbd.c"
264 #include "x11evtsel.c"
265 #include "x11evtmouse.c"
268 ////////////////////////////////////////////////////////////////////////////////
269 #include "x11CB.c"
272 ////////////////////////////////////////////////////////////////////////////////
273 // xembed?
274 static void xevtcbcmessage (XEvent *e) {
275 /* See xembed specs http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html */
276 if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) {
277 if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) {
278 xw.state |= K8T_WIN_FOCUSED;
279 xseturgency(0);
280 k8t_tmSendFocusEvent(curterm, 1);
281 } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) {
282 xw.state &= ~K8T_WIN_FOCUSED;
283 k8t_tmSendFocusEvent(curterm, 0);
285 k8t_drawCursor(curterm, 0);
286 xdrawTabBar();
287 k8t_drawCopy(curterm, 0, 0, curterm->col, curterm->row);
288 return;
291 if (e->xclient.data.l[0] == XA_WM_DELETE_WINDOW) {
292 closeRequestComes = 1;
293 return;
298 ////////////////////////////////////////////////////////////////////////////////
299 static K8TTimeMSec nscb_lastForcedRedraw = 0; //FIXME: nasty global!
303 curterm->wantRedraw = 1;
304 curterm->lastDrawTime = 0;
306 static int afterNLSWCB (K8Term *term, int is_swap) {
307 static int lines_20 = 0; //FIXME: nasty global!
308 int res = 0;
309 /*FIXME: HACKERYHACK!*/
310 if (curterm == term && opt_fastupdate) {
311 K8TTimeMSec tt;
312 /* force redraw */
313 int xtime;
314 if (!is_swap && lines_20 > 0) {
315 --lines_20;
316 xtime = 20;
317 } else {
318 //if (is_swap) fprintf(stderr, "SWPALT: %d\n", K8T_ISSET(term, K8T_MODE_ALTSCREEN));
319 /* either not swapping or just swapped to alternate screen: longer delay */
320 if (is_swap) {
321 /* swap */
322 if (K8T_ISSET(term, K8T_MODE_ALTSCREEN)) {
323 /* swapped to alternate screen */
324 xtime = 100;
325 lines_20 = 0;
326 } else {
327 /* swapped to normal screen */
328 xtime = 10;
329 lines_20 = 10;
331 } else {
332 /* newline */
333 xtime = (K8T_ISSET(term, K8T_MODE_ALTSCREEN) ? 150 : 80);
334 lines_20 = 0;
337 tt = mclock_ticks();
338 if (tt >= xtime && tt-xtime >= nscb_lastForcedRedraw) {
339 k8t_tmWantRedraw(term, 1); /* force it */
340 k8t_drawTerm(term, 0);
343 K8TTimeMSec t1 = mclock_ticks();
344 if (t1-tt > 20) fprintf(stderr, "DRAWING TOO SLOW! %u\n", t1-tt);
347 nscb_lastForcedRedraw = tt;
348 curterm->lastDrawTime = tt;
349 res = 1;
350 } else {
351 k8t_tmWantRedraw(term, 0); /* shedule */
352 curterm->lastDrawTime = tt;
354 curterm->justSwapped = 0;
355 } else {
356 lines_20 = 0;
358 return res;
362 static void afterNewLineCB (K8Term *term) {
363 afterNLSWCB(term, 0);
367 static void afterScreenSwapCB (K8Term *term) {
368 //fprintf(stderr, "afterScreenSwapCB:%d; curterm==term:%d; opt_fastupdate:%d\n", K8T_ISSET(term, K8T_MODE_ALTSCREEN), curterm==term, opt_fastupdate);
369 afterNLSWCB(term, 1);
373 static inline int last_draw_too_old (void) {
374 K8TTimeMSec tt;
375 if (curterm != NULL) {
376 if (curterm->dead || !curterm->wantRedraw) return 0;
377 #if 0
378 if (curterm->justSwapped) {
379 afterNLSWCB(curterm, 1);
380 curterm->justSwapped = 0; /* repainter will fix it, but... */
381 return 0;
383 #endif
384 tt = mclock_ticks();
385 return
386 (tt-curterm->lastDrawTime >= opt_drawtimeout ||
387 tt-lastDrawTime >= (curterm->justSwapped ? opt_swapdrawtimeout : opt_maxdrawtimeout));
388 } else {
389 tt = mclock_ticks();
390 return (tt-lastDrawTime >= opt_maxdrawtimeout);
395 ////////////////////////////////////////////////////////////////////////////////
396 // main loop
397 #include "childkiller.c"
398 static int childsigfd = -1;
401 static void (*handler[LASTEvent])(XEvent *) = {
402 [KeyPress] = xevtcbkpress,
403 [ClientMessage] = xevtcbcmessage,
404 [ConfigureNotify] = xevtcbresise,
405 [VisibilityNotify] = xevtcbvisibility,
406 [UnmapNotify] = xevtcbunmap,
407 [Expose] = xevtcbexpose,
408 [FocusIn] = xevtcbfocus,
409 [FocusOut] = xevtcbfocus,
410 [MotionNotify] = xevtcbbmotion,
411 [ButtonPress] = xevtcbbpress,
412 [ButtonRelease] = xevtcbbrelease,
413 [SelectionNotify] = xevtcbselnotify,
414 [SelectionRequest] = xevtcbselrequest,
415 [SelectionClear] = xevtcbselclear,
420 static void doTermWrFlush (K8Term *term) {
421 for (;;) {
422 fd_set rfd, wfd;
423 //struct timeval timeout;
425 if (term->dead || term->cmdfd < 0) {
426 // empty write buffer
427 term->wrbufpos = term->wrbufused = 0;
428 return;
431 FD_ZERO(&rfd);
432 FD_ZERO(&wfd);
433 FD_SET(term->cmdfd, &rfd);
434 FD_SET(term->cmdfd, &wfd);
435 if (select(term->cmdfd+1, &rfd, &wfd, NULL, NULL) < 0) {
436 if (errno == EINTR) continue;
437 k8t_die("select failed: %s", strerror(errno));
440 if (FD_ISSET(term->cmdfd, &rfd)) {
441 int rd = k8t_ttyRead(term);
443 if (K8T_DATA(term)->waitkeypress && K8T_DATA(term)->exitmsg != NULL && rd < 0) {
444 // there will be no more data
445 close(term->cmdfd);
446 term->cmdfd = -1;
448 tdrawfatalmsg(term, K8T_DATA(term)->exitmsg);
449 free(K8T_DATA(term)->exitmsg);
450 K8T_DATA(term)->exitmsg = NULL;
451 k8t_tmWantRedraw(term, 1);
452 // empty write buffer
453 term->wrbufpos = term->wrbufused = 0;
454 return;
457 continue;
458 } else {
459 if (term->wrbufpos >= term->wrbufused) {
460 // empty write buffer
461 term->wrbufpos = term->wrbufused = 0;
462 return;
466 if (K8T_DATA(term)->pid != 0 && FD_ISSET(term->cmdfd, &wfd)) {
467 k8t_ttySendWriteBuf(term);
474 static int termsDoIO (int xfd) {
475 int res = 0, done;
477 do {
478 fd_set rfd, wfd;
479 struct timeval timeout;
480 int sres, maxfd;
482 done = 0;
483 FD_ZERO(&rfd);
484 FD_ZERO(&wfd);
485 if (xfd >= 0) { FD_SET(xfd, &rfd); maxfd = xfd; } else { maxfd = 0; }
486 //FD_SET(curterm->cmdfd, &rfd);
487 // have something to write?
488 for (int f = 0; f < term_count; ++f) {
489 K8Term *t = term_array[f];
491 if (!t->dead && t->cmdfd >= 0) {
492 checkAndFixWindowTitle(t, 0);
493 if (t->cmdfd > maxfd) maxfd = t->cmdfd;
494 FD_SET(t->cmdfd, &rfd);
495 if (K8T_DATA(t)->pid != 0 && t->wrbufpos < t->wrbufused) FD_SET(t->cmdfd, &wfd);
499 timeout.tv_sec = 0;
500 timeout.tv_usec = (xfd >= 0 ? (opt_drawtimeout+2)*1000 : 0);
501 if (childsigfd >= 0) {
502 if (childsigfd > maxfd) maxfd = childsigfd;
503 FD_SET(childsigfd, &rfd);
505 //fprintf(stderr, "before select...\n");
506 sres = select(maxfd+1, &rfd, &wfd, NULL, &timeout);
507 if (sres < 0) {
508 if (errno == EINTR) continue;
509 k8t_die("select failed: %s", strerror(errno));
511 if (childsigfd >= 0 && FD_ISSET(childsigfd, &rfd)) processDeadChildren(childsigfd);
512 if (xfd >= 0 && FD_ISSET(xfd, &rfd)) {
513 res = done = 1;
514 } else {
515 done = (sres == 0);
517 //fprintf(stderr, "after select...\n");
518 // process terminals i/o
519 for (int f = 0; f < term_count; ++f) {
520 K8Term *t = term_array[f];
522 if (!t->dead && t->cmdfd >= 0) {
523 int rd = -1;
525 if (K8T_DATA(t)->pid != 0 && FD_ISSET(t->cmdfd, &wfd)) k8t_ttyFlushWriteBuf(t);
526 if (FD_ISSET(t->cmdfd, &rfd)) rd = k8t_ttyRead(t);
528 if (K8T_DATA(t)->waitkeypress && K8T_DATA(t)->exitmsg != NULL && rd < 0) {
529 // there will be no more data
530 close(t->cmdfd);
531 t->cmdfd = -1;
533 tdrawfatalmsg(t, K8T_DATA(t)->exitmsg);
534 free(K8T_DATA(t)->exitmsg);
535 K8T_DATA(t)->exitmsg = NULL;
536 k8t_tmWantRedraw(t, 1);
540 } while (!done);
542 return res;
546 static void run (void) {
547 //int stuff_to_print = 0;
548 int xfd = XConnectionNumber(xw.dpy);
550 ptrLastMove = mclock_ticks();
551 while (term_count > 0) {
552 XEvent ev;
553 int doX, dodraw;
555 doX = termsDoIO(xfd);
557 termcleanup();
558 if (term_count == 0) exit(exitcode);
560 dodraw = 0;
561 if (curterm != NULL && curterm->curblink > 0) {
562 K8TTimeMSec tt = mclock_ticks();
564 if (tt-curterm->lastBlinkTime >= curterm->curblink) {
565 curterm->lastBlinkTime = tt;
566 if ((xw.state&K8T_WIN_FOCUSED) || curterm->curblinkinactive) {
567 curterm->curbhidden = (curterm->curbhidden ? 0 : -1);
568 dodraw = 1;
569 } else {
570 curterm->curbhidden = 0;
574 if (updateTabBar) xdrawTabBar();
575 if (dodraw || last_draw_too_old()) k8t_drawTerm(curterm, 0);
577 if (doX || XPending(xw.dpy)) {
578 while (XPending(xw.dpy)) {
579 XNextEvent(xw.dpy, &ev);
580 switch (ev.type) {
581 //case VisibilityNotify:
582 //case UnmapNotify:
583 //case FocusIn:
584 //case FocusOut:
585 case MotionNotify:
586 case ButtonPress:
587 case ButtonRelease:
588 xunblankPointer();
589 ptrLastMove = mclock_ticks();
590 break;
591 default: ;
593 if (XFilterEvent(&ev, xw.win)) continue;
594 if (handler[ev.type]) (handler[ev.type])(&ev);
598 if (curterm != NULL) {
599 switch (closeRequestComes) {
600 case 1: // just comes
601 if (opt_ignoreclose == 0) {
602 tcmdlinehide(curterm, &K8T_DATA(curterm)->cmdline);
603 tcmdlineinitex(curterm, &K8T_DATA(curterm)->cmdline, "Do you really want to close sterm [N/y]? ");
604 K8T_DATA(curterm)->cmdline.cmdexecfn = cmdline_closequeryexec;
605 } else if (opt_ignoreclose < 0) {
606 //FIXME: kill all clients?
607 return;
609 closeRequestComes = 0;
610 break;
611 case 2: // ok, die now
612 //FIXME: kill all clients?
613 return;
617 if (!ptrBlanked && opt_ptrblank > 0 && mclock_ticks()-ptrLastMove >= opt_ptrblank) {
618 xblankPointer();
624 ////////////////////////////////////////////////////////////////////////////////
625 static void termSetDefaults (K8Term *term) {
626 term->deffg = defaultFG;
627 term->defbg = defaultBG;
628 term->defboldfg = defaultBoldFG;
629 term->defunderfg = defaultUnderlineFG;
631 term->defcurfg = defaultCursorFG;
632 term->defcurbg = defaultCursorBG;
633 term->defcurinactivefg = defaultCursorInactiveFG;
634 term->defcurinactivebg = defaultCursorInactiveBG;
636 term->tabsize = opt_tabsize;
637 term->wrapOnCtrlChars = 0;
638 term->historyLinesUsed = 0;
642 static void termSetCallbacks (K8Term *term) {
643 K8TermData *td = calloc(1, sizeof(K8TermData));
645 if (td == NULL) k8t_die("out of memory!");
646 term->udata = td;
648 term->clockTicks = clockTicksCB;
649 term->setLastDrawTime = setLastDrawTimeCB;
651 term->isConversionNecessary = isConversionNecessaryCB;
652 term->loc2utf = loc2utfCB;
653 term->utf2loc = utf2locCB;
655 term->isFocused = isFocusedCB;
656 term->isVisible = isVisibleCB;
658 term->drawSetFG = drawSetFGCB;
659 term->drawSetBG = drawSetBGCB;
660 term->drawRect = drawRectCB;
661 term->drawFillRect = drawFillRectCB;
662 term->drawCopyArea = drawCopyAreaCB;
663 term->drawString = drawStringCB;
664 term->drawOverlay = drawOverlayCB;
666 term->fixSelection = fixSelectionCB;
667 term->clearSelection = clearSelectionCB;
669 term->titleChanged = titleChangedCB;
670 term->doBell = doBellCB;
672 term->isUnderOverlay = isUnderOverlayCB;
673 term->clipToOverlay = clipToOverlayCB;
674 term->addHistoryLine = NULL;
675 //term->doWriteBufferFlush = doTermWrFlush;
676 //term->doWriteBufferFlush = NULL;
677 term->afterNewLine = afterNewLineCB;
678 term->afterScreenSwap = afterScreenSwapCB; // the same
680 term->unimap = unimap;
681 td->execcmd = NULL;
682 td->lastpname = strdup("");
683 td->lastppath = strdup("");
684 td->titleset = 0;
688 static void termCreateXPixmap (K8Term *term) {
689 int w = K8T_MAX(1, term->col*xw.cw);
690 int h = K8T_MAX(1, term->row*xw.ch);
691 Pixmap newbuf;
693 newbuf = XCreatePixmap(xw.dpy, xw.win, w, h, XDefaultDepth(xw.dpy, xw.scr));
695 if (xw.picscrhere) {
696 //XCopyArea(xw.dpy, td->picbuf, newbuf, dc.gc, 0, 0, td->picbufw, td->picbufh, 0, 0);
697 XFreePixmap(xw.dpy, xw.picscr);
699 xw.picscr = newbuf;
701 term->drawSetFG(term, term->defbg);
704 if (td->picalloced) {
705 if (td->picbufw > oldw) {
706 XFillRectangle(xw.dpy, newbuf, dc.gc, oldw, 0, td->picbufw-oldw, K8T_MIN(td->picbufh, oldh));
707 } else if (td->picbufw < oldw && xw.w > td->picbufw) {
708 XClearArea(xw.dpy, xw.win, td->picbufw, 0, xw.w-td->picbufh, K8T_MIN(td->picbufh, oldh), False);
711 if (td->picbufh > oldh) {
712 XFillRectangle(xw.dpy, newbuf, dc.gc, 0, oldh, td->picbufw, td->picbufh-oldh);
713 } else if (td->picbufh < oldh && xw.h > td->picbufh) {
714 XClearArea(xw.dpy, xw.win, 0, td->picbufh, xw.w, xw.h-td->picbufh, False);
716 } else {
717 XFillRectangle(xw.dpy, newbuf, dc.gc, 0, 0, td->picbufw, td->picbufh);
720 XFillRectangle(xw.dpy, newbuf, dc.gc, 0, 0, w, h);
722 k8t_tmFullDirty(term);
726 ////////////////////////////////////////////////////////////////////////////////
727 #include "inifile.c"
728 #include "commands.c"
731 ////////////////////////////////////////////////////////////////////////////////
732 #define PUSH_BACK(_c) (*ress)[dpos++] = (_c)
733 #define DECODE_TUPLE(tuple,bytes) \
734 for (tmp = bytes; tmp > 0; tmp--, tuple = (tuple & 0x00ffffff)<<8) \
735 PUSH_BACK((char)((tuple >> 24)&0xff))
737 // returns ress length
738 static int ascii85Decode (char **ress, const char *srcs/*, int start, int length*/) {
739 static uint32_t pow85[5] = { 85*85*85*85UL, 85*85*85UL, 85*85UL, 85UL, 1UL };
740 const uint8_t *data = (const uint8_t *)srcs;
741 int len = strlen(srcs);
742 uint32_t tuple = 0;
743 int count = 0, c = 0;
744 int dpos = 0;
745 int start = 0, length = len;
746 int tmp;
748 if (start < 0) start = 0; else { len -= start; data += start; }
749 if (length < 0 || len < length) length = len;
751 if (length > 0) {
752 int xlen = 4*((length+4)/5);
753 kstringReserve(ress, xlen);
757 *ress = (char *)calloc(1, len+1);
758 for (int f = length; f > 0; --f, ++data) {
759 c = *data;
760 if (c <= ' ') continue; // skip blanks
761 switch (c) {
762 case 'z': // zero tuple
763 if (count != 0) {
764 //fprintf(stderr, "%s: z inside ascii85 5-tuple\n", file);
765 free(*ress);
766 *ress = NULL;
767 return -1;
769 PUSH_BACK('\0');
770 PUSH_BACK('\0');
771 PUSH_BACK('\0');
772 PUSH_BACK('\0');
773 break;
774 case '~': // '~>': end of sequence
775 if (f < 1 || data[1] != '>') { free(*ress); return -2; } // error
776 if (count > 0) { f = -1; break; }
777 default:
778 if (c < '!' || c > 'u') {
779 //fprintf(stderr, "%s: bad character in ascii85 region: %#o\n", file, c);
780 free(*ress);
781 return -3;
783 tuple += ((uint8_t)(c-'!'))*pow85[count++];
784 if (count == 5) {
785 DECODE_TUPLE(tuple, 4);
786 count = 0;
787 tuple = 0;
789 break;
792 // write last (possibly incomplete) tuple
793 if (count-- > 0) {
794 tuple += pow85[count];
795 DECODE_TUPLE(tuple, count);
797 return dpos;
800 #undef PUSH_BACK
801 #undef DECODE_TUPLE
804 static void decodeBA (char *str, int len) {
805 char pch = 42;
807 for (int f = 0; f < len; ++f, ++str) {
808 char ch = *str;
810 ch = (ch-f-1)^pch;
811 *str = ch;
812 pch = ch;
817 static void printEC (const char *txt) {
818 char *dest;
819 int len;
821 if ((len = ascii85Decode(&dest, txt)) >= 0) {
822 decodeBA(dest, len);
823 fprintf(stderr, "%s\n", dest);
824 free(dest);
829 static int isStr85Equ (const char *txt, const char *str) {
830 char *dest;
831 int len, res = 0;
833 if (str != NULL && str[0] && (len = ascii85Decode(&dest, txt)) >= 0) {
834 if (len > 0) res = (strcmp(dest+1, str+1) == 0);
835 free(dest);
837 return res;
841 static int checkEGG (const char *str) {
842 if (isStr85Equ("06:]JASq", str) || isStr85Equ("0/i", str)) {
843 printEC(
844 "H8lZV&6)1>+AZ>m)Cf8;A1/cP+CnS)0OJ`X.QVcHA4^cc5r3=m1c%0D3&c263d?EV6@4&>"
845 "3DYQo;c-FcO+UJ;MOJ$TAYO@/FI]+B?C.L$>%:oPAmh:4Au)>AAU/H;ZakL2I!*!%J;(AK"
846 "NIR#5TXgZ6c'F1%^kml.JW5W8e;ql0V3fQUNfKpng6ppMf&ip-VOX@=jKl;#q\"DJ-_>jG"
847 "8#L;nm]!q;7c+hR6p;tVY#J8P$aTTK%c-OT?)<00,+q*8f&ff9a/+sbU,:`<H*[fk0o]7k"
848 "^l6nRkngc6Tl2Ngs!!P2I%KHG=7n*an'bsgn>!*8s7TLTC+^\\\"W+<=9^%Ol$1A1eR*Be"
849 "gqjEag:M0OnrC4FBY5@QZ&'HYYZ#EHs8t4$5]!22QoJ3`;-&=\\DteO$d6FBqT0E@:iu?N"
850 "a5ePUf^_uEEcjTDKfMpX/9]DFL8N-Ee;*8C5'WgbGortZuh1\\N0;/rJB6'(MSmYiS\"6+"
851 "<NK)KDV3e+Ad[@).W:%.dd'0h=!QUhghQaNNotIZGrpHr-YfEuUpsKW<^@qlZcdTDA!=?W"
852 "Yd+-^`'G8Or)<0-T&CT.i+:mJp(+/M/nLaVb#5$p2jR2<rl7\"XlngcN`mf,[4oK5JLr\\"
853 "m=X'(ue;'*1ik&/@T4*=j5t=<&/e/Q+2=((h`>>uN(#>&#i>2/ajK+=eib1coVe3'D)*75"
854 "m_h;28^M6p6*D854Jj<C^,Q8Wd\"O<)&L/=C$lUAQNN<=eTD:A6kn-=EItXSss.tAS&!;F"
855 "EsgpJTHIYNNnh'`kmX^[`*ELOHGcWbfPOT`J]A8P`=)AS;rYlR$\"-.RG440lK5:Dg?G'2"
856 "['dE=nEm1:k,,Se_=%-6Z*L^J[)EC"
858 return 1;
860 if (isStr85Equ("04Jj?B)", str)) {
861 printEC(
862 "IPaSa(`c:T,o9Bq3\\)IY++?+!-S9%P0/OkjE&f$l.OmK'Ai2;ZHn[<,6od7^8;)po:HaP"
863 "m<'+&DRS:/1L7)IA7?WI$8WKTUB2tXg>Zb$.?\"@AIAu;)6B;2_PB5M?oBPDC.F)606Z$V"
864 "=ONd6/5P*LoWKTLQ,d@&;+Ru,\\ESY*rg!l1XrhpJ:\"WKWdOg?l;=RHE:uU9C?aotBqj]"
865 "=k8cZ`rp\"ZO=GjkfD#o]Z\\=6^]+Gf&-UFthT*hN"
867 return 1;
869 if (isStr85Equ("04o69A7Tr", str)) {
870 printEC(
871 "Ag7d[&R#Ma9GVV5,S(D;De<T_+W).?,%4n+3cK=%4+0VN@6d\")E].np7l?8gF#cWF7SS_m"
872 "4@V\\nQ;h!WPD2h#@\\RY&G\\LKL=eTP<V-]U)BN^b.DffHkTPnFcCN4B;]8FCqI!p1@H*_"
873 "jHJ<%g']RG*MLqCrbP*XbNL=4D1R[;I(c*<FuesbWmSCF1jTW+rplg;9[S[7eDVl6YsjT"
875 return 1;
877 return 0;
881 ////////////////////////////////////////////////////////////////////////////////
882 int main (int argc, char *argv[]) {
883 char *configfile = NULL;
884 char *runcmd = NULL;
885 int optWidth = 80;
886 int optHeight = 25;
888 //dbgLogInit();
889 for (int f = 1; f < argc; f++) {
890 if (argv[f][0] == '-' && checkEGG(argv[f])) exit(1);
891 if (strcmp(argv[f], "-name") == 0) { ++f; continue; }
892 if (strcmp(argv[f], "-into") == 0) { ++f; continue; }
893 if (strcmp(argv[f], "-embed") == 0) { ++f; continue; }
894 switch (argv[f][0] != '-' || argv[f][2] ? -1 : argv[f][1]) {
895 case 'e': f = argc+1; break;
896 case 't':
897 case 'c':
898 case 'w':
899 case 'b':
900 case 'R':
901 case 'W':
902 case 'H':
903 ++f;
904 break;
905 case 'T':
906 if (++f < argc) {
907 free(opt_term);
908 opt_term = strdup(argv[f]);
909 opt_term_locked = 1;
911 break;
912 case 'C':
913 if (++f < argc) {
914 if (configfile != NULL) free(configfile);
915 configfile = strdup(argv[f]);
917 break;
918 case 'l':
919 if (++f < argc) cliLocale = argv[f];
920 break;
921 case 'S': // single-tab mode
922 opt_disabletabs = 1;
923 break;
924 case 'v':
925 fprintf(stderr, "%s", USAGE_VERSION);
926 exit(EXIT_FAILURE);
927 case 'h':
928 default:
929 fprintf(stderr, "%s%s", USAGE_VERSION, USAGE);
930 exit(EXIT_FAILURE);
934 initDefaultOptions();
935 if (configfile == NULL) {
936 static const char *systemcfg[] = {"./.sterm.rc", "/etc/sterm.rc", "/etc/.sterm.rc", "/etc/sterm/sterm.rc", "/etc/sterm/.sterm.rc"};
937 const char *home = getenv("HOME");
938 if (home != NULL) {
939 static const char *homecfg[] = {"%s/.sterm.rc", "%s/.config/sterm.rc", "%s/.config/sterm/sterm.rc"};
940 for (size_t f = 0; f < sizeof(homecfg)/sizeof(homecfg[0]); ++f) {
941 configfile = SPrintf(homecfg[f], home);
942 if (loadConfig(configfile) == 0) goto cfgdone;
943 free(configfile);
944 configfile = NULL;
947 for (size_t f = 0; f < sizeof(systemcfg)/sizeof(systemcfg[0]); ++f) {
948 configfile = SPrintf(systemcfg[f]);
949 if (loadConfig(configfile) == 0) goto cfgdone;
950 free(configfile);
951 configfile = NULL;
953 // no config
954 } else {
955 if (loadConfig(configfile) < 0) k8t_die("config file '%s' not found!", configfile);
957 cfgdone:
958 if (configfile != NULL) free(configfile); configfile = NULL;
960 for (int f = 1; f < argc; ++f) {
961 if (strcmp(argv[f], "-name") == 0) { ++f; continue; }
962 if (strcmp(argv[f], "-into") == 0 || strcmp(argv[f], "-embed") == 0) {
963 if (opt_embed) free(opt_embed);
964 opt_embed = strdup(argv[f]);
965 continue;
967 switch (argv[f][0] != '-' || argv[f][2] ? -1 : argv[f][1]) {
968 case 't':
969 if (++f < argc) {
970 free(opt_title);
971 opt_title = strdup(argv[f]);
973 break;
974 case 'c':
975 if (++f < argc) {
976 free(opt_class);
977 opt_class = strdup(argv[f]);
979 break;
980 case 'w':
981 if (++f < argc) {
982 if (opt_embed) free(opt_embed);
983 opt_embed = strdup(argv[f]);
985 break;
986 case 'R':
987 if (++f < argc) runcmd = argv[f];
988 break;
989 case 'W':
990 if (++f < argc) {
991 optWidth = atoi(argv[f]);
992 if (optWidth < 2) optWidth = 2;
993 if (optWidth > 512) optWidth = 512;
995 break;
996 case 'H':
997 if (++f < argc) {
998 optHeight = atoi(argv[f]);
999 if (optHeight < 1) optHeight = 1;
1000 if (optHeight > 512) optHeight = 512;
1002 break;
1003 case 'e':
1004 /* eat all remaining arguments */
1005 if (++f < argc) opt_cmd = &argv[f];
1006 f = argc+1;
1007 case 'T':
1008 case 'C':
1009 case 'l':
1010 ++f;
1011 break;
1012 case 'S':
1013 break;
1017 setenv("TERM", opt_term, 1);
1018 mclock_init();
1019 setlocale(LC_ALL, "");
1020 initLCConversion();
1021 childsigfd = summonChildSaviour();
1022 if (childsigfd < 0) k8t_die("can't summon dead children saviour!");
1023 updateTabBar = 1;
1024 termidx = 0;
1025 curterm = k8t_termalloc();
1026 k8t_tmInitialize(curterm, optWidth, optHeight, opt_maxhistory);
1027 // pixmap will be created in xinit()
1028 if (K8T_DATA(curterm)->execcmd != NULL) { free(K8T_DATA(curterm)->execcmd); K8T_DATA(curterm)->execcmd = NULL; }
1029 if (k8t_ttyNew(curterm) != 0) k8t_die("can't run process");
1030 opt_cmd = NULL;
1031 xinit();
1032 k8t_selInit(curterm);
1033 if (runcmd != NULL) executeCommands(runcmd);
1034 run();
1035 return 0;