Skip to content
Closed
19 changes: 16 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ include(CTest)
set(CMAKE_AUTOMOC ON)

find_package(PkgConfig REQUIRED)
find_package(Qt6 6.8 REQUIRED COMPONENTS Concurrent Core Gui Test Widgets)
find_package(Qt6 6.8 REQUIRED COMPONENTS Concurrent Core Gui Network Test Widgets)
find_package(LayerShellQt REQUIRED)
pkg_check_modules(WaylandClient REQUIRED IMPORTED_TARGET wayland-client)
find_program(WAYLAND_SCANNER wayland-scanner REQUIRED)
Expand Down Expand Up @@ -43,6 +43,8 @@ add_library(omasnap-core STATIC
src/pin-file.hpp
src/pin-layout.cpp
src/pin-layout.hpp
src/shelf-layout.cpp
src/shelf-layout.hpp
src/capture.cpp
src/capture.hpp
src/cut.cpp
Expand Down Expand Up @@ -128,10 +130,19 @@ target_link_libraries(omasnap-core PUBLIC
PkgConfig::WaylandClient
)

qt_add_executable(omasnap src/main.cpp src/pin.cpp src/pin.hpp)
qt_add_executable(omasnap
src/main.cpp
src/pin.cpp
src/pin.hpp
src/shelf.cpp
src/shelf.hpp
)
target_compile_options(omasnap PRIVATE -Wall -Wextra -Wpedantic)
target_compile_definitions(omasnap PRIVATE OMASNAP_VERSION="${PROJECT_VERSION}")
target_link_libraries(omasnap PRIVATE omasnap-core)
target_link_libraries(omasnap PRIVATE
omasnap-core
Qt6::Network
)
target_precompile_headers(omasnap REUSE_FROM omasnap-core)

if(BUILD_TESTING)
Expand Down Expand Up @@ -169,6 +180,8 @@ qt_add_executable(omasnap-smoke
src/auto-capture.hpp
tests/stitch-smoke.cpp
tests/stitch-smoke.hpp
tests/shelf-layout-smoke.cpp
tests/shelf-layout-smoke.hpp
)
target_include_directories(omasnap-smoke PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/tests"
Expand Down
16 changes: 16 additions & 0 deletions src/capture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1399,6 +1399,22 @@ QString temporarySnapshotPath() {
.arg(nonce, 8, 16, QChar('0')));
}

QString shelfSnapshotPath() {
return runtimePath(QStringLiteral("shelf-%1-%2.png")
.arg(QCoreApplication::applicationPid())
.arg(QRandomGenerator::global()->generate64(), 16, 16,
QChar('0')));
}

bool isShelfSnapshotPath(const QString &path) {
const QString runtime = secureRuntimeDirectory();
const QFileInfo info(path);
return !runtime.isEmpty() &&
QDir::cleanPath(info.absolutePath()) == QDir::cleanPath(runtime) &&
info.fileName().startsWith(QStringLiteral("shelf-")) &&
info.suffix().compare(QStringLiteral("png"), Qt::CaseInsensitive) == 0;
}

QString temporaryExportPath() {
return runtimePath(QStringLiteral("export-%1-%2.png")
.arg(QCoreApplication::applicationPid())
Expand Down
2 changes: 2 additions & 0 deletions src/capture.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,8 @@ QImage applyRedactionsScaled(QImage image, const QVector<Annotation> &redactions
QString &error,
const QString &appSlug = {});
[[nodiscard]] QString temporarySnapshotPath();
[[nodiscard]] QString shelfSnapshotPath();
[[nodiscard]] bool isShelfSnapshotPath(const QString &path);
[[nodiscard]] QString pinnedSnapshotPath(int index);
void prunePinnedSnapshots();
/**
Expand Down
79 changes: 63 additions & 16 deletions src/editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -629,9 +629,11 @@ QPointF centeredCreationStart(CaptureEditor::Tool tool, const QPointF &center,

CaptureEditor::CaptureEditor(CaptureData capture, CaptureMode mode,
QuickOutputMode quickOutput, OperationLog log,
PostCaptureHandler postCaptureHandler,
QWidget *parent)
: QWidget(parent), capture_(std::move(capture)),
quickOutputMode_(quickOutput) {
quickOutputMode_(quickOutput),
postCaptureHandler_(std::move(postCaptureHandler)) {
startupTimingMark("CaptureEditor constructor entered");
pristineSource_ = capture_.source;
pristineLogicalSize_ = capture_.previewSize;
Expand Down Expand Up @@ -862,10 +864,15 @@ CaptureEditor::CaptureEditor(CaptureData capture, CaptureMode mode,
? QStringLiteral("Editing image from file · Copy/Save to output")
: QStringLiteral("Full screen selected · native resolution · "
"outer handles crop");
if (mode == CaptureMode::File)
if (mode == CaptureMode::File) {
enterEdit(editStatus);
else
} else if (postCaptureHandler_) {
QTimer::singleShot(0, this, [this, editStatus] {
enterSelectedCapture(editStatus);
});
} else {
enterSelectedCapture(editStatus);
}
} else if (mode == CaptureMode::Window) {
windowMode_ = true;
hoveredWindow_ = windowAt(cursor_);
Expand Down Expand Up @@ -2924,6 +2931,22 @@ void CaptureEditor::enterSelectedCapture(QString editStatus) {
enterExport();
return;
}
// A fresh capture with somewhere to go does not open the editor: it is
// handed off and the window closes. An explicit --copy/--save above still
// wins, and a file opened for editing never reaches here.
if (postCaptureHandler_) {
const QImage image = renderCurrentOutput();
QString error;
if (image.isNull() || !postCaptureHandler_(image, error)) {
setStatus(error.isEmpty()
? QStringLiteral("Could not add capture to Shelf")
: error);
update();
return;
}
close();
return;
}
enterEdit(std::move(editStatus));
}

Expand Down Expand Up @@ -3373,10 +3396,12 @@ void CaptureEditor::finish(OutputMode mode) {
const QImage backdrop = customBackdrop_;
const QString appSlug =
appFilenameSlug(dominantAppClass(capture_.windows, selection_));
const QString replacementOutput = replacementOutputPath_;
finishWatcher_.setFuture(QtConcurrent::run([captureCopy, selection,
annotations, background,
imageShadow, canvasBoundary,
backdrop, appSlug, mode]() {
backdrop, appSlug,
replacementOutput, mode]() {
FinishResult result;
result.mode = mode;
const QImage image = renderCapture(captureCopy, selection, annotations,
Expand All @@ -3403,11 +3428,22 @@ void CaptureEditor::finish(OutputMode mode) {
}
}
if (mode == OutputMode::Save || mode == OutputMode::Both) {
result.saved = moveSnapshotToScreenshots(exportPath, error, appSlug);
if (result.saved.isEmpty()) {
if (!replacementOutput.isEmpty()) {
if (!saveTemporarySnapshot(image, replacementOutput, error, -1)) {
QFile::remove(exportPath);
result.error = error;
return result;
}
result.saved = replacementOutput;
QFile::remove(exportPath);
result.error = error;
return result;
QFile::remove(operationLogPath(result.saved));
} else {
result.saved = moveSnapshotToScreenshots(exportPath, error, appSlug);
if (result.saved.isEmpty()) {
QFile::remove(exportPath);
result.error = error;
return result;
}
}
} else {
QFile::remove(exportPath);
Expand Down Expand Up @@ -3435,19 +3471,30 @@ void CaptureEditor::completeFinish(const FinishResult &result) {
QString recentError;
const bool drained = waitForSnapshot();
snapshotDirty_ = false;
if (drained && recordRecentSnap(snapshotPath_, workingLogPath(),
result.thumbnail, recentError)) {
if (editingRecent_)
removeRecentSnap(*editingRecent_);
} else {
if (!recentError.isEmpty())
qWarning().noquote() << recentError;
if (!replacementOutputPath_.isEmpty()) {
QFile::remove(workingLogPath());
QFile::remove(snapshotPath_);
} else {
if (drained && recordRecentSnap(snapshotPath_, workingLogPath(),
result.thumbnail, recentError)) {
if (editingRecent_)
removeRecentSnap(*editingRecent_);
} else {
if (!recentError.isEmpty())
qWarning().noquote() << recentError;
QFile::remove(workingLogPath());
QFile::remove(snapshotPath_);
}
}
snapshotPath_.clear();
}
if (result.mode == OutputMode::Copy)
if (!replacementOutputPath_.isEmpty() && result.mode == OutputMode::Save)
sendCaptureNotification(QStringLiteral("Shelf screenshot updated"));
else if (!replacementOutputPath_.isEmpty() &&
result.mode == OutputMode::Both)
sendCaptureNotification(
QStringLiteral("Shelf screenshot updated and copied"));
else if (result.mode == OutputMode::Copy)
sendCaptureNotification(QStringLiteral("Screenshot copied to clipboard"));
else if (result.mode == OutputMode::Save)
sendCaptureNotification(QStringLiteral("Screenshot saved"), result.saved);
Expand Down
11 changes: 11 additions & 0 deletions src/editor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
#include <QTimer>
#include <QWidget>

#include <functional>
#include <optional>
#include <utility>

class QKeyEvent;
class QMouseEvent;
Expand Down Expand Up @@ -44,11 +46,14 @@ class CaptureEditor final : public QWidget {
Q_OBJECT
public:
enum class CaptureMode { Region, Scroll, Window, Fullscreen, File };
using PostCaptureHandler =
std::function<bool(const QImage &image, QString &error)>;

explicit CaptureEditor(CaptureData capture,
CaptureMode mode = CaptureMode::Region,
QuickOutputMode quickOutput = QuickOutputMode::None,
OperationLog log = {},
PostCaptureHandler postCaptureHandler = {},
QWidget *parent = nullptr);
~CaptureEditor() override;

Expand Down Expand Up @@ -121,6 +126,10 @@ class CaptureEditor final : public QWidget {
* for it would render the full capture for nothing and stall process exit.
*/
void setSuppressSnapshots(bool suppress) { suppressSnapshots_ = suppress; }
/** Save/Enter replaces this Shelf image instead of creating a new file. */
void setReplacementOutputPath(QString path) {
replacementOutputPath_ = std::move(path);
}

protected:
bool eventFilter(QObject *watched, QEvent *event) override;
Expand Down Expand Up @@ -759,7 +768,9 @@ class CaptureEditor final : public QWidget {
bool dragStartStateValid_ = false;
bool dragChanged_ = false;
QString snapshotPath_;
QString replacementOutputPath_;
QuickOutputMode quickOutputMode_ = QuickOutputMode::None;
PostCaptureHandler postCaptureHandler_;
int pinCount_ = 0;
QString status_ =
QStringLiteral("Drag to select an area · Space selects a window");
Expand Down
Loading