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/>.
24 // do not turn this on, you don't need it
25 //#define USELESS_DEBUG_FEATURES
42 #include <sys/ioctl.h>
43 #include <sys/resource.h>
44 #include <sys/signalfd.h>
47 #include <sys/types.h>
50 #include <X11/Xatom.h>
52 #include <X11/Xutil.h>
53 #include <X11/Xresource.h>
54 #include <X11/cursorfont.h>
55 #include <X11/keysym.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 // ////////////////////////////////////////////////////////////////////////// //
102 // ////////////////////////////////////////////////////////////////////////// //
106 char *path
; // can be NULL or empty
110 // ////////////////////////////////////////////////////////////////////////// //
111 // terminal mode flags (bitmask)
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 // ////////////////////////////////////////////////////////////////////////// //
148 Mod_Ctrl
= ControlMask
,
149 Mod_Shift
= ShiftMask
,
151 Mod_NumLock
= Mod2Mask
,
152 Mod_AltGr
= Mod3Mask
,
153 Mod_Hyper
= Mod4Mask
,
156 // pressed/released button number
159 // WARNING! numbers are important!
175 // waiting for a keypress
177 // dead, remove from the list on the next main loop iteration
181 // selection block type
183 SBLOCK_NONE
= 0, // selection mode is inactive
190 // ////////////////////////////////////////////////////////////////////////// //
192 int fd
; // history file fd (-1 if none yet)
193 int width
; // history line width
194 int lcount
; // number of history lines
198 // ////////////////////////////////////////////////////////////////////////// //
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
;
208 typedef struct Message_t
{
210 uint64_t time
; // either die time, our timeout for yet-to-show messages
211 struct Message_t
*next
;
217 CursorPos cursaved
; // saved cursor position
218 CharCell currattr
; // for attributes and flags
219 yterm_bool curhidden
;
220 // scroll top and bottom lines (inclusive)
222 // for "ESC 7" and "ESC 8"
224 uint32_t charsetsaved
;
225 uint32_t modesaved
; // charsets
228 #define TAB_MAX_TEXT_LEN (128)
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
239 XChar2b text
[TAB_MAX_TEXT_LEN
];
242 typedef struct term_t
{
243 TermScreen
*wkscr
; // check this to determine which buffer is active
244 // main and alternate screens
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"
254 // we also need to remember the scrolling area
255 int scroll_y0
, scroll_y1
;
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
269 int cmdfd
; // terminal control fd for child
274 yterm_bool custom
; // custom title?
275 uint64_t next_check_time
; // next title check time
277 int deadstate
; // see `DS_xxx`
284 // read DFA (for one UTF-8 char)
287 int mode
; // mouse report mode: 1000, 1005, 1006, 1015
288 int lastx
, lasty
; // last reported coords
289 int lastbtn
; // last pressed button
291 // "selection mode" support
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`
299 yterm_bool inprogress
; // we're currently selecting
301 Message
*osd_messages
;
302 // last activation time, so we could return to the previous term
303 uint64_t last_acttime
;
306 // local color mode, or -1
308 // local mouse reports, or -1
310 // local escesc, or -1
312 // linked list of terminals
318 // ////////////////////////////////////////////////////////////////////////// //
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
;
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
;
353 static Atom xaCSTRING
;
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
;
401 static uint64_t next_redraw_time
= 0;
402 static yterm_bool need_update_tabs
= 0;
405 static char exe_path
[4096];
407 static yterm_bool xrm_reloading
= 0;
410 // ////////////////////////////////////////////////////////////////////////// //
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;
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))
510 if ((term
->mode
& YTERM_MODE_MOUSE
) != 0 && !TERM_IN_SELECTION(term
) &&
511 term
->deadstate
== DS_ALIVE
&& term
->child
.cmdfd
>= 0) {
520 //==========================================================================
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
);
532 if (pgrp
<= 0) pgrp
= tcgetpgrp(term
->child
.cmdfd
);
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
;
548 //==========================================================================
552 //==========================================================================
553 static void get_exe_path (void) {
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);
568 //==========================================================================
572 //==========================================================================
573 static void term_check_title (Term
*term
) {
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
;
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);
597 if (rd
< 0) goto error
;
600 exe
= strrchr(txexe
, '/');
601 if (exe
!= NULL
) exe
+= 1; else exe
= txexe
;
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
613 // append to the title
614 size_t tlen
= strlen(term
->title
.last
);
615 if (tlen
+ 8 <= sizeof(term
->title
.last
)) {
617 size_t plen
= (size_t)rd
;
618 size_t nlen
= tlen
+ plen
+ 1;
619 if (nlen
< sizeof(term
->title
.last
)) {
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
;
625 // should always be true
626 path
= txpath
+ (plen
- xleft
);
627 snprintf(term
->title
.last
, sizeof(term
->title
.last
), "[%s] ...%s", exe
, path
);
636 snprintf(term
->title
.last
, sizeof(term
->title
.last
), "%s", opt_title
);
640 //==========================================================================
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;
658 fprintf(stderr
, "COPY-SCROLL DISABLED.\n");
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 //==========================================================================
701 //==========================================================================
702 YTERM_STATIC_INLINE yterm_bool
x11_filter_event (XEvent
*eventp
) {
703 if (XFilterEvent(eventp
, (Window
)0)) {
707 if (eventp
->type
== MappingNotify
) {
708 if (eventp
->xmapping
.request
== MappingKeyboard
||
709 eventp
->xmapping
.request
== MappingModifier
)
711 XRefreshKeyboardMapping(&eventp
->xmapping
);
720 //==========================================================================
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 //==========================================================================
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
) {
770 if (event
->xclient
.message_type
== xaWMProtocols
) {
771 if ((Atom
)event
->xclient
.data
.l
[0] == xaWMDeleteWindow
) {
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
);
780 term_invalidate_cursor(term
);
781 term_send_focus_event(term
, 1);
784 } else if (event
->xclient
.data
.l
[1] == XEMBED_FOCUS_OUT
) {
785 if (x11_xic
) XUnsetICFocus(x11_xic
);
788 term_invalidate_cursor(term
);
789 term_send_focus_event(term
, 0);
795 x11_parse_keyevent(&event
->xkey
);
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
);
805 /*if (event->xexpose.count == 0)*/ menu_mark_all_dirty(curr_menu
);
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;
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;
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
));
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;
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
);
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
);
846 /*if (event->xgraphicsexpose.count == 0)*/ menu_mark_all_dirty(curr_menu
);
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
);
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
);
866 case VisibilityNotify
:
867 #ifdef DUMP_X11_EVENTS
868 fprintf(stderr
, "VisibilityNotify: %d\n", event
->xvisibility
.state
);
870 if (event
->xvisibility
.state
== VisibilityFullyObscured
) {
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
) {
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
886 term
->scroll_count
= 0; // we can accumulate scrolls again
887 cbuf_mark_all_clean(TERM_CBUF(term
));
892 #ifdef DUMP_X11_EVENTS
893 fprintf(stderr
, "FocusIn!\n");
895 if (x11_xic
) XSetICFocus(x11_xic
);
898 term_invalidate_cursor(term
);
899 term_send_focus_event(term
, 1);
904 #ifdef DUMP_X11_EVENTS
905 fprintf(stderr
, "FocusOut!\n");
908 if (x11_xic
) XUnsetICFocus(x11_xic
);
911 term_invalidate_cursor(term
);
912 term_send_focus_event(term
, 0);
916 if (term
!= NULL
) x11_motion_report(&event
->xmotion
);
920 if (term
!= NULL
) x11_mouse_report(&event
->xbutton
);
922 case SelectionNotify
:
923 // we got our requested selection, and should paste it
924 //cwin_selection_came(cwin, &event->xselection);
926 case SelectionRequest
:
927 //!prepareCopy(&event->xselectionrequest);
930 //!clearSelection(&event->xselectionclear);
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);
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);
947 fprintf(stderr
, "PROP: unknown event subtype!\n");
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
);
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
);
976 winHeight
= max2(1, winRealHeight
/ charHeight
* charHeight
);
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
);
984 // first run commandline
985 if (first_run_ed
.argc
!= 0 && term
!= NULL
) {
986 const yterm_bool rres
= term_exec(term
, &first_run_ed
);
988 fprintf(stderr
, "FIRST-RUN: <%s> (fd=%d; pid=%u)\n", first_run_ed
.argv
[0],
989 term
->child
.cmdfd
, (unsigned)term
->child
.pid
);
991 execsh_free(&first_run_ed
);
993 fprintf(stderr
, "FATAL: cannot run initial program!\n");
999 #ifdef DUMP_X11_EVENTS
1000 fprintf(stderr
, "MapNotify!\n");
1004 fprintf(stderr
, "WARNING: WTF?! window is just mapped, yet marked as visible!\n");
1007 // recheck all terminal titles
1008 for (Term
*tt
= termlist
; tt
!= NULL
; tt
= tt
->next
) tt
->title
.next_check_time
= 0;
1010 cbuf_mark_all_clean(TERM_CBUF(term
));
1018 term_lock_scroll(term
);
1019 cbuf_mark_all_clean(TERM_CBUF(term
));
1034 //==========================================================================
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
);
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
);
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
);
1064 yterm_assert((uint32_t)wr
<= term
->wrbuf
.used
);
1065 const uint32_t dleft
= term
->wrbuf
.used
- (uint32_t)wr
;
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;
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
);
1084 char *nbx
= realloc(term
->wrbuf
.buf
, nsz
);
1086 term
->wrbuf
.buf
= nbx
;
1087 term
->wrbuf
.size
= nsz
;
1094 //==========================================================================
1098 //==========================================================================
1099 static void all_term_write (void) {
1100 for (Term
*term
= termlist
; term
!= NULL
; term
= term
->next
) {
1101 one_term_write(term
);
1106 //==========================================================================
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
)) {
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
);
1121 // `EAGAIN` should not happen, but better be safe than sorry
1122 if (errno
!= EINTR
&& errno
!= EAGAIN
) {
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) {
1137 term
->rdcp
= yterm_koi2uni(((const unsigned char *)rdbuf
)[pos
]); pos
+= 1;
1138 term_process_char(term
, term
->rdcp
);
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 //==========================================================================
1160 //==========================================================================
1161 static void all_term_read (uint64_t rdett
) {
1162 #ifdef DUMP_READ_AMOUNT
1167 if (rdett
!= 0) rdett
-= 1;
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
1179 fprintf(stderr
, "...has more!\n");
1186 ctt
= (has_more
? yterm_get_msecs() : rdett
+ 1);
1187 #ifdef DUMP_RW_TIMEOUTS
1188 fprintf(stderr
, "RDETT: %llu; ctt: %llu\n", rdett
, ctt
);
1190 } while (has_more
&& ctt
<= rdett
);
1191 #ifdef DUMP_READ_AMOUNT
1192 if (was_more
) fprintf(stderr
, ":::reading done!\n");
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
) {
1208 // mark dirty first, because we may need to redraw tabbar
1209 menu_mark_all_dirty(curr_menu
);
1211 next
->prev
= menu
->prev
;
1212 menu_free_window(menu
);
1215 curr_menu
= menu
->prev
;
1216 menu_free_window(menu
);
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
);
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
;
1245 // tab numbers changed, so we need to update tabs
1246 force_frame_redraw(1);
1253 //==========================================================================
1257 //==========================================================================
1258 static void fix_term_size (Term
*term
) {
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
) {
1268 fprintf(stderr
, "WINCH fix.\n");
1270 if (!opt_vttest
&& term_fix_size(term
)) {
1271 // redraw immediately
1272 force_frame_redraw(1);
1276 fprintf(stderr
, "WINCH delay...\n");
1284 //==========================================================================
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);
1294 term_invalidate_cursor(currterm
);
1295 term_send_focus_event(currterm
, 0);
1297 //currterm->history.blocktype = SBLOCK_NONE;
1298 currterm
->active
= 0;
1303 force_frame_redraw(1);
1304 term
->title
.next_check_time
= 0;
1305 if (winFocused
) term_send_focus_event(term
, 1);
1308 if (TERM_IN_SELECTION(term
)) {
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
;
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
))
1346 // and properly activate it
1347 term
= currterm
; currterm
= NULL
;
1348 term_activate(term
);
1349 yterm_assert(currterm
!= NULL
);
1350 yterm_assert(currterm
== term
);
1352 fix_term_size(term
);
1357 //==========================================================================
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
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
);
1393 Message
*osd
= term
->osd_messages
;
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);
1400 free(term
->osd_messages
);
1401 term
->osd_messages
= osd
;
1402 if (osd
!= NULL
) osd
->time
+= ctt
;
1407 if (next_redraw_time
<= ctt
) {
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) {
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
),
1418 TERM_CBUF(term
)->width
,
1422 fprintf(stderr
, "SCROLL-ACC-UP: y0=%d; y1=%d; lines=%d\n",
1423 term
->scroll_y0
, term
->scroll_y1
, lines
);
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) {
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
),
1440 TERM_CBUF(term
)->width
,
1444 fprintf(stderr
, "SCROLL-ACC-DOWN: y0=%d; y1=%d; lines=%d\n",
1445 term
->scroll_y0
, term
->scroll_y1
, lines
);
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();
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 //==========================================================================
1474 //==========================================================================
1475 static void blink_cursor (void) {
1476 Term
*term
= currterm
;
1478 if (TERM_CURVISIBLE(term
) && winFocused
&& winVisible
) {
1480 if (opt_cur_blink_time
> 0) {
1481 newPhase
= ((int)(yterm_get_msecs() % (unsigned)(opt_cur_blink_time
* 2)) >= opt_cur_blink_time
);
1485 if (newPhase
!= curPhase
) {
1487 fprintf(stderr
, "***CURBLINK!***\n");
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
);
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) {
1509 Term
*term
= termlist
;
1510 while (res
== 0 && term
!= NULL
) {
1511 res
= (term
->wrbuf
.used
!= 0);
1518 //==========================================================================
1522 //==========================================================================
1523 static void wait_for_events (int xfd
) {
1524 Term
*term
= currterm
;
1527 while (!XPending(x11_dpy
) && !has_term_to_write()) {
1530 // invisible window, wait infinitely (if we have nothing to write)
1533 const uint64_t ctt
= yterm_get_msecs();
1534 if (next_redraw_time
<= ctt
) {
1535 // poll, for some reason
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
);
1542 // cursor is invisible, wait infinitely
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;
1554 #ifdef DUMP_RW_TIMEOUTS
1555 fprintf(stderr
, "TOUT: %d\n", tout
);
1559 struct timeval timeout
;
1561 timeout
.tv_sec
= tout
/ 1000;
1562 timeout
.tv_usec
= tout
* 1000;
1564 memset(&timeout
, 0, sizeof(timeout
));
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
);
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
));
1597 //==========================================================================
1599 // term_create_new_term
1601 //==========================================================================
1602 static Term
*term_create_new_term (void) {
1603 Term
*term
= calloc(1, sizeof(Term
));
1605 term_init(term
, max2(1, winWidth
/ charWidth
),
1606 max2(1, winHeight
/ charHeight
));
1607 Term
*last
= termlist
;
1609 while (last
->next
!= NULL
) last
= last
->next
;
1620 //==========================================================================
1624 // event processing loop
1626 //==========================================================================
1627 static void x11_main_loop (void) {
1628 yterm_bool quit
= 0;
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
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
));
1650 x11_enable_mouse(0, 0);
1656 #ifdef DUMP_RW_TIMEOUTS
1657 fprintf(stderr
, "NTT: %llu; ctt: %llu\n", next_redraw_time
, yterm_get_msecs());
1660 // write data to terminals while we can
1661 // there should not be a lot of data, so write everything
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
);
1679 if (opt_debug_perform_hotswap
) {
1680 opt_debug_perform_hotswap
= 0;
1682 open_menu(menu_new_message_box("ERROR!", "Hotswapping failed!"));
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
);
1712 //==========================================================================
1714 // summonChildSaviour
1716 //==========================================================================
1717 static int summonChildSaviour (void) {
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 //==========================================================================
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];
1738 return xsel_main(argc
, argv
);
1741 memset(&first_run_ed
, 0, sizeof(first_run_ed
));
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 0 --trim");
1753 opt_paste_from
[1] = strdup("$SELF xsel -s -o -t 0 --trim");
1754 opt_paste_from
[2] = strdup("$SELF xsel -b -o -t 0 --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();
1764 memset(&hvbuf
, 0, sizeof(hvbuf
));
1766 if (execsh_prepare(&first_run_ed
, "$SHELL -i") == 0) {
1767 fprintf(stderr
, "WTF?!\n");
1771 parse_args(argc
, argv
);
1772 // override some initial options
1773 if (opt_debug_hotswap_fd
>= 0) {
1774 hotswap_load_options();
1779 childsigfd
= summonChildSaviour();
1780 if (childsigfd
< 0) {
1781 fprintf(stderr
, "FATAL: can't summon dead children saviour!\n");
1785 x11_create_cwin_x11();
1787 if (opt_debug_hotswap_fd
>= 0) {
1788 execsh_free(&first_run_ed
);
1789 hotswap_load_tabs();
1791 // it will be automatically activated
1792 if (term_create_new_term() == NULL
) {
1793 fprintf(stderr
, "FATAL: cannot create terminal!\n");
1797 xrm_unseal_ptr_options();
1800 fprintf(stderr
, "TERM INITIAL: %dx%d\n", TERM_CBUF(term
)->width
, TERM_CBUF(term
)->height
);
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
);
1810 fprintf(stderr
, "ERROR: created log file '%s'.\n", logfname
);
1816 if (opt_dump_fd
>= 0) close(opt_dump_fd
);
1819 XCloseDisplay(x11_dpy
);