1 /* See LICENSE for licence details. */
3 # define VERSION "0.3.1.beta9"
5 # define VERSION GIT_VERSION
11 #define _XOPEN_SOURCE 600
28 #include <sys/ioctl.h>
29 #include <sys/select.h>
32 #include <sys/types.h>
36 #include <X11/Xatom.h>
38 #include <X11/Xutil.h>
39 #include <X11/cursorfont.h>
40 #include <X11/keysym.h>
45 // uncomment the following to use XCreateGlyphCursor() instead of XCreatePixmapCursor()
46 //#define BLANKPTR_USE_GLYPH_CURSOR
49 //#define PASTE_SELECTION_DEBUG
51 //#define DUMP_KEYSYMS
55 //#define DUMP_PROG_OUTPUT
56 //#define DUMP_PROG_INPUT
58 //#define DUMP_IO_READ
59 //#define DUMP_IO_WRITE
61 #if defined(DUMP_IO_READ) || defined(DUMP_IO_WRITE)
66 # define DUMP_KEYPAD_SWITCH(strflag,strstate) do { fprintf(stderr, "KEYPAD %s (%s)\n", (strstate), (strflag)); } while (0)
68 # define DUMP_KEYPAD_SWITCH(strflag,strstate) ((void)(sizeof(strflag)+sizeof(strstate)))
72 ////////////////////////////////////////////////////////////////////////////////
77 ////////////////////////////////////////////////////////////////////////////////
78 #define USAGE_VERSION "k8sterm " VERSION "\n(c) 2010-2012 st engineers and Ketmar // Vampire Avalon\n"
80 "usage: sterm [options]\n" \
82 "-v show version and exit\n" \
83 "-h show this help and exit\n" \
85 "-t title set window title\n" \
86 "-c class set window class\n" \
87 "-w windowid embed in window id given id\n" \
88 "-T termname set TERM name\n" \
89 "-C config_file use custom config file\n" \
90 "-l langiconv use given locale\n" \
91 "-R stcmd run this INTERNAL k8sterm command\n" \
92 "-e command... run given command and pass all following args to it\n"
95 ////////////////////////////////////////////////////////////////////////////////
96 /* masks for key translation */
97 #define XK_NO_MOD (UINT_MAX)
98 #define XK_ANY_MOD (0)
101 /* misc utility macros */
105 #define K8T_MAX(__a,__b) \
106 ({ typeof(__a)_a = (__a); \
107 typeof(__b)_b = (__b); \
108 _a > _b ? _a : _b; })
113 #define K8T_MIN(__a,__b) \
114 ({ typeof(__a)_a = (__a); \
115 typeof(__b)_b = (__b); \
116 _a < _b ? _a : _b; })
118 #define K8T_ARRLEN(a) (sizeof(a)/sizeof(a[0]))
119 #define K8T_DEFAULT(a, b) (a) = ((a) ? (a) : (b))
120 #define K8T_BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
121 #define K8T_LIMIT(x, a, b) ((x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x))
122 #define K8T_ATTRCMP(a, b) ((a).attr != (b).attr || (a).fg != (b).fg || (a).bg != (b).bg)
123 #define K8T_ISSET(_tr,flag) ((_tr)->mode&(flag))
124 #define K8T_X2COL(x) ((x)/xw.cw)
125 #define K8T_Y2ROW(_tr, y) ((y-(opt_tabposition==1 ? xw.tabheight : 0))/xw.ch-((_tr) != NULL ? (_tr)->topline : 0))
126 #define K8T_ISGFX(mode) ((mode)&K8T_ATTR_GFX)
129 ////////////////////////////////////////////////////////////////////////////////
131 K8T_BELL_AUDIO
= 0x01,
132 K8T_BELL_URGENT
= 0x02
141 // glyph attributes (bitmask)
143 K8T_ATTR_NULL
= 0x00,
144 K8T_ATTR_REVERSE
= 0x01,
145 K8T_ATTR_UNDERLINE
= 0x02,
146 K8T_ATTR_BOLD
= 0x04,
148 K8T_ATTR_DEFFG
= 0x10,
149 K8T_ATTR_DEFBG
= 0x20,
152 // cursor operations for k8t_tmCursor()
153 enum cursor_movement
{
158 // cursor state (bitmask)
160 K8T_CURSOR_DEFAULT
= 0x00,
161 K8T_CURSOR_HIDE
= 0x01,
162 K8T_CURSOR_WRAPNEXT
= 0x02
165 // glyph state (bitmask)
167 K8T_GLYPH_SET
= 0x01, /* for selection only */
168 K8T_GLYPH_DIRTY
= 0x02,
169 K8T_GLYPH_WRAP
= 0x10, /* can be set for the last line glyph */
172 // terminal mode flags (bitmask)
174 K8T_MODE_WRAP
= 0x01,
175 K8T_MODE_INSERT
= 0x02,
176 K8T_MODE_APPKEYPAD
= 0x04,
177 K8T_MODE_ALTSCREEN
= 0x08,
178 K8T_MODE_CRLF
= 0x10,
179 K8T_MODE_MOUSEBTN
= 0x20,
180 K8T_MODE_MOUSEMOTION
= 0x40,
181 K8T_MODE_MOUSE
= 0x20|0x40,
182 K8T_MODE_REVERSE
= 0x80,
183 K8T_MODE_BRACPASTE
= 0x100,
184 K8T_MODE_FOCUSEVT
= 0x200,
185 K8T_MODE_DISPCTRL
= 0x400, //TODO: not implemented yet
186 K8T_MODE_GFX0
= 0x1000,
187 K8T_MODE_GFX1
= 0x2000,
190 // escape sequence processor state
192 K8T_ESC_START
= 0x01,
195 K8T_ESC_TITLE
= 0x08,
196 K8T_ESC_ALTCHARSET
= 0x10,
198 K8T_ESC_PERCENT
= 0x40,
199 K8T_ESC_ALTG1
= 0x80,
202 // X11 window state flags
204 K8T_WIN_VISIBLE
= 0x01,
205 K8T_WIN_REDRAW
= 0x02,
206 K8T_WIN_FOCUSED
= 0x04
210 ////////////////////////////////////////////////////////////////////////////////
211 typedef struct __attribute__((packed
)) {
213 union __attribute__((packed
)) {
217 uint8_t attr
; /* attribute flags */
218 uint16_t fg
; /* foreground */
219 uint16_t bg
; /* background */
220 uint8_t state
; /* state flags */
223 typedef K8TGlyph
*K8TLine
;
227 K8TGlyph attr
; /* current char attributes */
234 /* CSI Escape sequence structs */
235 /* ESC '[' [[ [<priv>] <arg> [;]] <mode>] */
237 char buf
[K8T_ESC_BUF_SIZ
]; /* raw string */
238 int len
; /* raw string length */
240 int arg
[K8T_ESC_ARG_SIZ
];
241 int narg
; /* nb of args */
246 /* Purely graphic info */
257 int w
; /* window width */
258 int h
; /* window height */
259 int bufw
; /* pixmap width */
260 int bufh
; /* pixmap height */
261 int ch
; /* char height */
262 int cw
; /* char width */
263 char state
; /* focus, redraw, visible */
265 int tch
; /* tab text char height */
268 //struct timeval lastdraw;
272 /* TODO: use better name for vars... */
277 struct { int x
, y
; } b
, e
;
285 /* Drawing Context */
287 uint32_t *ncol
; // normal colors
288 uint32_t *bcol
; // b/w colors
289 uint32_t *gcol
; // green colors
303 typedef struct K8TCmdLine K8TCmdLine
;
304 typedef struct K8Term K8Term
;
306 typedef void (*CmdLineExecFn
) (K8Term
*term
, K8TCmdLine
*cmdline
, int cancelled
);
309 int cmdMode
; // K8T_CMDMODE_xxx
310 char cmdline
[UTF_SIZ
*CMDLINE_SIZE
];
311 int cmdreslen
; // byte length of 'reserved' (read-only) part
312 int cmdofs
; // byte offset of the first visible UTF-8 char in cmdline
313 char cmdc
[UTF_SIZ
+1]; // buffer to collect UTF-8 char
314 int cmdcl
; // index in cmdc, used to collect UTF-8 chars
315 int cmdtabpos
; // # of bytes (not UTF-8 chars!) used in autocompletion or -1
316 const char *cmdcurtabc
; // current autocompleted command
317 CmdLineExecFn cmdexecfn
;
322 /* Internal representation of the screen */
327 int waitkeypress
; /* child is dead, awaiting for keypress */
328 char *exitmsg
; /* message for waitkeypress */
329 int needConv
; /* 0: utf-8 locale */
337 MSTime lastBlinkTime
;
338 int curblinkinactive
;
340 int row
; /* nb row */
341 int col
; /* nb col */
342 int topline
; /* top line for drawing (0: no history; 1: show one history line; etc) */
343 int linecount
; /* full, with history */
344 int maxhistory
;/* max history lines; 0: none; <0: infinite */
345 K8TLine
*line
; /* screen */
346 K8TLine
*alt
; /* alternate screen */
347 char *dirty
; /* dirtyness of lines */
348 K8TCursor c
; /* cursor */
349 int top
; /* top scroll limit */
350 int bot
; /* bottom scroll limit */
351 int mode
; /* terminal mode flags */
352 int mousemode
; /* mouse mode: 1000, 1005, 1006, 1015 */
353 int esc
; /* escape state flags */
354 int charset
; /* K8T_MODE_GFX0 or K8T_MODE_GFX1 */
355 int ignoredecpad
; /* ignore DEC keypad switching sequences */
357 K8TCursor csaved
; /* saved cursor info */
358 // old cursor position
362 char title
[K8T_ESC_TITLE_SIZ
+1];
370 #ifdef DUMP_PROG_OUTPUT
378 char drawbuf
[DRAW_BUF_SIZ
];
401 MSTime lastActiveTime
;
408 // locale conversions
409 // returns new length
410 int (*loc2utf
) (K8Term
*term
, char *dest
, const char *src
, int len
);
411 int (*utf2loc
) (K8Term
*term
, char *dest
, const char *src
, int len
);
413 void (*drawSetFG
) (K8Term
*term
, int clr
);
414 void (*drawSetBG
) (K8Term
*term
, int clr
);
416 void (*drawRect
) (K8Term
*term
, int x0
, int y0
, int cols
, int rows
); // use FG
417 void (*drawFillRect
) (K8Term
*term
, int x0
, int y0
, int cols
, int rows
); // use FG
418 void (*drawCopyArea
) (K8Term
*term
, int x0
, int y0
, int cols
, int rows
); // copy pixmap to screen
419 void (*drawString
) (K8Term
*term
, int x
, int y
, int cols
, const K8TGlyph
*base
, int fontset
, const char *s
, int bytelen
);
423 ////////////////////////////////////////////////////////////////////////////////
427 ////////////////////////////////////////////////////////////////////////////////
433 ////////////////////////////////////////////////////////////////////////////////
434 static void executeCommands (const char *str
);
435 static const char *findCommandCompletion (const char *str
, int slen
, const char *prev
);
438 static void k8t_ttyResize (K8Term *term);
439 static void k8t_tmSetAttr (K8Term *term, int *attr, int l);
440 static void k8t_tmResetAttrs (K8Term *term);
441 static int k8t_tmDoWrap (K8Term *term);
442 static void k8t_tmPutC (K8Term *term, const char *c); // `c` is utf-8
443 static void k8t_ttyWrite (K8Term *term, const char *s, size_t n);
444 static void k8t_ttyWriteNoEnc (K8Term *term, const char *s, size_t n);
445 static void k8t_tmDirty (K8Term *term, int top, int bot);
446 static void k8t_tmFullDirty (K8Term *term);
449 static void tdrawfatalmsg (K8Term
*term
, const char *msg
);
451 static void xclearunused (void);
452 static void xseturgency (int add
);
453 static void xfixsel (void);
454 static void xblankPointer (void);
455 static void xunblankPointer (void);
456 static void xdrawTabBar (void);
459 static void k8t_Draw (K8Term *term, int forced);
462 static void tcmdput (K8Term
*term
, K8TCmdLine
*cmdline
, const char *s
, int len
);
467 ////////////////////////////////////////////////////////////////////////////////
468 static inline void k8t_ttyWriteStr (K8Term
*term
, const char *s
) { if (s
!= NULL
&& s
[0]) k8t_ttyWrite(term
, s
, strlen(s
)); }
469 static inline void k8t_ttyWriteStrNoEnc (K8Term
*term
, const char *s
) { if (s
!= NULL
&& s
[0]) k8t_ttyWriteNoEnc(term
, s
, strlen(s
)); }
472 static inline void k8t_ttyPutStr (K8Term
*term
, const char *s
) { if (s
!= NULL
) while (*s
) { k8t_tmPutC(term
, s
); ++s
; } }
475 static inline uint32_t getColor (int idx
) {
476 if (globalBW
&& (curterm
== NULL
|| !curterm
->blackandwhite
)) return dc
.clrs
[globalBW
][idx
];
477 if (curterm
!= NULL
) return dc
.clrs
[curterm
->blackandwhite
%3][idx
];
478 return dc
.clrs
[0][idx
];
482 ////////////////////////////////////////////////////////////////////////////////
483 #include "iniparse.c"
487 ////////////////////////////////////////////////////////////////////////////////
491 ////////////////////////////////////////////////////////////////////////////////
492 #include "termswitch.c"
495 ////////////////////////////////////////////////////////////////////////////////
496 #include "selection.c"
497 #include "tabmouseutils.c"
498 #include "mouseevents.c"
501 ////////////////////////////////////////////////////////////////////////////////
504 #include "ttyutils.c"
506 #include "ttyresize.c"
509 ////////////////////////////////////////////////////////////////////////////////
510 #include "tcmdline.c"
511 #include "tfatalbox.c"
514 ////////////////////////////////////////////////////////////////////////////////
516 #include "x11drawstr.c"
517 #include "x11drawcur.c"
518 #include "x11drawtabs.c"
519 #include "x11drawcmdline.c"
523 ////////////////////////////////////////////////////////////////////////////////
524 #include "x11evtvis.c"
525 #include "x11evtkbd.c"
528 ////////////////////////////////////////////////////////////////////////////////
530 static void xevtcbcmessage (XEvent
*e
) {
531 /* See xembed specs http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html */
532 if (e
->xclient
.message_type
== xw
.xembed
&& e
->xclient
.format
== 32) {
533 if (e
->xclient
.data
.l
[1] == XEMBED_FOCUS_IN
) {
534 xw
.state
|= K8T_WIN_FOCUSED
;
536 k8t_tmSendFocusEvent(curterm
, 1);
537 } else if (e
->xclient
.data
.l
[1] == XEMBED_FOCUS_OUT
) {
538 xw
.state
&= ~K8T_WIN_FOCUSED
;
539 k8t_tmSendFocusEvent(curterm
, 0);
541 k8t_DrawCursor(curterm
);
543 k8t_DrawCopy(curterm
, 0, 0, curterm
->col
, curterm
->row
);
547 if (e
->xclient
.data
.l
[0] == XA_WM_DELETE_WINDOW
) {
548 closeRequestComes
= 1;
554 ////////////////////////////////////////////////////////////////////////////////
555 static inline int last_draw_too_old (void) {
556 MSTime tt
= mclock_ticks();
558 if (curterm
!= NULL
) {
559 if (curterm
->dead
|| !curterm
->wantRedraw
) return 0;
562 (tt
-curterm
->lastDrawTime
>= opt_drawtimeout
||
563 tt
-lastDrawTime
>= (curterm
->justSwapped
? opt_swapdrawtimeout
: opt_maxdrawtimeout
));
565 return (tt
-lastDrawTime
>= opt_maxdrawtimeout
);
569 ////////////////////////////////////////////////////////////////////////////////
571 static void (*handler
[LASTEvent
])(XEvent
*) = {
572 [KeyPress
] = xevtcbkpress
,
573 [ClientMessage
] = xevtcbcmessage
,
574 [ConfigureNotify
] = xevtcbresise
,
575 [VisibilityNotify
] = xevtcbvisibility
,
576 [UnmapNotify
] = xevtcbunmap
,
577 [Expose
] = xevtcbexpose
,
578 [FocusIn
] = xevtcbfocus
,
579 [FocusOut
] = xevtcbfocus
,
580 [MotionNotify
] = xevtcbbmotion
,
581 [ButtonPress
] = xevtcbbpress
,
582 [ButtonRelease
] = xevtcbbrelease
,
583 [SelectionNotify
] = xevtcbselnotify
,
584 [SelectionRequest
] = xevtcbselrequest
,
585 [SelectionClear
] = xevtcbselclear
,
589 static void run (void) {
590 //int stuff_to_print = 0;
591 int xfd
= XConnectionNumber(xw
.dpy
);
593 ptrLastMove
= mclock_ticks();
594 while (term_count
> 0) {
597 struct timeval timeout
;
604 //FD_SET(curterm->cmdfd, &rfd);
605 // have something to write?
606 for (int f
= 0; f
< term_count
; ++f
) {
607 K8Term
*t
= term_array
[f
];
609 if (!t
->dead
&& t
->cmdfd
>= 0) {
610 if (t
->cmdfd
> maxfd
) maxfd
= t
->cmdfd
;
611 FD_SET(t
->cmdfd
, &rfd
);
612 if (t
->pid
!= 0 && t
->wrbufpos
< t
->wrbufused
) FD_SET(t
->cmdfd
, &wfd
);
617 timeout
.tv_usec
= (opt_drawtimeout
+2)*1000;
618 //fprintf(stderr, "before select...\n");
619 if (select(maxfd
+1, &rfd
, &wfd
, NULL
, &timeout
) < 0) {
620 if (errno
== EINTR
) continue;
621 k8t_die("select failed: %s", strerror(errno
));
623 //fprintf(stderr, "after select...\n");
624 // process terminals i/o
625 for (int f
= 0; f
< term_count
; ++f
) {
626 K8Term
*t
= term_array
[f
];
628 if (!t
->dead
&& t
->cmdfd
>= 0) {
631 if (t
->pid
!= 0 && FD_ISSET(t
->cmdfd
, &wfd
)) k8t_ttyFlushWriteBuf(t
);
632 if (FD_ISSET(t
->cmdfd
, &rfd
)) rd
= k8t_ttyRead(t
);
634 if (t
->waitkeypress
&& t
->exitmsg
!= NULL
&& rd
< 0) {
635 // there will be no more data
639 tdrawfatalmsg(t
, t
->exitmsg
);
642 k8t_tmWantRedraw(t
, 1);
647 //fprintf(stderr, "000: term=%p; dead=%d; waitkeypress=%d\n", term, (term ? term->dead : 666), (term ? term->waitkeypress : 666));
649 //fprintf(stderr, "001: term=%p; dead=%d; waitkeypress=%d\n", term, (term ? term->dead : 666), (term ? term->waitkeypress : 666));
650 if (term_count
== 0) exit(exitcode
);
653 if (curterm
!= NULL
&& curterm
->curblink
> 0) {
654 MSTime tt
= mclock_ticks();
656 if (tt
-curterm
->lastBlinkTime
>= curterm
->curblink
) {
657 curterm
->lastBlinkTime
= tt
;
658 if ((xw
.state
&K8T_WIN_FOCUSED
) || curterm
->curblinkinactive
) {
659 curterm
->curbhidden
= (curterm
->curbhidden
? 0 : -1);
662 curterm
->curbhidden
= 0;
666 if (updateTabBar
) xdrawTabBar();
667 if (dodraw
|| last_draw_too_old()) k8t_Draw(curterm
, 0);
669 if (XPending(xw
.dpy
)) {
670 while (XPending(xw
.dpy
)) {
671 XNextEvent(xw
.dpy
, &ev
);
673 //case VisibilityNotify:
681 ptrLastMove
= mclock_ticks();
685 if (XFilterEvent(&ev
, xw
.win
)) continue;
686 if (handler
[ev
.type
]) (handler
[ev
.type
])(&ev
);
690 if (curterm
!= NULL
) {
691 switch (closeRequestComes
) {
692 case 1: // just comes
693 if (opt_ignoreclose
== 0) {
694 tcmdlinehide(curterm
, &curterm
->cmdline
);
695 tcmdlineinitex(curterm
, &curterm
->cmdline
, "Do you really want to close sterm [Y/n]? ");
696 curterm
->cmdline
.cmdexecfn
= cmdline_closequeryexec
;
697 } else if (opt_ignoreclose
< 0) {
698 //FIXME: kill all clients?
701 closeRequestComes
= 0;
703 case 2: // ok, die now
704 //FIXME: kill all clients?
709 if (!ptrBlanked
&& opt_ptrblank
> 0 && mclock_ticks()-ptrLastMove
>= opt_ptrblank
) {
716 ////////////////////////////////////////////////////////////////////////////////
718 #include "commands.c"
721 ////////////////////////////////////////////////////////////////////////////////
722 #include "childkiller.c"
725 ////////////////////////////////////////////////////////////////////////////////
726 int main (int argc
, char *argv
[]) {
727 char *configfile
= NULL
;
732 for (int f
= 1; f
< argc
; f
++) {
733 if (strcmp(argv
[f
], "-name") == 0) { ++f
; continue; }
734 if (strcmp(argv
[f
], "-into") == 0) { ++f
; continue; }
735 if (strcmp(argv
[f
], "-embed") == 0) { ++f
; continue; }
736 switch (argv
[f
][0] != '-' || argv
[f
][2] ? -1 : argv
[f
][1]) {
737 case 'e': f
= argc
+1; break;
748 opt_term
= strdup(argv
[f
]);
754 if (configfile
!= NULL
) free(configfile
);
755 configfile
= strdup(argv
[f
]);
759 if (++f
< argc
) cliLocale
= argv
[f
];
761 case 'S': // single-tab mode
765 fprintf(stderr
, "%s", USAGE_VERSION
);
769 fprintf(stderr
, "%s%s", USAGE_VERSION
, USAGE
);
774 initDefaultOptions();
775 if (configfile
== NULL
) {
776 const char *home
= getenv("HOME");
779 configfile
= SPrintf("%s/.sterm.rc", home
);
780 if (loadConfig(configfile
) == 0) goto cfgdone
;
781 free(configfile
); configfile
= NULL
;
783 configfile
= SPrintf("%s/.config/sterm.rc", home
);
784 if (loadConfig(configfile
) == 0) goto cfgdone
;
785 free(configfile
); configfile
= NULL
;
788 configfile
= SPrintf("/etc/sterm.rc");
789 if (loadConfig(configfile
) == 0) goto cfgdone
;
790 free(configfile
); configfile
= NULL
;
792 configfile
= SPrintf("/etc/sterm/sterm.rc");
793 if (loadConfig(configfile
) == 0) goto cfgdone
;
794 free(configfile
); configfile
= NULL
;
796 configfile
= SPrintf("./.sterm.rc");
797 if (loadConfig(configfile
) == 0) goto cfgdone
;
798 free(configfile
); configfile
= NULL
;
801 if (loadConfig(configfile
) < 0) k8t_die("config file '%s' not found!", configfile
);
804 if (configfile
!= NULL
) free(configfile
); configfile
= NULL
;
806 for (int f
= 1; f
< argc
; f
++) {
807 if (strcmp(argv
[f
], "-name") == 0) { ++f
; continue; }
808 if (strcmp(argv
[f
], "-into") == 0 || strcmp(argv
[f
], "-embed") == 0) {
809 if (opt_embed
) free(opt_embed
);
810 opt_embed
= strdup(argv
[f
]);
813 switch (argv
[f
][0] != '-' || argv
[f
][2] ? -1 : argv
[f
][1]) {
817 opt_title
= strdup(argv
[f
]);
823 opt_class
= strdup(argv
[f
]);
828 if (opt_embed
) free(opt_embed
);
829 opt_embed
= strdup(argv
[f
]);
833 if (++f
< argc
) runcmd
= argv
[f
];
836 /* eat all remaining arguments */
837 if (++f
< argc
) opt_cmd
= &argv
[f
];
849 setenv("TERM", opt_term
, 1);
851 setlocale(LC_ALL
, "");
856 curterm
= k8t_termalloc();
857 if (curterm
->execcmd
!= NULL
) { free(curterm
->execcmd
); curterm
->execcmd
= NULL
; }
858 k8t_tmInitialize(curterm
, 80, 25);
859 if (k8t_ttyNew(curterm
) != 0) k8t_die("can't run process");
862 k8t_selInit(curterm
);
863 if (runcmd
!= NULL
) executeCommands(runcmd
);