Revert "tsset/getattr signal handling"
[far2l.git] / WinPort / src / Backend / TTY / TTYBackend.cpp
blob53cdd652be4d966cf95052bf9137a34db04b7669
1 #include <assert.h>
2 #include <errno.h>
3 #include <signal.h>
4 #include <fcntl.h>
5 #include <exception>
6 #include <sys/ioctl.h>
8 #ifdef __linux__
9 # include <termios.h>
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>
15 #endif
17 #include <os_call.hpp>
18 #include <ScopeHelpers.h>
19 #include <sudo.h>
20 #include "utils.h"
21 #include "CheckedCast.hpp"
22 #include "WinPortHandle.h"
23 #include "Backend.h"
24 #include "TTYBackend.h"
25 #include "TTYRevive.h"
26 #include "TTYFar2lClipboardBackend.h"
27 #include "TTYNegotiateFar2l.h"
28 #include "FarTTY.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')) {
48 return (WORD)wc;
50 if (wc >= L'a' && wc <= L'z') {
51 return (WORD)wc - (L'a' - L'A');
53 switch (wc) {
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);
78 return VK_UNASSIGNED;
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),
84 _stdin(std_in),
85 _stdout(std_out),
86 _ext_clipboard(ext_clipboard),
87 _norgb(norgb),
88 _nodetect(nodetect),
89 _far2l_tty(far2l_tty),
90 _esc_expiration(esc_expiration),
91 _notify_pipe(notify_pipe),
92 _result(result),
93 _largest_window_size_ready(false)
96 if (pipe_cloexec(_kickass) == -1) {
97 _kickass[0] = _kickass[1] = -1;
98 } else {
99 MakeFDNonBlocking(_kickass[1]);
102 struct winsize w{};
103 if (GetWinSize(w)) {
104 g_winport_con_out->SetSize(w.ws_col, w.ws_row);
106 g_winport_con_out->GetSize(_cur_width, _cur_height);
107 g_vtb = this;
110 TTYBackend::~TTYBackend()
112 if (g_vtb == this)
113 g_vtb = nullptr;
115 OnConsoleExit();
117 _exiting = 1;
119 if (_reader_trd) {
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);
124 _reader_trd = 0;
127 CheckedCloseFDPair(_kickass);
128 DetachNotifyPipe();
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");
138 return false;
142 return true;
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) {
168 return false;
171 return true;
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)) {
180 abort();
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('|');
194 if (_far2l_tty) {
195 AppendBackendIdentificationChar('F');
197 } else if (_ttyx || _using_extension) {
198 if (_ttyx) {
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;
214 while (!_exiting) {
215 _far2l_cursor_height = -1; // force cursor height update on next output dispatch
216 _fkeys_support = _far2l_tty ? FKS_UNKNOWN : FKS_NOT_SUPPORTED;
218 if (_far2l_tty) {
219 if (!prev_far2l_tty && !_ext_clipboard) {
220 IFar2lInterractor *interractor = this;
221 _clipboard_backend_setter.Set<TTYFar2lClipboardBackend>(interractor);
224 } else {
225 if (!strchr(_nodetect, 'x') || strstr(_nodetect, "xi")) {
226 _ttyx = StartTTYX(_full_exe_path, !strstr(_nodetect, "xi"));
228 if (_ttyx) {
229 if (!_ext_clipboard) {
230 _clipboard_backend_setter.Set<TTYXClipboard>(_ttyx);
233 } else {
234 ChooseSimpleClipboardBackend();
237 UpdateBackendIdentification();
238 prev_far2l_tty = _far2l_tty;
241 std::unique_lock<std::mutex> lock(_async_mutex);
242 _deadio = false;
243 _ae.flags.term_resized = true;
247 pthread_t writer_trd = 0;
248 if (pthread_create(&writer_trd, nullptr, sWriterThread, this) != 0) {
249 break;
252 try {
253 ReaderLoop();
255 } catch (const std::exception &e) {
256 fprintf(stderr, "ReaderLoop: %s <%d>\n", e.what(), errno);
258 OnUsingExtension(0);
260 OnInputBroken();
263 std::unique_lock<std::mutex> lock(_async_mutex);
264 _deadio = true;
265 _async_cond.notify_all();
268 pthread_join(writer_trd, nullptr);
269 DetachNotifyPipe();
270 _ttyx.reset();
272 while (!_exiting) {
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) {
276 break;
282 void TTYBackend::ReaderLoop()
284 std::unique_ptr<TTYInput> tty_in(new TTYInput(this));
286 fd_set fds, fde;
287 bool idle_expired = false;
288 while (!_exiting && !_deadio) {
289 int maxfd = (_kickass[0] > _stdin) ? _kickass[0] : _stdin;
291 FD_ZERO(&fds);
292 FD_ZERO(&fde);
293 FD_SET(_kickass[0], &fds);
294 FD_SET(_stdin, &fds);
295 FD_SET(_stdin, &fde);
297 int rs;
299 if (!idle_expired && _esc_expiration > 0 && !_far2l_tty) {
300 struct timeval tv;
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);
305 } else {
306 rs = os_call_int(select, maxfd + 1, &fds, (fd_set*)nullptr, &fde, (timeval*)nullptr);
309 if (rs == -1) {
310 throw std::runtime_error("select failed");
313 if (rs != 0) {
314 idle_expired = false;
316 } else if (!idle_expired) {
317 idle_expired = true;
318 tty_in->OnIdleExpired();
321 if (_flush_input_queue) {
322 _flush_input_queue = false;
323 tty_in.reset();
324 tty_in.reset(new TTYInput(this));
325 OnInputBroken();
328 if (FD_ISSET(_stdin, &fds)) {
329 char buf[0x1000];
330 ssize_t rd = os_call_ssize(read, _stdin, (void*)buf, sizeof(buf));
331 if (rd <= 0) {
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)) {
350 char cmd = 0;
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;
370 try {
371 TTYOutput tty_out(_stdout, _far2l_tty, _norgb);
372 DispatchPalette(tty_out);
373 // DispatchTermResized(tty_out);
374 while (!_exiting && !_deadio) {
375 AsyncEvent ae;
376 ae.all = 0;
377 do {
378 std::unique_lock<std::mutex> lock(_async_mutex);
379 if (_ae.all == 0) {
380 _async_cond.wait(lock);
382 if (_ae.all != 0) {
383 std::swap(ae.all, _ae.all);
384 if (ae.flags.palette) {
385 _async_cond.notify_all();
387 break;
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;
400 if (ae.flags.output)
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();
419 tty_out.Flush();
420 tcdrain(_stdout);
422 if (ae.flags.go_background) {
423 gone_background = true;
424 break;
428 } catch (const std::exception &e) {
429 fprintf(stderr, "WriterThread: %s <%d>\n", e.what(), errno);
431 _deadio = true;
433 if (gone_background) {
434 OnSigHup(SIGHUP);
439 /////////////////////////////////////////////////////////////////////////
441 void TTYBackend::DispatchPalette(TTYOutput &tty_out)
443 TTYBasePalette palette;
445 std::lock_guard<std::mutex> lock(_palette_mtx);
446 palette = _palette;
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)
463 struct winsize w{};
464 if (!GetWinSize(w)) {
465 return;
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;
494 #endif
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_])) {
512 return 4;
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) {
524 if (!Modified(x)) {
525 skipped_weight+= ApproxWeight(x);
526 continue;
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);
540 if (print_skipped) {
541 tty_out.WriteLine(&cur_line[skipped_start], x + 1 - skipped_start);
542 #ifdef LOG_OUTPUT_COUNT
543 printed_skipable+= x - skipped_start;
544 #endif
545 } else {
546 tty_out.MoveCursorLazy(y + 1, x + 1);
547 tty_out.WriteLine(&cur_line[x], 1);
549 #ifdef LOG_OUTPUT_COUNT
550 printed_count++;
551 #endif
552 skipped_start = x + 1;
553 skipped_weight = 0;
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());
561 #endif
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) {
590 uint8_t id = 0;
591 if (i->waited) {
592 if (_far2l_interracts_sent.size() >= 0xff) {
593 fprintf(stderr,
594 "TTYBackend::DispatchFar2lInterract: too many sent interracts - %ld\n",
595 _far2l_interracts_sent.size());
596 i->stk_ser.Clear();
597 i->evnt.Signal();
598 return;
600 for (;;) {
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);
607 if (i->waited)
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;
631 unsigned char c = 0;
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)};
663 if (_far2l_tty) {
664 if (_largest_window_size_ready)
665 return _largest_window_size;
667 try {
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 &) {; }
679 return out;
682 bool TTYBackend::OnConsoleSetFKeyTitles(const char **titles)
684 if (_fkeys_support == FKS_NOT_SUPPORTED) {
685 return false;
688 try {
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;
693 if (state != 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()
722 if (_norgb) {
723 return 4;
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);
733 return bits;
735 } catch (std::exception &) {
736 return 4;
739 static BYTE s_out = []() {
740 const char *env = getenv("COLORTERM");
741 if (env) {
742 return (strcmp(env, "truecolor") == 0 || strcmp(env, "24bit") == 0) ? 24 : 8;
744 env = getenv("TERM");
745 if (env && strstr(env, "256") != nullptr) {
746 return 8;
748 return 4;
749 } ();
751 return s_out;
754 void TTYBackend::OnConsoleAdhocQuickEdit()
756 try {
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;
798 return out;
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);
805 return;
809 std::unique_lock<std::mutex> lock(_palette_mtx);
810 if (_palette.foreground[Index] == *ColorFG && _palette.background[Index] == *ColorBK) {
811 return;
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)
836 try {
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) {
846 return;
849 if (_osc52clip_set) {
850 IOSC52Interractor *interractor = this;
851 _clipboard_backend_setter.Set<OSC52ClipboardBackend>(interractor);
852 } else {
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);
861 _osc52clip = text;
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)
869 return false;
871 std::shared_ptr<Far2lInterractData> pfi = std::make_shared<Far2lInterractData>();
872 pfi->stk_ser.Swap(stk_ser);
873 pfi->waited = wait;
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();
882 if (!wait)
883 return true;
885 pfi->evnt.Wait();
887 std::unique_lock<std::mutex> lock_sent(_far2l_interracts_sent);
888 if (_exiting)
889 return false;
891 pfi->stk_ser.Swap(stk_ser);
893 return true;
896 void TTYBackend::OnConsoleExit()
899 std::unique_lock<std::mutex> lock(_async_mutex);
900 _exiting = true;
901 _async_cond.notify_all();
903 KickAss();
906 bool TTYBackend::OnConsoleIsActive()
908 return false;//true;
911 static void OnFar2lKey(bool down, StackSerializer &stk_ser)
913 try {
914 INPUT_RECORD ir {};
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)
932 try {
933 INPUT_RECORD ir {};
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)
950 try {
951 INPUT_RECORD ir {};
952 ir.EventType = MOUSE_EVENT;
954 if (compact) {
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);
962 } else {
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);
991 } else {
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);
998 } else {
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)
1009 if (!_far2l_tty) {
1010 fprintf(stderr, "Far2lEvent unexpected!\n");
1011 return;
1014 char code = stk_ser.PopChar();
1016 switch (code) {
1017 case FARTTY_INPUT_MOUSE: case FARTTY_INPUT_MOUSE_COMPACT:
1018 OnFar2lMouse(code == FARTTY_INPUT_MOUSE_COMPACT, stk_ser);
1019 break;
1021 case FARTTY_INPUT_KEYDOWN: case FARTTY_INPUT_KEYUP:
1022 OnFar2lKey(code == FARTTY_INPUT_KEYDOWN, stk_ser);
1023 break;
1025 case FARTTY_INPUT_KEYDOWN_COMPACT: case FARTTY_INPUT_KEYUP_COMPACT:
1026 OnFar2lKeyCompact(code == FARTTY_INPUT_KEYDOWN_COMPACT, stk_ser);
1027 break;
1029 default:
1030 fprintf(stderr, "Far2lEvent unknown code=0x%x!\n", (unsigned int)(unsigned char)code);
1034 void TTYBackend::OnFar2lReply(StackSerializer &stk_ser)
1036 if (!_far2l_tty) {
1037 fprintf(stderr, "OnFar2lReply: unexpected!\n");
1038 return;
1041 uint8_t id;
1042 stk_ser.PopNum(id);
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);
1051 pfi->evnt.Signal();
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()
1067 DWORD out = 0;
1069 #ifdef __linux__
1070 unsigned char state = 6;
1071 /* #ifndef KG_SHIFT
1072 # define KG_SHIFT 0
1073 # define KG_CTRL 2
1074 # define KG_ALT 3
1075 # define KG_ALTGR 1
1076 # define KG_SHIFTL 4
1077 # define KG_KANASHIFT 4
1078 # define KG_SHIFTR 5
1079 # define KG_CTRLL 6
1080 # define KG_CTRLR 7
1081 # define KG_CAPSSHIFT 8
1082 #endif */
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;
1106 #endif
1108 #if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__linux__)
1109 unsigned long int leds = 0;
1110 if (ioctl(_stdin, KDGETLED, &leds) == 0) {
1111 if (leds & 1) {
1112 out|= SCROLLLOCK_ON;
1114 if (leds & 2) {
1115 out|= NUMLOCK_ON;
1117 if (leds & 4) {
1118 out|= CAPSLOCK_ON;
1121 #endif
1122 return out;
1125 void TTYBackend::OnConsoleDisplayNotification(const wchar_t *title, const wchar_t *text)
1127 try {
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) {
1139 return false;
1142 if (TryEnterBackgroundMode) {
1143 std::unique_lock<std::mutex> lock(_async_mutex);
1144 _ae.flags.go_background = true;
1145 _async_cond.notify_all();
1148 return true;
1152 void TTYBackend_OnTerminalDamaged(bool flush_input_queue)
1154 __sync_add_and_fetch ( &s_terminal_size_change_id, 1);
1155 if (g_vtb) {
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)
1173 if (g_far2l_tty)
1174 TTYNegotiateFar2l(g_std_in, g_std_out, false);
1176 tcgetattr(g_std_out, &g_ts_cont);
1177 raise(SIGSTOP);
1181 static void OnSigCont(int signo)
1183 tcsetattr(g_std_out, TCSADRAIN, &g_ts_cont );
1184 if (g_far2l_tty)
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
1194 sudo_client_drop();
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);
1203 if (g_std_in != -1)
1204 dup2(dev_null, g_std_in);
1206 if (g_vtb) {
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()) {
1217 return false;
1220 g_std_in = std_in;
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);
1237 return true;