added paste timeout
[yterm.git] / src / yterm_main.c
bloba2d0fff4314877f21da30de68337b18edddbf50d
1 /*
2 * YTerm -- (mostly) GNU/Linux X11 terminal emulator
4 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
5 * Understanding is not required. Only obedience.
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, version 3 of the License ONLY.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 /* O_TMPFILE */
20 #ifndef _GNU_SOURCE
21 # define _GNU_SOURCE
22 #endif
24 // do not turn this on, you don't need it
25 //#define USELESS_DEBUG_FEATURES
27 #include "common.h"
28 #include "utf.h"
29 #include "cellwin.h"
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <locale.h>
34 #include <pty.h>
35 #include <signal.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <time.h>
40 #include <unistd.h>
42 #include <sys/ioctl.h>
43 #include <sys/resource.h>
44 #include <sys/signalfd.h>
45 #include <sys/stat.h>
46 #include <sys/time.h>
47 #include <sys/types.h>
48 #include <sys/wait.h>
50 #include <X11/Xatom.h>
51 #include <X11/Xlib.h>
52 #include <X11/Xutil.h>
53 #include <X11/Xresource.h>
54 #include <X11/cursorfont.h>
55 #include <X11/keysym.h>
58 #include "common.c"
59 #include "utf.c"
60 #include "cellwin.c"
62 #define HAVE_CONFIG_H
63 #include "xsel/xsel.c"
66 // ////////////////////////////////////////////////////////////////////////// //
67 //#define FORCE_LOCALE
68 //#define DUMP_X11_EVENTS
69 //#define DUMP_RW_TIMEOUTS
70 //#define DUMP_READ_AMOUNT
71 //#define DUMP_WRITE_AMOUNT
72 //#define DUMP_WRITE_SHRINK
73 //#define DUMP_WRITE_GROW
76 // ////////////////////////////////////////////////////////////////////////// //
77 // bytes to reserve for write buffer
78 // write buffer may grow, but will eventually be shrinked to this value
79 #define WRITE_BUF_SIZE (128)
82 // ////////////////////////////////////////////////////////////////////////// //
83 #define DEFAULT_MONO_FONT "Terminus Bold, 20"
84 #define DEFAULT_TABS_FONT "Helvetica Medium Regular, 14"
87 // ////////////////////////////////////////////////////////////////////////// //
88 typedef struct MenuWindow_t MenuWindow;
91 // ////////////////////////////////////////////////////////////////////////// //
92 typedef struct {
93 int ascent;
94 int descent;
95 short lbearing;
96 short rbearing;
97 int width;
98 XFontStruct *fst;
99 } XFont;
102 // ////////////////////////////////////////////////////////////////////////// //
103 typedef struct {
104 char **argv;
105 int argc;
106 char *path; // can be NULL or empty
107 } ExecData;
110 // ////////////////////////////////////////////////////////////////////////// //
111 // terminal mode flags (bitmask)
112 enum {
113 YTERM_MODE_WRAP = 1u<<0, // cursor wrapping enabled
114 YTERM_MODE_INSERT = 1u<<1, // insert chars instead of overwriting?
115 YTERM_MODE_APPKEYPAD = 1u<<2, //TODO: currently does nothing (will not implement)
116 YTERM_MODE_CRLF = 1u<<3, // LF should print CR too
117 YTERM_MODE_MOUSEBTN = 1u<<4, // want mouse buttons reporting
118 YTERM_MODE_MOUSEMOTION = 1u<<5, // want mouse motion reporting
119 YTERM_MODE_REVERSE = 1u<<6, // all output is inversed
120 YTERM_MODE_BRACPASTE = 1u<<7, // "bracketed paste" mode enabled
121 YTERM_MODE_FOCUSEVT = 1u<<8, // send focus/unfocus events?
122 YTERM_MODE_DISPCTRL = 1u<<9, //TODO: not implemented yet
123 YTERM_MODE_GFX0 = 1u<<10, // font G0 is in gfx mode
124 YTERM_MODE_GFX1 = 1u<<11, // font G1 is in gfx mode
125 YTERM_MODE_FORCED_UTF = 1u<<12, // is UTF-8 mode forced via ESC # G / ESC # 8 ?
126 YTERM_MODE_VT220_KBD = 1u<<13, // xterm VT220 keyboard emulation (Home/End keys)? (reset on PID change)
127 YTERM_MODE_XTERM = 1u<<14, // is in xterm mode for Home/End keys? (reset on PID change)
128 YTERM_MODE_NO_ALTSCR = 1u<<15, // is switching to alternate screen disabled?
129 YTERM_MODE_ORIGIN = 1u<<16, // vertical cursor position is relative to scroll region
131 YTERM_MODE_MOUSE = (YTERM_MODE_MOUSEBTN | YTERM_MODE_MOUSEMOTION),
133 // GFX0 means "charset 0 is in gfx mode"
134 // GFX1 means "charset 1 is in gfx mode"
135 // `charset` field contains the same constant, so it is
136 // easy to check for gfx mode with `term->mode & term->charset`
137 YTERM_MODE_GFX_MASK = (YTERM_MODE_GFX0 | YTERM_MODE_GFX1),
140 // ////////////////////////////////////////////////////////////////////////// //
141 // maxumum number of arguments in escape seqence for VT-100
142 #define YTERM_ESC_ARG_SIZ (16)
145 // ////////////////////////////////////////////////////////////////////////// //
146 // xmodmap -pm
147 enum {
148 Mod_Ctrl = ControlMask,
149 Mod_Shift = ShiftMask,
150 Mod_Alt = Mod1Mask,
151 Mod_NumLock = Mod2Mask,
152 Mod_AltGr = Mod3Mask,
153 Mod_Hyper = Mod4Mask,
156 // pressed/released button number
157 enum {
158 YTERM_MR_NONE,
159 // WARNING! numbers are important!
160 YTERM_MR_LEFT,
161 YTERM_MR_MIDDLE,
162 YTERM_MR_RIGHT,
165 // event type
166 enum {
167 YTERM_MR_UP,
168 YTERM_MR_DOWN,
169 YTERM_MR_MOTION,
172 // for `deadstate`
173 enum {
174 DS_ALIVE,
175 // waiting for a keypress
176 DS_WAIT_KEY,
177 // dead, remove from the list on the next main loop iteration
178 DS_DEAD,
181 // selection block type
182 enum {
183 SBLOCK_NONE = 0, // selection mode is inactive
184 SBLOCK_STREAM,
185 SBLOCK_LINE,
186 SBLOCK_RECT,
190 // ////////////////////////////////////////////////////////////////////////// //
191 typedef struct {
192 int fd; // history file fd (-1 if none yet)
193 int width; // history line width
194 int lcount; // number of history lines
195 } HistoryFile;
198 // ////////////////////////////////////////////////////////////////////////// //
199 typedef struct {
200 int x, y;
201 // "last column flag", used for delayed wrapping
202 yterm_bool lastColFlag;
203 // set on ant cursor repositioning ESC; reset in `term_putc()`
204 // used to reset "autowrap" flag on the line we're changing
205 yterm_bool justMoved;
206 } CursorPos;
208 typedef struct Message_t {
209 char *message;
210 uint64_t time; // either die time, our timeout for yet-to-show messages
211 struct Message_t *next;
212 } Message;
214 typedef struct {
215 CellBuffer cbuf;
216 CursorPos curpos;
217 CursorPos cursaved; // saved cursor position
218 CharCell currattr; // for attributes and flags
219 yterm_bool curhidden;
220 // scroll top and bottom lines (inclusive)
221 int scTop, scBot;
222 // for "ESC 7" and "ESC 8"
223 CharCell attrsaved;
224 uint32_t charsetsaved;
225 uint32_t modesaved; // charsets
226 } TermScreen;
228 #define TAB_MAX_TEXT_LEN (128)
230 // tab info
231 typedef struct {
232 int posx; // position in window; may be out of window
233 int width; // tab width, including a one-pixel right delimiter
234 int numwidth; // tab num text width
235 int textwidth; // tab text width
236 int numlen;
237 int textlen;
238 char textnum[8];
239 XChar2b text[TAB_MAX_TEXT_LEN];
240 } TabInfo;
242 typedef struct term_t {
243 TermScreen *wkscr; // check this to determine which buffer is active
244 // main and alternate screens
245 TermScreen main;
246 TermScreen alt;
247 yterm_bool active; // is this terminal active? inactive terminals are invisible
248 uint32_t mode; // terminal mode flags
249 uint32_t charset; // YTERM_MODE_GFX0 or YTERM_MODE_GFX1
250 // we will not issue `XCopyArea()` immediately, but accumulate the value instead
251 // otherwise it looks bad with deferred updates
252 // negative is up, positive is down, `0x7fffffff` is "locked"
253 int scroll_count;
254 // we also need to remember the scrolling area
255 int scroll_y0, scroll_y1;
256 struct {
257 int state; // escape processor state
258 int priv; // "private CSI sequence" flag (-1, 0, 1); OSC: seen accum digits
259 int cmd; // final CSI char (command); OSC command accumulator
260 int argv[YTERM_ESC_ARG_SIZ]; // -1 means "omited"
261 int argc; // 0 if we haven't seen any args yet
262 // for OSC, and for queries
263 char sbuf[256];
264 int sbufpos;
265 } escseq;
266 // child PID
267 struct {
268 pid_t pid;
269 int cmdfd; // terminal control fd for child
270 } child;
271 struct {
272 pid_t pgrp;
273 char last[256];
274 yterm_bool custom; // custom title?
275 uint64_t next_check_time; // next title check time
276 } title;
277 int deadstate; // see `DS_xxx`
278 // write buffer
279 struct {
280 char *buf;
281 uint32_t size;
282 uint32_t used;
283 } wrbuf;
284 // read DFA (for one UTF-8 char)
285 uint32_t rdcp;
286 struct {
287 int mode; // mouse report mode: 1000, 1005, 1006, 1015
288 int lastx, lasty; // last reported coords
289 int lastbtn; // last pressed button
290 } mreport;
291 // "selection mode" support
292 struct {
293 int cx, cy; // selection cursor position in screen
294 int sback; // top history line index, counting from 1; 1 is the *LAST* line
295 int x0, y0, x1, y1; // currently selected block; inclusive; from the bottom of cbuf and up
296 int blocktype; // `SBLOCK_xxx`
297 HistoryFile file;
298 yterm_bool enabled;
299 yterm_bool inprogress; // we're currently selecting
300 } history;
301 Message *osd_messages;
302 // last activation time, so we could return to the previous term
303 uint64_t last_acttime;
304 // tab info
305 TabInfo tab;
306 // local color mode, or -1
307 int colorMode;
308 // local mouse reports, or -1
309 int mouse_reports;
310 // local escesc, or -1
311 int escesc;
312 // linked list of terminals
313 struct term_t *prev;
314 struct term_t *next;
315 } Term;
318 // ////////////////////////////////////////////////////////////////////////// //
319 // handy macros ;-)
321 #define TERM_CBUF(tt_) (&((tt_)->wkscr->cbuf))
322 #define TERM_CPOS(tt_) (&((tt_)->wkscr->curpos))
323 #define TERM_CATTR(tt_) (&((tt_)->wkscr->currattr))
324 #define TERM_SCTOP(tt_) ((tt_)->wkscr->scTop)
325 #define TERM_SCBOT(tt_) ((tt_)->wkscr->scBot)
327 #define TERM_CURHIDDEN(tt_) ((tt_)->wkscr->curhidden)
328 #define TERM_CURVISIBLE(tt_) (!((tt_)->wkscr->curhidden))
330 #define TERM_IN_SELECTION(tt_) ((tt_)->history.blocktype != SBLOCK_NONE)
333 // ////////////////////////////////////////////////////////////////////////// //
334 static Display *x11_dpy = NULL;
335 static int x11_screen;
336 static Window x11_win = None;
337 static Window x11_embed = None;
338 static XFont x11_font;
339 static XFont x11_tabsfont;
340 static GC x11_gc;
341 static XIM x11_xim;
342 static XIC x11_xic;
343 static int koiLocale = 0;
344 //static Cursor mXTextCursor = None; // text cursor
345 static Cursor mXDefaultCursor = None; // 'default' cursor
346 //static Cursor mXBlankCursor = None;
347 static uint64_t lastBellTime = 0;
349 static Atom xaXEmbed;
350 static Atom xaVTSelection;
351 static Atom xaClipboard;
352 static Atom xaUTF8;
353 static Atom xaCSTRING;
354 static Atom xaINCR;
355 static Atom xaTargets;
356 static Atom xaNetWMName;
357 static Atom xaWMProtocols;
358 static Atom xaWMDeleteWindow;
359 static Atom xaWorkArea;
361 static uint32_t lastBG = 0xffffffffU, lastFG = 0xffffffffU;
362 // if high byte is not zero, don't show
363 static uint32_t curColorsFG[2] = { 0x005500, 0x005500 };
364 // the last is for unfocused cursor
365 static uint32_t curColorsBG[3] = { 0x00ff00, 0x00cc00, 0x009900 };
367 static int winHeight = 0, winWidth = 0;
368 static int charWidth = 0, charHeight = 0;
369 // tabs height can be bigger than requested due to WM window resizing
370 // it is calculated in configure notify handler
371 static int winTabsHeight = 0;
372 // calculated (desired) tabs height
373 static int winDesiredTabsHeight = 0;
374 // "real" height includes tabbar; tabbar is always at bottom
375 static int winRealHeight = 0;
377 static int curPhase = 0;
379 static int quitMessage = 0;
380 static int winVisible = 0;
381 static int winFocused = 0;
382 static int winMapped = 0;
383 static char x11_lasttitle[1024] = {0};
384 static int x11_haslocale = 0;
386 static Term *currterm = NULL;
387 static Term *termlist = NULL;
389 static char *xrm_app_name = NULL;
391 static MenuWindow *curr_menu = NULL;
393 // to render scrolled history, we will use the separate buffer,
394 // because main buffer holds current terminal contents
395 static CellBuffer hvbuf;
397 static int childsigfd;
398 static ExecData first_run_ed;
400 // render FPS
401 static uint64_t next_redraw_time = 0;
402 static yterm_bool need_update_tabs = 0;
404 // hotswap support
405 static char exe_path[4096];
407 static yterm_bool xrm_reloading = 0;
410 // ////////////////////////////////////////////////////////////////////////// //
411 enum {
412 TT_ANY,
413 TT_RXVT,
414 TT_XTERM,
417 static char *opt_term = NULL;
418 static char *opt_class = NULL;
419 static char *opt_title = NULL;
420 static char *opt_mono_font = NULL;
421 static char *opt_tabs_font = NULL;
422 static int opt_twidth = 80;
423 static int opt_theight = 24;
424 static yterm_bool opt_enable_tabs = 1;
425 // send ESC ESC on escape, and single ESC on alt-escape?
426 static yterm_bool opt_esc_twice = 1;
427 static yterm_bool opt_mouse_reports = 0;
428 static int opt_cur_blink_time = 700; // 0: don't blink
429 static int opt_term_type = TT_RXVT;
430 static int opt_tabs_visible = 6;
431 static yterm_bool opt_history_enabled = 1;
432 static int opt_fps = 40;
433 static yterm_bool opt_terminus_gfx = 1;
434 // wait for some time before setting terminal size
435 // this hack is to allow WM to setup yterm size
436 // sadly, there is no X11 event to tell us that
437 // WM is done configuring the window, hence this hack
438 // time in msecs, and affects only "-e" and hotswap
439 static int opt_winch_delay = 50;
440 // if 0, then we should init it with the above
441 static uint64_t winch_allowed_time = 0;
443 // xsel command lines for 3 clipboard types
444 static char *opt_paste_from[3] = {NULL, NULL, NULL};
445 static char *opt_copy_to[3] = {NULL, NULL, NULL};
447 static int opt_dump_fd = -1;
448 static yterm_bool opt_dump_fd_enabled = 1;
449 static yterm_bool opt_dump_esc_enabled = 0;
450 #ifdef USELESS_DEBUG_FEATURES
451 static yterm_bool opt_debug_slowdown = 0;
452 #endif
453 static yterm_bool opt_vttest = 0;
455 static uint32_t opt_osd_menu_shadow_color = 0x000000;
456 static yterm_bool opt_osd_menu_shadow_xofs = 6;
457 static yterm_bool opt_osd_menu_shadow_yofs = 6;
459 static yterm_bool opt_amber_tint = 1;
461 static yterm_bool opt_debug_perform_hotswap = 0;
462 static int opt_debug_hotswap_fd = -1;
463 #define IS_HOTSWAPPING() (opt_debug_hotswap_fd >= 0)
465 static char txrdp[256];
466 static char txexe[4096];
467 static char txpath[4096];
470 // ////////////////////////////////////////////////////////////////////////// //
471 static void term_write (Term *term, const char *str);
472 static void term_release_alt_buffer (Term *term);
473 static void one_term_write (Term *term);
474 static Term *term_create_new_term (void);
475 static void term_activate (Term *term);
476 static void xrm_load_options (void);
477 static void exec_hotswap (void);
478 static void xrm_seal_option_by_ptr (const void *ptr);
479 static yterm_bool xrm_is_option_sealed_by_ptr (const void *ptr);
480 static void do_paste (Term *term, int mode);
481 static void do_copy_to (Term *term, int mode);
482 static Region x11_render_osd (const char *msg, Region clipreg);
483 static Region x11_render_osd_menus (MenuWindow *menu);
485 YTERM_STATIC_INLINE void force_tab_redraw (void) { need_update_tabs = 1; }
487 YTERM_STATIC_INLINE void force_frame_redraw (yterm_bool dirtyterm) {
488 if (dirtyterm && currterm != NULL) cbuf_mark_all_dirty(TERM_CBUF(currterm));
489 next_redraw_time = 0;
493 // ////////////////////////////////////////////////////////////////////////// //
494 #include "osd_menu.inc.c"
497 //==========================================================================
499 // term_mouse_reports_enabled
501 // enabled and requested
503 //==========================================================================
504 YTERM_STATIC_INLINE yterm_bool term_mouse_reports_enabled (const Term *term) {
505 if (term == NULL || term->mouse_reports == 0 ||
506 (term->mouse_reports == -1 && opt_mouse_reports == 0))
508 return 0;
509 } else {
510 if ((term->mode & YTERM_MODE_MOUSE) != 0 && !TERM_IN_SELECTION(term) &&
511 term->deadstate == DS_ALIVE && term->child.cmdfd >= 0) {
512 return 1;
513 } else {
514 return 0;
520 //==========================================================================
522 // term_check_pgrp
524 // this will reset sone terminal mode flags on program change
526 //==========================================================================
527 static void term_check_pgrp (Term *term, pid_t pgrp) {
528 if (term != NULL && term->deadstate == DS_ALIVE) {
529 if (term->child.cmdfd < 0) {
530 term->mode &= ~(YTERM_MODE_VT220_KBD | YTERM_MODE_XTERM);
531 } else {
532 if (pgrp <= 0) pgrp = tcgetpgrp(term->child.cmdfd);
533 if (pgrp > 0) {
534 if (term->title.pgrp != pgrp) {
535 if (term->title.pgrp != 0) {
536 term->mode &= ~(YTERM_MODE_VT220_KBD | YTERM_MODE_XTERM);
537 term_release_alt_buffer(term); // it is safe to call this
539 term->title.pgrp = pgrp;
541 force_tab_redraw();
548 //==========================================================================
550 // get_exe_path
552 //==========================================================================
553 static void get_exe_path (void) {
554 exe_path[0] = 0;
555 pid_t pid = getpid();
556 if (pid <= 0) return;
557 // get program current dir
558 snprintf(txrdp, sizeof(txrdp), "/proc/%d/exe", (int)pid);
559 ssize_t rd = readlink(txrdp, exe_path, sizeof(exe_path) - 1);
560 if (rd < 1) {
561 exe_path[0] = 0;
562 } else {
563 exe_path[rd] = 0;
568 //==========================================================================
570 // term_check_title
572 //==========================================================================
573 static void term_check_title (Term *term) {
574 int fd;
575 char *exe;
576 char *path;
577 ssize_t rd;
579 if (term == NULL || term->deadstate != DS_ALIVE || term->child.cmdfd < 0) return;
580 if (!winMapped) return;
582 pid_t pgrp = tcgetpgrp(term->child.cmdfd);
583 term_check_pgrp(term, pgrp);
585 if (term->title.custom) return;
587 term->title.last[0] = 0;
589 if (pgrp < 1) goto error;
591 // get program title
592 snprintf(txrdp, sizeof(txrdp), "/proc/%d/cmdline", (int)pgrp);
593 fd = open(txrdp, O_RDONLY|O_CLOEXEC);
594 if (fd < 0) goto error;
595 rd = read(fd, txexe, sizeof(txexe) - 1);
596 close(fd);
597 if (rd < 0) goto error;
598 txexe[rd] = 0;
600 exe = strrchr(txexe, '/');
601 if (exe != NULL) exe += 1; else exe = txexe;
603 // put into title
604 snprintf(term->title.last, sizeof(term->title.last), "[%s]", exe);
606 // get program current dir
607 snprintf(txrdp, sizeof(txrdp), "/proc/%d/cwd", (int)pgrp);
608 rd = readlink(txrdp, txpath, sizeof(txpath) - 1);
609 if (rd < 0) return; // keep program name title
610 txpath[rd] = 0;
612 if (rd > 0) {
613 // append to the title
614 size_t tlen = strlen(term->title.last);
615 if (tlen + 8 <= sizeof(term->title.last)) {
616 // have room
617 size_t plen = (size_t)rd;
618 size_t nlen = tlen + plen + 1;
619 if (nlen < sizeof(term->title.last)) {
620 // ok
621 snprintf(term->title.last, sizeof(term->title.last), "[%s] %s", exe, txpath);
622 } else if (tlen + 8 < sizeof(term->title.last)) {
623 size_t xleft = sizeof(term->title.last) - 5 - tlen;
624 if (xleft < plen) {
625 // should always be true
626 path = txpath + (plen - xleft);
627 snprintf(term->title.last, sizeof(term->title.last), "[%s] ...%s", exe, path);
633 return;
635 error:
636 snprintf(term->title.last, sizeof(term->title.last), "%s", opt_title);
640 //==========================================================================
642 // term_lock_scroll
644 // locks scrolling
646 // scroll area will be marked dirty by the caller
648 //==========================================================================
649 YTERM_STATIC_INLINE void term_lock_scroll (Term *term) {
650 if (term != NULL && term->scroll_count != 0x7fffffff) {
651 // i don't know the area, so assume the whole scroll region
652 if (term->scroll_count == 0) {
653 term->scroll_y0 = term->wkscr->scTop;
654 term->scroll_y1 = term->wkscr->scBot;
656 term->scroll_count = 0x7fffffff;
657 #if 0
658 fprintf(stderr, "COPY-SCROLL DISABLED.\n");
659 #endif
664 //==========================================================================
666 // term_scroll_locked
668 //==========================================================================
669 YTERM_STATIC_INLINE yterm_bool term_scroll_locked (Term *term) {
670 return (term != NULL && term->scroll_count == 0x7fffffff);
674 // ////////////////////////////////////////////////////////////////////////// //
675 #include "history.inc.c"
676 #include "x11_init.inc.c"
677 #include "x11_render.inc.c"
678 #include "x11_utils.inc.c"
679 #include "term_lib.inc.c"
680 #include "child_run.inc.c"
681 #include "x11_keypress.inc.c"
682 #include "clipboard.inc.c"
683 #include "tabbar.inc.c"
685 #include "hotswap.inc.c"
687 #include "x11_xrm.inc.c"
688 #include "cli_args.inc.c"
691 // ////////////////////////////////////////////////////////////////////////// //
692 /* XEMBED messages */
693 #define XEMBED_FOCUS_IN (4)
694 #define XEMBED_FOCUS_OUT (5)
697 //==========================================================================
699 // x11_filter_event
701 //==========================================================================
702 YTERM_STATIC_INLINE yterm_bool x11_filter_event (XEvent *eventp) {
703 if (XFilterEvent(eventp, (Window)0)) {
704 return 1;
707 if (eventp->type == MappingNotify) {
708 if (eventp->xmapping.request == MappingKeyboard ||
709 eventp->xmapping.request == MappingModifier)
711 XRefreshKeyboardMapping(&eventp->xmapping);
713 return 1;
716 return 0;
720 //==========================================================================
722 // x11_motion_report
724 //==========================================================================
725 static void x11_motion_report (XMotionEvent *event) {
726 Term *term = currterm;
728 if (!term_mouse_reports_enabled(term)) return;
730 if ((event->state&(Button1Mask|Button2Mask|Button3Mask|Button4Mask|Button5Mask)) != 0 &&
731 /*(event->state&Mod_Shift) == 0*/1)
733 term_send_mouse(term, event->x / charWidth, event->y / charHeight,
734 YTERM_MR_MOTION, 0, event->state);
739 //==========================================================================
741 // x11_mouse_report
743 //==========================================================================
744 static void x11_mouse_report (XButtonEvent *event) {
745 //TODO: check tab clicking here
747 Term *term = currterm;
749 if (!term_mouse_reports_enabled(term)) return;
751 // 4 and 5 is a wheel
752 if (/*(event->state&Mod_Shift) == 0 &&*/ event->button >= 1 && event->button <= 5) {
753 term_send_mouse(term, event->x / charWidth, event->y / charHeight,
754 (event->type == ButtonPress ? YTERM_MR_DOWN : YTERM_MR_UP),
755 event->button, event->state);
760 //==========================================================================
762 // x11_event_process_other
764 //==========================================================================
765 static void x11_event_process (XEvent *event) {
766 Term *term = currterm;
768 switch (event->type) {
769 case ClientMessage:
770 if (event->xclient.message_type == xaWMProtocols) {
771 if ((Atom)event->xclient.data.l[0] == xaWMDeleteWindow) {
772 quitMessage = 1;
774 } else if (event->xclient.message_type == xaXEmbed && event->xclient.format == 32) {
775 // see xembed specs http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html
776 if (event->xclient.data.l[1] == XEMBED_FOCUS_IN) {
777 if (x11_xic) XSetICFocus(x11_xic);
778 if (!winFocused) {
779 winFocused = 1;
780 term_invalidate_cursor(term);
781 term_send_focus_event(term, 1);
782 x11_set_urgency(0);
784 } else if (event->xclient.data.l[1] == XEMBED_FOCUS_OUT) {
785 if (x11_xic) XUnsetICFocus(x11_xic);
786 if (winFocused) {
787 winFocused = 0;
788 term_invalidate_cursor(term);
789 term_send_focus_event(term, 0);
793 break;
794 case KeyPress:
795 x11_parse_keyevent(&event->xkey);
796 break;
797 case KeyRelease:
798 break;
799 case Expose:
800 #ifdef DUMP_X11_EVENTS
801 fprintf(stderr, "Expose: x=%d; y=%d; w=%d; h=%d\n",
802 event->xexpose.x, event->xexpose.y,
803 event->xexpose.width, event->xexpose.height);
804 #endif
805 /*if (event->xexpose.count == 0)*/ menu_mark_all_dirty(curr_menu);
806 if (term != NULL) {
807 // if scrolled, and trying to expose, render everything
808 if (term->scroll_count != 0) {
809 // do nothing if this is non-last event
810 if (event->xexpose.count != 0) return;
811 term->scroll_count = 0;
812 // fix render coords
813 int x0 = event->xexpose.x;
814 int y0 = event->xexpose.y;
815 int x1 = x0 + event->xexpose.width - 1;
816 int y1 = y0 + event->xexpose.height - 1;
817 x0 = 0; y0 = 0;
818 x1 = max2(x1, winWidth * charWidth - 1);
819 y1 = max2(y1, winHeight * charHeight - 1);
820 if (x1 <= x0 || y1 <= y0) {
821 cbuf_mark_all_dirty(TERM_CBUF(term));
822 return;
824 // repaint everything
825 event->xexpose.x = x0; event->xexpose.y = y0;
826 event->xexpose.width = x1 - x0 + 1;
827 event->xexpose.height = y1 - y0 + 1;
829 CALL_RENDER(
830 renderArea(rcbuf, rcpos,
831 event->xexpose.x, event->xexpose.y,
832 event->xexpose.x + event->xexpose.width,
833 event->xexpose.y + event->xexpose.height);
835 if (event->xexpose.y + event->xexpose.height > winHeight) {
836 repaint_tabs(event->xexpose.x, event->xexpose.width);
839 break;
840 case GraphicsExpose: // this is from area copying
841 #ifdef DUMP_X11_EVENTS
842 fprintf(stderr, "GraphicsExpose: x=%d; y=%d; w=%d; h=%d\n",
843 event->xgraphicsexpose.x, event->xgraphicsexpose.y,
844 event->xgraphicsexpose.width, event->xgraphicsexpose.height);
845 #endif
846 /*if (event->xgraphicsexpose.count == 0)*/ menu_mark_all_dirty(curr_menu);
847 if (term != NULL) {
848 #if 1
849 CALL_RENDER(
850 renderArea(rcbuf, rcpos,
851 event->xgraphicsexpose.x, event->xgraphicsexpose.y,
852 event->xgraphicsexpose.x + event->xgraphicsexpose.width,
853 event->xgraphicsexpose.y + event->xgraphicsexpose.height);
855 if (event->xgraphicsexpose.y + event->xgraphicsexpose.height > winHeight) {
856 repaint_tabs(event->xgraphicsexpose.x, event->xgraphicsexpose.width);
858 #else
859 SET_FG_COLOR(0xff0000);
860 XFillRectangle(x11_dpy, (Drawable)x11_win, x11_gc,
861 event->xgraphicsexpose.x, event->xgraphicsexpose.y,
862 event->xgraphicsexpose.width, event->xgraphicsexpose.height);
863 #endif
865 break;
866 case VisibilityNotify:
867 #ifdef DUMP_X11_EVENTS
868 fprintf(stderr, "VisibilityNotify: %d\n", event->xvisibility.state);
869 #endif
870 if (event->xvisibility.state == VisibilityFullyObscured) {
871 if (winVisible) {
872 winVisible = 0;
873 if (term != NULL) {
874 term_lock_scroll(term);
875 // no need to mark anything dirty here, because nothing is visible
876 // if anything will become visible, X server will send proper expose events
877 cbuf_mark_all_clean(TERM_CBUF(term));
880 } else if (!winVisible) {
881 winVisible = 1;
882 if (winFocused) x11_set_urgency(0);
883 // no need to have any dirty cells here, X server will send proper expose events
884 // it is safe, because the window was fully invisible
885 if (term != NULL) {
886 term->scroll_count = 0; // we can accumulate scrolls again
887 cbuf_mark_all_clean(TERM_CBUF(term));
890 break;
891 case FocusIn:
892 #ifdef DUMP_X11_EVENTS
893 fprintf(stderr, "FocusIn!\n");
894 #endif
895 if (x11_xic) XSetICFocus(x11_xic);
896 if (!winFocused) {
897 winFocused = 1;
898 term_invalidate_cursor(term);
899 term_send_focus_event(term, 1);
900 x11_set_urgency(0);
902 break;
903 case FocusOut:
904 #ifdef DUMP_X11_EVENTS
905 fprintf(stderr, "FocusOut!\n");
906 #endif
907 //!doneSelection();
908 if (x11_xic) XUnsetICFocus(x11_xic);
909 if (winFocused) {
910 winFocused = 0;
911 term_invalidate_cursor(term);
912 term_send_focus_event(term, 0);
914 break;
915 case MotionNotify:
916 if (term != NULL) x11_motion_report(&event->xmotion);
917 break;
918 case ButtonPress:
919 case ButtonRelease:
920 if (term != NULL) x11_mouse_report(&event->xbutton);
921 break;
922 case SelectionNotify:
923 // we got our requested selection, and should paste it
924 //cwin_selection_came(cwin, &event->xselection);
925 break;
926 case SelectionRequest:
927 //!prepareCopy(&event->xselectionrequest);
928 break;
929 case SelectionClear:
930 //!clearSelection(&event->xselectionclear);
931 break;
932 case PropertyNotify:
933 if (event->xproperty.state == PropertyNewValue) {
935 char *pn = XGetAtomName(x11_dpy, event->xproperty.atom);
936 fprintf(stderr, "PROP: new value for property '%s' (time=%u)\n", pn, (unsigned)event->xproperty.time);
937 XFree(pn);
939 //if (event->xproperty.atom == xaVTSelection) cwin_more_selection(cwin);
940 } else if (event->xproperty.state == PropertyDelete) {
942 char *pn = XGetAtomName(x11_dpy, event->xproperty.atom);
943 fprintf(stderr, "PROP: deleted property '%s' (time=%u)\n", pn, (unsigned)event->xproperty.time);
944 XFree(pn);
946 } else {
947 fprintf(stderr, "PROP: unknown event subtype!\n");
949 break;
950 case ConfigureNotify:
951 #if defined(DUMP_X11_EVENTS) || 0
952 fprintf(stderr, "CONFIGURE! w=%d; h=%d; sent=%d\n",
953 event->xconfigure.width / charWidth,
954 event->xconfigure.height / charHeight,
955 event->xconfigure.send_event);
956 #endif
957 winWidth = max2(charWidth, event->xconfigure.width);
958 // calculate new window height
959 winRealHeight = max2(charWidth, event->xconfigure.height);
960 if (opt_enable_tabs) {
961 winHeight = (winRealHeight - winDesiredTabsHeight) / charHeight * charHeight;
962 winHeight = max2(charHeight, winHeight);
963 // calculate real tabs height
964 winTabsHeight = max2(0, winRealHeight - winHeight);
965 // if our tabs are smaller than desired, try to allocate more room for them
966 if (winTabsHeight < winDesiredTabsHeight &&
967 winRealHeight - winDesiredTabsHeight > charHeight * 2)
969 // we have enough room
970 winHeight = winRealHeight - winDesiredTabsHeight;
971 winHeight = (winRealHeight - winDesiredTabsHeight) / charHeight * charHeight;
972 yterm_assert(winHeight >= charHeight);
974 } else {
975 winTabsHeight = 0;
976 winHeight = max2(1, winRealHeight / charHeight * charHeight);
978 force_tab_redraw();
979 #if 0
980 fprintf(stderr, "NEW WSIZE: %dx%d (real=%d; tabs=%d); evt: %dx%d\n",
981 winWidth, winHeight, winRealHeight, winDesiredTabsHeight,
982 event->xconfigure.width, event->xconfigure.height);
983 #endif
984 // first run commandline
985 if (first_run_ed.argc != 0 && term != NULL) {
986 const yterm_bool rres = term_exec(term, &first_run_ed);
987 #if 0
988 fprintf(stderr, "FIRST-RUN: <%s> (fd=%d; pid=%u)\n", first_run_ed.argv[0],
989 term->child.cmdfd, (unsigned)term->child.pid);
990 #endif
991 execsh_free(&first_run_ed);
992 if (rres == 0) {
993 fprintf(stderr, "FATAL: cannot run initial program!\n");
994 quitMessage = 666;
997 break;
998 case MapNotify:
999 #ifdef DUMP_X11_EVENTS
1000 fprintf(stderr, "MapNotify!\n");
1001 #endif
1002 winMapped = 1;
1003 if (winVisible) {
1004 fprintf(stderr, "WARNING: WTF?! window is just mapped, yet marked as visible!\n");
1005 winVisible = 0;
1007 // recheck all terminal titles
1008 for (Term *tt = termlist; tt != NULL; tt = tt->next) tt->title.next_check_time = 0;
1009 if (term != NULL) {
1010 cbuf_mark_all_clean(TERM_CBUF(term));
1012 break;
1013 case UnmapNotify:
1014 winMapped = 0;
1015 // just in case
1016 winVisible = 0;
1017 if (term != NULL) {
1018 term_lock_scroll(term);
1019 cbuf_mark_all_clean(TERM_CBUF(term));
1021 break;
1022 case DestroyNotify:
1023 winVisible = 0;
1024 winFocused = 0;
1025 winMapped = 0;
1026 x11_win = None;
1027 break;
1028 default:
1029 break;
1034 //==========================================================================
1036 // one_term_write
1038 // usually we don't have a lot of writes, so don't bother looping.
1039 // writing a lot of data can happen on clipboard paste, but in this
1040 // case we are blocked anyway.
1042 //==========================================================================
1043 static void one_term_write (Term *term) {
1044 yterm_assert(term != NULL);
1045 if (term->wrbuf.used != 0 && term_can_write(term)) {
1046 #ifdef DUMP_WRITE_AMOUNT
1047 fprintf(stderr, "NEED TO WRITE: %u\n", term->wrbuf.used);
1048 #endif
1049 ssize_t wr = write(term->child.cmdfd, term->wrbuf.buf, term->wrbuf.used);
1050 #ifdef DUMP_WRITE_AMOUNT
1051 fprintf(stderr, "WROTE: %d / %u\n", (int)wr, term->wrbuf.used);
1052 #endif
1053 if (wr < 0) {
1054 // `EAGAIN` should not happen, but better be safe than sorry
1055 if (errno != EINTR && errno != EAGAIN) {
1056 fprintf(stderr, "OOPS! cannot write data to terminal!\n");
1057 term_close_fd(term);
1059 } else if (wr == 0) {
1060 // wutafuck?! this should not happen at all
1061 fprintf(stderr, "OOPS! terminal seems to close the connection (write)!\n");
1062 term_close_fd(term);
1063 } else {
1064 yterm_assert((uint32_t)wr <= term->wrbuf.used);
1065 const uint32_t dleft = term->wrbuf.used - (uint32_t)wr;
1066 if (dleft != 0) {
1067 // move data
1068 memmove(term->wrbuf.buf, term->wrbuf.buf + (size_t)wr, dleft);
1070 term->wrbuf.used -= (uint32_t)wr;
1071 //if (term->wrbuf.used != 0) again = 1;
1075 // shrink buffer
1076 if (term->wrbuf.size > WRITE_BUF_SIZE) {
1077 const uint32_t nsz = max2(WRITE_BUF_SIZE,
1078 (term->wrbuf.size > 4096 ? 4096 : term->wrbuf.size / 2));
1079 if (term->wrbuf.used <= nsz) {
1080 #ifdef DUMP_WRITE_SHRINK
1081 fprintf(stderr, "SHRINKING: %u -> %u (used: %u)\n",
1082 term->wrbuf.size, nsz, term->wrbuf.used);
1083 #endif
1084 char *nbx = realloc(term->wrbuf.buf, nsz);
1085 if (nbx != NULL) {
1086 term->wrbuf.buf = nbx;
1087 term->wrbuf.size = nsz;
1094 //==========================================================================
1096 // all_term_write
1098 //==========================================================================
1099 static void all_term_write (void) {
1100 for (Term *term = termlist; term != NULL; term = term->next) {
1101 one_term_write(term);
1106 //==========================================================================
1108 // one_term_read
1110 // terminal should be valid, and checked for readability
1112 //==========================================================================
1113 static void one_term_read (Term *term) {
1114 if (term->child.cmdfd >= 0 && !TERM_IN_SELECTION(term)) {
1115 char rdbuf[4096];
1116 ssize_t rd = read(term->child.cmdfd, rdbuf, sizeof(rdbuf));
1117 #ifdef DUMP_READ_AMOUNT
1118 fprintf(stderr, "READ(%p): %d\n", term, (int)rd);
1119 #endif
1120 if (rd < 0) {
1121 // `EAGAIN` should not happen, but better be safe than sorry
1122 if (errno != EINTR && errno != EAGAIN) {
1123 if (errno != EIO) {
1124 fprintf(stderr, "OOPS! cannot read data from terminal! (errno=%d: %s)\n",
1125 errno, strerror(errno));
1126 term_close_fd(term);
1129 } else if (rd == 0) {
1130 // wutafuck?! this should not happen at all
1131 fprintf(stderr, "OOPS! terminal seems to close the connection (read)!\n");
1132 term_close_fd(term);
1133 } else if (koiLocale && (term->mode & YTERM_MODE_FORCED_UTF) == 0) {
1134 // read KOI
1135 ssize_t pos = 0;
1136 while (pos < rd) {
1137 term->rdcp = yterm_koi2uni(((const unsigned char *)rdbuf)[pos]); pos += 1;
1138 term_process_char(term, term->rdcp);
1140 } else {
1141 // read UTF
1142 ssize_t pos = 0;
1143 while (pos < rd) {
1144 term->rdcp = yterm_utf8d_consume_ex(term->rdcp, rdbuf[pos]); pos += 1;
1145 if (yterm_utf8_valid_cp(term->rdcp)) {
1146 term_process_char(term, term->rdcp);
1147 } else if (yterm_utf8d_fuckedup(term->rdcp)) {
1148 term_putc(term, YTERM_UTF8_REPLACEMENT_CP);
1156 //==========================================================================
1158 // all_term_read
1160 //==========================================================================
1161 static void all_term_read (uint64_t rdett) {
1162 #ifdef DUMP_READ_AMOUNT
1163 int was_more = 0;
1164 #endif
1165 int has_more;
1166 uint64_t ctt;
1167 if (rdett != 0) rdett -= 1;
1168 do {
1169 has_more = 0;
1170 Term *term = termlist;
1171 while (term != NULL) {
1172 if (!TERM_IN_SELECTION(term) && term_can_read(term)) {
1173 one_term_read(term);
1174 if (!has_more && term->child.cmdfd >= 0) {
1175 has_more = term_can_read(term);
1176 #ifdef DUMP_READ_AMOUNT
1177 if (has_more) {
1178 was_more = 1;
1179 fprintf(stderr, "...has more!\n");
1181 #endif
1184 term = term->next;
1186 ctt = (has_more ? yterm_get_msecs() : rdett + 1);
1187 #ifdef DUMP_RW_TIMEOUTS
1188 fprintf(stderr, "RDETT: %llu; ctt: %llu\n", rdett, ctt);
1189 #endif
1190 } while (has_more && ctt <= rdett);
1191 #ifdef DUMP_READ_AMOUNT
1192 if (was_more) fprintf(stderr, ":::reading done!\n");
1193 #endif
1197 //==========================================================================
1199 // remove_menus_for_term
1201 //==========================================================================
1202 static void remove_menus_for_term (Term *term) {
1203 MenuWindow *menu = curr_menu;
1204 MenuWindow *next = NULL;
1205 while (menu != NULL) {
1206 if (menu->term == term) {
1207 // remove this menu
1208 // mark dirty first, because we may need to redraw tabbar
1209 menu_mark_all_dirty(curr_menu);
1210 if (next != NULL) {
1211 next->prev = menu->prev;
1212 menu_free_window(menu);
1213 menu = next->prev;
1214 } else {
1215 curr_menu = menu->prev;
1216 menu_free_window(menu);
1217 menu = curr_menu;
1219 } else {
1220 next = menu;
1221 menu = menu->prev;
1227 //==========================================================================
1229 // remove_dead_terminals
1231 //==========================================================================
1232 static void remove_dead_terminals (void) {
1233 Term *term = termlist;
1234 while (term != NULL) {
1235 Term *cc = term; term = cc->next;
1236 if (cc->deadstate == DS_DEAD) {
1237 remove_menus_for_term(cc);
1238 // other checks
1239 if (cc == currterm) currterm = NULL; // will be fixed later
1240 // remove from the list
1241 if (cc->prev != NULL) cc->prev->next = cc->next; else termlist = cc->next;
1242 if (cc->next != NULL) cc->next->prev = cc->prev;
1243 term_cleanup(cc);
1244 free(cc);
1245 // tab numbers changed, so we need to update tabs
1246 force_frame_redraw(1);
1247 force_tab_redraw();
1253 //==========================================================================
1255 // fix_term_size
1257 //==========================================================================
1258 static void fix_term_size (Term *term) {
1259 if (term != NULL) {
1260 if (winch_allowed_time == 0) {
1261 winch_allowed_time = yterm_get_msecs() + max2(0, opt_winch_delay);
1263 const int neww = clamp(winWidth / charWidth, 1, MaxBufferWidth);
1264 const int newh = clamp(winHeight / charHeight, 1, MaxBufferHeight);
1265 if (neww != TERM_CBUF(term)->width || newh != TERM_CBUF(term)->height) {
1266 if (opt_winch_delay < 1 || yterm_get_msecs() >= winch_allowed_time) {
1267 #if 0
1268 fprintf(stderr, "WINCH fix.\n");
1269 #endif
1270 if (!opt_vttest && term_fix_size(term)) {
1271 // redraw immediately
1272 force_frame_redraw(1);
1274 } else {
1275 #if 0
1276 fprintf(stderr, "WINCH delay...\n");
1277 #endif
1284 //==========================================================================
1286 // term_activate
1288 //==========================================================================
1289 static void term_activate (Term *term) {
1290 if (term != NULL && term->deadstate != DS_DEAD && currterm != term) {
1291 if (currterm != NULL) {
1292 yterm_assert(currterm->active != 0);
1293 if (winFocused) {
1294 term_invalidate_cursor(currterm);
1295 term_send_focus_event(currterm, 0);
1297 //currterm->history.blocktype = SBLOCK_NONE;
1298 currterm->active = 0;
1301 currterm = term;
1302 term->active = 1;
1303 force_frame_redraw(1);
1304 term->title.next_check_time = 0;
1305 if (winFocused) term_send_focus_event(term, 1);
1306 force_tab_redraw();
1308 if (TERM_IN_SELECTION(term)) {
1309 cbuf_free(&hvbuf);
1310 cbuf_new(&hvbuf, TERM_CBUF(term)->width, TERM_CBUF(term)->height);
1311 memcpy(hvbuf.buf, TERM_CBUF(term)->buf,
1312 (unsigned)(TERM_CBUF(term)->width * TERM_CBUF(term)->height) * sizeof(CharCell));
1313 hvbuf.dirtyCount = TERM_CBUF(term)->dirtyCount;
1314 term_draw_selection(term);
1317 fix_term_size(term);
1322 //==========================================================================
1324 // find_active_terminal
1326 // returns non-zero if we need to refresh terminal
1328 //==========================================================================
1329 static void find_active_terminal (void) {
1330 // if we have no active terminal, find a new one
1331 Term *term = currterm;
1333 if (term == NULL) {
1334 Term *term = termlist;
1335 if (term == NULL) return; // no more
1336 while (term != NULL) {
1337 // select most recently used tab
1338 if (term->deadstate != DS_DEAD &&
1339 (currterm == NULL || term->last_acttime > currterm->last_acttime))
1341 currterm = term;
1343 term = term->next;
1346 // and properly activate it
1347 term = currterm; currterm = NULL;
1348 term_activate(term);
1349 yterm_assert(currterm != NULL);
1350 yterm_assert(currterm == term);
1351 } else {
1352 fix_term_size(term);
1357 //==========================================================================
1359 // check_draw_frame
1361 //==========================================================================
1362 static void check_draw_frame (void) {
1363 Term *term = currterm;
1364 if (term == NULL) return;
1366 const uint64_t ctt = yterm_get_msecs();
1367 term->last_acttime = ctt;
1369 // check terminal titles
1370 if (winMapped) {
1371 Term *tt = termlist;
1372 while (tt != NULL) {
1373 if (tt->deadstate == DS_ALIVE && tt->child.cmdfd >= 0) {
1374 if (ctt >= tt->title.next_check_time) {
1375 tt->title.next_check_time = ctt + 600 + rand()%200;
1376 // check if process changed
1377 if (tt->title.custom) {
1378 pid_t pgrp = tcgetpgrp(tt->child.cmdfd);
1379 if (pgrp > 0 && pgrp != tt->title.pgrp) tt->title.custom = 0;
1381 if (!tt->title.custom) {
1382 term_check_title(tt);
1383 if (tt->active) x11_change_title(tt->title.last);
1384 force_tab_redraw();
1388 tt = tt->next;
1392 // filter out OSDs
1393 Message *osd = term->osd_messages;
1394 if (osd != NULL) {
1395 while (osd != NULL && osd->time <= ctt) {
1396 // invalidate top line (that's where OSDs are)
1397 cbuf_mark_line_dirty(TERM_CBUF(term), 0);
1398 free(osd->message);
1399 osd = osd->next;
1400 free(term->osd_messages);
1401 term->osd_messages = osd;
1402 if (osd != NULL) osd->time += ctt;
1406 // render screen
1407 if (next_redraw_time <= ctt) {
1408 if (winVisible) {
1409 if (term_scroll_locked(term)) {
1410 // everything we need to draw is properly marked as dirty, do nothing
1411 } else if (term->scroll_count < 0) {
1412 // scroll up
1413 const int lines = -term->scroll_count;
1414 if (lines >= term->scroll_y1 - term->scroll_y0) {
1415 // fully scrolled away, full repaint
1416 cbuf_mark_region_dirty(TERM_CBUF(term),
1417 0, term->scroll_y0,
1418 TERM_CBUF(term)->width,
1419 term->scroll_y1);
1420 } else {
1421 #if 0
1422 fprintf(stderr, "SCROLL-ACC-UP: y0=%d; y1=%d; lines=%d\n",
1423 term->scroll_y0, term->scroll_y1, lines);
1424 #endif
1425 const int wwdt = TERM_CBUF(term)->width * charWidth;
1426 const int winy = term->scroll_y0 * charHeight;
1427 const int winh = (term->scroll_y1 - term->scroll_y0 + 1 - lines) * charHeight;
1428 XCopyArea(x11_dpy, (Drawable)x11_win, (Drawable)x11_win, x11_gc,
1429 0, winy + lines * charHeight, wwdt, winh, 0, winy);
1430 // should not happen, but...
1431 menu_mark_all_dirty(curr_menu);
1433 } else if (term->scroll_count > 0) {
1434 // scroll down
1435 const int lines = term->scroll_count;
1436 if (lines >= term->scroll_y1 - term->scroll_y0) {
1437 // fully scrolled away, full repaint
1438 cbuf_mark_region_dirty(TERM_CBUF(term),
1439 0, term->scroll_y0,
1440 TERM_CBUF(term)->width,
1441 term->scroll_y1);
1442 } else {
1443 #if 0
1444 fprintf(stderr, "SCROLL-ACC-DOWN: y0=%d; y1=%d; lines=%d\n",
1445 term->scroll_y0, term->scroll_y1, lines);
1446 #endif
1447 const int wwdt = TERM_CBUF(term)->width * charWidth;
1448 const int winy = term->scroll_y0 * charHeight;
1449 const int winh = (term->scroll_y1 - term->scroll_y0 + 1 - lines) * charHeight;
1450 XCopyArea(x11_dpy, (Drawable)x11_win, (Drawable)x11_win, x11_gc,
1451 0, winy, wwdt, winh, 0, winy + lines * charHeight);
1454 term->scroll_count = 0;
1455 // this can be optimised
1456 if (TERM_CBUF(term)->dirtyCount != 0) menu_mark_dirty_after_cell_redraw();
1457 CALL_RENDER(
1458 renderDirty(rcbuf, rcpos);
1461 const int fps = clamp(opt_fps, 1, 120);
1462 next_redraw_time = ctt + 1000/fps;
1465 // updater will repaint tabs if necessary
1466 if (need_update_tabs) update_all_tabs();
1470 //==========================================================================
1472 // blink_cursor
1474 //==========================================================================
1475 static void blink_cursor (void) {
1476 Term *term = currterm;
1478 if (TERM_CURVISIBLE(term) && winFocused && winVisible) {
1479 int newPhase;
1480 if (opt_cur_blink_time > 0) {
1481 newPhase = ((int)(yterm_get_msecs() % (unsigned)(opt_cur_blink_time * 2)) >= opt_cur_blink_time);
1482 } else {
1483 newPhase = 0;
1485 if (newPhase != curPhase) {
1486 #if 0
1487 fprintf(stderr, "***CURBLINK!***\n");
1488 #endif
1489 curPhase = newPhase;
1490 if (TERM_IN_SELECTION(term)) {
1491 if (term->history.inprogress) term_draw_selection(term);
1492 cbuf_mark_dirty(&hvbuf, term->history.cx, term->history.cy);
1493 } else {
1494 term_invalidate_cursor(term);
1496 force_frame_redraw(0); // redraw everything, why not
1502 //==========================================================================
1504 // has_term_to_write
1506 //==========================================================================
1507 static yterm_bool has_term_to_write (void) {
1508 yterm_bool res = 0;
1509 Term *term = termlist;
1510 while (res == 0 && term != NULL) {
1511 res = (term->wrbuf.used != 0);
1512 term = term->next;
1514 return res;
1518 //==========================================================================
1520 // wait_for_events
1522 //==========================================================================
1523 static void wait_for_events (int xfd) {
1524 Term *term = currterm;
1526 XFlush(x11_dpy);
1527 while (!XPending(x11_dpy) && !has_term_to_write()) {
1528 int tout;
1529 if (!winVisible) {
1530 // invisible window, wait infinitely (if we have nothing to write)
1531 tout = -1;
1532 } else {
1533 const uint64_t ctt = yterm_get_msecs();
1534 if (next_redraw_time <= ctt) {
1535 // poll, for some reason
1536 tout = 0;
1537 } else if (!HAS_DIRTY(term)) {
1538 // no dirty cells, wait until cursor blink
1539 if (winFocused && TERM_CURVISIBLE(term) && opt_cur_blink_time > 0) {
1540 tout = opt_cur_blink_time - (int)(ctt % (unsigned)opt_cur_blink_time);
1541 } else {
1542 // cursor is invisible, wait infinitely
1543 tout = -1;
1545 } else {
1546 // has dirty cells, wait until a new frame
1547 tout = (int)(next_redraw_time - ctt);
1550 // debug slowdown is handled at a different place
1551 #ifdef USELESS_DEBUG_FEATURES
1552 if (opt_debug_slowdown) tout = 0;
1553 #endif
1554 #ifdef DUMP_RW_TIMEOUTS
1555 fprintf(stderr, "TOUT: %d\n", tout);
1556 #endif
1557 fd_set rfd;
1558 fd_set wfd;
1559 struct timeval timeout;
1560 if (tout > 0) {
1561 timeout.tv_sec = tout / 1000;
1562 timeout.tv_usec = tout * 1000;
1563 } else {
1564 memset(&timeout, 0, sizeof(timeout));
1566 FD_ZERO(&rfd);
1567 FD_ZERO(&wfd);
1568 // add X11 socket
1569 FD_SET(xfd, &rfd);
1570 int maxfd = xfd;
1571 // add signal fd
1572 FD_SET(childsigfd, &rfd);
1573 maxfd = max2(maxfd, childsigfd);
1574 // add terminal reading and writing fds
1575 Term *term = termlist;
1576 while (term != NULL) {
1577 if (term->deadstate == DS_ALIVE && term->child.cmdfd >= 0) {
1578 FD_SET(term->child.cmdfd, &rfd);
1579 maxfd = max2(maxfd, term->child.cmdfd);
1580 // if we need to write something, mark this too
1581 if (term->wrbuf.used != 0) {
1582 FD_SET(term->child.cmdfd, &wfd);
1585 term = term->next;
1587 if (select(maxfd + 1, &rfd, &wfd, NULL, (tout >= 0 ? &timeout : NULL)) < 0) {
1588 if (errno == EINTR) continue;
1589 fprintf(stderr, "FATAL: select failed: %s\n", strerror(errno));
1590 exit(1);
1592 break;
1597 //==========================================================================
1599 // term_create_new_term
1601 //==========================================================================
1602 static Term *term_create_new_term (void) {
1603 Term *term = calloc(1, sizeof(Term));
1604 if (term != NULL) {
1605 term_init(term, max2(1, winWidth / charWidth),
1606 max2(1, winHeight / charHeight));
1607 Term *last = termlist;
1608 if (last != NULL) {
1609 while (last->next != NULL) last = last->next;
1610 term->prev = last;
1611 last->next = term;
1612 } else {
1613 termlist = term;
1616 return term;
1620 //==========================================================================
1622 // x11_main_loop
1624 // event processing loop
1626 //==========================================================================
1627 static void x11_main_loop (void) {
1628 yterm_bool quit = 0;
1629 XEvent x11eventrec;
1630 Term *term;
1632 const int xfd = XConnectionNumber(x11_dpy);
1633 yterm_assert(xfd >= 0);
1635 while (!quit && x11_win != None && !opt_debug_perform_hotswap) {
1636 remove_dead_terminals();
1638 find_active_terminal();
1639 if (currterm == NULL) break; // no more terminals
1641 term = currterm;
1643 // fix mouse events
1644 // it is safe to call `x11_enable_mouse()` repeatedly,
1645 // because it remembers the last state
1646 if (term_mouse_reports_enabled(term)) {
1647 x11_enable_mouse((term->mode & YTERM_MODE_MOUSEBTN),
1648 (term->mode & YTERM_MODE_MOUSEMOTION));
1649 } else {
1650 x11_enable_mouse(0, 0);
1653 // draw new frame
1654 check_draw_frame();
1656 #ifdef DUMP_RW_TIMEOUTS
1657 fprintf(stderr, "NTT: %llu; ctt: %llu\n", next_redraw_time, yterm_get_msecs());
1658 #endif
1660 // write data to terminals while we can
1661 // there should not be a lot of data, so write everything
1662 all_term_write();
1663 wait_for_events(xfd);
1664 collect_dead_children(childsigfd);
1665 all_term_read(next_redraw_time);
1667 while (XPending(x11_dpy)) {
1668 // process X11 events
1669 XNextEvent(x11_dpy, &x11eventrec);
1670 if (!x11_filter_event(&x11eventrec)) {
1671 if (x11eventrec.xany.window == x11_win) {
1672 x11_event_process(&x11eventrec);
1677 blink_cursor();
1679 if (opt_debug_perform_hotswap) {
1680 opt_debug_perform_hotswap = 0;
1681 exec_hotswap();
1682 open_menu(menu_new_message_box("ERROR!", "Hotswapping failed!"));
1685 if (quitMessage) {
1686 if (quitMessage == 666) quit = 1;
1687 else { quitMessage = 0; show_quit_prompt(); }
1691 // shutdown all terminals
1692 while (termlist != NULL) {
1693 term_kill_child(termlist);
1694 termlist = termlist->next;
1697 if (x11_xic) XDestroyIC(x11_xic);
1698 if (x11_xim) XCloseIM(x11_xim);
1699 XFreeGC(x11_dpy, x11_gc);
1700 if (x11_font.fst) XFreeFont(x11_dpy, x11_font.fst);
1701 x11_font.fst = NULL;
1703 if (x11_win != None) {
1704 if (winMapped) XUnmapWindow(x11_dpy, x11_win);
1705 XDestroyWindow(x11_dpy, x11_win);
1706 x11_win = None;
1707 XSync(x11_dpy, 0);
1712 //==========================================================================
1714 // summonChildSaviour
1716 //==========================================================================
1717 static int summonChildSaviour (void) {
1718 sigset_t mask;
1719 sigemptyset(&mask);
1720 sigaddset(&mask, SIGCHLD);
1721 sigaddset(&mask, SIGHUP); // reload config
1722 sigprocmask(SIG_BLOCK, &mask, NULL); // we block the signal
1723 //pthread_sigmask(SIG_BLOCK, &mask, NULL); // we block the signal
1724 return signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
1728 //==========================================================================
1730 // main
1732 //==========================================================================
1733 int main (int argc, char **argv) {
1734 if (argc > 1 && strcmp(argv[1], "xsel") == 0) {
1735 for (int f = 1; f < argc - 1; f += 1) argv[f] = argv[f + 1];
1736 argc -= 1;
1737 argv[argc] = NULL;
1738 return xsel_main(argc, argv);
1741 memset(&first_run_ed, 0, sizeof(first_run_ed));
1742 get_exe_path();
1743 xrm_app_name = strdup("k8-yterm");
1745 //opt_term = strdup("xterm");
1746 opt_term = strdup("rxvt");
1747 opt_mono_font = strdup(DEFAULT_MONO_FONT);
1748 opt_tabs_font = strdup(DEFAULT_TABS_FONT);
1749 opt_class = strdup("k8_yterm_x11");
1750 opt_title = strdup("YTERM");
1752 opt_paste_from[0] = strdup("$SELF xsel -p -o -t 16666 --trim");
1753 opt_paste_from[1] = strdup("$SELF xsel -s -o -t 16666 --trim");
1754 opt_paste_from[2] = strdup("$SELF xsel -b -o -t 16666 --trim");
1756 opt_copy_to[0] = strdup("$SELF xsel -p -i -t 0 --trim");
1757 opt_copy_to[1] = strdup("$SELF xsel -s -i -t 0 --trim");
1758 opt_copy_to[2] = strdup("$SELF xsel -b -i -t 0 --trim");
1760 cbuf_init_palette();
1762 termlist = NULL;
1763 currterm = NULL;
1764 memset(&hvbuf, 0, sizeof(hvbuf));
1766 if (execsh_prepare(&first_run_ed, "$SHELL -i") == 0) {
1767 fprintf(stderr, "WTF?!\n");
1768 exit(1);
1771 parse_args(argc, argv);
1772 // override some initial options
1773 if (opt_debug_hotswap_fd >= 0) {
1774 hotswap_load_options();
1776 x11_init_display();
1777 xrm_load_options();
1779 childsigfd = summonChildSaviour();
1780 if (childsigfd < 0) {
1781 fprintf(stderr, "FATAL: can't summon dead children saviour!\n");
1782 exit(1);
1785 x11_create_cwin_x11();
1787 if (opt_debug_hotswap_fd >= 0) {
1788 execsh_free(&first_run_ed);
1789 hotswap_load_tabs();
1790 } else {
1791 // it will be automatically activated
1792 if (term_create_new_term() == NULL) {
1793 fprintf(stderr, "FATAL: cannot create terminal!\n");
1794 exit(1);
1797 xrm_unseal_ptr_options();
1799 #if 0
1800 fprintf(stderr, "TERM INITIAL: %dx%d\n", TERM_CBUF(term)->width, TERM_CBUF(term)->height);
1801 #endif
1803 if (logfname != NULL) {
1804 opt_dump_fd = open(logfname, O_CLOEXEC|O_WRONLY|O_CREAT|O_TRUNC, 0644);
1805 if (opt_dump_fd < 0) {
1806 fprintf(stderr, "ERROR: cannot create log file '%s'!\n", logfname);
1807 } else if (opt_dump_fd_enabled) {
1808 fprintf(stderr, "ERROR: logging into file '%s'.\n", logfname);
1809 } else {
1810 fprintf(stderr, "ERROR: created log file '%s'.\n", logfname);
1814 x11_main_loop();
1816 if (opt_dump_fd >= 0) close(opt_dump_fd);
1818 x11_free_fonts();
1819 XCloseDisplay(x11_dpy);
1821 return 0;