Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
c2b6ddd
Enabling websocket support in web
jcfain Oct 20, 2025
485927c
Change version
jcfain Oct 20, 2025
ed1ad13
Up web version
jcfain Oct 20, 2025
0b45ac5
Adding media format check to thumb generation
jcfain Oct 22, 2025
f8affa2
Adding Global web offset
jcfain Nov 12, 2025
b9d231f
Remove media format on thumb extraction as it is not working right on…
jcfain Nov 13, 2025
45dea45
Disable tcode validation by default for serial
jcfain Nov 20, 2025
bd842ca
Adding clear filter input button to webui
jcfain Jan 29, 2026
a51f7a4
Change web version
jcfain Jan 29, 2026
16c039d
Fix issue with auto mark media item viewed
jcfain Feb 4, 2026
4884247
Move viewed threshold to SettingsMap
jcfain Feb 4, 2026
649d138
Adding OR filter on tag filter
jcfain Feb 5, 2026
10d0943
Adding delay to channel random motion
jcfain Feb 11, 2026
ecdcb18
Change delay to a percentage
jcfain Feb 11, 2026
0603a51
Fix mispelling
jcfain Feb 11, 2026
2431681
Adding profile management actions to WebUI
jcfain Feb 12, 2026
1d36b1d
Forgot a file
jcfain Feb 12, 2026
d4da7db
Fix issue where delay did not round
jcfain Feb 12, 2026
14c47b0
Make herespere use new select modal on link to script selection
jcfain Feb 12, 2026
3723168
disableVRScriptSelect true by default
jcfain Feb 13, 2026
0488c8c
Adding settings export management from web UI
jcfain Feb 13, 2026
e9e6736
Add error handling on default empty export dir
jcfain Feb 13, 2026
ba63513
Add auto backups when updating versions before the data has been migr…
jcfain Feb 14, 2026
9dfd505
Guess I need to keep dead files around a little longer
jcfain Feb 14, 2026
adf6e01
Skip to funscript action in web even though no device is connected.
jcfain Feb 14, 2026
497b680
nvm failed at last changeset. Im cooked
jcfain Feb 14, 2026
314f69f
Made motion mod delay an offset. (needs more testing)
jcfain Feb 25, 2026
2bfd24c
Fix issue with persisting playlists on default
jcfain Feb 25, 2026
911c85e
Changed a couple web UI names
jcfain Mar 22, 2026
3991fd2
Cleanup funscript action search abit attempting to troubleshoot missi…
jcfain Apr 6, 2026
bac4ddd
up version
jcfain Apr 6, 2026
d4c3f70
Adding crude tcode command interface for web
jcfain Apr 13, 2026
3327d34
Adding names and hidden properties to custom TCode commands
jcfain Apr 14, 2026
2155f22
Adding quick import
jcfain Apr 15, 2026
264414b
Add web UI for managing tcode commands
jcfain Apr 16, 2026
29a3282
Make metadata and tcode command save settings to disk on http API call.
jcfain Apr 16, 2026
b1e366e
Sort exported by birthTime and move exported to its own section.
jcfain Apr 17, 2026
1b8c33f
Hopefully fix crash in windows on exit
jcfain Apr 29, 2026
2f6fab4
up version to 0,6
jcfain Apr 29, 2026
57b3b3e
Save now happens on version increase if not migrations happen
jcfain Apr 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ src/www/.vscode/settings.json
/src/build
/build
.DS_Store
/src/.qtc_clangd
6 changes: 6 additions & 0 deletions src/XTEngine.pro
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ SOURCES += \
lib/tool/funscriptsearch.cpp \
lib/tool/heatmap.cpp \
lib/tool/imagefactory.cpp \
lib/tool/mediaformat.cpp \
lib/tool/medialibrarycache.cpp \
lib/tool/migration.cpp \
lib/tool/qsettings_json.cpp \
lib/tool/simplecrypt.cpp \
lib/tool/tcodefactory.cpp \
Expand Down Expand Up @@ -127,15 +129,19 @@ HEADERS += \
lib/struct/OutputConnectionPacket.h \
lib/struct/ScriptInfo.h \
lib/struct/SerialComboboxItem.h \
lib/struct/TCodeCommand.h \
lib/struct/connection.h \
lib/struct/device.h \
lib/struct/xmessage.h \
lib/tool/array-util.h \
lib/tool/boolinq.h \
lib/tool/file-util.h \
lib/tool/funscriptsearch.h \
lib/tool/heatmap.h \
lib/tool/imagefactory.h \
lib/tool/mediaformat.h \
lib/tool/medialibrarycache.h \
lib/tool/migration.h \
lib/tool/qsettings_json.h \
lib/tool/simplecrypt.h \
lib/tool/string-util.h \
Expand Down
2 changes: 1 addition & 1 deletion src/XTEngine_global.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

