--- /dev/null
+# 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*
+
+
--- /dev/null
+TEMPLATE = subdirs
+
+SUBDIRS += \
+ core \
+ backendplugins
--- /dev/null
+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
--- /dev/null
+#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
--- /dev/null
+#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();
+}
--- /dev/null
+#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
--- /dev/null
+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
+}
--- /dev/null
+#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
--- /dev/null
+#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)
+{
+}
--- /dev/null
+#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
--- /dev/null
+include(../buildconfig.pri)
+DESTDIR=../../build/backendplugins
\ No newline at end of file
--- /dev/null
+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
--- /dev/null
+DESTDIR = ../build
+CONFIG += c++14
+# Output Temporary files
+OBJECTS_DIR = workfiles/obj
+MOC_DIR = workfiles/moc
+RCC_DIR = workfiles/rcc
--- /dev/null
+<?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
--- /dev/null
+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
--- /dev/null
+#include "backendpluginbase.h"
+
--- /dev/null
+#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
--- /dev/null
+INCLUDEPATH += $$PWD
+include(../buildconfig.pri)
--- /dev/null
+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
+}
--- /dev/null
+#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]));
+}
--- /dev/null
+#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
--- /dev/null
+#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;
+}
--- /dev/null
+#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{});
+}
--- /dev/null
+#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
--- /dev/null
+#include "playerplugininterface.h"
--- /dev/null
+#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
--- /dev/null
+#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"); }
--- /dev/null
+#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
--- /dev/null
+<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>
--- /dev/null
+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()
+ }
+}
--- /dev/null
+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();
+ }
+}
--- /dev/null
+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
+}
+}
--- /dev/null
+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)
+ }
+}
+
--- /dev/null
+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);
+ }
+ }
+}
--- /dev/null
+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);
+ }
+ }
+}
--- /dev/null
+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;
+ }
+ }
+}
--- /dev/null
+#include "qtlockedfile.h"
--- /dev/null
+#include "qtsingleapplication.h"
--- /dev/null
+/****************************************************************************
+**
+** 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)
+}
--- /dev/null
+/****************************************************************************
+**
+** 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
--- /dev/null
+/****************************************************************************
+**
+** 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.
+*/
--- /dev/null
+/****************************************************************************
+**
+** 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
--- /dev/null
+/****************************************************************************
+**
+** 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();
+}
+
--- /dev/null
+/****************************************************************************
+**
+** 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);
+}
--- /dev/null
+/****************************************************************************
+**
+** 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
+*/
--- /dev/null
+/****************************************************************************
+**
+** 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
--- /dev/null
+INCLUDEPATH += $$PWD
+DEPENDPATH += $$PWD
+QT *= network
+
+SOURCES += $$PWD/qtsingleapplication.cpp $$PWD/qtlocalpeer.cpp
+HEADERS += $$PWD/qtsingleapplication.h $$PWD/qtlocalpeer.h
--- /dev/null
+/****************************************************************************
+**
+** 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()
+*/
--- /dev/null
+/****************************************************************************
+**
+** 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
--- /dev/null
+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)
+}
--- /dev/null
+#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");
+}
--- /dev/null
+#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
--- /dev/null
+#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(); }
--- /dev/null
+#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