From ef653527fb6ea751ae7f66c203cd352d6bd6ecba Mon Sep 17 00:00:00 2001 From: Elliu <elliu@hashi.re> Date: Sun, 19 Dec 2021 21:43:21 +0100 Subject: [PATCH] WIP: first take at qt_window player module --- CMakeLists.txt | 42 ++- src/module/module_qt_window.c | 114 ++++++ src/module/qt_window/mpvwidget.cpp | 160 +++++++++ src/module/qt_window/mpvwidget.hh | 42 +++ src/module/qt_window/mpvwidget_interface.cpp | 7 + src/module/qt_window/mpvwidget_interface.h | 18 + src/module/qt_window/qthelper.cpp | 353 +++++++++++++++++++ 7 files changed, 734 insertions(+), 2 deletions(-) create mode 100644 src/module/module_qt_window.c create mode 100644 src/module/qt_window/mpvwidget.cpp create mode 100644 src/module/qt_window/mpvwidget.hh create mode 100644 src/module/qt_window/mpvwidget_interface.cpp create mode 100644 src/module/qt_window/mpvwidget_interface.h create mode 100644 src/module/qt_window/qthelper.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b1c20ba1..d9d8d8d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ if(CMAKE_BUILD_TYPE STREQUAL "Release") set(CMAKE_BUILD_TYPE RelWithDebInfo) endif() -project(lektor VERSION ${LKT_VERSION} LANGUAGES C) +project(lektor VERSION ${LKT_VERSION} LANGUAGES C CXX) cmake_policy(SET CMP0100 NEW) cmake_policy(SET CMP0009 NEW) @@ -68,6 +68,14 @@ find_package(SDL2 REQUIRED version>=2.0) # tested find_package(SDL2_image REQUIRED version>=2.0) # same as above ^ find_package(CURL REQUIRED HTTP HTTPS) # tested with 7.74.0 +# For Qt +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +# Find Qt dependencies +find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED) + find_program(MKVPROPEDIT mkvpropedit REQUIRED) # To DL karas and apply metadata find_program(XXD xxd REQUIRED) # To embed the sqlite db schemas into the binary @@ -138,6 +146,10 @@ set(lektor_module_SOURCES src/module/module_repo.c src/module/mpv.c src/module/module_sdl2.c + src/module/module_qt_window.c + src/module/qt_window/mpvwidget.cpp + src/module/qt_window/mpvwidget_interface.cpp + src/module/qt_window/qthelper.cpp ) set(lektor_mkv_SOURCES @@ -227,7 +239,18 @@ add_custom_command(OUTPUT ${SQL_GENERATED_FILE} COMMENT "Generating SQL included files to embed them" ) -add_executable(lektord ${lektord_SOURCES} ${SQL_GENERATED_FILE}) +if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) + qt_add_executable(lektord + MANUAL_FINALIZATION + ${lektord_SOURCES} + ${SQL_GENERATED_FILE} + ) +else() + add_executable(lektord + ${lektord_SOURCES} + ${SQL_GENERATED_FILE} + ) +endif() add_executable(lkt ${lkt_SOURCES}) add_executable(luka ${luka_SOURCES} ${SQL_GENERATED_FILE}) @@ -255,6 +278,7 @@ target_link_libraries(lektord PRIVATE ${SDL2_LIBRARIES} ${CURL_LIBRARIES} ${SDL2_IMAGE_LIBRARIES} + Qt${QT_VERSION_MAJOR}::Widgets ) target_link_libraries(luka PRIVATE ${MPV_LIBRARY} @@ -263,6 +287,7 @@ target_link_libraries(luka PRIVATE ${SDL2_LIBRARIES} ${CURL_LIBRARIES} ${SDL2_IMAGE_LIBRARIES} + Qt${QT_VERSION_MAJOR}::Widgets ) target_include_directories(lkt PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/inc) @@ -279,6 +304,7 @@ target_include_directories(lektord PRIVATE ${CURL_INCLUDE_DIRS} ) +target_link_libraries(lektord PRIVATE Qt${QT_VERSION_MAJOR}::Widgets) target_link_libraries(lektord PRIVATE -fopenmp) target_link_libraries(luka PRIVATE -fopenmp) @@ -292,6 +318,14 @@ target_compile_definitions(luka PRIVATE ${common_DEFINITIONS}) # target_precompile_headers(luka PRIVATE ${common_HEADERS}) target_compile_options(lektord PRIVATE ${COMMON_C_FLAGS} ${${CMAKE_C_COMPILER_ID}_C_FLAGS}) +# Prepare for Qt6 +target_compile_definitions(lektord PRIVATE + QT_DISABLE_DEPRECATED_BEFORE=0x050F00 + QT_NO_CAST_TO_ASCII + QT_RESTRICTED_CAST_FROM_ASCII + QTCREATOR_UTILS_STATIC_LIB +) + target_compile_options(lkt PRIVATE ${COMMON_C_FLAGS} ${${CMAKE_C_COMPILER_ID}_C_FLAGS}) target_compile_options(luka PRIVATE ${COMMON_C_FLAGS} ${${CMAKE_C_COMPILER_ID}_C_FLAGS}) @@ -320,3 +354,7 @@ foreach(CMD IN LISTS MANPAGE_COMMANDS) DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 # A cat 1 manpage ) endforeach() + +if(QT_VERSION_MAJOR EQUAL 6) + qt_finalize_executable(lektord) +endif() diff --git a/src/module/module_qt_window.c b/src/module/module_qt_window.c new file mode 100644 index 00000000..0284b0ed --- /dev/null +++ b/src/module/module_qt_window.c @@ -0,0 +1,114 @@ +#define __LKT_MODULE_MAIN_SOURCE__ +#include <lektor/lktmodule.h> +#include "qt_window/mpvwidget_interface.h" + +struct module_qt_window_s { + MpvWidget* mpv_widget; +}; + +/************************ + * Function definitions * + ************************/ + +static void module_qt_window_free(struct module_qt_window_s *); +static void module_qt_window_close(struct module_qt_window_s *); +static bool module_qt_window_new(struct module_qt_window_s **, struct queue *, lkt_db *); + +/********************* + * Private functions * + *********************/ + + +/******************************** + * va_list version of functions * + ********************************/ + +static int +mod_new(va_list *va) +{ + va_list copy; + struct module_qt_window_s **win; + va_copy(copy, *va); + win = (struct module_qt_window_s **)va_arg(copy, void **); + struct queue *queue = va_arg(copy, struct queue *); + lkt_db *db = va_arg(copy, lkt_db *); + bool ret = module_qt_window_new(win, queue, db); + va_end(copy); + return !ret; +} + +static int +mod_close(va_list *va) +{ + va_list copy; + va_copy(copy, *va); + struct module_qt_window_s **win = (struct module_qt_window_s **)va_arg(copy, void **); + module_qt_window_close(*win); + va_end(copy); + return 0; +} + +static int +mod_free(va_list *va) +{ + va_list copy; + va_copy(copy, *va); + struct module_qt_window_s **win = (struct module_qt_window_s **)va_arg(copy, void **); + module_qt_window_free(*win); + va_end(copy); + return 0; +} + +/******************** + * The module stuff * + ********************/ + +REG_BEGIN(qt_window_reg) +REG_ADD_NAMED("new", mod_new) +REG_ADD_NAMED("free", mod_free) +REG_ADD_NAMED("close", mod_close) +REG_END() +#if !defined(LKT_STATIC_MODULE) +REG_EXPORT(qt_window_reg) +#endif + +/**************************** + * Private helper functions * + ****************************/ + + +/*************************** + * Function implementation * + ***************************/ + +static bool +module_qt_window_new(struct module_qt_window_s **win, struct queue *queue, lkt_db *db) +{ + (void)mod_new; + (void)mod_free; + (void)mod_close; + RETURN_UNLESS(win, "Invalid arguments", false); + + if (*win == NULL) { + *win = (struct module_qt_window_s*)calloc(1, sizeof(struct module_qt_window_s)); + RETURN_UNLESS(*win, "Out of memory", false); + memset(*win, 0, sizeof(struct module_qt_window_s)); + + (*win)->mpv_widget = ___create_mpv_widget(queue, db); + } + + return true; +} + +static void +module_qt_window_close(struct module_qt_window_s *win) +{ + RETURN_UNLESS(win && win->mpv_widget, "Invalid arguments", NOTHING); +} + +static void +module_qt_window_free(struct module_qt_window_s *win) +{ + RETURN_UNLESS(win, "Invalid arguments", NOTHING); + module_qt_window_close(win); +} diff --git a/src/module/qt_window/mpvwidget.cpp b/src/module/qt_window/mpvwidget.cpp new file mode 100644 index 00000000..201bfa3d --- /dev/null +++ b/src/module/qt_window/mpvwidget.cpp @@ -0,0 +1,160 @@ +#include "mpvwidget.hh" + +#include <stdexcept> +#include <QtGui/QOpenGLContext> +#include <QtCore/QMetaObject> + +#include "qthelper.cpp" + +static void wakeup(void *ctx) +{ + QMetaObject::invokeMethod((MpvWidget*)ctx, "on_mpv_events", Qt::QueuedConnection); +} + +static void *get_proc_address(void *ctx, const char *name) { + Q_UNUSED(ctx); + QOpenGLContext *glctx = QOpenGLContext::currentContext(); + if (!glctx) + return nullptr; + return reinterpret_cast<void *>(glctx->getProcAddress(QByteArray(name))); +} + +MpvWidget::MpvWidget(struct queue *queue, lkt_db *db) + : QOpenGLWidget(), + m_queue(queue), + m_db(db) +{ + setFocusPolicy(Qt::StrongFocus); + mpv = mpv_create(); + if (!mpv) + throw std::runtime_error("could not create mpv context"); + + mpv_set_option_string(mpv, "input-default-bindings", "yes"); + mpv_set_option_string(mpv, "input-vo-keyboard", "yes"); + int val = 1; + mpv_set_option(mpv, "osc", MPV_FORMAT_FLAG, &val); + + mpv_set_option_string(mpv, "terminal", "yes"); + mpv_set_option_string(mpv, "msg-level", "all=v"); + if (mpv_initialize(mpv) < 0) + throw std::runtime_error("could not initialize mpv context"); + + // Request hw decoding, just for testing. + mpv::qt::set_option_variant(mpv, "hwdec", "auto"); + + mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE); + mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE); + mpv_set_wakeup_callback(mpv, wakeup, this); +} + +MpvWidget::~MpvWidget() +{ + makeCurrent(); + if (mpv_gl) + mpv_render_context_free(mpv_gl); + mpv_terminate_destroy(mpv); +} + +void MpvWidget::command(const QVariant& params) +{ + mpv::qt::command_variant(mpv, params); +} + +void MpvWidget::setProperty(const QString& name, const QVariant& value) +{ + mpv::qt::set_property_variant(mpv, name, value); +} + +QVariant MpvWidget::getProperty(const QString &name) const +{ + return mpv::qt::get_property_variant(mpv, name); +} + +void MpvWidget::initializeGL() +{ + mpv_opengl_init_params gl_init_params{get_proc_address, nullptr, nullptr}; + mpv_render_param params[]{ + {MPV_RENDER_PARAM_API_TYPE, const_cast<char *>(MPV_RENDER_API_TYPE_OPENGL)}, + {MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_init_params}, + {MPV_RENDER_PARAM_INVALID, nullptr} + }; + + if (mpv_render_context_create(&mpv_gl, mpv, params) < 0) + throw std::runtime_error("failed to initialize mpv GL context"); + mpv_render_context_set_update_callback(mpv_gl, MpvWidget::on_update, reinterpret_cast<void *>(this)); +} + +void MpvWidget::paintGL() +{ + mpv_opengl_fbo mpfbo{static_cast<int>(defaultFramebufferObject()), width(), height(), 0}; + int flip_y{1}; + + mpv_render_param params[] = { + {MPV_RENDER_PARAM_OPENGL_FBO, &mpfbo}, + {MPV_RENDER_PARAM_FLIP_Y, &flip_y}, + {MPV_RENDER_PARAM_INVALID, nullptr} + }; + // See render_gl.h on what OpenGL environment mpv expects, and + // other API details. + mpv_render_context_render(mpv_gl, params); +} + +void MpvWidget::on_mpv_events() +{ + // Process all events, until the event queue is empty. + while (mpv) { + mpv_event *event = mpv_wait_event(mpv, 0); + if (event->event_id == MPV_EVENT_NONE) { + break; + } + handle_mpv_event(event); + } +} + +void MpvWidget::handle_mpv_event(mpv_event *event) +{ + switch (event->event_id) { + case MPV_EVENT_PROPERTY_CHANGE: { + mpv_event_property *prop = (mpv_event_property *)event->data; + if (strcmp(prop->name, "time-pos") == 0) { + if (prop->format == MPV_FORMAT_DOUBLE) { + double time = *(double *)prop->data; + Q_EMIT positionChanged((int)time); + } + } else if (strcmp(prop->name, "duration") == 0) { + if (prop->format == MPV_FORMAT_DOUBLE) { + double time = *(double *)prop->data; + Q_EMIT durationChanged((int)time); + } + } + break; + } + default: ; + // Ignore uninteresting or unknown events. + } +} + +// Make Qt invoke mpv_render_context_render() to draw a new/updated video frame. +void MpvWidget::maybeUpdate() +{ + // If the Qt window is not visible, Qt's update() will just skip rendering. + // This confuses mpv's render API, and may lead to small occasional + // freezes due to video rendering timing out. + // Handle this by manually redrawing. + // Note: Qt doesn't seem to provide a way to query whether update() will + // be skipped, and the following code still fails when e.g. switching + // to a different workspace with a reparenting window manager. + if (window()->isMinimized()) { + makeCurrent(); + paintGL(); + context()->swapBuffers(context()->surface()); + doneCurrent(); + } else { + update(); + } +} + +void MpvWidget::on_update(void *ctx) +{ + QMetaObject::invokeMethod((MpvWidget*)ctx, "maybeUpdate"); +} diff --git a/src/module/qt_window/mpvwidget.hh b/src/module/qt_window/mpvwidget.hh new file mode 100644 index 00000000..54a1f997 --- /dev/null +++ b/src/module/qt_window/mpvwidget.hh @@ -0,0 +1,42 @@ +#ifndef __LKT_MODULE_QT_WINDOW__ +#define __LKT_MODULE_QT_WINDOW__ + +#include <QtWidgets/QOpenGLWidget> +#include <mpv/client.h> +#include <mpv/render_gl.h> +#include <QtGui> + +#include <lektor/common.h> + +class MpvWidget Q_DECL_FINAL: public QOpenGLWidget +{ + Q_OBJECT +public: + MpvWidget(struct queue *queue, lkt_db *db); + ~MpvWidget(); + void command(const QVariant& params); + void setProperty(const QString& name, const QVariant& value); + QVariant getProperty(const QString& name) const; + QSize sizeHint() const { return QSize(480, 270);} +Q_SIGNALS: + void durationChanged(int value); + void positionChanged(int value); +protected: + void initializeGL() Q_DECL_OVERRIDE; + void paintGL() Q_DECL_OVERRIDE; +private Q_SLOTS: + void on_mpv_events(); + void maybeUpdate(); +public: + void handle_mpv_event(mpv_event *event); + static void on_update(void *ctx); + + mpv_handle *mpv; + mpv_render_context *mpv_gl; + +private: + struct queue *m_queue; + lkt_db *m_db; +}; + +#endif // __LKT_MODULE_QT_WINDOW__ diff --git a/src/module/qt_window/mpvwidget_interface.cpp b/src/module/qt_window/mpvwidget_interface.cpp new file mode 100644 index 00000000..5c6cd9c8 --- /dev/null +++ b/src/module/qt_window/mpvwidget_interface.cpp @@ -0,0 +1,7 @@ +#include "mpvwidget_interface.h" +#include "mpvwidget.hh" + +MpvWidget* +___create_mpv_widget(struct queue *queue, lkt_db* db){ + return new MpvWidget(queue, db); +} diff --git a/src/module/qt_window/mpvwidget_interface.h b/src/module/qt_window/mpvwidget_interface.h new file mode 100644 index 00000000..fa62dd73 --- /dev/null +++ b/src/module/qt_window/mpvwidget_interface.h @@ -0,0 +1,18 @@ +#ifndef __LKT_MODULE_QT_WINDOW_MPVWIDGET_INTERFACE_H__ +#define __LKT_MODULE_QT_WINDOW_MPVWIDGET_INTERFACE_H__ + +#if defined( __cplusplus) +extern "C" { +class MpvWidget; +#else +typedef struct MpvWidget MpvWidget; +#endif + +#include <lektor/common.h> + +MpvWidget* ___create_mpv_widget(struct queue *queue, lkt_db* db); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/module/qt_window/qthelper.cpp b/src/module/qt_window/qthelper.cpp new file mode 100644 index 00000000..09c28c10 --- /dev/null +++ b/src/module/qt_window/qthelper.cpp @@ -0,0 +1,353 @@ +#ifndef LIBMPV_QTHELPER_H_ +#define LIBMPV_QTHELPER_H_ + +#include <mpv/client.h> + +#include <cstring> + +#include <QVariant> +#include <QString> +#include <QList> +#include <QHash> +#include <QSharedPointer> +#include <QMetaType> + +namespace mpv { +namespace qt { + +// Wrapper around mpv_handle. Does refcounting under the hood. +class Handle +{ + struct container { + container(mpv_handle *h) : mpv(h) {} + ~container() { mpv_terminate_destroy(mpv); } + mpv_handle *mpv; + }; + QSharedPointer<container> sptr; +public: + // Construct a new Handle from a raw mpv_handle with refcount 1. If the + // last Handle goes out of scope, the mpv_handle will be destroyed with + // mpv_terminate_destroy(). + // Never destroy the mpv_handle manually when using this wrapper. You + // will create dangling pointers. Just let the wrapper take care of + // destroying the mpv_handle. + // Never create multiple wrappers from the same raw mpv_handle; copy the + // wrapper instead (that's what it's for). + static Handle FromRawHandle(mpv_handle *handle) { + Handle h; + h.sptr = QSharedPointer<container>(new container(handle)); + return h; + } + + // Return the raw handle; for use with the libmpv C API. + operator mpv_handle*() const { return sptr ? (*sptr).mpv : 0; } +}; + +static inline QVariant node_to_variant(const mpv_node *node) +{ + switch (node->format) { + case MPV_FORMAT_STRING: + return QVariant(QString::fromUtf8(node->u.string)); + case MPV_FORMAT_FLAG: + return QVariant(static_cast<bool>(node->u.flag)); + case MPV_FORMAT_INT64: + return QVariant(static_cast<qlonglong>(node->u.int64)); + case MPV_FORMAT_DOUBLE: + return QVariant(node->u.double_); + case MPV_FORMAT_NODE_ARRAY: { + mpv_node_list *list = node->u.list; + QVariantList qlist; + for (int n = 0; n < list->num; n++) + qlist.append(node_to_variant(&list->values[n])); + return QVariant(qlist); + } + case MPV_FORMAT_NODE_MAP: { + mpv_node_list *list = node->u.list; + QVariantMap qmap; + for (int n = 0; n < list->num; n++) { + qmap.insert(QString::fromUtf8(list->keys[n]), + node_to_variant(&list->values[n])); + } + return QVariant(qmap); + } + default: // MPV_FORMAT_NONE, unknown values (e.g. future extensions) + return QVariant(); + } +} + +struct node_builder { + node_builder(const QVariant& v) { + set(&node_, v); + } + ~node_builder() { + free_node(&node_); + } + mpv_node *node() { return &node_; } +private: + Q_DISABLE_COPY(node_builder) + mpv_node node_; + mpv_node_list *create_list(mpv_node *dst, bool is_map, int num) { + dst->format = is_map ? MPV_FORMAT_NODE_MAP : MPV_FORMAT_NODE_ARRAY; + mpv_node_list *list = new mpv_node_list(); + dst->u.list = list; + if (!list) + goto err; + list->values = new mpv_node[num](); + if (!list->values) + goto err; + if (is_map) { + list->keys = new char*[num](); + if (!list->keys) + goto err; + } + return list; + err: + free_node(dst); + return NULL; + } + char *dup_qstring(const QString &s) { + QByteArray b = s.toUtf8(); + char *r = new char[b.size() + 1]; + if (r) + std::memcpy(r, b.data(), (size_t)b.size() + 1); + return r; + } + bool test_type(const QVariant &v, QMetaType::Type t) { + // The Qt docs say: "Although this function is declared as returning + // "QVariant::Type(obsolete), the return value should be interpreted + // as QMetaType::Type." + // So a cast really seems to be needed to avoid warnings (urgh). + return static_cast<int>(v.type()) == static_cast<int>(t); + } + void set(mpv_node *dst, const QVariant &src) { + if (test_type(src, QMetaType::QString)) { + dst->format = MPV_FORMAT_STRING; + dst->u.string = dup_qstring(src.toString()); + if (!dst->u.string) + goto fail; + } else if (test_type(src, QMetaType::Bool)) { + dst->format = MPV_FORMAT_FLAG; + dst->u.flag = src.toBool() ? 1 : 0; + } else if (test_type(src, QMetaType::Int) || + test_type(src, QMetaType::LongLong) || + test_type(src, QMetaType::UInt) || + test_type(src, QMetaType::ULongLong)) + { + dst->format = MPV_FORMAT_INT64; + dst->u.int64 = src.toLongLong(); + } else if (test_type(src, QMetaType::Double)) { + dst->format = MPV_FORMAT_DOUBLE; + dst->u.double_ = src.toDouble(); + } else if (src.canConvert<QVariantList>()) { + QVariantList qlist = src.toList(); + mpv_node_list *list = create_list(dst, false, qlist.size()); + if (!list) + goto fail; + list->num = qlist.size(); + for (int n = 0; n < qlist.size(); n++) + set(&list->values[n], qlist[n]); + } else if (src.canConvert<QVariantMap>()) { + QVariantMap qmap = src.toMap(); + mpv_node_list *list = create_list(dst, true, qmap.size()); + if (!list) + goto fail; + list->num = qmap.size(); + for (int n = 0; n < qmap.size(); n++) { + list->keys[n] = dup_qstring(qmap.keys()[n]); + if (!list->keys[n]) { + free_node(dst); + goto fail; + } + set(&list->values[n], qmap.values()[n]); + } + } else { + goto fail; + } + return; + fail: + dst->format = MPV_FORMAT_NONE; + } + void free_node(mpv_node *dst) { + switch (dst->format) { + case MPV_FORMAT_STRING: + delete[] dst->u.string; + break; + case MPV_FORMAT_NODE_ARRAY: + case MPV_FORMAT_NODE_MAP: { + mpv_node_list *list = dst->u.list; + if (list) { + for (int n = 0; n < list->num; n++) { + if (list->keys) + delete[] list->keys[n]; + if (list->values) + free_node(&list->values[n]); + } + delete[] list->keys; + delete[] list->values; + } + delete list; + break; + } + default: ; + } + dst->format = MPV_FORMAT_NONE; + } +}; + +/** + * RAII wrapper that calls mpv_free_node_contents() on the pointer. + */ +struct node_autofree { + mpv_node *ptr; + node_autofree(mpv_node *a_ptr) : ptr(a_ptr) {} + ~node_autofree() { mpv_free_node_contents(ptr); } +}; + +/** + * Return the given property as mpv_node converted to QVariant, or QVariant() + * on error. + * + * @deprecated use get_property() instead + * + * @param name the property name + */ +static inline QVariant get_property_variant(mpv_handle *ctx, const QString &name) +{ + mpv_node node; + if (mpv_get_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, &node) < 0) + return QVariant(); + node_autofree f(&node); + return node_to_variant(&node); +} + +/** + * Set the given property as mpv_node converted from the QVariant argument. + + * @deprecated use set_property() instead + */ +static inline int set_property_variant(mpv_handle *ctx, const QString &name, + const QVariant &v) +{ + node_builder node(v); + return mpv_set_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, node.node()); +} + +/** + * Set the given option as mpv_node converted from the QVariant argument. + * + * @deprecated use set_property() instead + */ +static inline int set_option_variant(mpv_handle *ctx, const QString &name, + const QVariant &v) +{ + node_builder node(v); + return mpv_set_option(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, node.node()); +} + +/** + * mpv_command_node() equivalent. Returns QVariant() on error (and + * unfortunately, the same on success). + * + * @deprecated use command() instead + */ +static inline QVariant command_variant(mpv_handle *ctx, const QVariant &args) +{ + node_builder node(args); + mpv_node res; + if (mpv_command_node(ctx, node.node(), &res) < 0) + return QVariant(); + node_autofree f(&res); + return node_to_variant(&res); +} + +/** + * This is used to return error codes wrapped in QVariant for functions which + * return QVariant. + * + * You can use get_error() or is_error() to extract the error status from a + * QVariant value. + */ +struct ErrorReturn +{ + /** + * enum mpv_error value (or a value outside of it if ABI was extended) + */ + int error; + + ErrorReturn() : error(0) {} + explicit ErrorReturn(int err) : error(err) {} +}; + +/** + * Return the mpv error code packed into a QVariant, or 0 (success) if it's not + * an error value. + * + * @return error code (<0) or success (>=0) + */ +static inline int get_error(const QVariant &v) +{ + if (!v.canConvert<ErrorReturn>()) + return 0; + return v.value<ErrorReturn>().error; +} + +/** + * Return whether the QVariant carries a mpv error code. + */ +static inline bool is_error(const QVariant &v) +{ + return get_error(v) < 0; +} + +/** + * Return the given property as mpv_node converted to QVariant, or QVariant() + * on error. + * + * @param name the property name + * @return the property value, or an ErrorReturn with the error code + */ +static inline QVariant get_property(mpv_handle *ctx, const QString &name) +{ + mpv_node node; + int err = mpv_get_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, &node); + if (err < 0) + return QVariant::fromValue(ErrorReturn(err)); + node_autofree f(&node); + return node_to_variant(&node); +} + +/** + * Set the given property as mpv_node converted from the QVariant argument. + * + * @return mpv error code (<0 on error, >= 0 on success) + */ +static inline int set_property(mpv_handle *ctx, const QString &name, + const QVariant &v) +{ + node_builder node(v); + return mpv_set_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, node.node()); +} + +/** + * mpv_command_node() equivalent. + * + * @param args command arguments, with args[0] being the command name as string + * @return the property value, or an ErrorReturn with the error code + */ +static inline QVariant command(mpv_handle *ctx, const QVariant &args) +{ + node_builder node(args); + mpv_node res; + int err = mpv_command_node(ctx, node.node(), &res); + if (err < 0) + return QVariant::fromValue(ErrorReturn(err)); + node_autofree f(&res); + return node_to_variant(&res); +} + +} +} + +Q_DECLARE_METATYPE(mpv::qt::ErrorReturn) + +#endif -- GitLab