]> Some of my projects - aniplayer.git/commitdiff
Initial Aniplayer3 commit
authorAPTX <marek321@gmail.com>
Sun, 26 Feb 2017 12:44:11 +0000 (13:44 +0100)
committerAPTX <marek321@gmail.com>
Sun, 26 Feb 2017 12:44:11 +0000 (13:44 +0100)
60 files changed:
.gitignore [new file with mode: 0644]
aniplayer3.pro [new file with mode: 0644]
backendplugins/backend_mpv/backend_mpv.json [new file with mode: 0644]
backendplugins/backend_mpv/backend_mpv.pro [new file with mode: 0644]
backendplugins/backend_mpv/backend_mpv_global.h [new file with mode: 0644]
backendplugins/backend_mpv/backendmpv.cpp [new file with mode: 0644]
backendplugins/backend_mpv/backendmpv.h [new file with mode: 0644]
backendplugins/backend_null/backend_null.json [new file with mode: 0644]
backendplugins/backend_null/backend_null.pro [new file with mode: 0644]
backendplugins/backend_null/backend_null_global.h [new file with mode: 0644]
backendplugins/backend_null/backendnull.cpp [new file with mode: 0644]
backendplugins/backend_null/backendnull.h [new file with mode: 0644]
backendplugins/backendbuildconfig.pri [new file with mode: 0644]
backendplugins/backendplugins.pro [new file with mode: 0644]
buildconfig.pri [new file with mode: 0644]
config.pri [new file with mode: 0644]
core/aniplayer.exe.manifest [new file with mode: 0644]
core/aniplayer.rc [new file with mode: 0644]
core/backendpluginbase.cpp [new file with mode: 0644]
core/backendpluginbase.h [new file with mode: 0644]
core/core.pri [new file with mode: 0644]
core/core.pro [new file with mode: 0644]
core/instancemanager.cpp [new file with mode: 0644]
core/instancemanager.h [new file with mode: 0644]
core/main.cpp [new file with mode: 0644]
core/player.cpp [new file with mode: 0644]
core/player.h [new file with mode: 0644]
core/playerplugininterface.cpp [new file with mode: 0644]
core/playerplugininterface.h [new file with mode: 0644]
core/pluginmanager.cpp [new file with mode: 0644]
core/pluginmanager.h [new file with mode: 0644]
core/qml.qrc [new file with mode: 0644]
core/qml/Button.qml [new file with mode: 0644]
core/qml/OpenButton.qml [new file with mode: 0644]
core/qml/PlaybackPosition.qml [new file with mode: 0644]
core/qml/PlayerControls.qml [new file with mode: 0644]
core/qml/SeekSlider.qml [new file with mode: 0644]
core/qml/VolumeSlider.qml [new file with mode: 0644]
core/qml/main.qml [new file with mode: 0644]
core/qtsingleapplication/QtLockedFile [new file with mode: 0644]
core/qtsingleapplication/QtSingleApplication [new file with mode: 0644]
core/qtsingleapplication/qtlocalpeer.cpp [new file with mode: 0644]
core/qtsingleapplication/qtlocalpeer.h [new file with mode: 0644]
core/qtsingleapplication/qtlockedfile.cpp [new file with mode: 0644]
core/qtsingleapplication/qtlockedfile.h [new file with mode: 0644]
core/qtsingleapplication/qtlockedfile_unix.cpp [new file with mode: 0644]
core/qtsingleapplication/qtlockedfile_win.cpp [new file with mode: 0644]
core/qtsingleapplication/qtsingleapplication.cpp [new file with mode: 0644]
core/qtsingleapplication/qtsingleapplication.h [new file with mode: 0644]
core/qtsingleapplication/qtsingleapplication.pri [new file with mode: 0644]
core/qtsingleapplication/qtsinglecoreapplication.cpp [new file with mode: 0644]
core/qtsingleapplication/qtsinglecoreapplication.h [new file with mode: 0644]
core/qtsingleapplication/qtsinglecoreapplication.pri [new file with mode: 0644]
core/timeformatter.cpp [new file with mode: 0644]
core/timeformatter.h [new file with mode: 0644]
core/videoelement.cpp [new file with mode: 0644]
core/videoelement.h [new file with mode: 0644]
resource/aniplayer-mikuru.ico [new file with mode: 0644]
resource/mikuru-icon-base.png [new file with mode: 0644]
resource/mikuru-icon-original.png [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..f298019
--- /dev/null
@@ -0,0 +1,82 @@
+# This file is used to ignore files which are generated
+# ----------------------------------------------------------------------------
+
+*~
+*.a
+*.core
+*.moc
+*.o
+*.obj
+*.orig
+*.rej
+*.so
+*_pch.h.cpp
+*_resource.rc
+*.qm
+.#*
+*.*#
+.qmake.cache
+tags
+.DS_Store
+*.debug
+Makefile*
+*.prl
+*.app
+moc_*.cpp
+ui_*.h
+qrc_*.cpp
+*.qmake.stash
+
+# qtcreator generated files
+*.pro.user
+*.pro.user.*
+*.autosave
+*.files
+*.creator
+*.creator.*
+*.config
+*.includes
+
+# xemacs temporary files
+*.flc
+
+# Vim temporary files
+.*.swp
+
+# Visual Studio generated files
+*.ib_pdb_index
+*.idb
+*.ilk
+*.pdb
+*.sln
+*.suo
+*.vcproj
+*vcproj.*.*.user
+*.ncb
+*.exp
+
+# MinGW generated files
+*.Debug
+*.Release
+
+# Directories to ignore
+# ---------------------
+
+build
+debug
+release
+lib/qtsingleapplication/lib
+lib/qtsingleapplication/examples
+lib/qtsingleapplication/doc
+.tmp
+qtc-gdbmacros
+test-data
+
+# Binaries
+# --------
+build/*.dll
+build/*.lib
+build/*.exe
+build/*.so*
+
+
diff --git a/aniplayer3.pro b/aniplayer3.pro
new file mode 100644 (file)
index 0000000..821e32c
--- /dev/null
@@ -0,0 +1,5 @@
+TEMPLATE = subdirs
+
+SUBDIRS += \
+    core \
+    backendplugins
diff --git a/backendplugins/backend_mpv/backend_mpv.json b/backendplugins/backend_mpv/backend_mpv.json
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/backendplugins/backend_mpv/backend_mpv.pro b/backendplugins/backend_mpv/backend_mpv.pro
new file mode 100644 (file)
index 0000000..4cb0b3b
--- /dev/null
@@ -0,0 +1,28 @@
+TARGET = backend_mpv
+TEMPLATE = lib
+include(../../core/core.pri)
+include(../backendbuildconfig.pri)
+
+DEFINES += BACKEND_MPV_LIBRARY QT_DEPRECATED_WARNINGS
+
+SOURCES += \
+    backendmpv.cpp
+
+HEADERS += \
+    backendmpv.h \
+    backend_mpv_global.h
+
+unix {
+    LIBS += $$system(pkg-config --libs mpv)
+}
+!unix {
+    LIBS += -lmpv-1
+}
+
+unix {
+    target.path = /usr/lib/aniplayer/backendplugins
+    INSTALLS += target
+}
+
+DISTFILES += \
+    backend_mpv.json
diff --git a/backendplugins/backend_mpv/backend_mpv_global.h b/backendplugins/backend_mpv/backend_mpv_global.h
new file mode 100644 (file)
index 0000000..f7f4b2d
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef BACKEND_MPV_GLOBAL_H
+#define BACKEND_MPV_GLOBAL_H
+
+#include <QtCore/qglobal.h>
+
+#if defined(BACKEND_MPV_LIBRARY)
+#  define BACKEND_MPVSHARED_EXPORT Q_DECL_EXPORT
+#else
+#  define BACKEND_MPVSHARED_EXPORT Q_DECL_IMPORT
+#endif
+
+#endif // BACKEND_MPV_GLOBAL_H
diff --git a/backendplugins/backend_mpv/backendmpv.cpp b/backendplugins/backend_mpv/backendmpv.cpp
new file mode 100644 (file)
index 0000000..2181fc0
--- /dev/null
@@ -0,0 +1,319 @@
+#include "backendmpv.h"
+
+#include <QOpenGLContext>
+#include <QOpenGLFramebufferObject>
+#include <QUrl>
+
+#include <mpv/client.h>
+#include <mpv/opengl_cb.h>
+
+#include <QLoggingCategory>
+
+Q_LOGGING_CATEGORY(mpvBackend, "MPV")
+Q_LOGGING_CATEGORY(mpvLog, "MPV Log")
+
+BackendMpv::BackendMpv() {
+#ifdef Q_OS_UNIX
+  setlocale(LC_NUMERIC, "C");
+#endif
+}
+
+BackendMpv::~BackendMpv() {}
+
+bool BackendMpv::initialize(PlayerPluginInterface *playerInterface) {
+  qCDebug(mpvBackend) << "Initialize";
+  m_player = playerInterface;
+  m_handle = mpv_create();
+
+  qCDebug(mpvBackend()).nospace()
+      << "Client API version: " << (mpv_client_api_version() >> 16) << '.'
+      << (mpv_client_api_version() & ~(~0u << 16));
+
+  // mpv_set_option(handle, "wid", MPV_FORMAT_INT64, &wid);
+  mpv_set_option_string(m_handle, "vo", "opengl-cb");
+
+  int error = mpv_initialize(m_handle);
+  if (error) {
+    qCDebug(mpvBackend) << "Error initializing mpv" << mpv_error_string(error);
+    return false;
+  }
+
+  mpv_set_wakeup_callback(m_handle, mpvWakeupCb, this);
+  qCDebug(mpvBackend) << "register pause"
+                      << mpv_observe_property(m_handle, 0, "pause",
+                                              MPV_FORMAT_FLAG);
+  qCDebug(mpvBackend) << "register duration"
+                      << mpv_observe_property(m_handle, 0, "duration",
+                                              MPV_FORMAT_DOUBLE);
+  qCDebug(mpvBackend) << "register playback-time"
+                      << mpv_observe_property(m_handle, 0, "playback-time",
+                                              MPV_FORMAT_DOUBLE);
+  qCDebug(mpvBackend) << "register ao-volume"
+                      << mpv_observe_property(m_handle, 0, "ao-volume",
+                                              MPV_FORMAT_DOUBLE);
+
+  qCDebug(mpvBackend) << "request log messages"
+                      << mpv_request_log_messages(m_handle, "info");
+  return !error;
+}
+
+void BackendMpv::deinitialize() {
+  qCDebug(mpvBackend) << "Deinitialize";
+  mpv_terminate_destroy(m_handle);
+}
+
+VideoRendererBase *BackendMpv::createRenderer(VideoUpdateInterface *vui) {
+  qCDebug(mpvBackend, "BackendMpv::createRenderer");
+  return new VideoRendererMpv(m_handle, vui);
+}
+
+bool BackendMpv::open(const QUrl &source) {
+  qCDebug(mpvBackend) << "Opening " << source;
+  const QByteArray tmp = source.toLocalFile().toUtf8();
+
+  const char *args[] = {"loadfile", tmp.constData(), NULL};
+  mpv_command_async(m_handle, 1, args);
+  pause();
+
+  return true;
+}
+
+void BackendMpv::play() {
+  qCDebug(mpvBackend) << "Play";
+  int f = 0;
+  mpv_set_property(m_handle, "pause", MPV_FORMAT_FLAG, &f);
+}
+
+void BackendMpv::pause() {
+  qCDebug(mpvBackend) << "Pause";
+  int f = 1;
+  mpv_set_property(m_handle, "pause", MPV_FORMAT_FLAG, &f);
+}
+
+void BackendMpv::stop() { qCDebug(mpvBackend) << "Stop"; }
+
+void BackendMpv::seek(TimeStamp pos) {
+  mpv_set_property(m_handle, "playback-time", MPV_FORMAT_DOUBLE, &pos);
+}
+
+void BackendMpv::setVolume(BackendPluginBase::Volume volume) {
+  double percantageVolume = volume * 100;
+  int error = mpv_set_property(m_handle, "ao-volume", MPV_FORMAT_DOUBLE,
+                               &percantageVolume);
+  if (error) {
+    qCDebug(mpvBackend)
+        << "Audio output not yet ready, setting volume at a later time";
+    m_volumeToSet = volume;
+    m_player->playbackVolumeChanged(volume);
+  } else {
+    qCDebug(mpvBackend) << "Audio volume set";
+    m_volumeToSet = -1;
+  }
+}
+
+template <int type> struct MpvProperty;
+
+template <> struct MpvProperty<MPV_FORMAT_NONE> {
+  static void read(struct mpv_event_property *property) {
+    Q_ASSERT(property->format == MPV_FORMAT_NONE);
+  }
+};
+
+template <> struct MpvProperty<MPV_FORMAT_FLAG> {
+  static bool read(struct mpv_event_property *property) {
+    Q_ASSERT(property->format == MPV_FORMAT_FLAG);
+    if (!property->data)
+      qWarning("Property data data is null");
+    return *static_cast<int *>(property->data) ? true : false;
+  }
+};
+
+template <> struct MpvProperty<MPV_FORMAT_INT64> {
+  static qint64 read(struct mpv_event_property *property) {
+    Q_ASSERT(property->format == MPV_FORMAT_INT64);
+    if (!property->data)
+      qWarning("Property data data is null");
+    return *static_cast<qint64 *>(property->data);
+  }
+};
+
+template <> struct MpvProperty<MPV_FORMAT_DOUBLE> {
+  static double read(struct mpv_event_property *property) {
+    Q_ASSERT(property->format == MPV_FORMAT_DOUBLE);
+    if (!property->data)
+      qWarning("Property data data is null");
+    return *static_cast<double *>(property->data);
+  }
+};
+
+template <int TYPE>
+decltype(auto) readProperty(struct mpv_event_property *property) {
+  return MpvProperty<TYPE>::read(property);
+}
+
+void BackendMpv::processMpvEvents() {
+  struct mpv_event *event = nullptr;
+  do {
+    event = mpv_wait_event(m_handle, 0);
+    if (event->event_id == MPV_EVENT_NONE)
+      break;
+    qCDebug(mpvBackend).nospace()
+        << "Event " << mpv_event_name(event->event_id) << '(' << event->event_id
+        << "), error: " << event->error;
+    switch (event->event_id) {
+    case MPV_EVENT_PROPERTY_CHANGE: {
+      if (!event->data)
+        qCWarning(mpvBackend, "PROPERTY CHANGE data is null");
+      auto property = static_cast<mpv_event_property *>(event->data);
+      qCDebug(mpvBackend) << "Property" << property->name << "changed";
+      if (property->format == MPV_FORMAT_NONE) {
+        qCDebug(mpvBackend) << "No data in event";
+        break;
+      }
+      if (strcmp(property->name, "pause") == 0) {
+        auto paused = readProperty<MPV_FORMAT_FLAG>(property);
+        auto state = paused ? PlayerPluginInterface::PlayState::Paused
+                            : PlayerPluginInterface::PlayState::Playing;
+        if (!m_loadedFile)
+          state = PlayerPluginInterface::PlayState::Stopped;
+        m_player->playStateChanged(state);
+      } else if (strcmp(property->name, "duration") == 0) {
+        m_player->playbackDurationChanged(
+            static_cast<PlayerPluginInterface::TimeStamp>(
+                readProperty<MPV_FORMAT_DOUBLE>(property)));
+      } else if (strcmp(property->name, "playback-time") == 0) {
+        m_player->playbackPositionChanged(
+            static_cast<PlayerPluginInterface::TimeStamp>(
+                readProperty<MPV_FORMAT_DOUBLE>(property)));
+      } else if (strcmp(property->name, "ao-volume") == 0) {
+        if (m_volumeToSet > 0) {
+          qCDebug(mpvBackend)
+              << "Requested volume still not set, skipping this update";
+        } else {
+          m_player->playbackVolumeChanged(
+              static_cast<PlayerPluginInterface::Volume>(
+                  readProperty<MPV_FORMAT_DOUBLE>(property) / 100.0));
+        }
+      }
+    } break;
+    case MPV_EVENT_LOG_MESSAGE: {
+      if (!event->data)
+        qCWarning(mpvBackend, "LOG MESSAGE data is null");
+      auto log = static_cast<mpv_event_log_message *>(event->data);
+      QMessageLogger l{0, 0, 0, mpvLog().categoryName()};
+      if (log->log_level <= MPV_LOG_LEVEL_ERROR)
+        l.critical() << log->text;
+      else if (log->log_level <= MPV_LOG_LEVEL_WARN)
+        l.warning() << log->text;
+      else if (log->log_level <= MPV_LOG_LEVEL_INFO)
+        l.info() << log->text;
+      else
+        l.debug() << log->text;
+    } break;
+    case MPV_EVENT_FILE_LOADED: {
+      int paused = 0;
+      mpv_get_property(m_handle, "paused", MPV_FORMAT_FLAG, &paused);
+      qCDebug(mpvBackend) << "file-loaded event!" << paused;
+      m_loadedFile = true;
+      auto state = paused ? PlayerPluginInterface::PlayState::Paused
+                          : PlayerPluginInterface::PlayState::Playing;
+      m_player->playStateChanged(state);
+    } break;
+    case MPV_EVENT_END_FILE: {
+      m_loadedFile = false;
+
+      if (!event->data)
+        qCWarning(mpvBackend, "END FILE data is null");
+      auto endFile = static_cast<mpv_event_end_file *>(event->data);
+      if (endFile->reason == MPV_END_FILE_REASON_ERROR)
+        qCWarning(mpvBackend).nospace()
+            << "File ended due to error " << endFile->error << ": "
+            << mpv_error_string(endFile->error);
+      else
+        qCInfo(mpvBackend) << "File ended. Reason:" << endFile->reason;
+      m_player->playStateChanged(PlayerPluginInterface::PlayState::Stopped);
+    } break;
+    case MPV_EVENT_IDLE: {
+      m_player->playStateChanged(PlayerPluginInterface::PlayState::Stopped);
+      m_player->backendReadyToPlay();
+    } break;
+    case MPV_EVENT_AUDIO_RECONFIG: {
+      if (m_volumeToSet >= 0) {
+        qCDebug(mpvBackend) << "Audio reconfigured, maybe it's ready now?";
+        setVolume(m_volumeToSet);
+      }
+    } break;
+    default:;
+    }
+  } while (event->event_id != MPV_EVENT_NONE);
+}
+
+void BackendMpv::mpvWakeupCb(void *obj) {
+  auto self = static_cast<BackendMpv *>(obj);
+  QMetaObject::invokeMethod(self, "processMpvEvents", Qt::QueuedConnection);
+}
+
+VideoRendererMpv::VideoRendererMpv(mpv_handle *handle,
+                                   VideoUpdateInterface *vui)
+    : m_handle{handle} {
+  qCDebug(mpvBackend, "VideoRendererMpv::VideoRendererMpv");
+  m_oglCtx = static_cast<mpv_opengl_cb_context *>(
+      mpv_get_sub_api(handle, MPV_SUB_API_OPENGL_CB));
+  qCDebug(mpvBackend) << "created ogl ctx" << m_oglCtx;
+  if (!m_oglCtx) {
+    qCDebug(mpvBackend) << "Error obtaining mpv ogl context";
+    return;
+  }
+
+  qCDebug(mpvBackend, "setting callback");
+  mpv_opengl_cb_set_update_callback(m_oglCtx, mpvUpdate, vui);
+  qCDebug(mpvBackend, "initializing gl context");
+  int error = mpv_opengl_cb_init_gl(m_oglCtx, NULL, getProcAddress, this);
+  if (error) {
+    qCCritical(mpvBackend())
+        << "Error initializing mpv ogl context:" << mpv_error_string(error);
+    m_oglCtx = nullptr;
+  }
+  qCDebug(mpvBackend, "all done");
+}
+
+VideoRendererMpv::~VideoRendererMpv() {
+  if (m_oglCtx) {
+    int error = mpv_opengl_cb_uninit_gl(m_oglCtx);
+    if (error)
+      qCWarning(mpvBackend)
+          << "Error uninitializing mpv ogl context:" << mpv_error_string(error);
+  }
+}
+
+void VideoRendererMpv::render(QOpenGLFramebufferObject *fbo) {
+  int error =
+      mpv_opengl_cb_draw(m_oglCtx,
+                         static_cast<int>(fbo->handle()), // GLuint is unsigned
+                         fbo->width(), fbo->height());
+  if (error)
+    qCCritical(mpvBackend) << "Error rendering mpv frame:"
+                           << mpv_error_string(error);
+}
+
+#ifdef Q_OS_WIN
+#include <windows.h>
+#endif
+
+void *VideoRendererMpv::getProcAddress(void *, const char *name) {
+  const QByteArray ext{name};
+  void *ret = reinterpret_cast<void *>(
+      QOpenGLContext::currentContext()->getProcAddress(ext));
+#ifdef Q_OS_WIN
+  if (!ret) {
+    HMODULE module = ::LoadLibraryA("opengl32.dll");
+    ret = reinterpret_cast<void *>(::GetProcAddress(module, name));
+  }
+#endif
+  return ret;
+}
+
+void VideoRendererMpv::mpvUpdate(void *obj) {
+  const auto vui = static_cast<VideoUpdateInterface *>(obj);
+  vui->videoUpdated();
+}
diff --git a/backendplugins/backend_mpv/backendmpv.h b/backendplugins/backend_mpv/backendmpv.h
new file mode 100644 (file)
index 0000000..41b0d3a
--- /dev/null
@@ -0,0 +1,61 @@
+#ifndef BACKENDMPV_H
+#define BACKENDMPV_H
+
+#include <QObject>
+#include <QtPlugin>
+
+#include "backend_mpv_global.h"
+#include "backendpluginbase.h"
+
+struct mpv_handle;
+struct mpv_opengl_cb_context;
+
+class BACKEND_MPVSHARED_EXPORT BackendMpv : public QObject,
+                                            public BackendPluginBase {
+  Q_OBJECT
+  Q_PLUGIN_METADATA(IID ANIPLAYER_BACKEND_DPLUGIN_INTERFACE_IID FILE
+                    "backend_mpv.json")
+  Q_INTERFACES(BackendPluginBase)
+
+public:
+  BackendMpv();
+  virtual ~BackendMpv();
+  bool initialize(PlayerPluginInterface *) override;
+  void deinitialize() override;
+
+  VideoRendererBase *createRenderer(VideoUpdateInterface *) override;
+
+  bool open(const QUrl &source) override;
+  void play() override;
+  void pause() override;
+  void stop() override;
+
+  void seek(TimeStamp) override;
+
+  void setVolume(Volume) override;
+
+private:
+  mpv_handle *m_handle = nullptr;
+  PlayerPluginInterface *m_player = nullptr;
+  Volume m_volumeToSet = -1;
+  bool m_loadedFile = false;
+
+  Q_INVOKABLE void processMpvEvents();
+  static void mpvWakeupCb(void *);
+};
+
+class BACKEND_MPVSHARED_EXPORT VideoRendererMpv : public VideoRendererBase {
+public:
+  explicit VideoRendererMpv(mpv_handle *, VideoUpdateInterface *);
+  ~VideoRendererMpv() override;
+  void render(QOpenGLFramebufferObject *) override;
+
+private:
+  mpv_handle *m_handle = nullptr;
+  mpv_opengl_cb_context *m_oglCtx = nullptr;
+
+  static void *getProcAddress(void *, const char *name);
+  static void mpvUpdate(void *);
+};
+
+#endif // BACKENDMPV_H
diff --git a/backendplugins/backend_null/backend_null.json b/backendplugins/backend_null/backend_null.json
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/backendplugins/backend_null/backend_null.pro b/backendplugins/backend_null/backend_null.pro
new file mode 100644 (file)
index 0000000..42eae60
--- /dev/null
@@ -0,0 +1,16 @@
+TARGET = backend_null
+QT -= gui
+TEMPLATE = lib
+include(../../core/core.pri)
+
+DEFINES += BACKEND_NULL_LIBRARY QT_DEPRECATED_WARNINGS
+
+SOURCES += backendnull.cpp
+
+HEADERS += backendnull.h\
+           backend_null_global.h
+
+unix {
+    target.path = /usr/lib
+    INSTALLS += target
+}
diff --git a/backendplugins/backend_null/backend_null_global.h b/backendplugins/backend_null/backend_null_global.h
new file mode 100644 (file)
index 0000000..cdc30e3
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef BACKEND_NULL_GLOBAL_H
+#define BACKEND_NULL_GLOBAL_H
+
+#include <QtCore/qglobal.h>
+
+#if defined(BACKEND_NULL_LIBRARY)
+#  define BACKEND_NULLSHARED_EXPORT Q_DECL_EXPORT
+#else
+#  define BACKEND_NULLSHARED_EXPORT Q_DECL_IMPORT
+#endif
+
+#endif // BACKEND_NULL_GLOBAL_H
diff --git a/backendplugins/backend_null/backendnull.cpp b/backendplugins/backend_null/backendnull.cpp
new file mode 100644 (file)
index 0000000..c09259e
--- /dev/null
@@ -0,0 +1,63 @@
+#include "backendnull.h"
+
+#include <QTimer>
+#include <QDebug>
+
+BackendNull::BackendNull()
+{
+}
+
+BackendNull::~BackendNull()
+{
+}
+
+bool BackendNull::initialize(PlayerPluginInterface *)
+{
+    qDebug() << "Initialize";
+    m_timer = new QTimer{this};
+    m_timer->setInterval(1000);
+    return true;
+}
+
+void BackendNull::deinitialize()
+{
+    qDebug() << "Deinitialize";
+    delete m_timer;
+    m_timer = nullptr;
+}
+
+bool BackendNull::open(const QUrl &source)
+{
+    qDebug() << "Opening " << source;
+    return true;
+}
+
+void BackendNull::play()
+{
+    qDebug() << "Play";
+}
+
+void BackendNull::pause()
+{
+    qDebug() << "Pause";
+}
+
+void BackendNull::stop()
+{
+    qDebug() << "Stop";
+}
+
+
+VideoRendererBase *BackendNull::createRenderer(VideoUpdateInterface *)
+{
+    return nullptr;
+}
+
+
+void BackendNull::seek(TimeStamp)
+{
+}
+
+void BackendNull::setVolume(BackendPluginBase::Volume)
+{
+}
diff --git a/backendplugins/backend_null/backendnull.h b/backendplugins/backend_null/backendnull.h
new file mode 100644 (file)
index 0000000..9915925
--- /dev/null
@@ -0,0 +1,41 @@
+#ifndef BACKENDNULL_H
+#define BACKENDNULL_H
+
+#include <QObject>
+#include <QtPlugin>
+
+#include "backend_null_global.h"
+#include "backendpluginbase.h"
+
+class QTimer;
+
+class BACKEND_NULLSHARED_EXPORT BackendNull : public QObject, public BackendPluginBase
+{
+    Q_OBJECT
+    Q_PLUGIN_METADATA(IID ANIPLAYER_BACKEND_DPLUGIN_INTERFACE_IID FILE "backend_null.json")
+    Q_INTERFACES(BackendPluginBase)
+
+public:
+    BackendNull();
+    virtual ~BackendNull();
+    bool initialize(PlayerPluginInterface *) override;
+    void deinitialize() override;
+
+    bool open(const QUrl &source) override;
+    void play() override;
+    void pause() override;
+    void stop() override;
+
+
+    void seek(TimeStamp) override;
+
+    void setVolume(Volume) override;
+
+     VideoRendererBase *createRenderer(VideoUpdateInterface *) override;
+
+private:
+    QTimer *m_timer = nullptr;
+
+};
+
+#endif // BACKENDNULL_H
diff --git a/backendplugins/backendbuildconfig.pri b/backendplugins/backendbuildconfig.pri
new file mode 100644 (file)
index 0000000..09a7862
--- /dev/null
@@ -0,0 +1,2 @@
+include(../buildconfig.pri)
+DESTDIR=../../build/backendplugins
\ No newline at end of file
diff --git a/backendplugins/backendplugins.pro b/backendplugins/backendplugins.pro
new file mode 100644 (file)
index 0000000..5f3faf9
--- /dev/null
@@ -0,0 +1,15 @@
+TEMPLATE = subdirs
+
+include(../config.pri)
+
+!no_backend_null {
+    SUBDIRS += backend_null
+}
+
+backend_mpv {
+    SUBDIRS += backend_mpv
+}
+
+backend_qtav {
+    SUBDIRS += backend_qtav
+}
\ No newline at end of file
diff --git a/buildconfig.pri b/buildconfig.pri
new file mode 100644 (file)
index 0000000..7972793
--- /dev/null
@@ -0,0 +1,6 @@
+DESTDIR = ../build
+CONFIG += c++14
+# Output Temporary files
+OBJECTS_DIR = workfiles/obj
+MOC_DIR = workfiles/moc
+RCC_DIR = workfiles/rcc
diff --git a/config.pri b/config.pri
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/core/aniplayer.exe.manifest b/core/aniplayer.exe.manifest
new file mode 100644 (file)
index 0000000..4533801
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
+    <description>AniPlayer</description>
+    <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
+        <security>
+            <requestedPrivileges>
+                <requestedExecutionLevel
+                    level="asInvoker"
+                    uiAccess="false"
+                />     
+            </requestedPrivileges>
+        </security>
+    </trustInfo>
+    <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> 
+        <application> 
+            <!-- Windows 10 --> 
+            <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+            <!-- Windows 8.1 -->
+            <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+            <!-- Windows Vista -->
+            <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/> 
+            <!-- Windows 7 -->
+            <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+            <!-- Windows 8 -->
+            <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+        </application> 
+    </compatibility>
+</assembly>
\ No newline at end of file
diff --git a/core/aniplayer.rc b/core/aniplayer.rc
new file mode 100644 (file)
index 0000000..bae35df
--- /dev/null
@@ -0,0 +1,38 @@
+1 TYPELIB "aniplayer.rc"
+IDI_ICON1              ICON            DISCARDABLE     "../resource/aniplayer-mikuru.ico"
+1 24 "aniplayer.exe.manifest"
+1 VERSIONINFO
+ FILEVERSION 3,0,0,0
+ PRODUCTVERSION 3,0,0,0
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x4L
+ FILETYPE 0x2L
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904e4"
+        BEGIN
+            VALUE "CompanyName", "aptx.org\0"
+            VALUE "FileDescription", "aniplayer\0"
+            VALUE "FileExtents", "xxx\0"
+            VALUE "FileOpenName", "Video Files (*.*)\0"
+            VALUE "FileVersion", "3, 0, 0, 0\0"
+            VALUE "InternalName", "aniplayer\0"
+                       VALUE "LegalCopyright", "Copyright Â© 2017 APTX\0"
+                       VALUE "MIMEType", "application/x-aniplayer\0"
+            VALUE "OriginalFilename", "aniplayer.exe\0"
+            VALUE "ProductName", "AniPlayer\0"
+            VALUE "ProductVersion", "3, 0, 0, 0\0"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1252
+    END
+END
diff --git a/core/backendpluginbase.cpp b/core/backendpluginbase.cpp
new file mode 100644 (file)
index 0000000..bdb08e1
--- /dev/null
@@ -0,0 +1,2 @@
+#include "backendpluginbase.h"
+
diff --git a/core/backendpluginbase.h b/core/backendpluginbase.h
new file mode 100644 (file)
index 0000000..58f1a68
--- /dev/null
@@ -0,0 +1,38 @@
+#ifndef BACKENDPLUGINBASE_H
+#define BACKENDPLUGINBASE_H
+
+#include "playerplugininterface.h"
+
+class QUrl;
+
+class BackendPluginBase {
+public:
+  using TimeStamp = double;
+  // Volume valid range is 0.0-1.0
+  using Volume = double;
+
+  virtual ~BackendPluginBase() = default;
+
+  virtual bool initialize(PlayerPluginInterface *) = 0;
+  virtual void deinitialize() = 0;
+
+  virtual VideoRendererBase *createRenderer(VideoUpdateInterface *) = 0;
+
+  virtual bool open(const QUrl &resource) = 0;
+
+  virtual void play() = 0;
+  virtual void pause() = 0;
+  virtual void stop() = 0;
+
+  virtual void seek(TimeStamp) = 0;
+
+  virtual void setVolume(Volume) = 0;
+};
+
+#define ANIPLAYER_BACKEND_DPLUGIN_INTERFACE_IID                                \
+  "org.aptx.aniplayer.BackendPluginInterface"
+
+#include <QObject>
+Q_DECLARE_INTERFACE(BackendPluginBase, ANIPLAYER_BACKEND_DPLUGIN_INTERFACE_IID)
+
+#endif // BACKENDPLUGINBASE_H
diff --git a/core/core.pri b/core/core.pri
new file mode 100644 (file)
index 0000000..1264d97
--- /dev/null
@@ -0,0 +1,2 @@
+INCLUDEPATH += $$PWD
+include(../buildconfig.pri)
diff --git a/core/core.pro b/core/core.pro
new file mode 100644 (file)
index 0000000..5e7a100
--- /dev/null
@@ -0,0 +1,36 @@
+TEMPLATE = app
+
+QT += widgets qml quick
+CONFIG += c++11
+
+TARGET = aniplayer
+
+include(core.pri)
+
+SOURCES += main.cpp \
+    player.cpp \
+    pluginmanager.cpp \
+    videoelement.cpp \
+    timeformatter.cpp \
+    instancemanager.cpp
+
+RESOURCES += qml.qrc
+
+# Additional import path used to resolve QML modules in Qt Creator's code model
+QML_IMPORT_PATH =
+
+HEADERS += \
+    player.h \
+    backendpluginbase.h \
+    pluginmanager.h \
+    videoelement.h \
+    playerplugininterface.h \
+    timeformatter.h \
+    instancemanager.h
+
+include(qtsingleapplication/qtsingleapplication.pri)
+
+win32 {
+    CONFIG -= embed_manifest_exe
+    RC_FILE += aniplayer.rc
+}
diff --git a/core/instancemanager.cpp b/core/instancemanager.cpp
new file mode 100644 (file)
index 0000000..74b2454
--- /dev/null
@@ -0,0 +1,64 @@
+#include "instancemanager.h"
+
+#include <QLoggingCategory>
+#include <QQmlApplicationEngine>
+#include <QQmlContext>
+
+#include "timeformatter.h"
+
+Q_LOGGING_CATEGORY(imCategory, "InstanceManager")
+
+InstanceManager::InstanceManager(QObject *parent) : QObject(parent) {
+  parser.addHelpOption();
+  parser.addVersionOption();
+  parser.addPositionalArgument("files", "Files to play", "[files...]");
+  parser.addOption(backendOption);
+  parser.addOption(uiOption);
+  parser.addOption(positionOption);
+}
+
+int InstanceManager::runInstance(const QCoreApplication &app) {
+  parser.process(app);
+
+  const auto positionalArgs = parser.positionalArguments();
+
+  QQmlApplicationEngine engine;
+
+  if (!positionalArgs.empty())
+    player.setNextSource(QUrl::fromUserInput(positionalArgs[0]));
+  qCDebug(imCategory, "Player Created");
+  TimeFormatter timeFormatter;
+  engine.rootContext()->setContextProperty("player", &player);
+  engine.rootContext()->setContextProperty("timeFormatter", &timeFormatter);
+  qCDebug(imCategory, "Player Added");
+  engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
+  qCDebug(imCategory, "QML engine loaded");
+  return app.exec();
+}
+
+void InstanceManager::handleSingleInstanceMessage(const QString &message) {
+  const QByteArray base64 = message.toLatin1();
+  const QByteArray arr = QByteArray::fromBase64(base64);
+  QDataStream stream(arr);
+  QStringList args;
+  stream >> args;
+
+  if (stream.status() != QDataStream::Ok) {
+    qCWarning(imCategory)
+        << "Failed to read serialized single instance message";
+    return;
+  }
+  if (!parser.parse(args)) {
+    qCWarning(imCategory)
+        << "Failed to parse arguments from single instance message";
+    return;
+  }
+
+  const auto positionalArgs = parser.positionalArguments();
+  if (positionalArgs.empty()) {
+    qCInfo(imCategory()) << "No new file to open";
+    return;
+  }
+
+  player.loadAndPlay(QUrl::fromUserInput(positionalArgs[0]));
+}
diff --git a/core/instancemanager.h b/core/instancemanager.h
new file mode 100644 (file)
index 0000000..e337139
--- /dev/null
@@ -0,0 +1,32 @@
+#ifndef INSTANCEMANAGER_H
+#define INSTANCEMANAGER_H
+
+#include <QObject>
+
+#include <QCoreApplication>
+#include <QCommandLineOption>
+#include <QCommandLineParser>
+
+#include "player.h"
+
+class InstanceManager : public QObject {
+  Q_OBJECT
+public:
+  explicit InstanceManager(QObject *parent = 0);
+
+  int runInstance(const QCoreApplication &app);
+public slots:
+  void handleSingleInstanceMessage(const QString &message);
+
+private:
+  const QCommandLineOption backendOption{"backend", "Use backend",
+                                         "name of backend plugin", "default"};
+  const QCommandLineOption uiOption{"ui", "Use ui", "name of ui", "default"};
+  const QCommandLineOption positionOption{"position",
+                                          "Start playback specific position",
+                                          "position in seconds", "0"};
+  QCommandLineParser parser;
+  Player player;
+};
+
+#endif // INSTANCEMANAGER_H
diff --git a/core/main.cpp b/core/main.cpp
new file mode 100644 (file)
index 0000000..0ee70f9
--- /dev/null
@@ -0,0 +1,44 @@
+#include <QtSingleApplication>
+
+#include <QCommandLineOption>
+#include <QCommandLineParser>
+#include <QDataStream>
+
+#include "instancemanager.h"
+#include "player.h"
+#include "videoelement.h"
+
+#include <QDebug>
+
+int main(int argc, char *argv[]) {
+  QtSingleApplication app(argc, argv);
+  app.setApplicationDisplayName("AniPlayer");
+  app.setApplicationName("AniPlayer");
+  app.setApplicationVersion("1.0");
+
+  InstanceManager im;
+  QObject::connect(&app, SIGNAL(messageReceived(QString)), &im,
+                   SLOT(handleSingleInstanceMessage(QString)));
+
+  if (app.isRunning()) {
+    QByteArray arr;
+    {
+      QDataStream stream{&arr, QIODevice::WriteOnly};
+      stream << app.arguments();
+    }
+    const auto base64 = arr.toBase64();
+    if (app.sendMessage(QString::fromUtf8(base64)))
+      return 0;
+    return 1;
+  }
+
+  qmlRegisterType<VideoElement>("org.aptx.aniplayer", 1, 0, "VideoElement");
+  Player::reqisterQmlTypes();
+
+  try {
+    return im.runInstance(app);
+  } catch (const std::exception &ex) {
+    qDebug("Exception: %s", ex.what());
+  }
+  return 1;
+}
diff --git a/core/player.cpp b/core/player.cpp
new file mode 100644 (file)
index 0000000..a1e4098
--- /dev/null
@@ -0,0 +1,218 @@
+#include "player.h"
+
+#include <QLoggingCategory>
+#include <QQmlApplicationEngine>
+
+Q_LOGGING_CATEGORY(playerCategory, "Player")
+
+Player::Player(QObject *parent) : QObject(parent) { loadBackend(); }
+
+BackendPluginBase *Player::backend() const { return m_backend; }
+
+bool Player::hasRenderer() const { return m_renderer != nullptr; }
+
+QUrl Player::currentSource() const { return m_currentSource; }
+
+QUrl Player::nextSource() const { return m_nextSource; }
+
+Player::PlayState Player::state() const { return m_state; }
+
+Player::Volume Player::volume() const { return m_volume; }
+
+bool Player::muted() const { return m_muted; }
+
+Player::AudioStreams Player::availableAudioStreams() const {
+  return m_availableAudioStreams;
+}
+
+Player::StreamIndex Player::currentAudioStream() const {
+  return m_currentAudioStream;
+}
+
+Player::StreamIndex Player::currentVideoStream() const {
+  return m_currentVideoStream;
+}
+
+Player::StreamIndex Player::currentSubtitleStream() const {
+  return m_currentSubtitleStream;
+}
+
+Player::VideoStreams Player::availableVideoStreams() const {
+  return m_availableVideoStreams;
+}
+
+Player::SubtitleStreams Player::availableSubtitleStreams() const {
+  return m_availableSubtitleStreams;
+}
+
+Player::TimeStamp Player::duration() const { return m_duration; }
+
+PlayerPluginInterface::TimeStamp Player::position() const { return m_position; }
+
+void Player::load(const QUrl &resource) {
+  if (canLoadVideoNow())
+    m_backend->open(resource);
+  else
+    setNextSource(resource);
+}
+
+void Player::loadAndPlay(const QUrl &resource) {
+  load(resource);
+  play();
+}
+
+void Player::setNextSource(QUrl nextSource) {
+  if (m_nextSource == nextSource)
+    return;
+
+  m_nextSource = nextSource;
+  emit nextSourceChanged(nextSource);
+}
+
+void Player::play() { m_backend->play(); }
+
+void Player::pause() { m_backend->pause(); }
+
+void Player::stop() { m_backend->stop(); }
+
+void Player::togglePlay() { PlayState::Playing == state() ? pause() : play(); }
+
+void Player::seek(Player::TimeStamp position) { m_backend->seek(position); }
+
+void Player::setVolume(Volume volume) {
+  volume = qBound(Volume{}, volume, MAX_VOLUME);
+  m_backend->setVolume(volume);
+}
+
+void Player::volumeUp(int byPercentagePoints) {
+  Volume realValue =
+      static_cast<Volume>(byPercentagePoints) / Volume{100.0} * MAX_VOLUME;
+  setVolume(volume() + realValue);
+}
+
+void Player::volumeDown(int byPercentagePoints) {
+  volumeUp(-byPercentagePoints);
+}
+
+void Player::setMuted(bool muted) {
+  if (m_muted == muted)
+    return;
+
+  m_muted = muted;
+  emit mutedChanged(muted);
+}
+
+void Player::toggleMuted() { setMuted(!muted()); }
+
+void Player::setCurrentAudioStream(Player::StreamIndex currentAudioStream) {
+  if (m_currentAudioStream == currentAudioStream)
+    return;
+
+  m_currentAudioStream = currentAudioStream;
+  emit currentAudioStreamChanged(currentAudioStream);
+}
+
+void Player::setCurrentVideoStream(Player::StreamIndex currentVideoStream) {
+  if (m_currentVideoStream == currentVideoStream)
+    return;
+
+  m_currentVideoStream = currentVideoStream;
+  emit currentVideoStreamChanged(currentVideoStream);
+}
+
+void Player::setCurrentSubtitleStream(
+    Player::StreamIndex currentSubtitleStream) {
+  if (m_currentSubtitleStream == currentSubtitleStream)
+    return;
+
+  m_currentSubtitleStream = currentSubtitleStream;
+  emit currentSubtitleStreamChanged(currentSubtitleStream);
+}
+
+void Player::backendReadyToPlay() {
+  m_backendReady = true;
+  if (canLoadVideoNow())
+    loadNextFile();
+}
+
+void Player::rendererSinkSet(VideoUpdateInterface *renderer) {
+  m_renderer = renderer;
+  m_rendererReady = false;
+}
+
+void Player::rendererReady() {
+  m_rendererReady = true;
+  if (canLoadVideoNow())
+    loadNextFile();
+}
+
+void Player::playStateChanged(PlayerPluginInterface::PlayState state) {
+  auto s = static_cast<PlayState>(state);
+  // if (currentSource().isEmpty()) s = PlayState::Stopped;
+  if (m_state == s)
+    return;
+  m_state = s;
+  qCDebug(playerCategory) << "Play state changed to" << s;
+  emit stateChanged(s);
+}
+
+void Player::playbackDurationChanged(
+    PlayerPluginInterface::TimeStamp duration) {
+  if (qFuzzyCompare(m_duration, duration))
+    return;
+
+  qCDebug(playerCategory) << "Duration changed to" << duration;
+  m_duration = duration;
+  emit durationChanged(duration);
+}
+
+void Player::playbackPositionChanged(
+    PlayerPluginInterface::TimeStamp position) {
+  if (qFuzzyCompare(m_position, position))
+    return;
+
+  qCDebug(playerCategory) << "Duration changed to" << position;
+  m_position = position;
+  emit positionChanged(position);
+}
+
+void Player::playbackVolumeChanged(Player::Volume volume) {
+  qCDebug(playerCategory) << "Volume changed to" << volume;
+  if (qFuzzyCompare(m_volume, volume))
+    return;
+
+  qCDebug(playerCategory) << "Volume changed to!!" << volume;
+  m_volume = volume;
+  emit volumeChanged(volume);
+}
+
+void Player::streamsChanged() {}
+
+void Player::reqisterQmlTypes() {
+  qRegisterMetaType<TimeStamp>("TimeStamp");
+  qRegisterMetaType<StreamIndex>("StreamIndex");
+  qRegisterMetaType<Volume>("Volume");
+  qmlRegisterType<Player>("org.aptx.aniplayer", 1, 0, "Player");
+}
+
+void Player::loadBackend() {
+  m_pluginManager.setPluginDirectory("backendplugins");
+  m_pluginManager.setPluginPrefix("backend");
+  m_pluginManager.loadDefaultPlugin();
+  m_backend = m_pluginManager.instance<BackendPluginBase>();
+  if (!m_backend)
+    throw std::runtime_error{std::string("Failed to load backend: ") +
+                             qPrintable(m_pluginManager.errorString())};
+  m_backend->initialize(this);
+  qCDebug(playerCategory) << "Loaded backend" << m_backend;
+}
+
+bool Player::canLoadVideoNow() const {
+  return m_backendReady && m_renderer && m_rendererReady;
+}
+
+void Player::loadNextFile() {
+  if (!m_nextSource.isEmpty())
+    loadAndPlay(m_nextSource);
+  setNextSource(QUrl{});
+}
diff --git a/core/player.h b/core/player.h
new file mode 100644 (file)
index 0000000..51cc43e
--- /dev/null
@@ -0,0 +1,176 @@
+#ifndef PLAYER_H
+#define PLAYER_H
+
+#include <QList>
+#include <QObject>
+#include <QQmlEngine>
+#include <QUrl>
+
+#include "backendpluginbase.h"
+#include "pluginmanager.h"
+
+class Player : public QObject,
+               public PlayerPluginInterface,
+               public PlayerRendererInterface {
+  Q_OBJECT
+  Q_PROPERTY(QUrl currentSource READ currentSource WRITE load NOTIFY
+                 currentSourceChanged)
+  Q_PROPERTY(QUrl nextSource READ nextSource WRITE setNextSource NOTIFY
+                 nextSourceChanged)
+
+  Q_PROPERTY(Player::TimeStamp duration READ duration NOTIFY durationChanged)
+  Q_PROPERTY(Player::TimeStamp position READ position WRITE seek NOTIFY
+                 positionChanged)
+
+  Q_PROPERTY(Player::PlayState state READ state NOTIFY stateChanged)
+  Q_PROPERTY(double volume READ volume WRITE setVolume NOTIFY volumeChanged)
+  Q_PROPERTY(bool muted READ muted WRITE setMuted NOTIFY mutedChanged)
+
+  Q_PROPERTY(Player::StreamIndex currentAudioStream READ currentAudioStream
+                 WRITE setCurrentAudioStream NOTIFY currentAudioStreamChanged)
+  Q_PROPERTY(Player::StreamIndex currentVideoStream READ currentVideoStream
+                 WRITE setCurrentVideoStream NOTIFY currentVideoStreamChanged)
+  Q_PROPERTY(
+      Player::StreamIndex currentSubtitleStream READ currentSubtitleStream WRITE
+          setCurrentSubtitleStream NOTIFY currentSubtitleStreamChanged)
+
+  Q_PROPERTY(Player::AudioStreams availableAudioStreams READ
+                 availableAudioStreams NOTIFY availableAudioStreamsChanged)
+  Q_PROPERTY(Player::VideoStreams availableVideoStreams READ
+                 availableVideoStreams NOTIFY availableVideoStreamsChanged)
+  Q_PROPERTY(
+      Player::SubtitleStreams availableSubtitleStreams READ
+          availableSubtitleStreams NOTIFY availableSubtitleStreamsChanged)
+
+public:
+  using StreamIndex = int;
+  using AudioStreams = QList<QString>;
+  using VideoStreams = QList<QString>;
+  using SubtitleStreams = QList<QString>;
+  using Volume = double;
+  using TimeStamp = PlayerPluginInterface::TimeStamp;
+
+  static const constexpr Volume MAX_VOLUME = Volume{1.0};
+
+  explicit Player(QObject *parent = 0);
+  //    ~Player() /*override*/;
+
+  enum class PlayState {
+    Stopped = static_cast<int>(PlayerPluginInterface::PlayState::Stopped),
+    Paused = static_cast<int>(PlayerPluginInterface::PlayState::Paused),
+    Playing = static_cast<int>(PlayerPluginInterface::PlayState::Playing),
+  };
+  Q_ENUM(PlayState)
+
+  BackendPluginBase *backend() const;
+  bool hasRenderer() const;
+
+  QUrl currentSource() const;
+  QUrl nextSource() const;
+
+  PlayState state() const;
+  Volume volume() const;
+  bool muted() const;
+
+  StreamIndex currentAudioStream() const;
+  StreamIndex currentVideoStream() const;
+  StreamIndex currentSubtitleStream() const;
+
+  AudioStreams availableAudioStreams() const;
+  VideoStreams availableVideoStreams() const;
+  SubtitleStreams availableSubtitleStreams() const;
+
+  Player::TimeStamp duration() const;
+  Player::TimeStamp position() const;
+
+signals:
+  void stateChanged(PlayState state);
+  void volumeChanged(Volume volume);
+  void mutedChanged(bool muted);
+
+  void availableAudioStreamsChanged(AudioStreams availableAudioStreams);
+  void availableVideoStreamsChanged(VideoStreams availableVideoStreams);
+  void
+  availableSubtitleStreamsChanged(SubtitleStreams availableSubtitleStreams);
+
+  void currentAudioStreamChanged(int currentAudioStream);
+  void currentVideoStreamChanged(StreamIndex currentVideoStream);
+  void currentSubtitleStreamChanged(StreamIndex currentSubtitleStream);
+
+  void currentSourceChanged(QUrl currentSource);
+  void nextSourceChanged(QUrl nextSource);
+
+  void durationChanged(Player::TimeStamp duration);
+  void positionChanged(Player::TimeStamp position);
+
+public slots:
+  // Basic Play state
+  void load(const QUrl &resource);
+  void loadAndPlay(const QUrl &resource);
+  void setNextSource(QUrl nextSource);
+
+  void play();
+  void pause();
+  void stop();
+
+  void togglePlay();
+
+  void seek(Player::TimeStamp position);
+
+  // Volume
+  void setVolume(Volume volume);
+  void volumeUp(int byPercentagePoints = 5);
+  void volumeDown(int byPercentagePoints = 5);
+
+  void setMuted(bool muted);
+  void toggleMuted();
+
+  // Streams
+  void setCurrentAudioStream(StreamIndex currentAudioStream);
+  void setCurrentVideoStream(StreamIndex currentVideoStream);
+  void setCurrentSubtitleStream(StreamIndex currentSubtitleStream);
+
+protected:
+  void backendReadyToPlay() override;
+  void rendererSinkSet(VideoUpdateInterface *) override;
+  void rendererReady() override;
+  void playStateChanged(PlayerPluginInterface::PlayState) override;
+  void playbackDurationChanged(TimeStamp) override;
+  void playbackPositionChanged(TimeStamp) override;
+  void playbackVolumeChanged(Volume) override;
+  void streamsChanged() override;
+
+public:
+  static void reqisterQmlTypes();
+
+private:
+  void loadBackend();
+  bool canLoadVideoNow() const;
+  void loadNextFile();
+
+  PluginManager m_pluginManager;
+  BackendPluginBase *m_backend = nullptr;
+  QUrl m_currentSource;
+  PlayState m_state = PlayState::Stopped;
+  Volume m_volume = MAX_VOLUME;
+  AudioStreams m_availableAudioStreams;
+  StreamIndex m_currentAudioStream = StreamIndex{};
+  StreamIndex m_currentVideoStream = StreamIndex{};
+  StreamIndex m_currentSubtitleStream = StreamIndex{};
+  VideoStreams m_availableVideoStreams;
+  SubtitleStreams m_availableSubtitleStreams;
+  Player::TimeStamp m_duration = 0;
+  Player::TimeStamp m_position = 0;
+  QUrl m_nextSource;
+  VideoUpdateInterface *m_renderer = nullptr;
+  bool m_muted = false;
+  bool m_backendReady = false;
+  bool m_rendererReady = false;
+};
+
+Q_DECLARE_METATYPE(Player::PlayState)
+Q_DECLARE_METATYPE(Player::StreamIndex)
+Q_DECLARE_METATYPE(Player::TimeStamp)
+// Q_DECLARE_METATYPE(Player::Volume)
+
+#endif // PLAYER_H
diff --git a/core/playerplugininterface.cpp b/core/playerplugininterface.cpp
new file mode 100644 (file)
index 0000000..106c287
--- /dev/null
@@ -0,0 +1 @@
+#include "playerplugininterface.h"
diff --git a/core/playerplugininterface.h b/core/playerplugininterface.h
new file mode 100644 (file)
index 0000000..a4b62c9
--- /dev/null
@@ -0,0 +1,64 @@
+#ifndef PLAYERPLUGININTERFACE_H
+#define PLAYERPLUGININTERFACE_H
+
+#include <QtGlobal>
+
+class QOpenGLFramebufferObject;
+
+class VideoUpdateInterface {
+public:
+  virtual ~VideoUpdateInterface() = default;
+
+  virtual void videoUpdated() = 0;
+};
+
+class PlayerPluginInterface {
+public:
+  using TimeStamp = double;
+  using Volume = double;
+
+  enum class PlayState { Stopped, Paused, Playing };
+  /*
+   *    .-----.
+   *    |     | Error
+   *    v     |
+   * Stopped -'<--+<-------.
+   *    |         ^        |
+   *    | Load    | Error  |
+   *    v         |        |
+   * Paused<------+        | File End
+   *    |         ^        |
+   *    | Play    | Pause  |
+   *    v         |        |
+   * Playing------+--------'
+   */
+
+  virtual ~PlayerPluginInterface() = default;
+
+  virtual void backendReadyToPlay() = 0;
+
+  virtual void playStateChanged(PlayState) = 0;
+  virtual void playbackDurationChanged(TimeStamp) = 0;
+  virtual void playbackPositionChanged(TimeStamp) = 0;
+  virtual void playbackVolumeChanged(Volume) = 0;
+
+  virtual void streamsChanged() = 0;
+};
+
+class PlayerRendererInterface {
+public:
+  virtual ~PlayerRendererInterface() = default;
+  virtual void rendererSinkSet(VideoUpdateInterface *) = 0;
+  virtual void rendererReady() = 0;
+};
+
+class VideoRendererBase {
+public:
+  VideoRendererBase() = default;
+  VideoRendererBase(const VideoRendererBase &) = delete;
+  VideoRendererBase &operator=(const VideoRendererBase &) = delete;
+  virtual ~VideoRendererBase() = default;
+  virtual void render(QOpenGLFramebufferObject *) = 0;
+};
+
+#endif // PLAYERPLUGININTERFACE_H
diff --git a/core/pluginmanager.cpp b/core/pluginmanager.cpp
new file mode 100644 (file)
index 0000000..cec097a
--- /dev/null
@@ -0,0 +1,50 @@
+#include "pluginmanager.h"
+
+PluginManager::PluginManager() {}
+
+QString PluginManager::errorString() const { return m_loader.errorString(); }
+
+QString PluginManager::pluginDirectory() const { return m_pluginDirectory; }
+
+QString PluginManager::pluginPrefix() const { return m_pluginPrefix; }
+
+void PluginManager::setPluginDirectory(QString pluginDirectory) {
+  if (m_pluginDirectory == pluginDirectory)
+    return;
+
+  m_pluginDirectory = pluginDirectory;
+  emit pluginDirectoryChanged(pluginDirectory);
+}
+
+void PluginManager::setPluginPrefix(QString pluginPrefix) {
+  if (m_pluginPrefix == pluginPrefix)
+    return;
+
+  m_pluginPrefix = pluginPrefix;
+  emit pluginPrefixChanged(pluginPrefix);
+}
+
+bool PluginManager::load(const QString &plugin) {
+  if (m_loader.isLoaded())
+    if (!m_loader.unload())
+      return false;
+
+  QString pluginPath = QString{"%1/%2_%3"}
+                           .arg(m_pluginDirectory)
+                           .arg(m_pluginPrefix)
+                           .arg(plugin);
+  m_loader.setFileName(pluginPath);
+
+  m_loader.load();
+
+  return m_loader.isLoaded();
+}
+
+QObject *PluginManager::qObjectInstance() {
+  if (!m_loader.isLoaded())
+    return nullptr;
+
+  return m_loader.instance();
+}
+
+void PluginManager::loadDefaultPlugin() { load("mpv"); }
diff --git a/core/pluginmanager.h b/core/pluginmanager.h
new file mode 100644 (file)
index 0000000..245e7d5
--- /dev/null
@@ -0,0 +1,44 @@
+#ifndef PLUGINMANAGER_H
+#define PLUGINMANAGER_H
+
+#include <QObject>
+#include <QPluginLoader>
+
+class PluginManager : public QObject {
+  Q_OBJECT
+  Q_PROPERTY(QString pluginDirectory READ pluginDirectory WRITE
+                 setPluginDirectory NOTIFY pluginDirectoryChanged)
+  Q_PROPERTY(QString pluginPrefix READ pluginPrefix WRITE setPluginPrefix NOTIFY
+                 pluginPrefixChanged)
+public:
+  PluginManager();
+
+  QString errorString() const;
+
+  QString pluginDirectory() const;
+  QString pluginPrefix() const;
+
+  template <typename Interface> Interface *instance() {
+    return qobject_cast<Interface *>(qObjectInstance());
+  }
+
+public slots:
+  void setPluginDirectory(QString pluginDirectory);
+  void setPluginPrefix(QString pluginPrefix);
+
+  bool load(const QString &plugin);
+  QObject *qObjectInstance();
+
+  void loadDefaultPlugin();
+
+signals:
+  void pluginDirectoryChanged(QString pluginDirectory);
+  void pluginPrefixChanged(QString pluginPrefix);
+
+private:
+  QString m_pluginDirectory;
+  QString m_pluginPrefix;
+  QPluginLoader m_loader;
+};
+
+#endif // PLUGINMANAGER_H
diff --git a/core/qml.qrc b/core/qml.qrc
new file mode 100644 (file)
index 0000000..0e27bae
--- /dev/null
@@ -0,0 +1,10 @@
+<RCC>
+    <qresource prefix="/">
+        <file>qml/main.qml</file>
+        <file>qml/OpenButton.qml</file>
+        <file>qml/PlayerControls.qml</file>
+        <file>qml/SeekSlider.qml</file>
+        <file>qml/PlaybackPosition.qml</file>
+        <file>qml/VolumeSlider.qml</file>
+    </qresource>
+</RCC>
diff --git a/core/qml/Button.qml b/core/qml/Button.qml
new file mode 100644 (file)
index 0000000..1cb984d
--- /dev/null
@@ -0,0 +1,23 @@
+import QtQuick 2.0
+
+Item {
+    id: buttonRoot
+    property alias text: label.text
+
+    signal clicked
+
+    width: 80
+    height: label.lineHeight
+
+    Text {
+        id: label
+        color: "red"
+        font.pointSize: 24
+        anchors.fill: parent
+    }
+    MouseArea {
+        id: ma
+        anchors.fill: parent
+        onClicked: buttonRoot.clicked()
+    }
+}
diff --git a/core/qml/OpenButton.qml b/core/qml/OpenButton.qml
new file mode 100644 (file)
index 0000000..4e2f967
--- /dev/null
@@ -0,0 +1,24 @@
+import QtQuick 2.0
+import QtQuick.Dialogs 1.2
+import QtQuick.Controls 1.4
+
+Button {
+    signal openFileRequested(string file)
+
+    text: "Open"
+
+    FileDialog {
+        id: fileDialog
+        title: "Play file"
+        onAccepted: {
+            openFileRequested(fileUrl)
+        }
+    }
+    onClicked: {
+        open();
+    }
+
+    function open() {
+        fileDialog.open();
+    }
+}
diff --git a/core/qml/PlaybackPosition.qml b/core/qml/PlaybackPosition.qml
new file mode 100644 (file)
index 0000000..c9db341
--- /dev/null
@@ -0,0 +1,17 @@
+import QtQuick 2.0
+import QtQml 2.2
+
+Item {
+    property double duration: 0
+    property double position: 0
+Text {
+    text: {
+        if (!timeFormatter) return "";
+        var durationStr = timeFormatter.format(duration);
+        var positionStr = timeFormatter.format(position);
+        return positionStr + " / " + durationStr;
+    }
+    color: "red"
+    anchors.centerIn: parent
+}
+}
diff --git a/core/qml/PlayerControls.qml b/core/qml/PlayerControls.qml
new file mode 100644 (file)
index 0000000..ceabcf8
--- /dev/null
@@ -0,0 +1,111 @@
+import QtQuick 2.0
+import QtQuick.Controls 1.4
+import QtQuick.Window 2.2
+import org.aptx.aniplayer 1.0
+
+Row {
+    property Player controlledPlayer: null
+    property Window controlledWindow: null
+
+    enabled: controlledPlayer !== null && controlledWindow !== null
+
+    function toggleFullScreen() {
+        fullscreenButton.checked = !fullscreenButton.checked;
+    }
+
+    OpenButton {
+        id: openButton
+        text: "Open"
+        onOpenFileRequested: {
+            controlledPlayer.loadAndPlay(file)
+        }
+    }
+    Button {
+        id: togglePlayButton
+        text: "Play"
+        onClicked: {
+            console.log("STATE")
+            console.log(controlledPlayer.state)
+            console.log(Player.Stopped)
+            if (controlledPlayer.state === Player.Stopped) {
+                openButton.open()
+            } else {
+                controlledPlayer.togglePlay()
+            }
+        }
+        state: {
+            if (!controlledPlayer) return "";
+            return controlledPlayer.state === Player.Playing ? "pause" : ""
+        }
+        states: [
+            State {
+                name: "pause"
+                PropertyChanges {
+                    target: togglePlayButton
+                    text: "Pause"
+                }
+            }
+        ]
+    }
+    Button {
+        id: fullscreenButton
+        text: "FS"
+        checkable: true
+        onCheckedChanged: {
+            if (!checked) {
+                console.log("show normal")
+                controlledWindow.showNormal();
+            } else {
+                console.log("show fs")
+                controlledWindow.showFullScreen();
+            }
+        }
+    }
+    Button {
+        id: stayOnTopButton
+        text: "OnTop"
+        enabled: !controlledWindow.isFullScreen()
+        checkable: true
+        onCheckedChanged: {
+            if (!checked) {
+                controlledWindow.flags = controlledWindow.flags & ~Qt.WindowStaysOnTopHint
+            } else {
+                controlledWindow.flags = controlledWindow.flags | Qt.WindowStaysOnTopHint
+            }
+        }
+    }
+    Button {
+        id: framelessButton
+        text: "Frameless"
+        enabled: !controlledWindow.isFullScreen()
+        checkable: true
+        onCheckedChanged: {
+            if (!checked) {
+                controlledWindow.flags = controlledWindow.flags & ~Qt.FramelessWindowHint
+            } else {
+                controlledWindow.flags = controlledWindow.flags | Qt.FramelessWindowHint
+            }
+        }
+    }
+    SeekSlider {
+        width: 200
+        height: fullscreenButton.height
+        id: ss
+        duration: controlledPlayer ? controlledPlayer.duration : 0
+        position: controlledPlayer ? controlledPlayer.position : 0
+        onSeekRequested: controlledPlayer.seek(position)
+    }
+    PlaybackPosition {
+        height: fullscreenButton.height
+        width: 200
+        duration: controlledPlayer ? controlledPlayer.duration : 0
+        position: controlledPlayer ? controlledPlayer.position : 0
+    }
+    VolumeSlider {
+        height: fullscreenButton.height
+        width: 100
+        volume: controlledPlayer ? controlledPlayer.volume : 1
+        onVolumeChangeRequested: controlledPlayer.setVolume(volume)
+    }
+}
+
diff --git a/core/qml/SeekSlider.qml b/core/qml/SeekSlider.qml
new file mode 100644 (file)
index 0000000..e6a392e
--- /dev/null
@@ -0,0 +1,41 @@
+import QtQuick 2.0
+import org.aptx.aniplayer 1.0
+
+Item {
+    property double duration: 0
+    property double position: 0
+
+    signal seekRequested(double position)
+
+    enabled: duration > 1
+
+    Rectangle {
+        id: watched
+        color: "#00BB00"
+        anchors.left: parent.left
+        anchors.top: parent.top
+        anchors.bottom: parent.bottom
+        width: {
+            return duration ? position / duration * parent.width : 0;
+        }
+    }
+
+    Rectangle {
+        id: unwatched
+        color: "#FF0000"
+        anchors.left: watched.right
+        anchors.right: parent.right
+        anchors.top: parent.top
+        anchors.bottom: parent.bottom
+    }
+
+    MouseArea {
+        id: ma
+        anchors.fill: parent
+        onClicked: {
+            var pos = mouseX / parent.width * duration;
+            console.log("seek clicked " + pos)
+            seekRequested(pos);
+        }
+    }
+}
diff --git a/core/qml/VolumeSlider.qml b/core/qml/VolumeSlider.qml
new file mode 100644 (file)
index 0000000..5e68d5f
--- /dev/null
@@ -0,0 +1,37 @@
+import QtQuick 2.0
+
+Item {
+    signal volumeChangeRequested(double volume)
+
+    property double maxVolume: 1.0
+    property double volume: 0.0
+
+    Rectangle {
+        id: heard
+        color: "#00B000"
+        anchors.left: parent.left
+        anchors.top: parent.top
+        anchors.bottom: parent.bottom
+        width: {
+            return maxVolume ? volume / maxVolume * parent.width : 0;
+        }
+    }
+
+    Rectangle {
+        id: unheard
+        color: "#F00000"
+        anchors.left: heard.right
+        anchors.right: parent.right
+        anchors.top: parent.top
+        anchors.bottom: parent.bottom
+    }
+
+    MouseArea {
+        id: ma
+        anchors.fill: parent
+        onClicked: {
+            var vol = mouseX / parent.width * maxVolume;
+            volumeChangeRequested(vol);
+        }
+    }
+}
diff --git a/core/qml/main.qml b/core/qml/main.qml
new file mode 100644 (file)
index 0000000..b52ec00
--- /dev/null
@@ -0,0 +1,96 @@
+import QtQuick.Window 2.2
+import org.aptx.aniplayer 1.0
+import QtQuick 2.7
+
+Window {
+    id: window
+    visible: true
+    width: 300
+    height: 300
+    property int clicks: 0
+    //property Visibility previousVisibility: Window.Normal
+
+    function isFullScreen() {
+        return visibility === Window.FullScreen
+    }
+
+    VideoElement {
+        source: player
+        anchors.fill: parent
+    }
+
+    MouseArea {
+        anchors.fill: parent
+        acceptedButtons: Qt.AllButtons
+
+        property int origX
+        property int origY
+        property bool isBeingDragged: false
+        property bool ignoreOnClick: false
+
+        onWheel: {
+            if (wheel.angleDelta.y > 0)
+                player.volumeUp(5);
+            else
+                player.volumeDown(5);
+        }
+
+        onPressed: {
+            console.log("isBeingDragged", isBeingDragged);
+            if (isBeingDragged)
+                return;
+            console.log("setting orig");
+            origX = mouseX
+            origY = mouseY
+        }
+
+        onPositionChanged: {
+            if (window.isFullScreen()) return;
+            if (!(mouse.buttons & Qt.LeftButton))
+                return;
+            window.x += mouseX - origX
+            window.y += mouseY - origY
+            isBeingDragged = true;
+        }
+
+        onReleased: {
+            console.log("mouse.buttons", mouse.buttons,  Qt.NoButton);
+            if ((mouse.buttons != Qt.NoButton)) return;
+            if (isBeingDragged) {
+                isBeingDragged = false;
+                ignoreOnClick = true;
+            }
+        }
+
+        onClicked: {
+            if (isBeingDragged) {
+                return;
+            }
+            if (ignoreOnClick) {
+                ignoreOnClick = false;
+                return;
+            }
+            console.log(mouse.button);
+            if (mouse.button === Qt.LeftButton)
+                controls.toggleVisible();
+            else if (mouse.button === Qt.RightButton)
+                controls.toggleFullScreen();
+            else if (mouse.button === Qt.MiddleButton)
+                player.togglePlay();
+        }
+        cursorShape: !controls.visible && window.visibility === Window.FullScreen ? Qt.BlankCursor : Qt.ArrowCursor;
+    }
+
+    PlayerControls {
+        id: controls
+        controlledPlayer: player
+        controlledWindow: window
+        anchors.bottom: parent.bottom
+        anchors.left: parent.left
+        anchors.right: parent.right
+
+        function toggleVisible() {
+            visible = !visible;
+        }
+    }
+}
diff --git a/core/qtsingleapplication/QtLockedFile b/core/qtsingleapplication/QtLockedFile
new file mode 100644 (file)
index 0000000..16b48ba
--- /dev/null
@@ -0,0 +1 @@
+#include "qtlockedfile.h"
diff --git a/core/qtsingleapplication/QtSingleApplication b/core/qtsingleapplication/QtSingleApplication
new file mode 100644 (file)
index 0000000..d111bf7
--- /dev/null
@@ -0,0 +1 @@
+#include "qtsingleapplication.h"
diff --git a/core/qtsingleapplication/qtlocalpeer.cpp b/core/qtsingleapplication/qtlocalpeer.cpp
new file mode 100644 (file)
index 0000000..e4cf804
--- /dev/null
@@ -0,0 +1,203 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of a Qt Solutions component.
+**
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**   * Redistributions of source code must retain the above copyright
+**     notice, this list of conditions and the following disclaimer.
+**   * Redistributions in binary form must reproduce the above copyright
+**     notice, this list of conditions and the following disclaimer in
+**     the documentation and/or other materials provided with the
+**     distribution.
+**   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+**     the names of its contributors may be used to endorse or promote
+**     products derived from this software without specific prior written
+**     permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+****************************************************************************/
+
+
+#include "qtlocalpeer.h"
+#include <QCoreApplication>
+#include <QTime>
+#include <QDataStream>
+
+#if defined(Q_OS_WIN)
+#include <QLibrary>
+#include <qt_windows.h>
+typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*);
+static PProcessIdToSessionId pProcessIdToSessionId = 0;
+#endif
+#if defined(Q_OS_UNIX)
+#include <time.h>
+#endif
+
+namespace QtLP_Private {
+#include "qtlockedfile.cpp"
+#if defined(Q_OS_WIN)
+#include "qtlockedfile_win.cpp"
+#else
+#include "qtlockedfile_unix.cpp"
+#endif
+}
+
+const char* QtLocalPeer::ack = "ack";
+
+QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId)
+    : QObject(parent), id(appId)
+{
+    QString prefix = id;
+    if (id.isEmpty()) {
+        id = QCoreApplication::applicationFilePath();
+#if defined(Q_OS_WIN)
+        id = id.toLower();
+#endif
+        prefix = id.section(QLatin1Char('/'), -1);
+    }
+    prefix.remove(QRegExp("[^a-zA-Z]"));
+    prefix.truncate(6);
+
+    QByteArray idc = id.toUtf8();
+    quint16 idNum = qChecksum(idc.constData(), idc.size());
+    socketName = QLatin1String("qtsingleapp-") + prefix
+                 + QLatin1Char('-') + QString::number(idNum, 16);
+
+#if defined(Q_OS_WIN)
+    if (!pProcessIdToSessionId) {
+        QLibrary lib("kernel32");
+        pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId");
+    }
+    if (pProcessIdToSessionId) {
+        DWORD sessionId = 0;
+        pProcessIdToSessionId(GetCurrentProcessId(), &sessionId);
+        socketName += QLatin1Char('-') + QString::number(sessionId, 16);
+    }
+#else
+    {
+        using namespace QtLP_Private;
+        socketName += QLatin1Char('-') + QString::number(getuid(), 16);
+    }
+#endif
+
+    server = new QLocalServer(this);
+    QString lockName = QDir(QDir::tempPath()).absolutePath()
+                       + QLatin1Char('/') + socketName
+                       + QLatin1String("-lockfile");
+    lockFile.setFileName(lockName);
+    lockFile.open(QIODevice::ReadWrite);
+}
+
+
+
+bool QtLocalPeer::isClient()
+{
+    if (lockFile.isLocked())
+        return false;
+
+    if (!lockFile.lock(QtLP_Private::QtLockedFile::WriteLock, false))
+        return true;
+
+    bool res = server->listen(socketName);
+#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(4,5,0))
+    // ### Workaround
+    if (!res && server->serverError() == QAbstractSocket::AddressInUseError) {
+        QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName);
+        res = server->listen(socketName);
+    }
+#endif
+    if (!res)
+        qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString()));
+    QObject::connect(server, SIGNAL(newConnection()), SLOT(receiveConnection()));
+    return false;
+}
+
+
+bool QtLocalPeer::sendMessage(const QString &message, int timeout)
+{
+    if (!isClient())
+        return false;
+
+    QLocalSocket socket;
+    bool connOk = false;
+    for(int i = 0; i < 2; i++) {
+        // Try twice, in case the other instance is just starting up
+        socket.connectToServer(socketName);
+        connOk = socket.waitForConnected(timeout/2);
+        if (connOk || i)
+            break;
+        int ms = 250;
+#if defined(Q_OS_WIN)
+        Sleep(DWORD(ms));
+#else
+        struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 };
+        nanosleep(&ts, NULL);
+#endif
+    }
+    if (!connOk)
+        return false;
+
+    QByteArray uMsg(message.toUtf8());
+    QDataStream ds(&socket);
+    ds.writeBytes(uMsg.constData(), uMsg.size());
+    bool res = socket.waitForBytesWritten(timeout);
+    if (res) {
+        res &= socket.waitForReadyRead(timeout);   // wait for ack
+        if (res)
+            res &= (socket.read(qstrlen(ack)) == ack);
+    }
+    return res;
+}
+
+
+void QtLocalPeer::receiveConnection()
+{
+    QLocalSocket* socket = server->nextPendingConnection();
+    if (!socket)
+        return;
+
+    while (socket->bytesAvailable() < (int)sizeof(quint32))
+        socket->waitForReadyRead();
+    QDataStream ds(socket);
+    QByteArray uMsg;
+    quint32 remaining = 0;
+    ds >> remaining;
+    uMsg.resize(remaining);
+    int got = 0;
+    char* uMsgBuf = uMsg.data();
+    do {
+        got = ds.readRawData(uMsgBuf, remaining);
+        remaining -= got;
+        uMsgBuf += got;
+    } while (remaining && got >= 0 && socket->waitForReadyRead(2000));
+    if (got < 0) {
+        qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData());
+        delete socket;
+        return;
+    }
+    QString message(QString::fromUtf8(uMsg));
+    socket->write(ack, qstrlen(ack));
+    socket->waitForBytesWritten(1000);
+    delete socket;
+    emit messageReceived(message); //### (might take a long time to return)
+}
diff --git a/core/qtsingleapplication/qtlocalpeer.h b/core/qtsingleapplication/qtlocalpeer.h
new file mode 100644 (file)
index 0000000..7b3fa81
--- /dev/null
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of a Qt Solutions component.
+**
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**   * Redistributions of source code must retain the above copyright
+**     notice, this list of conditions and the following disclaimer.
+**   * Redistributions in binary form must reproduce the above copyright
+**     notice, this list of conditions and the following disclaimer in
+**     the documentation and/or other materials provided with the
+**     distribution.
+**   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+**     the names of its contributors may be used to endorse or promote
+**     products derived from this software without specific prior written
+**     permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+****************************************************************************/
+
+#ifndef QTLOCALPEER_H
+#define QTLOCALPEER_H
+
+#include <QLocalServer>
+#include <QLocalSocket>
+#include <QDir>
+
+#include "qtlockedfile.h"
+
+class QtLocalPeer : public QObject
+{
+    Q_OBJECT
+
+public:
+    QtLocalPeer(QObject *parent = 0, const QString &appId = QString());
+    bool isClient();
+    bool sendMessage(const QString &message, int timeout);
+    QString applicationId() const
+        { return id; }
+
+Q_SIGNALS:
+    void messageReceived(const QString &message);
+
+protected Q_SLOTS:
+    void receiveConnection();
+
+protected:
+    QString id;
+    QString socketName;
+    QLocalServer* server;
+    QtLP_Private::QtLockedFile lockFile;
+
+private:
+    static const char* ack;
+};
+
+#endif // QTLOCALPEER_H
diff --git a/core/qtsingleapplication/qtlockedfile.cpp b/core/qtsingleapplication/qtlockedfile.cpp
new file mode 100644 (file)
index 0000000..7fdf487
--- /dev/null
@@ -0,0 +1,192 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of a Qt Solutions component.
+**
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**   * Redistributions of source code must retain the above copyright
+**     notice, this list of conditions and the following disclaimer.
+**   * Redistributions in binary form must reproduce the above copyright
+**     notice, this list of conditions and the following disclaimer in
+**     the documentation and/or other materials provided with the
+**     distribution.
+**   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+**     the names of its contributors may be used to endorse or promote
+**     products derived from this software without specific prior written
+**     permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+****************************************************************************/
+
+#include "qtlockedfile.h"
+
+/*!
+       \class QtLockedFile
+
+       \brief The QtLockedFile class extends QFile with advisory locking
+       functions.
+
+       A file may be locked in read or write mode. Multiple instances of
+       \e QtLockedFile, created in multiple processes running on the same
+       machine, may have a file locked in read mode. Exactly one instance
+       may have it locked in write mode. A read and a write lock cannot
+       exist simultaneously on the same file.
+
+       The file locks are advisory. This means that nothing prevents
+       another process from manipulating a locked file using QFile or
+       file system functions offered by the OS. Serialization is only
+       guaranteed if all processes that access the file use
+       QLockedFile. Also, while holding a lock on a file, a process
+       must not open the same file again (through any API), or locks
+       can be unexpectedly lost.
+
+       The lock provided by an instance of \e QtLockedFile is released
+       whenever the program terminates. This is true even when the
+       program crashes and no destructors are called.
+*/
+
+/*! \enum QtLockedFile::LockMode
+
+       This enum describes the available lock modes.
+
+       \value ReadLock A read lock.
+       \value WriteLock A write lock.
+       \value NoLock Neither a read lock nor a write lock.
+*/
+
+/*!
+       Constructs an unlocked \e QtLockedFile object. This constructor
+       behaves in the same way as \e QFile::QFile().
+
+       \sa QFile::QFile()
+*/
+QtLockedFile::QtLockedFile()
+       : QFile()
+{
+#ifdef Q_OS_WIN
+       wmutex = 0;
+       rmutex = 0;
+#endif
+       m_lock_mode = NoLock;
+}
+
+/*!
+       Constructs an unlocked QtLockedFile object with file \a name. This
+       constructor behaves in the same way as \e QFile::QFile(const
+       QString&).
+
+       \sa QFile::QFile()
+*/
+QtLockedFile::QtLockedFile(const QString &name)
+       : QFile(name)
+{
+#ifdef Q_OS_WIN
+       wmutex = 0;
+       rmutex = 0;
+#endif
+       m_lock_mode = NoLock;
+}
+
+/*!
+  Opens the file in OpenMode \a mode.
+
+  This is identical to QFile::open(), with the one exception that the
+  Truncate mode flag is disallowed. Truncation would conflict with the
+  advisory file locking, since the file would be modified before the
+  write lock is obtained. If truncation is required, use resize(0)
+  after obtaining the write lock.
+
+  Returns true if successful; otherwise false.
+
+  \sa QFile::open(), QFile::resize()
+*/
+bool QtLockedFile::open(OpenMode mode)
+{
+       if (mode & QIODevice::Truncate) {
+               qWarning("QtLockedFile::open(): Truncate mode not allowed.");
+               return false;
+       }
+       return QFile::open(mode);
+}
+
+/*!
+       Returns \e true if this object has a in read or write lock;
+       otherwise returns \e false.
+
+       \sa lockMode()
+*/
+bool QtLockedFile::isLocked() const
+{
+       return m_lock_mode != NoLock;
+}
+
+/*!
+       Returns the type of lock currently held by this object, or \e
+       QtLockedFile::NoLock.
+
+       \sa isLocked()
+*/
+QtLockedFile::LockMode QtLockedFile::lockMode() const
+{
+       return m_lock_mode;
+}
+
+/*!
+       \fn bool QtLockedFile::lock(LockMode mode, bool block = true)
+
+       Obtains a lock of type \a mode. The file must be opened before it
+       can be locked.
+
+       If \a block is true, this function will block until the lock is
+       aquired. If \a block is false, this function returns \e false
+       immediately if the lock cannot be aquired.
+
+       If this object already has a lock of type \a mode, this function
+       returns \e true immediately. If this object has a lock of a
+       different type than \a mode, the lock is first released and then a
+       new lock is obtained.
+
+       This function returns \e true if, after it executes, the file is
+       locked by this object, and \e false otherwise.
+
+       \sa unlock(), isLocked(), lockMode()
+*/
+
+/*!
+       \fn bool QtLockedFile::unlock()
+
+       Releases a lock.
+
+       If the object has no lock, this function returns immediately.
+
+       This function returns \e true if, after it executes, the file is
+       not locked by this object, and \e false otherwise.
+
+       \sa lock(), isLocked(), lockMode()
+*/
+
+/*!
+       \fn QtLockedFile::~QtLockedFile()
+
+       Destroys the \e QtLockedFile object. If any locks were held, they
+       are released.
+*/
diff --git a/core/qtsingleapplication/qtlockedfile.h b/core/qtsingleapplication/qtlockedfile.h
new file mode 100644 (file)
index 0000000..08be78f
--- /dev/null
@@ -0,0 +1,96 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of a Qt Solutions component.
+**
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**   * Redistributions of source code must retain the above copyright
+**     notice, this list of conditions and the following disclaimer.
+**   * Redistributions in binary form must reproduce the above copyright
+**     notice, this list of conditions and the following disclaimer in
+**     the documentation and/or other materials provided with the
+**     distribution.
+**   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+**     the names of its contributors may be used to endorse or promote
+**     products derived from this software without specific prior written
+**     permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+****************************************************************************/
+
+#ifndef QTLOCKEDFILE_H
+#define QTLOCKEDFILE_H
+
+#include <QFile>
+#ifdef Q_OS_WIN
+#include <QVector>
+#endif
+
+#if defined(Q_WS_WIN)
+#  if !defined(QT_QTLOCKEDFILE_EXPORT) && !defined(QT_QTLOCKEDFILE_IMPORT)
+#    define QT_QTLOCKEDFILE_EXPORT
+#  elif defined(QT_QTLOCKEDFILE_IMPORT)
+#    if defined(QT_QTLOCKEDFILE_EXPORT)
+#      undef QT_QTLOCKEDFILE_EXPORT
+#    endif
+#    define QT_QTLOCKEDFILE_EXPORT __declspec(dllimport)
+#  elif defined(QT_QTLOCKEDFILE_EXPORT)
+#    undef QT_QTLOCKEDFILE_EXPORT
+#    define QT_QTLOCKEDFILE_EXPORT __declspec(dllexport)
+#  endif
+#else
+#  define QT_QTLOCKEDFILE_EXPORT
+#endif
+
+namespace QtLP_Private {
+
+class QT_QTLOCKEDFILE_EXPORT QtLockedFile : public QFile
+{
+public:
+       enum LockMode { NoLock = 0, ReadLock, WriteLock };
+
+       QtLockedFile();
+       QtLockedFile(const QString &name);
+       ~QtLockedFile();
+
+       bool open(OpenMode mode);
+
+       bool lock(LockMode mode, bool block = true);
+       bool unlock();
+       bool isLocked() const;
+       LockMode lockMode() const;
+
+private:
+#ifdef Q_OS_WIN
+       Qt::HANDLE wmutex;
+       Qt::HANDLE rmutex;
+       QVector<Qt::HANDLE> rmutexes;
+       QString mutexname;
+
+       Qt::HANDLE getMutexHandle(int idx, bool doCreate);
+       bool waitMutex(Qt::HANDLE mutex, bool doBlock);
+
+#endif
+       LockMode m_lock_mode;
+};
+}
+#endif
diff --git a/core/qtsingleapplication/qtlockedfile_unix.cpp b/core/qtsingleapplication/qtlockedfile_unix.cpp
new file mode 100644 (file)
index 0000000..715c7d9
--- /dev/null
@@ -0,0 +1,114 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of a Qt Solutions component.
+**
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**   * Redistributions of source code must retain the above copyright
+**     notice, this list of conditions and the following disclaimer.
+**   * Redistributions in binary form must reproduce the above copyright
+**     notice, this list of conditions and the following disclaimer in
+**     the documentation and/or other materials provided with the
+**     distribution.
+**   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+**     the names of its contributors may be used to endorse or promote
+**     products derived from this software without specific prior written
+**     permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+****************************************************************************/
+
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "qtlockedfile.h"
+
+bool QtLockedFile::lock(LockMode mode, bool block)
+{
+    if (!isOpen()) {
+        qWarning("QtLockedFile::lock(): file is not opened");
+        return false;
+    }
+    if (mode == NoLock)
+        return unlock();
+           
+    if (mode == m_lock_mode)
+        return true;
+
+    if (m_lock_mode != NoLock)
+        unlock();
+
+    struct flock fl;
+    fl.l_whence = SEEK_SET;
+    fl.l_start = 0;
+    fl.l_len = 0;
+    fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK;
+    int cmd = block ? F_SETLKW : F_SETLK;
+    int ret = fcntl(handle(), cmd, &fl);
+    
+    if (ret == -1) {
+        if (errno != EINTR && errno != EAGAIN)
+            qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno));
+        return false;
+    }
+
+    
+    m_lock_mode = mode;
+    return true;
+}
+
+
+bool QtLockedFile::unlock()
+{
+    if (!isOpen()) {
+        qWarning("QtLockedFile::unlock(): file is not opened");
+        return false;
+    }
+
+    if (!isLocked())
+        return true;
+
+    struct flock fl;
+    fl.l_whence = SEEK_SET;
+    fl.l_start = 0;
+    fl.l_len = 0;
+    fl.l_type = F_UNLCK;
+    int ret = fcntl(handle(), F_SETLKW, &fl);
+    
+    if (ret == -1) {
+        qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno));
+        return false;
+    }
+    
+    m_lock_mode = NoLock;
+    return true;
+}
+
+QtLockedFile::~QtLockedFile()
+{
+    if (isOpen())
+        unlock();
+}
+
diff --git a/core/qtsingleapplication/qtlockedfile_win.cpp b/core/qtsingleapplication/qtlockedfile_win.cpp
new file mode 100644 (file)
index 0000000..b54ac34
--- /dev/null
@@ -0,0 +1,204 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of a Qt Solutions component.
+**
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**   * Redistributions of source code must retain the above copyright
+**     notice, this list of conditions and the following disclaimer.
+**   * Redistributions in binary form must reproduce the above copyright
+**     notice, this list of conditions and the following disclaimer in
+**     the documentation and/or other materials provided with the
+**     distribution.
+**   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+**     the names of its contributors may be used to endorse or promote
+**     products derived from this software without specific prior written
+**     permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+****************************************************************************/
+
+#include "qtlockedfile.h"
+#include <qt_windows.h>
+#include <QtCore/QFileInfo>
+
+#define MUTEX_PREFIX "QtLockedFile mutex "
+// Maximum number of concurrent read locks. Must not be greater than MAXIMUM_WAIT_OBJECTS
+#define MAX_READERS MAXIMUM_WAIT_OBJECTS
+
+Qt::HANDLE QtLockedFile::getMutexHandle(int idx, bool doCreate)
+{
+       if (mutexname.isEmpty()) {
+               QFileInfo fi(*this);
+               mutexname = QString::fromLatin1(MUTEX_PREFIX)
+                                       + fi.absoluteFilePath().toLower();
+       }
+       QString mname(mutexname);
+       if (idx >= 0)
+               mname += QString::number(idx);
+
+       Qt::HANDLE mutex;
+       if (doCreate) {
+               mutex = CreateMutexW(NULL, FALSE, (TCHAR*)mname.utf16());
+               if (!mutex) {
+                       qErrnoWarning("QtLockedFile::lock(): CreateMutex failed");
+                       return 0;
+               }
+       }
+       else {
+               mutex = OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, (TCHAR*)mname.utf16());
+               if (!mutex) {
+                       if (GetLastError() != ERROR_FILE_NOT_FOUND)
+                               qErrnoWarning("QtLockedFile::lock(): OpenMutex failed");
+                       return 0;
+               }
+       }
+       return mutex;
+}
+
+bool QtLockedFile::waitMutex(Qt::HANDLE mutex, bool doBlock)
+{
+       Q_ASSERT(mutex);
+       DWORD res = WaitForSingleObject(mutex, doBlock ? INFINITE : 0);
+       switch (res) {
+       case WAIT_OBJECT_0:
+       case WAIT_ABANDONED:
+               return true;
+               break;
+       case WAIT_TIMEOUT:
+               break;
+       default:
+               qErrnoWarning("QtLockedFile::lock(): WaitForSingleObject failed");
+       }
+       return false;
+}
+
+
+
+bool QtLockedFile::lock(LockMode mode, bool block)
+{
+       if (!isOpen()) {
+               qWarning("QtLockedFile::lock(): file is not opened");
+               return false;
+       }
+
+       if (mode == NoLock)
+               return unlock();
+
+       if (mode == m_lock_mode)
+               return true;
+
+       if (m_lock_mode != NoLock)
+               unlock();
+
+       if (!wmutex && !(wmutex = getMutexHandle(-1, true)))
+               return false;
+
+       if (!waitMutex(wmutex, block))
+               return false;
+
+       if (mode == ReadLock) {
+               int idx = 0;
+               for (; idx < MAX_READERS; idx++) {
+                       rmutex = getMutexHandle(idx, false);
+                       if (!rmutex || waitMutex(rmutex, false))
+                               break;
+                       CloseHandle(rmutex);
+               }
+               bool ok = true;
+               if (idx >= MAX_READERS) {
+                       qWarning("QtLockedFile::lock(): too many readers");
+                       rmutex = 0;
+                       ok = false;
+               }
+               else if (!rmutex) {
+                       rmutex = getMutexHandle(idx, true);
+                       if (!rmutex || !waitMutex(rmutex, false))
+                               ok = false;
+               }
+               if (!ok && rmutex) {
+                       CloseHandle(rmutex);
+                       rmutex = 0;
+               }
+               ReleaseMutex(wmutex);
+               if (!ok)
+                       return false;
+       }
+       else {
+               Q_ASSERT(rmutexes.isEmpty());
+               for (int i = 0; i < MAX_READERS; i++) {
+                       Qt::HANDLE mutex = getMutexHandle(i, false);
+                       if (mutex)
+                               rmutexes.append(mutex);
+               }
+               if (rmutexes.size()) {
+                       DWORD res = WaitForMultipleObjects(rmutexes.size(), rmutexes.constData(),
+                                                                                          TRUE, block ? INFINITE : 0);
+                       if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) {
+                               if (res != WAIT_TIMEOUT)
+                                       qErrnoWarning("QtLockedFile::lock(): WaitForMultipleObjects failed");
+                               m_lock_mode = WriteLock;  // trick unlock() to clean up - semiyucky
+                               unlock();
+                               return false;
+                       }
+               }
+       }
+
+       m_lock_mode = mode;
+       return true;
+}
+
+bool QtLockedFile::unlock()
+{
+       if (!isOpen()) {
+               qWarning("QtLockedFile::unlock(): file is not opened");
+               return false;
+       }
+
+       if (!isLocked())
+               return true;
+
+       if (m_lock_mode == ReadLock) {
+               ReleaseMutex(rmutex);
+               CloseHandle(rmutex);
+               rmutex = 0;
+       }
+       else {
+               foreach(Qt::HANDLE mutex, rmutexes) {
+                       ReleaseMutex(mutex);
+                       CloseHandle(mutex);
+               }
+               rmutexes.clear();
+               ReleaseMutex(wmutex);
+       }
+
+       m_lock_mode = QtLockedFile::NoLock;
+       return true;
+}
+
+QtLockedFile::~QtLockedFile()
+{
+       if (isOpen())
+               unlock();
+       if (wmutex)
+               CloseHandle(wmutex);
+}
diff --git a/core/qtsingleapplication/qtsingleapplication.cpp b/core/qtsingleapplication/qtsingleapplication.cpp
new file mode 100644 (file)
index 0000000..3cc0682
--- /dev/null
@@ -0,0 +1,331 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of a Qt Solutions component.
+**
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**   * Redistributions of source code must retain the above copyright
+**     notice, this list of conditions and the following disclaimer.
+**   * Redistributions in binary form must reproduce the above copyright
+**     notice, this list of conditions and the following disclaimer in
+**     the documentation and/or other materials provided with the
+**     distribution.
+**   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+**     the names of its contributors may be used to endorse or promote
+**     products derived from this software without specific prior written
+**     permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+****************************************************************************/
+
+
+#include "qtsingleapplication.h"
+#include "qtlocalpeer.h"
+#include <QWidget>
+
+
+/*!
+    \class QtSingleApplication qtsingleapplication.h
+    \brief The QtSingleApplication class provides an API to detect and
+    communicate with running instances of an application.
+
+    This class allows you to create applications where only one
+    instance should be running at a time. I.e., if the user tries to
+    launch another instance, the already running instance will be
+    activated instead. Another usecase is a client-server system,
+    where the first started instance will assume the role of server,
+    and the later instances will act as clients of that server.
+
+    By default, the full path of the executable file is used to
+    determine whether two processes are instances of the same
+    application. You can also provide an explicit identifier string
+    that will be compared instead.
+
+    The application should create the QtSingleApplication object early
+    in the startup phase, and call isRunning() to find out if another
+    instance of this application is already running. If isRunning()
+    returns false, it means that no other instance is running, and
+    this instance has assumed the role as the running instance. In
+    this case, the application should continue with the initialization
+    of the application user interface before entering the event loop
+    with exec(), as normal.
+
+    The messageReceived() signal will be emitted when the running
+    application receives messages from another instance of the same
+    application. When a message is received it might be helpful to the
+    user to raise the application so that it becomes visible. To
+    facilitate this, QtSingleApplication provides the
+    setActivationWindow() function and the activateWindow() slot.
+
+    If isRunning() returns true, another instance is already
+    running. It may be alerted to the fact that another instance has
+    started by using the sendMessage() function. Also data such as
+    startup parameters (e.g. the name of the file the user wanted this
+    new instance to open) can be passed to the running instance with
+    this function. Then, the application should terminate (or enter
+    client mode).
+
+    If isRunning() returns true, but sendMessage() fails, that is an
+    indication that the running instance is frozen.
+
+    Here's an example that shows how to convert an existing
+    application to use QtSingleApplication. It is very simple and does
+    not make use of all QtSingleApplication's functionality (see the
+    examples for that).
+
+    \code
+    // Original
+    int main(int argc, char **argv)
+    {
+        QApplication app(argc, argv);
+
+        MyMainWidget mmw;
+        mmw.show();
+        return app.exec();
+    }
+
+    // Single instance
+    int main(int argc, char **argv)
+    {
+        QtSingleApplication app(argc, argv);
+
+        if (app.isRunning())
+            return !app.sendMessage(someDataString);
+
+        MyMainWidget mmw;
+        app.setActivationWindow(&mmw);
+        mmw.show();
+        return app.exec();
+    }
+    \endcode
+
+    Once this QtSingleApplication instance is destroyed (normally when
+    the process exits or crashes), when the user next attempts to run the
+    application this instance will not, of course, be encountered. The
+    next instance to call isRunning() or sendMessage() will assume the
+    role as the new running instance.
+
+    For console (non-GUI) applications, QtSingleCoreApplication may be
+    used instead of this class, to avoid the dependency on the QtGui
+    library.
+
+    \sa QtSingleCoreApplication
+*/
+
+
+void QtSingleApplication::sysInit(const QString &appId)
+{
+    actWin = 0;
+    peer = new QtLocalPeer(this, appId);
+    connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
+}
+
+
+/*!
+    Creates a QtSingleApplication object. The application identifier
+    will be QCoreApplication::applicationFilePath(). \a argc, \a
+    argv, and \a GUIenabled are passed on to the QAppliation constructor.
+
+    If you are creating a console application (i.e. setting \a
+    GUIenabled to false), you may consider using
+    QtSingleCoreApplication instead.
+*/
+
+QtSingleApplication::QtSingleApplication(int &argc, char **argv, bool GUIenabled)
+    : QApplication(argc, argv, GUIenabled)
+{
+    sysInit();
+}
+
+
+/*!
+    Creates a QtSingleApplication object with the application
+    identifier \a appId. \a argc and \a argv are passed on to the
+    QAppliation constructor.
+*/
+
+QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv)
+    : QApplication(argc, argv)
+{
+    sysInit(appId);
+}
+
+#if defined(Q_WS_X11)
+/*!
+  Special constructor for X11, ref. the documentation of
+  QApplication's corresponding constructor. The application identifier
+  will be QCoreApplication::applicationFilePath(). \a dpy, \a visual,
+  and \a cmap are passed on to the QApplication constructor.
+*/
+QtSingleApplication::QtSingleApplication(Display* dpy, Qt::HANDLE visual, Qt::HANDLE cmap)
+    : QApplication(dpy, visual, cmap)
+{
+    sysInit();
+}
+
+/*!
+  Special constructor for X11, ref. the documentation of
+  QApplication's corresponding constructor. The application identifier
+  will be QCoreApplication::applicationFilePath(). \a dpy, \a argc, \a
+  argv, \a visual, and \a cmap are passed on to the QApplication
+  constructor.
+*/
+QtSingleApplication::QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap)
+    : QApplication(dpy, argc, argv, visual, cmap)
+{
+    sysInit();
+}
+
+/*!
+  Special constructor for X11, ref. the documentation of
+  QApplication's corresponding constructor. The application identifier
+  will be \a appId. \a dpy, \a argc, \a
+  argv, \a visual, and \a cmap are passed on to the QApplication
+  constructor.
+*/
+QtSingleApplication::QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap)
+    : QApplication(dpy, argc, argv, visual, cmap)
+{
+    sysInit(appId);
+}
+#endif
+
+
+/*!
+    Returns true if another instance of this application is running;
+    otherwise false.
+
+    This function does not find instances of this application that are
+    being run by a different user (on Windows: that are running in
+    another session).
+
+    \sa sendMessage()
+*/
+
+bool QtSingleApplication::isRunning()
+{
+    return peer->isClient();
+}
+
+
+/*!
+    Tries to send the text \a message to the currently running
+    instance. The QtSingleApplication object in the running instance
+    will emit the messageReceived() signal when it receives the
+    message.
+
+    This function returns true if the message has been sent to, and
+    processed by, the current instance. If there is no instance
+    currently running, or if the running instance fails to process the
+    message within \a timeout milliseconds, this function return false.
+
+    \sa isRunning(), messageReceived()
+*/
+bool QtSingleApplication::sendMessage(const QString &message, int timeout)
+{
+    return peer->sendMessage(message, timeout);
+}
+
+
+/*!
+    Returns the application identifier. Two processes with the same
+    identifier will be regarded as instances of the same application.
+*/
+QString QtSingleApplication::id() const
+{
+    return peer->applicationId();
+}
+
+
+/*!
+  Sets the activation window of this application to \a aw. The
+  activation window is the widget that will be activated by
+  activateWindow(). This is typically the application's main window.
+
+  If \a activateOnMessage is true (the default), the window will be
+  activated automatically every time a message is received, just prior
+  to the messageReceived() signal being emitted.
+
+  \sa activateWindow(), messageReceived()
+*/
+
+void QtSingleApplication::setActivationWindow(QWidget* aw, bool activateOnMessage)
+{
+    actWin = aw;
+    if (activateOnMessage)
+        connect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow()));
+    else
+        disconnect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow()));
+}
+
+
+/*!
+    Returns the applications activation window if one has been set by
+    calling setActivationWindow(), otherwise returns 0.
+
+    \sa setActivationWindow()
+*/
+QWidget* QtSingleApplication::activationWindow() const
+{
+    return actWin;
+}
+
+
+/*!
+  De-minimizes, raises, and activates this application's activation window.
+  This function does nothing if no activation window has been set.
+
+  This is a convenience function to show the user that this
+  application instance has been activated when he has tried to start
+  another instance.
+
+  This function should typically be called in response to the
+  messageReceived() signal. By default, that will happen
+  automatically, if an activation window has been set.
+
+  \sa setActivationWindow(), messageReceived(), initialize()
+*/
+void QtSingleApplication::activateWindow()
+{
+    if (actWin) {
+        actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized);
+        actWin->raise();
+        actWin->activateWindow();
+    }
+}
+
+
+/*!
+    \fn void QtSingleApplication::messageReceived(const QString& message)
+
+    This signal is emitted when the current instance receives a \a
+    message from another instance of this application.
+
+    \sa sendMessage(), setActivationWindow(), activateWindow()
+*/
+
+
+/*!
+    \fn void QtSingleApplication::initialize(bool dummy = true)
+
+    \obsolete
+*/
diff --git a/core/qtsingleapplication/qtsingleapplication.h b/core/qtsingleapplication/qtsingleapplication.h
new file mode 100644 (file)
index 0000000..3ed62d5
--- /dev/null
@@ -0,0 +1,101 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of a Qt Solutions component.
+**
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**   * Redistributions of source code must retain the above copyright
+**     notice, this list of conditions and the following disclaimer.
+**   * Redistributions in binary form must reproduce the above copyright
+**     notice, this list of conditions and the following disclaimer in
+**     the documentation and/or other materials provided with the
+**     distribution.
+**   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+**     the names of its contributors may be used to endorse or promote
+**     products derived from this software without specific prior written
+**     permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+****************************************************************************/
+
+#ifndef QTSINGLEAPPLICATION_H
+#define QTSINGLEAPPLICATION_H
+
+#include <QApplication>
+
+class QtLocalPeer;
+
+#if defined(Q_WS_WIN)
+#  if !defined(QT_QTSINGLEAPPLICATION_EXPORT) && !defined(QT_QTSINGLEAPPLICATION_IMPORT)
+#    define QT_QTSINGLEAPPLICATION_EXPORT
+#  elif defined(QT_QTSINGLEAPPLICATION_IMPORT)
+#    if defined(QT_QTSINGLEAPPLICATION_EXPORT)
+#      undef QT_QTSINGLEAPPLICATION_EXPORT
+#    endif
+#    define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllimport)
+#  elif defined(QT_QTSINGLEAPPLICATION_EXPORT)
+#    undef QT_QTSINGLEAPPLICATION_EXPORT
+#    define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllexport)
+#  endif
+#else
+#  define QT_QTSINGLEAPPLICATION_EXPORT
+#endif
+
+class QT_QTSINGLEAPPLICATION_EXPORT QtSingleApplication : public QApplication
+{
+    Q_OBJECT
+
+public:
+    QtSingleApplication(int &argc, char **argv, bool GUIenabled = true);
+    QtSingleApplication(const QString &id, int &argc, char **argv);
+#if defined(Q_WS_X11)
+    QtSingleApplication(Display* dpy, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0);
+    QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap= 0);
+    QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0);
+#endif
+
+    bool isRunning();
+    QString id() const;
+
+    void setActivationWindow(QWidget* aw, bool activateOnMessage = true);
+    QWidget* activationWindow() const;
+
+    // Obsolete:
+    void initialize(bool dummy = true)
+        { isRunning(); Q_UNUSED(dummy) }
+
+public Q_SLOTS:
+    bool sendMessage(const QString &message, int timeout = 5000);
+    void activateWindow();
+
+
+Q_SIGNALS:
+    void messageReceived(const QString &message);
+
+
+private:
+    void sysInit(const QString &appId = QString());
+    QtLocalPeer *peer;
+    QWidget *actWin;
+};
+
+#endif // QTSINGLEAPPLICATION_H
diff --git a/core/qtsingleapplication/qtsingleapplication.pri b/core/qtsingleapplication/qtsingleapplication.pri
new file mode 100644 (file)
index 0000000..273ecb9
--- /dev/null
@@ -0,0 +1,6 @@
+INCLUDEPATH += $$PWD
+DEPENDPATH += $$PWD
+QT *= network
+
+SOURCES += $$PWD/qtsingleapplication.cpp $$PWD/qtlocalpeer.cpp
+HEADERS += $$PWD/qtsingleapplication.h $$PWD/qtlocalpeer.h
diff --git a/core/qtsingleapplication/qtsinglecoreapplication.cpp b/core/qtsingleapplication/qtsinglecoreapplication.cpp
new file mode 100644 (file)
index 0000000..cf60771
--- /dev/null
@@ -0,0 +1,148 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of a Qt Solutions component.
+**
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**   * Redistributions of source code must retain the above copyright
+**     notice, this list of conditions and the following disclaimer.
+**   * Redistributions in binary form must reproduce the above copyright
+**     notice, this list of conditions and the following disclaimer in
+**     the documentation and/or other materials provided with the
+**     distribution.
+**   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+**     the names of its contributors may be used to endorse or promote
+**     products derived from this software without specific prior written
+**     permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+****************************************************************************/
+
+
+#include "qtsinglecoreapplication.h"
+#include "qtlocalpeer.h"
+
+/*!
+    \class QtSingleCoreApplication qtsinglecoreapplication.h
+    \brief A variant of the QtSingleApplication class for non-GUI applications.
+
+    This class is a variant of QtSingleApplication suited for use in
+    console (non-GUI) applications. It is an extension of
+    QCoreApplication (instead of QApplication). It does not require
+    the QtGui library.
+
+    The API and usage is identical to QtSingleApplication, except that
+    functions relating to the "activation window" are not present, for
+    obvious reasons. Please refer to the QtSingleApplication
+    documentation for explanation of the usage.
+
+    A QtSingleCoreApplication instance can communicate to a
+    QtSingleApplication instance if they share the same application
+    id. Hence, this class can be used to create a light-weight
+    command-line tool that sends commands to a GUI application.
+
+    \sa QtSingleApplication
+*/
+
+/*!
+    Creates a QtSingleCoreApplication object. The application identifier
+    will be QCoreApplication::applicationFilePath(). \a argc and \a
+    argv are passed on to the QCoreAppliation constructor.
+*/
+
+QtSingleCoreApplication::QtSingleCoreApplication(int &argc, char **argv)
+    : QCoreApplication(argc, argv)
+{
+    peer = new QtLocalPeer(this);
+    connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
+}
+
+
+/*!
+    Creates a QtSingleCoreApplication object with the application
+    identifier \a appId. \a argc and \a argv are passed on to the
+    QCoreAppliation constructor.
+*/
+QtSingleCoreApplication::QtSingleCoreApplication(const QString &appId, int &argc, char **argv)
+    : QCoreApplication(argc, argv)
+{
+    peer = new QtLocalPeer(this, appId);
+    connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
+}
+
+
+/*!
+    Returns true if another instance of this application is running;
+    otherwise false.
+
+    This function does not find instances of this application that are
+    being run by a different user (on Windows: that are running in
+    another session).
+
+    \sa sendMessage()
+*/
+
+bool QtSingleCoreApplication::isRunning()
+{
+    return peer->isClient();
+}
+
+
+/*!
+    Tries to send the text \a message to the currently running
+    instance. The QtSingleCoreApplication object in the running instance
+    will emit the messageReceived() signal when it receives the
+    message.
+
+    This function returns true if the message has been sent to, and
+    processed by, the current instance. If there is no instance
+    currently running, or if the running instance fails to process the
+    message within \a timeout milliseconds, this function return false.
+
+    \sa isRunning(), messageReceived()
+*/
+
+bool QtSingleCoreApplication::sendMessage(const QString &message, int timeout)
+{
+    return peer->sendMessage(message, timeout);
+}
+
+
+/*!
+    Returns the application identifier. Two processes with the same
+    identifier will be regarded as instances of the same application.
+*/
+
+QString QtSingleCoreApplication::id() const
+{
+    return peer->applicationId();
+}
+
+
+/*!
+    \fn void QtSingleCoreApplication::messageReceived(const QString& message)
+
+    This signal is emitted when the current instance receives a \a
+    message from another instance of this application.
+
+    \sa sendMessage()
+*/
diff --git a/core/qtsingleapplication/qtsinglecoreapplication.h b/core/qtsingleapplication/qtsinglecoreapplication.h
new file mode 100644 (file)
index 0000000..7cde4b8
--- /dev/null
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of a Qt Solutions component.
+**
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**   * Redistributions of source code must retain the above copyright
+**     notice, this list of conditions and the following disclaimer.
+**   * Redistributions in binary form must reproduce the above copyright
+**     notice, this list of conditions and the following disclaimer in
+**     the documentation and/or other materials provided with the
+**     distribution.
+**   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+**     the names of its contributors may be used to endorse or promote
+**     products derived from this software without specific prior written
+**     permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+****************************************************************************/
+
+#ifndef QTSINGLECOREAPPLICATION_H
+#define QTSINGLECOREAPPLICATION_H
+
+#include <QtCore/QCoreApplication>
+
+class QtLocalPeer;
+
+class QtSingleCoreApplication : public QCoreApplication
+{
+    Q_OBJECT
+
+public:
+    QtSingleCoreApplication(int &argc, char **argv);
+    QtSingleCoreApplication(const QString &id, int &argc, char **argv);
+
+    bool isRunning();
+    QString id() const;
+
+public Q_SLOTS:
+    bool sendMessage(const QString &message, int timeout = 5000);
+
+
+Q_SIGNALS:
+    void messageReceived(const QString &message);
+
+
+private:
+    QtLocalPeer* peer;
+};
+
+#endif // QTSINGLECOREAPPLICATION_H
diff --git a/core/qtsingleapplication/qtsinglecoreapplication.pri b/core/qtsingleapplication/qtsinglecoreapplication.pri
new file mode 100644 (file)
index 0000000..d2d6cc3
--- /dev/null
@@ -0,0 +1,10 @@
+INCLUDEPATH    += $$PWD
+DEPENDPATH      += $$PWD
+HEADERS                += $$PWD/qtsinglecoreapplication.h $$PWD/qtlocalpeer.h
+SOURCES                += $$PWD/qtsinglecoreapplication.cpp $$PWD/qtlocalpeer.cpp
+
+QT *= network
+
+win32:contains(TEMPLATE, lib):contains(CONFIG, shared) {
+    DEFINES += QT_QTSINGLECOREAPPLICATION_EXPORT=__declspec(dllexport)
+}
diff --git a/core/timeformatter.cpp b/core/timeformatter.cpp
new file mode 100644 (file)
index 0000000..af57d22
--- /dev/null
@@ -0,0 +1,10 @@
+#include "timeformatter.h"
+
+#include <QTime>
+
+TimeFormatter::TimeFormatter(QObject *parent) : QObject(parent) {}
+
+QString TimeFormatter::format(double seconds) {
+  return QTime::fromMSecsSinceStartOfDay(static_cast<int>(seconds * 1000))
+      .toString("hh:mm:ss.zzz");
+}
diff --git a/core/timeformatter.h b/core/timeformatter.h
new file mode 100644 (file)
index 0000000..7f0d602
--- /dev/null
@@ -0,0 +1,15 @@
+#ifndef TIMEFORMATTER_H
+#define TIMEFORMATTER_H
+
+#include <QObject>
+
+class TimeFormatter : public QObject {
+  Q_OBJECT
+public:
+  explicit TimeFormatter(QObject *parent = 0);
+
+public slots:
+  QString format(double seconds);
+};
+
+#endif // TIMEFORMATTER_H
diff --git a/core/videoelement.cpp b/core/videoelement.cpp
new file mode 100644 (file)
index 0000000..05b40fb
--- /dev/null
@@ -0,0 +1,68 @@
+#include "videoelement.h"
+#include "player.h"
+
+VideoElement::VideoElement() : m_source{nullptr} {
+  connect(this, SIGNAL(updateRequested()), this, SLOT(update()),
+          Qt::QueuedConnection);
+}
+
+VideoElement::~VideoElement() {}
+
+VideoElement::Renderer *VideoElement::createRenderer() const {
+  qDebug("creating VideoElement::Renderer");
+  return new Renderer;
+}
+
+Player *VideoElement::source() const { return m_source; }
+
+void VideoElement::setSource(Player *source) {
+  if (m_source == source)
+    return;
+
+  if (source) {
+    if (source->hasRenderer())
+      return;
+
+    PlayerRendererInterface *pri = source;
+    pri->rendererSinkSet(this);
+
+    m_source = source;
+
+    emit sourceChanged(source);
+    qDebug() << "source has been set!";
+    rendererUpdateRequired = true;
+    return;
+  }
+
+  PlayerRendererInterface *pri = m_source;
+  pri->rendererSinkSet(nullptr);
+  m_source = nullptr;
+  rendererUpdateRequired = true;
+}
+
+VideoElement::Renderer::~Renderer() { delete m_renderer; }
+
+void VideoElement::Renderer::render() {
+  if (m_renderer)
+    m_renderer->render(framebufferObject());
+}
+
+void VideoElement::Renderer::synchronize(QQuickFramebufferObject *object) {
+  auto ve = static_cast<VideoElement *>(object);
+  if (!ve->rendererUpdateRequired)
+    return;
+  ve->rendererUpdateRequired = false;
+  if (!ve->source()) {
+    delete m_renderer;
+    m_renderer = nullptr;
+    return;
+  }
+  qDebug("creating backend renderer");
+  m_renderer = ve->source()->backend()->createRenderer(ve);
+  // Call via base to ensure a public method.
+  PlayerRendererInterface *src = ve->m_source;
+  src->rendererReady();
+  qDebug("backend renderer created");
+}
+
+void VideoElement::videoUpdated() { emit updateRequested(); }
diff --git a/core/videoelement.h b/core/videoelement.h
new file mode 100644 (file)
index 0000000..a26f512
--- /dev/null
@@ -0,0 +1,48 @@
+#ifndef VIDEOELEMENT_H
+#define VIDEOELEMENT_H
+
+#include <QQuickFramebufferObject>
+
+#include "player.h"
+#include "playerplugininterface.h"
+
+class VideoElement : public QQuickFramebufferObject,
+                     public VideoUpdateInterface {
+  Q_OBJECT
+  Q_PROPERTY(Player *source READ source WRITE setSource NOTIFY sourceChanged)
+
+  class Renderer : public QQuickFramebufferObject::Renderer {
+  public:
+    ~Renderer();
+    void render() override;
+    void synchronize(QQuickFramebufferObject *object) override;
+
+  private:
+    VideoRendererBase *m_renderer;
+  };
+
+  Player *m_source;
+
+public:
+  VideoElement();
+  ~VideoElement();
+
+  Renderer *createRenderer() const override;
+  Player *source() const;
+
+public slots:
+  void setSource(Player *source);
+
+signals:
+  void updateRequested();
+
+  void sourceChanged(Player *source);
+
+protected:
+  void videoUpdated() override;
+
+private:
+  bool rendererUpdateRequired = false;
+};
+
+#endif // VIDEOELEMENT_H
diff --git a/resource/aniplayer-mikuru.ico b/resource/aniplayer-mikuru.ico
new file mode 100644 (file)
index 0000000..2439682
Binary files /dev/null and b/resource/aniplayer-mikuru.ico differ
diff --git a/resource/mikuru-icon-base.png b/resource/mikuru-icon-base.png
new file mode 100644 (file)
index 0000000..7c362f8
Binary files /dev/null and b/resource/mikuru-icon-base.png differ
diff --git a/resource/mikuru-icon-original.png b/resource/mikuru-icon-original.png
new file mode 100644 (file)
index 0000000..7c0c662
Binary files /dev/null and b/resource/mikuru-icon-original.png differ