/* Copyright (C) 2017 the mpv developers * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MPV_CLIENT_API_QTHELPER_H_ #define MPV_CLIENT_API_QTHELPER_H_ #include #if !MPV_ENABLE_DEPRECATED #error "This helper is deprecated. Copy it into your project instead." #else /** * Note: these helpers are provided for convenience for C++/Qt applications. * This is based on the public API in client.h, and it does not encode any * knowledge that is not known or guaranteed outside of the C client API. You * can even copy and modify this code as you like, or implement similar things * for other languages. */ #include #include #include #include #include #include #include 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 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(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(node->u.flag)); case MPV_FORMAT_INT64: return QVariant(static_cast(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(), 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(v.type()) == static_cast(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 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 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); } }; #if MPV_ENABLE_DEPRECATED /** * 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); } #endif /** * 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()) return 0; return v.value().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 /* else #if MPV_ENABLE_DEPRECATED */ #endif