From 8286dee5c5d1e60d76d244c041b6bdea1a9dcc40 Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Sun, 8 Oct 2023 13:55:02 +0200 Subject: [PATCH 1/8] add vcpkg_installed/** to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 24f8c521f5..0954cba015 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ _deps/** out out/** CMakeSettings.json +vcpkg_installed/** # VIM: code completion / IntelliSense .clangd From 4c6ee519580197039a0e63158a77ccfb79485a1f Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Sun, 8 Oct 2023 13:57:37 +0200 Subject: [PATCH 2/8] [vtbackend] Minimal refactor of fmt::make_format_args-use --- src/vtbackend/Terminal.h | 10 +++++----- src/vtbackend/VTWriter.h | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/vtbackend/Terminal.h b/src/vtbackend/Terminal.h index 5af7e56a25..e82072032c 100644 --- a/src/vtbackend/Terminal.h +++ b/src/vtbackend/Terminal.h @@ -588,13 +588,13 @@ class Terminal void notify(std::string_view title, std::string_view body); void reply(std::string_view text); - template - void reply(fmt::format_string fmt, T&&... args) + template + void reply(fmt::format_string message, Ts&&... args) { -#if defined(__APPLE__) - reply(fmt::vformat(fmt, fmt::make_format_args(args...))); +#if defined(__APPLE__) || defined(_MSC_VER) + reply(fmt::vformat(message, fmt::make_format_args(args...))); #else - reply(fmt::vformat(fmt, fmt::make_format_args(std::forward(args)...))); + reply(fmt::vformat(message, fmt::make_format_args(std::forward(args)...))); #endif } diff --git a/src/vtbackend/VTWriter.h b/src/vtbackend/VTWriter.h index f2401be187..be139b0ac6 100644 --- a/src/vtbackend/VTWriter.h +++ b/src/vtbackend/VTWriter.h @@ -71,13 +71,13 @@ class VTWriter Color _currentBackgroundColor = DefaultColor(); }; -template -inline void VTWriter::write(fmt::format_string fmt, T&&... args) +template +inline void VTWriter::write(fmt::format_string fmt, Ts&&... args) { #if defined(__APPLE__) write(fmt::vformat(fmt, fmt::make_format_args(args...))); #else - write(fmt::vformat(fmt, fmt::make_format_args(std::forward(args)...))); + write(fmt::vformat(fmt, fmt::make_format_args(std::forward(args)...))); #endif } From 36d0b9297ad706b524e6980274eaba5a5782ea07 Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Fri, 6 Oct 2023 23:55:58 +0200 Subject: [PATCH 3/8] Use color scheme based on OS system dark/light mode preferences Signed-off-by: Christian Parpart --- .clang-format | 2 +- docs/configuration/profiles.md | 16 ++++- metainfo.xml | 2 + src/contour/CMakeLists.txt | 1 - src/contour/Config.cpp | 93 +++++++++++++++----------- src/contour/Config.h | 15 ++++- src/contour/ContourGuiApp.cpp | 24 +++++++ src/contour/ContourGuiApp.h | 10 ++- src/contour/TerminalSession.cpp | 53 +++++++++++++-- src/contour/TerminalSession.h | 21 +++--- src/contour/TerminalSessionManager.cpp | 6 ++ src/contour/TerminalSessionManager.h | 3 + src/contour/contour.yml | 11 ++- src/contour/display/TerminalWidget.cpp | 5 +- src/vtbackend/ColorPalette.h | 21 ++++++ src/vtbackend/Terminal.cpp | 7 +- src/vtbackend/Terminal.h | 10 +++ 17 files changed, 231 insertions(+), 69 deletions(-) diff --git a/.clang-format b/.clang-format index bd35115950..0e63501492 100644 --- a/.clang-format +++ b/.clang-format @@ -95,7 +95,7 @@ IncludeCategories: Priority: 52 - Regex: '^Do not clear search term when entering search editor again.
  • Clear search term when switch to insert vi mode (#1135)
  • Delete dpi_scale entry in configuration (#1137)
  • +
  • Removes the ability to inline colorschemes within a configuration profile. Colorschemes must now always be referenced by their name.
  • +
  • Adds the ability to chose a color scheme based on the operating systems's dark/light mode setting. This will change live whenever the OS's dark/light mode setting changes as well (#604).
  • Adds VT sequence DECSSCLS (change scroll speed) and properly handle DECSCLM (enable slow scrolling mode) (#1204)
  • Adds percentage value to Indicator Statusline to indicate scroll offset in scrollback buffer.
  • Adds inheritance of profiles in configuration file based on default profile (#1063).
  • diff --git a/src/contour/CMakeLists.txt b/src/contour/CMakeLists.txt index 51872d1d71..22ecd930ee 100644 --- a/src/contour/CMakeLists.txt +++ b/src/contour/CMakeLists.txt @@ -111,7 +111,6 @@ target_compile_definitions(contour PRIVATE ) set_target_properties(contour PROPERTIES AUTOMOC ON) - set_target_properties(contour PROPERTIES AUTORCC ON) # Disable all deprecated Qt functions prior to Qt 6.0 diff --git a/src/contour/Config.cpp b/src/contour/Config.cpp index fc23551a3b..fa89d30e2b 100644 --- a/src/contour/Config.cpp +++ b/src/contour/Config.cpp @@ -985,7 +985,6 @@ namespace { if (!node) return; - ; usedKeys.emplace(basePath); using vtbackend::RGBColor; @@ -1159,6 +1158,37 @@ namespace return colors; } + vtbackend::ColorPalette loadColorSchemeByName( + UsedKeys& usedKeys, + string const& path, + YAML::Node const& nameNode, + unordered_map const& colorschemes, + logstore::message_builder& logger) + { + auto const name = nameNode.as(); + if (auto i = colorschemes.find(name); i != colorschemes.end()) + { + usedKeys.emplace(path); + return i->second; + } + + for (fs::path const& prefix: configHomes("contour")) + { + auto const filePath = prefix / "colorschemes" / (name + ".yml"); + auto fileContents = readFile(filePath); + if (!fileContents) + continue; + YAML::Node subDocument = YAML::Load(fileContents.value()); + UsedKeys usedColorKeys; + auto colors = loadColorScheme(usedColorKeys, "", subDocument); + // TODO: Check usedColorKeys for validity. + configLog()("Loaded colors from {}.", filePath.string()); + return colors; + } + logger("Could not open colorscheme file for \"{}\".", name); + return vtbackend::ColorPalette {}; + } + void softLoadFont(UsedKeys& usedKeys, string_view basePath, YAML::Node const& node, @@ -1301,40 +1331,29 @@ namespace unordered_map const& colorschemes, logstore::message_builder logger) { - if (auto colors = profile["colors"]; colors) // {{{ { usedKeys.emplace(fmt::format("{}.{}.colors", parentPath, profileName)); auto const path = fmt::format("{}.{}.{}", parentPath, profileName, "colors"); if (colors.IsMap()) - terminalProfile.colors = loadColorScheme(usedKeys, path, colors); - else if (auto i = colorschemes.find(colors.as()); i != colorschemes.end()) { - usedKeys.emplace(path); - terminalProfile.colors = i->second; + terminalProfile.colors = + DualColorConfig { .darkMode = loadColorSchemeByName( + usedKeys, path + ".dark", colors["dark"], colorschemes, logger), + .lightMode = loadColorSchemeByName( + usedKeys, path + ".light", colors["light"], colorschemes, logger) }; +#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) + errorLog()("Dual color scheme is not supported by your local Qt version. " + "Falling back to single color scheme."); +#endif } else if (colors.IsScalar()) { - bool found = false; - for (fs::path const& prefix: configHomes("contour")) - { - auto const filePath = prefix / "colorschemes" / (colors.as() + ".yml"); - auto fileContents = readFile(filePath); - if (!fileContents) - continue; - YAML::Node subDocument = YAML::Load(fileContents.value()); - UsedKeys usedColorKeys; - terminalProfile.colors = loadColorScheme(usedColorKeys, "", subDocument); - // TODO: Check usedColorKeys for validity. - configLog()("Loaded colors from {}.", filePath.string()); - found = true; - break; - } - if (!found) - logger("Could not open colorscheme file for \"{}\".", colors.as()); + terminalProfile.colors = + SimpleColorConfig { loadColorSchemeByName(usedKeys, path, colors, colorschemes, logger) }; } else - logger("scheme '{}' not found.", colors.as()); + logger("Invalid colors value."); } else logger("No colors section in profile {} found.", profileName); @@ -1372,12 +1391,18 @@ namespace "size_indicator_on_resize", terminalProfile.sizeIndicatorOnResize, logger); - tryLoadChildRelative(usedKeys, - profile, - basePath, - "draw_bold_text_with_bright_colors", - terminalProfile.colors.useBrightColors, - logger); + bool useBrightColors = false; + tryLoadChildRelative( + usedKeys, profile, basePath, "draw_bold_text_with_bright_colors", useBrightColors, logger); + + if (auto* simple = get_if(&terminalProfile.colors)) + simple->colors.useBrightColors = useBrightColors; + else if (auto* dual = get_if(&terminalProfile.colors)) + { + dual->darkMode.useBrightColors = useBrightColors; + dual->lightMode.useBrightColors = useBrightColors; + } + tryLoadChildRelative(usedKeys, profile, basePath, "wm_class", terminalProfile.wmClass, logger); if (auto args = profile["arguments"]; args && args.IsSequence()) @@ -2026,19 +2051,11 @@ void loadConfigFromFile(Config& config, fs::path const& fileName) if (auto colorschemes = doc["color_schemes"]; colorschemes) { usedKeys.emplace("color_schemes"); - // load default colorschemes - const std::string nameDefault = "default"; - auto const pathDefault = "color_schemes." + nameDefault; - config.colorschemes[nameDefault] = - loadColorScheme(usedKeys, pathDefault, colorschemes.begin()->second); for (auto i = colorschemes.begin(); i != colorschemes.end(); ++i) { auto const name = i->first.as(); - if (name == nameDefault) - continue; auto const path = "color_schemes." + name; - config.colorschemes[name] = config.colorschemes[nameDefault]; updateColorScheme(config.colorschemes[name], usedKeys, path, i->second); } } diff --git a/src/contour/Config.h b/src/contour/Config.h index 2993856428..4ea364062a 100644 --- a/src/contour/Config.h +++ b/src/contour/Config.h @@ -132,6 +132,19 @@ struct InputModeConfig CursorConfig cursor; }; +struct DualColorConfig +{ + vtbackend::ColorPalette darkMode {}; + vtbackend::ColorPalette lightMode {}; +}; + +struct SimpleColorConfig +{ + vtbackend::ColorPalette colors {}; +}; + +using ColorConfig = std::variant; + struct TerminalProfile { vtpty::Process::ExecInfo shell; @@ -168,7 +181,7 @@ struct TerminalProfile } permissions; bool drawBoldTextWithBrightColors = false; - vtbackend::ColorPalette colors {}; + ColorConfig colors = SimpleColorConfig {}; vtbackend::LineCount modalCursorScrollOff { 8 }; diff --git a/src/contour/ContourGuiApp.cpp b/src/contour/ContourGuiApp.cpp index c7d0bffc8e..fa67435452 100644 --- a/src/contour/ContourGuiApp.cpp +++ b/src/contour/ContourGuiApp.cpp @@ -12,7 +12,12 @@ #include #include +#if !defined(__APPLE__) && !defined(_WIN32) + #include +#endif +#include #include +#include #include #include #include @@ -347,6 +352,25 @@ int ContourGuiApp::terminalGuiAction() // NB: We use QApplication over QGuiApplication because we want to use SystemTrayIcon. QApplication app(qtArgsCount, (char**) qtArgsPtr.data()); +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) + _colorPreference = QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark + ? vtbackend::ColorPreference::Dark + : vtbackend::ColorPreference::Light; + + displayLog()("Color theme mode at startup: {}", _colorPreference); + + connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, [&](Qt::ColorScheme newScheme) { + auto const newValue = newScheme == Qt::ColorScheme::Dark ? vtbackend::ColorPreference::Dark + : vtbackend::ColorPreference::Light; + if (_colorPreference == newValue) + return; + + _colorPreference = newValue; + displayLog()("Color preference changed to {} mode\n", _colorPreference); + sessionsManager().updateColorPreference(_colorPreference); + }); +#endif + #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) // Enforce OpenGL over any other. As much as I'd love to provide other backends, too. // We currently only support OpenGL. diff --git a/src/contour/ContourGuiApp.h b/src/contour/ContourGuiApp.h index e29d43ea47..b289021c64 100644 --- a/src/contour/ContourGuiApp.h +++ b/src/contour/ContourGuiApp.h @@ -4,16 +4,16 @@ #include #include #include +#include #include +#include #include #include -#include #include #include -#include namespace contour { @@ -55,7 +55,7 @@ class ContourGuiApp: public QObject, public ContourApp { if (const auto* const profile = config().profile(profileName())) return *profile; - fmt::print("Failed to access config profile.\n"); + displayLog()("Failed to access config profile."); Require(false); } @@ -69,6 +69,8 @@ class ContourGuiApp: public QObject, public ContourApp [[nodiscard]] static QUrl resolveResource(std::string_view path); + vtbackend::ColorPreference colorPreference() const noexcept { return _colorPreference; } + private: static void ensureTermInfoFile(); bool loadConfig(std::string const& target); @@ -82,6 +84,8 @@ class ContourGuiApp: public QObject, public ContourApp char const** _argv = nullptr; std::optional _exitStatus; + vtbackend::ColorPreference _colorPreference = vtbackend::ColorPreference::Dark; + std::unique_ptr _qmlEngine; }; diff --git a/src/contour/TerminalSession.cpp b/src/contour/TerminalSession.cpp index c97f6a3677..591123cbe6 100644 --- a/src/contour/TerminalSession.cpp +++ b/src/contour/TerminalSession.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -72,6 +73,24 @@ namespace #endif } + ColorPalette const* preferredColorPalette(config::ColorConfig const& config, + vtbackend::ColorPreference preference) + { + if (auto const* dualColorConfig = std::get_if(&config)) + { + switch (preference) + { + case vtbackend::ColorPreference::Dark: return &dualColorConfig->darkMode; + case vtbackend::ColorPreference::Light: return &dualColorConfig->lightMode; + } + } + else if (auto const* simpleColorConfig = std::get_if(&config)) + return &simpleColorConfig->colors; + + errorLog()("preferredColorPalette: Unknown color config type."); + return nullptr; + } + string normalize_crlf(QString&& text) { #if !defined(_WIN32) @@ -100,7 +119,8 @@ namespace } vtbackend::Settings createSettingsFromConfig(config::Config const& config, - config::TerminalProfile const& profile) + config::TerminalProfile const& profile, + ColorPreference colorPreference) { auto settings = vtbackend::Settings {}; @@ -118,7 +138,8 @@ namespace settings.statusDisplayPosition = profile.statusDisplayPosition; settings.syncWindowTitleWithHostWritableStatusDisplay = profile.syncWindowTitleWithHostWritableStatusDisplay; - settings.colorPalette = profile.colors; + if (auto const* p = preferredColorPalette(profile.colors, colorPreference)) + settings.colorPalette = *p; settings.refreshRate = profile.refreshRate; settings.primaryScreen.allowReflowOnResize = config.reflowOnResize; settings.highlightDoubleClickedWord = profile.highlightDoubleClickedWord; @@ -143,9 +164,11 @@ TerminalSession::TerminalSession(unique_ptr pty, ContourGuiApp& app) _profileName { app.profileName() }, _profile { *_config.profile(_profileName) }, _app { app }, - _terminal { - *this, std::move(pty), createSettingsFromConfig(_config, _profile), std::chrono::steady_clock::now() - } + _currentColorPreference { app.colorPreference() }, + _terminal { *this, + std::move(pty), + createSettingsFromConfig(_config, _profile, _currentColorPreference), + std::chrono::steady_clock::now() } { if (app.liveConfig()) { @@ -342,6 +365,23 @@ void TerminalSession::requestPermission(config::Permission allowedByConfig, Guar } } +void TerminalSession::updateColorPreference(vtbackend::ColorPreference preference) +{ + if (preference == _currentColorPreference) + return; + + _currentColorPreference = preference; + if (auto const* colorPalette = preferredColorPalette(_profile.colors, preference)) + { + _terminal.settings().colorPalette = *colorPalette; + _terminal.factorySettings().colorPalette = *colorPalette; + _terminal.colorPalette() = *colorPalette; + _terminal.defaultColorPalette() = *colorPalette; + + emit backgroundColorChanged(); + } +} + void TerminalSession::requestCaptureBuffer(LineCount lines, bool logical) { if (!_display) @@ -1293,8 +1333,7 @@ void TerminalSession::configureTerminal() // return; configureCursor(_profile.inputModes.insert.cursor); - _terminal.colorPalette() = _profile.colors; - _terminal.defaultColorPalette() = _profile.colors; + updateColorPreference(_app.colorPreference()); _terminal.setMaxHistoryLineCount(_profile.maxHistoryLineCount); _terminal.setHighlightTimeout(_profile.highlightTimeout); _terminal.viewport().setScrollOff(_profile.modalCursorScrollOff); diff --git a/src/contour/TerminalSession.h b/src/contour/TerminalSession.h index 34b43c3308..9ec72160a8 100644 --- a/src/contour/TerminalSession.h +++ b/src/contour/TerminalSession.h @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -18,9 +19,7 @@ #include #include -#include #include -#include #include @@ -98,7 +97,8 @@ class TerminalSession: public QAbstractItemModel, public vtbackend::Terminal::Ev } QString pathToBackground() const { - if (const auto& p = std::get_if(&(profile().colors.backgroundImage->location))) + if (const auto& p = + std::get_if(&(_terminal.colorPalette().backgroundImage->location))) { return QString("file:") + QString(p->string().c_str()); } @@ -108,19 +108,19 @@ class TerminalSession: public QAbstractItemModel, public vtbackend::Terminal::Ev QColor getBackgroundColor() const noexcept { auto color = terminal().isModeEnabled(vtbackend::DECMode::ReverseVideo) - ? _profile.colors.defaultForeground - : _profile.colors.defaultBackground; + ? _terminal.colorPalette().defaultForeground + : _terminal.colorPalette().defaultBackground; return QColor(color.red, color.green, color.blue, static_cast(_profile.backgroundOpacity)); } float getOpacityBackground() const noexcept { - if (profile().colors.backgroundImage) - return profile().colors.backgroundImage->opacity; + if (_terminal.colorPalette().backgroundImage) + return _terminal.colorPalette().backgroundImage->opacity; return 0.0; } bool getIsImageBackground() const noexcept { - if (profile().colors.backgroundImage) + if (_terminal.colorPalette().backgroundImage) return true; return false; } @@ -128,7 +128,7 @@ class TerminalSession: public QAbstractItemModel, public vtbackend::Terminal::Ev bool getIsBlurBackground() const noexcept { if (getIsImageBackground()) - return profile().colors.backgroundImage->blur; + return _terminal.colorPalette().backgroundImage->blur; return false; } @@ -219,6 +219,8 @@ class TerminalSession: public QAbstractItemModel, public vtbackend::Terminal::Ev Q_INVOKABLE void executeShowHostWritableStatusLine(bool answer, bool remember); Q_INVOKABLE void requestWindowResize(QJSValue w, QJSValue h); + void updateColorPreference(vtbackend::ColorPreference preference); + // vtbackend::Events // void requestCaptureBuffer(vtbackend::LineCount lineCount, bool logical) override; @@ -384,6 +386,7 @@ class TerminalSession: public QAbstractItemModel, public vtbackend::Terminal::Ev config::TerminalProfile _profile; double _contentScale = 1.0; ContourGuiApp& _app; + vtbackend::ColorPreference _currentColorPreference; vtbackend::Terminal _terminal; bool _terminatedAndWaitingForKeyPress = false; diff --git a/src/contour/TerminalSessionManager.cpp b/src/contour/TerminalSessionManager.cpp index a7abb1d965..d37c30c4a2 100644 --- a/src/contour/TerminalSessionManager.cpp +++ b/src/contour/TerminalSessionManager.cpp @@ -43,6 +43,12 @@ void TerminalSessionManager::removeSession(TerminalSession& thatSession) // Notify app if all sessions have been killed to trigger app termination. } +void TerminalSessionManager::updateColorPreference(vtbackend::ColorPreference const& preference) +{ + for (auto& session: _sessions) + session->updateColorPreference(preference); +} + // {{{ QAbstractListModel QVariant TerminalSessionManager::data(const QModelIndex& index, int role) const { diff --git a/src/contour/TerminalSessionManager.h b/src/contour/TerminalSessionManager.h index 853a3c6566..b8ff8cca6c 100644 --- a/src/contour/TerminalSessionManager.h +++ b/src/contour/TerminalSessionManager.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -34,6 +35,8 @@ class TerminalSessionManager: public QAbstractListModel [[nodiscard]] int count() const noexcept { return static_cast(_sessions.size()); } + void updateColorPreference(vtbackend::ColorPreference const& preference); + private: ContourGuiApp& _app; std::chrono::seconds _earlyExitThreshold; diff --git a/src/contour/contour.yml b/src/contour/contour.yml index 9df37283e8..d4646855a4 100644 --- a/src/contour/contour.yml +++ b/src/contour/contour.yml @@ -452,7 +452,16 @@ profiles: blur: false # Specifies a colorscheme to use (alternatively the colors can be inlined). - colors: "default" + # + # This can be either the name to a single colorscheme to always use, + # or a map with two keys (dark and light) to determine the color scheme to use for each. + # + # The dark color scheme is used when the system is configured to prefer dark mode and light theme otherwise. + # + # Default: "default" + colors: + light: default + dark: default # Hyperlinks (via OSC-8) can be stylized and colorized on hover. hyperlink_decoration: diff --git a/src/contour/display/TerminalWidget.cpp b/src/contour/display/TerminalWidget.cpp index 62cfa4ebac..8f3118c606 100644 --- a/src/contour/display/TerminalWidget.cpp +++ b/src/contour/display/TerminalWidget.cpp @@ -37,11 +37,8 @@ #include #include -#include -#include #include #include -#include #include #include #include @@ -292,7 +289,7 @@ void TerminalWidget::setSession(TerminalSession* newSession) _renderer = make_unique(newSession->profile().terminalSize, sanitizeFontDescription(profile().fonts, fontDPI()), - newSession->profile().colors, + _session->terminal().colorPalette(), newSession->config().textureAtlasHashtableSlots, newSession->config().textureAtlasTileCount, newSession->config().textureAtlasDirectMapping, diff --git a/src/vtbackend/ColorPalette.h b/src/vtbackend/ColorPalette.h index a8358cfbdb..0643d896c3 100644 --- a/src/vtbackend/ColorPalette.h +++ b/src/vtbackend/ColorPalette.h @@ -17,6 +17,12 @@ namespace vtbackend { +enum class ColorPreference +{ + Dark, + Light, +}; + struct ImageData { vtbackend::ImageFormat format; @@ -140,6 +146,21 @@ RGBColor apply(ColorPalette const& colorPalette, Color color, ColorTarget target } // namespace vtbackend // {{{ fmtlib custom formatter support +template <> +struct fmt::formatter: fmt::formatter +{ + auto format(vtbackend::ColorPreference value, format_context& ctx) -> format_context::iterator + { + string_view name; + switch (value) + { + case vtbackend::ColorPreference::Dark: name = "Dark"; break; + case vtbackend::ColorPreference::Light: name = "Light"; break; + } + return formatter::format(name, ctx); + } +}; + template <> struct fmt::formatter: fmt::formatter { diff --git a/src/vtbackend/Terminal.cpp b/src/vtbackend/Terminal.cpp index b89dd9ca31..dfe757802b 100644 --- a/src/vtbackend/Terminal.cpp +++ b/src/vtbackend/Terminal.cpp @@ -1704,7 +1704,8 @@ void Terminal::softReset() setLeftRightMargin({}, boxed_cast(_settings.pageSize.columns) - ColumnOffset(1)); // DECRLM _currentScreen.get().cursor().hyperlink = {}; - _state.colorPalette = _state.defaultColorPalette; + + resetColorPalette(); setActiveStatusDisplay(ActiveStatusDisplay::Main); setStatusDisplay(StatusDisplayType::None); @@ -1769,7 +1770,7 @@ void Terminal::hardReset() _state.imagePool.clear(); _state.tabs.clear(); - _state.colorPalette = _state.defaultColorPalette; + resetColorPalette(); _hostWritableStatusLineScreen.margin() = Margin { Margin::Vertical { {}, boxed_cast(_hostWritableStatusLineScreen.pageSize().lines) - 1 }, @@ -2217,7 +2218,7 @@ void Terminal::popColorPalette(size_t slot) auto const index = slot == MagicStackTopId ? _state.savedColorPalettes.size() - 1 : slot - 1; - _state.colorPalette = _state.savedColorPalettes[index]; + setColorPalette(_state.savedColorPalettes[index]); if (slot == MagicStackTopId) _state.savedColorPalettes.pop_back(); } diff --git a/src/vtbackend/Terminal.h b/src/vtbackend/Terminal.h index e82072032c..e8fb2153f9 100644 --- a/src/vtbackend/Terminal.h +++ b/src/vtbackend/Terminal.h @@ -375,6 +375,15 @@ class Terminal [[nodiscard]] ColorPalette& colorPalette() noexcept { return _state.colorPalette; } [[nodiscard]] ColorPalette& defaultColorPalette() noexcept { return _state.defaultColorPalette; } + void setDefaultColorPalette(ColorPalette palette) noexcept + { + _state.defaultColorPalette = std::move(palette); + } + + void setColorPalette(ColorPalette const& palette) noexcept { _state.colorPalette = palette; } + + void resetColorPalette() noexcept { setColorPalette(defaultColorPalette()); } + void pushColorPalette(size_t slot); void popColorPalette(size_t slot); void reportColorPaletteStack(); @@ -691,6 +700,7 @@ class Terminal [[nodiscard]] std::tuple extractWordUnderCursor( CellLocation position) const noexcept; + Settings& factorySettings() noexcept { return _factorySettings; } Settings const& factorySettings() const noexcept { return _factorySettings; } Settings const& settings() const noexcept { return _settings; } Settings& settings() noexcept { return _settings; } From 818e5601085cc2b099a5895762d4c155a64ddb8d Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Tue, 10 Oct 2023 10:02:35 +0200 Subject: [PATCH 4/8] [vtbackend] Terminal: drop dead function Signed-off-by: Christian Parpart --- src/vtbackend/Terminal.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vtbackend/Terminal.h b/src/vtbackend/Terminal.h index e8fb2153f9..e2c50c2d92 100644 --- a/src/vtbackend/Terminal.h +++ b/src/vtbackend/Terminal.h @@ -375,13 +375,11 @@ class Terminal [[nodiscard]] ColorPalette& colorPalette() noexcept { return _state.colorPalette; } [[nodiscard]] ColorPalette& defaultColorPalette() noexcept { return _state.defaultColorPalette; } - void setDefaultColorPalette(ColorPalette palette) noexcept + void setColorPalette(ColorPalette const& palette) noexcept { - _state.defaultColorPalette = std::move(palette); + _state.colorPalette = palette; } - void setColorPalette(ColorPalette const& palette) noexcept { _state.colorPalette = palette; } - void resetColorPalette() noexcept { setColorPalette(defaultColorPalette()); } void pushColorPalette(size_t slot); From 333020fc93b355d24ca1a0a2714aaff1145bf064 Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Tue, 10 Oct 2023 10:09:46 +0200 Subject: [PATCH 5/8] [vtbackend] Refactor resetting color palette to hide complexity Signed-off-by: Christian Parpart --- src/contour/TerminalSession.cpp | 5 +---- src/vtbackend/Terminal.cpp | 13 +++++++++++++ src/vtbackend/Terminal.h | 7 ++----- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/contour/TerminalSession.cpp b/src/contour/TerminalSession.cpp index 591123cbe6..04198566da 100644 --- a/src/contour/TerminalSession.cpp +++ b/src/contour/TerminalSession.cpp @@ -373,10 +373,7 @@ void TerminalSession::updateColorPreference(vtbackend::ColorPreference preferenc _currentColorPreference = preference; if (auto const* colorPalette = preferredColorPalette(_profile.colors, preference)) { - _terminal.settings().colorPalette = *colorPalette; - _terminal.factorySettings().colorPalette = *colorPalette; - _terminal.colorPalette() = *colorPalette; - _terminal.defaultColorPalette() = *colorPalette; + _terminal.resetColorPalette(*colorPalette); emit backgroundColorChanged(); } diff --git a/src/vtbackend/Terminal.cpp b/src/vtbackend/Terminal.cpp index dfe757802b..72030d6923 100644 --- a/src/vtbackend/Terminal.cpp +++ b/src/vtbackend/Terminal.cpp @@ -2184,6 +2184,19 @@ void Terminal::setHighlightRange(HighlightRange highlightRange) constexpr auto MagicStackTopId = size_t { 0 }; +void Terminal::setColorPalette(ColorPalette const& palette) noexcept +{ + _state.colorPalette = palette; +} + +void Terminal::resetColorPalette(ColorPalette const& colors) +{ + _state.colorPalette = colors; + _state.defaultColorPalette = colors; + _settings.colorPalette = colors; + _factorySettings.colorPalette = colors; +} + void Terminal::pushColorPalette(size_t slot) { if (slot > MaxColorPaletteSaveStackSize) diff --git a/src/vtbackend/Terminal.h b/src/vtbackend/Terminal.h index e2c50c2d92..14e008790b 100644 --- a/src/vtbackend/Terminal.h +++ b/src/vtbackend/Terminal.h @@ -375,12 +375,9 @@ class Terminal [[nodiscard]] ColorPalette& colorPalette() noexcept { return _state.colorPalette; } [[nodiscard]] ColorPalette& defaultColorPalette() noexcept { return _state.defaultColorPalette; } - void setColorPalette(ColorPalette const& palette) noexcept - { - _state.colorPalette = palette; - } - + void setColorPalette(ColorPalette const& palette) noexcept; void resetColorPalette() noexcept { setColorPalette(defaultColorPalette()); } + void resetColorPalette(ColorPalette const& colors); void pushColorPalette(size_t slot); void popColorPalette(size_t slot); From 916e4078d3ab81fd3bb244852126be18a7692247 Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Tue, 10 Oct 2023 11:14:44 +0200 Subject: [PATCH 6/8] [vtbackend] TerminalState: fix default initialization of color palettes Signed-off-by: Christian Parpart --- src/vtbackend/TerminalState.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vtbackend/TerminalState.cpp b/src/vtbackend/TerminalState.cpp index 919cb6282c..a5c42446c1 100644 --- a/src/vtbackend/TerminalState.cpp +++ b/src/vtbackend/TerminalState.cpp @@ -10,6 +10,8 @@ class Terminal; TerminalState::TerminalState(Terminal& terminal): settings { terminal.settings() }, cellPixelSize {}, + defaultColorPalette { settings.colorPalette }, + colorPalette { settings.colorPalette }, effectiveImageCanvasSize { settings.maxImageSize }, imageColorPalette { std::make_shared(maxImageColorRegisters, maxImageColorRegisters) }, imagePool { [te = &terminal](Image const* image) { From 70de84b453e08fad66477210986feba22342149e Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Tue, 10 Oct 2023 11:28:20 +0200 Subject: [PATCH 7/8] [vtbackend] Fix function definition DSR Signed-off-by: Christian Parpart --- src/vtbackend/Functions.h | 6 ++++-- src/vtbackend/Screen.cpp | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/vtbackend/Functions.h b/src/vtbackend/Functions.h index 578b97a2a0..563a8f523b 100644 --- a/src/vtbackend/Functions.h +++ b/src/vtbackend/Functions.h @@ -383,7 +383,8 @@ constexpr inline auto CHA = detail::CSI(std::nullopt, 0, 1, std::nullopt constexpr inline auto CHT = detail::CSI(std::nullopt, 0, 1, std::nullopt, 'I', VTType::VT100, "CHT", "Cursor Horizontal Forward Tabulation"); constexpr inline auto CNL = detail::CSI(std::nullopt, 0, 1, std::nullopt, 'E', VTType::VT100, "CNL", "Move cursor to next line"); constexpr inline auto CPL = detail::CSI(std::nullopt, 0, 1, std::nullopt, 'F', VTType::VT100, "CPL", "Move cursor to previous line"); -constexpr inline auto CPR = detail::CSI(std::nullopt, 1, 1, std::nullopt, 'n', VTType::VT100, "CPR", "Request Cursor position"); +constexpr inline auto ANSIDSR = detail::CSI(std::nullopt, 1, 1, std::nullopt, 'n', VTType::VT100, "DSR", "Device Status Report (ANSI)"); +constexpr inline auto DSR = detail::CSI('?', 1, 1, std::nullopt, 'n', VTType::VT100, "DSR", "Device Status Report (DEC)"); constexpr inline auto CUB = detail::CSI(std::nullopt, 0, 1, std::nullopt, 'D', VTType::VT100, "CUB", "Move cursor backward"); constexpr inline auto CUD = detail::CSI(std::nullopt, 0, 1, std::nullopt, 'B', VTType::VT100, "CUD", "Move cursor down"); constexpr inline auto CUF = detail::CSI(std::nullopt, 0, 1, std::nullopt, 'C', VTType::VT100, "CUF", "Move cursor forward"); @@ -538,7 +539,6 @@ constexpr static auto allFunctionsArray() noexcept CHT, CNL, CPL, - CPR, CUB, CUD, CUF, @@ -558,6 +558,8 @@ constexpr static auto allFunctionsArray() noexcept DECSED, DECSERA, DECSEL, + ANSIDSR, + DSR, XTRESTORE, XTSAVE, DECPS, diff --git a/src/vtbackend/Screen.cpp b/src/vtbackend/Screen.cpp index e71b5fc373..689e17ea47 100644 --- a/src/vtbackend/Screen.cpp +++ b/src/vtbackend/Screen.cpp @@ -2785,7 +2785,7 @@ namespace impl } template - ApplyResult CPR(Sequence const& seq, Screen& screen) + ApplyResult ANSIDSR(Sequence const& seq, Screen& screen) { switch (seq.param(0)) { @@ -2795,6 +2795,15 @@ namespace impl } } + template + ApplyResult DSR(Sequence const& seq, Screen& screen) + { + switch (seq.param(0)) + { + default: return ApplyResult::Unsupported; + } + } + template ApplyResult DECRQPSR(Sequence const& seq, Screen& screen) { @@ -3450,7 +3459,8 @@ ApplyResult Screen::apply(FunctionDefinition const& function, Sequence con case CPL: moveCursorToPrevLine(LineCount::cast_from(seq.param_or(0, Sequence::Parameter { 1 }))); break; - case CPR: return impl::CPR(seq, *this); + case ANSIDSR: return impl::ANSIDSR(seq, *this); + case DSR: return impl::DSR(seq, *this); case CUB: moveCursorBackward(seq.param_or(0, ColumnCount { 1 })); break; case CUD: moveCursorDown(seq.param_or(0, LineCount { 1 })); break; case CUF: moveCursorForward(seq.param_or(0, ColumnCount { 1 })); break; From 60781dca40036bfc94b39a93446d561484b3ae45 Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Tue, 10 Oct 2023 11:47:04 +0200 Subject: [PATCH 8/8] [vtbackend] Implements DSR ?996 to report color palette updates (explicit and unsolicited) and adds DEC mode 2031 to enable/disable unsolicited reports of that Signed-off-by: Christian Parpart --- .../color-palette-update-notifications.md | 40 +++++++++++++++++++ metainfo.xml | 2 + mkdocs.yml | 1 + src/vtbackend/Screen.cpp | 24 +++++++++++ src/vtbackend/Screen.h | 2 + src/vtbackend/Terminal.cpp | 3 ++ src/vtbackend/TerminalState.cpp | 1 + src/vtbackend/primitives.h | 6 +++ 8 files changed, 79 insertions(+) create mode 100644 docs/vt-extensions/color-palette-update-notifications.md diff --git a/docs/vt-extensions/color-palette-update-notifications.md b/docs/vt-extensions/color-palette-update-notifications.md new file mode 100644 index 0000000000..e40bf28027 --- /dev/null +++ b/docs/vt-extensions/color-palette-update-notifications.md @@ -0,0 +1,40 @@ +# Dark and Light Mode detection + +Most modern operating systems and desktop environments do support Dark and Light themes, +this includes at least MacOS, Windows, KDE Plasma, Gnome, and probably others. + +Some even support switching from dark to light and light to dark mode based on sun rise / sun set. + +In order to not make the terminal emulator look bad after such switch, we must +enable the applications inside the terminal to detect when the terminal has +updated the color palette. This may happen either due to the operaging system having +changed the current theme or simply because the user has explicitly requested to +reconfigure the currently used theme. + +Ideally we are getting CLI tools like [delta]() to query the theme mode before sending out RGB values +to the terminal to make the output look more in line with the rest of the desktop. + +But also TUIs like vim should be able to reflect dark/light mode changes as soon as the +desktop has charnged from light to dark and vice versa, e.g. to make it more pleasing to the eyes. + +## Query the current theme mode? + +Send `CSI ? 996 n` to the terminal to explicitly request the current +color preference (dark mode or light mode) by the operating system. + +The terminal will reply back in either of the two ways: + +VT sequence | description +------------------|--------------------------------- +`CSI ? 997 ; 1 n` | DSR reply to indicate dark mode +`CSI ? 997 ; 2 n` | DSR reply to indicate light mode + +## Request unsolicited DSR on color palette updates + +Send `CSI ? 2031 h` to the terminal to enable unsolicited DSR (device status report) messages +for color palette updates and `CSI ? 2031 l` respectively to disable it again. + +The sent out DSR looks equivalent to the already above mentioned. +This notification is not just sent when dark/light mode has been changed +by the operating system / desktop, but also if the user explicitly changed color scheme, +e.g. by configuration. diff --git a/metainfo.xml b/metainfo.xml index bd46707524..f01140cd75 100644 --- a/metainfo.xml +++ b/metainfo.xml @@ -129,6 +129,8 @@
  • Removes the ability to inline colorschemes within a configuration profile. Colorschemes must now always be referenced by their name.
  • Adds the ability to chose a color scheme based on the operating systems's dark/light mode setting. This will change live whenever the OS's dark/light mode setting changes as well (#604).
  • Adds VT sequence DECSSCLS (change scroll speed) and properly handle DECSCLM (enable slow scrolling mode) (#1204)
  • +
  • Adds VT sequence parameter ?996 to DSR to request a report of current color scheme dark/light mode hint.
  • +
  • Adds VT sequence `SM ?2031` and `RM ?2031` to enable/disable unsolicited DSR for color scheme updates by the user or OS.
  • Adds percentage value to Indicator Statusline to indicate scroll offset in scrollback buffer.
  • Adds inheritance of profiles in configuration file based on default profile (#1063).
  • Adds config option `profile.*.bell` to adjust BEL behavior and fixes (#1162) and (#1163).
  • diff --git a/mkdocs.yml b/mkdocs.yml index ab30a53903..ad7546dce4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -107,6 +107,7 @@ nav: - vt-extensions/index.md - vt-extensions/clickable-links.md - vt-extensions/vertical-line-marks.md + - vt-extensions/color-palette-update-notifications.md - vt-extensions/synchronized-output.md - vt-extensions/buffer-capture.md - vt-extensions/font-settings.md diff --git a/src/vtbackend/Screen.cpp b/src/vtbackend/Screen.cpp index 689e17ea47..56d9c04676 100644 --- a/src/vtbackend/Screen.cpp +++ b/src/vtbackend/Screen.cpp @@ -78,6 +78,9 @@ using std::vector; namespace vtbackend { +auto constexpr inline ColorPaletteUpdateDsrRequestId = 996; +auto constexpr inline ColorPaletteUpdateDsrReplyId = 997; + auto constexpr inline TabWidth = ColumnCount(8); auto const inline vtCaptureBufferLog = logstore::category("vt.ext.capturebuffer", @@ -92,6 +95,11 @@ auto const inline vtCaptureBufferLog = logstore::category("vt.ext.capturebuffer" namespace // {{{ helper { + constexpr bool isLightColor(RGBColor color) noexcept + { + return ((5 * color.green) + (2 * color.red) + color.blue) > 8 * 128; + } + template inline void sleep_for(std::chrono::duration const& rtime) { @@ -877,6 +885,19 @@ void Screen::reportCursorPosition() _terminal->reply("\033[{};{}R", logicalCursorPosition().line + 1, logicalCursorPosition().column + 1); } +template +CRISPY_REQUIRES(CellConcept) +void Screen::reportColorPaletteUpdate() +{ + auto constexpr DarkModeHint = 1; + auto constexpr LightModeHint = 2; + + auto const modeHint = isLightColor(_state->colorPalette.defaultForeground) ? DarkModeHint : LightModeHint; + + _terminal->reply("\033[?{};{}n", ColorPaletteUpdateDsrReplyId, modeHint); + _terminal->flushInput(); +} + template CRISPY_REQUIRES(CellConcept) void Screen::reportExtendedCursorPosition() @@ -2800,6 +2821,9 @@ namespace impl { switch (seq.param(0)) { + case ColorPaletteUpdateDsrRequestId: + screen.reportColorPaletteUpdate(); + return ApplyResult::Ok; default: return ApplyResult::Unsupported; } } diff --git a/src/vtbackend/Screen.h b/src/vtbackend/Screen.h index 314b499545..0dfd352d9f 100644 --- a/src/vtbackend/Screen.h +++ b/src/vtbackend/Screen.h @@ -50,6 +50,7 @@ class ScreenBase: public SequenceHandler void resetSavedCursorState() { _savedCursor = {}; } virtual void saveCursor() = 0; virtual void restoreCursor() = 0; + virtual void reportColorPaletteUpdate() = 0; [[nodiscard]] virtual Margin margin() const noexcept = 0; [[nodiscard]] virtual Margin& margin() noexcept = 0; @@ -220,6 +221,7 @@ class Screen final: public ScreenBase, public capabilities::StaticDatabase void deviceStatusReport(); // DSR void reportCursorPosition(); // CPR void reportExtendedCursorPosition(); // DECXCPR + void reportColorPaletteUpdate() override; void selectConformanceLevel(VTType level); void requestDynamicColor(DynamicColorName name); void requestCapability(capabilities::Code code); diff --git a/src/vtbackend/Terminal.cpp b/src/vtbackend/Terminal.cpp index 72030d6923..ab2426934c 100644 --- a/src/vtbackend/Terminal.cpp +++ b/src/vtbackend/Terminal.cpp @@ -2195,6 +2195,9 @@ void Terminal::resetColorPalette(ColorPalette const& colors) _state.defaultColorPalette = colors; _settings.colorPalette = colors; _factorySettings.colorPalette = colors; + + if (isModeEnabled(DECMode::ReportColorPaletteUpdated)) + _currentScreen.get().reportColorPaletteUpdate(); } void Terminal::pushColorPalette(size_t slot) diff --git a/src/vtbackend/TerminalState.cpp b/src/vtbackend/TerminalState.cpp index a5c42446c1..1fe54b749e 100644 --- a/src/vtbackend/TerminalState.cpp +++ b/src/vtbackend/TerminalState.cpp @@ -81,6 +81,7 @@ std::string to_string(DECMode mode) case DECMode::Unicode: return "Unicode"; case DECMode::TextReflow: return "TextReflow"; case DECMode::SixelCursorNextToGraphic: return "SixelCursorNextToGraphic"; + case DECMode::ReportColorPaletteUpdated: return "ReportColorPaletteUpdated"; } return fmt::format("({})", static_cast(mode)); } diff --git a/src/vtbackend/primitives.h b/src/vtbackend/primitives.h index 564b00ea3d..5fba012051 100644 --- a/src/vtbackend/primitives.h +++ b/src/vtbackend/primitives.h @@ -686,6 +686,10 @@ enum class DECMode // intersecting with the main page area. ReportGridCellSelection = 2030, + // If enabled, the terminal will report color palette changes to the application, + // if modified by the user or operating system (e.g. dark/light mode adaption). + ReportColorPaletteUpdated = 2031, + // If enabled (default, as per spec), then the cursor is left next to the graphic, // that is, the text cursor is placed at the position of the sixel cursor. // If disabled otherwise, the cursor is placed below the image, as if CR LF was sent, @@ -791,6 +795,7 @@ constexpr unsigned toDECModeNum(DECMode m) case DECMode::MouseAlternateScroll: return 1007; case DECMode::MousePassiveTracking: return 2029; case DECMode::ReportGridCellSelection: return 2030; + case DECMode::ReportColorPaletteUpdated: return 2031; case DECMode::BatchedRendering: return 2026; case DECMode::Unicode: return 2027; case DECMode::TextReflow: return 2028; @@ -837,6 +842,7 @@ constexpr bool isValidDECMode(unsigned int mode) noexcept case DECMode::MouseAlternateScroll: case DECMode::MousePassiveTracking: case DECMode::ReportGridCellSelection: + case DECMode::ReportColorPaletteUpdated: case DECMode::BatchedRendering: case DECMode::Unicode: case DECMode::TextReflow: