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.
This commit is contained in:
parent
080d5be40f
commit
f2b2a7eb4a
@ -93,7 +93,7 @@ mac {
|
|||||||
} else:win32 {
|
} else:win32 {
|
||||||
SOURCES += $$PWD/platformspecifics/u32/u32backend.cpp
|
SOURCES += $$PWD/platformspecifics/u32/u32backend.cpp
|
||||||
HEADERS += $$PWD/platformspecifics/u32/u32backend.hpp
|
HEADERS += $$PWD/platformspecifics/u32/u32backend.hpp
|
||||||
LIBS += -luser32
|
LIBS += -luser32 -lkernel32 -lpthread
|
||||||
QT += winextras
|
QT += winextras
|
||||||
} else:unix {
|
} else:unix {
|
||||||
SOURCES += $$PWD/platformspecifics/x11/x11backend.cpp
|
SOURCES += $$PWD/platformspecifics/x11/x11backend.cpp
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#include <colorpicker/colorpickerscene.hpp>
|
#include <colorpicker/colorpickerscene.hpp>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <hotkeying.hpp>
|
#include <hotkeying.hpp>
|
||||||
|
#include <recording/recordingformats.hpp>
|
||||||
#include <settings.hpp>
|
#include <settings.hpp>
|
||||||
#include <uploaders/uploadersingleton.hpp>
|
#include <uploaders/uploadersingleton.hpp>
|
||||||
|
|
||||||
@ -88,6 +89,15 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
|
|||||||
addHotkeyItem("Area image", "area", [] { screenshotter::area(); });
|
addHotkeyItem("Area image", "area", [] { screenshotter::area(); });
|
||||||
addHotkeyItem("Color picker", "picker", [] { ColorPickerScene::showPicker(); });
|
addHotkeyItem("Color picker", "picker", [] { ColorPickerScene::showPicker(); });
|
||||||
addHotkeyItem("Stop Recording", "recordingstop", [&] { controller->end(); });
|
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->quickMode->setChecked(settings::settings().value("quickMode", false).toBool());
|
||||||
ui->hideToTray->setChecked(settings::settings().value("hideOnClose", true).toBool());
|
ui->hideToTray->setChecked(settings::settings().value("hideOnClose", true).toBool());
|
||||||
|
@ -7,3 +7,7 @@ QPixmap PlatformBackend::getCursor() {
|
|||||||
// Some on how to do NSImage -> QPixmap: http://stackoverflow.com/a/2468961/3809164
|
// Some on how to do NSImage -> QPixmap: http://stackoverflow.com/a/2468961/3809164
|
||||||
// This is gonna be easier than with Windows
|
// This is gonna be easier than with Windows
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pid_t PlatformBackend::pid() {
|
||||||
|
return getpid();
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
class PlatformBackend {
|
class PlatformBackend {
|
||||||
public:
|
public:
|
||||||
QPixmap getCursor();
|
QPixmap getCursor();
|
||||||
|
pid_t pid();
|
||||||
static PlatformBackend &inst() {
|
static PlatformBackend &inst() {
|
||||||
static PlatformBackend inst;
|
static PlatformBackend inst;
|
||||||
return inst;
|
return inst;
|
||||||
|
@ -11,7 +11,8 @@ std::tuple<QPoint, QPixmap> PlatformBackend::getCursor() {
|
|||||||
if (cursorInfo.flags == CURSOR_SHOWING) {
|
if (cursorInfo.flags == CURSOR_SHOWING) {
|
||||||
ICONINFO info; // It took me 5 hours to get to here
|
ICONINFO info; // It took me 5 hours to get to here
|
||||||
if (GetIconInfo(cursorInfo.hCursor, &info)) {
|
if (GetIconInfo(cursorInfo.hCursor, &info)) {
|
||||||
return std::tuple<QPoint, QPixmap>(QPoint(info.xHotspot, info.yHotspot), QtWin::fromHBITMAP(info.hbmColor, QtWin::HBitmapAlpha));
|
return std::tuple<QPoint, QPixmap>(QPoint(info.xHotspot, info.yHotspot),
|
||||||
|
QtWin::fromHBITMAP(info.hbmColor, QtWin::HBitmapAlpha));
|
||||||
} else
|
} else
|
||||||
return std::tuple<QPoint, QPixmap>(QPoint(0, 0), QPixmap());
|
return std::tuple<QPoint, QPixmap>(QPoint(0, 0), QPixmap());
|
||||||
} else
|
} else
|
||||||
@ -19,3 +20,7 @@ std::tuple<QPoint, QPixmap> PlatformBackend::getCursor() {
|
|||||||
} else
|
} else
|
||||||
return std::tuple<QPoint, QPixmap>(QPoint(0, 0), QPixmap());
|
return std::tuple<QPoint, QPixmap>(QPoint(0, 0), QPixmap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DWORD pid() {
|
||||||
|
return GetCurrentProcessId();
|
||||||
|
}
|
||||||
|
@ -2,10 +2,12 @@
|
|||||||
#define U32BACKEND_HPP
|
#define U32BACKEND_HPP
|
||||||
|
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
class PlatformBackend {
|
class PlatformBackend {
|
||||||
public:
|
public:
|
||||||
std::tuple<QPoint, QPixmap> getCursor();
|
std::tuple<QPoint, QPixmap> getCursor();
|
||||||
|
DWORD pid();
|
||||||
static PlatformBackend &inst() {
|
static PlatformBackend &inst() {
|
||||||
static PlatformBackend inst;
|
static PlatformBackend inst;
|
||||||
return inst;
|
return inst;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
#include <QX11Info>
|
#include <QX11Info>
|
||||||
|
#include <unistd.h>
|
||||||
#include <xcb/xcb_cursor.h>
|
#include <xcb/xcb_cursor.h>
|
||||||
#include <xcb/xcb_util.h>
|
#include <xcb/xcb_util.h>
|
||||||
#include <xcb/xfixes.h>
|
#include <xcb/xfixes.h>
|
||||||
@ -22,3 +23,7 @@ std::tuple<QPoint, QPixmap> PlatformBackend::getCursor() {
|
|||||||
QPixmap::fromImage(QImage((quint8 *)pixels, cursorReply->width, cursorReply->height,
|
QPixmap::fromImage(QImage((quint8 *)pixels, cursorReply->width, cursorReply->height,
|
||||||
QImage::Format_ARGB32_Premultiplied)));
|
QImage::Format_ARGB32_Premultiplied)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pid_t PlatformBackend::pid() {
|
||||||
|
return getpid();
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
class PlatformBackend {
|
class PlatformBackend {
|
||||||
public:
|
public:
|
||||||
std::tuple<QPoint, QPixmap> getCursor();
|
std::tuple<QPoint, QPixmap> getCursor();
|
||||||
|
pid_t pid();
|
||||||
static PlatformBackend &inst() {
|
static PlatformBackend &inst() {
|
||||||
static PlatformBackend inst;
|
static PlatformBackend inst;
|
||||||
return inst;
|
return inst;
|
||||||
|
@ -30,18 +30,33 @@ bool RecordingController::start(RecordingContext *context) {
|
|||||||
|
|
||||||
bool RecordingController::end() {
|
bool RecordingController::end() {
|
||||||
if (!isRunning()) return false;
|
if (!isRunning()) return false;
|
||||||
timer.stop();
|
|
||||||
area = QRect();
|
area = QRect();
|
||||||
preview->close();
|
preview->close();
|
||||||
preview = 0;
|
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;
|
frame = 0;
|
||||||
time = 0;
|
time = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RecordingController::queue(QByteArray arr) {
|
||||||
|
QMutexLocker l(&lock);
|
||||||
|
uploadQueue.enqueue(arr);
|
||||||
|
}
|
||||||
|
|
||||||
void RecordingController::timeout() {
|
void RecordingController::timeout() {
|
||||||
if (isRunning()) {
|
if (isRunning()) {
|
||||||
|
if (!_context->validator()) {
|
||||||
|
preview->close();
|
||||||
|
frame = 0;
|
||||||
|
time = 0;
|
||||||
|
preview = 0;
|
||||||
|
area = QRect();
|
||||||
|
}
|
||||||
time++;
|
time++;
|
||||||
int localTime = time * timer.interval() - 3000;
|
int localTime = time * timer.interval() - 3000;
|
||||||
if (localTime > 0) {
|
if (localTime > 0) {
|
||||||
@ -59,6 +74,9 @@ void RecordingController::timeout() {
|
|||||||
long second = localTime / 1000 % 60;
|
long second = localTime / 1000 % 60;
|
||||||
long minute = localTime / 60000;
|
long minute = localTime / 60000;
|
||||||
preview->setTime(QString("%1:%2").arg(QString::number(minute)).arg(QString::number(second)), frame);
|
preview->setTime(QString("%1:%2").arg(QString::number(minute)).arg(QString::number(second)), frame);
|
||||||
|
} else {
|
||||||
|
QMutexLocker l(&lock);
|
||||||
|
UploaderSingleton::inst().upload(uploadQueue.dequeue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
|
#include <QMutex>
|
||||||
#include <QRect>
|
#include <QRect>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
@ -14,6 +15,7 @@ class RecordingContext {
|
|||||||
public:
|
public:
|
||||||
QImage::Format format;
|
QImage::Format format;
|
||||||
std::function<void(QImage)> consumer;
|
std::function<void(QImage)> consumer;
|
||||||
|
std::function<bool()> validator;
|
||||||
std::function<QByteArray()> finalizer;
|
std::function<QByteArray()> finalizer;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -32,6 +34,8 @@ private slots:
|
|||||||
void startWithArea(QRect newArea);
|
void startWithArea(QRect newArea);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QMutex lock;
|
||||||
|
QQueue<QByteArray> uploadQueue;
|
||||||
QRect area;
|
QRect area;
|
||||||
RecordingContext *_context = 0;
|
RecordingContext *_context = 0;
|
||||||
QTimer timer;
|
QTimer timer;
|
||||||
|
@ -1,12 +1,52 @@
|
|||||||
#include "recordingformats.hpp"
|
#include "recordingformats.hpp"
|
||||||
|
|
||||||
|
#include <QBuffer>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QDir>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <gif-h/gif.h>
|
||||||
|
#include <platformbackend.hpp>
|
||||||
|
#include <settings.hpp>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
RecordingFormats::RecordingFormats(RecordingFormats::Format f) {
|
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) {
|
switch (f) {
|
||||||
case GIF:
|
case GIF: {
|
||||||
// TODO
|
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;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -20,7 +60,15 @@ std::function<QByteArray()> RecordingFormats::getFinalizer() {
|
|||||||
return finalizer;
|
return finalizer;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString RecordingFormats::getPrettyName(RecordingFormats::Format f) {
|
std::function<bool()> RecordingFormats::getValidator() {
|
||||||
|
return validator;
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage::Format RecordingFormats::getFormat() {
|
||||||
|
return iFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString RecordingFormats::getExt(RecordingFormats::Format f) {
|
||||||
switch (f) {
|
switch (f) {
|
||||||
case None:
|
case None:
|
||||||
return "None";
|
return "None";
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#ifndef RECORDINGFORMATS_HPP
|
#ifndef RECORDINGFORMATS_HPP
|
||||||
#define RECORDINGFORMATS_HPP
|
#define RECORDINGFORMATS_HPP
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
@ -8,16 +9,22 @@
|
|||||||
|
|
||||||
class RecordingFormats {
|
class RecordingFormats {
|
||||||
public:
|
public:
|
||||||
enum Format { None, GIF };
|
enum Format { GIF, None };
|
||||||
RecordingFormats(Format f);
|
RecordingFormats(Format f);
|
||||||
std::function<void(QImage)> getConsumer();
|
std::function<void(QImage)> getConsumer();
|
||||||
std::function<QByteArray()> getFinalizer();
|
std::function<QByteArray()> getFinalizer();
|
||||||
|
std::function<bool()> getValidator();
|
||||||
|
QImage::Format getFormat();
|
||||||
|
|
||||||
static QString getPrettyName(Format f);
|
static QString getExt(Format f);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::function<void(QImage)> consumer;
|
std::function<void(QImage)> consumer;
|
||||||
|
std::function<bool()> validator;
|
||||||
std::function<QByteArray()> finalizer;
|
std::function<QByteArray()> finalizer;
|
||||||
|
QImage::Format iFormat;
|
||||||
|
QDir tmpDir;
|
||||||
|
int frame = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // RECORDINGFORMATS_HPP
|
#endif // RECORDINGFORMATS_HPP
|
||||||
|
@ -18,6 +18,7 @@ void Worker::queue(WorkerContext *context) {
|
|||||||
c->image = context->pixmap.toImage();
|
c->image = context->pixmap.toImage();
|
||||||
c->consumer = context->consumer;
|
c->consumer = context->consumer;
|
||||||
c->targetFormat = context->targetFormat;
|
c->targetFormat = context->targetFormat;
|
||||||
|
c->underlyingThing = context;
|
||||||
inst->qqueue.enqueue(c);
|
inst->qqueue.enqueue(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,6 +61,8 @@ void Worker::process() {
|
|||||||
if (!qqueue.isEmpty()) {
|
if (!qqueue.isEmpty()) {
|
||||||
_WorkerContext *c = qqueue.dequeue();
|
_WorkerContext *c = qqueue.dequeue();
|
||||||
c->consumer(c->image.convertToFormat(c->targetFormat));
|
c->consumer(c->image.convertToFormat(c->targetFormat));
|
||||||
|
delete c->underlyingThing;
|
||||||
|
delete c;
|
||||||
}
|
}
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(10)); // STL likes it's scopes
|
std::this_thread::sleep_for(std::chrono::milliseconds(10)); // STL likes it's scopes
|
||||||
|
@ -19,6 +19,7 @@ struct _WorkerContext {
|
|||||||
QImage image;
|
QImage image;
|
||||||
QImage::Format targetFormat;
|
QImage::Format targetFormat;
|
||||||
std::function<void(QImage)> consumer;
|
std::function<void(QImage)> consumer;
|
||||||
|
WorkerContext *underlyingThing;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Worker : public QObject {
|
class Worker : public QObject {
|
||||||
|
Loading…
Reference in New Issue
Block a user