From 9040b187a1c4fa380f8a12207b9dd6d04b3a10ac Mon Sep 17 00:00:00 2001
From: Stanislaw Halik <sthalik@misaki.pl>
Date: Fri, 12 Aug 2016 18:00:49 +0200
Subject: all: rename modules s#^opentrack-##. and opentrack -> api

Adjust usages.
---
 compat/CMakeLists.txt      |   8 +
 compat/camera-names.cpp    | 111 +++++++++++
 compat/camera-names.hpp    |  18 ++
 compat/export.hpp          |  27 +++
 compat/make-unique.hpp     |  43 ++++
 compat/nan.cpp             |  17 ++
 compat/nan.hpp             |  11 ++
 compat/options.cpp         | 239 ++++++++++++++++++++++
 compat/options.hpp         | 481 +++++++++++++++++++++++++++++++++++++++++++++
 compat/pi-constant.hpp     |   3 +
 compat/process-list.hpp    | 169 ++++++++++++++++
 compat/qcopyable-mutex.hpp |  37 ++++
 compat/shm.cpp             | 199 +++++++++++++++++++
 compat/shm.h               |  48 +++++
 compat/sleep.hpp           |  22 +++
 compat/slider.cpp          |  73 +++++++
 compat/slider.hpp          |  62 ++++++
 compat/timer.hpp           | 100 ++++++++++
 compat/util.hpp            |  93 +++++++++
 compat/win32-com.cpp       |  60 ++++++
 compat/win32-com.hpp       |  18 ++
 21 files changed, 1839 insertions(+)
 create mode 100644 compat/CMakeLists.txt
 create mode 100644 compat/camera-names.cpp
 create mode 100644 compat/camera-names.hpp
 create mode 100644 compat/export.hpp
 create mode 100644 compat/make-unique.hpp
 create mode 100644 compat/nan.cpp
 create mode 100644 compat/nan.hpp
 create mode 100644 compat/options.cpp
 create mode 100644 compat/options.hpp
 create mode 100644 compat/pi-constant.hpp
 create mode 100644 compat/process-list.hpp
 create mode 100644 compat/qcopyable-mutex.hpp
 create mode 100644 compat/shm.cpp
 create mode 100644 compat/shm.h
 create mode 100644 compat/sleep.hpp
 create mode 100644 compat/slider.cpp
 create mode 100644 compat/slider.hpp
 create mode 100644 compat/timer.hpp
 create mode 100644 compat/util.hpp
 create mode 100644 compat/win32-com.cpp
 create mode 100644 compat/win32-com.hpp

(limited to 'compat')

