File indexing completed on 2024-12-15 04:23:42

0001 /*
0002  * SPDX-FileCopyrightText: https://github.com/mpv-player
0003  *
0004  * SPDX-License-Identifier: WTFPL
0005  */
0006 
0007 #ifndef LIBMPV_QTHELPER_H_
0008 #define LIBMPV_QTHELPER_H_
0009 
0010 #include <mpv/client.h>
0011 
0012 #include <cstring>
0013 
0014 #include <QVariant>
0015 #include <QString>
0016 #include <QList>
0017 #include <QHash>
0018 #include <QSharedPointer>
0019 #include <QMetaType>
0020 
0021 namespace mpv {
0022 namespace qt {
0023 
0024 // Wrapper around mpv_handle. Does refcounting under the hood.
0025 class Handle
0026 {
0027     struct container {
0028         container(mpv_handle *h) : mpv(h) {}
0029         ~container() { mpv_terminate_destroy(mpv); }
0030         mpv_handle *mpv;
0031     };
0032     QSharedPointer<container> sptr;
0033 public:
0034     // Construct a new Handle from a raw mpv_handle with refcount 1. If the
0035     // last Handle goes out of scope, the mpv_handle will be destroyed with
0036     // mpv_terminate_destroy().
0037     // Never destroy the mpv_handle manually when using this wrapper. You
0038     // will create dangling pointers. Just let the wrapper take care of
0039     // destroying the mpv_handle.
0040     // Never create multiple wrappers from the same raw mpv_handle; copy the
0041     // wrapper instead (that's what it's for).
0042     static Handle FromRawHandle(mpv_handle *handle) {
0043         Handle h;
0044         h.sptr = QSharedPointer<container>(new container(handle));
0045         return h;
0046     }
0047 
0048     // Return the raw handle; for use with the libmpv C API.
0049     operator mpv_handle*() const { return sptr ? (*sptr).mpv : 0; }
0050 };
0051 
0052 static inline QVariant node_to_variant(const mpv_node *node)
0053 {
0054     switch (node->format) {
0055     case MPV_FORMAT_STRING:
0056         return QVariant(QString::fromUtf8(node->u.string));
0057     case MPV_FORMAT_FLAG:
0058         return QVariant(static_cast<bool>(node->u.flag));
0059     case MPV_FORMAT_INT64:
0060         return QVariant(static_cast<qlonglong>(node->u.int64));
0061     case MPV_FORMAT_DOUBLE:
0062         return QVariant(node->u.double_);
0063     case MPV_FORMAT_NODE_ARRAY: {
0064         mpv_node_list *list = node->u.list;
0065         QVariantList qlist;
0066         for (int n = 0; n < list->num; n++)
0067             qlist.append(node_to_variant(&list->values[n]));
0068         return QVariant(qlist);
0069     }
0070     case MPV_FORMAT_NODE_MAP: {
0071         mpv_node_list *list = node->u.list;
0072         QVariantMap qmap;
0073         for (int n = 0; n < list->num; n++) {
0074             qmap.insert(QString::fromUtf8(list->keys[n]),
0075                         node_to_variant(&list->values[n]));
0076         }
0077         return QVariant(qmap);
0078     }
0079     default: // MPV_FORMAT_NONE, unknown values (e.g. future extensions)
0080         return QVariant();
0081     }
0082 }
0083 
0084 struct node_builder {
0085     node_builder(const QVariant &v) {
0086         set(&node_, v);
0087     }
0088     ~node_builder() {
0089         free_node(&node_);
0090     }
0091     mpv_node *node() { return &node_; }
0092 private:
0093     Q_DISABLE_COPY(node_builder)
0094     mpv_node node_;
0095     mpv_node_list *create_list(mpv_node *dst, bool is_map, int num) {
0096         dst->format = is_map ? MPV_FORMAT_NODE_MAP : MPV_FORMAT_NODE_ARRAY;
0097         mpv_node_list *list = new mpv_node_list();
0098         dst->u.list = list;
0099         if (!list)
0100             goto err;
0101         list->values = new mpv_node[num]();
0102         if (!list->values)
0103             goto err;
0104         if (is_map) {
0105             list->keys = new char*[num]();
0106             if (!list->keys)
0107                 goto err;
0108         }
0109         return list;
0110     err:
0111         free_node(dst);
0112         return NULL;
0113     }
0114     char *dup_qstring(const QString &s) {
0115         QByteArray b = s.toUtf8();
0116         char *r = new char[b.size() + 1];
0117         if (r)
0118             std::memcpy(r, b.data(), b.size() + 1);
0119         return r;
0120     }
0121     bool test_type(const QVariant &v, QMetaType::Type t) {
0122         // The Qt docs say: "Although this function is declared as returning
0123         // "QVariant::Type(obsolete), the return value should be interpreted
0124         // as QMetaType::Type."
0125         // So a cast really seems to be needed to avoid warnings (urgh).
0126         return static_cast<int>(v.type()) == static_cast<int>(t);
0127     }
0128     void set(mpv_node *dst, const QVariant &src) {
0129         if (test_type(src, QMetaType::QString)) {
0130             dst->format = MPV_FORMAT_STRING;
0131             dst->u.string = dup_qstring(src.toString());
0132             if (!dst->u.string)
0133                 goto fail;
0134         } else if (test_type(src, QMetaType::Bool)) {
0135             dst->format = MPV_FORMAT_FLAG;
0136             dst->u.flag = src.toBool() ? 1 : 0;
0137         } else if (test_type(src, QMetaType::Int) ||
0138                    test_type(src, QMetaType::LongLong) ||
0139                    test_type(src, QMetaType::UInt) ||
0140                    test_type(src, QMetaType::ULongLong))
0141         {
0142             dst->format = MPV_FORMAT_INT64;
0143             dst->u.int64 = src.toLongLong();
0144         } else if (test_type(src, QMetaType::Double)) {
0145             dst->format = MPV_FORMAT_DOUBLE;
0146             dst->u.double_ = src.toDouble();
0147         } else if (src.canConvert<QVariantList>()) {
0148             QVariantList qlist = src.toList();
0149             mpv_node_list *list = create_list(dst, false, qlist.size());
0150             if (!list)
0151                 goto fail;
0152             list->num = qlist.size();
0153             for (int n = 0; n < qlist.size(); n++)
0154                 set(&list->values[n], qlist[n]);
0155         } else if (src.canConvert<QVariantMap>()) {
0156             QVariantMap qmap = src.toMap();
0157             mpv_node_list *list = create_list(dst, true, qmap.size());
0158             if (!list)
0159                 goto fail;
0160             list->num = qmap.size();
0161             int n = 0;
0162             for (auto it = qmap.constKeyValueBegin(); it != qmap.constKeyValueEnd(); ++it) {
0163                 list->keys[n] = dup_qstring(it.operator*().first);
0164                 if (!list->keys[n]) {
0165                     free_node(dst);
0166                     goto fail;
0167                 }
0168                 set(&list->values[n], it.operator*().second);
0169                 ++n;
0170             }
0171         } else {
0172             goto fail;
0173         }
0174         return;
0175     fail:
0176         dst->format = MPV_FORMAT_NONE;
0177     }
0178     void free_node(mpv_node *dst) {
0179         switch (dst->format) {
0180         case MPV_FORMAT_STRING:
0181             delete[] dst->u.string;
0182             break;
0183         case MPV_FORMAT_NODE_ARRAY:
0184         case MPV_FORMAT_NODE_MAP: {
0185             mpv_node_list *list = dst->u.list;
0186             if (list) {
0187                 for (int n = 0; n < list->num; n++) {
0188                     if (list->keys)
0189                         delete[] list->keys[n];
0190                     if (list->values)
0191                         free_node(&list->values[n]);
0192                 }
0193                 delete[] list->keys;
0194                 delete[] list->values;
0195             }
0196             delete list;
0197             break;
0198         }
0199         default: ;
0200         }
0201         dst->format = MPV_FORMAT_NONE;
0202     }
0203 };
0204 
0205 /**
0206  * RAII wrapper that calls mpv_free_node_contents() on the pointer.
0207  */
0208 struct node_autofree {
0209     mpv_node *ptr;
0210     node_autofree(mpv_node *a_ptr) : ptr(a_ptr) {}
0211     ~node_autofree() { mpv_free_node_contents(ptr); }
0212 };
0213 
0214 /**
0215  * This is used to return error codes wrapped in QVariant for functions which
0216  * return QVariant.
0217  *
0218  * You can use get_error() or is_error() to extract the error status from a
0219  * QVariant value.
0220  */
0221 struct ErrorReturn
0222 {
0223     /**
0224      * enum mpv_error value (or a value outside of it if ABI was extended)
0225      */
0226     int error;
0227 
0228     ErrorReturn() : error(0) {}
0229     explicit ErrorReturn(int err) : error(err) {}
0230 };
0231 
0232 /**
0233  * Return the mpv error code packed into a QVariant, or 0 (success) if it's not
0234  * an error value.
0235  *
0236  * @return error code (<0) or success (>=0)
0237  */
0238 static inline int get_error(const QVariant &v)
0239 {
0240     if (!v.canConvert<ErrorReturn>())
0241         return 0;
0242     return v.value<ErrorReturn>().error;
0243 }
0244 
0245 /**
0246  * Return whether the QVariant carries a mpv error code.
0247  */
0248 static inline bool is_error(const QVariant &v)
0249 {
0250     return get_error(v) < 0;
0251 }
0252 
0253 /**
0254  * Return the given property as mpv_node converted to QVariant, or QVariant()
0255  * on error.
0256  *
0257  * @param name the property name
0258  * @return the property value, or an ErrorReturn with the error code
0259  */
0260 static inline QVariant get_property(mpv_handle *ctx, const QString &name)
0261 {
0262     mpv_node node;
0263     int err = mpv_get_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, &node);
0264     if (err < 0)
0265         return QVariant::fromValue(ErrorReturn(err));
0266     node_autofree f(&node);
0267     return node_to_variant(&node);
0268 }
0269 
0270 /**
0271  * Set the given property as mpv_node converted from the QVariant argument.
0272  *
0273  * @return mpv error code (<0 on error, >= 0 on success)
0274  */
0275 static inline int set_property(mpv_handle *ctx, const QString &name,
0276                                        const QVariant &v)
0277 {
0278     node_builder node(v);
0279     return mpv_set_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, node.node());
0280 }
0281 
0282 /**
0283  * mpv_command_node() equivalent.
0284  *
0285  * @param args command arguments, with args[0] being the command name as string
0286  * @return the property value, or an ErrorReturn with the error code
0287  */
0288 static inline QVariant command(mpv_handle *ctx, const QVariant &args)
0289 {
0290     node_builder node(args);
0291     mpv_node res;
0292     int err = mpv_command_node(ctx, node.node(), &res);
0293     if (err < 0)
0294         return QVariant::fromValue(ErrorReturn(err));
0295     node_autofree f(&res);
0296     return node_to_variant(&res);
0297 }
0298 
0299 }
0300 }
0301 
0302 Q_DECLARE_METATYPE(mpv::qt::ErrorReturn)
0303 
0304 #endif