From 20f8d81f13d51185d730fc4f0abfc1423f0dbecd Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Tue, 28 Nov 2023 13:09:01 +0100 Subject: [PATCH] work --- src/librssguard/gui/dialogs/formmain.cpp | 14 ++- .../gui/mediaplayer/libmpv/libmpvbackend.cpp | 104 +++++++++++------ .../gui/mediaplayer/libmpv/libmpvbackend.h | 4 + .../gui/mediaplayer/libmpv/qthelper.h | 109 +++++++++++++++--- .../services/abstract/serviceentrypoint.h | 2 +- 5 files changed, 174 insertions(+), 59 deletions(-) diff --git a/src/librssguard/gui/dialogs/formmain.cpp b/src/librssguard/gui/dialogs/formmain.cpp index 10c2fa7f9..b1939f94a 100644 --- a/src/librssguard/gui/dialogs/formmain.cpp +++ b/src/librssguard/gui/dialogs/formmain.cpp @@ -528,7 +528,7 @@ void FormMain::switchVisibility(bool force_hide) { QSystemTrayIcon::MessageIcon::Warning}); } else { - close(); + hide(); } } else { @@ -1078,9 +1078,15 @@ void FormMain::changeEvent(QEvent* event) { } void FormMain::closeEvent(QCloseEvent* event) { - QMainWindow::closeEvent(event); + if (qApp->quitOnLastWindowClosed()) { + QMainWindow::closeEvent(event); + } + else /*if (!event->spontaneous())*/ { + event->ignore(); + hide(); + } - qDebugNN << LOGSEC_GUI << "Main window's close event"; + qDebugNN << LOGSEC_GUI << "Main window close event"; } void FormMain::showEvent(QShowEvent* event) { @@ -1092,7 +1098,7 @@ void FormMain::showEvent(QShowEvent* event) { void FormMain::hideEvent(QHideEvent* event) { QMainWindow::hideEvent(event); - qDebugNN << LOGSEC_GUI << "Main window's hide event"; + qDebugNN << LOGSEC_GUI << "Main window hide event"; } void FormMain::showDocs() { diff --git a/src/librssguard/gui/mediaplayer/libmpv/libmpvbackend.cpp b/src/librssguard/gui/mediaplayer/libmpv/libmpvbackend.cpp index 37a9489d5..b2182e772 100644 --- a/src/librssguard/gui/mediaplayer/libmpv/libmpvbackend.cpp +++ b/src/librssguard/gui/mediaplayer/libmpv/libmpvbackend.cpp @@ -33,13 +33,14 @@ LibMpvBackend::LibMpvBackend(QWidget* parent) } // Create a video child window. Force Qt to create a native window, and - // pass the window ID to the mpv wid option. Works on: X11, win32, Cocoa + // pass the window ID to the mpv wid option. Works on: X11, win32, Cocoa. m_mpvContainer->setAttribute(Qt::WidgetAttribute::WA_DontCreateNativeAncestors); m_mpvContainer->setAttribute(Qt::WidgetAttribute::WA_NativeWindow); layout()->addWidget(m_mpvContainer); auto raw_wid = m_mpvContainer->winId(); + #if defined(Q_OS_WIN) // Truncate to 32-bit, as all Windows handles are. This also ensures // it doesn't go negative. @@ -49,10 +50,19 @@ LibMpvBackend::LibMpvBackend(QWidget* parent) #endif mpv_set_option(m_mpvHandle, "wid", MPV_FORMAT_INT64, &wid); - mpv_set_option_string(m_mpvHandle, "input-default-bindings", "yes"); mpv_set_option_string(m_mpvHandle, "msg-level", "all=v"); - mpv::qt::set_option_variant(m_mpvHandle, "hwdec", "auto"); + mpv_set_option_string(m_mpvHandle, "terminal", "yes"); + mpv_set_option_string(m_mpvHandle, "keep-open", "yes"); + // mpv_set_option_string(m_mpvHandle, "input-terminal", "yes"); + mpv_set_option_string(m_mpvHandle, "hwdec", "auto"); + mpv_set_option_string(m_mpvHandle, "osd-playing-msg", "${media-title}"); + mpv_set_option_string(m_mpvHandle, "osc", "yes"); + mpv_set_option_string(m_mpvHandle, "input-cursor", "yes"); + + // mpv_set_option_string(m_mpvHandle, "osd-on-seek", "msg-bar"); + + // mpv::qt::set_option_variant(m_mpvHandle, "hwdec", "auto"); // Enable keyboard input on the X11 window. For the messy details, see // --input-vo-keyboard on the manpage. @@ -62,6 +72,8 @@ LibMpvBackend::LibMpvBackend(QWidget* parent) mpv_observe_property(m_mpvHandle, 0, "time-pos", MPV_FORMAT_DOUBLE); mpv_observe_property(m_mpvHandle, 0, "track-list", MPV_FORMAT_NODE); mpv_observe_property(m_mpvHandle, 0, "chapter-list", MPV_FORMAT_NODE); + mpv_observe_property(m_mpvHandle, 0, "duration", MPV_FORMAT_NODE); + mpv_observe_property(m_mpvHandle, 0, "volume", MPV_FORMAT_NODE); // From this point on, the wakeup function will be called. The callback // can come from any thread, so we use the QueuedConnection mechanism to @@ -71,6 +83,7 @@ LibMpvBackend::LibMpvBackend(QWidget* parent) this, &LibMpvBackend::onMpvEvents, Qt::ConnectionType::QueuedConnection); + mpv_set_wakeup_callback(m_mpvHandle, wakeup, this); if (mpv_initialize(m_mpvHandle) < 0) { @@ -88,25 +101,8 @@ void LibMpvBackend::handleMpvEvent(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; - std::stringstream ss; - ss << "At: " << time; - } - else if (prop->format == MPV_FORMAT_NONE) { - } - } - else if (strcmp(prop->name, "chapter-list") == 0 || strcmp(prop->name, "track-list") == 0) { - // Dump the properties as JSON for demo purposes. - if (prop->format == MPV_FORMAT_NODE) { - QVariant v = mpv::qt::node_to_variant((mpv_node*)prop->data); - // Abuse JSON support for easily printing the mpv_node contents. - QJsonDocument d = QJsonDocument::fromVariant(v); - appendLog("Change property " + QString(prop->name) + ":\n"); - appendLog(d.toJson().data()); - } - } + + processPropertyChange(prop); break; } @@ -128,16 +124,14 @@ void LibMpvBackend::handleMpvEvent(mpv_event* event) { } case MPV_EVENT_LOG_MESSAGE: { - struct mpv_event_log_message* msg = (struct mpv_event_log_message*)event->data; - std::stringstream ss; - ss << "[" << msg->prefix << "] " << msg->level << ": " << msg->text; - qDebugNN << LOGSEC_MPV << QString::fromStdString(ss.str()); - break; + mpv_event_log_message* msg = (mpv_event_log_message*)event->data; + + processLogMessage(msg); } case MPV_EVENT_SHUTDOWN: { mpv_terminate_destroy(m_mpvHandle); - m_mpvHandle = NULL; + m_mpvHandle = nullptr; break; } @@ -159,6 +153,35 @@ void LibMpvBackend::onMpvEvents() { } } +void LibMpvBackend::processPropertyChange(mpv_event_property* prop) { + if (strcmp(prop->name, "time-pos") == 0) { + if (prop->format == MPV_FORMAT_DOUBLE) { + double time = *(double*)prop->data; + std::stringstream ss; + ss << "At: " << time; + } + else if (prop->format == MPV_FORMAT_NONE) { + } + } + else if (strcmp(prop->name, "chapter-list") == 0 || strcmp(prop->name, "track-list") == 0) { + // Dump the properties as JSON for demo purposes. + if (prop->format == MPV_FORMAT_NODE) { + QVariant v = mpv::qt::node_to_variant((mpv_node*)prop->data); + // Abuse JSON support for easily printing the mpv_node contents. + QJsonDocument d = QJsonDocument::fromVariant(v); + appendLog("Change property " + QString(prop->name) + ":\n"); + appendLog(d.toJson().data()); + } + } +} + +void LibMpvBackend::processLogMessage(mpv_event_log_message* msg) { + std::stringstream ss; + ss << "[" << msg->prefix << "] " << msg->level << ": " << msg->text; + + appendLog(QString::fromStdString(ss.str())); +} + void LibMpvBackend::appendLog(const QString& text) { qDebugNN << LOGSEC_MPV << text; } @@ -166,16 +189,27 @@ void LibMpvBackend::appendLog(const QString& text) { bool LibMpvBackend::eventFilter(QObject* watched, QEvent* event) { Q_UNUSED(watched) + if (event->type() == QEvent::Type::ShortcutOverride) { + // NOTE: If user presses key which is application-wide assigned to some + // action, do not propagate the shortcut to application. + event->accept(); + return true; + } + if (event->type() == QEvent::Type::KeyPress) { - char txt = (char)dynamic_cast(event)->key(); - char str[2]; + if (m_mpvHandle != nullptr) { + char txt = (char)dynamic_cast(event)->key(); + char str[2]; - str[0] = txt; - str[1] = '\0'; + str[0] = txt; + str[1] = '\0'; - const char* args[] = {"keypress", str, NULL}; + const char* args[] = {"keypress", str, nullptr}; - mpv_command_async(m_mpvHandle, 0, args); + mpv_command_async(m_mpvHandle, 0, args); + } + + event->accept(); return true; } @@ -185,7 +219,7 @@ bool LibMpvBackend::eventFilter(QObject* watched, QEvent* event) { void LibMpvBackend::playUrl(const QUrl& url) { auto eb = url.toString().toLocal8Bit(); const char* css = eb.data(); - const char* args[] = {"loadfile", css, NULL}; + const char* args[] = {"loadfile", css, nullptr}; mpv_command_async(m_mpvHandle, 0, args); } diff --git a/src/librssguard/gui/mediaplayer/libmpv/libmpvbackend.h b/src/librssguard/gui/mediaplayer/libmpv/libmpvbackend.h index 06af15cf2..6451dbaec 100644 --- a/src/librssguard/gui/mediaplayer/libmpv/libmpvbackend.h +++ b/src/librssguard/gui/mediaplayer/libmpv/libmpvbackend.h @@ -7,6 +7,8 @@ struct mpv_handle; struct mpv_event; +struct mpv_event_property; +struct mpv_event_log_message; class LibMpvBackend : public PlayerBackend { Q_OBJECT @@ -38,6 +40,8 @@ class LibMpvBackend : public PlayerBackend { void launchMpvEvents(); private: + void processPropertyChange(mpv_event_property* prop); + void processLogMessage(mpv_event_log_message* msg); void appendLog(const QString& text); void createPlayer(); void handleMpvEvent(mpv_event* event); diff --git a/src/librssguard/gui/mediaplayer/libmpv/qthelper.h b/src/librssguard/gui/mediaplayer/libmpv/qthelper.h index 818c737ff..973dacd83 100644 --- a/src/librssguard/gui/mediaplayer/libmpv/qthelper.h +++ b/src/librssguard/gui/mediaplayer/libmpv/qthelper.h @@ -1,3 +1,5 @@ +// For license of this file, see /LICENSE.md. + #ifndef LIBMPV_QTHELPER_H_ #define LIBMPV_QTHELPER_H_ @@ -14,7 +16,6 @@ namespace mpv { namespace qt { - // Wrapper around mpv_handle. Does refcounting under the hood. class Handle { struct container { @@ -51,25 +52,34 @@ namespace mpv { 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++) + + 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) @@ -81,9 +91,11 @@ namespace mpv { node_builder(const QVariant& v) { set(&node_, v); } + ~node_builder() { free_node(&node_); } + mpv_node* node() { return &node_; } @@ -91,45 +103,63 @@ namespace mpv { 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) + + if (!list) { goto err; + } + list->values = new mpv_node[num](); - if (!list->values) + + if (!list->values) { goto err; + } + if (is_map) { list->keys = new char*[num](); - if (!list->keys) + + if (!list->keys) { goto err; + } } + return list; + err: free_node(dst); - return NULL; + return nullptr; } + char* dup_qstring(const QString& s) { QByteArray b = s.toUtf8(); char* r = new char[b.size() + 1]; - if (r) + + 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); + return v.typeId() == 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) + + if (!dst->u.string) { goto fail; + } } else if (test_type(src, QMetaType::Bool)) { dst->format = MPV_FORMAT_FLAG; @@ -147,57 +177,80 @@ namespace mpv { else if (src.canConvert()) { QVariantList qlist = src.toList(); mpv_node_list* list = create_list(dst, false, qlist.size()); - if (!list) + + if (!list) { goto fail; + } + list->num = qlist.size(); - for (int n = 0; n < qlist.size(); n++) + + 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) + + 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) + if (list->keys) { delete[] list->keys[n]; - if (list->values) + } + + if (list->values) { free_node(&list->values[n]); + } } + delete[] list->keys; delete[] list->values; } + delete list; break; } + default:; } + dst->format = MPV_FORMAT_NONE; } }; @@ -207,7 +260,9 @@ namespace mpv { */ struct node_autofree { mpv_node* ptr; + node_autofree(mpv_node* a_ptr) : ptr(a_ptr) {} + ~node_autofree() { mpv_free_node_contents(ptr); } @@ -223,8 +278,11 @@ namespace mpv { */ 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) + + if (mpv_get_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, &node) < 0) { return QVariant(); + } + node_autofree f(&node); return node_to_variant(&node); } @@ -236,6 +294,7 @@ namespace mpv { */ 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()); } @@ -246,6 +305,7 @@ namespace mpv { */ 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()); } @@ -258,8 +318,11 @@ namespace mpv { 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) + + if (mpv_command_node(ctx, node.node(), &res) < 0) { return QVariant(); + } + node_autofree f(&res); return node_to_variant(&res); } @@ -288,8 +351,10 @@ namespace mpv { * @return error code (<0) or success (>=0) */ static inline int get_error(const QVariant& v) { - if (!v.canConvert()) + if (!v.canConvert()) { return 0; + } + return v.value().error; } @@ -310,8 +375,11 @@ namespace mpv { 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) + + if (err < 0) { return QVariant::fromValue(ErrorReturn(err)); + } + node_autofree f(&node); return node_to_variant(&node); } @@ -336,8 +404,11 @@ namespace mpv { node_builder node(args); mpv_node res; int err = mpv_command_node(ctx, node.node(), &res); - if (err < 0) + + if (err < 0) { return QVariant::fromValue(ErrorReturn(err)); + } + node_autofree f(&res); return node_to_variant(&res); } diff --git a/src/librssguard/services/abstract/serviceentrypoint.h b/src/librssguard/services/abstract/serviceentrypoint.h index 369ab4c6d..009a89865 100644 --- a/src/librssguard/services/abstract/serviceentrypoint.h +++ b/src/librssguard/services/abstract/serviceentrypoint.h @@ -19,7 +19,7 @@ class ServiceEntryPoint { // into the model. This method can for example display // some kind of first-time configuration dialog inside itself // before returning the root item. - // Returns NULL if initialization of new root cannot be done. + // Returns nullptr if initialization of new root cannot be done. virtual ServiceRoot* createNewRoot() const = 0; // Performs initialization of all service accounts created using this entry