#include <QtCore/qglobal.h>

#if defined(XTENGINE_LIBRARY)
#ifdef XTENGINE_LIBRARY
# define XTENGINE_EXPORT Q_DECL_EXPORT
#else
# define XTENGINE_EXPORT Q_DECL_IMPORT
Expand Down
7 changes: 7 additions & 0 deletions src/lib/handler/connectionhandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,13 @@ void ConnectionHandler::sendTCode(QString tcode)
m_outputConnection->sendTCode(tcode);
}

void ConnectionHandler::delayTCode(QString tcode, int delayMS)
{
QTimer::singleShot(delayMS, [this, tcode]() {
sendTCode(tcode);
});
}

void ConnectionHandler::stopOutputConnection()
{
sendTCode("DSTOP");
Expand Down
1 change: 1 addition & 0 deletions src/lib/handler/connectionhandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ private slots:
explicit ConnectionHandler(QObject *parent = nullptr);
void init();
void sendTCode(QString tcode);
void delayTCode(QString tcode, int delayMS);
void stopOutputConnection();

bool isOutputConnected();
Expand Down
134 changes: 104 additions & 30 deletions src/lib/handler/funscripthandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,28 +291,57 @@ const QList<Track> FunscriptHandler::getLoaded()
return m_funscripts.keys();
}

