2023-05-15 15:29:47 -04:00
|
|
|
// This file is part of Motion Watch.
|
|
|
|
|
|
|
|
// Motion Watch is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
|
|
|
|
// Motion Watch is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
|
|
|
|
#include "camera.h"
|
|
|
|
|
2023-07-31 11:16:07 -04:00
|
|
|
Camera::Camera(QObject *parent) : QObject(parent) {}
|
2023-05-15 15:29:47 -04:00
|
|
|
|
2023-05-17 15:06:58 -04:00
|
|
|
int Camera::start(const QStringList &args)
|
2023-05-15 15:29:47 -04:00
|
|
|
{
|
2023-07-31 11:16:07 -04:00
|
|
|
if (rdConf(getParam("-c", args), &shared))
|
2023-10-19 15:04:39 -04:00
|
|
|
{
|
|
|
|
auto thr1 = new QThread(nullptr);
|
|
|
|
auto thr2 = new QThread(nullptr);
|
|
|
|
auto thr3 = new QThread(nullptr);
|
|
|
|
|
2023-10-27 15:43:17 -04:00
|
|
|
new Upkeep(&shared, thr1, nullptr);
|
|
|
|
new EventLoop(&shared, thr2, nullptr);
|
|
|
|
new DetectLoop(&shared, thr3, nullptr);
|
|
|
|
|
2023-10-19 15:04:39 -04:00
|
|
|
thr1->start();
|
|
|
|
thr2->start();
|
|
|
|
thr3->start();
|
2023-05-15 15:29:47 -04:00
|
|
|
}
|
|
|
|
|
2023-05-17 15:06:58 -04:00
|
|
|
return shared.retCode;
|
2023-05-15 15:29:47 -04:00
|
|
|
}
|
|
|
|
|
2023-05-26 16:12:53 -04:00
|
|
|
Loop::Loop(shared_t *sharedRes, QThread *thr, QObject *parent) : QObject(parent)
|
2023-05-15 15:29:47 -04:00
|
|
|
{
|
|
|
|
shared = sharedRes;
|
|
|
|
heartBeat = 10;
|
2023-05-26 16:12:53 -04:00
|
|
|
loopTimer = 0;
|
2023-05-17 15:06:58 -04:00
|
|
|
|
2023-05-27 09:33:14 -04:00
|
|
|
connect(thr, &QThread::started, this, &Loop::init);
|
2023-05-17 15:06:58 -04:00
|
|
|
|
|
|
|
moveToThread(thr);
|
2023-05-15 15:29:47 -04:00
|
|
|
}
|
|
|
|
|
2023-05-17 15:06:58 -04:00
|
|
|
void Loop::init()
|
2023-05-15 15:29:47 -04:00
|
|
|
{
|
2023-05-27 09:33:14 -04:00
|
|
|
loopTimer = new QTimer(this);
|
2023-05-26 16:12:53 -04:00
|
|
|
|
|
|
|
connect(loopTimer, &QTimer::timeout, this, &Loop::loopSlot);
|
|
|
|
|
|
|
|
loopTimer->setSingleShot(false);
|
2023-05-21 09:34:57 -04:00
|
|
|
loopTimer->start(heartBeat * 1000);
|
2023-06-09 16:24:32 -04:00
|
|
|
|
|
|
|
loopSlot();
|
2023-05-17 15:06:58 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void Loop::loopSlot()
|
|
|
|
{
|
2023-05-21 09:34:57 -04:00
|
|
|
if (!exec())
|
2023-05-15 15:29:47 -04:00
|
|
|
{
|
2023-05-21 09:34:57 -04:00
|
|
|
loopTimer->stop(); QCoreApplication::exit(shared->retCode);
|
2023-05-15 15:29:47 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Loop::exec()
|
|
|
|
{
|
2023-05-26 16:12:53 -04:00
|
|
|
if (loopTimer->interval() != heartBeat * 1000)
|
|
|
|
{
|
|
|
|
loopTimer->start(heartBeat * 1000);
|
|
|
|
}
|
|
|
|
|
2023-05-15 15:29:47 -04:00
|
|
|
return shared->retCode == 0;
|
|
|
|
}
|
|
|
|
|
2023-05-17 15:06:58 -04:00
|
|
|
Upkeep::Upkeep(shared_t *sharedRes, QThread *thr, QObject *parent) : Loop(sharedRes, thr, parent) {}
|
2023-05-15 15:29:47 -04:00
|
|
|
|
|
|
|
bool Upkeep::exec()
|
|
|
|
{
|
|
|
|
enforceMaxEvents(shared);
|
2023-10-19 15:04:39 -04:00
|
|
|
enforceMaxImages(shared);
|
2023-10-08 10:09:15 -04:00
|
|
|
enforceMaxVids(shared);
|
2023-05-15 15:29:47 -04:00
|
|
|
|
|
|
|
return Loop::exec();
|
|
|
|
}
|
|
|
|
|
2023-05-17 15:06:58 -04:00
|
|
|
EventLoop::EventLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : Loop(sharedRes, thr, parent)
|
2023-05-15 15:29:47 -04:00
|
|
|
{
|
2023-06-09 16:24:32 -04:00
|
|
|
heartBeat = 2;
|
2023-06-10 09:45:26 -04:00
|
|
|
highScore = 0;
|
|
|
|
cycles = 0;
|
2023-05-15 15:29:47 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
bool EventLoop::exec()
|
2023-06-10 09:45:26 -04:00
|
|
|
{
|
2023-06-20 17:49:52 -04:00
|
|
|
if (!vidList.isEmpty())
|
2023-05-15 15:29:47 -04:00
|
|
|
{
|
2023-06-10 09:45:26 -04:00
|
|
|
vidList.removeDuplicates();
|
2023-05-15 15:29:47 -04:00
|
|
|
|
2023-06-11 07:41:57 -04:00
|
|
|
if (vidList.size() > 1)
|
2023-05-15 15:29:47 -04:00
|
|
|
{
|
2023-10-27 15:43:17 -04:00
|
|
|
QTextStream(stdout) << "attempting write out of event: " << name << Qt::endl;
|
2023-06-10 09:45:26 -04:00
|
|
|
|
2023-06-11 07:41:57 -04:00
|
|
|
if (wrOutVod())
|
2023-06-10 09:45:26 -04:00
|
|
|
{
|
|
|
|
QProcess proc;
|
|
|
|
QStringList args;
|
2023-05-26 16:12:53 -04:00
|
|
|
|
2023-06-10 09:45:26 -04:00
|
|
|
args << "convert";
|
|
|
|
args << imgPath;
|
2023-10-27 15:43:17 -04:00
|
|
|
args << shared->recPath + "/" + name + shared->thumbExt;
|
2023-05-30 20:03:22 -04:00
|
|
|
|
2023-06-10 09:45:26 -04:00
|
|
|
proc.start("magick", args);
|
|
|
|
proc.waitForFinished();
|
|
|
|
}
|
|
|
|
}
|
2023-05-30 20:03:22 -04:00
|
|
|
|
2023-06-10 09:45:26 -04:00
|
|
|
cycles = 0;
|
|
|
|
highScore = 0;
|
|
|
|
|
|
|
|
vidList.clear();
|
|
|
|
}
|
2023-06-20 17:49:52 -04:00
|
|
|
|
|
|
|
QList<int> rmIndx;
|
|
|
|
|
|
|
|
for (auto i = 0; i < shared->recList.size(); ++i)
|
2023-06-09 16:24:32 -04:00
|
|
|
{
|
2023-06-20 17:49:52 -04:00
|
|
|
auto event = &shared->recList[i];
|
2023-05-26 16:12:53 -04:00
|
|
|
|
2023-06-20 17:49:52 -04:00
|
|
|
if (highScore < event->score)
|
|
|
|
{
|
|
|
|
name = event->timeStamp.toString(DATETIME_FMT);
|
|
|
|
imgPath = event->imgPath;
|
|
|
|
highScore = event->score;
|
|
|
|
}
|
2023-06-10 21:51:39 -04:00
|
|
|
|
2023-06-20 17:49:52 -04:00
|
|
|
if (event->queAge >= (shared->evMaxSecs / heartBeat))
|
2023-06-11 07:41:57 -04:00
|
|
|
{
|
2023-06-20 17:49:52 -04:00
|
|
|
auto maxSecs = shared->evMaxSecs / 2;
|
|
|
|
// half the maxsecs value to get front-back half secs
|
2023-05-26 16:12:53 -04:00
|
|
|
|
2023-10-27 15:43:17 -04:00
|
|
|
auto backFiles = backwardFacingFiles(shared->buffPath + "/live", shared->streamExt, event->timeStamp, maxSecs);
|
|
|
|
auto frontFiles = forwardFacingFiles(shared->buffPath + "/live", shared->streamExt, event->timeStamp, maxSecs);
|
2023-05-26 16:12:53 -04:00
|
|
|
|
2023-06-20 17:49:52 -04:00
|
|
|
vidList.append(backFiles + frontFiles);
|
|
|
|
rmIndx.append(i);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
event->queAge += heartBeat;
|
2023-06-09 16:24:32 -04:00
|
|
|
}
|
2023-06-20 17:49:52 -04:00
|
|
|
}
|
2023-05-15 15:29:47 -04:00
|
|
|
|
2023-06-20 17:49:52 -04:00
|
|
|
for (auto i : rmIndx)
|
|
|
|
{
|
2023-06-20 18:02:35 -04:00
|
|
|
shared->recList.removeAt(i);
|
2023-06-11 07:41:57 -04:00
|
|
|
}
|
2023-10-27 15:43:17 -04:00
|
|
|
|
2023-05-15 15:29:47 -04:00
|
|
|
return Loop::exec();
|
|
|
|
}
|
|
|
|
|
2023-06-11 07:41:57 -04:00
|
|
|
bool EventLoop::wrOutVod()
|
2023-05-15 15:29:47 -04:00
|
|
|
{
|
|
|
|
auto cnt = 0;
|
2023-05-26 16:12:53 -04:00
|
|
|
auto concat = name + ".tmp";
|
2023-06-10 22:44:38 -04:00
|
|
|
auto ret = false;
|
2023-05-15 15:29:47 -04:00
|
|
|
|
|
|
|
QFile file(concat);
|
|
|
|
|
|
|
|
file.open(QFile::WriteOnly);
|
|
|
|
|
2023-06-11 07:41:57 -04:00
|
|
|
for (auto &&vid : vidList)
|
2023-05-15 15:29:47 -04:00
|
|
|
{
|
2023-10-27 15:43:17 -04:00
|
|
|
QTextStream(stdout) << "event_src: " << vid << Qt::endl;
|
2023-05-15 15:29:47 -04:00
|
|
|
|
2023-05-26 16:12:53 -04:00
|
|
|
if (QFile::exists(vid))
|
2023-05-15 15:29:47 -04:00
|
|
|
{
|
2023-05-26 16:12:53 -04:00
|
|
|
file.write(QString("file '" + vid + "'\n").toUtf8()); cnt++;
|
2023-05-15 15:29:47 -04:00
|
|
|
}
|
2023-10-27 15:43:17 -04:00
|
|
|
else
|
|
|
|
{
|
|
|
|
QTextStream(stdout) << "warning: the event hls clip does not exists." << Qt::endl;
|
|
|
|
}
|
2023-05-15 15:29:47 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
file.close();
|
|
|
|
|
|
|
|
if (cnt == 0)
|
|
|
|
{
|
2023-10-27 15:43:17 -04:00
|
|
|
QTextStream(stderr) << "err: none of the event hls clips exists, canceling write out." << Qt::endl;
|
2023-05-15 15:29:47 -04:00
|
|
|
|
2023-06-10 22:44:38 -04:00
|
|
|
QFile::remove(concat);
|
2023-05-15 15:29:47 -04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
QStringList args;
|
|
|
|
|
|
|
|
args << "-f";
|
|
|
|
args << "concat";
|
|
|
|
args << "-safe" << "0";
|
|
|
|
args << "-i" << concat;
|
|
|
|
args << "-c" << "copy";
|
2023-10-27 15:43:17 -04:00
|
|
|
args << shared->recPath + "/" + name + shared->recExt;
|
|
|
|
|
|
|
|
QProcess::execute("ffmpeg", args);
|
2023-05-15 15:29:47 -04:00
|
|
|
QFile::remove(concat);
|
|
|
|
}
|
2023-06-10 22:44:38 -04:00
|
|
|
|
|
|
|
return ret;
|
2023-05-15 15:29:47 -04:00
|
|
|
}
|
|
|
|
|
2023-05-17 15:06:58 -04:00
|
|
|
DetectLoop::DetectLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : Loop(sharedRes, thr, parent)
|
2023-05-15 15:29:47 -04:00
|
|
|
{
|
2023-10-27 15:43:17 -04:00
|
|
|
seed = 0;
|
2023-05-27 09:33:14 -04:00
|
|
|
pcTimer = 0;
|
2023-05-29 20:06:19 -04:00
|
|
|
heartBeat = 2;
|
2023-06-20 17:49:52 -04:00
|
|
|
delayCycles = 12; // this will be used to delay the
|
|
|
|
// actual start of DetectLoop by
|
|
|
|
// 24secs.
|
2023-05-17 15:06:58 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void DetectLoop::init()
|
|
|
|
{
|
2023-05-27 09:33:14 -04:00
|
|
|
pcTimer = new QTimer(this);
|
2023-05-26 16:12:53 -04:00
|
|
|
mod = false;
|
|
|
|
|
|
|
|
connect(pcTimer, &QTimer::timeout, this, &DetectLoop::pcBreak);
|
2023-05-15 15:29:47 -04:00
|
|
|
|
2023-05-20 19:18:55 -04:00
|
|
|
resetTimers();
|
2023-05-21 09:34:57 -04:00
|
|
|
|
|
|
|
Loop::init();
|
2023-05-15 15:29:47 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void DetectLoop::resetTimers()
|
|
|
|
{
|
2023-05-26 16:12:53 -04:00
|
|
|
pcTimer->start(shared->postSecs * 1000);
|
2023-05-15 15:29:47 -04:00
|
|
|
}
|
|
|
|
|
2023-05-26 16:12:53 -04:00
|
|
|
void DetectLoop::pcBreak()
|
2023-05-15 15:29:47 -04:00
|
|
|
{
|
2023-05-26 16:12:53 -04:00
|
|
|
if (!shared->postCmd.isEmpty())
|
2023-05-15 15:29:47 -04:00
|
|
|
{
|
2023-10-27 15:43:17 -04:00
|
|
|
QTextStream(stdout) << "---POST_BREAK---" << Qt::endl;
|
2023-05-15 15:29:47 -04:00
|
|
|
|
2023-05-26 16:12:53 -04:00
|
|
|
if (mod)
|
2023-05-15 15:29:47 -04:00
|
|
|
{
|
2023-10-27 15:43:17 -04:00
|
|
|
QTextStream(stdout) << "motion detected, skipping the post command." << Qt::endl;
|
2023-05-15 15:29:47 -04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-05-29 20:06:19 -04:00
|
|
|
if (delayCycles == 0) delayCycles = 5;
|
|
|
|
else delayCycles += 5;
|
2023-05-29 17:43:31 -04:00
|
|
|
|
2023-10-27 15:43:17 -04:00
|
|
|
QTextStream(stdout) << "no motion detected, running post command: " << shared->postCmd << Qt::endl;
|
2023-08-06 10:17:10 -04:00
|
|
|
|
|
|
|
auto args = parseArgs(shared->postCmd.toUtf8(), -1);
|
|
|
|
|
|
|
|
if (args.isEmpty())
|
|
|
|
{
|
2023-10-27 15:43:17 -04:00
|
|
|
QTextStream(stderr) << "err: did not parse an executable from the post command line." << Qt::endl;
|
2023-08-06 10:17:10 -04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
QProcess::execute(args[0], args.mid(1));
|
|
|
|
}
|
2023-05-15 15:29:47 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-26 16:12:53 -04:00
|
|
|
mod = false;
|
2023-05-20 19:18:55 -04:00
|
|
|
}
|
|
|
|
|
2023-08-06 10:17:10 -04:00
|
|
|
float DetectLoop::getFloatFromExe(const QByteArray &line)
|
|
|
|
{
|
|
|
|
QString strLine(line);
|
|
|
|
QString strNum;
|
|
|
|
|
|
|
|
for (auto chr : strLine)
|
|
|
|
{
|
|
|
|
if (chr.isDigit() || (chr == '.'))
|
|
|
|
{
|
|
|
|
strNum.append(chr);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return strNum.toFloat();
|
|
|
|
}
|
|
|
|
|
|
|
|
QStringList DetectLoop::buildArgs(const QString &prev, const QString &next)
|
|
|
|
{
|
|
|
|
auto args = parseArgs(shared->compCmd.toUtf8(), -1);
|
|
|
|
|
|
|
|
for (auto i = 0; i < args.size(); ++i)
|
|
|
|
{
|
|
|
|
if (args[i] == PREV_IMG) args[i] = prev;
|
|
|
|
if (args[i] == NEXT_IMG) args[i] = next;
|
|
|
|
}
|
|
|
|
|
|
|
|
return args;
|
|
|
|
}
|
|
|
|
|
2023-10-27 15:43:17 -04:00
|
|
|
QStringList DetectLoop::buildSnapArgs(const QString &vidSrc, const QString &imgPath)
|
|
|
|
{
|
|
|
|
QStringList ret;
|
|
|
|
|
|
|
|
ret.append("-y");
|
|
|
|
ret.append("-i");
|
|
|
|
ret.append(vidSrc);
|
|
|
|
ret.append("-frames:v");
|
|
|
|
ret.append("1");
|
|
|
|
ret.append(imgPath);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2023-05-26 16:12:53 -04:00
|
|
|
bool DetectLoop::exec()
|
2023-05-20 19:18:55 -04:00
|
|
|
{
|
2023-05-27 09:33:14 -04:00
|
|
|
if (delayCycles > 0)
|
2023-05-15 15:29:47 -04:00
|
|
|
{
|
2023-05-27 09:33:14 -04:00
|
|
|
delayCycles -= 1;
|
2023-05-29 17:43:31 -04:00
|
|
|
|
2023-10-27 15:43:17 -04:00
|
|
|
QTextStream(stdout) << "delay: detection cycle skipped. cycles left to be skipped: " << QString::number(delayCycles) << Qt::endl;
|
2023-05-15 15:29:47 -04:00
|
|
|
}
|
2023-05-26 16:12:53 -04:00
|
|
|
else
|
2023-05-15 15:29:47 -04:00
|
|
|
{
|
2023-10-27 15:43:17 -04:00
|
|
|
auto imgAPath = "img/" + QString::number(seed++) + ".bmp";
|
|
|
|
auto imgBPath = "img/" + QString::number(seed++) + ".bmp";
|
|
|
|
auto curDT = QDateTime::currentDateTime();
|
|
|
|
auto clips = backwardFacingFiles("live", shared->streamExt, curDT, 16);
|
2023-05-21 09:34:57 -04:00
|
|
|
|
2023-10-27 15:43:17 -04:00
|
|
|
if (clips.size() < 2)
|
2023-05-27 09:33:14 -04:00
|
|
|
{
|
2023-10-27 15:43:17 -04:00
|
|
|
QTextStream(stdout) << "warning: didn't pick up enough clips files from the video stream. number of files: " << QString::number(clips.size()) << Qt::endl;
|
|
|
|
QTextStream(stdout) << " will try again on the next loop." << Qt::endl;
|
2023-05-27 09:33:14 -04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-10-27 15:43:17 -04:00
|
|
|
auto snapArgsA = buildSnapArgs(clips[clips.size() - 2], imgAPath);
|
|
|
|
auto snapArgsB = buildSnapArgs(clips[clips.size() - 1], imgAPath);
|
|
|
|
auto compArgs = buildArgs(imgAPath, imgBPath);
|
2023-05-15 15:29:47 -04:00
|
|
|
|
2023-10-27 15:43:17 -04:00
|
|
|
if (compArgs.isEmpty())
|
2023-05-27 09:33:14 -04:00
|
|
|
{
|
2023-10-27 15:43:17 -04:00
|
|
|
QTextStream(stderr) << "err: could not parse a executable name from img_comp_cmd: " << shared->compCmd << Qt::endl;
|
2023-08-06 10:17:10 -04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-10-27 15:43:17 -04:00
|
|
|
QProcess snapFromVidA;
|
|
|
|
QProcess snapFromVidB;
|
2023-08-06 10:17:10 -04:00
|
|
|
QProcess extComp;
|
|
|
|
|
2023-10-27 15:43:17 -04:00
|
|
|
snapFromVidA.start("ffmpeg", snapArgsA);
|
|
|
|
snapFromVidA.waitForFinished();
|
|
|
|
|
|
|
|
snapFromVidB.start("ffmpeg", snapArgsB);
|
|
|
|
snapFromVidB.waitForFinished();
|
|
|
|
|
|
|
|
extComp.start(compArgs[0], compArgs.mid(1));
|
2023-08-06 10:17:10 -04:00
|
|
|
extComp.waitForFinished();
|
|
|
|
|
|
|
|
float score = 0;
|
|
|
|
auto ok = true;
|
|
|
|
|
|
|
|
if (shared->outputType == "stdout")
|
|
|
|
{
|
|
|
|
score = getFloatFromExe(extComp.readAllStandardOutput());
|
|
|
|
}
|
|
|
|
else if (shared->outputType == "stderr")
|
|
|
|
{
|
|
|
|
score = getFloatFromExe(extComp.readAllStandardError());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ok = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ok)
|
|
|
|
{
|
2023-10-27 15:43:17 -04:00
|
|
|
QTextStream(stderr) << "err: img_comp_out: " << shared->outputType << " is not valid. it must be 'stdout' or 'stderr'" << Qt::endl;
|
2023-08-06 10:17:10 -04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-10-27 15:43:17 -04:00
|
|
|
QTextStream(stdout) << compArgs.join(" ") << " --result: " << QString::number(score) << Qt::endl;
|
2023-08-06 10:17:10 -04:00
|
|
|
|
|
|
|
if (score >= shared->imgThresh)
|
|
|
|
{
|
2023-10-27 15:43:17 -04:00
|
|
|
QTextStream(stdout) << "--threshold_breached: " << QString::number(shared->imgThresh) << Qt::endl;
|
2023-08-06 10:17:10 -04:00
|
|
|
|
|
|
|
evt_t event;
|
|
|
|
|
|
|
|
event.timeStamp = curDT;
|
|
|
|
event.score = score;
|
2023-10-27 15:43:17 -04:00
|
|
|
event.imgPath = imgBPath;
|
2023-08-06 10:17:10 -04:00
|
|
|
event.queAge = 0;
|
2023-10-27 15:43:17 -04:00
|
|
|
|
|
|
|
mod = true;
|
|
|
|
|
|
|
|
shared->recList.append(event);
|
2023-08-06 10:17:10 -04:00
|
|
|
}
|
|
|
|
}
|
2023-05-27 09:33:14 -04:00
|
|
|
}
|
2023-05-21 09:34:57 -04:00
|
|
|
}
|
2023-05-15 15:29:47 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return Loop::exec();
|
|
|
|
}
|