File indexing completed on 2024-02-18 15:46:09

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