ckb-next  v0.2.8 at branch master
ckb-next driver for corsair devices
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
animscript.cpp
Go to the documentation of this file.
1 #include <cmath>
2 #include <QApplication>
3 #include <QDateTime>
4 #include <QDebug>
5 #include <QDir>
6 #include <QUrl>
7 #include "animscript.h"
8 
9 QHash<QUuid, AnimScript*> AnimScript::scripts;
10 
11 AnimScript::AnimScript(QObject* parent, const QString& path) :
12  QObject(parent), _path(path), initialized(false), process(0)
13 {
14 }
15 
16 AnimScript::AnimScript(QObject* parent, const AnimScript& base) :
17  QObject(parent), _info(base._info), _path(base._path), initialized(false), process(0)
18 {
19 }
20 
22  if(process){
23  process->kill();
24  process->waitForFinished(1000);
25  delete process;
26  }
27 }
28 
29 QString AnimScript::path(){
30 #ifdef __APPLE__
31  return QDir(QApplication::applicationDirPath() + "/../Resources").absoluteFilePath("ckb-animations");
32 #else
33  return QDir("/usr/lib").absoluteFilePath("ckb-animations");
34 #endif
35 }
36 
38  QDir dir(path());
39  foreach(AnimScript* script, scripts)
40  delete script;
41  scripts.clear();
42  foreach(QString file, dir.entryList(QDir::Files | QDir::Executable)){
43  AnimScript* script = new AnimScript(qApp, dir.absoluteFilePath(file));
44  if(script->load() && !scripts.contains(script->_info.guid))
45  scripts[script->_info.guid] = script;
46  else
47  delete script;
48  }
49 }
50 
51 QList<const AnimScript*> AnimScript::list(){
52  // Gather the animations into an alphabetically-sorted list
53  QMap<QString, const AnimScript*> result;
54  foreach(AnimScript* script, scripts.values()){
55  QString name = script->name();
56  if(result.contains(name)){
57  // If duplicate names exist, make them unique by including their GUIDs
58  AnimScript* last = (AnimScript*)result[name];
59  last->_info.name += " " + last->guid().toString().toUpper();
60  script->_info.name += " " + script->guid().toString().toUpper();
61  }
62  result[script->name()] = script;
63  }
64  return result.values();
65 }
66 
67 AnimScript* AnimScript::copy(QObject* parent, const QUuid& id){
68  return scripts.contains(id) ? new AnimScript(parent, *scripts.value(id)) : 0;
69 }
70 
71 inline QString urlParam(const QString& param){
72  return QUrl::fromPercentEncoding(param.trimmed().toLatin1()).trimmed();
73 }
74 
75 const static double ONE_DAY = 24. * 60. * 60.;
76 
78  // Run the process to get script info
79  QProcess infoProcess;
80  infoProcess.start(_path, QStringList("--ckb-info"));
81  qDebug() << "Scanning " << _path;
82  infoProcess.waitForFinished(1000);
83  if(infoProcess.state() == QProcess::Running){
84  // Kill the process if it takes more than 1s
85  infoProcess.kill();
86  return false;
87  }
88  // Set defaults for performance info
89  _info.kpMode = KP_NONE;
90  _info.absoluteTime = _info.preempt = _info.liveParams = false;
91  _info.repeat = true;
92  // Read output
93  QString line;
94  while((line = infoProcess.readLine()) != ""){
95  line = line.trimmed();
96  QStringList components = line.split(" ");
97  int count = components.count();
98  if(count < 2)
99  continue;
100  QString param = components[0].trimmed();
101  if(param == "guid")
102  _info.guid = QUuid(urlParam(components[1]));
103  else if(param == "name")
104  _info.name = urlParam(components[1]);
105  else if(param == "version")
106  _info.version = urlParam(components[1]);
107  else if(param == "year")
108  _info.year = urlParam(components[1]);
109  else if(param == "author")
110  _info.author = urlParam(components[1]);
111  else if(param == "license")
112  _info.license = urlParam(components[1]);
113  else if(param == "description")
114  _info.description = urlParam(components[1]);
115  else if(param == "kpmode")
116  _info.kpMode = (components[1] == "position") ? KP_POSITION : (components[1] == "name") ? KP_NAME : KP_NONE;
117  else if(param == "time")
118  _info.absoluteTime = (components[1] == "absolute");
119  else if(param == "repeat")
120  _info.repeat = (components[1] == "on");
121  else if(param == "preempt")
122  _info.preempt = (components[1] == "on");
123  else if(param == "parammode")
124  _info.liveParams = (components[1] == "live");
125  else if(param == "param"){
126  // Read parameter
127  if(count < 3)
128  continue;
129  while(components.count() < 8)
130  components.append("");
132  QString sType = components[1].toLower();
133  if(sType == "long")
134  type = Param::LONG;
135  else if(sType == "double")
136  type = Param::DOUBLE;
137  else if(sType == "bool")
138  type = Param::BOOL;
139  else if(sType == "rgb")
140  type = Param::RGB;
141  else if(sType == "argb")
142  type = Param::ARGB;
143  else if(sType == "gradient")
144  type = Param::GRADIENT;
145  else if(sType == "agradient")
146  type = Param::AGRADIENT;
147  else if(sType == "angle")
148  type = Param::ANGLE;
149  else if(sType == "string")
150  type = Param::STRING;
151  else if(sType == "label")
152  type = Param::LABEL;
153  else
154  continue;
155  // "param <type> <name> <prefix> <postfix> <default>"
156  QString name = components[2].toLower();
157  // Make sure it's not present already
158  if(hasParam(name))
159  continue;
160  QString prefix = urlParam(components[3]), postfix = urlParam(components[4]);
161  QVariant def = urlParam(components[5]), minimum = urlParam(components[6]), maximum = urlParam(components[7]);
162  // Don't allow predefined params
163  if(name == "trigger" || name == "kptrigger" || name == "kpmode" || name == "duration" || name == "delay" || name == "kpdelay" || name == "repeat" || name == "kprepeat" || name == "stop" || name == "kpstop" || name == "kpmodestop" || name == "kprelease")
164  continue;
165  Param param = { type, name, prefix, postfix, def, minimum, maximum };
166  _info.params.append(param);
167  } else if(param == "preset"){
168  // Add preset
169  QString name = urlParam(components.at(1));
170  QMap<QString, QVariant> preset;
171  for(int i = 2; i < count; i++){
172  // Scan name/value setting pairs
173  QString setting = components.at(i);
174  QStringList sComponents = setting.split("=");
175  if(sComponents.count() != 2)
176  continue;
177  QString param = sComponents.first().trimmed();
178  QString value = urlParam(sComponents.last());
179  preset[param] = value;
180  }
181  // If the preset contains a duration, set the repeat time to the same value
182  if(preset.contains("duration")){
183  QVariant duration = preset.value("duration");
184  preset["repeat"] = duration;
185  preset["kprepeat"] = duration;
186  }
187  _presets.append(name);
188  _presetValues.append(preset);
189  }
190  }
191  // Make sure the required parameters are filled out
192  if(_info.guid.isNull() || _info.name == "" || _info.version == "" || _info.year == "" || _info.author == "" || _info.license == "")
193  return false;
194  // Add timing parameters
195  double defaultDuration = -1.;
196  if(!_info.absoluteTime){
197  defaultDuration = 1.;
198  Param duration = { Param::DOUBLE, "duration", "", "", defaultDuration, 0.1, ONE_DAY };
199  _info.params.append(duration);
200  }
201  Param trigger = { Param::BOOL, "trigger", "", "", true, 0, 0 };
202  _info.params.append(trigger);
203  Param kptrigger = { Param::BOOL, "kptrigger", "", "", false, 0, 0 };
204  _info.params.append(kptrigger);
205  if(_info.absoluteTime || !_info.repeat)
206  _info.preempt = false;
207  Param kpmode = { Param::LONG, "kpmode", "", "", 1, 0, 0 };
208  if(_info.kpMode)
209  kpmode.defaultValue = 0;
210  _info.params.append(kpmode);
211  Param delay = { Param::DOUBLE, "delay", "", "", 0., 0., ONE_DAY };
212  Param kpdelay = { Param::DOUBLE, "kpdelay", "", "", 0., 0., ONE_DAY };
213  _info.params.append(delay);
214  _info.params.append(kpdelay);
215  Param kpmodestop = { Param::BOOL, "kpmodestop", "", "", false, 0, 0 };
216  Param kprelease = { Param::BOOL, "kprelease", "", "", false, 0, 0 };
217  _info.params.append(kpmodestop);
218  _info.params.append(kprelease);
219  if(_info.repeat){
220  Param repeat = { Param::DOUBLE, "repeat", "", "", defaultDuration, 0.1, ONE_DAY };
221  Param kprepeat = { Param::DOUBLE, "kprepeat", "", "", defaultDuration, 0.1, ONE_DAY };
222  // When repeats are enabled, stop and kpstop are LONG values (number of repeats)
223  Param stop = { Param::LONG, "stop", "", "", -1, 0, 1000 };
224  Param kpstop = { Param::LONG, "kpstop", "", "", 0, 0, 1000 };
225  _info.params.append(repeat);
226  _info.params.append(kprepeat);
227  _info.params.append(stop);
228  _info.params.append(kpstop);
229  } else {
230  // When repeats are disabled, stop and kpstop are DOUBLE values (seconds)
231  Param stop = { Param::DOUBLE, "stop", "", "", -1., 0.1, ONE_DAY };
232  Param kpstop = { Param::DOUBLE, "kpstop", "", "", -1., 0.1, ONE_DAY };
233  _info.params.append(stop);
234  _info.params.append(kpstop);
235  }
236  return true;
237 }
238 
239 void AnimScript::init(const KeyMap& map, const QStringList& keys, const QMap<QString, QVariant>& paramValues){
240  if(_path == "")
241  return;
242  end();
243  _map = map;
244  _colors.init(map);
245  _colorBuffer.init(map);
246  _keys = keys;
247  _paramValues = paramValues;
248  setDuration();
249  stopped = firstFrame = false;
250  initialized = true;
251 }
252 
254  if(_info.absoluteTime){
255  durationMsec = 1000;
256  repeatMsec = 0;
257  } else {
258  durationMsec = round(_paramValues.value("duration").toDouble() * 1000.);
259  if(durationMsec <= 0)
260  durationMsec = -1;
261  repeatMsec = round(_paramValues.value("repeat").toDouble() * 1000.);
262  }
263 }
264 
265 void AnimScript::parameters(const QMap<QString, QVariant>& paramValues){
266  if(!initialized || !process || !_info.liveParams)
267  return;
268  _paramValues = paramValues;
269  setDuration();
270  printParams();
271 }
272 
274  process->write("begin params\n");
275  QMapIterator<QString, QVariant> i(_paramValues);
276  while(i.hasNext()){
277  i.next();
278  process->write("param ");
279  process->write(i.key().toLatin1());
280  process->write(" ");
281  process->write(QUrl::toPercentEncoding(i.value().toString()));
282  process->write("\n");
283  }
284  process->write("end params\n");
285 }
286 
287 void AnimScript::begin(quint64 timestamp){
288  if(!initialized)
289  return;
290  end();
292  // Determine the upper left corner of the given keys
293  QStringList keysCopy = _keys;
294  minX = INT_MAX;
295  minY = INT_MAX;
296  foreach(const QString& key, keysCopy){
297  const Key& pos = _map.key(key);
298  if(!pos){
299  keysCopy.removeAll(key);
300  continue;
301  }
302  if(pos.x < minX)
303  minX = pos.x;
304  if(pos.y < minY)
305  minY = pos.y;
306  }
307  if(keysCopy.isEmpty()){
308  // If the key list is empty, don't actually start the animation but pretend it's running anyway
309  firstFrame = readFrame = readAnyFrame = true;
310  return;
311  }
312  process = new QProcess(this);
313  connect(process, SIGNAL(readyRead()), this, SLOT(readProcess()));
314  process->start(_path, QStringList("--ckb-run"));
315  qDebug() << "Starting " << _path;
316  // Write the keymap to the process
317  process->write("begin keymap\n");
318  process->write(QString("keycount %1\n").arg(keysCopy.count()).toLatin1());
319  foreach(const QString& key, keysCopy){
320  const Key& pos = _map.key(key);
321  process->write(QString("key %1 %2,%3\n").arg(key).arg(pos.x - minX).arg(pos.y - minY).toLatin1());
322  }
323  process->write("end keymap\n");
324  // Write parameters
325  printParams();
326  // Begin animating
327  process->write("begin run\n");
328  lastFrame = timestamp;
329 }
330 
331 void AnimScript::retrigger(quint64 timestamp, bool allowPreempt){
332  if(!initialized)
333  return;
334  if(allowPreempt && _info.preempt && repeatMsec > 0)
335  // If preemption is wanted, trigger the animation 1 duration in the past first
336  retrigger(timestamp - repeatMsec);
337  if(!process)
338  begin(timestamp);
339  advance(timestamp);
340  if(process)
341  process->write("start\n");
342 }
343 
344 void AnimScript::stop(quint64 timestamp){
345  if(!initialized)
346  return;
347  if(!process)
348  begin(timestamp);
349  advance(timestamp);
350  if(process)
351  process->write("stop\n");
352 }
353 
354 void AnimScript::keypress(const QString& key, bool pressed, quint64 timestamp){
355  if(!initialized)
356  return;
357  if(!process)
358  begin(timestamp);
359  int kpMode = _info.kpMode;
360  if(_paramValues.value("kpmode", 0).toInt() != 0)
361  // Disable KP mode according to user preferences
362  kpMode = KP_NONE;
363  switch(kpMode){
364  case KP_NONE:
365  // If KPs aren't allowed, call retrigger/stop instead
366  if(pressed)
367  retrigger(timestamp);
368  else if(_paramValues.value("kprelease", false).toBool())
369  stop(timestamp);
370  break;
371  case KP_NAME:
372  // Print keypress by name
373  advance(timestamp);
374  process->write(("key " + key + (pressed ? " down\n" : " up\n")).toLatin1());
375  break;
376  case KP_POSITION:
377  // Print keypress by position
378  const Key& kp = _map.key(key);
379  if(!kp)
380  return;
381  advance(timestamp);
382  process->write(("key " + QString("%1,%2").arg(kp.x - minX).arg(kp.y - minY) + (pressed ? " down\n" : " up\n")).toLatin1());
383  break;
384  }
385 }
386 
388  _colors.clear();
389  if(process){
390  process->kill();
391  connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater()));
392  disconnect(process, SIGNAL(readyRead()), this, SLOT(readProcess()));
393  process = 0;
394  }
395 }
396 
398  while(process->canReadLine()){
399  QByteArray line = process->readLine().trimmed();
400  if(!inFrame){
401  // Ignore anything not between "begin frame" and "end frame", except for "end run", which indicates that the program is done.
402  if(line == "begin frame")
403  inFrame = true;
404  else if(line == "end run"){
405  stopped = true;
406  return;
407  }
408  continue;
409  }
410  if(line.startsWith("argb ")){
411  // Add a color to the buffer
412  char keyName[31];
413  QRgb keyColor = 0;
414  if(sscanf(line, "argb %30s %x", keyName, &keyColor) != 2)
415  continue;
416  QRgb* inMap = _colorBuffer.colorForName(keyName);
417  if(!inMap)
418  continue;
419  *inMap = keyColor;
420  }
421  if(line == "end frame"){
422  // Frame is finished. Copy color buffer back to the atomic map
423  memcpy(_colors.colors(), _colorBuffer.colors(), sizeof(QRgb) * _colors.count());
424  inFrame = false;
425  readFrame = readAnyFrame = true;
426  }
427  }
428 }
429 
430 void AnimScript::frame(quint64 timestamp){
431  if(!initialized || stopped)
432  return;
433  // Start the animation if it's not running yet
434  if(!process)
435  begin(timestamp);
436 
437  advance(timestamp);
438  if((readFrame || !firstFrame) && process)
439  // Don't ask for a new frame if the animation hasn't delivered the last one yet
440  process->write("frame\n");
441  firstFrame = true;
442  readFrame = false;
443 }
444 
445 void AnimScript::advance(quint64 timestamp){
446  if(timestamp <= lastFrame || !process)
447  // Don't do anything if the time hasn't actually advanced.
448  return;
449  double delta = (timestamp - lastFrame) / (double)durationMsec;
450  if(!_info.absoluteTime){
451  // Skip any complete durations
452  while(delta > 1.){
453  process->write("time 1\n");
454  delta--;
455  }
456  }
457  process->write(QString("time %1\n").arg(delta).toLatin1());
458  lastFrame = timestamp;
459 }
int repeatMsec
Definition: animscript.h:140
QRgb * colors()
Definition: colormap.h:33
static const int KP_NONE
Definition: animscript.h:124
ColorMap _colorBuffer
Definition: animscript.h:143
static int count()
Definition: animscript.h:54
QString urlParam(const QString &param)
Definition: animscript.cpp:71
bool firstFrame
Definition: animscript.h:141
Param param(const QString &name) const
Definition: animscript.h:73
void init(const KeyMap &map)
Definition: colormap.cpp:69
void advance(quint64 timestamp)
Definition: animscript.cpp:445
static QList< const AnimScript * > list()
Definition: animscript.cpp:51
bool load()
Definition: animscript.cpp:77
void retrigger(quint64 timestamp, bool allowPreempt=false)
Definition: animscript.cpp:331
static QString path()
Definition: animscript.cpp:29
ColorMap _colors
Definition: animscript.h:135
QVariant defaultValue
Definition: animscript.h:43
void frame(quint64 timestamp)
Definition: animscript.cpp:430
const PresetValue & preset(int index) const
Definition: animscript.h:69
Key key(const QString &name) const
Definition: keymap.h:141
AnimScript(QObject *parent, const QString &path)
Definition: animscript.cpp:11
Definition: keymap.h:49
static const int KP_NAME
Definition: animscript.h:124
QString _path
Definition: animscript.h:128
QRgb * colorForName(const char *name)
Definition: colormap.cpp:88
bool hasParam(const QString &name) const
Definition: animscript.h:74
static QHash< QUuid, AnimScript * > scripts
Definition: animscript.h:152
struct AnimScript::@0 _info
void printParams()
Definition: animscript.cpp:273
static const int KP_POSITION
Definition: animscript.h:124
bool stopped
Definition: animscript.h:141
QList< PresetValue > _presetValues
Definition: animscript.h:126
short x
Definition: keymap.h:16
KeyMap _map
Definition: animscript.h:130
bool readFrame
Definition: animscript.h:141
void init(const KeyMap &map, const QStringList &keys, const QMap< QString, QVariant > &paramValues)
Definition: animscript.cpp:239
void keypress(const QString &key, bool pressed, quint64 timestamp)
Definition: animscript.cpp:354
const QString & name() const
Definition: animscript.h:60
static void scan()
Definition: animscript.cpp:37
void end()
Definition: animscript.cpp:387
quint64 lastFrame
Definition: animscript.h:139
PresetValue _paramValues
Definition: animscript.h:136
QStringList _keys
Definition: animscript.h:133
Definition: keymap.h:8
bool inFrame
Definition: animscript.h:141
Definition: keymap.h:49
static AnimScript * copy(QObject *parent, const QUuid &id)
Definition: animscript.cpp:67
QStringList _presets
Definition: animscript.h:125
QProcess * process
Definition: animscript.h:142
static const double ONE_DAY
Definition: animscript.cpp:75
int count() const
Definition: colormap.h:31
const QUuid & guid() const
Definition: animscript.h:59
int kprelease
Definition: main.c:53
void setDuration()
Definition: animscript.cpp:253
void parameters(const QMap< QString, QVariant > &paramValues)
Definition: animscript.cpp:265
bool readAnyFrame
Definition: animscript.h:141
bool initialized
Definition: animscript.h:141
void begin(quint64 timestamp)
Definition: animscript.cpp:287
void readProcess()
Definition: animscript.cpp:397
void clear()
Definition: colormap.cpp:61
void stop(quint64 timestamp)
Definition: animscript.cpp:344
short y
Definition: keymap.h:16
int durationMsec
Definition: animscript.h:140