summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--example/example.pro2
-rw-r--r--example/main.cpp11
-rw-r--r--library/components/scriptui.cpp153
-rw-r--r--library/components/scriptui.h38
-rw-r--r--library/fibers/fiber.cpp120
-rw-r--r--library/fibers/fiber.h48
-rw-r--r--library/fibers/fibers.pri43
-rw-r--r--library/fibers/initializestack_32.cpp21
-rw-r--r--library/fibers/initializestack_64_linux_mac.cpp22
-rw-r--r--library/fibers/initializestack_64_win.cpp44
-rw-r--r--library/fibers/switchstack_gcc_32_linux_mac.s41
-rw-r--r--library/fibers/switchstack_gcc_32_win.s42
-rw-r--r--library/fibers/switchstack_gcc_64_linux_mac.s44
-rw-r--r--library/fibers/switchstack_gcc_64_win.s93
-rw-r--r--library/fibers/switchstack_msvc_32.cpp44
-rw-r--r--library/fibers/switchstack_msvc_64.asm98
-rw-r--r--library/remotecontrolwidget.pro10
-rw-r--r--library/scriptadapter.cpp214
-rw-r--r--library/scriptadapter.h90
19 files changed, 1173 insertions, 5 deletions
diff --git a/example/example.pro b/example/example.pro
index 9b8f895..b1fd669 100644
--- a/example/example.pro
+++ b/example/example.pro
@@ -4,7 +4,7 @@
#
#-------------------------------------------------
-QT += core gui
+QT += core gui script
TARGET = example
TEMPLATE = app
diff --git a/example/main.cpp b/example/main.cpp
index e8716bc..e66f03a 100644
--- a/example/main.cpp
+++ b/example/main.cpp
@@ -1,5 +1,7 @@
#include <QtGui/QApplication>
#include "remotecontrolwidget.h"
+#include "scriptadapter.h"
+#include "components/scriptui.h"
#include "components/locationui.h"
#include "components/batterybutton.h"
#include "components/powerbutton.h"
@@ -9,8 +11,8 @@ int main(int argc, char *argv[])
QApplication a(argc, argv);
RemoteControlWidget w;
- LocationUi l;
- w.addToolBoxPage(&l);
+ LocationUi locationUi;
+ w.addToolBoxPage(&locationUi);
BatteryButton batteryButton;
w.addMenuButton(&batteryButton);
@@ -18,6 +20,11 @@ int main(int argc, char *argv[])
PowerButton powerButton;
w.addMenuButton(&powerButton);
+ ScriptAdapter adapter;
+ adapter.addScriptInterface("location", locationUi.scriptInterface());
+ ScriptUi scriptUi(&adapter);
+ w.addToolBoxPage(&scriptUi);
+
w.show();
return a.exec();
}
diff --git a/library/components/scriptui.cpp b/library/components/scriptui.cpp
new file mode 100644
index 0000000..43ed447
--- /dev/null
+++ b/library/components/scriptui.cpp
@@ -0,0 +1,153 @@
+#include "scriptui.h"
+
+#include "scriptadapter.h"
+#include "optionsitem.h"
+
+#include <QtCore/QCoreApplication>
+#include <QtCore/QSignalMapper>
+#include <QtGui/QGridLayout>
+#include <QtGui/QAbstractItemView>
+#include <QtGui/QPushButton>
+#include <QtGui/QHeaderView>
+#include <QtGui/QTableWidget>
+#include <QtGui/QTreeView>
+#include <QtGui/QFileSystemModel>
+#include <QtGui/QToolButton>
+#include <QtGui/QMenu>
+
+ScriptUi::ScriptUi(ScriptAdapter *adapter, QWidget *parent)
+ : ToolBoxPage(parent)
+ , mAdapter(adapter)
+{
+ QStringList tags;
+ QList<OptionsItem *> options;
+ tags << tr("scripting");
+
+ QGridLayout *scriptingLayout = new QGridLayout();
+ {
+ mScriptList = new QTableWidget(0, 2);
+ mScriptList->verticalHeader()->setHidden(true);
+ QStringList headerLabels;
+ headerLabels << tr("Active scripts") << tr("Status");
+ mScriptList->setHorizontalHeaderLabels(headerLabels);
+ mScriptList->horizontalHeader()->setStretchLastSection(false);
+ mScriptList->horizontalHeader()->setResizeMode(0, QHeaderView::Stretch);
+ mScriptList->horizontalHeader()->setResizeMode(1, QHeaderView::Fixed);
+ mScriptList->setSelectionMode(QAbstractItemView::SingleSelection);
+ mScriptList->setSelectionBehavior(QAbstractItemView::SelectRows);
+
+ scriptingLayout->addWidget(mScriptList, 1, 0);
+
+ QVBoxLayout *scriptListButtonLayout = new QVBoxLayout();
+ {
+ QPushButton *abortButton = new QPushButton(tr("Abort"));
+ QPushButton *pauseButton = new QPushButton(tr("Pause"));
+ connect(abortButton, SIGNAL(clicked()), this, SLOT(abortScript()));
+ connect(pauseButton, SIGNAL(clicked()), this, SLOT(togglePauseScript()));
+
+ scriptListButtonLayout->addWidget(pauseButton);
+ scriptListButtonLayout->addWidget(abortButton);
+ scriptListButtonLayout->addStretch();
+ }
+ scriptingLayout->addLayout(scriptListButtonLayout, 1, 1);
+
+ mFileView = new QTreeView();
+ mFileModel = new QFileSystemModel(mFileView);
+ QDir scriptsDir(QCoreApplication::applicationDirPath());
+ scriptsDir.cd(QLatin1String("scripts"));
+ QStringList nameFilters;
+ nameFilters << "*.js" << "*.qs";
+ mFileModel->setNameFilters(nameFilters);
+ mFileModel->setNameFilterDisables(false);
+ mFileModel->setRootPath(scriptsDir.path());
+ mFileView->setModel(mFileModel);
+ mFileView->setColumnHidden(1, true);
+ mFileView->setColumnHidden(2, true);
+ mFileView->setColumnHidden(3, true);
+ mFileView->setHeaderHidden(true);
+ mFileView->setRootIndex(mFileModel->index(scriptsDir.path()));
+ connect(mFileView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(runSelectedScript()));
+
+ scriptingLayout->addWidget(mFileView, 0, 0);
+
+ QVBoxLayout *scriptRunnerButtonLayout = new QVBoxLayout();
+ {
+ QPushButton *runButton = new QPushButton(tr("Run"));
+ connect(runButton, SIGNAL(clicked()), this, SLOT(runSelectedScript()));
+
+ scriptRunnerButtonLayout->addWidget(runButton);
+ scriptRunnerButtonLayout->addStretch();
+ }
+ scriptingLayout->addLayout(scriptRunnerButtonLayout, 0, 1);
+ }
+
+ QWidget *tmp = new QWidget();
+ tmp->setLayout(scriptingLayout);
+
+ OptionsItem *item = new OptionsItem("", tmp, true);
+ item->setTags(tags);
+ options << item;
+
+ connect(mAdapter, SIGNAL(scriptStart(ScriptFiber*)), this, SLOT(addScript(ScriptFiber*)));
+ connect(mAdapter, SIGNAL(scriptStop(int)), this, SLOT(removeScript(int)));
+
+ setTitle(tr("Scripting"));
+ setOptions(options);
+}
+
+void ScriptUi::runScript(const QString &filePath)
+{
+ mAdapter->run(filePath);
+}
+
+void ScriptUi::runSelectedScript()
+{
+ QModelIndex index = mFileView->currentIndex();
+
+ if (index.isValid() && !mFileModel->isDir(index)) {
+ QString scriptFilePath = mFileModel->filePath(index);
+ runScript(scriptFilePath);
+ }
+}
+
+void ScriptUi::abortScript()
+{
+ if (!mScriptList->currentItem())
+ return;
+
+ ScriptFiber *script = mAdapter->script(mScriptList->currentIndex().row());
+ if (!script)
+ return;
+
+ script->requestTerminate();
+ mScriptList->item(mScriptList->currentItem()->row(), 1)->setText(tr("aborting"));
+}
+
+void ScriptUi::togglePauseScript()
+{
+ if (!mScriptList->currentItem())
+ return;
+
+ ScriptFiber *script = mAdapter->script(mScriptList->currentIndex().row());
+ if (!script)
+ return;
+
+ script->togglePause();
+ if (script->isPaused())
+ mScriptList->item(mScriptList->currentItem()->row(), 1)->setText(tr("paused"));
+ else
+ mScriptList->item(mScriptList->currentItem()->row(), 1)->setText(tr("running"));
+}
+
+void ScriptUi::addScript(ScriptFiber *script)
+{
+ int newRow = mScriptList->rowCount();
+ mScriptList->insertRow(newRow);
+ mScriptList->setItem(newRow, 0, new QTableWidgetItem(script->name()));
+ mScriptList->setItem(newRow, 1, new QTableWidgetItem(tr("running")));
+}
+
+void ScriptUi::removeScript(int index)
+{
+ mScriptList->removeRow(index);
+}
diff --git a/library/components/scriptui.h b/library/components/scriptui.h
new file mode 100644
index 0000000..80f9b98
--- /dev/null
+++ b/library/components/scriptui.h
@@ -0,0 +1,38 @@
+#ifndef SCRIPTUI_H
+#define SCRIPTUI_H
+
+#include "remotecontrolwidget_global.h"
+#include "toolbox.h"
+
+#include <QtCore/QObject>
+
+class ScriptAdapter;
+class ScriptFiber;
+class QTreeView;
+class QFileSystemModel;
+class QTableWidget;
+
+class REMOTECONTROLWIDGETSHARED_EXPORT ScriptUi : public ToolBoxPage
+{
+ Q_OBJECT
+public:
+ ScriptUi(ScriptAdapter *adapter, QWidget *parent = 0);
+
+private slots:
+ void runScript(const QString &filePath);
+ void runSelectedScript();
+ void abortScript();
+ void togglePauseScript();
+
+ void addScript(ScriptFiber *script);
+ void removeScript(int index);
+
+private:
+ ScriptAdapter *mAdapter;
+
+ QTreeView *mFileView;
+ QFileSystemModel *mFileModel;
+ QTableWidget *mScriptList;
+};
+
+#endif // SCRIPTUI_H
diff --git a/library/fibers/fiber.cpp b/library/fibers/fiber.cpp
new file mode 100644
index 0000000..7208ced
--- /dev/null
+++ b/library/fibers/fiber.cpp
@@ -0,0 +1,120 @@
+#include <stdlib.h>
+#include <QtCore/QtGlobal>
+
+#include "fiber.h"
+
+/*!
+ \class Fiber
+ \brief The Fiber class provides cooperatively scheduled stacks of execution.
+
+ Fibers, also known as coroutines, allow managing multiple stacks in the same
+ thread.
+
+ To create a fiber, subclass Fiber and override the run() method. To run it,
+ call cont(). This will execute the code in run() until it calls Fiber::yield().
+ At that point, the call to cont() returns. Subsequent calls to cont() will
+ continue execution of the fiber just after the yield().
+
+ Example:
+ class MyFiber : public Fiber
+ {
+ virtual void run()
+ {
+ qDebug() << "1";
+ Fiber::yield();
+ qDebug() << "2";
+ }
+ }
+
+ MyFiber fib;
+ qDebug() << "0.5";
+ fib.cont(); // prints 1
+ qDebug() << "1.5";
+ fib.cont(); // prints 2
+*/
+
+#ifdef Q_OS_MAC
+extern "C" void switchStackInternal(void* to, void** from);
+void initializeStack(void *data, int size, void (*entry)(), void **stackPointer);
+void switchStack(void* to, void** from) { switchStackInternal(to, from); }
+#else
+extern "C" void _switchStackInternal(void* to, void** from);
+void initializeStack(void *data, int size, void (*entry)(), void **stackPointer);
+void switchStack(void* to, void** from) { _switchStackInternal(to, from); }
+#endif
+
+Fiber *Fiber::_currentFiber = 0;
+
+Fiber::Fiber(int stackSize)
+ : _stackData(0)
+ , _stackPointer(0)
+ , _previousFiber(0)
+ , _status(NotStarted)
+{
+ // establish starting fiber context if necessary
+ currentFiber();
+
+ _stackData = malloc(stackSize);
+ initializeStack(_stackData, stackSize, &entryPoint, &_stackPointer);
+}
+
+Fiber::Fiber(bool)
+ : _stackData(0)
+ , _stackPointer(0)
+ , _previousFiber(0)
+ , _status(Running)
+{
+}
+
+Fiber::~Fiber()
+{
+ if (_stackData)
+ free(_stackData);
+}
+
+Fiber *Fiber::currentFiber()
+{
+ // establish a context for the starting fiber
+ if (!_currentFiber)
+ _currentFiber = new Fiber(true);
+
+ return _currentFiber;
+}
+
+void Fiber::entryPoint()
+{
+ _currentFiber->run();
+ yieldHelper(Terminated);
+ Q_ASSERT(0); // unreachable
+}
+
+// returns whether it can be continued again
+bool Fiber::cont()
+{
+ Q_ASSERT(_status == NotStarted || _status == Stopped);
+ Q_ASSERT(!_previousFiber);
+
+ _previousFiber = _currentFiber;
+ _currentFiber = this;
+ _status = Running;
+ switchStack(_stackPointer, &_previousFiber->_stackPointer);
+ return _status != Terminated;
+}
+
+void Fiber::yield()
+{
+ yieldHelper(Stopped);
+}
+
+void Fiber::yieldHelper(Status stopStatus)
+{
+ Fiber *stoppingFiber = _currentFiber;
+ Q_ASSERT(stoppingFiber);
+ Q_ASSERT(stoppingFiber->_previousFiber);
+ Q_ASSERT(stoppingFiber->_status == Running);
+
+ _currentFiber = stoppingFiber->_previousFiber;
+ stoppingFiber->_previousFiber = 0;
+ stoppingFiber->_status = stopStatus;
+ switchStack(_currentFiber->_stackPointer, &stoppingFiber->_stackPointer);
+}
diff --git a/library/fibers/fiber.h b/library/fibers/fiber.h
new file mode 100644
index 0000000..6afc57d
--- /dev/null
+++ b/library/fibers/fiber.h
@@ -0,0 +1,48 @@
+#ifndef INCLUDE_FIBER_H
+#define INCLUDE_FIBER_H
+
+class Fiber
+{
+public:
+ enum Status
+ {
+ NotStarted,
+ Running,
+ Stopped,
+ Terminated
+ };
+
+public:
+ explicit Fiber(int stackSize = 32768);
+ virtual ~Fiber();
+
+ bool cont();
+ static void yield();
+
+ static Fiber *currentFiber();
+
+ Status status()
+ { return _status; }
+
+protected:
+ // could be abstract if subclassing for start fiber
+ virtual void run() {}
+
+private:
+ // for the original fiber
+ Fiber(bool);
+
+ static void yieldHelper(Status stopStatus);
+
+ void *_stackData;
+ void *_stackPointer;
+ Fiber *_previousFiber;
+ Status _status;
+
+ // should be thread local
+ static Fiber *_currentFiber;
+
+ static void entryPoint();
+};
+
+#endif // INCLUDE_FIBER_H
diff --git a/library/fibers/fibers.pri b/library/fibers/fibers.pri
new file mode 100644
index 0000000..24fafca
--- /dev/null
+++ b/library/fibers/fibers.pri
@@ -0,0 +1,43 @@
+INCLUDEPATH += fibers
+DEPENDPATH += fibers
+
+HEADERS += fiber.h
+SOURCES += fiber.cpp
+
+contains(QMAKE_CXX,g++) {
+ win32 {
+ # will fail for 64 bit win!
+ SOURCES += \
+ switchstack_gcc_32_win.cpp \
+ initializestack_32.cpp
+ }
+
+ mac {
+ CONFIG(x86_64) {
+ SOURCES += \
+ switchstack_gcc_64_linux_mac.s \
+ initializestack_64_linux_mac.cpp
+ } else {
+ SOURCES += \
+ switchstack_gcc_32_linux_mac.s \
+ initializestack_32.cpp
+ }
+ }
+ !win32:!mac {
+ contains(QMAKE_CFLAGS,-m64) {
+ SOURCES += \
+ switchstack_gcc_64_linux_mac.s \
+ initializestack_64_linux_mac.cpp
+ } else {
+ SOURCES += \
+ switchstack_gcc_32_linux_mac.s \
+ initializestack_32.cpp
+ }
+ }
+}
+win32:contains(QMAKE_CXX,cl) {
+ # will fail for 64 bit win!
+ SOURCES += \
+ switchstack_msvc_32.cpp \
+ initializestack_32.cpp
+}
diff --git a/library/fibers/initializestack_32.cpp b/library/fibers/initializestack_32.cpp
new file mode 100644
index 0000000..d387460
--- /dev/null
+++ b/library/fibers/initializestack_32.cpp
@@ -0,0 +1,21 @@
+#include <stdlib.h>
+
+void initializeStack(void *data, int size, void (*entry)(), void **stackPointer)
+{
+ void* stackBottom = (char*)data + size;
+ // align to 16 byte
+ stackBottom = (void*)((size_t)stackBottom & ~0xF);
+
+ void **p = (void**)stackBottom;
+
+ *(--p) = 0; // align
+ *(--p) = (void*)entry; // rip
+ *(--p) = stackBottom; // ebp
+ *(--p) = 0; // ebx
+ *(--p) = 0; // esi
+ *(--p) = 0; // edi
+ *(--p) = (void*)0x00001f80; // SIMD floating point control default
+ *(--p) = (void*)0x0000033f; // floating point control default
+
+ *stackPointer = p;
+}
diff --git a/library/fibers/initializestack_64_linux_mac.cpp b/library/fibers/initializestack_64_linux_mac.cpp
new file mode 100644
index 0000000..5465dfb
--- /dev/null
+++ b/library/fibers/initializestack_64_linux_mac.cpp
@@ -0,0 +1,22 @@
+#include <stdlib.h>
+
+void initializeStack(void *data, int size, void (*entry)(), void **stackPointer)
+{
+ void* stackBottom = (char*)data + size;
+ // align to 16 byte
+ stackBottom = (void*)((size_t)stackBottom & ~0xF);
+
+ void **p = (void**)stackBottom;
+
+ *(--p) = 0; // align
+ *(--p) = (void*)entry; // rip
+ *(--p) = stackBottom; // rbp
+ *(--p) = 0; // rbx
+ *(--p) = 0; // r12
+ *(--p) = 0; // r13
+ *(--p) = 0; // r14
+ *(--p) = 0; // r15
+ *(--p) = (void*)0x00001f800000033f; // SIMD and regular floating point control defaults
+
+ *stackPointer = p;
+}
diff --git a/library/fibers/initializestack_64_win.cpp b/library/fibers/initializestack_64_win.cpp
new file mode 100644
index 0000000..3b29caf
--- /dev/null
+++ b/library/fibers/initializestack_64_win.cpp
@@ -0,0 +1,44 @@
+#include <stdlib.h>
+
+void initializeStack(void *data, int size, void (*entry)(), void **stackPointer)
+{
+ void* stackBottom = (char*)data + size;
+ // align to 16 byte
+ stackBottom = (void*)((size_t)stackBottom & ~0xF);
+
+ void **p = (void**)stackBottom;
+
+ //*(--p) = 0; // align to 16 bytes
+ *(--p) = (void*)fn; // rip
+ *(--p) = entry; // rbp
+ *(--p) = 0; // rbx
+ *(--p) = 0; // r12
+ *(--p) = 0; // r13
+ *(--p) = 0; // r14
+ *(--p) = 0; // r15
+ *(--p) = 0; // rsi
+ *(--p) = 0; // rdi
+ *(--p) = 0; // xmm6
+ *(--p) = 0; // xmm6
+ *(--p) = 0; // xmm7
+ *(--p) = 0; // xmm7
+ *(--p) = 0; // xmm8
+ *(--p) = 0; // xmm8
+ *(--p) = 0; // xmm9
+ *(--p) = 0; // xmm9
+ *(--p) = 0; // xmm10
+ *(--p) = 0; // xmm10
+ *(--p) = 0; // xmm11
+ *(--p) = 0; // xmm11
+ *(--p) = 0; // xmm12
+ *(--p) = 0; // xmm12
+ *(--p) = 0; // xmm13
+ *(--p) = 0; // xmm13
+ *(--p) = 0; // xmm14
+ *(--p) = 0; // xmm14
+ *(--p) = 0; // xmm15
+ *(--p) = 0; // xmm15
+ *(--p) = (void*)0x00001f800000033f; // SIMD and regular floating point control defaults
+
+ *stackPointer = p;
+}
diff --git a/library/fibers/switchstack_gcc_32_linux_mac.s b/library/fibers/switchstack_gcc_32_linux_mac.s
new file mode 100644
index 0000000..e4eac63
--- /dev/null
+++ b/library/fibers/switchstack_gcc_32_linux_mac.s
@@ -0,0 +1,41 @@
+.text
+.globl _switchStackInternal
+
+_switchStackInternal:
+ // save callee-saved registers
+ push %ebp
+ movl %esp, %ebp
+ push %ebx
+ push %esi
+ push %edi
+
+ // store SIMD floating point control word
+ sub $4, %esp
+ stmxcsr (%esp)
+
+ // store floating point control bytes
+ sub $4, %esp
+ fstcw (%esp)
+
+ // save the old stack pointer
+ movl 0xc(%ebp), %edx
+ movl %esp, (%edx)
+ // set the new stack pointer
+ movl 0x8(%ebp), %esp
+
+ // restore floating point control bytes
+ fnclex
+ fldcw (%esp)
+ add $4, %esp
+
+ // restore SIMD floating point control word
+ ldmxcsr (%esp)
+ add $4, %esp
+
+ // pop callee-saved registers
+ pop %edi
+ pop %esi
+ pop %ebx
+ pop %ebp
+
+ ret
diff --git a/library/fibers/switchstack_gcc_32_win.s b/library/fibers/switchstack_gcc_32_win.s
new file mode 100644
index 0000000..2e8c140
--- /dev/null
+++ b/library/fibers/switchstack_gcc_32_win.s
@@ -0,0 +1,42 @@
+.global _switchStackInternal
+.section .text
+.def _switchStackInternal ; .scl 2 ; .type 32 ; .endef
+
+_switchStackInternal:
+ // save callee-saved registers
+ push %ebp
+ movl %esp, %ebp
+ push %ebx
+ push %esi
+ push %edi
+
+ // store SIMD floating point control word
+ sub $4, %esp
+ stmxcsr (%esp)
+
+ // store floating point control bytes
+ sub $4, %esp
+ fstcw (%esp)
+
+ // save the old stack pointer
+ movl 0xc(%ebp), %edx
+ movl %esp, (%edx)
+ // set the new stack pointer
+ movl 0x8(%ebp), %esp
+
+ // restore floating point control bytes
+ fnclex
+ fldcw (%esp)
+ add $4, %esp
+
+ // restore SIMD floating point control word
+ ldmxcsr (%esp)
+ add $4, %esp
+
+ // pop callee-saved registers
+ pop %edi
+ pop %esi
+ pop %ebx
+ pop %ebp
+
+ ret
diff --git a/library/fibers/switchstack_gcc_64_linux_mac.s b/library/fibers/switchstack_gcc_64_linux_mac.s
new file mode 100644
index 0000000..ced6a5d
--- /dev/null
+++ b/library/fibers/switchstack_gcc_64_linux_mac.s
@@ -0,0 +1,44 @@
+.text
+.globl _switchStackInternal
+
+_switchStackInternal:
+ // save callee-saved registers
+ push %rbp
+ movq %rsp, %rbp
+ push %rbx
+ push %r12
+ push %r13
+ push %r14
+ push %r15
+
+ // store SIMD floating point control word
+ sub $4, %rsp
+ stmxcsr (%rsp)
+
+ // store floating point control bytes
+ sub $4, %rsp
+ fstcw (%rsp)
+
+ // save the old stack pointer (second arg in rsi)
+ movq %rsp, (%rsi)
+ // set the new stack pointer (first arg in rdi)
+ movq %rdi, %rsp
+
+ // restore floating point control bytes
+ fnclex
+ fldcw (%rsp)
+ add $4, %rsp
+
+ // restore SIMD floating point control word
+ ldmxcsr (%rsp)
+ add $4, %rsp
+
+ // pop callee-saved registers
+ pop %r15
+ pop %r14
+ pop %r13
+ pop %r12
+ pop %rbx
+ pop %rbp
+
+ retq
diff --git a/library/fibers/switchstack_gcc_64_win.s b/library/fibers/switchstack_gcc_64_win.s
new file mode 100644
index 0000000..f75c53c
--- /dev/null
+++ b/library/fibers/switchstack_gcc_64_win.s
@@ -0,0 +1,93 @@
+.global _switchStackInternal
+.section .text
+.def _switchStackInternal ; .scl 2 ; .type 32 ; .endef
+
+_switchStackInternal:
+ // save callee-saved registers
+ push %rbp
+ movq %rsp, %rbp
+ push %rbx
+ push %r12
+ push %r13
+ push %r14
+ push %r15
+
+ push %rsi
+ push %rdi
+
+ sub $0x10, %rsp
+ movupd %xmm6, (%rsp)
+ sub $0x10, %rsp
+ movupd %xmm7, (%rsp)
+ sub $0x10, %rsp
+ movupd %xmm8, (%rsp)
+ sub $0x10, %rsp
+ movupd %xmm9, (%rsp)
+ sub $0x10, %rsp
+ movupd %xmm10, (%rsp)
+ sub $0x10, %rsp
+ movupd %xmm11, (%rsp)
+ sub $0x10, %rsp
+ movupd %xmm12, (%rsp)
+ sub $0x10, %rsp
+ movupd %xmm13, (%rsp)
+ sub $0x10, %rsp
+ movupd %xmm14, (%rsp)
+ sub $0x10, %rsp
+ movupd %xmm15, (%rsp)
+
+ // store SIMD floating point control word
+ sub $4, %rsp
+ stmxcsr (%rsp)
+
+ // store floating point control bytes
+ sub $4, %rsp
+ fstcw (%rsp)
+
+ // save the old stack pointer (second arg in rsi)
+ movq %rsp, (%rsi)
+ // set the new stack pointer (first arg in rdi)
+ movq %rdi, %rsp
+
+ // restore floating point control bytes
+ fnclex
+ fldcw (%rsp)
+ add $4, %rsp
+
+ // restore SIMD floating point control word
+ ldmxcsr (%rsp)
+ add $4, %rsp
+
+ // pop callee-saved registers
+ movupd (%rsp), %xmm15
+ add $0x10, %rsp
+ movupd (%rsp), %xmm14
+ add $0x10, %rsp
+ movupd (%rsp), %xmm13
+ add $0x10, %rsp
+ movupd (%rsp), %xmm12
+ add $0x10, %rsp
+ movupd (%rsp), %xmm11
+ add $0x10, %rsp
+ movupd (%rsp), %xmm10
+ add $0x10, %rsp
+ movupd (%rsp), %xmm9
+ add $0x10, %rsp
+ movupd (%rsp), %xmm8
+ add $0x10, %rsp
+ movupd (%rsp), %xmm7
+ add $0x10, %rsp
+ movupd (%rsp), %xmm6
+ add $0x10, %rsp
+
+ pop %rdi
+ pop %rsi
+
+ pop %r15
+ pop %r14
+ pop %r13
+ pop %r12
+ pop %rbx
+ pop %rbp
+
+ retq
diff --git a/library/fibers/switchstack_msvc_32.cpp b/library/fibers/switchstack_msvc_32.cpp
new file mode 100644
index 0000000..eae68ee
--- /dev/null
+++ b/library/fibers/switchstack_msvc_32.cpp
@@ -0,0 +1,44 @@
+extern "C" {
+void __declspec(naked) _switchStackInternal(void* to, void** from)
+{
+ __asm {
+ // save callee-saved registers
+ PUSH EBP
+ MOV EBP, ESP
+ PUSH EBX
+ PUSH ESI
+ PUSH EDI
+
+ // store SIMD floating point control word
+ SUB ESP, 4
+ STMXCSR [ESP]
+
+ // store floating point control bytes
+ SUB ESP, 4
+ FSTCW [ESP]
+
+ // save the old stack pointer
+ MOV EDX, dword ptr 12[EBP]
+ MOV [EDX], ESP
+ // set the new stack pointer
+ MOV ESP, dword ptr 8[EBP]
+
+ // restore floating point control bytes
+ FNCLEX
+ FLDCW [ESP]
+ ADD ESP, 4
+
+ // restore SIMD floating point control word
+ LDMXCSR [ESP]
+ ADD ESP, 4
+
+ // pop callee-saved registers
+ POP EDI
+ POP ESI
+ POP EBX
+ POP EBP
+
+ RET
+ }
+}
+}
diff --git a/library/fibers/switchstack_msvc_64.asm b/library/fibers/switchstack_msvc_64.asm
new file mode 100644
index 0000000..3c6f321
--- /dev/null
+++ b/library/fibers/switchstack_msvc_64.asm
@@ -0,0 +1,98 @@
+.model flat, c
+.code
+
+_switchStackInternal PROC to:QWORD, from:QWORD
+ ; save callee-saved registers
+ PUSH RBP
+ MOV RBP, RSP
+ PUSH RBX
+ PUSH ESI
+ PUSH EDI
+
+ PUSH R12
+ PUSH R13
+ PUSH R14
+ PUSH R15
+
+ PUSH RSI
+ PUSH RDI
+
+ SUB RSP, 0x10
+ MOVUPD [RSP], XMM6
+ SUB RSP, 0x10
+ MOVUPD [RSP], XMM7
+ SUB RSP, 0x10
+ MOVUPD [RSP], XMM8
+ SUB RSP, 0x10
+ MOVUPD [RSP], XMM9
+ SUB RSP, 0x10
+ MOVUPD [RSP], XMM10
+ SUB RSP, 0x10
+ MOVUPD [RSP], XMM11
+ SUB RSP, 0x10
+ MOVUPD [RSP], XMM12
+ SUB RSP, 0x10
+ MOVUPD [RSP], XMM13
+ SUB RSP, 0x10
+ MOVUPD [RSP], XMM14
+ SUB RSP, 0x10
+ MOVUPD [RSP], XMM15
+
+ ; store SIMD floating point control word
+ SUB RSP, 4
+ STMXCSR [RSP]
+
+ ; store floating point control bytes
+ SUB RSP, 4
+ FSTCW [RSP]
+
+ ; save the old stack pointer
+ MOV [from], RSP
+ ; set the new stack pointer
+ MOV RSP, to
+
+ ; restore floating point control bytes
+ FNCLEX
+ FLDCW [RSP]
+ ADD RSP, 4
+
+ ; restore SIMD floating point control word
+ LDMXCSR [RSP]
+ ADD RSP, 4
+
+ ; pop callee-saved registers
+ MOVUPD XMM15, [RSP]
+ ADD RSP, 0x10
+ MOVUPD XMM14, [RSP]
+ ADD RSP, 0x10
+ MOVUPD XMM13, [RSP]
+ ADD RSP, 0x10
+ MOVUPD XMM12, [RSP]
+ ADD RSP, 0x10
+ MOVUPD XMM11, [RSP]
+ ADD RSP, 0x10
+ MOVUPD XMM10, [RSP]
+ ADD RSP, 0x10
+ MOVUPD XMM9, [RSP]
+ ADD RSP, 0x10
+ MOVUPD XMM8, [RSP]
+ ADD RSP, 0x10
+ MOVUPD XMM7, [RSP]
+ ADD RSP, 0x10
+ MOVUPD XMM6, [RSP]
+ ADD RSP, 0x10
+
+ POP RDI
+ POP RSI
+
+ POP R15
+ POP R14
+ POP R13
+ POP R12
+ POP RBX
+ POP RBP
+
+ RET
+_switchStackInternal ENDP
+
+end
diff --git a/library/remotecontrolwidget.pro b/library/remotecontrolwidget.pro
index 3244dfe..c99b487 100644
--- a/library/remotecontrolwidget.pro
+++ b/library/remotecontrolwidget.pro
@@ -15,6 +15,7 @@ SOURCES += \
remotecontrolwidget.cpp \
toolbox.cpp \
optionsitem.cpp \
+ scriptadapter.cpp \
style/stylehelper.cpp \
style/styledbar.cpp \
style/styleanimator.cpp \
@@ -23,13 +24,15 @@ SOURCES += \
style/fancylineedit.cpp \
components/locationui.cpp \
components/powerbutton.cpp \
- components/batterybutton.cpp
+ components/batterybutton.cpp \
+ components/scriptui.cpp
HEADERS += \
remotecontrolwidget.h \
remotecontrolwidget_global.h \
toolbox.h \
optionsitem.h \
+ scriptadapter.h \
style/styledbar.h \
style/styleanimator.h \
style/qtcassert.h \
@@ -39,8 +42,11 @@ HEADERS += \
style/stylehelper.h \
components/locationui.h \
components/powerbutton.h \
- components/batterybutton.h
+ components/batterybutton.h \
+ components/scriptui.h
RESOURCES += \
style/style.qrc \
components/component.qrc
+
+include(fibers/fibers.pri)
diff --git a/library/scriptadapter.cpp b/library/scriptadapter.cpp
new file mode 100644
index 0000000..dabf103
--- /dev/null
+++ b/library/scriptadapter.cpp
@@ -0,0 +1,214 @@
+#include "scriptadapter.h"
+
+#include "optionsitem.h"
+
+#include <QtCore/QMetaEnum>
+#include <QtCore/QCoreApplication>
+#include <QtCore/QTimer>
+#include <QtCore/QTime>
+#include <QtCore/QFile>
+#include <QtCore/QDir>
+#include <QtCore/QDebug>
+#include <QtGui/QMessageBox>
+
+template <typename Enum>
+static void scriptToEnum(const QScriptValue &obj, Enum &e)
+{
+ e = static_cast<Enum>(obj.toInt32());
+}
+
+template <typename Enum>
+static QScriptValue enumToScript(QScriptEngine *engine, const Enum &e)
+{
+ QScriptValue obj = engine->newVariant(e);
+ return obj;
+}
+
+// Adds a property 'name' to 'to' that contains all the values of the enum 'name',
+// which must be registered in 'mo'. Also registers the type 'Enum' to the QScriptEngine.
+template <typename Enum>
+static void exposeEnum(QScriptEngine *engine, QScriptValue *to, const char *name, const QMetaObject *mo)
+{
+ qScriptRegisterMetaType<Enum>(engine, &enumToScript<Enum>, &scriptToEnum<Enum>);
+ QScriptValue enumvals = engine->newObject();
+ int enumIndex = mo->indexOfEnumerator(name);
+ Q_ASSERT(enumIndex != -1 && "enum not found in meta object");
+ QMetaEnum metaEnum = mo->enumerator(enumIndex);
+ for (int i = 0; i < metaEnum.keyCount(); ++i)
+ enumvals.setProperty(metaEnum.key(i), metaEnum.value(i));
+ to->setProperty(name, enumvals);
+}
+
+ScriptAdapter::ScriptAdapter(QObject *parent)
+ : QObject(parent)
+{
+ mScriptRunner = new QTimer(this);
+ mScriptRunner->setSingleShot(true);
+ mScriptRunner->setInterval(100);
+ connect(mScriptRunner, SIGNAL(timeout()), this, SLOT(continueScripts()));
+ mScriptRunner->start();
+
+ mMessageBox = new QMessageBox;
+}
+
+ScriptAdapter::~ScriptAdapter()
+{
+ delete mMessageBox;
+}
+
+void ScriptAdapter::addScriptInterface(const QString &name, QObject *interface)
+{
+ mScriptInterfaces.insert(name, interface);
+}
+
+ScriptFiber *ScriptAdapter::script(int index)
+{
+ if (index >= 0 && index < mActiveScripts.length())
+ return mActiveScripts[index];
+ else
+ return 0;
+}
+
+void ScriptAdapter::runAutostartScripts()
+{
+ QDir dir(QCoreApplication::applicationDirPath());
+ dir.cd(QLatin1String("scripts"));
+ dir.cd(QLatin1String("autostart"));
+
+ QStringList scriptPatterns;
+ scriptPatterns << "*.js" << "*.qs";
+ foreach (const QFileInfo &file, dir.entryInfoList(scriptPatterns, QDir::Files)) {
+ run(file.filePath());
+ }
+}
+
+ScriptFiber *ScriptAdapter::run(const QString &filePath)
+{
+ QFile file(filePath);
+ if (!file.open(QIODevice::ReadOnly)) {
+ qWarning() << "Could not read script file: " << filePath;
+ return 0;
+ }
+
+ QFileInfo fileInfo(file);
+ ScriptFiber *script = new ScriptFiber(fileInfo.baseName(), file.readAll(), this);
+ mActiveScripts += script;
+
+ emit scriptStart(script);
+
+ return script;
+}
+
+void ScriptAdapter::continueScripts() {
+ for (int i = 0; i < mActiveScripts.length(); ++i) {
+ ScriptFiber *script = mActiveScripts[i];
+
+ if (script->status() == Fiber::Terminated) {
+ emit scriptStop(i);
+
+ mActiveScripts.removeOne(script);
+ delete script;
+ break;
+ }
+
+ if (!script->isPaused() || script->isTerminating()) {
+ script->cont();
+ }
+ }
+
+ mScriptRunner->start();
+}
+
+void ScriptAdapter::yield(int ms)
+{
+ ScriptFiber *scriptFiber = dynamic_cast<ScriptFiber *>(Fiber::currentFiber());
+ if (!scriptFiber)
+ return;
+
+ QTime timer;
+ timer.start();
+ while (timer.elapsed() < ms && !scriptFiber->isTerminating()) {
+ scriptFiber->suspend();
+ }
+}
+
+void ScriptAdapter::messageBox(const QString &text)
+{
+ mMessageBox->setText(text);
+
+ // message boxes should block
+ ScriptFiber *fiber = dynamic_cast<ScriptFiber *>(Fiber::currentFiber());
+ fiber->beginBlocking();
+ mMessageBox->exec();
+ fiber->endBlocking();
+}
+
+ScriptFiber::ScriptFiber(const QString &name, const QString &scriptCode,
+ ScriptAdapter *adapter)
+ : Fiber(524228) // FIXME: 131072 was not big enough...
+ , mName(name)
+ , mScriptCode(scriptCode)
+ , mAdapter(adapter)
+ , mPaused(false)
+ , mTerminate(false)
+{
+ mStopTimer.setInterval(10);
+ connect(&mStopTimer, SIGNAL(timeout()), this, SLOT(suspend()));
+
+ // trigger event processing during script evaluation
+ mEngine.setProcessEventsInterval(10);
+
+ // inject the ScriptAdapters functions into the global scope
+ QScriptValue adapterValue = mEngine.newQObject(adapter);
+ adapterValue.setPrototype(mEngine.globalObject());
+ mEngine.setGlobalObject(adapterValue);
+
+ QHashIterator<QString, QObject *> iter(mAdapter->mScriptInterfaces);
+ while (iter.hasNext()) {
+ iter.next();
+ QScriptValue interface = mEngine.newQObject(iter.value());
+ mEngine.globalObject().setProperty(iter.key(), interface);
+ }
+}
+
+void ScriptFiber::run()
+{
+ mStopTimer.start();
+ QScriptValue value = mEngine.evaluate(mScriptCode);
+ if (value.isError()) {
+ qWarning() << "Script execution resulted in an error:" << value.toString();
+ }
+ mStopTimer.stop();
+}
+
+void ScriptFiber::requestTerminate()
+{
+ mTerminate = true;
+}
+
+void ScriptFiber::togglePause()
+{
+ mPaused = !mPaused;
+}
+
+void ScriptFiber::suspend()
+{
+ if (Fiber::currentFiber() == this) {
+ Fiber::yield();
+
+ if (mTerminate) {
+ mEngine.abortEvaluation(mEngine.currentContext()->throwError("Aborted"));
+ mStopTimer.stop();
+ }
+ }
+}
+
+void ScriptFiber::beginBlocking()
+{
+ mStopTimer.stop();
+}
+
+void ScriptFiber::endBlocking()
+{
+ mStopTimer.start();
+}
diff --git a/library/scriptadapter.h b/library/scriptadapter.h
new file mode 100644
index 0000000..d17525e
--- /dev/null
+++ b/library/scriptadapter.h
@@ -0,0 +1,90 @@
+#ifndef SCRIPTADAPTER_H
+#define SCRIPTADAPTER_H
+
+#include "remotecontrolwidget_global.h"
+#include "fibers/fiber.h"
+
+#include <QtCore/QObject>
+#include <QtCore/QTimer>
+#include <QtCore/QStringList>
+#include <QtCore/QHash>
+#include <QtScript/QScriptEngine>
+
+class ScriptFiber;
+class QMessageBox;
+
+class REMOTECONTROLWIDGETSHARED_EXPORT ScriptAdapter : public QObject
+{
+ Q_OBJECT
+public:
+ explicit ScriptAdapter(QObject *parent = 0);
+ virtual ~ScriptAdapter();
+
+ Q_INVOKABLE void yield(int ms);
+ Q_INVOKABLE void messageBox(const QString &text);
+
+ void runAutostartScripts();
+ ScriptFiber *script(int index);
+
+ void addScriptInterface(const QString &name, QObject *interface);
+
+public slots:
+ ScriptFiber *run(const QString &filePath);
+
+signals:
+ void scriptStart(ScriptFiber *script) const;
+ void scriptStop(int index) const;
+
+private slots:
+ void continueScripts();
+
+private:
+ QHash<QString, QObject *> mScriptInterfaces;
+ QList<ScriptFiber *> mActiveScripts;
+ QTimer *mScriptRunner;
+
+ QMessageBox *mMessageBox;
+
+ friend class ScriptFiber;
+};
+
+class ScriptFiber : public QObject, public Fiber
+{
+ Q_OBJECT
+public:
+ ScriptFiber(const QString &name, const QString &scriptCode,
+ ScriptAdapter *adapter);
+
+ QString name() const
+ { return mName; }
+
+ void requestTerminate();
+ bool isTerminating() const
+ { return mTerminate; }
+
+ void togglePause();
+ bool isPaused() const
+ { return mPaused; }
+
+ void beginBlocking();
+ void endBlocking();
+
+public slots:
+ void suspend();
+
+protected:
+ virtual void run();
+
+private:
+ QString mName;
+ QString mScriptCode;
+ ScriptAdapter *mAdapter;
+
+ bool mPaused;
+ bool mTerminate;
+
+ QScriptEngine mEngine;
+ QTimer mStopTimer;
+};
+
+#endif // SCRIPTADAPTER_H