File indexing completed on 2024-04-21 04:51:40

0001 /*
0002     SPDX-FileCopyrightText: 2008 Jean-Baptiste Mardelle <jb@kdenlive.org>
0003     Based on code by Arendt David <admin@prnet.org>
0004 
0005 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 
0008 #include "jogshuttle.h"
0009 
0010 #include "kdenlive_debug.h"
0011 
0012 #include <QApplication>
0013 #include <QDir>
0014 #include <cerrno>
0015 #include <cstring>
0016 #include <sys/select.h>
0017 #include <utility>
0018 // according to earlier standards
0019 #include <sys/time.h>
0020 #include <sys/types.h>
0021 #include <unistd.h>
0022 
0023 // init media event type constants
0024 const QEvent::Type MediaCtrlEvent::Key = QEvent::Type(QEvent::registerEventType());
0025 const QEvent::Type MediaCtrlEvent::Jog = QEvent::Type(QEvent::registerEventType());
0026 const QEvent::Type MediaCtrlEvent::Shuttle = QEvent::Type(QEvent::registerEventType());
0027 
0028 ShuttleThread::ShuttleThread(QString device, QObject *parent)
0029     : m_device(std::move(device))
0030     , m_parent(parent)
0031     , m_isRunning(false)
0032 {
0033 }
0034 
0035 ShuttleThread::~ShuttleThread() = default;
0036 
0037 QString ShuttleThread::device()
0038 {
0039     return m_device;
0040 }
0041 
0042 void ShuttleThread::stop()
0043 {
0044     m_isRunning = false;
0045 }
0046 
0047 void ShuttleThread::run()
0048 {
0049     media_ctrl mc;
0050     media_ctrl_open_dev(&mc, m_device.toUtf8().data());
0051     if (mc.fd < 0) {
0052         return;
0053     }
0054     fd_set readset;
0055     struct timeval timeout;
0056     // enter thread loop
0057     m_isRunning = true;
0058     while (m_isRunning) {
0059         // reset the read set
0060         FD_ZERO(&readset);
0061         FD_SET(mc.fd, &readset);
0062         // reinit the timeout structure
0063         timeout.tv_sec = 0;
0064         timeout.tv_usec = 400000;
0065         // do select in blocked mode and wake up after timeout
0066         // for stop_me evaluation
0067         int result = select(mc.fd + 1, &readset, nullptr, nullptr, &timeout);
0068         // see if there was an error or timeout else process event
0069         if (result < 0 && errno == EINTR) {
0070             // EINTR event caught. This is not a problem - continue processing
0071             continue;
0072         }
0073         if (result < 0) {
0074             // stop thread
0075             m_isRunning = false;
0076         } else if (result > 0) {
0077             // we have input
0078             if (FD_ISSET(mc.fd, &readset)) {
0079                 media_ctrl_event mev;
0080                 mev.type = MEDIA_CTRL_EVENT_NONE;
0081                 // read input
0082                 media_ctrl_read_event(&mc, &mev);
0083                 // process event
0084                 handleEvent(mev);
0085             }
0086         } else if (result == 0) {
0087             // on timeout
0088         }
0089     }
0090     // close the handle and return thread
0091     media_ctrl_close(&mc);
0092 }
0093 
0094 void ShuttleThread::handleEvent(const media_ctrl_event &ev)
0095 {
0096     if (ev.type == MEDIA_CTRL_EVENT_KEY) {
0097         key(ev);
0098     } else if (ev.type == MEDIA_CTRL_EVENT_JOG) {
0099         jog(ev);
0100     } else if (ev.type == MEDIA_CTRL_EVENT_SHUTTLE) {
0101         shuttle(ev);
0102     }
0103 }
0104 
0105 void ShuttleThread::key(const media_ctrl_event &ev)
0106 {
0107     if (ev.value == KEY_PRESS) {
0108         QApplication::postEvent(m_parent, new MediaCtrlEvent(MediaCtrlEvent::Key, ev.index + 1));
0109     }
0110 }
0111 
0112 void ShuttleThread::shuttle(const media_ctrl_event &ev)
0113 {
0114     int value = ev.value / 2;
0115 
0116     if (value > MaxShuttleRange || value < -MaxShuttleRange) {
0117         // qCDebug(KDENLIVE_LOG) << "Jog shuttle value is out of range: " << MaxShuttleRange;
0118         return;
0119     }
0120     QApplication::postEvent(m_parent, new MediaCtrlEvent(MediaCtrlEvent::Shuttle, value));
0121 }
0122 
0123 void ShuttleThread::jog(const media_ctrl_event &ev)
0124 {
0125     QApplication::postEvent(m_parent, new MediaCtrlEvent(MediaCtrlEvent::Jog, ev.value));
0126 }
0127 
0128 JogShuttle::JogShuttle(const QString &device, QObject *parent)
0129     : QObject(parent)
0130     , m_shuttleProcess(device, this)
0131 {
0132     m_shuttleProcess.start();
0133 }
0134 
0135 JogShuttle::~JogShuttle()
0136 {
0137     stopDevice();
0138 }
0139 
0140 void JogShuttle::stopDevice()
0141 {
0142     if (m_shuttleProcess.isRunning()) {
0143         // tell thread to stop
0144         m_shuttleProcess.stop();
0145         m_shuttleProcess.quit();
0146         // give the thread some time (ms) to shutdown
0147         m_shuttleProcess.wait(600);
0148 
0149         // if still running - do it in the hardcore way
0150         if (m_shuttleProcess.isRunning()) {
0151             m_shuttleProcess.terminate();
0152             qCWarning(KDENLIVE_LOG) << "Needed to force jogshuttle process termination";
0153         }
0154     }
0155 }
0156 
0157 void JogShuttle::customEvent(QEvent *e)
0158 {
0159     QEvent::Type type = e->type();
0160 
0161     if (type == MediaCtrlEvent::Key) {
0162         auto *mev = static_cast<MediaCtrlEvent *>(e);
0163         Q_EMIT button(mev->value());
0164     } else if (type == MediaCtrlEvent::Jog) {
0165         auto *mev = static_cast<MediaCtrlEvent *>(e);
0166         int value = mev->value();
0167 
0168         if (value < 0) {
0169             Q_EMIT jogBack();
0170         } else if (value > 0) {
0171             Q_EMIT jogForward();
0172         }
0173     } else if (type == MediaCtrlEvent::Shuttle) {
0174         auto *mev = static_cast<MediaCtrlEvent *>(e);
0175         Q_EMIT shuttlePos(mev->value());
0176     }
0177 }
0178 
0179 QString JogShuttle::canonicalDevice(const QString &device)
0180 {
0181     return QDir(device).canonicalPath();
0182 }
0183 
0184 DeviceMap JogShuttle::enumerateDevices(const QString &devPath)
0185 {
0186     DeviceMap devs;
0187     QDir devDir(devPath);
0188 
0189     if (!devDir.exists()) {
0190         return devs;
0191     }
0192 
0193     const QStringList fileList = devDir.entryList(QDir::System | QDir::Files);
0194     for (const QString &fileName : fileList) {
0195         QString devFullPath = devDir.absoluteFilePath(fileName);
0196         QString fileLink = JogShuttle::canonicalDevice(devFullPath);
0197         // qCDebug(KDENLIVE_LOG) << QString(" [%1] ").arg(fileName);
0198         // qCDebug(KDENLIVE_LOG) << QString(" [%1] ").arg(fileLink);
0199 
0200         media_ctrl mc;
0201         media_ctrl_open_dev(&mc, fileLink.toUtf8().data());
0202         if (mc.fd > 0 && (mc.device != nullptr)) {
0203             devs.insert(QString(mc.device->name), devFullPath);
0204             qCDebug(KDENLIVE_LOG) << QStringLiteral(" [keys-count=%1] ").arg(media_ctrl_get_keys_count(&mc));
0205         }
0206         media_ctrl_close(&mc);
0207     }
0208 
0209     return devs;
0210 }
0211 
0212 int JogShuttle::keysCount(const QString &devPath)
0213 {
0214     media_ctrl mc;
0215     int keysCount = 0;
0216 
0217     QString fileLink = canonicalDevice(devPath);
0218     media_ctrl_open_dev(&mc, fileLink.toUtf8().data());
0219     if (mc.fd > 0 && (mc.device != nullptr)) {
0220         keysCount = media_ctrl_get_keys_count(&mc);
0221     }
0222 
0223     return keysCount;
0224 }