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