From f2b2a7eb4a465323467af7c60d2fa087cea93f4a Mon Sep 17 00:00:00 2001 From: ArsenArsen Date: Tue, 6 Jun 2017 01:26:26 +0200 Subject: [PATCH] Implement recording [!tested] [prob. broken] [1AM] Implement the basic barebone structure of GIF recording. While it is likely broken (probably black frames and similar issues) it's a good start. Good night. --- KShare.pro | 2 +- mainwindow.cpp | 10 ++++++ platformspecifics/mac/macbackend.cpp | 4 +++ platformspecifics/mac/macbackend.hpp | 3 +- platformspecifics/u32/u32backend.cpp | 7 +++- platformspecifics/u32/u32backend.hpp | 4 ++- platformspecifics/x11/x11backend.cpp | 5 +++ platformspecifics/x11/x11backend.hpp | 3 +- recording/recordingcontroller.cpp | 22 ++++++++++-- recording/recordingcontroller.hpp | 4 +++ recording/recordingformats.cpp | 54 ++++++++++++++++++++++++++-- recording/recordingformats.hpp | 11 ++++-- worker/worker.cpp | 3 ++ worker/worker.hpp | 1 + 14 files changed, 121 insertions(+), 12 deletions(-) diff --git a/KShare.pro b/KShare.pro index 3cbef22..561d5ac 100644 --- a/KShare.pro +++ b/KShare.pro @@ -93,7 +93,7 @@ mac { } else:win32 { SOURCES += $$PWD/platformspecifics/u32/u32backend.cpp HEADERS += $$PWD/platformspecifics/u32/u32backend.hpp - LIBS += -luser32 + LIBS += -luser32 -lkernel32 -lpthread QT += winextras } else:unix { SOURCES += $$PWD/platformspecifics/x11/x11backend.cpp diff --git a/mainwindow.cpp b/mainwindow.cpp index 56e07fd..3250e8d 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -88,6 +89,15 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi addHotkeyItem("Area image", "area", [] { screenshotter::area(); }); addHotkeyItem("Color picker", "picker", [] { ColorPickerScene::showPicker(); }); addHotkeyItem("Stop Recording", "recordingstop", [&] { controller->end(); }); + addHotkeyItem("Start Recording", "recordingstart", [&] { + RecordingContext *ctx = new RecordingContext; + RecordingFormats *format = new RecordingFormats(RecordingFormats::GIF); + ctx->consumer = format->getConsumer(); + ctx->finalizer = format->getFinalizer(); + ctx->validator = format->getValidator(); + ctx->format = format->getFormat(); + controller->start(ctx); + }); ui->quickMode->setChecked(settings::settings().value("quickMode", false).toBool()); ui->hideToTray->setChecked(settings::settings().value("hideOnClose", true).toBool()); diff --git a/platformspecifics/mac/macbackend.cpp b/platformspecifics/mac/macbackend.cpp index 0e45ff0..c750eb9 100644 --- a/platformspecifics/mac/macbackend.cpp +++ b/platformspecifics/mac/macbackend.cpp @@ -7,3 +7,7 @@ QPixmap PlatformBackend::getCursor() { // Some on how to do NSImage -> QPixmap: http://stackoverflow.com/a/2468961/3809164 // This is gonna be easier than with Windows } + +pid_t PlatformBackend::pid() { + return getpid(); +} diff --git a/platformspecifics/mac/macbackend.hpp b/platformspecifics/mac/macbackend.hpp index 324e375..b62eaf4 100644 --- a/platformspecifics/mac/macbackend.hpp +++ b/platformspecifics/mac/macbackend.hpp @@ -4,8 +4,9 @@ #include class PlatformBackend { - public: +public: QPixmap getCursor(); + pid_t pid(); static PlatformBackend &inst() { static PlatformBackend inst; return inst; diff --git a/platformspecifics/u32/u32backend.cpp b/platformspecifics/u32/u32backend.cpp index 80d3cec..96ea310 100644 --- a/platformspecifics/u32/u32backend.cpp +++ b/platformspecifics/u32/u32backend.cpp @@ -11,7 +11,8 @@ std::tuple PlatformBackend::getCursor() { if (cursorInfo.flags == CURSOR_SHOWING) { ICONINFO info; // It took me 5 hours to get to here if (GetIconInfo(cursorInfo.hCursor, &info)) { - return std::tuple(QPoint(info.xHotspot, info.yHotspot), QtWin::fromHBITMAP(info.hbmColor, QtWin::HBitmapAlpha)); + return std::tuple(QPoint(info.xHotspot, info.yHotspot), + QtWin::fromHBITMAP(info.hbmColor, QtWin::HBitmapAlpha)); } else return std::tuple(QPoint(0, 0), QPixmap()); } else @@ -19,3 +20,7 @@ std::tuple PlatformBackend::getCursor() { } else return std::tuple(QPoint(0, 0), QPixmap()); } + +DWORD pid() { + return GetCurrentProcessId(); +} diff --git a/platformspecifics/u32/u32backend.hpp b/platformspecifics/u32/u32backend.hpp index e779fc5..46e50e8 100644 --- a/platformspecifics/u32/u32backend.hpp +++ b/platformspecifics/u32/u32backend.hpp @@ -2,10 +2,12 @@ #define U32BACKEND_HPP #include +#include class PlatformBackend { - public: +public: std::tuple getCursor(); + DWORD pid(); static PlatformBackend &inst() { static PlatformBackend inst; return inst; diff --git a/platformspecifics/x11/x11backend.cpp b/platformspecifics/x11/x11backend.cpp index 2281831..decb605 100644 --- a/platformspecifics/x11/x11backend.cpp +++ b/platformspecifics/x11/x11backend.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -22,3 +23,7 @@ std::tuple PlatformBackend::getCursor() { QPixmap::fromImage(QImage((quint8 *)pixels, cursorReply->width, cursorReply->height, QImage::Format_ARGB32_Premultiplied))); } + +pid_t PlatformBackend::pid() { + return getpid(); +} diff --git a/platformspecifics/x11/x11backend.hpp b/platformspecifics/x11/x11backend.hpp index 808ad42..e72b8f8 100644 --- a/platformspecifics/x11/x11backend.hpp +++ b/platformspecifics/x11/x11backend.hpp @@ -4,8 +4,9 @@ #include class PlatformBackend { - public: +public: std::tuple getCursor(); + pid_t pid(); static PlatformBackend &inst() { static PlatformBackend inst; return inst; diff --git a/recording/recordingcontroller.cpp b/recording/recordingcontroller.cpp index 65bde7e..7dc1a00 100644 --- a/recording/recordingcontroller.cpp +++ b/recording/recordingcontroller.cpp @@ -30,18 +30,33 @@ bool RecordingController::start(RecordingContext *context) { bool RecordingController::end() { if (!isRunning()) return false; - timer.stop(); area = QRect(); preview->close(); preview = 0; - UploaderSingleton::inst().upload(_context->finalizer()); + WorkerContext *c = new WorkerContext; + c->consumer = [&](QImage) { queue(_context->finalizer()); }; + c->targetFormat = QImage::Format_Alpha8; + c->pixmap = QPixmap(0, 0); + frame = 0; time = 0; return true; } +void RecordingController::queue(QByteArray arr) { + QMutexLocker l(&lock); + uploadQueue.enqueue(arr); +} + void RecordingController::timeout() { if (isRunning()) { + if (!_context->validator()) { + preview->close(); + frame = 0; + time = 0; + preview = 0; + area = QRect(); + } time++; int localTime = time * timer.interval() - 3000; if (localTime > 0) { @@ -59,6 +74,9 @@ void RecordingController::timeout() { long second = localTime / 1000 % 60; long minute = localTime / 60000; preview->setTime(QString("%1:%2").arg(QString::number(minute)).arg(QString::number(second)), frame); + } else { + QMutexLocker l(&lock); + UploaderSingleton::inst().upload(uploadQueue.dequeue()); } } diff --git a/recording/recordingcontroller.hpp b/recording/recordingcontroller.hpp index 0754baf..f8d2073 100644 --- a/recording/recordingcontroller.hpp +++ b/recording/recordingcontroller.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -14,6 +15,7 @@ class RecordingContext { public: QImage::Format format; std::function consumer; + std::function validator; std::function finalizer; }; @@ -32,6 +34,8 @@ private slots: void startWithArea(QRect newArea); private: + QMutex lock; + QQueue uploadQueue; QRect area; RecordingContext *_context = 0; QTimer timer; diff --git a/recording/recordingformats.cpp b/recording/recordingformats.cpp index 8b6ca03..70c9c11 100644 --- a/recording/recordingformats.cpp +++ b/recording/recordingformats.cpp @@ -1,12 +1,52 @@ #include "recordingformats.hpp" +#include +#include +#include #include +#include +#include +#include +#include +#include +#include RecordingFormats::RecordingFormats(RecordingFormats::Format f) { + QString path = QStandardPaths::writableLocation(QStandardPaths::TempLocation); + + if (path.isEmpty()) { + validator = [] { return false; }; + return; + } + tmpDir = QDir(path); + QString name + = QString("KShareTemp-") + QString::number(PlatformBackend::inst().pid()) + "-" + QTime::currentTime().toString(); + tmpDir.mkdir(name); + tmpDir.cd(name); switch (f) { - case GIF: - // TODO + case GIF: { + iFormat = QImage::Format_Alpha8; + validator = [] { return true; }; + consumer = [&](QImage img) { frames.push_back(img); }; + finalizer = [&] { + if (frames.size() == 0) return QByteArray; + int f = 1; + uint32_t d = 1000 / settings::settings().value("recording/framerate", 30).toInt(); + QImage startImg = frames[0]; + GifWriter writer; + GifBegin(&writer, tmpDir.absoluteFilePath("resulting.gif"), startImg.width(), startImg.height(), d) + + for (QImage &a : frames){ GifWriteFrame(writer, a.bits(), a.width(), a.height(), d) } QFile res( + tmpDir.absoluteFilePath("resulting.gif")); + if (!res.open(QFile::ReadOnly)) { + return QByteArray; + } + QByteArray data = res.readAll(); + tmpDir.removeRecursively(); + return data; + }; break; + } default: break; } @@ -20,7 +60,15 @@ std::function RecordingFormats::getFinalizer() { return finalizer; } -QString RecordingFormats::getPrettyName(RecordingFormats::Format f) { +std::function RecordingFormats::getValidator() { + return validator; +} + +QImage::Format RecordingFormats::getFormat() { + return iFormat; +} + +QString RecordingFormats::getExt(RecordingFormats::Format f) { switch (f) { case None: return "None"; diff --git a/recording/recordingformats.hpp b/recording/recordingformats.hpp index 9a4e787..16fbe17 100644 --- a/recording/recordingformats.hpp +++ b/recording/recordingformats.hpp @@ -1,6 +1,7 @@ #ifndef RECORDINGFORMATS_HPP #define RECORDINGFORMATS_HPP +#include #include #include #include @@ -8,16 +9,22 @@ class RecordingFormats { public: - enum Format { None, GIF }; + enum Format { GIF, None }; RecordingFormats(Format f); std::function getConsumer(); std::function getFinalizer(); + std::function getValidator(); + QImage::Format getFormat(); - static QString getPrettyName(Format f); + static QString getExt(Format f); private: std::function consumer; + std::function validator; std::function finalizer; + QImage::Format iFormat; + QDir tmpDir; + int frame = 0; }; #endif // RECORDINGFORMATS_HPP diff --git a/worker/worker.cpp b/worker/worker.cpp index e629ddb..598ab68 100644 --- a/worker/worker.cpp +++ b/worker/worker.cpp @@ -18,6 +18,7 @@ void Worker::queue(WorkerContext *context) { c->image = context->pixmap.toImage(); c->consumer = context->consumer; c->targetFormat = context->targetFormat; + c->underlyingThing = context; inst->qqueue.enqueue(c); } @@ -60,6 +61,8 @@ void Worker::process() { if (!qqueue.isEmpty()) { _WorkerContext *c = qqueue.dequeue(); c->consumer(c->image.convertToFormat(c->targetFormat)); + delete c->underlyingThing; + delete c; } lock.unlock(); std::this_thread::sleep_for(std::chrono::milliseconds(10)); // STL likes it's scopes diff --git a/worker/worker.hpp b/worker/worker.hpp index 6e82db2..e433133 100644 --- a/worker/worker.hpp +++ b/worker/worker.hpp @@ -19,6 +19,7 @@ struct _WorkerContext { QImage image; QImage::Format targetFormat; std::function consumer; + WorkerContext *underlyingThing; }; class Worker : public QObject {