std::shared_ptr<FunscriptAction> FunscriptHandler::getPosition(const Track& channelName, const qint64& at)
int lastOffset = -1;
///
/// \brief FunscriptHandler::getPosition
/// This method finds the action to execute to move to the next point in the script.
/// This point is in the future so wehn the previous action has finished executing
/// over the interval of time, the next action will start
/// \param channelName the track you need to fine the position via the media time
/// \param at The current media time
/// \param offset Shift the time before or after the actual time.
/// negative values is before so the current time is 100 and you shift -10 then the time will be 90
/// \return nullptr if no action is ready to be executed
///
std::shared_ptr<FunscriptAction> FunscriptHandler::getPosition(const Track& channelName, const qint64& at, const int offset)
{
QMutexLocker locker(&mutex);
qint64 millis = at + getOffSet();
int offsetLocal = offset ?: getOffSet();
if(lastOffset != offsetLocal) {
lastOffset = offsetLocal;
LogHandler::Debug("FunscriptHandler::getPosition offset changed: "+ QString::number(offsetLocal));
}
qint64 millis = at + offsetLocal;
if(!m_funscripts.contains(channelName))
return nullptr;
Funscript* funscript = &m_funscripts[channelName];
auto atList = funscript->settings.atList;
qint64 closestMillis = findClosest(millis, atList);
if(closestMillis == -1)
return nullptr;
funscript->settings.nextActionIndex = atList.indexOf(closestMillis) + 1;
if(funscript->settings.nextActionIndex >= atList.length())
qint64 closestIndex = atList.indexOf(closestMillis);
qint64 nextAction = closestIndex + 1;
// We are still moving to the next funcript point.
if(funscript->settings.lastActionIndex == nextAction)
return nullptr;
// We are at the end of the funscript points.
if(nextAction >= atList.length())
return nullptr;
qint64 nextMillis = atList[funscript->settings.nextActionIndex];
//LogHandler::Debug("millis: "+ QString::number(millis));
//LogHandler::Debug("closestMillis: "+ QString::number(closestMillis));
//LogHandler::Debug("lastActionIndex: "+ QString::number(lastActionIndex));
//LogHandler::Debug("nextAction: "+ QString::number(nextAction));
//LogHandler::Debug("nextActionIndex: "+ QString::number(nextActionIndex));
// LogHandler::Debug("nextMillis: "+ QString::number(nextMillis));
if ((funscript->settings.lastActionIndex != funscript->settings.nextActionIndex && millis >= closestMillis) || funscript->settings.lastActionIndex == -1)
if (millis >= closestMillis || funscript->settings.lastActionIndex == -1)
{
// if(nextAction != funscript->settings.nextActionIndex)
// {
// LogHandler::Warn("Potential action skip nextActionIndex: "+ QString::number(funscript->settings.nextActionIndex) + ", closestIndex: "+ QString::number(closestIndex));
// LogHandler::Warn("at: " + QString::number(closestMillis) + ", pos: "+QString::number(funscript->actions.value(closestMillis)));
// }
qint64 currentIndex = nextAction;
qint64 nextMillis = atList[nextAction];
int interval = funscript->settings.lastActionIndex == -1 ? closestMillis : nextMillis - closestMillis;
if(!_firstActionExecuted)
{
Expand All @@ -330,28 +359,28 @@ std::shared_ptr<FunscriptAction> FunscriptHandler::getPosition(const Track& chan
// LogHandler::Debug("lastActionIndex: "+ QString::number(lastActionIndex));
// LogHandler::Debug("nextActionIndex: "+ QString::number(nextActionIndex));
//LogHandler::Debug("nextActionPos: "+ QString::number(funscript->actions.value(nextMillis)));
qint64 executionMillis = funscript->settings.lastActionIndex == -1 ? closestMillis : nextMillis;
int pos = funscript->actions.value(executionMillis);
qint64 currentMillis = funscript->settings.lastActionIndex == -1 ? closestMillis : nextMillis;
int currentPos = funscript->actions.value(currentMillis);
if(funscript->settings.lastActionIndex > -1 && funscript->settings.modifier != 1)
{
if(pos == funscript->settings.lastActionPos && funscript->settings.lastActionPosModified)
if(currentPos == funscript->settings.lastActionPos && funscript->settings.lastActionPosModified)
{
pos = funscript->settings.lastActionPosModified;
currentPos = funscript->settings.lastActionPosModified;
if(funscript->settings.channel == Track::Stroke)
LogHandler::Debug(funscript->settings.trackName + " Modified pos unchanged: "+QString::number(pos));
LogHandler::Debug(funscript->settings.trackName + " Modified pos unchanged: "+QString::number(currentPos));
}
else
{
int ogPos = pos;
double distance = pos - funscript->settings.lastActionPos;
int ogPos = currentPos;
double distance = currentPos - funscript->settings.lastActionPos;
double amplitude = distance / 2.0;
if(funscript->settings.modifier > 1)
{
pos = qRound(pos + (amplitude * (funscript->settings.modifier - 1)));
currentPos = qRound(currentPos + (amplitude * (funscript->settings.modifier - 1)));
}
else if(funscript->settings.modifier > 0)
{
pos = qRound(pos - (amplitude * (1 - funscript->settings.modifier)));
currentPos = qRound(currentPos - (amplitude * (1 - funscript->settings.modifier)));
}
// double modifier = funscript->settings.modifier;
// if(modifier > 1 )
Expand All @@ -367,32 +396,45 @@ std::shared_ptr<FunscriptAction> FunscriptHandler::getPosition(const Track& chan
// {
// pos = qRound(pos - (modifier * 100));// Subtract % from the top end (current position)
// }
pos = XMath::constrain(pos, 0, 100);
funscript->settings.lastActionPosModified = pos;
currentPos = XMath::constrain(currentPos, 0, 100);
funscript->settings.lastActionPosModified = currentPos;
if(funscript->settings.channel == Track::Stroke)
{
LogHandler::Debug(funscript->settings.trackName + " Modifier: "+QString::number(funscript->settings.modifier));
LogHandler::Debug(funscript->settings.trackName + " last pos: "+QString::number(funscript->settings.lastActionPos));
LogHandler::Debug(funscript->settings.trackName + " pos: "+QString::number(ogPos));
LogHandler::Debug(funscript->settings.trackName + " distance: "+QString::number(distance));
// LogHandler::Debug(funscript->settings.trackName + " amplitude: "+QString::number(amplitude));
LogHandler::Debug(funscript->settings.trackName + " Modified pos: "+QString::number(pos));
LogHandler::Debug(funscript->settings.trackName + " Modified pos: "+QString::number(currentPos));
}
}
}
qreal speedmodifier = XMediaStateHandler::getPlaybackSpeed();
if(speedmodifier > 0 && speedmodifier != 1.0 && speedmodifier < 2.0)

calculateSpeedModifier(interval);

int nextIndex = currentIndex + 1;
// int nextNextIndex = funscript->settings.nextActionIndex + 2;
if(nextIndex < atList.length())
{
qint64 nextActionMillis = atList[nextIndex];
// qint64 nextNextActionMillis = atList[nextNextIndex];
funscript->settings.nextActionPos = funscript->actions.value(nextActionMillis);
funscript->settings.nextActionInterval = nextActionMillis - currentMillis;
calculateSpeedModifier(funscript->settings.nextActionInterval);
}
else
{
LogHandler::Debug("interval before: "+ QString::number(interval));
interval = round(speedmodifier < 1.0 ? interval / speedmodifier :
interval - ((interval * speedmodifier) - interval));
LogHandler::Debug("interval after: "+ QString::number(interval));
funscript->settings.nextActionPos = -1;
funscript->settings.nextActionInterval = -1;
}
std::shared_ptr<FunscriptAction> nextAction(new FunscriptAction { funscript->settings.trackName, executionMillis, pos, interval, funscript->settings.lastActionPos, funscript->settings.lastActionInterval });

std::shared_ptr<FunscriptAction> nextAction(new FunscriptAction { funscript->settings.trackName, currentMillis, currentPos, interval, funscript->settings.lastActionPos, funscript->settings.lastActionInterval, funscript->settings.nextActionPos, funscript->settings.nextActionInterval, currentIndex });
//LogHandler::Debug("nextAction.speed: "+ QString::number(nextAction->speed));
funscript->settings.lastActionIndex = funscript->settings.nextActionIndex;
funscript->settings.lastActionPos = funscript->actions.value(executionMillis);
funscript->settings.lastActionIndex = currentIndex;
funscript->settings.lastActionPos = funscript->actions.value(currentMillis);
funscript->settings.lastActionInterval = interval;
funscript->settings.nextActionIndex = currentIndex + 1;

return nextAction;
}
return nullptr;
Expand Down Expand Up @@ -468,6 +510,18 @@ qint64 FunscriptHandler::findClosest(const qint64& value, const QList<qint64>& a
return (a[lo] - value) < (value - a[hi]) ? a[lo] : a[hi];
}

void FunscriptHandler::calculateSpeedModifier(int& interval)
{
qreal speedmodifier = XMediaStateHandler::getPlaybackSpeed();
if(speedmodifier > 0 && speedmodifier != 1.0 && speedmodifier < 2.0)
{
LogHandler::Debug("interval before: "+ QString::number(interval));
interval = round(speedmodifier < 1.0 ? interval / speedmodifier :
interval - ((interval * speedmodifier) - interval));
LogHandler::Debug("interval after: "+ QString::number(interval));
}
}


//static

Expand Down Expand Up @@ -526,7 +580,7 @@ double FunscriptHandler::getModifier(const Track& channelName)
void FunscriptHandler::updateMetadata(LibraryListItemMetaData258 value)
{
setModifier(value.funscriptModifier);
setOffset(value.offset);
setScriptOffset(value.offset);
}

void FunscriptHandler::resetModifier(const Track& channelName)
Expand All @@ -535,13 +589,33 @@ void FunscriptHandler::resetModifier(const Track& channelName)
}

int FunscriptHandler::getOffSet()
{
return m_offset ?: m_globalOffset;
}

int FunscriptHandler::getScriptOffSet()
{
return m_offset;
}

void FunscriptHandler::setOffset(int value)
void FunscriptHandler::setScriptOffset(int value)
{
m_offset = value;
}

void FunscriptHandler::setGlobalOffset(int value)
{
m_globalOffset = value;
}

void FunscriptHandler::setGlobalOffsetWeb(int value)
{
m_globalOffsetWeb = value;
}

int FunscriptHandler::getGlobalOffsetWeb()
{
m_offset = value ? value : SettingsHandler::getoffSet();
return m_globalOffsetWeb;
}

QList<ScriptInfo> FunscriptHandler::getSFMATracks(QString libraryItemMediaPath)
Expand Down
11 changes: 9 additions & 2 deletions src/lib/handler/funscripthandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public slots:
qint64 getMin(const Track& channelName) const;
qint64 getMax(const Track& channelName) const;
qint64 getNext(const Track& channelName) const;
std::shared_ptr<FunscriptAction> getPosition(const Track& channelName, const qint64& at);
std::shared_ptr<FunscriptAction> getPosition(const Track& channelName, const qint64& at, const int offset = 0);

void play(QString funscript);
void stop();
Expand All @@ -53,7 +53,11 @@ public slots:
static void resetModifier();
static void resetModifier(const Track& channelName);
static int getOffSet();
static void setOffset(int value);
static int getScriptOffSet();
static void setScriptOffset(int value);
static void setGlobalOffset(int value);
static void setGlobalOffsetWeb(int value);
static int getGlobalOffsetWeb();
static void resetOffset(int value);

static bool isSFMA(QString libraryItemMediaPath);
Expand All @@ -69,6 +73,8 @@ public slots:
bool m_loaded = false;
bool _firstActionExecuted;
static inline int m_offset;
static inline int m_globalOffset;
static inline int m_globalOffsetWeb;
static QByteArray readFile(QString file);
static QJsonObject readJson(QByteArray data);
void jsonToFunscript(QJsonObject json);
Expand All @@ -77,6 +83,7 @@ public slots:
void jsonToFunscript(const QJsonObject& json, QHash<qint64, int>& actions);
void setFunscriptSettings(const Track& channelName, Funscript& funscript);
qint64 findClosest(const qint64& value, const QList<qint64>& a);
void calculateSpeedModifier(int& interval);
};

#endif // FUNSCRIPTHANDLER_H
Loading