diff --git a/metainfo.xml b/metainfo.xml
index 9c56ae7bd5..e5e25f5dc7 100644
--- a/metainfo.xml
+++ b/metainfo.xml
@@ -109,6 +109,7 @@
- Expose current profile's name through env var `CONTOUR_PROFILE` (#1637)
- Add terminal tabs (#90)
+ - Add `SaveScreenshot` and `CopyScreenshot` action (#210)
- Add binding to exit normal mode with `Esc` (#1604)
- Add config option to switch into insert mode after yank (#1604)
- Improves window size/resize handling on HiDPI monitor settings (#1628)
diff --git a/src/contour/Actions.cpp b/src/contour/Actions.cpp
index 73f2740426..5822f7397d 100644
--- a/src/contour/Actions.cpp
+++ b/src/contour/Actions.cpp
@@ -54,6 +54,8 @@ optional fromString(string const& name)
mapAction("ResetConfig"),
mapAction("ResetFontSize"),
mapAction("ScreenshotVT"),
+ mapAction("SaveScreenshot"),
+ mapAction("CopyScreenshot"),
mapAction("ScrollDown"),
mapAction("ScrollMarkDown"),
mapAction("ScrollMarkUp"),
diff --git a/src/contour/Actions.h b/src/contour/Actions.h
index ddf5f92a9e..a50d1d0810 100644
--- a/src/contour/Actions.h
+++ b/src/contour/Actions.h
@@ -54,6 +54,8 @@ struct ReloadConfig{ std::optional profileName; };
struct ResetConfig{};
struct ResetFontSize{};
struct ScreenshotVT{};
+struct SaveScreenshot{};
+struct CopyScreenshot{};
struct ScrollDown{};
struct ScrollMarkDown{};
struct ScrollMarkUp{};
@@ -110,6 +112,8 @@ using Action = std::variant: std::formatter
HANDLE_ACTION(ResetConfig);
HANDLE_ACTION(ResetFontSize);
HANDLE_ACTION(ScreenshotVT);
+ HANDLE_ACTION(CopyScreenshot);
+ HANDLE_ACTION(SaveScreenshot);
HANDLE_ACTION(ScrollDown);
HANDLE_ACTION(ScrollMarkDown);
HANDLE_ACTION(ScrollMarkUp);
diff --git a/src/contour/ConfigDocumentation.h b/src/contour/ConfigDocumentation.h
index 30663d9846..ee5d6c0fb2 100644
--- a/src/contour/ConfigDocumentation.h
+++ b/src/contour/ConfigDocumentation.h
@@ -765,6 +765,8 @@ constexpr StringLiteral InputMappingsConfig {
"it. Attention, all your current configuration will be lost due to overwrite!\n"
"{comment} - ResetFontSize Resets font size to what is configured in the config file.\n"
"{comment} - ScreenshotVT Takes a screenshot in form of VT escape sequences.\n"
+ "{comment} - SaveScreenshot Takes a screenshot and saves it into a file.\n"
+ "{comment} - CopyScreenshot Takes a screenshot and puts it into the system clipboard\n"
"{comment} - ScrollDown Scrolls down by the multiplier factor.\n"
"{comment} - ScrollMarkDown Scrolls one mark down (if none present, bottom of the screen)\n"
"{comment} - ScrollMarkUp Scrolls one mark up\n"
diff --git a/src/contour/TerminalSession.cpp b/src/contour/TerminalSession.cpp
index 5d5a6a898b..860a789007 100644
--- a/src/contour/TerminalSession.cpp
+++ b/src/contour/TerminalSession.cpp
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
+#include
#include
#include
#include
@@ -1184,6 +1185,33 @@ bool TerminalSession::operator()(actions::ScreenshotVT)
return true;
}
+bool TerminalSession::operator()(actions::SaveScreenshot)
+{
+ auto savePath =
+ app().dumpStateAtExit().value_or(crispy::app::instance()->localStateDir())
+ / fs::path(std::format("contour-screenshot-{:%Y-%m-%d-%H-%M-%S}.png", chrono::system_clock::now()));
+
+ _display->setScreenshotOutput(savePath);
+ auto message = std::format("Saving screenshot to {}", savePath.string());
+ sessionLog()(message);
+
+ _display->post(
+ [this, message]() { emit showNotification("Screenshot", QString::fromStdString(message)); });
+ return true;
+}
+
+bool TerminalSession::operator()(actions::CopyScreenshot)
+{
+ _display->setScreenshotOutput(std::monostate {});
+ auto message = std::format("Saving screenshot to clipboard");
+ sessionLog()(message);
+
+ _display->post(
+ [this, message]() { emit showNotification("Screenshot", QString::fromStdString(message)); });
+
+ return true;
+}
+
bool TerminalSession::operator()(actions::ScrollDown)
{
terminal().viewport().scrollDown(_profile.history.value().historyScrollMultiplier);
diff --git a/src/contour/TerminalSession.h b/src/contour/TerminalSession.h
index d31ec07fe4..0ae7d8f79b 100644
--- a/src/contour/TerminalSession.h
+++ b/src/contour/TerminalSession.h
@@ -327,6 +327,8 @@ class TerminalSession: public QAbstractItemModel, public vtbackend::Terminal::Ev
bool operator()(actions::ResetConfig);
bool operator()(actions::ResetFontSize);
bool operator()(actions::ScreenshotVT);
+ bool operator()(actions::CopyScreenshot);
+ bool operator()(actions::SaveScreenshot);
bool operator()(actions::ScrollDown);
bool operator()(actions::ScrollMarkDown);
bool operator()(actions::ScrollMarkUp);
diff --git a/src/contour/display/TerminalDisplay.cpp b/src/contour/display/TerminalDisplay.cpp
index d39ee24761..dcc5345f1b 100644
--- a/src/contour/display/TerminalDisplay.cpp
+++ b/src/contour/display/TerminalDisplay.cpp
@@ -40,6 +40,7 @@
#include
#include
#include
+#include
#include
namespace fs = std::filesystem;
@@ -736,6 +737,20 @@ void TerminalDisplay::onAfterRendering()
// We use this to schedule the next rendering frame, if needed.
// This signal is emitted from the scene graph rendering thread
paint();
+ if (_saveScreenshot)
+ {
+ std::visit(crispy::overloaded { [&](const std::filesystem::path& path) {
+ screenshot().save(QString::fromStdString(path.string()));
+ },
+ [&](std::monostate) {
+ if (QClipboard* clipboard = QGuiApplication::clipboard();
+ clipboard != nullptr)
+ clipboard->setImage(screenshot(), QClipboard::Clipboard);
+ } },
+ _saveScreenshot.value());
+
+ _saveScreenshot = std::nullopt;
+ }
if (!_state.finish())
{
@@ -1092,6 +1107,18 @@ void TerminalDisplay::doDumpState()
_doDumpState = true;
}
+QImage TerminalDisplay::screenshot()
+{
+ _renderer->render(terminal(), _renderingPressure);
+ auto [size, image] = _renderTarget->takeScreenshot();
+
+ return QImage(image.data(),
+ size.width.as(),
+ size.height.as(),
+ QImage::Format_RGBA8888_Premultiplied)
+ .mirrored(false, true);
+}
+
void TerminalDisplay::doDumpStateInternal()
{
@@ -1178,10 +1205,7 @@ void TerminalDisplay::doDumpStateInternal()
auto screenshotFilePath = targetDir / "screenshot.png";
displayLog()("Saving screenshot to: {}", screenshotFilePath.generic_string());
- auto [size, image] = _renderTarget->takeScreenshot();
- QImage(image.data(), size.width.as(), size.height.as(), QImage::Format_RGBA8888_Premultiplied)
- .mirrored(false, true)
- .save(QString::fromStdString(screenshotFilePath.string()));
+ screenshot().save(QString::fromStdString(screenshotFilePath.string()));
}
void TerminalDisplay::notify(std::string_view /*_title*/, std::string_view /*_body*/)
diff --git a/src/contour/display/TerminalDisplay.h b/src/contour/display/TerminalDisplay.h
index ed336143b0..9214e48571 100644
--- a/src/contour/display/TerminalDisplay.h
+++ b/src/contour/display/TerminalDisplay.h
@@ -20,17 +20,17 @@
#include
#include
#include
+#include
#include
-#include
-
+#include
+#include
+#include
+#include
#if defined(CONTOUR_PERF_STATS)
#include
#endif
-#include
-#include
-
namespace contour::display
{
@@ -151,6 +151,7 @@ class TerminalDisplay: public QQuickItem
void onSelectionCompleted();
void bufferChanged(vtbackend::ScreenType);
void discardImage(vtbackend::Image const&);
+ void setScreenshotOutput(auto&& where) { _saveScreenshot = std::forward(where); }
// }}}
[[nodiscard]] std::optional queryContentScaleOverride() const;
@@ -220,6 +221,7 @@ class TerminalDisplay: public QQuickItem
// helper methods
//
void doDumpStateInternal();
+ QImage screenshot();
void createRenderer();
[[nodiscard]] QMatrix4x4 createModelMatrix() const;
void configureScreenHooks();
@@ -274,6 +276,7 @@ class TerminalDisplay: public QQuickItem
RenderStateManager _state;
bool _doDumpState = false;
+ std::optional> _saveScreenshot { std::nullopt };
QFileSystemWatcher _filesystemWatcher;
QMediaPlayer _mediaPlayer;