/* * Copyright (c) 2012, 2013, 2014, 2015, 2016, 2021 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * logterminal.cpp * A simple terminal that writes to the kernel log. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "logterminal.h" #define CONTROL(x) (((x) - 64) & 127) #define M_CONTROL(x) (128 + CONTROL(x)) namespace Sortix { static const int MODIFIER_ALT = 1 << 0; static const int MODIFIER_LSHIFT = 1 << 1; static const int MODIFIER_RSHIFT = 1 << 2; static const int MODIFIER_LCONTROL = 1 << 3; static const int MODIFIER_RCONTROL = 1 << 4; static const int SEQUENCE_1IFMOD = 1 << 0; static const int SEQUENCE_OSHORT = 1 << 1; struct kbkey_sequence { const char* sequence; int kbkey; int flags; }; static const struct kbkey_sequence kbkey_sequences[] = { { "\e[A", KBKEY_UP, SEQUENCE_1IFMOD }, { "\e[B", KBKEY_DOWN, SEQUENCE_1IFMOD}, { "\e[C", KBKEY_RIGHT, SEQUENCE_1IFMOD }, { "\e[D", KBKEY_LEFT, SEQUENCE_1IFMOD }, { "\e[F", KBKEY_END, SEQUENCE_1IFMOD }, { "\e[H", KBKEY_HOME, SEQUENCE_1IFMOD }, { "\e[2~", KBKEY_INSERT, 0 }, { "\e[3~", KBKEY_DELETE, 0 }, { "\e[5~", KBKEY_PGUP, 0 }, { "\e[6~", KBKEY_PGDOWN, 0 }, { "\e[1P", KBKEY_F1, SEQUENCE_OSHORT }, { "\e[1Q", KBKEY_F2, SEQUENCE_OSHORT }, { "\e[1R", KBKEY_F3, SEQUENCE_OSHORT }, { "\e[1S", KBKEY_F4, SEQUENCE_OSHORT }, { "\e[15~", KBKEY_F5, 0 }, { "\e[17~", KBKEY_F6, 0 }, { "\e[18~", KBKEY_F7, 0 }, { "\e[19~", KBKEY_F8, 0 }, { "\e[20~", KBKEY_F9, 0 }, { "\e[21~", KBKEY_F10, 0 }, { "\e[23~", KBKEY_F11, 0 }, { "\e[24~", KBKEY_F12, 0 }, }; static inline const struct kbkey_sequence* LookupKeystrokeSequence(int kbkey) { size_t count = sizeof(kbkey_sequences) / sizeof(kbkey_sequences[0]); for ( size_t i = 0; i < count; i++ ) if ( kbkey_sequences[i].kbkey == kbkey ) return &kbkey_sequences[i]; return NULL; } LogTerminal::LogTerminal(dev_t dev, mode_t mode, uid_t owner, gid_t group, Keyboard* keyboard, KeyboardLayoutExecutor* kblayout, const char* name) : TTY(dev, 0, mode, owner, group, name) { this->keyboard = keyboard; this->kblayout = kblayout; this->modifiers = 0; this->report_cursor_offset = 0; keyboard->SetOwner(this, NULL); } LogTerminal::~LogTerminal() { delete keyboard; delete kblayout; } void LogTerminal::tty_output(const unsigned char* buffer, size_t length) { const char* report_cursor = "\e[6n"; while ( length ) { size_t amount = 0; bool found_report_cursor = false; while ( !found_report_cursor && amount < length ) { if ( buffer[amount++] == report_cursor[report_cursor_offset++] ) found_report_cursor = !report_cursor[report_cursor_offset]; else report_cursor_offset = 0; } Log::PrintData(buffer, amount); buffer += amount; length -= amount; if ( found_report_cursor ) { size_t column, row; Log::GetCursor(&column, &row); char buf[64]; snprintf(buf, sizeof(buf), "\e[%zu;%zuR", row + 1, column + 1); for ( size_t n = 0; buf[n]; n++ ) if ( !linebuffer.Push(buf[n]) ) break; CommitLineBuffer(); } } } int LogTerminal::sync(ioctx_t* /*ctx*/) { ScopedLock lock(&termlock); if ( hungup ) return errno = EIO, -1; return Log::Sync() ? 0 : -1; } void LogTerminal::OnKeystroke(Keyboard* kb, void* /*user*/) { ScopedLock lock(&termlock); while ( kb->HasPending() ) { int kbkey = kb->Read(); if ( kbkey == KBKEY_LALT ) modifiers |= MODIFIER_ALT; else if ( kbkey == -KBKEY_LALT ) modifiers &= ~MODIFIER_ALT; else if ( kbkey == KBKEY_LSHIFT ) modifiers |= MODIFIER_LSHIFT; else if ( kbkey == -KBKEY_LSHIFT ) modifiers &= ~MODIFIER_LSHIFT; else if ( kbkey == KBKEY_RSHIFT ) modifiers |= MODIFIER_RSHIFT; else if ( kbkey == -KBKEY_RSHIFT ) modifiers &= ~MODIFIER_RSHIFT; else if ( kbkey == KBKEY_LCTRL ) modifiers |= MODIFIER_LCONTROL; else if ( kbkey == -KBKEY_LCTRL ) modifiers &= ~MODIFIER_LCONTROL; else if ( kbkey == KBKEY_RCTRL ) modifiers |= MODIFIER_RCONTROL; else if ( kbkey == -KBKEY_RCTRL ) modifiers &= ~MODIFIER_RCONTROL; uint32_t unicode = kblayout->Translate(kbkey); if ( !(tio.c_cflag & CREAD) ) continue; ProcessKeystroke(kbkey); if ( !unicode ) continue; if ( unicode == '\n' ) unicode = '\r'; bool control = modifiers & (MODIFIER_LCONTROL | MODIFIER_RCONTROL); if ( !(tio.c_cflag & ISORTIX_TERMMODE) && unicode == '\b' ) unicode = 127; if ( modifiers & MODIFIER_ALT && !(tio.c_lflag & ISORTIX_KBKEY) ) ProcessByte('\e'); if ( control && unicode == L' ' ) ProcessByte(0, unicode); else if ( control && (L'`' <= unicode && unicode <= L'}') ) ProcessByte(unicode - L'`', unicode); else if ( control && (L'@' <= unicode && unicode <= L'_') ) ProcessByte(unicode - L'@', unicode); else if ( control && unicode == L'?' ) ProcessByte(127, unicode); else ProcessUnicode(unicode); } } void LogTerminal::ProcessKeystroke(int kbkey) { if ( tio.c_lflag & ISORTIX_32BIT ) { if ( tio.c_lflag & ISORTIX_KBKEY ) { uint32_t unikbkey = KBKEY_ENCODE(kbkey); if ( !linebuffer.Push(unikbkey) ) return; if ( !(tio.c_lflag & ICANON) ) CommitLineBuffer(); } return; } if ( kbkey < 0 ) return; if ( kbkey == KBKEY_ESC ) { ProcessByte('\e'); return; } const struct kbkey_sequence* seq = LookupKeystrokeSequence(kbkey); if ( !seq ) return; const char* str = seq->sequence; size_t len = strlen(str); int mods = 0; if ( modifiers & (MODIFIER_LSHIFT | MODIFIER_RSHIFT) ) mods |= 1; if ( modifiers & MODIFIER_ALT ) mods |= 2; if ( modifiers & (MODIFIER_LCONTROL | MODIFIER_RCONTROL) ) mods |= 4; if ( (seq->flags & SEQUENCE_OSHORT) && mods == 0 ) { ProcessByte('\e'); ProcessByte('O'); ProcessByte((unsigned char) str[len-1]); return; } for ( size_t i = 0; i < len - 1; i++ ) ProcessByte((unsigned char) str[i]); if ( seq->flags & SEQUENCE_1IFMOD && mods != 0 ) ProcessByte('1'); if ( mods ) { ProcessByte(';'); ProcessByte('1' + mods); } ProcessByte(str[len-1]); } ssize_t LogTerminal::tcgetblob(ioctx_t* ctx, const char* name, void* buffer, size_t count) { ScopedLock lock(&termlock); if ( hungup ) return errno = EIO, -1; if ( !name ) { static const char index[] = "kblayout\0"; size_t index_size = sizeof(index) - 1; if ( buffer && count < index_size ) return errno = ERANGE, -1; if ( buffer && !ctx->copy_to_dest(buffer, &index, index_size) ) return -1; return (ssize_t) index_size; } else if ( !strcmp(name, "kblayout") ) { const uint8_t* data; size_t size; if ( !kblayout->Download(&data, &size) ) return -1; if ( buffer && count < size ) return errno = ERANGE, -1; if ( buffer && !ctx->copy_to_dest(buffer, data, size) ) return -1; return (ssize_t) size; } else return errno = ENOENT, -1; } ssize_t LogTerminal::tcsetblob(ioctx_t* ctx, const char* name, const void* buffer, size_t count) { ScopedLock lock(&termlock); if ( hungup ) return errno = EIO, -1; if ( !name ) return errno = EPERM, -1; else if ( !strcmp(name, "kblayout") ) { uint8_t* data = new uint8_t[count]; if ( !data ) return -1; if ( !ctx->copy_from_src(data, buffer, count) ) return -1; if ( !kblayout->Upload(data, count) ) return -1; delete[] data; return (ssize_t) count; } else return errno = ENOENT, -1; } int LogTerminal::tcgetwincurpos(ioctx_t* ctx, struct wincurpos* wcp) { ScopedLock lock(&termlock); if ( hungup ) return errno = EIO, -1; struct wincurpos retwcp; memset(&retwcp, 0, sizeof(retwcp)); size_t cursor_column, cursor_row; Log::GetCursor(&cursor_column, &cursor_row); retwcp.wcp_col = cursor_column; retwcp.wcp_row = cursor_row; if ( !ctx->copy_to_dest(wcp, &retwcp, sizeof(retwcp)) ) return -1; return 0; } int LogTerminal::ioctl(ioctx_t* ctx, int cmd, uintptr_t arg) { ScopedLock lock(&termlock); if ( hungup ) return errno = EIO, -1; if ( cmd == TIOCGWINSZ ) { struct winsize* ws = (struct winsize*) arg; struct winsize retws; memset(&retws, 0, sizeof(retws)); retws.ws_col = Log::Width(); retws.ws_row = Log::Height(); if ( !ctx->copy_to_dest(ws, &retws, sizeof(retws)) ) return -1; return 0; } else if ( cmd == TIOCGDISPLAYS ) { struct tiocgdisplays* input = (struct tiocgdisplays*) arg; struct tiocgdisplays gdisplays; if ( !ctx->copy_from_src(&gdisplays, input, sizeof(gdisplays)) ) return -1; if ( 0 < gdisplays.count ) { struct tiocgdisplay display; memset(&display, 0, sizeof(display)); display.device = 0; display.connector = 0; if ( !ctx->copy_to_dest(gdisplays.displays, &display, sizeof(display)) ) return -1; } gdisplays.count = 1; if ( !ctx->copy_to_dest(input, &gdisplays, sizeof(gdisplays)) ) return -1; return 0; } lock.Reset(); return TTY::ioctl(ctx, cmd, arg); } } // namespace Sortix