diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f46efc964e..9991fc8d35 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -304,7 +304,7 @@ jobs: uses: lukka/run-vcpkg@v5 id: runvcpkg with: - vcpkgArguments: angle fmt freetype fontconfig harfbuzz yaml-cpp range-v3 + vcpkgArguments: fmt freetype fontconfig harfbuzz yaml-cpp range-v3 vcpkgDirectory: ${{ runner.workspace }}/vcpkg/ vcpkgGitCommitId: faed44dfa013088fe1910908a8a65887622f412f vcpkgTriplet: x64-windows diff --git a/Changelog.md b/Changelog.md index 94bea6ca1a..1adef98808 100644 --- a/Changelog.md +++ b/Changelog.md @@ -27,6 +27,8 @@ - `` and `` don't just move the cursor up/down but also move the terminal's viewport respectively. - and more... - Adds specialized PTY implementation for Linux operating system utilizing OS-specific kernel APIs. +- Adds basic support for Indicator status line and their VT sequences `DECSASD` and `DECSSDT`, and `DECRQSS` has been adapted (#687). +- Adds configuration option `profiles.*.status_line.display` to be either `none` or `indicator` to reflect the initial state of the status line (more customizability of the Indicator status-line will come in future releases). - Changes CLI syntax for `contour parser-table` to `contour generate parser-table`. ### 0.3.1 (2022-05-01) diff --git a/src/contour/Actions.cpp b/src/contour/Actions.cpp index 314c3b5d5c..be33d92c0e 100644 --- a/src/contour/Actions.cpp +++ b/src/contour/Actions.cpp @@ -76,6 +76,7 @@ optional fromString(string const& _name) mapAction("SendChars"), mapAction("ToggleAllKeyMaps"), mapAction("ToggleFullscreen"), + mapAction("ToggleStatusLine"), mapAction("ToggleTitleBar"), mapAction("ViNormalMode"), mapAction("WriteScreen"), diff --git a/src/contour/Actions.h b/src/contour/Actions.h index 6c272b4558..4882929364 100644 --- a/src/contour/Actions.h +++ b/src/contour/Actions.h @@ -56,6 +56,7 @@ struct ScrollUp{}; struct SendChars{ std::string chars; }; struct ToggleAllKeyMaps{}; struct ToggleFullscreen{}; +struct ToggleStatusLine{}; struct ToggleTitleBar{}; struct ViNormalMode{}; struct WriteScreen{ std::string chars; }; // "\033[2J\033[3J" @@ -98,6 +99,7 @@ using Action = std::variant; @@ -159,6 +161,7 @@ DECLARE_ACTION_FMT(ScrollUp) DECLARE_ACTION_FMT(SendChars) DECLARE_ACTION_FMT(ToggleAllKeyMaps) DECLARE_ACTION_FMT(ToggleFullscreen) +DECLARE_ACTION_FMT(ToggleStatusLine) DECLARE_ACTION_FMT(ToggleTitleBar) DECLARE_ACTION_FMT(ViNormalMode) DECLARE_ACTION_FMT(WriteScreen) diff --git a/src/contour/Config.cpp b/src/contour/Config.cpp index 09097efd6a..9816aa2b30 100644 --- a/src/contour/Config.cpp +++ b/src/contour/Config.cpp @@ -1544,6 +1544,14 @@ TerminalProfile loadTerminalProfile(UsedKeys& _usedKeys, } } + tryLoadChildRelative(_usedKeys, _profile, basePath, "status_line.display", strValue); + if (strValue == "indicator") + profile.initialStatusDisplayType = terminal::StatusDisplayType::Indicator; + else if (strValue == "none") + profile.initialStatusDisplayType = terminal::StatusDisplayType::None; + else + errorlog()("Invalid value for config entry {}: {}", "status_line.display", strValue); + return profile; } diff --git a/src/contour/Config.h b/src/contour/Config.h index ae4b9359d5..96ccb0010b 100644 --- a/src/contour/Config.h +++ b/src/contour/Config.h @@ -179,6 +179,8 @@ struct TerminalProfile InputModeConfig visual; } inputModes; + terminal::StatusDisplayType initialStatusDisplayType = terminal::StatusDisplayType::None; + terminal::Opacity backgroundOpacity; // value between 0 (fully transparent) and 0xFF (fully visible). bool backgroundBlur; // On Windows 10, this will enable Acrylic Backdrop. diff --git a/src/contour/TerminalSession.cpp b/src/contour/TerminalSession.cpp index 4fb666d002..adf8bf77f0 100644 --- a/src/contour/TerminalSession.cpp +++ b/src/contour/TerminalSession.cpp @@ -419,7 +419,7 @@ void TerminalSession::onSelectionCompleted() } } -void TerminalSession::resizeWindow(LineCount _lines, ColumnCount _columns) +void TerminalSession::requestWindowResize(LineCount _lines, ColumnCount _columns) { if (!display_) return; @@ -428,7 +428,7 @@ void TerminalSession::resizeWindow(LineCount _lines, ColumnCount _columns) display_->post([this, _lines, _columns]() { display_->resizeWindow(_lines, _columns); }); } -void TerminalSession::resizeWindow(Width _width, Height _height) +void TerminalSession::requestWindowResize(Width _width, Height _height) { if (!display_) return; @@ -554,6 +554,9 @@ void TerminalSession::sendMouseMoveEvent(terminal::Modifier _modifier, // NB: This translation depends on the display's margin, so maybe // the display should provide the translation? + if (!(_pos < terminal().pageSize())) + return; + auto const handled = terminal().sendMouseMoveEvent(_modifier, _pos, _pixelPosition, _now); if (_pos == currentMousePosition_) @@ -867,6 +870,16 @@ bool TerminalSession::operator()(actions::ToggleFullscreen) return true; } +bool TerminalSession::operator()(actions::ToggleStatusLine) +{ + auto const _l = scoped_lock { terminal_ }; + if (terminal().state().statusDisplayType != StatusDisplayType::Indicator) + terminal().setStatusDisplay(StatusDisplayType::Indicator); + else + terminal().setStatusDisplay(StatusDisplayType::None); + return true; +} + bool TerminalSession::operator()(actions::ToggleTitleBar) { if (display_) @@ -1009,6 +1022,7 @@ void TerminalSession::configureTerminal() terminal_.setMaxImageColorRegisters(config_.maxImageColorRegisters); terminal_.setMaxImageSize(config_.maxImageSize); terminal_.setMode(terminal::DECMode::SixelScrolling, config_.sixelScrolling); + terminal_.setStatusDisplay(profile_.initialStatusDisplayType); SessionLog()("maxImageSize={}, sixelScrolling={}", config_.maxImageSize, config_.sixelScrolling); // XXX diff --git a/src/contour/TerminalSession.h b/src/contour/TerminalSession.h index 6cb509b530..d6a9c10237 100644 --- a/src/contour/TerminalSession.h +++ b/src/contour/TerminalSession.h @@ -100,8 +100,8 @@ class TerminalSession: public QObject, public terminal::Terminal::Events void onClosed() override; void pasteFromClipboard(unsigned count) override; void onSelectionCompleted() override; - void resizeWindow(terminal::LineCount, terminal::ColumnCount) override; - void resizeWindow(terminal::Width, terminal::Height) override; + void requestWindowResize(terminal::LineCount, terminal::ColumnCount) override; + void requestWindowResize(terminal::Width, terminal::Height) override; void setWindowTitle(std::string_view _title) override; void setTerminalProfile(std::string const& _configProfileName) override; void discardImage(terminal::Image const&) override; @@ -162,6 +162,7 @@ class TerminalSession: public QObject, public terminal::Terminal::Events bool operator()(actions::SendChars const& _event); bool operator()(actions::ToggleAllKeyMaps); bool operator()(actions::ToggleFullscreen); + bool operator()(actions::ToggleStatusLine); bool operator()(actions::ToggleTitleBar); bool operator()(actions::ViNormalMode); bool operator()(actions::WriteScreen const& _event); diff --git a/src/contour/contour.yml b/src/contour/contour.yml index 39fa121cc6..2df88080d1 100644 --- a/src/contour/contour.yml +++ b/src/contour/contour.yml @@ -341,6 +341,12 @@ profiles: blinking: false blinking_interval: 500 + status_line: + # Either none or indicator. + # This only reflects the initial state of the status line, as it can + # be changed at any time during runtime by the user or by an application. + display: none + # Background configuration background: # Background opacity to use. A value of 1.0 means fully opaque whereas 0.0 means fully @@ -533,6 +539,7 @@ color_schemes: # - SendChars Writes given characters in `chars` member to the applications input. # - ToggleAllKeyMaps Disables/enables responding to all keybinds (this keybind will be preserved when disabling all others). # - ToggleFullScreen Enables/disables full screen mode. +# - ToggleStatusLine Shows/hides the VT320 compatible Indicator status line. # - ToggleTitleBar Shows/Hides titlebar # - ViNormalMode Enters Vi-like normal mode. The cursor can then be moved via h/j/k/l movements and text can be selected via v, yanked via y, and clipboard pasted via p. # - WriteScreen Writes VT sequence in `chars` member to the screen (bypassing the application). @@ -574,3 +581,4 @@ input_mapping: - { mods: [Shift], mouse: WheelDown, action: ScrollPageDown } - { mods: [Shift], mouse: WheelUp, action: ScrollPageUp } - { mods: [Control, Alt], key: O, action: OpenFileManager } + - { mods: [Control, Alt], key: '.', action: ToggleStatusLine } diff --git a/src/contour/helper.cpp b/src/contour/helper.cpp index fcb0a0c444..121e06edc8 100644 --- a/src/contour/helper.cpp +++ b/src/contour/helper.cpp @@ -68,7 +68,7 @@ namespace auto constexpr MarginTop = 0; auto constexpr MarginLeft = 0; - auto const pageSize = session.terminal().pageSize(); + auto const pageSize = session.terminal().totalPageSize(); auto const cellSize = session.display()->cellSize(); auto const dpr = session.contentScale(); diff --git a/src/crispy/BufferObject.h b/src/crispy/BufferObject.h index 5cc1aedf14..b47762e0d0 100644 --- a/src/crispy/BufferObject.h +++ b/src/crispy/BufferObject.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #define BUFFER_OBJECT_INLINE 1 @@ -100,6 +101,9 @@ class BufferObject: public std::enable_shared_from_this void clear() noexcept; + void lock() { mutex_.lock(); } + void unlock() { mutex_.unlock(); } + private: #if !defined(BUFFER_OBJECT_INLINE) char* data_; @@ -108,6 +112,8 @@ class BufferObject: public std::enable_shared_from_this char* end_; friend class BufferFragment; + + std::mutex mutex_; }; /** diff --git a/src/terminal/Functions.h b/src/terminal/Functions.h index fb4caaa09e..60fec39abc 100644 --- a/src/terminal/Functions.h +++ b/src/terminal/Functions.h @@ -347,6 +347,9 @@ constexpr inline auto XTSHIFTESCAPE=detail::CSI('>', 0, 1, std::nullopt, 's', VT constexpr inline auto XTVERSION = detail::CSI('>', 0, 1, std::nullopt, 'q', VTType::VT525 /*Xterm*/, "XTVERSION", "Query terminal name and version"); constexpr inline auto CAPTURE = detail::CSI('>', 0, 2, std::nullopt, 't', VTType::VT525 /*Extension*/, "CAPTURE", "Report screen buffer capture."); +constexpr inline auto DECSSDT = detail::CSI(std::nullopt, 0, 1, '$', '~', VTType::VT320, "DECSSDT", "Select Status Display (Line) Type"); +constexpr inline auto DECSASD = detail::CSI(std::nullopt, 0, 1, '$', '}', VTType::VT420, "DECSASD", "Select Active Status Display"); + // DCS functions constexpr inline auto STP = detail::DCS(std::nullopt, 0, 0, '$', 'p', VTType::VT525, "STP", "Set Terminal Profile"); constexpr inline auto DECRQSS = detail::DCS(std::nullopt, 0, 0, '$', 'q', VTType::VT420, "DECRQSS", "Request Status String"); @@ -447,9 +450,9 @@ inline auto const& functions() noexcept DCH, DECCARA, DECCRA, + DECDC, DECERA, DECFRA, - DECDC, DECIC, DECMODERESTORE, DECMODESAVE, @@ -457,12 +460,14 @@ inline auto const& functions() noexcept DECRQM, DECRQM_ANSI, DECRQPSR, + DECSASD, DECSCL, DECSCPP, DECSCUSR, DECSLRM, DECSM, DECSNLS, + DECSSDT, DECSTBM, DECSTR, DECXCPR, @@ -486,8 +491,8 @@ inline auto const& functions() noexcept TBC, VPA, WINMANIP, - XTSMGRAPHICS, XTSHIFTESCAPE, + XTSMGRAPHICS, XTVERSION, // DCS diff --git a/src/terminal/RenderBufferBuilder.cpp b/src/terminal/RenderBufferBuilder.cpp index 3c1c223cc1..c71a45c0c1 100644 --- a/src/terminal/RenderBufferBuilder.cpp +++ b/src/terminal/RenderBufferBuilder.cpp @@ -93,14 +93,18 @@ namespace } // namespace template -RenderBufferBuilder::RenderBufferBuilder(Terminal const& _terminal, RenderBuffer& _output): +RenderBufferBuilder::RenderBufferBuilder(Terminal const& _terminal, + RenderBuffer& _output, + LineOffset base, + bool theReverseVideo): output { _output }, terminal { _terminal }, cursorPosition { _terminal.inputHandler().mode() == ViMode::Insert ? _terminal.realCursorPosition() - : _terminal.state().viCommands.cursorPosition } + : _terminal.state().viCommands.cursorPosition }, + baseLine { base }, + reverseVideo { theReverseVideo } { - output.clear(); output.frameID = _terminal.lastFrameID(); output.cursor = renderCursor(); } @@ -275,7 +279,7 @@ void RenderBufferBuilder::renderTrivialLine(TriviallyStyledLineBuffer cons fg, bg, lineBuffer.attributes.underlineColor, - lineOffset, + baseLine + lineOffset, columnOffset)); columnOffset += ColumnOffset::cast_from(width); @@ -301,7 +305,7 @@ void RenderBufferBuilder::renderTrivialLine(TriviallyStyledLineBuffer cons fg, bg, lineBuffer.attributes.underlineColor, - lineOffset, + baseLine + lineOffset, columnOffset)); } // }}} @@ -355,7 +359,7 @@ void RenderBufferBuilder::renderCell(Cell const& screenCell, LineOffset _l screenCell, fg, bg, - _line, + baseLine + _line, _column)); output.cells.back().groupStart = true; } @@ -373,7 +377,7 @@ void RenderBufferBuilder::renderCell(Cell const& screenCell, LineOffset _l screenCell, fg, bg, - _line, + baseLine + _line, _column)); if (isNewLine) diff --git a/src/terminal/RenderBufferBuilder.h b/src/terminal/RenderBufferBuilder.h index 4625ee91e5..2a72617e06 100644 --- a/src/terminal/RenderBufferBuilder.h +++ b/src/terminal/RenderBufferBuilder.h @@ -15,7 +15,7 @@ template class RenderBufferBuilder { public: - RenderBufferBuilder(Terminal const& terminal, RenderBuffer& output); + RenderBufferBuilder(Terminal const& terminal, RenderBuffer& output, LineOffset base, bool reverseVideo); /// Renders a single grid cell. /// This call is guaranteed to be invoked sequencially, from top line @@ -87,8 +87,9 @@ class RenderBufferBuilder RenderBuffer& output; Terminal const& terminal; CellLocation cursorPosition; + LineOffset baseLine; + bool reverseVideo; - bool reverseVideo = terminal.isModeEnabled(terminal::DECMode::ReverseVideo); int prevWidth = 0; bool prevHasCursor = false; State state = State::Gap; diff --git a/src/terminal/Screen.cpp b/src/terminal/Screen.cpp index e5a0117db3..727f61e381 100644 --- a/src/terminal/Screen.cpp +++ b/src/terminal/Screen.cpp @@ -237,8 +237,8 @@ namespace // {{{ helper // }}} template -Screen::Screen(TerminalState& terminalState, ScreenType screenType, Grid& grid): - _terminal { terminalState.terminal }, _state { terminalState }, _screenType { screenType }, _grid { grid } +Screen::Screen(TerminalState& terminalState, Grid& grid): + _terminal { terminalState.terminal }, _state { terminalState }, _grid { grid } { updateCursorIterator(); } @@ -425,6 +425,18 @@ void Screen::writeText(string_view _chars, size_t cellCount writeTextInternal(static_cast(ch)); } +template +void Screen::writeTextFromExternal(std::string_view _chars) +{ +#if defined(LIBTERMINAL_LOG_TRACE) + if (VTTraceSequenceLog) + VTTraceSequenceLog()("external text: \"{}\"", _chars); +#endif + + for (char const ch: _chars) + writeTextInternal(static_cast(ch)); +} + template void Screen::crlfIfWrapPending() { @@ -1800,6 +1812,21 @@ void Screen::requestStatusString(RequestStatusString _value case RequestStatusString::DECSCA: // TODO errorlog()(fmt::format("Requesting device status for {} not implemented yet.", _value)); break; + case RequestStatusString::DECSASD: + switch (_state.activeStatusDisplay) + { + case ActiveStatusDisplay::Main: return "0$}"; + case ActiveStatusDisplay::StatusLine: return "1$}"; + } + break; + case RequestStatusString::DECSSDT: + switch (_state.statusDisplayType) + { + case StatusDisplayType::None: return "0$~"; + case StatusDisplayType::Indicator: return "1$~"; + case StatusDisplayType::HostWritable: return "2$~"; + } + break; } return nullopt; }(_value); @@ -2799,11 +2826,11 @@ namespace impl switch (_seq.param(0)) { case 4: // resize in pixel units - terminal.resizeWindow(ImageSize { Width(_seq.param(2)), Height(_seq.param(1)) }); + terminal.requestWindowResize(ImageSize { Width(_seq.param(2)), Height(_seq.param(1)) }); break; case 8: // resize in cell units - terminal.resizeWindow(PageSize { LineCount::cast_from(_seq.param(1)), - ColumnCount::cast_from(_seq.param(2)) }); + terminal.requestWindowResize(PageSize { LineCount::cast_from(_seq.param(1)), + ColumnCount::cast_from(_seq.param(2)) }); break; case 22: terminal.saveWindowTitle(); break; case 23: terminal.restoreWindowTitle(); break; @@ -2819,7 +2846,7 @@ namespace impl case 8: // this means, resize to full display size // TODO: just create a dedicated callback for fulscreen resize! - terminal.resizeWindow(ImageSize {}); + terminal.requestWindowResize(ImageSize {}); break; case 14: if (_seq.parameterCount() == 2 && _seq.param(1) == 2) @@ -3261,6 +3288,26 @@ ApplyResult Screen::apply(FunctionDefinition const& functio case XTVERSION: _terminal.reply(fmt::format("\033P>|{} {}\033\\", LIBTERMINAL_NAME, LIBTERMINAL_VERSION_STRING)); return ApplyResult::Ok; + case DECSSDT: { + // Changes the status line display type. + switch (seq.param_or(0, 0)) + { + case 0: _terminal.setStatusDisplay(StatusDisplayType::None); break; + case 1: _terminal.setStatusDisplay(StatusDisplayType::Indicator); break; + case 2: _terminal.setStatusDisplay(StatusDisplayType::HostWritable); break; + default: return ApplyResult::Invalid; + } + break; + } + case DECSASD: + // Selects whether the terminal sends data to the main display or the status line. + switch (seq.param_or(0, 0)) + { + case 0: _terminal.setActiveStatusDisplay(ActiveStatusDisplay::Main); break; + case 1: _terminal.setActiveStatusDisplay(ActiveStatusDisplay::StatusLine); break; + default: return ApplyResult::Invalid; + } + break; // OSC case SETTITLE: @@ -3404,11 +3451,12 @@ unique_ptr Screen::hookDECRQSS(Sequence co { return make_unique([this](string_view const& _data) { auto const s = [](string_view _dataString) -> optional { - auto const mappings = array, 9> { + auto const mappings = array, 11> { pair { "m", RequestStatusString::SGR }, pair { "\"p", RequestStatusString::DECSCL }, pair { " q", RequestStatusString::DECSCUSR }, pair { "\"q", RequestStatusString::DECSCA }, pair { "r", RequestStatusString::DECSTBM }, pair { "s", RequestStatusString::DECSLRM }, pair { "t", RequestStatusString::DECSLPP }, pair { "$|", RequestStatusString::DECSCPP }, + pair { "$}", RequestStatusString::DECSASD }, pair { "$~", RequestStatusString::DECSSDT }, pair { "*|", RequestStatusString::DECSNLS } }; for (auto const& mapping: mappings) diff --git a/src/terminal/Screen.h b/src/terminal/Screen.h index 11eaed0d2f..2212d540d7 100644 --- a/src/terminal/Screen.h +++ b/src/terminal/Screen.h @@ -62,6 +62,7 @@ class ScreenBase: public SequenceHandler [[nodiscard]] virtual bool contains(CellLocation _coord) const noexcept = 0; [[nodiscard]] virtual bool isCellEmpty(CellLocation position) const noexcept = 0; [[nodiscard]] virtual bool compareCellTextAt(CellLocation position, char codepoint) const noexcept = 0; + [[nodiscard]] virtual std::string cellTextAt(CellLocation position) const noexcept = 0; [[nodiscard]] virtual bool isLineEmpty(LineOffset line) const noexcept = 0; [[nodiscard]] virtual uint8_t cellWithAt(CellLocation position) const noexcept = 0; [[nodiscard]] virtual LineCount historyLineCount() const noexcept = 0; @@ -69,6 +70,8 @@ class ScreenBase: public SequenceHandler [[nodiscard]] virtual std::shared_ptr hyperlinkAt( CellLocation pos) const noexcept = 0; virtual void inspect(std::string const& _message, std::ostream& _os) const = 0; + virtual void moveCursorTo(LineOffset line, ColumnOffset column) = 0; // CUP + virtual void updateCursorIterator() noexcept = 0; }; //#define LIBTERMINAL_CURRENT_LINE_CACHE 1 @@ -82,13 +85,10 @@ class ScreenBase: public SequenceHandler * to be viewn. */ template -class Screen: public ScreenBase, public capabilities::StaticDatabase +class Screen final: public ScreenBase, public capabilities::StaticDatabase { public: - constexpr static bool IsPrimaryScreen = TheScreenType == ScreenType::Primary; - constexpr static bool IsAlternateScreen = TheScreenType == ScreenType::Alternate; - - Screen(TerminalState& terminalState, ScreenType screenType, Grid& grid); + Screen(TerminalState& terminalState, Grid& grid); Screen(Screen const&) = delete; Screen& operator=(Screen const&) = delete; @@ -106,6 +106,8 @@ class Screen: public ScreenBase, public capabilities::StaticDatabase void processSequence(Sequence const& seq) override; // }}} + void writeTextFromExternal(std::string_view _chars); + /// Renders the full screen by passing every grid cell to the callback. template void render(Renderer&& _render, ScrollOffset _scrollOffset = {}) const @@ -156,17 +158,17 @@ class Screen: public ScreenBase, public capabilities::StaticDatabase void backIndex(); // DECBI void forwardIndex(); // DECFI - void moveCursorTo(LineOffset line, ColumnOffset column); // CUP - void moveCursorBackward(ColumnCount _n); // CUB - void moveCursorDown(LineCount _n); // CUD - void moveCursorForward(ColumnCount _n); // CUF - void moveCursorToBeginOfLine(); // CR - void moveCursorToColumn(ColumnOffset _n); // CHA - void moveCursorToLine(LineOffset _n); // VPA - void moveCursorToNextLine(LineCount _n); // CNL - void moveCursorToNextTab(); // HT - void moveCursorToPrevLine(LineCount _n); // CPL - void moveCursorUp(LineCount _n); // CUU + void moveCursorTo(LineOffset line, ColumnOffset column) override; // CUP + void moveCursorBackward(ColumnCount _n); // CUB + void moveCursorDown(LineCount _n); // CUD + void moveCursorForward(ColumnCount _n); // CUF + void moveCursorToBeginOfLine(); // CR + void moveCursorToColumn(ColumnOffset _n); // CHA + void moveCursorToLine(LineOffset _n); // VPA + void moveCursorToNextLine(LineCount _n); // CNL + void moveCursorToNextTab(); // HT + void moveCursorToPrevLine(LineCount _n); // CPL + void moveCursorUp(LineCount _n); // CUU void cursorBackwardTab(TabStopCount _n); // CBT void cursorForwardTab(TabStopCount _n); // CHT @@ -253,7 +255,7 @@ class Screen: public ScreenBase, public capabilities::StaticDatabase void requestAnsiMode(unsigned int _mode); void requestDECMode(unsigned int _mode); - [[nodiscard]] PageSize pageSize() const noexcept { return _state.pageSize; } + [[nodiscard]] PageSize pageSize() const noexcept { return _grid.pageSize(); } [[nodiscard]] ImageSize pixelSize() const noexcept { return _state.cellPixelSize * _state.pageSize; } constexpr CellLocation realCursorPosition() const noexcept { return _state.cursor.position; } @@ -305,12 +307,12 @@ class Screen: public ScreenBase, public capabilities::StaticDatabase [[nodiscard]] LineOffset clampedLine(LineOffset _line) const noexcept { - return std::clamp(_line, LineOffset(0), boxed_cast(_state.pageSize.lines) - 1); + return std::clamp(_line, LineOffset(0), boxed_cast(_grid.pageSize().lines) - 1); } [[nodiscard]] ColumnOffset clampedColumn(ColumnOffset _column) const noexcept { - return std::clamp(_column, ColumnOffset(0), boxed_cast(_state.pageSize.columns) - 1); + return std::clamp(_column, ColumnOffset(0), boxed_cast(_grid.pageSize().columns) - 1); } CellLocation clampToScreen(CellLocation coord) const noexcept @@ -331,7 +333,7 @@ class Screen: public ScreenBase, public capabilities::StaticDatabase return useCellAt(_state.lastCursorPosition.line, _state.lastCursorPosition.column); } - void updateCursorIterator() noexcept + void updateCursorIterator() noexcept override { #if defined(LIBTERMINAL_CURRENT_LINE_CACHE) _currentLine = &grid().lineAt(_state.cursor.position.line); @@ -440,6 +442,12 @@ class Screen: public ScreenBase, public capabilities::StaticDatabase .compareText(codepoint); } + // IMPORTANT: Invokig inflatedBuffer() is expensive. This function should be invoked with caution. + [[nodiscard]] std::string cellTextAt(CellLocation position) const noexcept override + { + return grid().lineAt(position.line).inflatedBuffer().at(position.column.as()).toUtf8(); + } + [[nodiscard]] bool isLineEmpty(LineOffset line) const noexcept override { return grid().lineAt(line).empty(); @@ -516,7 +524,6 @@ class Screen: public ScreenBase, public capabilities::StaticDatabase Terminal& _terminal; TerminalState& _state; - ScreenType const _screenType; Grid& _grid; #if defined(LIBTERMINAL_CURRENT_LINE_CACHE) Line* _currentLine = nullptr; diff --git a/src/terminal/Sequencer.cpp b/src/terminal/Sequencer.cpp index 269acf0e0b..8bf46090f3 100644 --- a/src/terminal/Sequencer.cpp +++ b/src/terminal/Sequencer.cpp @@ -52,7 +52,7 @@ void Sequencer::print(char _char) terminal_.state().instructionCounter++; auto const codepoint = holds_alternative(r) ? get(r).value : ReplacementCharacter; - terminal_.currentScreen().writeText(codepoint); + terminal_.activeDisplay().writeText(codepoint); terminal_.state().precedingGraphicCharacter = codepoint; } @@ -63,7 +63,7 @@ void Sequencer::print(string_view _chars, size_t cellCount) if (utf8DecoderState_.expectedLength == 0) { terminal_.state().instructionCounter += _chars.size(); - terminal_.currentScreen().writeText(_chars, cellCount); + terminal_.activeDisplay().writeText(_chars, cellCount); terminal_.state().precedingGraphicCharacter = static_cast(_chars.back()); } else @@ -76,7 +76,7 @@ void Sequencer::print(string_view _chars, size_t cellCount) void Sequencer::execute(char controlCode) { - terminal_.currentScreen().executeControlCode(controlCode); + terminal_.activeDisplay().executeControlCode(controlCode); resetUtf8DecoderState(); } @@ -170,7 +170,7 @@ void Sequencer::unhook() void Sequencer::handleSequence() { parameterBuilder_.fixiate(); - terminal_.currentScreen().processSequence(sequence_); + terminal_.activeDisplay().processSequence(sequence_); } } // namespace terminal diff --git a/src/terminal/Sequencer.h b/src/terminal/Sequencer.h index 3354e19077..94e3d49a22 100644 --- a/src/terminal/Sequencer.h +++ b/src/terminal/Sequencer.h @@ -91,7 +91,9 @@ enum class RequestStatusString DECSLRM, DECSLPP, DECSCPP, - DECSNLS + DECSNLS, + DECSASD, + DECSSDT, }; /// DECSIXEL - Sixel Graphics Image. @@ -239,6 +241,8 @@ struct formatter case terminal::RequestStatusString::DECSLPP: return format_to(ctx.out(), "DECSLPP"); case terminal::RequestStatusString::DECSCPP: return format_to(ctx.out(), "DECSCPP"); case terminal::RequestStatusString::DECSNLS: return format_to(ctx.out(), "DECSNLS"); + case terminal::RequestStatusString::DECSASD: return format_to(ctx.out(), "DECSASD"); + case terminal::RequestStatusString::DECSSDT: return format_to(ctx.out(), "DECSSDT"); } return format_to(ctx.out(), "{}", unsigned(value)); } diff --git a/src/terminal/Terminal.cpp b/src/terminal/Terminal.cpp index d9772ca348..034e0db380 100644 --- a/src/terminal/Terminal.cpp +++ b/src/terminal/Terminal.cpp @@ -110,8 +110,10 @@ Terminal::Terminal(unique_ptr _pty, ptyBufferPool_ { crispy::nextPowerOfTwo(ptyBufferObjectSize) }, currentPtyBuffer_ { ptyBufferPool_.allocateBufferObject() }, ptyReadBufferSize_ { crispy::nextPowerOfTwo(_ptyReadBufferSize) }, - primaryScreen_ { state_, ScreenType::Primary, state_.primaryBuffer }, - alternateScreen_ { state_, ScreenType::Alternate, state_.alternateBuffer }, + primaryScreen_ { state_, state_.primaryBuffer }, + alternateScreen_ { state_, state_.alternateBuffer }, + hostWritableStatusLineScreen_ { state_, state_.hostWritableStatusBuffer }, + indicatorStatusScreen_ { state_, state_.indicatorStatusBuffer }, currentScreen_ { primaryScreen_ }, viewport_ { *this, [this]() { @@ -138,7 +140,7 @@ void Terminal::setLastMarkRangeOffset(LineOffset _value) noexcept copyLastMarkRangeOffset_ = _value; } -bool Terminal::processInputOnce() +Pty::ReadResult Terminal::readFromPty() { auto const timeout = renderBuffer_.state == RenderBufferState::WaitingForRefresh && !screenDirty_ ? std::chrono::seconds(4) @@ -155,12 +157,18 @@ bool Terminal::processInputOnce() currentPtyBuffer_ = ptyBufferPool_.allocateBufferObject(); } - auto const readResult = pty_->read(*currentPtyBuffer_, timeout, ptyReadBufferSize_); + return pty_->read(*currentPtyBuffer_, timeout, ptyReadBufferSize_); +} + +bool Terminal::processInputOnce() +{ + auto const readResult = readFromPty(); + if (!readResult) { if (errno != EINTR && errno != EAGAIN) { - TerminalLog()("PTY read failed (timeout: {}). {}", timeout, strerror(errno)); + TerminalLog()("PTY read failed. {}", strerror(errno)); pty_->close(); } return errno == EINTR || errno == EAGAIN; @@ -331,6 +339,8 @@ void Terminal::refreshRenderBufferInternal(RenderBuffer& _output) { verifyState(); + _output.clear(); + changes_.store(0); screenDirty_ = false; ++lastFrameID_; @@ -341,14 +351,93 @@ void Terminal::refreshRenderBufferInternal(RenderBuffer& _output) #endif auto const hoveringHyperlinkGuard = ScopedHyperlinkHover { *this, currentScreen_ }; + auto const mainDisplayReverseVideo = isModeEnabled(terminal::DECMode::ReverseVideo); if (isPrimaryScreen()) - primaryScreen_.render(RenderBufferBuilder { *this, _output }, viewport_.scrollOffset()); + primaryScreen_.render( + RenderBufferBuilder { *this, _output, LineOffset(0), mainDisplayReverseVideo }, + viewport_.scrollOffset()); else - alternateScreen_.render(RenderBufferBuilder { *this, _output }, viewport_.scrollOffset()); + alternateScreen_.render( + RenderBufferBuilder { *this, _output, LineOffset(0), mainDisplayReverseVideo }, + viewport_.scrollOffset()); + + switch (state_.statusDisplayType) + { + case StatusDisplayType::None: + //. + break; + case StatusDisplayType::Indicator: + updateIndicatorStatusLine(); + indicatorStatusScreen_.render( + RenderBufferBuilder { + *this, _output, state_.pageSize.lines.as(), !mainDisplayReverseVideo }, + ScrollOffset(0)); + break; + case StatusDisplayType::HostWritable: + hostWritableStatusLineScreen_.render( + RenderBufferBuilder { + *this, _output, state_.pageSize.lines.as(), !mainDisplayReverseVideo }, + ScrollOffset(0)); + break; + } } // }}} +void Terminal::updateIndicatorStatusLine() +{ + auto& savedActiveDisplay = currentScreen_.get(); + assert(&savedActiveDisplay != &indicatorStatusScreen_); + + auto savedCursor = state_.cursor; + auto const savedWrapPending = state_.wrapPending; + + currentScreen_ = indicatorStatusScreen_; + + // Prepare old status line's cursor position and some other flags. + state_.cursor = {}; + state_.wrapPending = false; + indicatorStatusScreen_.updateCursorIterator(); + + auto const inputModeStr = [](auto mode) noexcept -> string_view { + switch (mode) + { + case ViMode::Normal: return "NORMAL"sv; + case ViMode::Insert: return "INSERT"sv; + case ViMode::Visual: return "VISUAL"sv; + case ViMode::VisualLine: return "VISUAL LINE"sv; + case ViMode::VisualBlock: return "VISUAL BLOCK"sv; + case ViMode::NormalMotionVisual: return "NORMAL (VISUAL MOTION)"sv; + } + crispy::unreachable(); + }(inputHandler().mode()); + + // Run status-line update. + // We cannot use VT writing here, because we shall not interfere with the application's VT state. + // TODO: Future improvement would be to allow full VT sequence support for the Indicator-status-line, + // such that we can pass display-control partially over to some user/thirdparty configuration. + indicatorStatusScreen_.clearLine(); + state_.cursor.graphicsRendition.styles |= CellFlags::Bold; + indicatorStatusScreen_.writeTextFromExternal( + fmt::format("{} | {} | Screen: {} | Cursor: {}:{} | Mouse: {}:{}", + state_.terminalId, + inputModeStr, + state_.screenType, + savedCursor.position.line + 1, + savedCursor.position.column + 1, + currentMousePosition_.line + 1, + currentMousePosition_.column + 1)); + + // Cleaning up. + currentScreen_ = savedActiveDisplay; + // restoreCursor(savedCursor, savedWrapPending); + state_.wrapPending = savedWrapPending; + state_.cursor = savedCursor; + state_.cursor.position = clampCoordinate(savedCursor.position); + currentScreen_.get().updateCursorIterator(); + verifyState(); +} + bool Terminal::sendKeyPressEvent(Key _key, Modifier _modifier, Timestamp _now) { cursorBlinkState_ = 1; @@ -465,6 +554,18 @@ bool Terminal::sendMouseMoveEvent(Modifier _modifier, if (leftMouseButtonPressed_ && isSelectionComplete()) clearSelection(); + // Force refresh on mouse position grid-cell change to get the Indicator status line updated. + auto changed = false; + auto const cursorPositionHasChanged = newPosition != currentMousePosition_; + auto const uiMaybeDisplayingMousePosition = state_.statusDisplayType == StatusDisplayType::Indicator; + if (cursorPositionHasChanged && uiMaybeDisplayingMousePosition) + { + currentMousePosition_ = newPosition; + markScreenDirty(); + eventListener_.renderBufferUpdated(); + changed = true; + } + if (newPosition == currentMousePosition_ && !isModeEnabled(DECMode::MouseSGRPixels)) return false; @@ -472,7 +573,7 @@ bool Terminal::sendMouseMoveEvent(Modifier _modifier, auto const relativePos = viewport_.translateScreenToGridCoordinate(currentMousePosition_); - bool changed = updateCursorHoveringState(); + changed = changed || updateCursorHoveringState(); // Do not handle mouse-move events in sub-cell dimensions. if (respectMouseProtocol_ @@ -620,6 +721,26 @@ void Terminal::writeToScreen(string_view _data) } } +string_view Terminal::lockedWriteToPtyBuffer(string_view data) +{ + if (currentPtyBuffer_->bytesAvailable() < 64 && currentPtyBuffer_->bytesAvailable() < data.size()) + currentPtyBuffer_ = ptyBufferPool_.allocateBufferObject(); + + auto const chunk = data.substr(0, std::min(data.size(), currentPtyBuffer_->bytesAvailable())); + auto const _l = scoped_lock { *currentPtyBuffer_ }; + return currentPtyBuffer_->writeAtEnd(chunk); +} + +void Terminal::writeToScreenInternal(std::string_view data) +{ + while (!data.empty()) + { + auto const chunk = lockedWriteToPtyBuffer(data); + data.remove_prefix(chunk.size()); + state_.parser.parseFragment(chunk); + } +} + void Terminal::updateCursorVisibilityState() const { if (cursorDisplay_ == CursorDisplay::Steady) @@ -665,25 +786,35 @@ optional Terminal::nextRender() const return chrono::milliseconds::min(); } -void Terminal::resizeScreen(PageSize _cells, optional _pixels) +void Terminal::resizeScreen(PageSize totalPageSize, optional _pixels) { auto const _l = lock_guard { *this }; + resizeScreenInternal(totalPageSize, _pixels); +} + +void Terminal::resizeScreenInternal(PageSize totalPageSize, std::optional _pixels) +{ + Require(hostWritableStatusLineScreen_.pageSize() == indicatorStatusScreen_.pageSize()); // NOTE: This will only resize the currently active buffer. // Any other buffer will be resized when it is switched to. + auto const statusLineHeight = hostWritableStatusLineScreen_.pageSize().lines; + auto const mainDisplayPageSize = state_.statusDisplayType == StatusDisplayType::None + ? totalPageSize + : totalPageSize - statusLineHeight; - state_.pageSize = _cells; + state_.pageSize = mainDisplayPageSize; currentMousePosition_ = clampToScreen(currentMousePosition_); if (_pixels) - setCellPixelSize(_pixels.value() / _cells); + setCellPixelSize(_pixels.value() / totalPageSize); // Reset margin to their default. - state_.margin = Margin { Margin::Vertical { {}, _cells.lines.as() - 1 }, - Margin::Horizontal { {}, _cells.columns.as() - 1 } }; + state_.margin = Margin { Margin::Vertical { {}, mainDisplayPageSize.lines.as() - 1 }, + Margin::Horizontal { {}, mainDisplayPageSize.columns.as() - 1 } }; applyPageSizeToCurrentBuffer(); - pty_->resizeScreen(_cells, _pixels); + pty_->resizeScreen(mainDisplayPageSize, _pixels); verifyState(); } @@ -710,7 +841,7 @@ void Terminal::resizeColumns(ColumnCount _newColumnCount, bool _clear) auto const pixels = cellPixelSize() * newSize; resizeScreen(newSize, pixels); - resizeWindow(newSize); + requestWindowResize(newSize); } void Terminal::verifyState() @@ -727,6 +858,9 @@ void Terminal::verifyState() else Require(state_.alternateBuffer.pageSize() == state_.pageSize); + Require(state_.hostWritableStatusBuffer.pageSize().columns == state_.pageSize.columns); + Require(state_.indicatorStatusBuffer.pageSize().columns == state_.pageSize.columns); + Require(*state_.cursor.position.column < *state_.pageSize.columns); Require(*state_.cursor.position.line < *state_.pageSize.lines); @@ -896,14 +1030,14 @@ void Terminal::reply(string_view _reply) sendRaw(_reply); } -void Terminal::resizeWindow(PageSize _size) +void Terminal::requestWindowResize(PageSize _size) { - eventListener_.resizeWindow(_size.lines, _size.columns); + eventListener_.requestWindowResize(_size.lines, _size.columns); } -void Terminal::resizeWindow(ImageSize _size) +void Terminal::requestWindowResize(ImageSize _size) { - eventListener_.resizeWindow(_size.width, _size.height); + eventListener_.requestWindowResize(_size.width, _size.height); } void Terminal::setApplicationkeypadMode(bool _enabled) @@ -1165,10 +1299,7 @@ void Terminal::clearScreen() void Terminal::moveCursorTo(LineOffset _line, ColumnOffset _column) { - if (isPrimaryScreen()) - primaryScreen_.moveCursorTo(_line, _column); - else - alternateScreen_.moveCursorTo(_line, _column); + currentScreen_.get().moveCursorTo(_line, _column); } void Terminal::saveCursor() @@ -1191,10 +1322,7 @@ void Terminal::restoreCursor(Cursor const& _savedCursor) state_.wrapPending = false; state_.cursor = _savedCursor; state_.cursor.position = clampCoordinate(_savedCursor.position); - if (isPrimaryScreen()) - primaryScreen_.updateCursorIterator(); - else - alternateScreen_.updateCursorIterator(); + currentScreen().updateCursorIterator(); verifyState(); } @@ -1217,6 +1345,9 @@ void Terminal::softReset() state_.cursor.hyperlink = {}; state_.colorPalette = state_.defaultColorPalette; + setActiveStatusDisplay(ActiveStatusDisplay::Main); + setStatusDisplay(StatusDisplayType::None); + // TODO: DECNKM (Numeric keypad) // TODO: DECSCA (Select character attribute) // TODO: DECNRCM (National replacement character set) @@ -1322,6 +1453,8 @@ void Terminal::hardReset() state_.primaryBuffer.reset(); state_.alternateBuffer.reset(); + state_.hostWritableStatusBuffer.reset(); + state_.indicatorStatusBuffer.reset(); state_.imagePool.clear(); @@ -1341,6 +1474,11 @@ void Terminal::hardReset() else alternateScreen_.updateCursorIterator(); + setActiveStatusDisplay(ActiveStatusDisplay::Main); + setStatusDisplay(StatusDisplayType::None); + hostWritableStatusLineScreen_.clearScreen(); + hostWritableStatusLineScreen_.updateCursorIterator(); + primaryScreen_.verifyState(); state_.inputGenerator.reset(); @@ -1390,6 +1528,11 @@ void Terminal::applyPageSizeToCurrentBuffer() : state_.alternateBuffer.resize(state_.pageSize, cursorPosition, state_.wrapPending); cursorPosition = clampCoordinate(cursorPosition); + (void) state_.hostWritableStatusBuffer.resize( + PageSize { LineCount(1), state_.pageSize.columns }, CellLocation {}, false); + (void) state_.indicatorStatusBuffer.resize( + PageSize { LineCount(1), state_.pageSize.columns }, CellLocation {}, false); + if (state_.cursor.position.column < boxed_cast(state_.pageSize.columns)) state_.wrapPending = false; @@ -1479,4 +1622,59 @@ LineCount Terminal::maxHistoryLineCount() const noexcept return primaryScreen_.grid().maxHistoryLineCount(); } +void Terminal::setStatusDisplay(StatusDisplayType statusDisplayType) +{ + assert(¤tScreen_.get() != &indicatorStatusScreen_); + + if (state_.statusDisplayType == statusDisplayType) + return; + + markScreenDirty(); + + auto const statusLineVisibleBefore = state_.statusDisplayType != StatusDisplayType::None; + auto const statusLineVisibleAfter = statusDisplayType != StatusDisplayType::None; + auto const theTotalPageSize = totalPageSize(); + state_.statusDisplayType = statusDisplayType; + + if (statusLineVisibleBefore != statusLineVisibleAfter) + resizeScreenInternal(theTotalPageSize, nullopt); +} + +void Terminal::setActiveStatusDisplay(ActiveStatusDisplay activeDisplay) +{ + if (state_.activeStatusDisplay == activeDisplay) + return; + + assert(¤tScreen_.get() != &indicatorStatusScreen_); + + state_.activeStatusDisplay = activeDisplay; + + switch (activeDisplay) + { + case ActiveStatusDisplay::Main: + switch (state_.screenType) + { + case ScreenType::Primary: currentScreen_ = primaryScreen_; break; + case ScreenType::Alternate: currentScreen_ = alternateScreen_; break; + } + restoreCursor(state_.savedCursorStatusLine); + break; + case ActiveStatusDisplay::StatusLine: { + currentScreen_ = hostWritableStatusLineScreen_; + // Prepare old status line's cursor position and some other flags. + auto cursor = state_.cursor; + cursor.position = state_.savedCursorStatusLine.position; + cursor.originMode = false; + cursor.visible = false; + + // Backup current cursor state. + state_.savedCursorStatusLine = state_.cursor; + + // Activate cursor. + restoreCursor(cursor); + break; + } + } +} + } // namespace terminal diff --git a/src/terminal/Terminal.h b/src/terminal/Terminal.h index 76b0cbf0e9..411bc90df0 100644 --- a/src/terminal/Terminal.h +++ b/src/terminal/Terminal.h @@ -69,8 +69,8 @@ class Terminal virtual void onClosed() {} virtual void pasteFromClipboard(unsigned /*count*/) {} virtual void onSelectionCompleted() {} - virtual void resizeWindow(LineCount, ColumnCount) {} - virtual void resizeWindow(Width, Height) {} + virtual void requestWindowResize(LineCount, ColumnCount) {} + virtual void requestWindowResize(Width, Height) {} virtual void setWindowTitle(std::string_view /*_title*/) {} virtual void setTerminalProfile(std::string const& /*_configProfileName*/) {} virtual void discardImage(Image const&) {} @@ -212,7 +212,26 @@ class Terminal Pty& device() noexcept { return *pty_; } PageSize pageSize() const noexcept { return pty_->pageSize(); } + + PageSize totalPageSize() const noexcept + { + switch (state_.statusDisplayType) + { + case StatusDisplayType::None: + //. + return pageSize(); + case StatusDisplayType::Indicator: + case StatusDisplayType::HostWritable: + return pageSize() + hostWritableStatusLineScreen_.pageSize().lines; + } + crispy::unreachable(); + } + + /// Resizes the terminal screen to the given amount of grid cells with their pixel dimensions. + /// Important! In case a status line is currently visible, the status line count is being + /// accumulated into the screen size, too. void resizeScreen(PageSize _cells, std::optional _pixels = std::nullopt); + void resizeScreenInternal(PageSize _cells, std::optional _pixels); /// Implements semantics for DECCOLM / DECSCPP. void resizeColumns(ColumnCount _newColumnCount, bool _clear); @@ -262,6 +281,9 @@ class Terminal /// Writes a given VT-sequence to screen. void writeToScreen(std::string_view _text); + /// Writes a given VT-sequence to screen - but without acquiring the lock (must be already acquired). + void writeToScreenInternal(std::string_view _text); + // viewport management Viewport& viewport() noexcept { return viewport_; } Viewport const& viewport() const noexcept { return viewport_; } @@ -348,6 +370,16 @@ class Terminal ScreenBase& currentScreen() noexcept { return currentScreen_.get(); } ScreenBase const& currentScreen() const noexcept { return currentScreen_.get(); } + ScreenBase& activeDisplay() noexcept + { + switch (state_.activeStatusDisplay) + { + case ActiveStatusDisplay::Main: return currentScreen_.get(); + case ActiveStatusDisplay::StatusLine: return hostWritableStatusLineScreen_; + } + crispy::unreachable(); + } + bool isPrimaryScreen() const noexcept { return state_.screenType == ScreenType::Primary; } bool isAlternateScreen() const noexcept { return state_.screenType == ScreenType::Alternate; } ScreenType screenType() const noexcept { return state_.screenType; } @@ -490,8 +522,8 @@ class Terminal reply(fmt::vformat(fmt, fmt::make_format_args(args...))); } - void resizeWindow(PageSize); - void resizeWindow(ImageSize); + void requestWindowResize(PageSize); + void requestWindowResize(ImageSize); void setApplicationkeypadMode(bool _enabled); void setBracketedPaste(bool _enabled); void setCursorStyle(CursorDisplay _display, CursorShape _shape); @@ -533,13 +565,23 @@ class Terminal ViInputHandler& inputHandler() noexcept { return state_.inputHandler; } ViInputHandler const& inputHandler() const noexcept { return state_.inputHandler; } + void setStatusDisplay(StatusDisplayType statusDisplayType); + void setActiveStatusDisplay(ActiveStatusDisplay activeDisplay); + private: void mainLoop(); void refreshRenderBuffer(RenderBuffer& _output); // <- acquires the lock void refreshRenderBufferInternal(RenderBuffer& _output); + void updateIndicatorStatusLine(); void updateCursorVisibilityState() const; bool updateCursorHoveringState(); + // Reads from PTY. + Pty::ReadResult readFromPty(); + + // Writes partially or all input data to the PTY buffer object and returns a string view to it. + std::string_view lockedWriteToPtyBuffer(std::string_view data); + // private data // @@ -583,6 +625,8 @@ class Terminal size_t ptyReadBufferSize_; Screen primaryScreen_; Screen alternateScreen_; + Screen hostWritableStatusLineScreen_; + Screen indicatorStatusScreen_; std::reference_wrapper currentScreen_; std::mutex mutable outerLock_; diff --git a/src/terminal/TerminalState.cpp b/src/terminal/TerminalState.cpp index 8b5da82c81..0688383889 100644 --- a/src/terminal/TerminalState.cpp +++ b/src/terminal/TerminalState.cpp @@ -46,6 +46,12 @@ TerminalState::TerminalState(Terminal& _terminal, allowReflowOnResize { _allowReflowOnResize }, primaryBuffer { Grid(_pageSize, _allowReflowOnResize, _maxHistoryLineCount) }, alternateBuffer { Grid(_pageSize, false, LineCount(0)) }, + hostWritableStatusBuffer { Grid( + PageSize { LineCount(1), _pageSize.columns }, false, LineCount(0)) }, + indicatorStatusBuffer { Grid { + PageSize { LineCount(1), _pageSize.columns }, false, LineCount(0) } }, + statusDisplayType { StatusDisplayType::None }, + activeStatusDisplay { ActiveStatusDisplay::Main }, cursor {}, lastCursorPosition {}, hyperlinks { HyperlinkCache { 1024 } }, diff --git a/src/terminal/TerminalState.h b/src/terminal/TerminalState.h index 26cc4dd4b2..ef11e66080 100644 --- a/src/terminal/TerminalState.h +++ b/src/terminal/TerminalState.h @@ -160,12 +160,17 @@ struct TerminalState ScreenType screenType = ScreenType::Primary; Grid primaryBuffer; Grid alternateBuffer; + Grid hostWritableStatusBuffer; // writable status-display, see DECSASD and DECSSDT. + Grid indicatorStatusBuffer; // status buffer as used for indicator status line AND error lines. + StatusDisplayType statusDisplayType; + ActiveStatusDisplay activeStatusDisplay; // cursor related // Cursor cursor; Cursor savedCursor; - Cursor savedPrimaryCursor; //!< saved cursor of primary-screen when switching to alt-screen. + Cursor savedPrimaryCursor; //!< Saved cursor of primary-screen when switching to alt-screen. + Cursor savedCursorStatusLine; //!< Saved cursor of the status line if not active, the other way around. CellLocation lastCursorPosition; bool wrapPending = false; diff --git a/src/terminal/primitives.h b/src/terminal/primitives.h index c62c9c3e9f..257de99fad 100644 --- a/src/terminal/primitives.h +++ b/src/terminal/primitives.h @@ -284,14 +284,33 @@ struct PageSize ColumnCount columns; [[nodiscard]] int area() const noexcept { return *lines * *columns; } }; + +constexpr PageSize operator+(PageSize pageSize, LineCount lines) noexcept +{ + return PageSize { pageSize.lines + lines, pageSize.columns }; +} + +constexpr PageSize operator-(PageSize pageSize, LineCount lines) noexcept +{ + return PageSize { pageSize.lines - lines, pageSize.columns }; +} + constexpr bool operator==(PageSize a, PageSize b) noexcept { return a.lines == b.lines && a.columns == b.columns; } + constexpr bool operator!=(PageSize a, PageSize b) noexcept { return !(a == b); } + +/// Tests whether given CellLocation is within the right hand side's PageSize. +constexpr bool operator<(CellLocation location, PageSize pageSize) noexcept +{ + return location.line < boxed_cast(pageSize.lines) + && location.column < boxed_cast(pageSize.columns); +} // }}} // {{{ Coordinate types @@ -522,6 +541,23 @@ enum class GraphicsRendition NoOverline = 55, //!< Reverses Overline. }; +enum class StatusDisplayType +{ + None, + Indicator, + HostWritable, +}; + +// Selects whether the terminal sends data to the main display or the status line. +enum class ActiveStatusDisplay +{ + // Selects the main display. The terminal sends data to the main display only. + Main, + + // Selects the host-writable status line. The terminal sends data to the status line only. + StatusLine, +}; + enum class AnsiMode { KeyboardAction = 2, // KAM diff --git a/src/terminal/pty/LinuxPty.cpp b/src/terminal/pty/LinuxPty.cpp index 387d717a80..bd411f41ec 100644 --- a/src/terminal/pty/LinuxPty.cpp +++ b/src/terminal/pty/LinuxPty.cpp @@ -48,6 +48,7 @@ using std::nullopt; using std::numeric_limits; using std::optional; using std::runtime_error; +using std::scoped_lock; using std::string_view; using std::tuple; @@ -314,8 +315,11 @@ int LinuxPty::waitForReadable(std::chrono::milliseconds timeout) noexcept Pty::ReadResult LinuxPty::read(crispy::BufferObject& sink, std::chrono::milliseconds timeout, size_t size) { if (int fd = waitForReadable(timeout); fd != -1) + { + auto const _l = scoped_lock { sink }; if (auto x = readSome(fd, sink.hotEnd(), min(size, sink.bytesAvailable()))) return { tuple { x.value(), fd == _stdoutFastPipe.reader() } }; + } return nullopt; } diff --git a/src/terminal/pty/UnixPty.cpp b/src/terminal/pty/UnixPty.cpp index f19ee0be54..3fad8b51ad 100644 --- a/src/terminal/pty/UnixPty.cpp +++ b/src/terminal/pty/UnixPty.cpp @@ -54,6 +54,7 @@ using std::nullopt; using std::numeric_limits; using std::optional; using std::runtime_error; +using std::scoped_lock; using std::string_view; using std::tuple; @@ -384,8 +385,11 @@ int waitForReadable(int ptyMaster, Pty::ReadResult UnixPty::read(crispy::BufferObject& sink, std::chrono::milliseconds timeout, size_t size) { if (int fd = waitForReadable(_masterFd, _stdoutFastPipe.reader(), _pipe[0], timeout); fd != -1) + { + auto const _l = scoped_lock { sink }; if (auto x = readSome(fd, sink.hotEnd(), min(size, sink.bytesAvailable()))) return { tuple { x.value(), fd == _stdoutFastPipe.reader() } }; + } return nullopt; } diff --git a/src/terminal_renderer/Renderer.cpp b/src/terminal_renderer/Renderer.cpp index 514c5d9cb7..ecb674fbec 100644 --- a/src/terminal_renderer/Renderer.cpp +++ b/src/terminal_renderer/Renderer.cpp @@ -291,7 +291,10 @@ void Renderer::updateFontMetrics() uint64_t Renderer::render(Terminal& _terminal, bool _pressure) { - gridMetrics_.pageSize = _terminal.pageSize(); + auto const statusLineHeight = _terminal.state().statusDisplayType == StatusDisplayType::None + ? LineCount(0) + : _terminal.state().hostWritableStatusBuffer.pageSize().lines; + gridMetrics_.pageSize = _terminal.pageSize() + statusLineHeight; auto const changes = _terminal.tick(steady_clock::now());