1 /***************************************************************************
2 * Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
3 * Based on code by Arendt David <admin@prnet.org> *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
19 ***************************************************************************/
22 #include "jogshuttle.h"
27 #include <QApplication>
37 #include <linux/input.h>
58 // Constants for the returned events when reading them from the device
63 // Constants for the signals sent.
64 #define JOG_BACK1 10001
65 #define JOG_FWD1 10002
66 #define KEY_EVENT_OFFSET 20000
68 // middle value for shuttle, will be +/-MAX_SHUTTLE_RANGE
69 #define JOG_STOP 10020
70 #define MAX_SHUTTLE_RANGE 7
72 void ShuttleThread::init(QObject *parent, const QString &device)
78 shuttlevalue = 0xffff;
83 bool ShuttleThread::isWorking()
88 void ShuttleThread::run()
90 kDebug() << "------- STARTING SHUTTLE: " << m_device;
94 media_ctrl_open2(&mc, m_device.toUtf8().data());
98 perror("Can't open Jog Shuttle FILE DESCRIPTOR");
105 struct timeval timeout;
107 /* enter thread loop */
109 /* reset the read set */
111 FD_SET(mc.fd, &readset);
113 // reinit the timeout structure
115 timeout.tv_usec = 400000; /* 400 ms */
117 // do select in blocked mode and wake up after timeout
118 // for stop_me evaluation
119 result = select(mc.fd + 1, &readset, NULL, NULL, &timeout);
121 /* see if there was an error or timeout else process event */
122 if (result < 0 && errno == EINTR) {
123 // EINTR event catched. This is not a problem - continue processing
124 kDebug() << strerror(errno) << "\n";
125 // continue processing
127 } else if (result < 0) {
130 kDebug() << strerror(errno) << "\n";
131 } else if (result > 0) {
133 if (FD_ISSET(mc.fd, &readset)) {
134 struct media_ctrl_event mev;
135 mev.type = MEDIA_CTRL_EVENT_NONE;
137 media_ctrl_read_event(&mc, &mev);
141 } else if (result == 0) {
142 // on timeout. let it here for debug
146 kDebug() << "------- STOPPING SHUTTLE: ";
147 /* close the handle and return thread */
148 media_ctrl_close(&mc);
151 void ShuttleThread::handle_event(const struct media_ctrl_event& ev)
153 if (ev.type == MEDIA_CTRL_EVENT_KEY)
155 else if (ev.type == MEDIA_CTRL_EVENT_JOG)
157 else if (ev.type == MEDIA_CTRL_EVENT_SHUTTLE)
161 void ShuttleThread::key(const struct media_ctrl_event& ev)
163 if (ev.value == KEY_PRESS) {
164 int code = ev.index + 1;
165 QApplication::postEvent(m_parent,
166 new QEvent((QEvent::Type)(KEY_EVENT_OFFSET + code)));
170 void ShuttleThread::shuttle(const struct media_ctrl_event& ev)
172 int value = ev.value / 2;
174 if (value > MAX_SHUTTLE_RANGE || value < -MAX_SHUTTLE_RANGE) {
175 kDebug() << "Jog shuttle value is out of range: " << MAX_SHUTTLE_RANGE;
179 QApplication::postEvent(m_parent,
180 new QEvent((QEvent::Type) (JOG_STOP + (value))));
183 void ShuttleThread::jog(const struct media_ctrl_event& ev)
186 QApplication::postEvent(m_parent, new QEvent((QEvent::Type) JOG_BACK1));
187 else if (ev.value > 0)
188 QApplication::postEvent(m_parent, new QEvent((QEvent::Type) JOG_FWD1));
191 #ifdef USE_DEPRECATED
192 void ShuttleThread::handle_event(EV ev)
196 key(ev.code, ev.value);
201 if (ev.code == SHUTTLE)
207 void ShuttleThread::key(unsigned short code, unsigned int value)
210 // Button release (ignored)
219 //kDebug() << "Button PRESSED: " << code;
220 QApplication::postEvent(m_parent, new QEvent((QEvent::Type)(KEY_EVENT_OFFSET + code)));
224 void ShuttleThread::shuttle(int value)
226 //gettimeofday( &last_shuttle, 0 );
227 //need_synthetic_shuttle = value != 0;
229 if (value == shuttlevalue) {
234 if (value > MAX_SHUTTLE_RANGE || value < -MAX_SHUTTLE_RANGE) {
235 fprintf(stderr, "Jog Shuttle returned value of %d (should be between -%d ad +%d)", value, MAX_SHUTTLE_RANGE, MAX_SHUTTLE_RANGE);
238 shuttlevalue = value;
240 QApplication::postEvent(m_parent, new QEvent((QEvent::Type) (JOG_STOP + value)));
243 void ShuttleThread::jog(unsigned int value)
245 // generate a synthetic event for the shuttle going
246 // to the home position if we have not seen one recently.
247 //if (shuttlevalue != 0) {
248 // QApplication::postEvent(m_parent, new QEvent((QEvent::Type) JOG_STOP));
252 // This code takes care of wrapping around the limits of the jog dial number (it is represented as a single byte, hence
253 // wraps at the 0/255 boundary). I used 25 as the difference to make sure that even in heavy load, with the jog dial
254 // turning fast and we miss some events that we do not mistakenly reverse the direction.
255 // Note also that at least the Contour ShuttlePRO v2 does not send an event for the value 0, so at the wrap it will
256 // need 2 events to go forward. But that is nothing we can do about...
257 if (jogvalue != 0xffff) {
258 //fprintf(stderr, "value=%d jogvalue=%d\n", value, jogvalue);
259 bool wrap = abs(value - jogvalue) > 25;
260 bool rewind = value < jogvalue;
261 bool forward = value > jogvalue;
262 if ((rewind && !wrap) || (forward && wrap))
263 QApplication::postEvent(m_parent, new QEvent((QEvent::Type) JOG_BACK1));
264 else if ((forward && !wrap) || (rewind && wrap))
265 QApplication::postEvent(m_parent, new QEvent((QEvent::Type) JOG_FWD1));
266 else if (!forward && !rewind && shuttlecounter > 2) {
267 // An event without changing the jog value is sent after each shuttle change.
268 // As the shuttle rest position does not get a shuttle event, only a non-position-changing jog event.
269 // Hence we stop on this when we see 2 non-position-changing jog events in a row.
271 QApplication::postEvent(m_parent, new QEvent((QEvent::Type) JOG_STOP));
275 if (shuttlecounter > 0) shuttlecounter++;
277 #endif // USE_DEPRECATED
279 JogShuttle::JogShuttle(const QString &device, QObject *parent) :
285 JogShuttle::~JogShuttle()
290 void JogShuttle::initDevice(const QString &device)
292 if (m_shuttleProcess.isRunning()) {
293 if (device == m_shuttleProcess.m_device)
299 m_shuttleProcess.init(this, device);
300 m_shuttleProcess.start(QThread::LowestPriority);
303 void JogShuttle::stopDevice()
305 if (m_shuttleProcess.isRunning()) {
306 /* tell thread to stop */
307 m_shuttleProcess.stop_me = true;
308 m_shuttleProcess.exit();
309 /* give the thread some time (ms) to shutdown */
310 m_shuttleProcess.wait(600);
312 /* if still running - do it in the hardcore way */
313 if (m_shuttleProcess.isRunning()) {
314 m_shuttleProcess.terminate();
315 kDebug() << "/// terminate jogshuttle process\n";
320 void JogShuttle::customEvent(QEvent* e)
322 int code = e->type();
324 // handle simple job events
334 // FIXME: this is a bad shuttle indication
335 int shuttle_pos = code - JOG_STOP;
336 if (shuttle_pos >= -MAX_SHUTTLE_RANGE && shuttle_pos <= MAX_SHUTTLE_RANGE) {
337 emit shuttlePos(shuttle_pos);
341 // we've got a key event.
342 emit button(e->type() - KEY_EVENT_OFFSET);
345 QString JogShuttle::canonicalDevice(const QString& device)
347 return QDir(device).canonicalPath();
350 DeviceMap JogShuttle::enumerateDevices(const QString& devPath)
353 QDir devDir(devPath);
355 if (!devDir.exists()) {
359 QStringList fileList = devDir.entryList(QDir::System | QDir::Files);
360 foreach (const QString &fileName, fileList) {
361 QString devFullPath = devDir.absoluteFilePath(fileName);
362 QString fileLink = JogShuttle::canonicalDevice(devFullPath);
363 kDebug() << QString(" [%1] ").arg(fileName);
364 kDebug() << QString(" [%1] ").arg(fileLink);
366 struct media_ctrl mc;
367 media_ctrl_open2(&mc, (char*)fileLink.toUtf8().data());
368 if (mc.fd > 0 && mc.device) {
369 devs.insert(QString(mc.device->name), devFullPath);
370 kDebug() << QString(" [keys-count=%1] ").arg(
371 media_ctrl_get_keys_count(&mc));
373 media_ctrl_close(&mc);
379 int JogShuttle::keysCount(const QString& devPath)
381 struct media_ctrl mc;
384 QString fileLink = canonicalDevice(devPath);
385 media_ctrl_open2(&mc, (char*)fileLink.toUtf8().data());
386 if (mc.fd > 0 && mc.device) {
387 keysCount = media_ctrl_get_keys_count(&mc);
394 #include "jogshuttle.moc"