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
;
410 ////////////////////////////////////////////////////////////////////////////////
414 ////////////////////////////////////////////////////////////////////////////////
420 ////////////////////////////////////////////////////////////////////////////////
421 static void executeCommands (const char *str
);
422 static const char *findCommandCompletion (const char *str
, int slen
, const char *prev
);
425 static void k8t_ttyResize (K8Term *term);
426 static void k8t_tmSetAttr (K8Term *term, int *attr, int l);
427 static void k8t_tmResetAttrs (K8Term *term);
428 static int k8t_tmDoWrap (K8Term *term);
429 static void k8t_tmPutC (K8Term *term, const char *c); // `c` is utf-8
430 static void k8t_ttyWrite (K8Term *term, const char *s, size_t n);
431 static void k8t_ttyWriteNoEnc (K8Term *term, const char *s, size_t n);
432 static void k8t_tmDirty (K8Term *term, int top, int bot);
433 static void k8t_tmFullDirty (K8Term *term);
436 static void tdrawfatalmsg (K8Term
*term
, const char *msg
);
438 static void xclearunused (void);
439 static void xseturgency (int add
);
440 static void xfixsel (void);
441 static void xblankPointer (void);
442 static void xunblankPointer (void);
443 static void xdrawTabBar (void);
446 static void k8t_Draw (K8Term *term, int forced);
449 static void tcmdput (K8Term
*term
, K8TCmdLine
*cmdline
, const char *s
, int len
);
454 ////////////////////////////////////////////////////////////////////////////////
455 static inline void k8t_ttyWriteStr (K8Term
*term
, const char *s
) { if (s
!= NULL
&& s
[0]) k8t_ttyWrite(term
, s
, strlen(s
)); }
456 static inline void k8t_ttyWriteStrNoEnc (K8Term
*term
, const char *s
) { if (s
!= NULL
&& s
[0]) k8t_ttyWriteNoEnc(term
, s
, strlen(s
)); }
459 static inline void k8t_ttyPutStr (K8Term
*term
, const char *s
) { if (s
!= NULL
) while (*s
) { k8t_tmPutC(term
, s
); ++s
; } }
462 static inline uint32_t getColor (int idx
) {
463 if (globalBW
&& (curterm
== NULL
|| !curterm
->blackandwhite
)) return dc
.clrs
[globalBW
][idx
];
464 if (curterm
!= NULL
) return dc
.clrs
[curterm
->blackandwhite
%3][idx
];
465 return dc
.clrs
[0][idx
];
469 ////////////////////////////////////////////////////////////////////////////////
470 #include "iniparse.c"
474 ////////////////////////////////////////////////////////////////////////////////
475 #include "termswitch.c"
478 ////////////////////////////////////////////////////////////////////////////////
479 #include "selection.c"
480 #include "tabmouseutils.c"
481 #include "mouseevents.c"
484 ////////////////////////////////////////////////////////////////////////////////
487 #include "ttyutils.c"
489 #include "ttyresize.c"
492 ////////////////////////////////////////////////////////////////////////////////
493 #include "tcmdline.c"
494 #include "tfatalbox.c"
497 ////////////////////////////////////////////////////////////////////////////////
499 #include "x11drawstr.c"
500 #include "x11drawcur.c"
501 #include "x11drawtabs.c"
502 #include "x11drawcmdline.c"
506 ////////////////////////////////////////////////////////////////////////////////
507 #include "x11evtvis.c"
508 #include "x11evtkbd.c"
511 ////////////////////////////////////////////////////////////////////////////////
513 static void xevtcbcmessage (XEvent
*e
) {
514 /* See xembed specs http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html */
515 if (e
->xclient
.message_type
== xw
.xembed
&& e
->xclient
.format
== 32) {
516 if (e
->xclient
.data
.l
[1] == XEMBED_FOCUS_IN
) {
517 xw
.state
|= K8T_WIN_FOCUSED
;
519 k8t_tmSendFocusEvent(curterm
, 1);
520 } else if (e
->xclient
.data
.l
[1] == XEMBED_FOCUS_OUT
) {
521 xw
.state
&= ~K8T_WIN_FOCUSED
;
522 k8t_tmSendFocusEvent(curterm
, 0);
524 k8t_DrawCursor(curterm
);
526 k8t_DrawCopy(curterm
, 0, 0, curterm
->col
, curterm
->row
);
530 if (e
->xclient
.data
.l
[0] == XA_WM_DELETE_WINDOW
) {
531 closeRequestComes
= 1;
537 ////////////////////////////////////////////////////////////////////////////////
538 static inline int last_draw_too_old (void) {
539 MSTime tt
= mclock_ticks();
541 if (curterm
!= NULL
) {
542 if (curterm
->dead
|| !curterm
->wantRedraw
) return 0;
545 (tt
-curterm
->lastDrawTime
>= opt_drawtimeout
||
546 tt
-lastDrawTime
>= (curterm
->justSwapped
? opt_swapdrawtimeout
: opt_maxdrawtimeout
));
548 return (tt
-lastDrawTime
>= opt_maxdrawtimeout
);
552 ////////////////////////////////////////////////////////////////////////////////
554 static void (*handler
[LASTEvent
])(XEvent
*) = {
555 [KeyPress
] = xevtcbkpress
,
556 [ClientMessage
] = xevtcbcmessage
,
557 [ConfigureNotify
] = xevtcbresise
,
558 [VisibilityNotify
] = xevtcbvisibility
,
559 [UnmapNotify
] = xevtcbunmap
,
560 [Expose
] = xevtcbexpose
,
561 [FocusIn
] = xevtcbfocus
,
562 [FocusOut
] = xevtcbfocus
,
563 [MotionNotify
] = xevtcbbmotion
,
564 [ButtonPress
] = xevtcbbpress
,
565 [ButtonRelease
] = xevtcbbrelease
,
566 [SelectionNotify
] = xevtcbselnotify
,
567 [SelectionRequest
] = xevtcbselrequest
,
568 [SelectionClear
] = xevtcbselclear
,
572 static void run (void) {
573 //int stuff_to_print = 0;
574 int xfd
= XConnectionNumber(xw
.dpy
);
576 ptrLastMove
= mclock_ticks();
577 while (term_count
> 0) {
580 struct timeval timeout
;
587 //FD_SET(curterm->cmdfd, &rfd);
588 // have something to write?
589 for (int f
= 0; f
< term_count
; ++f
) {
590 K8Term
*t
= term_array
[f
];
592 if (!t
->dead
&& t
->cmdfd
>= 0) {
593 if (t
->cmdfd
> maxfd
) maxfd
= t
->cmdfd
;
594 FD_SET(t
->cmdfd
, &rfd
);
595 if (t
->pid
!= 0 && t
->wrbufpos
< t
->wrbufused
) FD_SET(t
->cmdfd
, &wfd
);
600 timeout
.tv_usec
= (opt_drawtimeout
+2)*1000;
601 //fprintf(stderr, "before select...\n");
602 if (select(maxfd
+1, &rfd
, &wfd
, NULL
, &timeout
) < 0) {
603 if (errno
== EINTR
) continue;
604 die("select failed: %s", strerror(errno
));
606 //fprintf(stderr, "after select...\n");
607 // process terminals i/o
608 for (int f
= 0; f
< term_count
; ++f
) {
609 K8Term
*t
= term_array
[f
];
611 if (!t
->dead
&& t
->cmdfd
>= 0) {
614 if (t
->pid
!= 0 && FD_ISSET(t
->cmdfd
, &wfd
)) k8t_ttyFlushWriteBuf(t
);
615 if (FD_ISSET(t
->cmdfd
, &rfd
)) rd
= k8t_ttyRead(t
);
617 if (t
->waitkeypress
&& t
->exitmsg
!= NULL
&& rd
< 0) {
618 // there will be no more data
622 tdrawfatalmsg(t
, t
->exitmsg
);
625 k8t_tmWantRedraw(t
, 1);
630 //fprintf(stderr, "000: term=%p; dead=%d; waitkeypress=%d\n", term, (term ? term->dead : 666), (term ? term->waitkeypress : 666));
632 //fprintf(stderr, "001: term=%p; dead=%d; waitkeypress=%d\n", term, (term ? term->dead : 666), (term ? term->waitkeypress : 666));
633 if (term_count
== 0) exit(exitcode
);
636 if (curterm
!= NULL
&& curterm
->curblink
> 0) {
637 MSTime tt
= mclock_ticks();
639 if (tt
-curterm
->lastBlinkTime
>= curterm
->curblink
) {
640 curterm
->lastBlinkTime
= tt
;
641 if ((xw
.state
&K8T_WIN_FOCUSED
) || curterm
->curblinkinactive
) {
642 curterm
->curbhidden
= (curterm
->curbhidden
? 0 : -1);
645 curterm
->curbhidden
= 0;
649 if (updateTabBar
) xdrawTabBar();
650 if (dodraw
|| last_draw_too_old()) k8t_Draw(curterm
, 0);
652 if (XPending(xw
.dpy
)) {
653 while (XPending(xw
.dpy
)) {
654 XNextEvent(xw
.dpy
, &ev
);
656 //case VisibilityNotify:
664 ptrLastMove
= mclock_ticks();
668 if (XFilterEvent(&ev
, xw
.win
)) continue;
669 if (handler
[ev
.type
]) (handler
[ev
.type
])(&ev
);
673 if (curterm
!= NULL
) {
674 switch (closeRequestComes
) {
675 case 1: // just comes
676 if (opt_ignoreclose
== 0) {
677 tcmdlinehide(curterm
, &curterm
->cmdline
);
678 tcmdlineinitex(curterm
, &curterm
->cmdline
, "Do you really want to close sterm [Y/n]? ");
679 curterm
->cmdline
.cmdexecfn
= cmdline_closequeryexec
;
680 } else if (opt_ignoreclose
< 0) {
681 //FIXME: kill all clients?
684 closeRequestComes
= 0;
686 case 2: // ok, die now
687 //FIXME: kill all clients?
692 if (!ptrBlanked
&& opt_ptrblank
> 0 && mclock_ticks()-ptrLastMove
>= opt_ptrblank
) {
699 ////////////////////////////////////////////////////////////////////////////////
701 #include "commands.c"
704 ////////////////////////////////////////////////////////////////////////////////
705 #include "childkiller.c"
708 ////////////////////////////////////////////////////////////////////////////////
709 int main (int argc
, char *argv
[]) {
710 char *configfile
= NULL
;
715 for (int f
= 1; f
< argc
; f
++) {
716 if (strcmp(argv
[f
], "-name") == 0) { ++f
; continue; }
717 if (strcmp(argv
[f
], "-into") == 0) { ++f
; continue; }
718 if (strcmp(argv
[f
], "-embed") == 0) { ++f
; continue; }
719 switch (argv
[f
][0] != '-' || argv
[f
][2] ? -1 : argv
[f
][1]) {
720 case 'e': f
= argc
+1; break;
731 opt_term
= strdup(argv
[f
]);
737 if (configfile
!= NULL
) free(configfile
);
738 configfile
= strdup(argv
[f
]);
742 if (++f
< argc
) cliLocale
= argv
[f
];
744 case 'S': // single-tab mode
748 fprintf(stderr
, "%s", USAGE_VERSION
);
752 fprintf(stderr
, "%s%s", USAGE_VERSION
, USAGE
);
757 initDefaultOptions();
758 if (configfile
== NULL
) {
759 const char *home
= getenv("HOME");
762 configfile
= SPrintf("%s/.sterm.rc", home
);
763 if (loadConfig(configfile
) == 0) goto cfgdone
;
764 free(configfile
); configfile
= NULL
;
766 configfile
= SPrintf("%s/.config/sterm.rc", home
);
767 if (loadConfig(configfile
) == 0) goto cfgdone
;
768 free(configfile
); configfile
= NULL
;
771 configfile
= SPrintf("/etc/sterm.rc");
772 if (loadConfig(configfile
) == 0) goto cfgdone
;
773 free(configfile
); configfile
= NULL
;
775 configfile
= SPrintf("/etc/sterm/sterm.rc");
776 if (loadConfig(configfile
) == 0) goto cfgdone
;
777 free(configfile
); configfile
= NULL
;
779 configfile
= SPrintf("./.sterm.rc");
780 if (loadConfig(configfile
) == 0) goto cfgdone
;
781 free(configfile
); configfile
= NULL
;
784 if (loadConfig(configfile
) < 0) die("config file '%s' not found!", configfile
);
787 if (configfile
!= NULL
) free(configfile
); configfile
= NULL
;
789 for (int f
= 1; f
< argc
; f
++) {
790 if (strcmp(argv
[f
], "-name") == 0) { ++f
; continue; }
791 if (strcmp(argv
[f
], "-into") == 0 || strcmp(argv
[f
], "-embed") == 0) {
792 if (opt_embed
) free(opt_embed
);
793 opt_embed
= strdup(argv
[f
]);
796 switch (argv
[f
][0] != '-' || argv
[f
][2] ? -1 : argv
[f
][1]) {
800 opt_title
= strdup(argv
[f
]);
806 opt_class
= strdup(argv
[f
]);
811 if (opt_embed
) free(opt_embed
);
812 opt_embed
= strdup(argv
[f
]);
816 if (++f
< argc
) runcmd
= argv
[f
];
819 /* eat all remaining arguments */
820 if (++f
< argc
) opt_cmd
= &argv
[f
];
832 setenv("TERM", opt_term
, 1);
834 setlocale(LC_ALL
, "");
839 curterm
= k8t_termalloc();
840 if (curterm
->execcmd
!= NULL
) { free(curterm
->execcmd
); curterm
->execcmd
= NULL
; }
841 k8t_tmInitialize(curterm
, 80, 25);
842 if (k8t_ttyNew(curterm
) != 0) die("can't run process");
845 k8t_selInit(curterm
);
846 if (runcmd
!= NULL
) executeCommands(runcmd
);