diff --git a/compat/CMakeLists.txt b/compat/CMakeLists.txt
new file mode 100644
index 00000000..2bbb496c
--- /dev/null
+++ b/compat/CMakeLists.txt
@@ -0,0 +1,8 @@
+opentrack_boilerplate(opentrack-compat NO-COMPAT BIN)
+if(NOT WIN32 AND NOT APPLE)
+    target_link_libraries(opentrack-compat rt)
+endif()
+if(CMAKE_COMPILER_IS_GNUCXX)
+    set_source_files_properties(nan.cpp PROPERTIES
+        COMPILE_FLAGS "-fno-fast-math -fno-finite-math-only -O0")
+endif()
diff --git a/compat/camera-names.cpp b/compat/camera-names.cpp
new file mode 100644
index 00000000..21ff3b52
--- /dev/null
+++ b/compat/camera-names.cpp
@@ -0,0 +1,111 @@
+#include "camera-names.hpp"
+
+#ifdef _WIN32
+#   define NO_DSHOW_STRSAFE
+#   include <dshow.h>
+#   include "win32-com.hpp"
+#   include <cwchar>
+#elif defined(__unix) || defined(__linux) || defined(__APPLE__)
+#   include <unistd.h>
+#endif
+
+#ifdef __linux
+#   include <fcntl.h>
+#   include <sys/ioctl.h>
+#   include <linux/videodev2.h>
+#   include <cerrno>
+#endif
+
+#include <QDebug>
+
+OPENTRACK_COMPAT_EXPORT int camera_name_to_index(const QString &name)
+{
+    auto list = get_camera_names();
+    int ret = list.indexOf(name);
+    if (ret < 0)
+        ret = 0;
+    return ret;
+}
+
+OPENTRACK_COMPAT_EXPORT QList<QString> get_camera_names()
+{
+    QList<QString> ret;
+#if defined(_WIN32)
+    // Create the System Device Enumerator.
+    HRESULT hr;
+    init_com_threading(com_apartment);
+    ICreateDevEnum *pSysDevEnum = NULL;
+    hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pSysDevEnum);
+    if (FAILED(hr))
+    {
+        qDebug() << "failed CLSID_SystemDeviceEnum" << hr;
+        return ret;
+    }
+    // Obtain a class enumerator for the video compressor category.
+    IEnumMoniker *pEnumCat = NULL;
+    hr = pSysDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnumCat, 0);
+
+    if (hr == S_OK) {
+        // Enumerate the monikers.
+        IMoniker *pMoniker = NULL;
+        ULONG cFetched;
+        while (pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK) {
+            IPropertyBag *pPropBag;
+            hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag);
+            if (SUCCEEDED(hr))	{
+                // To retrieve the filter's friendly name, do the following:
+                VARIANT varName;
+                VariantInit(&varName);
+                hr = pPropBag->Read(L"FriendlyName", &varName, 0);
+                if (SUCCEEDED(hr))
+                {
+                    // Display the name in your UI somehow.
+                    QString str((QChar*)varName.bstrVal, int(std::wcslen(varName.bstrVal)));
+                    ret.append(str);
+                }
+                VariantClear(&varName);
+
+                ////// To create an instance of the filter, do the following:
+                ////IBaseFilter *pFilter;
+                ////hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter,
+                ////	(void**)&pFilter);
+                // Now add the filter to the graph.
+                //Remember to release pFilter later.
+                pPropBag->Release();
+            }
+            pMoniker->Release();
+        }
+        pEnumCat->Release();
+    }
+    else
+        qDebug() << "failed CLSID_VideoInputDeviceCategory" << hr;
+
+    pSysDevEnum->Release();
+#endif
+#ifdef __linux
+    for (int i = 0; i < 16; i++) {
+        char buf[128];
+        sprintf(buf, "/dev/video%d", i);
+        if (access(buf, F_OK) == 0)
+            ret.append(buf);
+        else
+            continue;
+
+        if (access(buf, R_OK | W_OK) == 0) {
+            int fd = open(buf, O_RDONLY);
+            if (fd == -1)
+                continue;
+            struct v4l2_capability video_cap;
+            if(ioctl(fd, VIDIOC_QUERYCAP, &video_cap) == -1)
+            {
+                qDebug() << "VIDIOC_QUERYCAP" << errno;
+                close(fd);
+                continue;
+            }
+            ret[ret.size()-1] = reinterpret_cast<const char*>(video_cap.card);
+            close(fd);
+        }
+    }
+#endif
+    return ret;
+}
diff --git a/compat/camera-names.hpp b/compat/camera-names.hpp
new file mode 100644
index 00000000..ae0c6b25
--- /dev/null
+++ b/compat/camera-names.hpp
@@ -0,0 +1,18 @@
+/* Copyright (c) 2014-2015, Stanislaw Halik <sthalik@misaki.pl>
+
+ * Permission to use, copy, modify, and/or distribute this
+ * software for any purpose with or without fee is hereby granted,
+ * provided that the above copyright notice and this permission
+ * notice appear in all copies.
+ */
+
+#pragma once
+
+#include <QList>
+#include <QString>
+
+#include "export.hpp"
+
+OPENTRACK_COMPAT_EXPORT QList<QString> get_camera_names();
+OPENTRACK_COMPAT_EXPORT int camera_name_to_index(const QString &name);
+
diff --git a/compat/export.hpp b/compat/export.hpp
new file mode 100644
index 00000000..ba10f131
--- /dev/null
+++ b/compat/export.hpp
@@ -0,0 +1,27 @@
+#pragma once
+
+#ifdef BUILD_compat
+#   ifdef _WIN32
+#       define OPENTRACK_COMPAT_LINKAGE __declspec(dllexport)
+#   else
+#       define OPENTRACK_COMPAT_LINKAGE
+#   endif
+
+#   ifndef _MSC_VER
+#       define OPENTRACK_COMPAT_EXPORT __attribute__ ((visibility ("default"))) OPENTRACK_COMPAT_LINKAGE
+#   else
+#       define OPENTRACK_COMPAT_EXPORT OPENTRACK_COMPAT_LINKAGE
+#   endif
+#else
+    #ifdef _WIN32
+    #    define OPENTRACK_COMPAT_LINKAGE __declspec(dllimport)
+    #else
+    #    define OPENTRACK_COMPAT_LINKAGE
+    #endif
+
+    #ifndef _MSC_VER
+    #    define OPENTRACK_COMPAT_EXPORT __attribute__ ((visibility ("default"))) OPENTRACK_COMPAT_LINKAGE
+    #else
+    #    define OPENTRACK_COMPAT_EXPORT OPENTRACK_COMPAT_LINKAGE
+    #endif
+#endif
diff --git a/compat/make-unique.hpp b/compat/make-unique.hpp
new file mode 100644
index 00000000..bb5315c5
--- /dev/null
+++ b/compat/make-unique.hpp
@@ -0,0 +1,43 @@
+#pragma once
+
+// GNU 5.4.0 doesn't have std::make_unique in -std=c++14 mode
+
+// this implementation was taken from http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3656.htm
+
+#include <memory>
+#include <utility>
+#include <cstddef>
+
+namespace detail {
+template<class T> struct Unique_if
+{
+    typedef std::unique_ptr<T> Single_object;
+};
+
+template<class T> struct Unique_if<T[]>
+{
+    typedef std::unique_ptr<T[]> Unknown_bound;
+};
+
+template<class T, size_t N> struct Unique_if<T[N]>
+{
+    typedef void Known_bound;
+};
+}
+
+template<class T, class... Args>
+    typename detail::Unique_if<T>::Single_object
+    make_unique(Args&&... args) {
+        return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
+    }
+
+template<class T>
+    typename detail::Unique_if<T>::Unknown_bound
+    make_unique(std::size_t n) {
+        typedef typename std::remove_extent<T>::type U;
+        return std::unique_ptr<T>(new U[n]());
+    }
+
+template<class T, class... Args>
+    typename detail::Unique_if<T>::Known_bound
+    make_unique(Args&&...) = delete;
diff --git a/compat/nan.cpp b/compat/nan.cpp
new file mode 100644
index 00000000..899b907a
--- /dev/null
+++ b/compat/nan.cpp
@@ -0,0 +1,17 @@
+#include <cmath>
+#include "export.hpp"
+
+#if defined(__GNUC__)
+extern "C" OPENTRACK_COMPAT_EXPORT bool __attribute__ ((noinline)) nanp(double value)
+#elif defined(_WIN32)
+extern "C" OPENTRACK_COMPAT_EXPORT __declspec(noinline) bool nanp(double value)
+#else
+extern "C" OPENTRACK_COMPAT_EXPORT bool nanp(double value)
+#endif
+{
+    using std::isnan;
+    using std::isinf;
+
+    const volatile double x = value;
+    return isnan(x) || isinf(x);
+}
diff --git a/compat/nan.hpp b/compat/nan.hpp
new file mode 100644
index 00000000..9926da13
--- /dev/null
+++ b/compat/nan.hpp
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "export.hpp"
+
+#if defined(__GNUC__)
+extern "C" OPENTRACK_COMPAT_EXPORT bool __attribute__ ((noinline)) nanp(double value);
+#elif defined(_WIN32)
+extern "C" OPENTRACK_COMPAT_EXPORT __declspec(noinline) bool nanp(double value);
+#else
+extern "C" OPENTRACK_COMPAT_EXPORT bool nanp(double value);
+#endif
diff --git a/compat/options.cpp b/compat/options.cpp
new file mode 100644
index 00000000..8c6e6c65
--- /dev/null
+++ b/compat/options.cpp
@@ -0,0 +1,239 @@
+#include "options.hpp"
+
+namespace options
+{
+
+group::group(const QString& name) : name(name)
+{
+    auto conf = ini_file();
+    conf->beginGroup(name);
+    for (auto& k_ : conf->childKeys())
+    {
+        auto tmp = k_.toUtf8();
+        QString k(tmp);
+        kvs[k] = conf->value(k_);
+    }
+    conf->endGroup();
+}
+
+void group::save() const
+{
+    auto s = ini_file();
+    s->beginGroup(name);
+    for (auto& i : kvs)
+        s->setValue(i.first, i.second);
+    s->endGroup();
+}
+
+void group::put(const QString &s, const QVariant &d)
+{
+    kvs[s] = d;
+}
+
+bool group::contains(const QString &s) const
+{
+    return kvs.count(s) != 0;
+}
+
+QString group::ini_directory()
+{
+    const auto dirs = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation);
+    if (dirs.size() == 0)
+        return "";
+    if (QDir(dirs[0]).mkpath(OPENTRACK_ORG))
+        return dirs[0] + "/" OPENTRACK_ORG;
+    return "";
+}
+
+QString group::ini_filename()
+{
+    QSettings settings(OPENTRACK_ORG);
+    return settings.value(OPENTRACK_CONFIG_FILENAME_KEY, OPENTRACK_DEFAULT_CONFIG).toString();
+}
+
+QString group::ini_pathname()
+{
+    const auto dir = ini_directory();
+    if (dir == "")
+        return "";
+    return dir + "/" + ini_filename();
+}
+
+const QStringList group::ini_list()
+{
+    const auto dirname = ini_directory();
+    if (dirname == "")
+        return QStringList();
+    QDir settings_dir(dirname);
+    return settings_dir.entryList( QStringList { "*.ini" } , QDir::Files, QDir::Name );
+}
+
+const mem<QSettings> group::ini_file()
+{
+    const auto pathname = ini_pathname();
+    if (pathname != "")
+        return std::make_shared<QSettings>(ini_pathname(), QSettings::IniFormat);
+    return std::make_shared<QSettings>();
+}
+
+bool group::operator==(const group& other) const
+{
+    for (const auto& kv : kvs)
+    {
+        const QVariant val = other.get<QVariant>(kv.first);
+        if (!other.contains(kv.first) || kv.second != val)
+        {
+            qDebug() << "bundle" << name << "modified" << "key" << kv.first << "-" << val << "<>" << kv.second;
+            return false;
+        }
+    }
+
+    for (const auto& kv : other.kvs)
+    {
+        const QVariant val = get<QVariant>(kv.first);
+        if (!contains(kv.first) || kv.second != val)
+        {
+            qDebug() << "bundle" << name << "modified" << "key" << kv.first << "-" << kv.second << "<>" << val;
+            return false;
+        }
+    }
+    return true;
+}
+
+impl_bundle::impl_bundle(const QString& group_name)
+    :
+      mtx(QMutex::Recursive),
+      group_name(group_name),
+      saved(group_name),
+      transient(saved)
+{
+}
+
+void impl_bundle::reload()
+{
+    {
+        QMutexLocker l(&mtx);
+        saved = group(group_name);
+        transient = saved;
+    }
+    emit reloading();
+}
+
+void impl_bundle::store_kv(const QString& name, const QVariant& datum)
+{
+    QMutexLocker l(&mtx);
+
+    transient.put(name, datum);
+}
+
+bool impl_bundle::contains(const QString &name) const
+{
+    QMutexLocker l(const_cast<QMutex*>(&mtx));
+    return transient.contains(name);
+}
+
+void impl_bundle::save()
+{
+    bool modified_ = false;
+
+    {
+        QMutexLocker l(&mtx);
+        if (saved != transient)
+        {
+            qDebug() << "bundle" << group_name << "changed, saving";
+            modified_ = true;
+            saved = transient;
+            saved.save();
+        }
+    }
+
+    if (modified_)
+        emit saving();
+}
+
+bool impl_bundle::modifiedp() const // XXX unused
+{
+    QMutexLocker l(const_cast<QMutex*>(&mtx));
+    return transient != saved;
+}
+
+base_value::base_value(pbundle b, const QString &name) :
+    b(b),
+    self_name(name)
+{
+}
+
+opts::~opts()
+{
+    b->reload();
+}
+
+opts::opts(const QString &name) : b(bundle(name))
+{
+}
+
+custom_type_initializer::custom_type_initializer()
+{
+    qRegisterMetaTypeStreamOperators<slider_value>("slider_value");
+    QMetaType::registerDebugStreamOperator<slider_value>();
+}
+
+custom_type_initializer custom_type_initializer::singleton;
+
+namespace detail {
+
+opt_bundle::opt_bundle(const QString& group_name)
+    : impl_bundle(group_name)
+{
+}
+
+opt_bundle::~opt_bundle()
+{
+    detail::singleton().bundle_decf(group_name);
+}
+
+void opt_singleton::bundle_decf(const opt_singleton::k& key)
+{
+    QMutexLocker l(&implsgl_mtx);
+
+    if (--std::get<0>(implsgl_data[key]) == 0)
+    {
+        qDebug() << "bundle -" << key;
+
+        implsgl_data.erase(key);
+    }
+}
+
+opt_singleton::opt_singleton() : implsgl_mtx(QMutex::Recursive)
+{
+}
+
+pbundle opt_singleton::bundle(const opt_singleton::k &key)
+{
+    QMutexLocker l(&implsgl_mtx);
+
+    if (implsgl_data.count(key) != 0)
+    {
+        auto shared = std::get<1>(implsgl_data[key]).lock();
+        if (shared != nullptr)
+            return shared;
+    }
+
+    qDebug() << "bundle +" << key;
+
+    auto shr = std::make_shared<v>(key);
+    implsgl_data[key] = tt(1, shr);
+    return shr;
+}
+
+OPENTRACK_COMPAT_EXPORT opt_singleton& singleton()
+{
+    static opt_singleton ret;
+    return ret;
+}
+
+
+} // end options::detail
+
+} // end options
+
diff --git a/compat/options.hpp b/compat/options.hpp
new file mode 100644
index 00000000..40aca2de
--- /dev/null
+++ b/compat/options.hpp
@@ -0,0 +1,481 @@
+/* Copyright (c) 2013-2016 Stanislaw Halik
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ */
+
+#pragma once
+
+#include <memory>
+#include <tuple>
+#include <map>
+#include <cinttypes>
+#include <vector>
+#include <memory>
+
+#include <QObject>
+#include <QSettings>
+#include <QString>
+#include <QVariant>
+#include <QMutex>
+#include <QMutexLocker>
+#include <QWidget>
+#include <QComboBox>
+#include <QCheckBox>
+#include <QDoubleSpinBox>
+#include <QSpinBox>
+#include <QSlider>
+#include <QLineEdit>
+#include <QLabel>
+#include <QTabWidget>
+#include <QCoreApplication>
+#include <QFileInfo>
+#include <QDir>
+#include <QStandardPaths>
+#include <QApplication>
+
+#include <QMetaType>
+#include <QDataStream>
+
+#include <QDebug>
+
+#include "export.hpp"
+#include "slider.hpp"
+
+#include "util.hpp"
+
+#define OPENTRACK_CONFIG_FILENAME_KEY "settings-filename"
+#define OPENTRACK_DEFAULT_CONFIG "default.ini"
+#define OPENTRACK_ORG "opentrack-2.3"
+
+namespace options {
+    namespace {
+        class custom_type_initializer
+        {
+            custom_type_initializer();
+            static custom_type_initializer singleton;
+        };
+    }
+
+    template<typename k, typename v> using map = std::map<k, v>;
+
+    // snapshot of qsettings group at given time
+    class OPENTRACK_COMPAT_EXPORT group
+    {
+    private:
+        map<QString, QVariant> kvs;
+        QString name;
+    public:
+        group(const QString& name);
+        void save() const;
+        void put(const QString& s, const QVariant& d);
+        bool contains(const QString& s) const;
+        static QString ini_directory();
+        static QString ini_filename();
+        static QString ini_pathname();
+        static const QStringList ini_list();
+        static const mem<QSettings> ini_file();
+        bool operator==(const group& other) const;
+        bool operator!=(const group& other) const { return !(*this == other); }
+
+        template<typename t>
+        t get(const QString& k) const
+        {
+            auto value = kvs.find(k);
+            if (value != kvs.cend())
+                return value->second.value<t>();
+            return t();
+        }
+    };
+
+    class OPENTRACK_COMPAT_EXPORT impl_bundle : public QObject
+    {
+        Q_OBJECT
+    protected:
+        QMutex mtx;
+        const QString group_name;
+        group saved;
+        group transient;
+        impl_bundle(const impl_bundle&) = delete;
+        impl_bundle& operator=(const impl_bundle&) = delete;
+    signals:
+        void reloading();
+        void saving() const;
+    public:
+        impl_bundle(const QString& group_name);
+        QString name() { return group_name; }
+        void reload();
+        void store_kv(const QString& name, const QVariant& datum);
+        bool contains(const QString& name) const;
+        void save();
+        bool modifiedp() const;
+
+        template<typename t>
+        t get(const QString& name) const
+        {
+            QMutexLocker l(const_cast<QMutex*>(&mtx));
+            return transient.get<t>(name);
+        }
+    };
+
+    namespace detail
+    {
+        class OPENTRACK_COMPAT_EXPORT opt_bundle final : public impl_bundle
+        {
+        public:
+            opt_bundle(const QString& group_name);
+            ~opt_bundle();
+        };
+
+        struct OPENTRACK_COMPAT_EXPORT opt_singleton
+        {
+        public:
+            using k = QString;
+            using v = opt_bundle;
+            using cnt = int;
+            using pbundle = std::shared_ptr<v>;
+            using tt = std::tuple<cnt, std::weak_ptr<v>>;
+        private:
+            QMutex implsgl_mtx;
+            map<k, tt> implsgl_data;
+        public:
+            opt_singleton();
+            pbundle bundle(const k& key);
+            void bundle_decf(const k& key);
+        };
+
+        OPENTRACK_COMPAT_EXPORT opt_singleton& singleton();
+    }
+
+    using pbundle = std::shared_ptr<detail::opt_bundle>;
+
+    inline pbundle bundle(const QString& name)
+    {
+         return detail::singleton().bundle(name);
+    }
+
+#define OPENTRACK_DEFINE_SLOT(t) void setValue(t datum) { store(datum); }
+#define OPENTRACK_DEFINE_SIGNAL(t) void valueChanged(t)
+
+    class OPENTRACK_COMPAT_EXPORT base_value : public QObject
+    {
+        Q_OBJECT
+    public:
+        QString name() const { return self_name; }
+        base_value(pbundle b, const QString& name);
+    signals:
+        OPENTRACK_DEFINE_SIGNAL(double);
+        OPENTRACK_DEFINE_SIGNAL(float);
+        OPENTRACK_DEFINE_SIGNAL(int);
+        OPENTRACK_DEFINE_SIGNAL(bool);
+        OPENTRACK_DEFINE_SIGNAL(const QString&);
+        OPENTRACK_DEFINE_SIGNAL(const slider_value&);
+    protected:
+        pbundle b;
+        QString self_name;
+
+        template<typename t>
+        void store(const t& datum)
+        {
+            b->store_kv(self_name, QVariant::fromValue(datum));
+            emit valueChanged(static_cast<t>(datum));
+        }
+        void store(float datum)
+        {
+            store(double(datum));
+        }
+
+    public slots:
+        OPENTRACK_DEFINE_SLOT(double)
+        OPENTRACK_DEFINE_SLOT(int)
+        OPENTRACK_DEFINE_SLOT(bool)
+        OPENTRACK_DEFINE_SLOT(const QString&)
+        OPENTRACK_DEFINE_SLOT(const slider_value&)
+    public slots:
+        virtual void reload() = 0;
+    };
+
+    namespace detail {
+        template<typename t>
+        struct value_get_traits
+        {
+            static inline t get(const t& val, const t&)
+            {
+                return val;
+            }
+        };
+
+        template<>
+        struct value_get_traits<slider_value>
+        {
+            using t = slider_value;
+            static inline t get(const t& val, const t& def)
+            {
+                return t(val.cur(), def.min(), def.max());
+            }
+        };
+    }
+
+    template<typename t_>
+    class value : public base_value
+    {
+        template<typename t__, typename Enable = void>
+        struct get_t
+        { using t = t__; };
+
+        // Qt uses int a lot in slots so use it for all enums
+        template<typename t__>
+        struct get_t<t__, typename std::enable_if<std::is_enum<t__>::value>::type>
+        //{ using t = typename std::underlying_type<t__>::type; };
+        { using t = int; };
+
+        using t = t_;
+    public:
+        using underlying_t = typename get_t<t_>::t;
+
+        t operator=(const t& datum)
+        {
+            store(static_cast<underlying_t>(datum));
+            return datum;
+        }
+
+        static constexpr const Qt::ConnectionType DIRECT_CONNTYPE = Qt::AutoConnection;
+        static constexpr const Qt::ConnectionType SAFE_CONNTYPE = Qt::QueuedConnection;
+
+        value(pbundle b, const QString& name, t def) : base_value(b, name), def(def)
+        {
+            QObject::connect(b.get(), SIGNAL(reloading()),
+                             this, SLOT(reload()),
+                             DIRECT_CONNTYPE);
+            if (!b->contains(name) || b->get<QVariant>(name).type() == QVariant::Invalid)
+                *this = def;
+        }
+
+        value(pbundle b, const char* name, t def) : value(b, QString(name), def)
+        {
+        }
+
+        t get() const
+        {
+            t val = b->contains(self_name)
+                    ? static_cast<t>(b->get<underlying_t>(self_name))
+                    : def;
+            return detail::value_get_traits<t>::get(val, def);
+        }
+
+        operator t() const { return get(); }
+
+        void reload() override
+        {
+            *this = static_cast<t>(*this);
+        }
+
+    private:
+        t def;
+    };
+
+    struct OPENTRACK_COMPAT_EXPORT opts
+    {
+        pbundle b;
+        opts(const QString& name);
+        opts& operator=(const opts&) = delete;
+        opts(const opts&) = delete;
+        ~opts();
+    };
+
+    template<typename t, typename q>
+    inline void tie_setting(value<t>&, q*);
+
+    template<typename t>
+    inline
+    typename std::enable_if<std::is_enum<t>::value>::type
+    tie_setting(value<t>& v, QComboBox* cb)
+    {
+        cb->setCurrentIndex(cb->findData((unsigned)static_cast<t>(v)));
+        v = static_cast<t>(cb->currentData().toInt());
+
+        // QObject::connect plays badly with std::bind of std::shared_ptr. Data seems to get freed.
+        // Direct accesses of cb->currentData within arbitrary thread context cause crashes as well.
+        // Hence we go for a verbose implementation.
+
+        std::vector<int> enum_cases;
+        enum_cases.reserve(unsigned(cb->count()));
+
+        for (int i = 0; i < cb->count(); i++)
+            enum_cases.push_back(cb->itemData(i).toInt());
+
+        struct fn1
+        {
+            value<t>& v;
+            QComboBox* cb;
+            std::vector<int> enum_cases;
+
+            fn1(value<t>& v, QComboBox* cb, const std::vector<int>& enum_cases) :
+                v(v),
+                cb(cb),
+                enum_cases(enum_cases)
+            {
+            }
+
+            void operator()(int idx)
+            {
+                if (idx < 0 || idx >= (int)enum_cases.size())
+                    v = static_cast<t>(-1);
+                else
+                    v = static_cast<t>(t(std::intptr_t(enum_cases[idx])));
+            }
+        };
+
+        struct fn2
+        {
+            value<t>& v;
+            QComboBox* cb;
+            std::vector<int> enum_cases;
+
+            fn2(value<t>& v, QComboBox* cb, const std::vector<int>& enum_cases) :
+                v(v),
+                cb(cb),
+                enum_cases(enum_cases)
+            {
+            }
+
+            void operator()(int val)
+            {
+                for (unsigned i = 0; i < enum_cases.size(); i++)
+                {
+                    if (val == enum_cases[i])
+                    {
+                        cb->setCurrentIndex(i);
+                        return;
+                    }
+                }
+                cb->setCurrentIndex(-1);
+            }
+        };
+
+        base_value::connect(cb,
+                            static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
+                            &v,
+                            fn1(v, cb, enum_cases),
+                            v.DIRECT_CONNTYPE);
+        base_value::connect(&v,
+                            static_cast<void (base_value::*)(int)>(&base_value::valueChanged),
+                            cb,
+                            fn2(v, cb, enum_cases),
+                            v.DIRECT_CONNTYPE);
+    }
+
+    template<>
+    inline void tie_setting(value<int>& v, QComboBox* cb)
+    {
+        cb->setCurrentIndex(v);
+        v = cb->currentIndex();
+        base_value::connect(cb, SIGNAL(currentIndexChanged(int)), &v, SLOT(setValue(int)), v.DIRECT_CONNTYPE);
+        base_value::connect(&v, SIGNAL(valueChanged(int)), cb, SLOT(setCurrentIndex(int)), v.SAFE_CONNTYPE);
+    }
+
+    template<>
+    inline void tie_setting(value<QString>& v, QComboBox* cb)
+    {
+        cb->setCurrentText(v);
+        v = cb->currentText();
+        base_value::connect(cb, SIGNAL(currentTextChanged(QString)), &v, SLOT(setValue(QString)), v.DIRECT_CONNTYPE);
+        base_value::connect(&v, SIGNAL(valueChanged(QString)), cb, SLOT(setCurrentText(QString)), v.SAFE_CONNTYPE);
+    }
+
+    template<>
+    inline void tie_setting(value<bool>& v, QCheckBox* cb)
+    {
+        cb->setChecked(v);
+        base_value::connect(cb, SIGNAL(toggled(bool)), &v, SLOT(setValue(bool)), v.DIRECT_CONNTYPE);
+        base_value::connect(&v, SIGNAL(valueChanged(bool)), cb, SLOT(setChecked(bool)), v.SAFE_CONNTYPE);
+    }
+
+    template<>
+    inline void tie_setting(value<double>& v, QDoubleSpinBox* dsb)
+    {
+        dsb->setValue(v);
+        base_value::connect(dsb, SIGNAL(valueChanged(double)), &v, SLOT(setValue(double)), v.DIRECT_CONNTYPE);
+        base_value::connect(&v, SIGNAL(valueChanged(double)), dsb, SLOT(setValue(double)), v.SAFE_CONNTYPE);
+    }
+
+    template<>
+    inline void tie_setting(value<int>& v, QSpinBox* sb)
+    {
+        sb->setValue(v);
+        base_value::connect(sb, SIGNAL(valueChanged(int)), &v, SLOT(setValue(int)), v.DIRECT_CONNTYPE);
+        base_value::connect(&v, SIGNAL(valueChanged(int)), sb, SLOT(setValue(int)), v.SAFE_CONNTYPE);
+    }
+
+    template<>
+    inline void tie_setting(value<int>& v, QSlider* sl)
+    {
+        sl->setValue(v);
+        v = sl->value();
+        base_value::connect(sl, SIGNAL(valueChanged(int)), &v, SLOT(setValue(int)), v.DIRECT_CONNTYPE);
+        base_value::connect(&v, SIGNAL(valueChanged(int)), sl, SLOT(setValue(int)), v.SAFE_CONNTYPE);
+    }
+
+    template<>
+    inline void tie_setting(value<QString>& v, QLineEdit* le)
+    {
+        le->setText(v);
+        base_value::connect(le, SIGNAL(textChanged(QString)), &v, SLOT(setValue(QString)), v.DIRECT_CONNTYPE);
+        base_value::connect(&v, SIGNAL(valueChanged(QString)),le, SLOT(setText(QString)), v.SAFE_CONNTYPE);
+    }
+
+    template<>
+    inline void tie_setting(value<QString>& v, QLabel* lb)
+    {
+        lb->setText(v);
+        base_value::connect(&v, SIGNAL(valueChanged(QString)), lb, SLOT(setText(QString)), v.DIRECT_CONNTYPE);
+    }
+
+    template<>
+    inline void tie_setting(value<int>& v, QTabWidget* t)
+    {
+        t->setCurrentIndex(v);
+        base_value::connect(t, SIGNAL(currentChanged(int)), &v, SLOT(setValue(int)), v.DIRECT_CONNTYPE);
+        base_value::connect(&v, SIGNAL(valueChanged(int)), t, SLOT(setCurrentIndex(int)), v.SAFE_CONNTYPE);
+    }
+
+    template<>
+    inline void tie_setting(value<slider_value>& v, QSlider* w)
+    {
+        // we can't get these at runtime since signals cross threads
+        const int q_min = w->minimum();
+        const int q_max = w->maximum();
+        const int q_diff = q_max - q_min;
+
+        slider_value sv(v);
+
+        const double sv_max = sv.max();
+        const double sv_min = sv.min();
+        const double sv_c = sv_max - sv_min;
+
+        w->setValue(int((sv.cur() - sv_min) / sv_c * q_diff + q_min));
+        v = slider_value(q_diff <= 0 ? 0 : (w->value() - q_min) * sv_c / (double)q_diff + sv_min, sv_min, sv_max);
+
+        base_value::connect(w,
+                            &QSlider::valueChanged,
+                            &v,
+                            [=, &v](int pos) -> void
+        {
+            if (q_diff <= 0 || pos <= 0)
+                v = slider_value(sv_min, sv_min, sv_max);
+            else
+                v = slider_value((pos - q_min) * sv_c / (double)q_diff + sv_min, sv_min, sv_max);
+        },
+        v.DIRECT_CONNTYPE);
+        base_value::connect(&v,
+                            static_cast<void(base_value::*)(double)>(&base_value::valueChanged),
+                            w,
+                            [=](double value) -> void
+        {
+            w->setValue(int(value * q_diff) + q_min);
+        },
+        v.SAFE_CONNTYPE);
+    }
+}
+
diff --git a/compat/pi-constant.hpp b/compat/pi-constant.hpp
new file mode 100644
index 00000000..52b98a7f
--- /dev/null
+++ b/compat/pi-constant.hpp
@@ -0,0 +1,3 @@
+#pragma once
+
+#define OPENTRACK_PI 3.14159265358979323846
diff --git a/compat/process-list.hpp b/compat/process-list.hpp
new file mode 100644
index 00000000..10613791
--- /dev/null
+++ b/compat/process-list.hpp
@@ -0,0 +1,169 @@
+/* Copyright (c) 2015 Stanislaw Halik <sthalik@misaki.pl>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ */
+
+#pragma once
+
+#include <QDebug>
+#include <QStringList>
+
+#if defined _WIN32
+
+#include <windows.h>
+#include <tlhelp32.h>
+
+template<typename = void>
+static QStringList get_all_executable_names()
+{
+    QStringList ret;
+    HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+    if (h == INVALID_HANDLE_VALUE)
+        return ret;
+
+    PROCESSENTRY32 e;
+    e.dwSize = sizeof(e);
+
+    if (Process32First(h, &e) != TRUE)
+    {
+        CloseHandle(h);
+        return ret;
+    }
+
+    do {
+        ret.append(e.szExeFile);
+    } while (Process32Next(h, &e) == TRUE);
+
+    CloseHandle(h);
+
+    return ret;
+}
+#elif defined __APPLE__
+#include <libproc.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <cerrno>
+#include <cstring>
+#include <vector>
+
+template<typename = void>
+static QStringList get_all_executable_names()
+{
+    QStringList ret;
+    std::vector<int> vec;
+
+    while (true)
+    {
+        int numproc = proc_listpids(PROC_ALL_PIDS, 0, nullptr, 0);
+        if (numproc == -1)
+        {
+            qDebug() << "proc_listpids numproc failed" << errno;
+            return ret;
+        }
+        vec.resize(numproc);
+        int cnt = proc_listpids(PROC_ALL_PIDS, 0, &vec[0], sizeof(int) * numproc);
+
+        if (cnt <= numproc)
+        {
+            std::vector<char> arglist;
+            int mib[2] { CTL_KERN, KERN_ARGMAX };
+            size_t sz = sizeof(int);
+            int maxarg = 0;
+            if (sysctl(mib, 2, &maxarg, &sz, NULL, 0) == -1)
+            {
+                qDebug() << "sysctl KERN_ARGMAX" << errno;
+                return ret;
+            }
+            arglist.resize(maxarg);
+            for (int i = 0; i < numproc; i++)
+            {
+                size_t maxarg_ = (size_t)maxarg;
+                int mib[3] { CTL_KERN, KERN_PROCARGS2, vec[i] };
+                if (sysctl(mib, 3, &arglist[0], &maxarg_, NULL, 0) == -1)
+                {
+                    //qDebug() << "sysctl KERN_PROCARGS2" << vec[i] << errno;
+                    continue;
+                }
+                QStringList cmdline;
+                for (unsigned j = sizeof(int) + strlen(&arglist[sizeof(int)]); j < maxarg_; j++)
+                {
+                    QString arg(&arglist[j]);
+                    if (arg.size() != 0)
+                    {
+                        cmdline << arg;
+                        j += arg.size();
+                    }
+                }
+                if (cmdline.size() > 0)
+                {
+                    int idx = cmdline[0].lastIndexOf('/');
+                    if (idx != -1)
+                    {
+                        QString tmp = cmdline[0].mid(idx+1);
+                        if (cmdline.size() > 1 && (tmp == "wine.bin" || tmp == "wine"))
+                        {
+                            idx = cmdline[1].lastIndexOf('/');
+                            if (idx == -1)
+                                idx = cmdline[1].lastIndexOf('\\');
+                            if (idx != -1)
+                            {
+                                ret.append(cmdline[1].mid(idx+1));
+                            }
+                            else
+                                ret.append(cmdline[1]);
+                        }
+                        else
+                        {
+                            ret.append(tmp);
+                        }
+                    }
+                    else
+                        ret.append(cmdline[0]);
+                }
+            }
+            return ret;
+        }
+    }
+}
+
+#elif defined __linux
+
+#include <proc/readproc.h>
+#include <cerrno>
+template<typename = void>
+static QStringList get_all_executable_names()
+{
+    QStringList ret;
+    proc_t** procs = readproctab(PROC_FILLCOM);
+    if (procs == nullptr)
+    {
+        qDebug() << "readproctab" << errno;
+        return ret;
+    }
+    for (int i = 0; procs[i]; i++)
+    {
+        // note, wine sets argv[0] so no parsing like in OSX case
+        auto proc = procs[i];
+        if (proc->cmdline && proc->cmdline[0])
+        {
+            QString tmp(proc->cmdline[0]);
+            const int idx = std::max(tmp.lastIndexOf('\\'), tmp.lastIndexOf('/'));
+            tmp = tmp.mid(idx == -1 ? 0 : idx+1);
+            ret.append(tmp);
+        }
+        freeproc(procs[i]);
+    }
+    free(procs);
+    return ret;
+}
+
+#else
+template<typename = void>
+static QStringList get_all_executable_names()
+{
+    return QStringList();
+}
+#endif
diff --git a/compat/qcopyable-mutex.hpp b/compat/qcopyable-mutex.hpp
new file mode 100644
index 00000000..57b0030d
--- /dev/null
+++ b/compat/qcopyable-mutex.hpp
@@ -0,0 +1,37 @@
+#pragma once
+
+#include <QMutex>
+
+class MyMutex {
+private:
+    QMutex inner;
+
+public:
+    QMutex* operator->() { return &inner; }
+    QMutex* operator->() const { return &const_cast<MyMutex*>(this)->inner; }
+
+    MyMutex operator=(const MyMutex& datum)
+    {
+        auto mode =
+                datum->isRecursive()
+                ? QMutex::Recursive
+                : QMutex::NonRecursive;
+
+        return MyMutex(mode);
+    }
+
+    MyMutex(const MyMutex& datum)
+    {
+        *this = datum;
+    }
+
+    MyMutex(QMutex::RecursionMode mode = QMutex::NonRecursive) :
+        inner(mode)
+    {
+    }
+
+    QMutex* operator&() const
+    {
+        return const_cast<QMutex*>(&inner);
+    }
+};
diff --git a/compat/shm.cpp b/compat/shm.cpp
new file mode 100644
index 00000000..83a3260c
--- /dev/null
+++ b/compat/shm.cpp
@@ -0,0 +1,199 @@
+/* Copyright (c) 2013 Stanislaw Halik <sthalik@misaki.pl>
+
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ */
+
+#include "shm.h"
+
+#if defined(_WIN32)
+
+#include <cstring>
+#include <stdio.h>
+
+#include <accctrl.h>
+#include <aclapi.h>
+
+struct secattr
+{
+    bool success;
+    SECURITY_DESCRIPTOR* pSD;
+    SECURITY_ATTRIBUTES attrs;
+    PSID pEveryoneSID;
+    PACL pACL;
+
+    void cleanup()
+    {
+        if (pEveryoneSID)
+            FreeSid(pEveryoneSID);
+        if (pACL)
+            LocalFree(pACL);
+        if (pSD)
+            LocalFree(pSD);
+        success = false;
+        pSD = nullptr;
+        pEveryoneSID = nullptr;
+        pACL = nullptr;
+    }
+
+    secattr(DWORD perms) : success(true), pSD(nullptr), pEveryoneSID(nullptr), pACL(nullptr)
+    {
+        SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
+        EXPLICIT_ACCESS ea;
+
+        if(!AllocateAndInitializeSid(&SIDAuthWorld, 1,
+                         SECURITY_WORLD_RID,
+                         0, 0, 0, 0, 0, 0, 0,
+                         &pEveryoneSID))
+        {
+            fprintf(stderr, "AllocateAndInitializeSid: %d\n", (int) GetLastError());
+            goto cleanup;
+        }
+
+        memset(&ea, 0, sizeof(ea));
+
+        ea.grfAccessPermissions = perms;
+        ea.grfAccessMode = SET_ACCESS;
+        ea.grfInheritance = NO_INHERITANCE;
+        ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
+        ea.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
+        ea.Trustee.ptstrName  = (LPTSTR) pEveryoneSID;
+
+        if (SetEntriesInAcl(1, &ea, NULL, &pACL) != ERROR_SUCCESS)
+        {
+            fprintf(stderr, "SetEntriesInAcl: %d\n", (int) GetLastError());
+            goto cleanup;
+        }
+
+        pSD = (SECURITY_DESCRIPTOR*) LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
+        if (pSD == nullptr)
+        {
+            fprintf(stderr, "LocalAlloc: %d\n", (int) GetLastError());
+            goto cleanup;
+        }
+
+        if (!InitializeSecurityDescriptor(pSD,
+                    SECURITY_DESCRIPTOR_REVISION))
+        {
+            fprintf(stderr, "InitializeSecurityDescriptor: %d\n", (int) GetLastError());
+            goto cleanup;
+        }
+
+        if (!SetSecurityDescriptorDacl(pSD,
+                                       TRUE,
+                                       pACL,
+                                       FALSE))
+        {
+            fprintf(stderr, "SetSecurityDescriptorDacl: %d\n", (int) GetLastError());
+            goto cleanup;
+        }
+
+        attrs.bInheritHandle = false;
+        attrs.lpSecurityDescriptor = pSD;
+        attrs.nLength = sizeof(SECURITY_ATTRIBUTES);
+
+        fflush(stderr);
+
+        return;
+cleanup:
+        fflush(stderr);
+        cleanup();
+    }
+
+    ~secattr()
+    {
+        cleanup();
+    }
+};
+
+PortableLockedShm::PortableLockedShm(const char* shmName, const char* mutexName, int mapSize)
+{
+    secattr sa(GENERIC_ALL|SYNCHRONIZE);
+
+    hMutex = CreateMutexA(sa.success ? &sa.attrs : nullptr, false, mutexName);
+    if (!hMutex)
+    {
+        fprintf(stderr, "CreateMutexA: %d\n", (int) GetLastError());
+        fflush(stderr);
+    }
+    hMapFile = CreateFileMappingA(
+                 INVALID_HANDLE_VALUE,
+                 sa.success ? &sa.attrs : nullptr,
+                 PAGE_READWRITE,
+                 0,
+                 mapSize,
+                 shmName);
+    if (!hMapFile)
+    {
+        fprintf(stderr, "CreateFileMappingA: %d\n", (int) GetLastError());
+        fflush(stderr);
+    }
+    mem = MapViewOfFile(hMapFile,
+                        FILE_MAP_WRITE,
+                        0,
+                        0,
+                        mapSize);
+    if (!mem)
+    {
+        fprintf(stderr, "MapViewOfFile: %d\n", (int) GetLastError());
+        fflush(stderr);
+    }
+}
+
+PortableLockedShm::~PortableLockedShm()
+{
+    UnmapViewOfFile(mem);
+    CloseHandle(hMapFile);
+    CloseHandle(hMutex);
+}
+
+void PortableLockedShm::lock()
+{
+    (void) WaitForSingleObject(hMutex, INFINITE);
+}
+
+void PortableLockedShm::unlock()
+{
+    (void) ReleaseMutex(hMutex);
+}
+#else
+
+#include <limits.h>
+
+#pragma GCC diagnostic ignored "-Wunused-result"
+PortableLockedShm::PortableLockedShm(const char *shmName, const char* /*mutexName*/, int mapSize) : size(mapSize)
+{
+    char filename[PATH_MAX+2] = {0};
+    strcpy(filename, "/");
+    strcat(filename, shmName);
+    fd = shm_open(filename, O_RDWR | O_CREAT, 0600);
+    (void) ftruncate(fd, mapSize);
+    mem = mmap(NULL, mapSize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, (off_t)0);
+}
+
+PortableLockedShm::~PortableLockedShm()
+{
+    (void) munmap(mem, size);
+    (void) close(fd);
+}
+
+void PortableLockedShm::lock()
+{
+    flock(fd, LOCK_EX);
+}
+
+void PortableLockedShm::unlock()
+{
+    flock(fd, LOCK_UN);
+}
+#endif
+
+bool PortableLockedShm::success()
+{
+#ifndef _WIN32
+    return mem != (void*) -1;
+#else
+    return mem != NULL;
+#endif
+}
diff --git a/compat/shm.h b/compat/shm.h
new file mode 100644
index 00000000..f212dc17
--- /dev/null
+++ b/compat/shm.h
@@ -0,0 +1,48 @@
+/* Copyright (c) 2013 Stanislaw Halik <sthalik@misaki.pl>
+
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ */
+#pragma once
+
+#if defined(_WIN32)
+#include <windows.h>
+#else
+#include <stdio.h>
+#include <string.h>
+#include <sys/file.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sys/types.h>
+#endif
+
+#ifdef __GNUC__
+#   pragma GCC diagnostic push
+#   pragma GCC diagnostic ignored "-Wattributes"
+#endif
+
+#include "export.hpp"
+
+class OPENTRACK_COMPAT_EXPORT PortableLockedShm {
+public:
+    PortableLockedShm(const char *shmName, const char *mutexName, int mapSize);
+    ~PortableLockedShm();
+    void lock();
+    void unlock();
+    bool success();
+    inline void* ptr() { return mem; }
+private:
+    void* mem;
+#if defined(_WIN32)
+    HANDLE hMutex, hMapFile;
+#else
+    int fd, size;
+#endif
+};
+
+#ifdef __GNUC__
+#   pragma GCC diagnostic pop
+#endif
diff --git a/compat/sleep.hpp b/compat/sleep.hpp
new file mode 100644
index 00000000..fab27286
--- /dev/null
+++ b/compat/sleep.hpp
@@ -0,0 +1,22 @@
+#pragma once
+
+#ifdef _WIN32
+#   include <windows.h>
+#else
+#   include <unistd.h>
+#endif
+
+namespace portable
+{
+#ifdef _WIN32
+    inline void sleep(unsigned milliseconds)
+    {
+        Sleep(milliseconds);
+    }
+#else
+    inline void sleep(unsigned milliseconds)
+    {
+        usleep(milliseconds * 1000U); // takes microseconds
+    }
+#endif
+}
diff --git a/compat/slider.cpp b/compat/slider.cpp
new file mode 100644
index 00000000..be1aaeb8
--- /dev/null
+++ b/compat/slider.cpp
@@ -0,0 +1,73 @@
+#include "slider.hpp"
+#include <cmath>
+
+namespace options {
+
+slider_value::slider_value(double cur, double min, double max) :
+    cur_(cur),
+    min_(min),
+    max_(max)
+{
+    if (min_ > max_)
+        min_ = max_;
+    if (cur_ > max_)
+        cur_ = max;
+    if (cur_ < min_)
+        cur_ = min_;
+}
+
+slider_value::slider_value(const slider_value& v) : slider_value(v.cur(), v.min(), v.max())
+{
+}
+
+slider_value::slider_value() : slider_value(0, 0, 0)
+{
+}
+
+slider_value& slider_value::operator=(const slider_value& v)
+{
+    cur_ = v.cur_;
+
+    min_ = v.min_;
+    max_ = v.max_;
+
+    return *this;
+}
+
+bool slider_value::operator==(const slider_value& v) const
+{
+    using std::fabs;
+
+    static constexpr double eps = 1e-3;
+
+    return (fabs(v.cur_ - cur_) < eps &&
+            fabs(v.min_ - min_) < eps &&
+            fabs(v.max_ - max_) < eps);
+}
+
+slider_value slider_value::update_from_slider(int pos, int q_min, int q_max) const
+{
+    slider_value v(*this);
+
+    const int q_diff = q_max - q_min;
+    const double sv_pos = q_diff == 0
+                          ? -1e6
+                          : (((pos - q_min) * (v.max() - v.min())) / q_diff + v.min());
+
+    if (sv_pos < v.min())
+        v = slider_value(v.min(), v.min(), v.max());
+    else if (sv_pos > v.max())
+        v = slider_value(v.max(), v.min(), v.max());
+    else
+        v = slider_value(sv_pos, v.min(), v.max());
+    return v;
+}
+
+int slider_value::to_slider_pos(int q_min, int q_max) const
+{
+    const int q_diff = q_max - q_min;
+
+    return int(std::round(((cur() - min() * q_diff) / (max() - min())) + q_min));
+}
+
+} // end ns options
diff --git a/compat/slider.hpp b/compat/slider.hpp
new file mode 100644
index 00000000..7d54c650
--- /dev/null
+++ b/compat/slider.hpp
@@ -0,0 +1,62 @@
+/* Copyright (c) 2016 Stanislaw Halik
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ */
+
+#pragma once
+
+#include "export.hpp"
+#include <QMetaType>
+#include <QDataStream>
+#include <QDebug>
+
+namespace options
+{
+    class OPENTRACK_COMPAT_EXPORT slider_value final
+    {
+        double cur_, min_, max_;
+    public:
+        slider_value(double cur, double min, double max);
+        slider_value(const slider_value& v);
+        slider_value();
+        slider_value& operator=(const slider_value& v);
+        bool operator==(const slider_value& v) const;
+        operator double() const { return cur_; }
+        double cur() const { return cur_; }
+        double min() const { return min_; }
+        double max() const { return max_; }
+        slider_value update_from_slider(int pos, int q_min, int q_max) const;
+        int to_slider_pos(int q_min, int q_max) const;
+    };
+}
+
+QT_BEGIN_NAMESPACE
+
+inline QDebug operator << (QDebug dbg, const options::slider_value& val)
+{
+    return dbg << val.cur();
+}
+
+inline QDataStream& operator << (QDataStream& out, const options::slider_value& v)
+{
+    out << v.cur()
+        << v.min()
+        << v.max();
+    return out;
+}
+
+inline QDataStream& operator >> (QDataStream& in, options::slider_value& v)
+{
+    double cur, min, max;
+    in >> cur;
+    in >> min;
+    in >> max;
+    v = options::slider_value(cur, min, max);
+    return in;
+}
+
+QT_END_NAMESPACE
+
+Q_DECLARE_METATYPE(options::slider_value)
diff --git a/compat/timer.hpp b/compat/timer.hpp
new file mode 100644
index 00000000..300a883c
--- /dev/null
+++ b/compat/timer.hpp
@@ -0,0 +1,100 @@
+/* Copyright (c) 2014-2015, Stanislaw Halik <sthalik@misaki.pl>
+
+ * Permission to use, copy, modify, and/or distribute this
+ * software for any purpose with or without fee is hereby granted,
+ * provided that the above copyright notice and this permission
+ * notice appear in all copies.
+ */
+
+#pragma once
+#include <ctime>
+
+#include "export.hpp"
+
+#if defined (_WIN32)
+#   include <windows.h>
+#	ifndef CLOCK_MONOTONIC
+#   	define CLOCK_MONOTONIC -1
+#	endif
+static inline void opentrack_clock_gettime(int, struct timespec* ts)
+{
+    static LARGE_INTEGER freq;
+
+    if (!freq.QuadPart)
+        (void) QueryPerformanceFrequency(&freq);
+
+    LARGE_INTEGER d;
+
+    (void) QueryPerformanceCounter(&d);
+
+    using ll = long long;
+    using ld = long double;
+    const long long part = ll(d.QuadPart / ld(freq.QuadPart) * 1000000000.L);
+    using t_s = decltype(ts->tv_sec);
+    using t_ns = decltype(ts->tv_nsec);
+
+    ts->tv_sec = t_s(part / 1000000000LL);
+    ts->tv_nsec = t_ns(part % 1000000000LL);
+}
+#	define clock_gettime opentrack_clock_gettime
+#else
+#   if defined(__MACH__)
+#       define CLOCK_MONOTONIC 0
+#       include <inttypes.h>
+#       include <mach/mach_time.h>
+static inline void clock_gettime(int, struct timespec* ts)
+{
+    static mach_timebase_info_data_t    sTimebaseInfo;
+    uint64_t state, nsec;
+    if ( sTimebaseInfo.denom == 0 ) {
+        (void) mach_timebase_info(&sTimebaseInfo);
+    }
+    state = mach_absolute_time();
+    nsec = state * sTimebaseInfo.numer / sTimebaseInfo.denom;
+    ts->tv_sec = nsec / 1000000000L;
+    ts->tv_nsec = nsec % 1000000000L;
+}
+#   endif
+#endif
+class Timer
+{
+private:
+    struct timespec state;
+    long long conv_nsecs(const struct timespec& cur) const
+    {
+        return (cur.tv_sec - state.tv_sec) * 1000000000LL + (cur.tv_nsec - state.tv_nsec);
+    }
+    long conv_usecs(const struct timespec& cur) const
+    {
+        return long(cur.tv_sec - state.tv_sec) * 1000000L + long(cur.tv_nsec - state.tv_nsec) / 1000l;
+    }
+public:
+    Timer()
+    {
+        start();
+    }
+    void start()
+    {
+        clock_gettime(CLOCK_MONOTONIC, &state);
+    }
+    long long elapsed_nsecs() const
+    {
+        struct timespec cur;
+        clock_gettime(CLOCK_MONOTONIC, &cur);
+        return conv_nsecs(cur);
+    }
+    long elapsed_usecs() const
+    {
+        struct timespec cur;
+        clock_gettime(CLOCK_MONOTONIC, &cur);
+        return long(conv_usecs(cur));
+    }
+    long elapsed_ms() const
+    {
+        return elapsed_usecs() / 1000L;
+    }
+    double elapsed_seconds() const
+    {
+        return double(elapsed_nsecs() * 1e-9L);
+    }
+};
diff --git a/compat/util.hpp b/compat/util.hpp
new file mode 100644
index 00000000..1217e654
--- /dev/null
+++ b/compat/util.hpp
@@ -0,0 +1,93 @@
+#pragma once
+
+#include "make-unique.hpp"
+
+#include <memory>
+#include <utility>
+#include <type_traits>
+#include <thread>
+#include <condition_variable>
+
+#include <QDebug>
+
+#define progn(...) ([&]() { __VA_ARGS__ }())
+template<typename t> using mem = std::shared_ptr<t>;
+template<typename t> using ptr = std::unique_ptr<t>;
+
+template<typename F>
+void run_in_thread_async(QObject* obj, F&& fun)
+{
+    QObject src;
+    src.moveToThread(obj->thread());
+    QObject::connect(&src, &QObject::destroyed, obj, std::move(fun), Qt::AutoConnection);
+}
+
+namespace detail {
+
+template<typename t>
+struct run_in_thread_traits
+{
+    using type = t;
+    using ret_type = t&&;
+    static inline void assign(t& lvalue, t&& rvalue) { lvalue = rvalue; }
+    static inline ret_type&& pass(ret_type&& val) { return std::move(val); }
+    template<typename F> static ret_type call(F& fun) { return std::move(fun()); }
+};
+
+template<>
+struct run_in_thread_traits<void>
+{
+    using type = unsigned char;
+    using ret_type = void;
+    static inline void assign(unsigned char&, unsigned char&&) {}
+    static inline void pass(type&&) {}
+    template<typename F> static type&& call(F& fun) { fun(); return std::move(type(0)); }
+};
+
+}
+
+template<typename F>
+auto run_in_thread_sync(QObject* obj, F&& fun)
+    -> typename detail::run_in_thread_traits<decltype(std::forward<F>(fun)())>::ret_type
+{
+    using lock_guard = std::unique_lock<std::mutex>;
+
+    std::mutex mtx;
+    lock_guard guard(mtx);
+    std::condition_variable cvar;
+
+    std::thread::id waiting_thread = std::this_thread::get_id();
+
+    using traits = detail::run_in_thread_traits<decltype(std::forward<F>(fun)())>;
+
+    typename traits::type ret;
+
+    bool skip_wait = false;
+
+    {
+        QObject src;
+        src.moveToThread(obj->thread());
+        QObject::connect(&src,
+                         &QObject::destroyed,
+                         obj,
+                         [&]() {
+            std::thread::id calling_thread = std::this_thread::get_id();
+            if (waiting_thread == calling_thread)
+            {
+                skip_wait = true;
+                traits::assign(ret, traits::call(fun));
+            }
+            else
+            {
+                lock_guard guard(mtx);
+                traits::assign(ret, traits::call(fun));
+                cvar.notify_one();
+            }
+        },
+        Qt::AutoConnection);
+    }
+
+    if (!skip_wait)
+        cvar.wait(guard);
+    return traits::pass(std::move(ret));
+}
diff --git a/compat/win32-com.cpp b/compat/win32-com.cpp
new file mode 100644
index 00000000..dd7c24a8
--- /dev/null
+++ b/compat/win32-com.cpp
@@ -0,0 +1,60 @@
+#ifdef _WIN32
+
+#include "win32-com.hpp"
+
+#include <QString>
+#include <QThread>
+#include <QDebug>
+
+bool OPENTRACK_COMPAT_EXPORT init_com_threading(com_type t)
+{
+    static thread_local com_type initialized = com_type(-1);
+
+    if (initialized != com_type(-1))
+    {
+        if (t != initialized)
+        {
+            QString tp("invalid type");
+            switch (t)
+            {
+            case com_apartment:
+                tp = "apartment threaded";
+                break;
+            case com_multithreaded:
+                tp = "multithreaded";
+                break;
+            }
+
+            qDebug() << "COM for thread"
+                     << QThread::currentThread() << QThread::currentThreadId()
+                     << "already initialized to" << tp;
+
+            return false;
+        }
+
+        return true;
+    }
+
+    HRESULT ret = CoInitializeEx(0, t);
+
+    if (ret != S_OK && ret != S_FALSE)
+    {
+        qDebug() << "CoInitializeEx failed:" << ret << GetLastError();
+        return false;
+    }
+
+    if (t == com_apartment)
+    {
+        ret = OleInitialize(nullptr);
+
+        if (ret != S_OK && ret != S_FALSE)
+            qDebug() << "OleInitialize() failed:" << ret << GetLastError();
+
+        return false;
+    }
+
+    initialized = t;
+
+    return true;
+}
+#endif
diff --git a/compat/win32-com.hpp b/compat/win32-com.hpp
new file mode 100644
index 00000000..dcbea089
--- /dev/null
+++ b/compat/win32-com.hpp
@@ -0,0 +1,18 @@
+#pragma once
+
+#ifdef _WIN32
+
+#include "export.hpp"
+
+#include <objbase.h>
+#include <ole2.h>
+
+enum com_type : int
+{
+    com_multithreaded = COINIT_MULTITHREADED,
+    com_apartment = COINIT_APARTMENTTHREADED,
+};
+
+bool OPENTRACK_COMPAT_EXPORT init_com_threading(com_type t = com_multithreaded);
+
+#endif
-- 
cgit v1.2.3