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 MAX(__a,__b) \
106 ({ typeof(__a)_a = (__a); \
107 typeof(__b)_b = (__b); \
108 _a > _b ? _a : _b; })
113 #define MIN(__a,__b) \
114 ({ typeof(__a)_a = (__a); \
115 typeof(__b)_b = (__b); \
116 _a < _b ? _a : _b; })
118 #define SERRNO strerror(errno)
119 #define LEN(a) (sizeof(a)/sizeof(a[0]))
120 #define DEFAULT(a, b) (a) = ((a) ? (a) : (b))
121 #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
122 #define LIMIT(x, a, b) ((x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x))
123 #define ATTRCMP(a, b) ((a).attr != (b).attr || (a).fg != (b).fg || (a).bg != (b).bg)
124 #define IS_SET(_tr,flag) ((_tr)->mode&(flag))
125 #define X2COL(x) ((x)/xw.cw)
126 #define Y2ROW(_tr, y) ((y-(opt_tabposition==1 ? xw.tabheight : 0))/xw.ch-((_tr) != NULL ? (_tr)->topline : 0))
127 #define IS_GFX(mode) ((mode)&ATTR_GFX)
130 ////////////////////////////////////////////////////////////////////////////////
142 // glyph attributes (bitmask)
146 ATTR_UNDERLINE
= 0x02,
153 // cursor operations for tcursor()
154 enum cursor_movement
{
159 // cursor state (bitmask)
161 CURSOR_DEFAULT
= 0x00,
163 CURSOR_WRAPNEXT
= 0x02
166 // glyph state (bitmask)
168 GLYPH_SET
= 0x01, /* for selection only */
170 GLYPH_WRAP
= 0x10, /* can be set for the last line glyph */
173 // terminal mode flags (bitmask)
177 MODE_APPKEYPAD
= 0x04,
178 MODE_ALTSCREEN
= 0x08,
180 MODE_MOUSEBTN
= 0x20,
181 MODE_MOUSEMOTION
= 0x40,
182 MODE_MOUSE
= 0x20|0x40,
184 MODE_BRACPASTE
= 0x100,
185 MODE_FOCUSEVT
= 0x200,
186 MODE_DISPCTRL
= 0x400, //TODO: not implemented yet
191 // escape sequence processor state
197 ESC_ALTCHARSET
= 0x10,
203 // X11 window state flags
212 ////////////////////////////////////////////////////////////////////////////////
213 typedef unsigned char uchar
;
214 typedef unsigned int uint
;
215 typedef uint32_t uint32
;
216 typedef uint16_t ushort
;
219 typedef struct __attribute__((packed
)) {
221 union __attribute__((packed
)) {
225 uchar attr
; /* attribute flags */
226 ushort fg
; /* foreground */
227 ushort bg
; /* background */
228 uchar state
; /* state flags */
235 Glyph attr
; /* current char attributes */
242 /* CSI Escape sequence structs */
243 /* ESC '[' [[ [<priv>] <arg> [;]] <mode>] */
245 char buf
[ESC_BUF_SIZ
]; /* raw string */
246 int len
; /* raw string length */
248 int arg
[ESC_ARG_SIZ
];
249 int narg
; /* nb of args */
254 /* Purely graphic info */
265 int w
; /* window width */
266 int h
; /* window height */
267 int bufw
; /* pixmap width */
268 int bufh
; /* pixmap height */
269 int ch
; /* char height */
270 int cw
; /* char width */
271 char state
; /* focus, redraw, visible */
273 int tch
; /* tab text char height */
276 //struct timeval lastdraw;
280 /* TODO: use better name for vars... */
285 struct { int x
, y
; } b
, e
;
293 /* Drawing Context */
295 uint32
*ncol
; // normal colors
296 uint32
*bcol
; // b/w colors
297 uint32
*gcol
; // green colors
311 typedef struct CmdLine CmdLine
;
312 typedef struct Term Term
;
314 typedef void (*CmdLineExecFn
) (Term
*term
, CmdLine
*cmdline
, int cancelled
);
317 int cmdMode
; // CMDMODE_xxx
318 char cmdline
[UTF_SIZ
*CMDLINE_SIZE
];
319 int cmdreslen
; // byte length of 'reserved' (read-only) part
320 int cmdofs
; // byte offset of the first visible UTF-8 char in cmdline
321 char cmdc
[UTF_SIZ
+1]; // buffer to collect UTF-8 char
322 int cmdcl
; // index in cmdc, used to collect UTF-8 chars
323 int cmdtabpos
; // # of bytes (not UTF-8 chars!) used in autocompletion or -1
324 const char *cmdcurtabc
; // current autocompleted command
325 CmdLineExecFn cmdexecfn
;
330 /* Internal representation of the screen */
335 int waitkeypress
; /* child is dead, awaiting for keypress */
336 char *exitmsg
; /* message for waitkeypress */
337 int needConv
; /* 0: utf-8 locale */
345 MSTime lastBlinkTime
;
346 int curblinkinactive
;
348 int row
; /* nb row */
349 int col
; /* nb col */
350 int topline
; /* top line for drawing (0: no history; 1: show one history line; etc) */
351 int linecount
; /* full, with history */
352 int maxhistory
;/* max history lines; 0: none; <0: infinite */
353 Line
*line
; /* screen */
354 Line
*alt
; /* alternate screen */
355 char *dirty
; /* dirtyness of lines */
356 TCursor c
; /* cursor */
357 int top
; /* top scroll limit */
358 int bot
; /* bottom scroll limit */
359 int mode
; /* terminal mode flags */
360 int mousemode
; /* mouse mode: 1000, 1005, 1006, 1015 */
361 int esc
; /* escape state flags */
362 int charset
; /* 0 or 1 */
364 TCursor csaved
; /* saved cursor info */
365 // old cursor position
369 char title
[ESC_TITLE_SIZ
+1];
377 #ifdef DUMP_PROG_OUTPUT
385 char drawbuf
[DRAW_BUF_SIZ
];
408 MSTime lastActiveTime
;
414 ////////////////////////////////////////////////////////////////////////////////
418 ////////////////////////////////////////////////////////////////////////////////
424 ////////////////////////////////////////////////////////////////////////////////
425 static void executeCommands (const char *str
);
426 static const char *findCommandCompletion (const char *str
, int slen
, const char *prev
);
428 static void ttyresize (Term
*term
);
429 static void tsetattr (Term
*term
, int *attr
, int l
);
430 static void tresetattrs (Term
*term
);
431 static int tdowrap (Term
*term
);
432 static void tputc (Term
*term
, const char *c
); // `c` is utf-8
433 static void ttywrite (Term
*term
, const char *s
, size_t n
);
434 static void ttywritenoenc (Term
*term
, const char *s
, size_t n
);
435 static void tsetdirt (Term
*term
, int top
, int bot
);
436 static void tfulldirt (Term
*term
);
438 static void tdrawfatalmsg (Term
*term
, const char *msg
);
440 static void xclearunused (void);
441 static void xseturgency (int add
);
442 static void xfixsel (void);
443 static void xblankPointer (void);
444 static void xunblankPointer (void);
445 static void xdrawTabBar (void);
447 static void draw (Term
*term
, int forced
);
449 static void tcmdput (Term
*term
, CmdLine
*cmdline
, const char *s
, int len
);
452 ////////////////////////////////////////////////////////////////////////////////
453 static inline void ttywritestr (Term
*term
, const char *s
) { if (s
!= NULL
&& s
[0]) ttywrite(term
, s
, strlen(s
)); }
454 static inline void ttywritestrnoenc (Term
*term
, const char *s
) { if (s
!= NULL
&& s
[0]) ttywritenoenc(term
, s
, strlen(s
)); }
457 static inline void tputstr (Term
*term
, const char *s
) { if (s
!= NULL
) while (*s
) { tputc(term
, s
); ++s
; } }
460 static inline uint32
getColor (int idx
) {
461 if (globalBW
&& (curterm
== NULL
|| !curterm
->blackandwhite
)) return dc
.clrs
[globalBW
][idx
];
462 if (curterm
!= NULL
) return dc
.clrs
[curterm
->blackandwhite
%3][idx
];
463 return dc
.clrs
[0][idx
];
467 ////////////////////////////////////////////////////////////////////////////////
468 #include "iniparse.c"
472 ////////////////////////////////////////////////////////////////////////////////
473 #include "termswitch.c"
476 ////////////////////////////////////////////////////////////////////////////////
477 #include "selection.c"
478 #include "tabmouseutils.c"
479 #include "mouseevents.c"
482 ////////////////////////////////////////////////////////////////////////////////
485 #include "ttyresizeioctl.c"
486 #include "ttyutils.c"
488 #include "ttyresize.c"
491 ////////////////////////////////////////////////////////////////////////////////
492 #include "tcmdline.c"
493 #include "tfatalbox.c"
496 ////////////////////////////////////////////////////////////////////////////////
498 #include "x11drawstr.c"
499 #include "x11drawcur.c"
500 #include "x11drawtabs.c"
501 #include "x11drawcmdline.c"
505 ////////////////////////////////////////////////////////////////////////////////
506 #include "x11evtvis.c"
507 #include "x11evtkbd.c"
510 ////////////////////////////////////////////////////////////////////////////////
512 static void xevtcbcmessage (XEvent
*e
) {
513 /* See xembed specs http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html */
514 if (e
->xclient
.message_type
== xw
.xembed
&& e
->xclient
.format
== 32) {
515 if (e
->xclient
.data
.l
[1] == XEMBED_FOCUS_IN
) {
516 xw
.state
|= WIN_FOCUSED
;
518 tsendfocusevent(curterm
, 1);
519 } else if (e
->xclient
.data
.l
[1] == XEMBED_FOCUS_OUT
) {
520 xw
.state
&= ~WIN_FOCUSED
;
521 tsendfocusevent(curterm
, 0);
523 xdrawcursor(curterm
);
525 xcopy(curterm
, 0, 0, curterm
->col
, curterm
->row
);
529 if (e
->xclient
.data
.l
[0] == XA_WM_DELETE_WINDOW
) {
530 closeRequestComes
= 1;
536 ////////////////////////////////////////////////////////////////////////////////
537 static inline int last_draw_too_old (void) {
538 MSTime tt
= mclock_ticks();
540 if (curterm
!= NULL
) {
541 if (curterm
->dead
|| !curterm
->wantRedraw
) return 0;
544 (tt
-curterm
->lastDrawTime
>= opt_drawtimeout
||
545 tt
-lastDrawTime
>= (curterm
->justSwapped
? opt_swapdrawtimeout
: opt_maxdrawtimeout
));
547 return (tt
-lastDrawTime
>= opt_maxdrawtimeout
);
551 ////////////////////////////////////////////////////////////////////////////////
553 static void (*handler
[LASTEvent
])(XEvent
*) = {
554 [KeyPress
] = xevtcbkpress
,
555 [ClientMessage
] = xevtcbcmessage
,
556 [ConfigureNotify
] = xevtcbresise
,
557 [VisibilityNotify
] = xevtcbvisibility
,
558 [UnmapNotify
] = xevtcbunmap
,
559 [Expose
] = xevtcbexpose
,
560 [FocusIn
] = xevtcbfocus
,
561 [FocusOut
] = xevtcbfocus
,
562 [MotionNotify
] = xevtcbbmotion
,
563 [ButtonPress
] = xevtcbbpress
,
564 [ButtonRelease
] = xevtcbbrelease
,
565 [SelectionNotify
] = xevtcbselnotify
,
566 [SelectionRequest
] = xevtcbselrequest
,
567 [SelectionClear
] = xevtcbselclear
,
571 static void run (void) {
572 //int stuff_to_print = 0;
573 int xfd
= XConnectionNumber(xw
.dpy
);
575 ptrLastMove
= mclock_ticks();
576 while (term_count
> 0) {
579 struct timeval timeout
;
586 //FD_SET(curterm->cmdfd, &rfd);
587 // have something to write?
588 for (int f
= 0; f
< term_count
; ++f
) {
589 Term
*t
= term_array
[f
];
591 if (!t
->dead
&& t
->cmdfd
>= 0) {
592 if (t
->cmdfd
> maxfd
) maxfd
= t
->cmdfd
;
593 FD_SET(t
->cmdfd
, &rfd
);
594 if (t
->pid
!= 0 && t
->wrbufpos
< t
->wrbufused
) FD_SET(t
->cmdfd
, &wfd
);
599 timeout
.tv_usec
= (opt_drawtimeout
+2)*1000;
600 //fprintf(stderr, "before select...\n");
601 if (select(maxfd
+1, &rfd
, &wfd
, NULL
, &timeout
) < 0) {
602 if (errno
== EINTR
) continue;
603 die("select failed: %s", SERRNO
);
605 //fprintf(stderr, "after select...\n");
606 // process terminals i/o
607 for (int f
= 0; f
< term_count
; ++f
) {
608 Term
*t
= term_array
[f
];
610 if (!t
->dead
&& t
->cmdfd
>= 0) {
613 if (t
->pid
!= 0 && FD_ISSET(t
->cmdfd
, &wfd
)) ttyflushwrbuf(t
);
614 if (FD_ISSET(t
->cmdfd
, &rfd
)) rd
= ttyread(t
);
616 if (t
->waitkeypress
&& t
->exitmsg
!= NULL
&& rd
< 0) {
617 // there will be no more data
621 tdrawfatalmsg(t
, t
->exitmsg
);
629 //fprintf(stderr, "000: term=%p; dead=%d; waitkeypress=%d\n", term, (term ? term->dead : 666), (term ? term->waitkeypress : 666));
631 //fprintf(stderr, "001: term=%p; dead=%d; waitkeypress=%d\n", term, (term ? term->dead : 666), (term ? term->waitkeypress : 666));
632 if (term_count
== 0) exit(exitcode
);
635 if (curterm
!= NULL
&& curterm
->curblink
> 0) {
636 MSTime tt
= mclock_ticks();
638 if (tt
-curterm
->lastBlinkTime
>= curterm
->curblink
) {
639 curterm
->lastBlinkTime
= tt
;
640 if ((xw
.state
&WIN_FOCUSED
) || curterm
->curblinkinactive
) {
641 curterm
->curbhidden
= (curterm
->curbhidden
? 0 : -1);
644 curterm
->curbhidden
= 0;
648 if (updateTabBar
) xdrawTabBar();
649 if (dodraw
|| last_draw_too_old()) draw(curterm
, 0);
651 if (XPending(xw
.dpy
)) {
652 while (XPending(xw
.dpy
)) {
653 XNextEvent(xw
.dpy
, &ev
);
655 //case VisibilityNotify:
663 ptrLastMove
= mclock_ticks();
667 if (XFilterEvent(&ev
, xw
.win
)) continue;
668 if (handler
[ev
.type
]) (handler
[ev
.type
])(&ev
);
672 if (curterm
!= NULL
) {
673 switch (closeRequestComes
) {
674 case 1: // just comes
675 if (opt_ignoreclose
== 0) {
676 tcmdlinehide(curterm
, &curterm
->cmdline
);
677 tcmdlineinitex(curterm
, &curterm
->cmdline
, "Do you really want to close sterm [Y/n]? ");
678 curterm
->cmdline
.cmdexecfn
= cmdline_closequeryexec
;
679 } else if (opt_ignoreclose
< 0) {
680 //FIXME: kill all clients?
683 closeRequestComes
= 0;
685 case 2: // ok, die now
686 //FIXME: kill all clients?
691 if (!ptrBlanked
&& opt_ptrblank
> 0 && mclock_ticks()-ptrLastMove
>= opt_ptrblank
) {
698 ////////////////////////////////////////////////////////////////////////////////
700 #include "commands.c"
703 ////////////////////////////////////////////////////////////////////////////////
704 #include "childkiller.c"
707 ////////////////////////////////////////////////////////////////////////////////
708 int main (int argc
, char *argv
[]) {
709 char *configfile
= NULL
;
714 for (int f
= 1; f
< argc
; f
++) {
715 if (strcmp(argv
[f
], "-name") == 0) { ++f
; continue; }
716 if (strcmp(argv
[f
], "-into") == 0) { ++f
; continue; }
717 if (strcmp(argv
[f
], "-embed") == 0) { ++f
; continue; }
718 switch (argv
[f
][0] != '-' || argv
[f
][2] ? -1 : argv
[f
][1]) {
719 case 'e': f
= argc
+1; break;
730 opt_term
= strdup(argv
[f
]);
736 if (configfile
!= NULL
) free(configfile
);
737 configfile
= strdup(argv
[f
]);
741 if (++f
< argc
) cliLocale
= argv
[f
];
743 case 'S': // single-tab mode
747 fprintf(stderr
, "%s", USAGE_VERSION
);
751 fprintf(stderr
, "%s%s", USAGE_VERSION
, USAGE
);
756 initDefaultOptions();
757 if (configfile
== NULL
) {
758 const char *home
= getenv("HOME");
761 configfile
= SPrintf("%s/.sterm.rc", home
);
762 if (loadConfig(configfile
) == 0) goto cfgdone
;
763 free(configfile
); configfile
= NULL
;
765 configfile
= SPrintf("%s/.config/sterm.rc", home
);
766 if (loadConfig(configfile
) == 0) goto cfgdone
;
767 free(configfile
); configfile
= NULL
;
770 configfile
= SPrintf("/etc/sterm.rc");
771 if (loadConfig(configfile
) == 0) goto cfgdone
;
772 free(configfile
); configfile
= NULL
;
774 configfile
= SPrintf("/etc/sterm/sterm.rc");
775 if (loadConfig(configfile
) == 0) goto cfgdone
;
776 free(configfile
); configfile
= NULL
;
778 configfile
= SPrintf("./.sterm.rc");
779 if (loadConfig(configfile
) == 0) goto cfgdone
;
780 free(configfile
); configfile
= NULL
;
783 if (loadConfig(configfile
) < 0) die("config file '%s' not found!", configfile
);
786 if (configfile
!= NULL
) free(configfile
); configfile
= NULL
;
788 for (int f
= 1; f
< argc
; f
++) {
789 if (strcmp(argv
[f
], "-name") == 0) { ++f
; continue; }
790 if (strcmp(argv
[f
], "-into") == 0 || strcmp(argv
[f
], "-embed") == 0) {
791 if (opt_embed
) free(opt_embed
);
792 opt_embed
= strdup(argv
[f
]);
795 switch (argv
[f
][0] != '-' || argv
[f
][2] ? -1 : argv
[f
][1]) {
799 opt_title
= strdup(argv
[f
]);
805 opt_class
= strdup(argv
[f
]);
810 if (opt_embed
) free(opt_embed
);
811 opt_embed
= strdup(argv
[f
]);
815 if (++f
< argc
) runcmd
= argv
[f
];
818 /* eat all remaining arguments */
819 if (++f
< argc
) opt_cmd
= &argv
[f
];
831 setenv("TERM", opt_term
, 1);
833 setlocale(LC_ALL
, "");
838 curterm
= termalloc();
839 if (curterm
->execcmd
!= NULL
) { free(curterm
->execcmd
); curterm
->execcmd
= NULL
; }
840 tinitialize(curterm
, 80, 25);
841 if (ttynew(curterm
) != 0) die("can't run process");
845 if (runcmd
!= NULL
) executeCommands(runcmd
);