10 # include <linux/kd.h>
11 # include <linux/keyboard.h>
12 #elif defined(__FreeBSD__) || defined(__DragonFly__)
13 # include <sys/ioctl.h>
14 # include <sys/kbio.h>
17 #include <os_call.hpp>
18 #include <ScopeHelpers.h>
21 #include "CheckedCast.hpp"
22 #include "WinPortHandle.h"
24 #include "TTYBackend.h"
25 #include "TTYRevive.h"
26 #include "TTYFar2lClipboardBackend.h"
27 #include "TTYNegotiateFar2l.h"
29 #include "../FSClipboardBackend.h"
31 static volatile long s_terminal_size_change_id
= 0;
32 static TTYBackend
* g_vtb
= nullptr;
34 long _iterm2_cmd_ts
= 0;
35 bool _iterm2_cmd_state
= 0;
37 static void OnSigHup(int signo
);
39 static bool IsEnhancedKey(WORD code
)
41 return (code
==VK_LEFT
|| code
==VK_RIGHT
|| code
==VK_UP
|| code
==VK_DOWN
42 || code
==VK_HOME
|| code
==VK_END
|| code
==VK_NEXT
|| code
==VK_PRIOR
);
45 static WORD
WChar2WinVKeyCode(WCHAR wc
)
47 if ((wc
>= L
'0' && wc
<= L
'9') || (wc
>= L
'A' && wc
<= L
'Z')) {
50 if (wc
>= L
'a' && wc
<= L
'z') {
51 return (WORD
)wc
- (L
'a' - L
'A');
54 case L
' ': return VK_SPACE
;
55 case L
'.': return VK_OEM_PERIOD
;
56 case L
',': return VK_OEM_COMMA
;
57 case L
'_': case L
'-': return VK_OEM_MINUS
;
58 case L
'+': return VK_OEM_PLUS
;
59 case L
';': case L
':': return VK_OEM_1
;
60 case L
'/': case L
'?': return VK_OEM_2
;
61 case L
'~': case L
'`': return VK_OEM_3
;
62 case L
'[': case L
'{': return VK_OEM_4
;
63 case L
'\\': case L
'|': return VK_OEM_5
;
64 case L
']': case '}': return VK_OEM_6
;
65 case L
'\'': case '\"': return VK_OEM_7
;
66 case L
'!': return '1';
67 case L
'@': return '2';
68 case L
'#': return '3';
69 case L
'$': return '4';
70 case L
'%': return '5';
71 case L
'^': return '6';
72 case L
'&': return '7';
73 case L
'*': return '8';
74 case L
'(': return '9';
75 case L
')': return '0';
77 fprintf(stderr
, "%s: not translated %u '%lc'\n", __FUNCTION__
, (unsigned int)wc
, wc
);
82 TTYBackend::TTYBackend(const char *full_exe_path
, int std_in
, int std_out
, bool ext_clipboard
, bool norgb
, const char *nodetect
, bool far2l_tty
, unsigned int esc_expiration
, int notify_pipe
, int *result
) :
83 _full_exe_path(full_exe_path
),
86 _ext_clipboard(ext_clipboard
),
89 _far2l_tty(far2l_tty
),
90 _esc_expiration(esc_expiration
),
91 _notify_pipe(notify_pipe
),
93 _largest_window_size_ready(false)
96 if (pipe_cloexec(_kickass
) == -1) {
97 _kickass
[0] = _kickass
[1] = -1;
99 MakeFDNonBlocking(_kickass
[1]);
104 g_winport_con_out
->SetSize(w
.ws_col
, w
.ws_row
);
106 g_winport_con_out
->GetSize(_cur_width
, _cur_height
);
110 TTYBackend::~TTYBackend()
120 if (os_call_ssize(write
, _kickass
[1], (const void*)&_kickass
, (size_t)1) == -1) {
121 perror("~TTYBackend: write kickass");
123 pthread_join(_reader_trd
, nullptr);
127 CheckedCloseFDPair(_kickass
);
131 bool TTYBackend::GetWinSize(struct winsize
&w
)
133 int r
= ioctl(_stdout
, TIOCGWINSZ
, &w
);
134 if (UNLIKELY(r
!= 0)) {
135 r
= ioctl(_stdin
, TIOCGWINSZ
, &w
);
136 if (UNLIKELY(r
!= 0)) {
137 perror("GetWinSize");
145 void TTYBackend::DetachNotifyPipe()
147 if (_notify_pipe
!= -1) {
148 MakeFDNonBlocking(_notify_pipe
);
150 if (_result
&& write(_notify_pipe
, _result
,
151 sizeof(*_result
)) != sizeof(*_result
)) {
152 perror("DetachNotifyPipe - write");
155 CheckedCloseFD(_notify_pipe
);
159 bool TTYBackend::Startup()
161 assert(!_reader_trd
);
163 _cur_width
=_cur_height
= 64;
164 g_winport_con_out
->GetSize(_cur_width
, _cur_height
);
165 g_winport_con_out
->SetBackend(this);
167 if (pthread_create(&_reader_trd
, nullptr, sReaderThread
, this) != 0) {
174 static wchar_t s_backend_identification
[8] = L
"TTY";
176 static void AppendBackendIdentificationChar(char ch
)
178 const size_t l
= wcslen(s_backend_identification
);
179 if (l
+ 1 >= ARRAYSIZE(s_backend_identification
)) {
182 s_backend_identification
[l
+ 1] = 0;
183 s_backend_identification
[l
] = (unsigned char)ch
;
186 void TTYBackend::UpdateBackendIdentification()
188 s_backend_identification
[3] = 0;
190 if (_far2l_tty
|| _ttyx
|| _using_extension
) {
191 AppendBackendIdentificationChar('|');
195 AppendBackendIdentificationChar('F');
197 } else if (_ttyx
|| _using_extension
) {
199 AppendBackendIdentificationChar('X');
201 if (_using_extension
) {
202 AppendBackendIdentificationChar(_using_extension
);
203 } else if (_ttyx
&& _ttyx
->HasXi()) {
204 AppendBackendIdentificationChar('i');
208 g_winport_backend
= s_backend_identification
;
211 void TTYBackend::ReaderThread()
213 bool prev_far2l_tty
= false;
215 _far2l_cursor_height
= -1; // force cursor height update on next output dispatch
216 _fkeys_support
= _far2l_tty
? FKS_UNKNOWN
: FKS_NOT_SUPPORTED
;
219 if (!prev_far2l_tty
&& !_ext_clipboard
) {
220 IFar2lInterractor
*interractor
= this;
221 _clipboard_backend_setter
.Set
<TTYFar2lClipboardBackend
>(interractor
);
225 if (!strchr(_nodetect
, 'x') || strstr(_nodetect
, "xi")) {
226 _ttyx
= StartTTYX(_full_exe_path
, !strstr(_nodetect
, "xi"));
229 if (!_ext_clipboard
) {
230 _clipboard_backend_setter
.Set
<TTYXClipboard
>(_ttyx
);
234 ChooseSimpleClipboardBackend();
237 UpdateBackendIdentification();
238 prev_far2l_tty
= _far2l_tty
;
241 std::unique_lock
<std::mutex
> lock(_async_mutex
);
243 _ae
.flags
.term_resized
= true;
247 pthread_t writer_trd
= 0;
248 if (pthread_create(&writer_trd
, nullptr, sWriterThread
, this) != 0) {
255 } catch (const std::exception
&e
) {
256 fprintf(stderr
, "ReaderLoop: %s <%d>\n", e
.what(), errno
);
263 std::unique_lock
<std::mutex
> lock(_async_mutex
);
265 _async_cond
.notify_all();
268 pthread_join(writer_trd
, nullptr);
273 const std::string
&info
= StrWide2MB(g_winport_con_out
->GetTitle());
274 _notify_pipe
= TTYReviveMe(_stdin
, _stdout
, _far2l_tty
, _kickass
[0], info
);
275 if (_notify_pipe
!= -1) {
282 void TTYBackend::ReaderLoop()
284 std::unique_ptr
<TTYInput
> tty_in(new TTYInput(this));
287 bool idle_expired
= false;
288 while (!_exiting
&& !_deadio
) {
289 int maxfd
= (_kickass
[0] > _stdin
) ? _kickass
[0] : _stdin
;
293 FD_SET(_kickass
[0], &fds
);
294 FD_SET(_stdin
, &fds
);
295 FD_SET(_stdin
, &fde
);
299 if (!idle_expired
&& _esc_expiration
> 0 && !_far2l_tty
) {
301 tv
.tv_sec
= _esc_expiration
/ 1000;
302 tv
.tv_usec
= suseconds_t((_esc_expiration
- tv
.tv_sec
* 1000) * 1000);
304 rs
= os_call_int(select
, maxfd
+ 1, &fds
, (fd_set
*)nullptr, &fde
, &tv
);
306 rs
= os_call_int(select
, maxfd
+ 1, &fds
, (fd_set
*)nullptr, &fde
, (timeval
*)nullptr);
310 throw std::runtime_error("select failed");
314 idle_expired
= false;
316 } else if (!idle_expired
) {
318 tty_in
->OnIdleExpired();
321 if (_flush_input_queue
) {
322 _flush_input_queue
= false;
324 tty_in
.reset(new TTYInput(this));
328 if (FD_ISSET(_stdin
, &fds
)) {
330 ssize_t rd
= os_call_ssize(read
, _stdin
, (void*)buf
, sizeof(buf
));
332 throw std::runtime_error("stdin read failed");
334 //fprintf(stderr, "ReaderThread: CHAR 0x%x\n", (unsigned char)c);
335 tty_in
->OnInput(buf
, (size_t)rd
);
337 // iTerm2 cmd+v workaround
338 if (_iterm2_cmd_state
|| _iterm2_cmd_ts
) {
339 std::unique_lock
<std::mutex
> lock(_async_mutex
);
340 _ae
.flags
.output
= true;
341 _async_cond
.notify_all();
345 if (FD_ISSET(_stdin
, &fde
)) {
346 throw std::runtime_error("stdin exception");
349 if (FD_ISSET(_kickass
[0], &fds
)) {
351 if (read(_kickass
[0], &cmd
, 1) <= 0) {
352 throw std::runtime_error("kickass read failed");
355 long terminal_size_change_id
=
356 __sync_val_compare_and_swap(&s_terminal_size_change_id
, 0, 0);
357 if (_terminal_size_change_id
!= terminal_size_change_id
) {
358 _terminal_size_change_id
= terminal_size_change_id
;
359 std::unique_lock
<std::mutex
> lock(_async_mutex
);
360 _ae
.flags
.term_resized
= true;
361 _async_cond
.notify_all();
367 void TTYBackend::WriterThread()
369 bool gone_background
= false;
371 TTYOutput
tty_out(_stdout
, _far2l_tty
, _norgb
);
372 DispatchPalette(tty_out
);
373 // DispatchTermResized(tty_out);
374 while (!_exiting
&& !_deadio
) {
378 std::unique_lock
<std::mutex
> lock(_async_mutex
);
380 _async_cond
.wait(lock
);
383 std::swap(ae
.all
, _ae
.all
);
384 if (ae
.flags
.palette
) {
385 _async_cond
.notify_all();
389 } while (!_exiting
&& !_deadio
);
391 if (ae
.flags
.palette
) {
392 DispatchPalette(tty_out
);
395 if (ae
.flags
.term_resized
) {
396 DispatchTermResized(tty_out
);
397 ae
.flags
.output
= true;
401 DispatchOutput(tty_out
);
403 if (ae
.flags
.title_changed
) {
404 tty_out
.ChangeTitle(StrWide2MB(g_winport_con_out
->GetTitle()));
407 if (ae
.flags
.far2l_interract
)
408 DispatchFar2lInterract(tty_out
);
410 if (ae
.flags
.osc52clip_set
) {
411 DispatchOSC52ClipSet(tty_out
);
414 // iTerm2 cmd+v workaround
415 if (_iterm2_cmd_state
|| _iterm2_cmd_ts
) {
416 tty_out
.CheckiTerm2Hack();
422 if (ae
.flags
.go_background
) {
423 gone_background
= true;
428 } catch (const std::exception
&e
) {
429 fprintf(stderr
, "WriterThread: %s <%d>\n", e
.what(), errno
);
433 if (gone_background
) {
439 /////////////////////////////////////////////////////////////////////////
441 void TTYBackend::DispatchPalette(TTYOutput
&tty_out
)
443 TTYBasePalette palette
;
445 std::lock_guard
<std::mutex
> lock(_palette_mtx
);
447 if (_override_default_palette
) {
448 for (size_t i
= 0; i
< BASE_PALETTE_SIZE
; ++i
) {
449 if (palette
.background
[i
] == (DWORD
)-1) {
450 palette
.background
[i
] = g_winport_palette
.background
[i
].AsRGB();
452 if (palette
.foreground
[i
] == (DWORD
)-1) {
453 palette
.foreground
[i
] = g_winport_palette
.foreground
[i
].AsRGB();
458 tty_out
.ChangePalette(palette
);
461 void TTYBackend::DispatchTermResized(TTYOutput
&tty_out
)
464 if (!GetWinSize(w
)) {
468 if (_cur_width
!= w
.ws_col
|| _cur_height
!= w
.ws_row
) {
469 g_winport_con_out
->SetSize(w
.ws_col
, w
.ws_row
);
470 g_winport_con_out
->GetSize(_cur_width
, _cur_height
);
471 INPUT_RECORD ir
= {};
472 ir
.EventType
= WINDOW_BUFFER_SIZE_EVENT
;
473 ir
.Event
.WindowBufferSizeEvent
.dwSize
.X
= _cur_width
;
474 ir
.Event
.WindowBufferSizeEvent
.dwSize
.Y
= _cur_height
;
475 g_winport_con_in
->Enqueue(&ir
, 1);
477 std::vector
<CHAR_INFO
> tmp
;
478 tty_out
.MoveCursorStrict(1, 1);
479 _prev_height
= _prev_width
= 0;
480 _prev_output
.swap(tmp
);// ensure memory released
483 //#define LOG_OUTPUT_COUNT
484 void TTYBackend::DispatchOutput(TTYOutput
&tty_out
)
486 _cur_output
.resize(size_t(_cur_width
) * _cur_height
);
488 COORD data_size
= {CheckedCast
<SHORT
>(_cur_width
), CheckedCast
<SHORT
>(_cur_height
) };
489 COORD data_pos
= {0, 0};
490 SMALL_RECT screen_rect
= {0, 0, CheckedCast
<SHORT
>(_cur_width
- 1), CheckedCast
<SHORT
>(_cur_height
- 1)};
491 g_winport_con_out
->Read(&_cur_output
[0], data_size
, data_pos
, screen_rect
);
492 #ifdef LOG_OUTPUT_COUNT
493 unsigned long printed_count
= 0, printed_skipable
= 0;
495 if (_cur_output
.empty()) {
498 } else if (_cur_width
!= _prev_width
|| _cur_height
!= _prev_height
) {
499 for (unsigned int y
= 0; y
< _cur_height
; ++y
) {
500 const CHAR_INFO
*cur_line
= &_cur_output
[size_t(y
) * _cur_width
];
501 tty_out
.MoveCursorLazy(y
+ 1, 1);
502 tty_out
.WriteLine(cur_line
, _cur_width
);
505 } else for (unsigned int y
= 0; y
< _cur_height
; ++y
) {
506 const CHAR_INFO
*cur_line
= &_cur_output
[size_t(y
) * _cur_width
];
507 const CHAR_INFO
*prev_line
= &_prev_output
[size_t(y
) * _prev_width
];
509 const auto ApproxWeight
= [&](unsigned int x_
)
511 if (CI_USING_COMPOSITE_CHAR(cur_line
[x_
])) {
514 return ((cur_line
[x_
].Char
.UnicodeChar
> 0x7f) ? 2 : 1);
517 const auto Modified
= [&](unsigned int x_
)
519 return (cur_line
[x_
].Char
.UnicodeChar
!= prev_line
[x_
].Char
.UnicodeChar
520 || cur_line
[x_
].Attributes
!= prev_line
[x_
].Attributes
);
523 for (unsigned int x
= 0, skipped_start
= 0, skipped_weight
= 0; x
< _cur_width
; ++x
) {
525 skipped_weight
+= ApproxWeight(x
);
529 // Current char doesn't match to what was on this position before
530 // so have to print it at right position.
531 // Note that cursor moving directive has its own output 'weight',
532 // so if skipped chars sequence is not bigger than cursor move then
533 // its better to print skipped chars instead of moving cursor.
535 bool print_skipped
= false;
536 if (x
!= skipped_start
&& tty_out
.WeightOfHorizontalMoveCursor(y
+ 1, skipped_start
+ 1) == 0) { // is cursor at expected pos?
537 const int move_cursor_weight
= tty_out
.WeightOfHorizontalMoveCursor(y
+ 1, x
+ 1);
538 print_skipped
= (move_cursor_weight
>= 0 && skipped_weight
<= (unsigned int)move_cursor_weight
);
541 tty_out
.WriteLine(&cur_line
[skipped_start
], x
+ 1 - skipped_start
);
542 #ifdef LOG_OUTPUT_COUNT
543 printed_skipable
+= x
- skipped_start
;
546 tty_out
.MoveCursorLazy(y
+ 1, x
+ 1);
547 tty_out
.WriteLine(&cur_line
[x
], 1);
549 #ifdef LOG_OUTPUT_COUNT
552 skipped_start
= x
+ 1;
556 #ifdef LOG_OUTPUT_COUNT
557 fprintf(stderr
, "!!! OUTPUT_COUNT: (normal=%lu + skipable=%lu) = %lu of %lu\n",
558 printed_count
, printed_skipable
,
559 printed_count
+ printed_skipable
,
560 (unsigned long)_cur_output
.size());
562 _prev_width
= _cur_width
;
563 _prev_height
= _cur_height
;
564 _prev_output
.swap(_cur_output
);
566 UCHAR cursor_height
= 1;
567 bool cursor_visible
= false;
568 COORD cursor_pos
= g_winport_con_out
->GetCursor(cursor_height
, cursor_visible
);
569 tty_out
.MoveCursorLazy(cursor_pos
.Y
+ 1, cursor_pos
.X
+ 1);
570 tty_out
.ChangeCursor(cursor_visible
);
572 if (_far2l_cursor_height
!= (int)(unsigned int)cursor_height
) {
573 _far2l_cursor_height
= (int)(unsigned int)cursor_height
;
574 tty_out
.ChangeCursorHeight(cursor_height
);
579 void TTYBackend::DispatchFar2lInterract(TTYOutput
&tty_out
)
581 Far2lInterractV queued
;
583 std::unique_lock
<std::mutex
> lock(_async_mutex
);
584 queued
.swap(_far2l_interracts_queued
);
587 std::unique_lock
<std::mutex
> lock_sent(_far2l_interracts_sent
);
589 for (auto & i
: queued
) {
592 if (_far2l_interracts_sent
.size() >= 0xff) {
594 "TTYBackend::DispatchFar2lInterract: too many sent interracts - %ld\n",
595 _far2l_interracts_sent
.size());
601 id
= ++_far2l_interracts_sent
._id_counter
;
602 if (id
&& _far2l_interracts_sent
.find(id
) == _far2l_interracts_sent
.end()) break;
605 i
->stk_ser
.PushNum(id
);
608 _far2l_interracts_sent
.emplace(id
, i
);
610 tty_out
.SendFar2lInterract(i
->stk_ser
);
614 void TTYBackend::DispatchOSC52ClipSet(TTYOutput
&tty_out
)
616 std::string osc52clip
;
618 std::unique_lock
<std::mutex
> lock(_async_mutex
);
619 osc52clip
.swap(_osc52clip
);
621 tty_out
.SendOSC52ClipSet(osc52clip
);
624 /////////////////////////////////////////////////////////////////////////
626 void TTYBackend::KickAss(bool flush_input_queue
)
628 if (flush_input_queue
)
629 _flush_input_queue
= true;
632 if (os_call_ssize(write
, _kickass
[1], (const void*)&c
, (size_t)1) != 1)
633 perror("write(_kickass[1]");
636 void TTYBackend::OnConsoleOutputUpdated(const SMALL_RECT
*areas
, size_t count
)
638 std::unique_lock
<std::mutex
> lock(_async_mutex
);
639 _ae
.flags
.output
= true;
640 _async_cond
.notify_all();
643 void TTYBackend::OnConsoleOutputResized()
645 OnConsoleOutputUpdated(nullptr, 0);
648 void TTYBackend::OnConsoleOutputTitleChanged()
650 std::unique_lock
<std::mutex
> lock(_async_mutex
);
651 _ae
.flags
.title_changed
= true;
652 _async_cond
.notify_all();
655 void TTYBackend::OnConsoleOutputWindowMoved(bool absolute
, COORD pos
)
659 COORD
TTYBackend::OnConsoleGetLargestWindowSize()
661 COORD out
= {CheckedCast
<SHORT
>(_cur_width
? _cur_width
: 0x10), CheckedCast
<SHORT
>(_cur_height
? _cur_height
: 0x10)};
664 if (_largest_window_size_ready
)
665 return _largest_window_size
;
668 StackSerializer stk_ser
;
669 stk_ser
.PushNum(FARTTY_INTERRACT_GET_WINDOW_MAXSIZE
);
670 if (Far2lInterract(stk_ser
, true)) {
671 stk_ser
.PopNum(out
.Y
);
672 stk_ser
.PopNum(out
.X
);
673 _largest_window_size
= out
;
674 _largest_window_size_ready
= true;
676 } catch(std::exception
&) {; }
682 bool TTYBackend::OnConsoleSetFKeyTitles(const char **titles
)
684 if (_fkeys_support
== FKS_NOT_SUPPORTED
) {
689 bool detect_support
= (_fkeys_support
== FKS_UNKNOWN
);
690 StackSerializer stk_ser
;
691 for (int i
= CONSOLE_FKEYS_COUNT
- 1; i
>= 0; --i
) {
692 unsigned char state
= (titles
!= NULL
&& titles
[i
] != NULL
) ? 1 : 0;
694 stk_ser
.PushStr(titles
[i
]);
696 stk_ser
.PushNum(state
);
698 stk_ser
.PushNum(FARTTY_INTERRACT_SET_FKEY_TITLES
);
700 if (Far2lInterract(stk_ser
, detect_support
)) {
701 if (detect_support
) {
702 bool supported
= false;
703 stk_ser
.PopNum(supported
);
704 fprintf(stderr
, "%s: %ssupported\n",
705 __FUNCTION__
, supported
? "" : "not ");
706 _fkeys_support
= supported
707 ? FKS_SUPPORTED
: FKS_NOT_SUPPORTED
;
711 } catch(std::exception
&e
) {
713 fprintf(stderr
, "%s: exception - %s\n", __FUNCTION__
, e
.what());
714 _fkeys_support
= FKS_NOT_SUPPORTED
;
717 return (_fkeys_support
== FKS_SUPPORTED
);
720 BYTE
TTYBackend::OnConsoleGetColorPalette()
726 if (_far2l_tty
) try {
727 StackSerializer stk_ser
;
728 stk_ser
.PushNum(FARTTY_INTERRACT_GET_COLOR_PALETTE
);
729 Far2lInterract(stk_ser
, true);
730 uint8_t bits
, reserved
;
731 stk_ser
.PopNum(bits
);
732 stk_ser
.PopNum(reserved
);
735 } catch (std::exception
&) {
739 static BYTE s_out
= []() {
740 const char *env
= getenv("COLORTERM");
742 return (strcmp(env
, "truecolor") == 0 || strcmp(env
, "24bit") == 0) ? 24 : 8;
744 env
= getenv("TERM");
745 if (env
&& strstr(env
, "256") != nullptr) {
754 void TTYBackend::OnConsoleAdhocQuickEdit()
757 StackSerializer stk_ser
;
758 stk_ser
.PushNum(FARTTY_INTERRACT_CONSOLE_ADHOC_QEDIT
);
759 Far2lInterract(stk_ser
, false);
760 } catch (std::exception
&) {}
763 DWORD64
TTYBackend::OnConsoleSetTweaks(DWORD64 tweaks
)
765 const auto prev_osc52clip_set
= _osc52clip_set
;
766 _osc52clip_set
= (tweaks
& CONSOLE_OSC52CLIP_SET
) != 0;
768 if (_osc52clip_set
!= prev_osc52clip_set
&& !_far2l_tty
&& !_ttyx
) {
769 ChooseSimpleClipboardBackend();
772 bool override_default_palette
= (tweaks
& CONSOLE_TTY_PALETTE_OVERRIDE
) != 0;
775 std::lock_guard
<std::mutex
> lock(_palette_mtx
);
776 std::swap(override_default_palette
, _override_default_palette
);
779 if (override_default_palette
!= ((tweaks
& CONSOLE_TTY_PALETTE_OVERRIDE
) != 0)) {
781 std::unique_lock
<std::mutex
> lock(_async_mutex
);
782 _ae
.flags
.palette
= true;
783 _async_cond
.notify_all();
784 while (_ae
.flags
.palette
) {
785 _async_cond
.wait(lock
);
792 DWORD64 out
= TWEAK_STATUS_SUPPORT_TTY_PALETTE
;
794 if (!_far2l_tty
&& !_ttyx
) {
795 out
|= TWEAK_STATUS_SUPPORT_OSC52CLIP_SET
;
801 void TTYBackend::OnConsoleOverrideColor(DWORD Index
, DWORD
*ColorFG
, DWORD
*ColorBK
)
803 if (Index
>= BASE_PALETTE_SIZE
) {
804 fprintf(stderr
, "%s: too big index=%u\n", __FUNCTION__
, Index
);
809 std::unique_lock
<std::mutex
> lock(_palette_mtx
);
810 if (_palette
.foreground
[Index
] == *ColorFG
&& _palette
.background
[Index
] == *ColorBK
) {
814 std::swap(_palette
.foreground
[Index
], *ColorFG
);
815 std::swap(_palette
.background
[Index
], *ColorBK
);
818 std::unique_lock
<std::mutex
> lock(_async_mutex
);
819 _ae
.flags
.palette
= true;
820 _async_cond
.notify_all();
821 while (_ae
.flags
.palette
) {
822 _async_cond
.wait(lock
);
826 void TTYBackend::OnConsoleChangeFont()
830 void TTYBackend::OnConsoleSaveWindowState()
834 void TTYBackend::OnConsoleSetMaximized(bool maximized
)
837 StackSerializer stk_ser
;
838 stk_ser
.PushNum(maximized
? FARTTY_INTERRACT_WINDOW_MAXIMIZE
: FARTTY_INTERRACT_WINDOW_RESTORE
);
839 Far2lInterract(stk_ser
, false);
840 } catch (std::exception
&) {}
843 void TTYBackend::ChooseSimpleClipboardBackend()
845 if (_ext_clipboard
) {
849 if (_osc52clip_set
) {
850 IOSC52Interractor
*interractor
= this;
851 _clipboard_backend_setter
.Set
<OSC52ClipboardBackend
>(interractor
);
853 _clipboard_backend_setter
.Set
<FSClipboardBackend
>();
857 void TTYBackend::OSC52SetClipboard(const char *text
)
859 fprintf(stderr
, "TTYBackend::OSC52SetClipboard\n");
860 std::unique_lock
<std::mutex
> lock(_async_mutex
);
862 _ae
.flags
.osc52clip_set
= true;
863 _async_cond
.notify_all();
866 bool TTYBackend::Far2lInterract(StackSerializer
&stk_ser
, bool wait
)
868 if (!_far2l_tty
|| _exiting
)
871 std::shared_ptr
<Far2lInterractData
> pfi
= std::make_shared
<Far2lInterractData
>();
872 pfi
->stk_ser
.Swap(stk_ser
);
876 std::unique_lock
<std::mutex
> lock(_async_mutex
);
877 _far2l_interracts_queued
.emplace_back(pfi
);
878 _ae
.flags
.far2l_interract
= 1;
879 _async_cond
.notify_all();
887 std::unique_lock
<std::mutex
> lock_sent(_far2l_interracts_sent
);
891 pfi
->stk_ser
.Swap(stk_ser
);
896 void TTYBackend::OnConsoleExit()
899 std::unique_lock
<std::mutex
> lock(_async_mutex
);
901 _async_cond
.notify_all();
906 bool TTYBackend::OnConsoleIsActive()
911 static void OnFar2lKey(bool down
, StackSerializer
&stk_ser
)
915 ir
.EventType
= KEY_EVENT
;
916 ir
.Event
.KeyEvent
.bKeyDown
= down
? TRUE
: FALSE
;
918 ir
.Event
.KeyEvent
.uChar
.UnicodeChar
= (wchar_t)stk_ser
.PopU32();
919 stk_ser
.PopNum(ir
.Event
.KeyEvent
.dwControlKeyState
);
920 stk_ser
.PopNum(ir
.Event
.KeyEvent
.wVirtualScanCode
);
921 stk_ser
.PopNum(ir
.Event
.KeyEvent
.wVirtualKeyCode
);
922 stk_ser
.PopNum(ir
.Event
.KeyEvent
.wRepeatCount
);
923 g_winport_con_in
->Enqueue(&ir
, 1);
925 } catch (std::exception
&) {
926 fprintf(stderr
, "OnFar2lKey: broken args!\n");
930 static void OnFar2lKeyCompact(bool down
, StackSerializer
&stk_ser
)
934 ir
.EventType
= KEY_EVENT
;
935 ir
.Event
.KeyEvent
.bKeyDown
= down
? TRUE
: FALSE
;
936 ir
.Event
.KeyEvent
.wRepeatCount
= 1;
937 ir
.Event
.KeyEvent
.uChar
.UnicodeChar
= (wchar_t)(uint32_t)stk_ser
.PopU16();
938 ir
.Event
.KeyEvent
.dwControlKeyState
= stk_ser
.PopU16();
939 ir
.Event
.KeyEvent
.wVirtualKeyCode
= stk_ser
.PopU8();
940 ir
.Event
.KeyEvent
.wVirtualScanCode
= WINPORT(MapVirtualKey
)(ir
.Event
.KeyEvent
.wVirtualKeyCode
, MAPVK_VK_TO_VSC
);
941 g_winport_con_in
->Enqueue(&ir
, 1);
943 } catch (std::exception
&e
) {
944 fprintf(stderr
, "OnFar2lKeyCompact: %s\n", e
.what());
948 static void OnFar2lMouse(bool compact
, StackSerializer
&stk_ser
)
952 ir
.EventType
= MOUSE_EVENT
;
955 ir
.Event
.MouseEvent
.dwEventFlags
= stk_ser
.PopU8();
956 ir
.Event
.MouseEvent
.dwControlKeyState
= stk_ser
.PopU8();
957 ir
.Event
.MouseEvent
.dwButtonState
= stk_ser
.PopU16();
959 ir
.Event
.MouseEvent
.dwButtonState
= (ir
.Event
.MouseEvent
.dwButtonState
& 0xff)
960 | ( (ir
.Event
.MouseEvent
.dwButtonState
& 0xff00) << 8);
963 stk_ser
.PopNum(ir
.Event
.MouseEvent
.dwEventFlags
);
964 stk_ser
.PopNum(ir
.Event
.MouseEvent
.dwControlKeyState
);
965 stk_ser
.PopNum(ir
.Event
.MouseEvent
.dwButtonState
);
968 stk_ser
.PopNum(ir
.Event
.MouseEvent
.dwMousePosition
.Y
);
969 stk_ser
.PopNum(ir
.Event
.MouseEvent
.dwMousePosition
.X
);
971 g_winport_con_in
->Enqueue(&ir
, 1);
973 } catch (std::exception
&) {
974 fprintf(stderr
, "OnFar2lMouse: broken args!\n");
978 void TTYBackend::OnUsingExtension(char extension
)
980 if (_using_extension
!= extension
) {
981 _using_extension
= extension
;
982 UpdateBackendIdentification();
986 void TTYBackend::OnInspectKeyEvent(KEY_EVENT_RECORD
&event
)
988 if (_ttyx
&& !_using_extension
) {
989 _ttyx
->InspectKeyEvent(event
);
992 event
.dwControlKeyState
|= QueryControlKeys();
995 if (!event
.wVirtualKeyCode
) {
996 if (event
.dwControlKeyState
& (LEFT_CTRL_PRESSED
| RIGHT_CTRL_PRESSED
| LEFT_ALT_PRESSED
| RIGHT_ALT_PRESSED
)) {
997 event
.wVirtualKeyCode
= WChar2WinVKeyCode(event
.uChar
.UnicodeChar
);
999 event
.wVirtualKeyCode
= VK_UNASSIGNED
;
1002 if (!event
.uChar
.UnicodeChar
&& IsEnhancedKey(event
.wVirtualKeyCode
)) {
1003 event
.dwControlKeyState
|= ENHANCED_KEY
;
1007 void TTYBackend::OnFar2lEvent(StackSerializer
&stk_ser
)
1010 fprintf(stderr
, "Far2lEvent unexpected!\n");
1014 char code
= stk_ser
.PopChar();
1017 case FARTTY_INPUT_MOUSE
: case FARTTY_INPUT_MOUSE_COMPACT
:
1018 OnFar2lMouse(code
== FARTTY_INPUT_MOUSE_COMPACT
, stk_ser
);
1021 case FARTTY_INPUT_KEYDOWN
: case FARTTY_INPUT_KEYUP
:
1022 OnFar2lKey(code
== FARTTY_INPUT_KEYDOWN
, stk_ser
);
1025 case FARTTY_INPUT_KEYDOWN_COMPACT
: case FARTTY_INPUT_KEYUP_COMPACT
:
1026 OnFar2lKeyCompact(code
== FARTTY_INPUT_KEYDOWN_COMPACT
, stk_ser
);
1030 fprintf(stderr
, "Far2lEvent unknown code=0x%x!\n", (unsigned int)(unsigned char)code
);
1034 void TTYBackend::OnFar2lReply(StackSerializer
&stk_ser
)
1037 fprintf(stderr
, "OnFar2lReply: unexpected!\n");
1044 std::unique_lock
<std::mutex
> lock_sent(_far2l_interracts_sent
);
1046 auto i
= _far2l_interracts_sent
.find(id
);
1047 if (i
!= _far2l_interracts_sent
.end()) {
1048 auto pfi
= i
->second
;
1049 _far2l_interracts_sent
.erase(i
);
1050 pfi
->stk_ser
.Swap(stk_ser
);
1055 void TTYBackend::OnInputBroken()
1057 std::unique_lock
<std::mutex
> lock_sent(_far2l_interracts_sent
);
1058 for (auto &i
: _far2l_interracts_sent
) {
1059 i
.second
->stk_ser
.Clear();
1060 i
.second
->evnt
.Signal();
1062 _far2l_interracts_sent
.clear();
1065 DWORD
TTYBackend::QueryControlKeys()
1070 unsigned char state
= 6;
1076 # define KG_SHIFTL 4
1077 # define KG_KANASHIFT 4
1078 # define KG_SHIFTR 5
1081 # define KG_CAPSSHIFT 8
1084 if (ioctl(_stdin
, TIOCLINUX
, &state
) == 0) {
1085 if (state
& ((1 << KG_SHIFT
) | (1 << KG_SHIFTL
) | (1 << KG_SHIFTR
))) {
1086 out
|= SHIFT_PRESSED
;
1088 if (state
& (1 << KG_CTRLL
)) {
1089 out
|= LEFT_CTRL_PRESSED
;
1091 if (state
& (1 << KG_CTRLR
)) {
1092 out
|= RIGHT_CTRL_PRESSED
;
1094 if ( (state
& (1 << KG_CTRL
)) != 0
1095 && ((state
& ((1 << KG_CTRLL
) | (1 << KG_CTRLR
))) == 0) ) {
1096 out
|= LEFT_CTRL_PRESSED
;
1099 if (state
& (1 << KG_ALTGR
)) {
1100 out
|= RIGHT_ALT_PRESSED
;
1102 else if (state
& (1 << KG_ALT
)) {
1103 out
|= LEFT_ALT_PRESSED
;
1108 #if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__linux__)
1109 unsigned long int leds
= 0;
1110 if (ioctl(_stdin
, KDGETLED
, &leds
) == 0) {
1112 out
|= SCROLLLOCK_ON
;
1125 void TTYBackend::OnConsoleDisplayNotification(const wchar_t *title
, const wchar_t *text
)
1128 StackSerializer stk_ser
;
1129 stk_ser
.PushStr(Wide2MB(text
));
1130 stk_ser
.PushStr(Wide2MB(title
));
1131 stk_ser
.PushNum(FARTTY_INTERRACT_DESKTOP_NOTIFICATION
);
1132 Far2lInterract(stk_ser
, false);
1133 } catch (std::exception
&) {}
1136 bool TTYBackend::OnConsoleBackgroundMode(bool TryEnterBackgroundMode
)
1138 if (_notify_pipe
== -1) {
1142 if (TryEnterBackgroundMode
) {
1143 std::unique_lock
<std::mutex
> lock(_async_mutex
);
1144 _ae
.flags
.go_background
= true;
1145 _async_cond
.notify_all();
1152 void TTYBackend_OnTerminalDamaged(bool flush_input_queue
)
1154 __sync_add_and_fetch ( &s_terminal_size_change_id
, 1);
1156 g_vtb
->KickAss(flush_input_queue
);
1160 static void OnSigWinch(int)
1162 TTYBackend_OnTerminalDamaged(false);
1166 static int g_std_in
= -1, g_std_out
= -1;
1167 static bool g_far2l_tty
= false;
1168 static struct termios g_ts_cont
{};
1171 static void OnSigTstp(int signo
)
1174 TTYNegotiateFar2l(g_std_in
, g_std_out
, false);
1176 tcgetattr(g_std_out
, &g_ts_cont
);
1181 static void OnSigCont(int signo
)
1183 tcsetattr(g_std_out
, TCSADRAIN
, &g_ts_cont
);
1185 TTYNegotiateFar2l(g_std_in
, g_std_out
, true);
1187 TTYBackend_OnTerminalDamaged(true);
1190 static void OnSigHup(int signo
)
1192 // drop sudo priviledges once pending sudo operation completes
1193 // leaving them is similar to leaving root console unattended
1196 FDScope
dev_null(open("/dev/null", O_RDWR
));
1197 if (dev_null
.Valid()) {
1198 // dup2(dev_null, 2);
1199 // dup2(dev_null, 1);
1200 // dup2(dev_null, 0);
1201 if (g_std_out
!= -1)
1202 dup2(dev_null
, g_std_out
);
1204 dup2(dev_null
, g_std_in
);
1207 g_vtb
->KickAss(true);
1212 bool WinPortMainTTY(const char *full_exe_path
, int std_in
, int std_out
, bool ext_clipboard
, bool norgb
, const char *nodetect
, bool far2l_tty
, unsigned int esc_expiration
, int notify_pipe
, int argc
, char **argv
, int(*AppMain
)(int argc
, char **argv
), int *result
)
1214 TTYBackend
vtb(full_exe_path
, std_in
, std_out
, ext_clipboard
, norgb
, nodetect
, far2l_tty
, esc_expiration
, notify_pipe
, result
);
1216 if (!vtb
.Startup()) {
1221 g_std_out
= std_out
;
1222 g_far2l_tty
= far2l_tty
;
1224 auto orig_winch
= signal(SIGWINCH
, OnSigWinch
);
1225 auto orig_tstp
= signal(SIGTSTP
, OnSigTstp
);
1226 auto orig_cont
= signal(SIGCONT
, OnSigCont
);
1227 auto orig_hup
= signal(SIGHUP
, (notify_pipe
!= -1) ? OnSigHup
: SIG_DFL
); // notify_pipe == -1 means --mortal specified
1229 *result
= 0; // set OK status for case if app will go background
1230 *result
= AppMain(argc
, argv
);
1232 signal(SIGHUP
, orig_hup
);
1233 signal(SIGCONT
, orig_tstp
);
1234 signal(SIGTSTP
, orig_cont
);
1235 signal(SIGWINCH
, orig_winch
);