[vlc-commits] [Git][videolan/vlc][master] 3 commits: qt: split DialogModel

Steve Lhomme (@robUx4) gitlab at videolan.org
Tue Oct 15 13:10:20 UTC 2024



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
f5255c06 by Pierre Lamot at 2024-10-15T12:40:21+00:00
qt: split DialogModel

This allows blocking early messages until the UI is ready to show the dialogs

- - - - -
db6dc2d0 by Pierre Lamot at 2024-10-15T12:40:21+00:00
qt: factorise module stubbing in tests

- - - - -
e4c67e4c by Pierre Lamot at 2024-10-15T12:40:21+00:00
qt: add DialogModel unit tests

- - - - -


11 changed files:

- modules/gui/qt/Makefile.am
- modules/gui/qt/dialogs/dialogs/dialogmodel.cpp
- modules/gui/qt/dialogs/dialogs/dialogmodel.hpp
- modules/gui/qt/dialogs/dialogs/qml/Dialogs.qml
- modules/gui/qt/maininterface/mainui.cpp
- modules/gui/qt/meson.build
- modules/gui/qt/qt.cpp
- modules/gui/qt/tests/test_renderer_manager_model.cpp
- + modules/gui/qt/tests/test_vlc_dialog_model.cpp
- + modules/gui/qt/tests/vlc_stub_modules.cpp
- + modules/gui/qt/tests/vlc_stub_modules.hpp


Changes:

=====================================
modules/gui/qt/Makefile.am
=====================================
@@ -1541,8 +1541,15 @@ TESTS = base_model_test
 if HAVE_QT_QTEST
 
 LIBVLC = -L../../../lib -lvlc
+QT_QTEST_COMMON_cppflags = $(libqt_plugin_la_CPPFLAGS) -I$(builddir)/tests -DTOP_BUILDDIR=\"$(abs_top_builddir)\"
+QT_QTEST_COMMON_cxxflags = $(AM_CXXFLAGS) $(QT_CFLAGS) $(QT_QTEST_CFLAGS) -fPIC $(CXXFLAGS_qt)
+QT_QTEST_COMMON_ldadd = $(QT_LIBS) $(LIBS_qt) $(QT_QTEST_LIBS) $(LIBVLCCORE) $(LIBVLC)
+QT_QTEST_COMMON_ldflags = $(AM_LDFLAGS) $(QT_LDFLAGS) $(QT_QTEST_LDFLAGS)
+
+# test_renderer_manager_model
 
 test_renderer_manager_model_SOURCES = \
+	tests/vlc_stub_modules.cpp \
 	tests/test_renderer_manager_model.cpp \
     util/renderer_manager.hpp util/renderer_manager.cpp
 
@@ -1552,13 +1559,33 @@ nodist_test_renderer_manager_model_SOURCES = \
 
 BUILT_SOURCES += tests/test_renderer_manager_model.moc
 CLEANFILES += tests/test_renderer_manager_model.moc
-test_renderer_manager_model_CPPFLAGS = $(libqt_plugin_la_CPPFLAGS) -I$(builddir)/tests -DTOP_BUILDDIR=\"$(abs_top_builddir)\"
-test_renderer_manager_model_CXXFLAGS = $(AM_CXXFLAGS) $(QT_CFLAGS) $(QT_QTEST_CFLAGS) -fPIC $(CXXFLAGS_qt)
-test_renderer_manager_model_LDADD = $(QT_LIBS) $(LIBS_qt) $(QT_QTEST_LIBS) $(LIBVLCCORE) $(LIBVLC)
-test_renderer_manager_model_LDFLAGS = $(AM_LDFLAGS) $(QT_LDFLAGS) $(QT_QTEST_LDFLAGS)
+test_renderer_manager_model_CPPFLAGS = $(QT_QTEST_COMMON_cppflags)
+test_renderer_manager_model_CXXFLAGS = $(QT_QTEST_COMMON_cxxflags)
+test_renderer_manager_model_LDADD = $(QT_QTEST_COMMON_ldadd)
+test_renderer_manager_model_LDFLAGS = $(QT_QTEST_COMMON_ldflags)
 check_PROGRAMS += test_renderer_manager_model
 TESTS += test_renderer_manager_model
 
+# test_vlc_dialog_model
+
+test_vlc_dialog_model_SOURCES = \
+	tests/vlc_stub_modules.cpp \
+	tests/test_vlc_dialog_model.cpp \
+	dialogs/dialogs/dialogmodel.cpp
+
+nodist_test_vlc_dialog_model_SOURCES = \
+	tests/test_vlc_dialog_model.moc \
+	dialogs/dialogs/dialogmodel.moc.cpp
+
+BUILT_SOURCES += tests/test_vlc_dialog_model.moc
+CLEANFILES += tests/test_vlc_dialog_model.moc
+test_vlc_dialog_model_CPPFLAGS = $(QT_QTEST_COMMON_cppflags)
+test_vlc_dialog_model_CXXFLAGS = $(QT_QTEST_COMMON_cxxflags)
+test_vlc_dialog_model_LDADD = $(QT_QTEST_COMMON_ldadd)
+test_vlc_dialog_model_LDFLAGS = $(QT_QTEST_COMMON_ldflags)
+check_PROGRAMS += test_vlc_dialog_model
+TESTS += test_vlc_dialog_model
+
 endif
 
 QML_LOG_COMPILER = $(builddir)/qml_test -input


=====================================
modules/gui/qt/dialogs/dialogs/dialogmodel.cpp
=====================================
@@ -23,6 +23,7 @@
 // VLC includes
 #include <vlc_dialog.h>
 #include "qt.hpp"
+#include <QThread>
 
 #include "maininterface/mainctx.hpp"
 
@@ -155,115 +156,158 @@ void DialogErrorModel::resetRepeatedMessageCount()
 // DialogModel
 //=================================================================================================
 
-DialogModel::DialogModel(QObject* parent)
-    : QObject(parent)
-{
-}
+//static functions
+namespace {
 
-DialogModel::~DialogModel()
+void onDialogLogin(void * p_data, vlc_dialog_id * dialogId,
+                                       const char * psz_title, const char * psz_text,
+                                       const char * psz_default_username, bool b_ask_store)
 {
-    if (m_ctx)
-        vlc_dialog_provider_set_callbacks(m_ctx->getIntf(), nullptr, nullptr);
+    VLCDialogModel * model = static_cast<VLCDialogModel *>(p_data);
+    model->dialogCallback(dialogId, [=](VLCDialog* provider){
+        emit provider->login(dialogId, psz_title, psz_text, psz_default_username, b_ask_store);
+    });
+
 }
 
-MainCtx* DialogModel::getCtx() const
+void onDialogQuestion(void * p_data, vlc_dialog_id * dialogId,
+                                          const char * psz_title, const char * psz_text,
+                                          vlc_dialog_question_type i_type,
+                                          const char * psz_cancel, const char * psz_action1,
+                                          const char * psz_action2)
 {
-    return m_ctx;
+    auto model = static_cast<VLCDialogModel *>(p_data);
+    model->dialogCallback(dialogId, [=](VLCDialog* provider){
+        emit provider->question(
+            dialogId, psz_title, psz_text,
+            static_cast<VLCDialog::QuestionType>(i_type), psz_cancel,
+            psz_action1, psz_action2);
+    });
 }
 
-void DialogModel::setCtx(MainCtx* ctx)
+void onDialogProgress(void * p_data, vlc_dialog_id * dialogId,
+                                          const char * psz_title, const char * psz_text,
+                                          bool b_indeterminate, float f_position,
+                                          const char * psz_cancel)
 {
-    if (ctx == m_ctx)
-        return;
-    if (ctx) {
-        m_ctx = ctx;
-
-        const vlc_dialog_cbs cbs =
-        {
-            onLogin, onQuestion, onProgress, onCancelled, onProgressUpdated
-        };
-        vlc_dialog_provider_set_callbacks(ctx->getIntf(), &cbs, this);
-    } else {
-        if (m_ctx)
-            vlc_dialog_provider_set_callbacks(m_ctx->getIntf(), nullptr, nullptr);
-
-        m_ctx = nullptr;
-    }
-    emit ctxChanged();
+    auto model = static_cast<VLCDialogModel*>(p_data);
+    model->dialogCallback(dialogId, [=](VLCDialog* provider){
+        emit provider->progress(dialogId, psz_title, psz_text, b_indeterminate, f_position, psz_cancel);
+    });
 }
 
-//-------------------------------------------------------------------------------------------------
-// Interface
-//-------------------------------------------------------------------------------------------------
-
-/* Q_INVOKABLE */ void DialogModel::post_login(DialogId dialogId, const QString & username,
-                                               const QString & password, bool store)
+void onDialogProgressUpdated(void * p_data, vlc_dialog_id * dialogId,
+                                                 float f_value, const char * psz_text)
 {
-    vlc_dialog_id_post_login(dialogId.m_id, qtu(username), qtu(password), store);
+    auto model = static_cast<VLCDialogModel*>(p_data);
+    model->dialogCallback(dialogId, [=](VLCDialog* provider){
+        emit provider->progressUpdated(dialogId, f_value, psz_text);
+    });
 }
 
-/* Q_INVOKABLE */ void DialogModel::post_action1(DialogId dialogId)
+void onDialogCancelled(void * p_data, vlc_dialog_id * dialogId)
 {
-    vlc_dialog_id_post_action(dialogId.m_id, 1);
+    auto model = static_cast<VLCDialogModel*>(p_data);
+    model->dialogCallback(dialogId, [=](VLCDialog* provider){
+        emit provider->cancelled(dialogId);
+    });
 }
 
-/* Q_INVOKABLE */ void DialogModel::post_action2(DialogId dialogId)
-{
-    vlc_dialog_id_post_action(dialogId.m_id, 2);
 }
 
-/* Q_INVOKABLE */ void DialogModel::dismiss(DialogId dialogId)
+VLCDialogModel::VLCDialogModel(qt_intf_t* intf, QObject* parent)
+    : QObject(parent)
+    , m_intf(intf)
 {
-    vlc_dialog_id_dismiss(dialogId.m_id);
+    const vlc_dialog_cbs cbs =
+        {
+            onDialogLogin, onDialogQuestion, onDialogProgress,
+            onDialogCancelled, onDialogProgressUpdated
+        };
+    vlc_dialog_provider_set_callbacks(intf, &cbs, this);
 }
 
-//-------------------------------------------------------------------------------------------------
-// Private static functions
-//-------------------------------------------------------------------------------------------------
-
-
-/* static */ void DialogModel::onLogin(void * p_data, vlc_dialog_id * dialogId,
-                                       const char * psz_title, const char * psz_text,
-                                       const char * psz_default_username, bool b_ask_store)
+VLCDialogModel::~VLCDialogModel()
 {
-    DialogModel * model = static_cast<DialogModel *>(p_data);
+    m_shuttingDown = true;
+    m_providerWait.wakeAll();
+    vlc_dialog_provider_set_callbacks(m_intf, nullptr, nullptr);
 
-    emit model->login(dialogId, psz_title, psz_text, psz_default_username, b_ask_store);
+    //ensure that we are not destroying ourselve before
+    {
+        QMutexLocker lock(&m_lock);
+        while (m_pendingDialog > 0)
+            m_pendingDialogCond.wait(&m_lock);
+    }
 }
 
-/* static */ void DialogModel::onQuestion(void * p_data, vlc_dialog_id * dialogId,
-                                          const char * psz_title, const char * psz_text,
-                                          vlc_dialog_question_type i_type,
-                                          const char * psz_cancel, const char * psz_action1,
-                                          const char * psz_action2)
+VLCDialog* VLCDialogModel::getProvider() const
 {
-    DialogModel * model = static_cast<DialogModel *>(p_data);
-
-    emit model->question(dialogId, psz_title, psz_text, static_cast<int>(i_type), psz_cancel,
-                         psz_action1, psz_action2);
+    return m_provider;
 }
 
-/* static */ void DialogModel::onProgress(void * p_data, vlc_dialog_id * dialogId,
-                                          const char * psz_title, const char * psz_text,
-                                          bool b_indeterminate, float f_position,
-                                          const char * psz_cancel)
+void VLCDialogModel::setProvider(VLCDialog* provider)
 {
-    DialogModel * model = static_cast<DialogModel *>(p_data);
+    if (m_provider == provider)
+        return;
+    if (m_provider)
+    {
+        disconnect(m_provider, nullptr, this, nullptr);
+    }
+    m_provider = provider;
+    if (m_provider)
+    {
+        connect(m_provider, &VLCDialog::post_login,
+                this, [](DialogId dialogId, const QString & username,
+                        const QString & password, bool store){
+                    vlc_dialog_id_post_login(dialogId.m_id, qtu(username), qtu(password), store);
+                });
+        connect(m_provider, &VLCDialog::post_action1,
+                this, [](DialogId dialogId){
+                    vlc_dialog_id_post_action(dialogId.m_id, 1);
+                });
+        connect(m_provider, &VLCDialog::post_action2,
+                this, [](DialogId dialogId){
+                    vlc_dialog_id_post_action(dialogId.m_id, 2);
+                });
+        connect(m_provider, &VLCDialog::dismiss,
+                this, [](DialogId dialogId){
+                    vlc_dialog_id_dismiss(dialogId.m_id);
+                });
 
-    emit model->progress(dialogId, psz_title, psz_text, b_indeterminate, f_position, psz_cancel);
+    }
+    m_providerWait.wakeAll();
+    emit providerChanged();
 }
 
-/* static */ void DialogModel::onProgressUpdated(void * p_data, vlc_dialog_id * dialogId,
-                                                 float f_value, const char * psz_text)
+
+void VLCDialogModel::dialogCallback(vlc_dialog_id * dialogId, std::function<void(VLCDialog* provider)> callback)
 {
-    DialogModel * model = static_cast<DialogModel *>(p_data);
+    QMutexLocker lock(&m_lock);
+    m_pendingDialog++;
+    //dialogs are synchronous calls, if they are spawned from qt thread
+    //(which shouldn't happen), we can't wait for m_provider
+    if (QThread::currentThread() == this->thread() && m_provider == nullptr)
+    {
+        vlc_dialog_id_dismiss(dialogId);
+        m_pendingDialog--;
+        m_pendingDialogCond.wakeOne();
+        return;
+    }
 
-    emit model->progressUpdated(dialogId, f_value, psz_text);
-}
+    while (m_provider == nullptr && !m_shuttingDown)
+        m_providerWait.wait(&m_lock);
 
-/* static */ void DialogModel::onCancelled(void * p_data, vlc_dialog_id * dialogId)
-{
-    DialogModel * model = static_cast<DialogModel *>(p_data);
+    if (m_shuttingDown)
+    {
+        vlc_dialog_id_dismiss(dialogId);
+        m_pendingDialog--;
+        m_pendingDialogCond.wakeOne();
+        return;
+    }
+
+    callback(m_provider);
 
-    emit model->cancelled(dialogId);
+    m_pendingDialog--;
+    m_pendingDialogCond.wakeOne();
 }


=====================================
modules/gui/qt/dialogs/dialogs/dialogmodel.hpp
=====================================
@@ -31,6 +31,9 @@
 
 // Qt includes
 #include <QAbstractListModel>
+#include <QQmlEngine>
+#include <QMutex>
+#include <QWaitCondition>
 
 #include "qt.hpp"
 #include "util/singleton.hpp"
@@ -119,59 +122,40 @@ private: // Variables
     friend class Singleton<DialogErrorModel>;
 };
 
-class DialogModel : public QObject
+/**
+ * this class expose vlc_dialog events and allow to reply
+ * to use it, instantiate the object, connect the signals then
+ * register it in VLCDialogModel
+ */
+class VLCDialog: public QObject
 {
     Q_OBJECT
 
-    Q_PROPERTY(MainCtx* ctx READ getCtx WRITE setCtx NOTIFY ctxChanged FINAL)
-
-public: // Enums
-    // NOTE: Is it really useful to have this declared here ?
-    enum QuestionType { QUESTION_NORMAL, QUESTION_WARNING, QUESTION_CRITICAL };
-    Q_ENUM(QuestionType)
-
 public:
-    explicit DialogModel(QObject *parent = nullptr);
-    ~DialogModel();
+    enum QuestionType {
+        QUESTION_NORMAL = VLC_DIALOG_QUESTION_NORMAL,
+        QUESTION_WARNING = VLC_DIALOG_QUESTION_WARNING,
+        QUESTION_CRITICAL = VLC_DIALOG_QUESTION_CRITICAL
+    };
+    Q_ENUM(QuestionType)
 
-public: // Interface
+signals:
+    //dialog user actions
     Q_INVOKABLE void post_login(DialogId dialogId, const QString & username,
-                                const QString & password, bool store = false);
+                    const QString & password, bool store = false);
 
     Q_INVOKABLE void post_action1(DialogId dialogId);
     Q_INVOKABLE void post_action2(DialogId dialogId);
 
     Q_INVOKABLE void dismiss(DialogId dialogId);
 
-private: // Static functions
-    static void onLogin(void * p_data, vlc_dialog_id * dialogId, const char * psz_title,
-                        const char * psz_text, const char * psz_default_username,
-                        bool b_ask_store);
-
-    static void onQuestion(void * p_data, vlc_dialog_id * dialogId, const char * psz_title,
-                           const char * psz_text, vlc_dialog_question_type i_type,
-                           const char * psz_cancel, const char * psz_action1,
-                           const char * psz_action2);
+    //dialog request
 
-    static void onProgress(void * p_data, vlc_dialog_id * dialogId, const char * psz_title,
-                           const char * psz_text, bool b_indeterminate, float f_position,
-                           const char *psz_cancel);
-
-    static void onProgressUpdated(void * p_data, vlc_dialog_id * dialogId, float f_value,
-                                  const char * psz_text);
-
-    static void onCancelled(void * p_data, vlc_dialog_id * dialogId);
-
-public:
-    MainCtx* getCtx() const;
-    void setCtx(MainCtx*);
-
-signals:
     void login(DialogId dialogId, const QString & title,
                const QString & text, const QString & defaultUsername,
                bool b_ask_store);
 
-    void question(DialogId dialogId, const QString & title, const QString & text, int type,
+    void question(DialogId dialogId, const QString & title, const QString & text, QuestionType type,
                   const QString & cancel, const QString & action1, const QString & action2);
 
     void progress(DialogId dialogId, const QString & title, const QString & text,
@@ -180,11 +164,46 @@ signals:
     void progressUpdated(DialogId dialogId, float f_value, const QString & text);
 
     void cancelled(DialogId dialogId);
+};
 
-    void ctxChanged();
+/**
+ * This class listen to vlc_dialog_t events and forward them to VLCDialog
+ */
+class VLCDialogModel : public QObject, public Singleton<VLCDialogModel>
+{
+    Q_OBJECT
+
+    Q_PROPERTY(VLCDialog* provider READ getProvider WRITE setProvider NOTIFY providerChanged FINAL)
+
+public:
+    explicit VLCDialogModel(qt_intf_t* intf, QObject *parent = nullptr);
+    ~VLCDialogModel();
+
+public:
+    VLCDialog* getProvider() const;
+    void setProvider(VLCDialog*);
+
+public:
+    //block dialog until m_provider is available the call the callback on it
+    void dialogCallback(vlc_dialog_id*, std::function<void(VLCDialog* provider)>);
+
+signals:
+    void providerChanged();
 
 private:
-    MainCtx* m_ctx = nullptr;
+    qt_intf_t* m_intf = nullptr;
+    QMutex  m_lock;
+    //waiting for the provider to be set
+    QWaitCondition m_providerWait;
+
+    //during destruction, waiting for dialogCallback to finish
+    QWaitCondition m_pendingDialogCond;
+    unsigned m_pendingDialog = 0;
+
+    VLCDialog* m_provider = nullptr;
+    bool m_shuttingDown = false;
+
+    friend class Singleton<VLCDialogModel>;
 };
 
 #endif // DIALOGMODEL_HPP


=====================================
modules/gui/qt/dialogs/dialogs/qml/Dialogs.qml
=====================================
@@ -53,10 +53,10 @@ Item {
 
     Component.onDestruction: {
         if (questionDialog.dialogId !== null) {
-            dialogModel.dismiss(questionDialog.dialogId)
+            vlcDialog.dismiss(questionDialog.dialogId)
             questionDialog.dialogId = null
         } if (loginDialog.dialogId !== null) {
-            dialogModel.dismiss(loginDialog.dialogId)
+            vlcDialog.dismiss(loginDialog.dialogId)
             loginDialog.dialogId = null
         }
     }
@@ -73,18 +73,26 @@ Item {
     // Connections
     //---------------------------------------------------------------------------------------------
 
-    Connections
+    VLCDialog
     {
-        target: dialogModel
+        id: vlcDialog
+
+        Component.onCompleted: {
+            VLCDialogModel.provider = vlcDialog
+        }
+
+        Component.onDestruction: {
+            VLCDialogModel.provider = null
+        }
 
-        function onLogin(dialogId, title, text, defaultUsername, askStore) {
+        onLogin: (dialogId, title, text, defaultUsername, askStore) => {
             loginDialog.dialogId = dialogId
             loginDialog.title = title
             loginDialog.defaultUsername = defaultUsername
             loginDialog.open()
         }
 
-        function onQuestion(dialogId, title, text, type, cancel, action1, action2) {
+        onQuestion: (dialogId, title, text, type, cancel, action1, action2) => {
             questionDialog.dialogId = dialogId
             questionDialog.title = title
             questionDialog.text = text
@@ -94,7 +102,7 @@ Item {
             questionDialog.open()
         }
 
-        function onProgress(dialogId, title, text, indeterminate, position, cancel) {
+        onProgress: (dialogId, title, text, indeterminate, position, cancel) => {
             progressDialog.dialogId = dialogId
             progressDialog.title = title
             progressDialog.text = text
@@ -104,7 +112,7 @@ Item {
             progressDialog.open()
         }
 
-        function onProgressUpdated(dialogId, position, text) {
+        onProgressUpdated: (dialogId, position, text) => {
             if (progressDialog.dialogId !== dialogId) {
                 console.warn("progress event on an inexisting dialog")
                 return
@@ -113,21 +121,21 @@ Item {
             progressDialog.position = position
         }
 
-        function onCancelled(dialogId) {
+        onCancelled: (dialogId) => {
             if (questionDialog.dialogId === dialogId) {
                 questionDialog.close()
                 questionDialog.dialogId = null
-                dialogModel.dismiss(dialogId)
+                dismiss(dialogId)
             } else if (loginDialog.dialogId === dialogId)  {
                 loginDialog.close()
                 loginDialog.dialogId = null
-                dialogModel.dismiss(dialogId)
+                dismiss(dialogId)
             } else if (progressDialog.dialogId === dialogId) {
                 progressDialog.close()
                 progressDialog.dialogId = null
-                dialogModel.dismiss(dialogId)
+                dismiss(dialogId)
             } else {
-                dialogModel.dismiss(dialogId)
+                dismiss(dialogId)
             }
         }
     }
@@ -146,12 +154,6 @@ Item {
     // Childs
     //---------------------------------------------------------------------------------------------
 
-    DialogModel {
-        id: dialogModel
-        ctx: MainCtx
-    }
-
-
     Widgets.DrawerExt {
         id: errorPopup
 
@@ -391,13 +393,13 @@ Item {
 
         onAccepted: {
             if (loginDialog.dialogId !== null) {
-                dialogModel.post_login(loginDialog.dialogId, username.text, password.text, savePassword.checked)
+                vlcDialog.post_login(loginDialog.dialogId, username.text, password.text, savePassword.checked)
                 loginDialog.dialogId = null
             }
         }
         onRejected: {
             if (loginDialog.dialogId !== null) {
-                dialogModel.dismiss(loginDialog.dialogId)
+                vlcDialog.dismiss(loginDialog.dialogId)
                 loginDialog.dialogId = null
             }
         }
@@ -466,7 +468,7 @@ Item {
                     text: progressDialog.cancelTxt
 
                     onClicked: {
-                        dialogModel.dismiss(progressDialog.dialogId)
+                        vlcDialog.dismiss(progressDialog.dialogId)
                         progressDialog.dialogId = null
                         progressDialog.close()
                     }
@@ -529,7 +531,7 @@ Item {
                         Keys.onPressed: (event) => Navigation.defaultKeyAction(event)
 
                         onClicked: {
-                            dialogModel.dismiss(questionDialog.dialogId)
+                            vlcDialog.dismiss(questionDialog.dialogId)
                             questionDialog.dialogId = null
                             questionDialog.close()
                         }
@@ -548,7 +550,7 @@ Item {
                         Keys.onPressed: (event) => Navigation.defaultKeyAction(event)
 
                         onClicked: {
-                            dialogModel.post_action1(questionDialog.dialogId)
+                            vlcDialog.post_action1(questionDialog.dialogId)
                             questionDialog.dialogId = null
                             questionDialog.close()
                         }
@@ -565,7 +567,7 @@ Item {
                         Keys.onPressed: (event) => Navigation.defaultKeyAction(event)
 
                         onClicked: {
-                            dialogModel.post_action2(questionDialog.dialogId)
+                            vlcDialog.post_action2(questionDialog.dialogId)
                             questionDialog.dialogId = null
                             questionDialog.close()
                         }


=====================================
modules/gui/qt/maininterface/mainui.cpp
=====================================
@@ -128,6 +128,8 @@ MainUI::MainUI(qt_intf_t *p_intf, MainCtx *mainCtx, QWindow* interfaceWindow,  Q
     assert(m_intf->p_mainPlaylistController);
     SingletonRegisterHelper<PlaylistController>::setInstance(m_intf->p_mainPlaylistController);
 
+    assert(VLCDialogModel::getInstance<false>());
+    SingletonRegisterHelper<VLCDialogModel>::setInstance(VLCDialogModel::getInstance<false>());
     assert(DialogsProvider::getInstance());
     SingletonRegisterHelper<DialogsProvider>::setInstance(DialogsProvider::getInstance());
 
@@ -252,7 +254,8 @@ void MainUI::registerQMLTypes()
 
         // @uri VLC.Dialogs
         qmlRegisterType<AboutModel>( uri, versionMajor, versionMinor, "AboutModel" );
-        qmlRegisterType<DialogModel>(uri, versionMajor, versionMinor, "DialogModel");
+        qmlRegisterType<VLCDialog>( uri, versionMajor, versionMinor, "VLCDialog" );
+        qmlRegisterSingletonType<VLCDialogModel>(uri, versionMajor, versionMinor, "VLCDialogModel", SingletonRegisterHelper<VLCDialogModel>::callback);
         qmlRegisterUncreatableType<DialogId>( uri, versionMajor, versionMinor, "dialogId", "");
         qmlRegisterSingletonType<DialogsProvider>(uri, versionMajor, versionMinor, "DialogsProvider", SingletonRegisterHelper<DialogsProvider>::callback);
         qmlRegisterSingletonType<DialogErrorModel>(uri, versionMajor, versionMinor, "DialogErrorModel", SingletonRegisterHelper<DialogErrorModel>::callback);


=====================================
modules/gui/qt/meson.build
=====================================
@@ -1107,6 +1107,7 @@ if qt6_dep.found()
         vlc_tests += {
             'name': 'test_qt_renderer_manager',
             'sources': files(
+                'tests/vlc_stub_modules.cpp',
                 'tests/test_renderer_manager_model.cpp',
                 'util/renderer_manager.hpp',
                 'util/renderer_manager.cpp'
@@ -1120,7 +1121,28 @@ if qt6_dep.found()
             'suite': ['qt'],
             'include_directories' : qt_include_dir,
             'link_with': [libvlccore, libvlc],
+            'dependencies': [qt6_dep, qt_extra_deps, qtest_qt6_dep]
+        }
+
+        vlc_tests += {
+            'name': 'test_qt_dialog_model',
+            'sources': files(
+                'tests/vlc_stub_modules.cpp',
+                'tests/test_vlc_dialog_model.cpp',
+                'dialogs/dialogs/dialogmodel.hpp',
+                'dialogs/dialogs/dialogmodel.cpp'
+            ),
+            'moc_sources': files(
+                'tests/test_vlc_dialog_model.cpp'
+            ),
+            'moc_headers': files(
+                'dialogs/dialogs/dialogmodel.hpp'
+            ),
+            'suite': ['qt'],
+            'include_directories' : qt_include_dir,
+            'link_with': [libvlccore, libvlc],
             'dependencies': [qt6_dep, qt_extra_deps, qtest_qt6_dep],
         }
+
     endif
 endif


=====================================
modules/gui/qt/qt.cpp
=====================================
@@ -1010,6 +1010,7 @@ static void *Thread( void *obj )
     app.setDesktopFileName( PACKAGE );
 
     DialogErrorModel::getInstance( p_intf );
+    VLCDialogModel::getInstance( p_intf );
 
     /* Initialize the Dialog Provider and the Main Input Manager */
     DialogsProvider::getInstance( p_intf );
@@ -1143,6 +1144,7 @@ static void *ThreadCleanup( qt_intf_t *p_intf, CleanupReason cleanupReason )
      */
     DialogsProvider::killInstance();
 
+    VLCDialogModel::killInstance();
     DialogErrorModel::killInstance();
 
     /* Destroy the main playlist controller */


=====================================
modules/gui/qt/tests/test_renderer_manager_model.cpp
=====================================
@@ -15,123 +15,17 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
  *****************************************************************************/
-#ifdef HAVE_CONFIG_H
-# include <config.h>
-#endif
-
-/* Define a builtin module for mocked parts */
-#define MODULE_NAME renderer_manager_test
-#undef VLC_DYNAMIC_PLUGIN
-
-#include "../../../../test/libvlc/test.h"
-
-#include <vlc/vlc.h>
-
-#include <vlc_common.h>
-#include <vlc_plugin.h>
-#include <vlc_playlist.h>
-#include <vlc_services_discovery.h>
-#include <vlc_renderer_discovery.h>
-#include <vlc_probe.h>
-#include <vlc_interface.h>
-#include <vlc_player.h>
-
-#include "../../../../lib/libvlc_internal.h"
-
-#include "qt.hpp"
-
-namespace vlc {
-class Compositor {};
-}
-
-static vlc_renderer_discovery_t* g_rd = nullptr;
-static bool g_rd_probe_enabled = false;
-
-static int OpenRD( vlc_object_t* p_this )
-{
-    g_rd = (vlc_renderer_discovery_t *)p_this;
-    return VLC_SUCCESS;
-}
-
-static void CloseRD( vlc_object_t* )
-{
-    g_rd = nullptr;
-
-}
-
-static int vlc_rd_probe_open(vlc_object_t *obj) {
-    auto probe = (struct vlc_probe_t *)obj;
-
-    if (g_rd_probe_enabled)
-        vlc_rd_probe_add(probe, "rd", "a fake renderer for testing purpose");
-    //only probe ourself
-    return VLC_PROBE_STOP;
-}
-
-static qt_intf_t* g_intf = nullptr;
-
-static int OpenIntf(vlc_object_t* p_this)
-{
-    auto intfThread = reinterpret_cast<intf_thread_t*>(p_this);
-    libvlc_int_t* libvlc = vlc_object_instance( p_this );
-
-    /* Ensure initialization of objects in qt_intf_t. */
-    g_intf = vlc_object_create<qt_intf_t>( libvlc );
-    if (!g_intf)
-        return VLC_ENOMEM;
-
-    g_intf->obj.logger = vlc_LogHeaderCreate(libvlc->obj.logger, "qt");
-    if (!g_intf->obj.logger)
-    {
-        vlc_object_delete(g_intf);
-        return VLC_EGENERIC;
-    }
-    g_intf->intf = intfThread;
-    intfThread->p_sys = reinterpret_cast<intf_sys_t*>(g_intf);
-    return VLC_SUCCESS;
-}
-
-static void CloseIntf( vlc_object_t *p_this )
-{
-    intf_thread_t* intfThread = (intf_thread_t*)(p_this);
-    auto p_intf = reinterpret_cast<qt_intf_t*>(intfThread->p_sys);
-    if (!p_intf)
-        return;
-    vlc_LogDestroy(p_intf->obj.logger);
-    vlc_object_delete(p_intf);
-}
-
-vlc_module_begin()
-    set_callbacks(OpenIntf, CloseIntf)
-    set_capability("interface", 0)
-add_submodule()
-    set_capability("renderer_discovery", 0)
-    add_shortcut("rd")
-    set_callbacks(OpenRD, CloseRD)
-add_submodule()
-    set_capability("renderer probe", 10000)
-    set_callback(vlc_rd_probe_open)
-vlc_module_end()
-
-extern "C" {
-
-const char vlc_module_name[] = MODULE_STRING;
-VLC_EXPORT vlc_plugin_cb vlc_static_modules[] = {
-    VLC_SYMBOL(vlc_entry),
-    NULL
-};
-}
-
 #include <QTest>
 #include <QAbstractItemModelTester>
+
+#include "vlc_stub_modules.hpp"
+
 #include "../util/renderer_manager.hpp"
-#include <vlc_cxx_helpers.hpp>
 
 using RendererItemPtr = vlc_shared_data_ptr_type(vlc_renderer_item_t,
     vlc_renderer_item_hold,
     vlc_renderer_item_release);
 
-
 class TestClass : public QObject
 {
     Q_OBJECT
@@ -143,7 +37,7 @@ private:
         RendererItemPtr item(vlc_renderer_item_new(
                 "type", qtu(name), qtu(sout), "extra sout",
                 nullptr, "icon://", i ));
-        vlc_rd_add_item( g_rd, item.get() );
+        vlc_rd_add_item( rd(), item.get() );
         return item;
     }
 
@@ -158,47 +52,49 @@ private:
         QCOMPARE(m_model->data(idx, RendererManager::FLAGS), id);
     }
 
+    vlc_renderer_discovery_t* rd() const
+    {
+        return m_env->renderDiscovery;
+    }
+
 private slots:
     void initTestCase() {
-        test_init();
-
-        m_vlc = libvlc_new(test_defaults_nargs, test_defaults_args);
-        libvlc_InternalAddIntf(m_vlc->p_libvlc_int, MODULE_STRING);
-        libvlc_InternalPlay(m_vlc->p_libvlc_int);
-
-        m_playlist = vlc_intf_GetMainPlaylist(g_intf->intf);
-        m_player = vlc_playlist_GetPlayer( m_playlist );
+        m_env = std::make_unique<VLCTestingEnv>();
+        QVERIFY(m_env->init());
+        m_player = m_env->intf->p_player;
     }
 
     void cleanupTestCase() {
-        libvlc_release(m_vlc);
+        m_player = nullptr;
+        m_env.reset();
     }
 
     void init() {
-        g_rd_probe_enabled = true;
-        m_model = new RendererManager(g_intf, m_player);
+        m_env->renderDiscoveryProbeEnabled = true;
+
+        m_model = new RendererManager(m_env->intf, m_player);
         //QAbstractItemModelTester checks that QAbstractItemModel events are coherents
-        m_modelTester = new QAbstractItemModelTester(m_model);
-        QVERIFY(g_rd == nullptr);
+        m_modelTester = std::make_unique<QAbstractItemModelTester>(m_model);
+        QVERIFY(rd() == nullptr);
     }
 
     void cleanup() {
-        delete m_modelTester;
+        m_modelTester.reset();
         delete m_model;
-        QVERIFY(g_rd == nullptr);
+        QVERIFY(rd() == nullptr);
     }
 
     void testEmpty() {
-        QVERIFY(g_rd == nullptr);
+        QVERIFY(rd() == nullptr);
         //model is empty before scan
         QCOMPARE(m_model->rowCount(), 0);
         m_model->StartScan();
-        QVERIFY(g_rd != nullptr);
+        QVERIFY(rd() != nullptr);
         QCOMPARE(m_model->rowCount(), 0);
         QCOMPARE(m_model->getStatus(), RendererManager::RUNNING);
         //scan didn't find anything
         m_model->StopScan();
-        QVERIFY(g_rd == nullptr);
+        QVERIFY(rd() == nullptr);
         QCOMPARE(m_model->rowCount(), 0);
         QCOMPARE(m_model->getStatus(), RendererManager::IDLE);
     }
@@ -209,7 +105,7 @@ private slots:
         QCOMPARE(m_model->rowCount(), 0);
         QCOMPARE(m_model->getStatus(), RendererManager::RUNNING);
 
-        QVERIFY(g_rd != nullptr);
+        QVERIFY(rd() != nullptr);
 
         for (int i = 0; i < 5; ++i) {
             pushDummyRDItem(i);
@@ -227,12 +123,12 @@ private slots:
         }
 
         //module is closed
-        QVERIFY(g_rd == nullptr);
+        QVERIFY(rd() == nullptr);
     }
 
     void testTwoPassesIdentical() {
         m_model->StartScan();
-        QVERIFY(g_rd != nullptr);
+        QVERIFY(rd() != nullptr);
         QCOMPARE(m_model->rowCount(), 0);
         for (int i = 0; i < 5; ++i) {
             pushDummyRDItem(i);
@@ -290,7 +186,7 @@ private slots:
             pushDummyRDItem(i);
         }
         QCOMPARE(m_model->rowCount(), 7);
-        vlc_rd_remove_item(g_rd, item.get());
+        vlc_rd_remove_item(rd(), item.get());
         QCOMPARE(m_model->rowCount(), 6);
         m_model->StopScan();
         QCOMPARE(m_model->rowCount(), 6);
@@ -423,7 +319,7 @@ private slots:
 
         //selected item is removed
         QCOMPARE(m_model->rowCount(), 5);
-        vlc_rd_remove_item( g_rd, r3.get() );
+        vlc_rd_remove_item( rd(), r3.get() );
 
         //item is held by model
         QCOMPARE(m_model->useRenderer(), true);
@@ -439,11 +335,11 @@ private slots:
 
         //item should be gone after next scan
         m_model->StartScan();
-        vlc_rd_add_item( g_rd, r0.get() );
-        vlc_rd_add_item( g_rd, r1.get() );
-        vlc_rd_add_item( g_rd, r2.get() );
+        vlc_rd_add_item( rd(), r0.get() );
+        vlc_rd_add_item( rd(), r1.get() );
+        vlc_rd_add_item( rd(), r2.get() );
         //no r3
-        vlc_rd_add_item( g_rd, r4.get() );
+        vlc_rd_add_item( rd(), r4.get() );
         m_model->StopScan();
         QCOMPARE(m_model->rowCount(), 4);
 
@@ -451,8 +347,8 @@ private slots:
         selidx = m_model->index(2);
         m_model->setData(selidx, true, RendererManager::SELECTED);
         m_model->StartScan();
-        vlc_rd_add_item( g_rd, r0.get() );
-        vlc_rd_add_item( g_rd, r1.get() );
+        vlc_rd_add_item( rd(), r0.get() );
+        vlc_rd_add_item( rd(), r1.get() );
         m_model->StopScan();
         QCOMPARE(m_model->rowCount(), 3);
         QCOMPARE(m_model->data(selidx, RendererManager::SELECTED), true);
@@ -472,20 +368,20 @@ private slots:
 
     //failed state when no renderer manager is found
     void testNoProbes() {
-        g_rd_probe_enabled = false;
+        m_env->renderDiscoveryProbeEnabled = false;
         QCOMPARE(m_model->getStatus(), RendererManager::IDLE);
         m_model->StartScan();
         QCOMPARE(m_model->getStatus(), RendererManager::FAILED);
         QCOMPARE(m_model->rowCount(), 0);
-        QVERIFY(g_rd == nullptr);
-        g_rd_probe_enabled = true;
+        QVERIFY(rd() == nullptr);
+        m_env->renderDiscoveryProbeEnabled = true;
     }
 private:
-    libvlc_instance_t* m_vlc = nullptr;
-    vlc_playlist_t* m_playlist = nullptr;
+
+    std::unique_ptr<VLCTestingEnv> m_env;
     vlc_player_t* m_player = nullptr;
-    QAbstractItemModelTester* m_modelTester = nullptr;
-    RendererManager* m_model;
+    std::unique_ptr<QAbstractItemModelTester> m_modelTester;
+    RendererManager* m_model = nullptr;
 };
 
 QTEST_GUILESS_MAIN(TestClass)


=====================================
modules/gui/qt/tests/test_vlc_dialog_model.cpp
=====================================
@@ -0,0 +1,370 @@
+/*****************************************************************************
+ * Copyright (C) 2024 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#include "vlc_stub_modules.hpp"
+
+#include <QTest>
+#include <QSignalSpy>
+#include <QThreadPool>
+#include "dialogs/dialogs/dialogmodel.hpp"
+#include <vlc_cxx_helpers.hpp>
+
+#include "../util/renderer_manager.hpp"
+
+class TestVLCDialogModel : public QObject
+{
+    Q_OBJECT
+
+    enum UserAnswer {
+        Dismiss,
+        PostLogin,
+        PostAction1,
+        PostAction2
+    };
+
+private slots:
+    void initTestCase() {
+        m_env = std::make_unique<VLCTestingEnv>();
+        QVERIFY(m_env->init());
+        m_intf = m_env->intf;
+    }
+
+    void cleanupTestCase() {
+        m_intf = nullptr;
+        m_env.reset();
+    }
+
+    void init() {
+        VLCDialogModel::getInstance(m_intf);
+        m_dialog = new VLCDialog();
+        m_loginSpy = std::make_unique<QSignalSpy>(m_dialog, &VLCDialog::login);
+        m_questionSpy = std::make_unique<QSignalSpy>(m_dialog, &VLCDialog::question);
+        m_progressSpy = std::make_unique<QSignalSpy>(m_dialog, &VLCDialog::progress);
+        m_progressUpdatedSpy = std::make_unique<QSignalSpy>(m_dialog, &VLCDialog::progressUpdated);
+        m_canceledSpy = std::make_unique<QSignalSpy>(m_dialog, &VLCDialog::cancelled);
+    }
+
+    void cleanup() {
+        m_loginSpy.reset();
+        m_questionSpy.reset();
+        m_progressSpy.reset();
+        m_progressUpdatedSpy.reset();
+        m_canceledSpy.reset();
+        VLCDialogModel* model = VLCDialogModel::getInstance<false>();
+        if (model) {
+            model->setProvider(nullptr);
+            VLCDialogModel::killInstance();
+        }
+        delete m_dialog;
+    }
+
+    void testLogin_data() {
+        QTest::addColumn<UserAnswer>("userReply");
+        QTest::addColumn<int>("expectedReply");
+
+        QTest::newRow("login") << PostLogin << 1;
+        QTest::newRow("dismiss") << Dismiss << 0;
+        QTest::newRow("wrongAnswer") << PostAction1 << VLC_EGENERIC;
+    }
+
+    //failed state when no renderer manager is found
+    void testLogin() {
+        QFETCH(UserAnswer, userReply);
+        QFETCH(int, expectedReply);
+
+        QObject::connect(m_dialog, &VLCDialog::login, this, [&](
+            DialogId dialogId, const QString & title,
+            const QString & text, const QString & defaultUsername,
+            bool){
+
+            QCOMPARE(defaultUsername, "default username");
+            QCOMPARE(title, "login title");
+            QCOMPARE(text, "login message");
+
+            switch(userReply)
+            {
+            case Dismiss:
+                m_dialog->dismiss(dialogId);
+                break;
+            case PostLogin:
+                m_dialog->post_login(dialogId, "username", "hunter2", false);
+                break;
+            case PostAction1:
+            default:
+                //bad behavior
+                m_dialog->post_action1(dialogId);
+                break;
+
+            }
+        });
+
+        VLCDialogModel::getInstance<false>()->setProvider(m_dialog);
+
+        std::atomic<bool> replied = false;
+        int reply = -1;
+        QString repliedUsername;
+        QString repliedPassword;
+        bool repliedStore = true;
+        QThreadPool::globalInstance()->start([&](){
+            char* username = nullptr;
+            char* password = nullptr;
+            reply = vlc_dialog_wait_login(m_intf, &username, &password, &repliedStore,
+                                  "default username", "login title",
+                                  "login message");
+            if (reply == 1) {
+                repliedUsername = username;
+                repliedPassword = password;
+                free(username);
+                free(password);
+            }
+
+            replied = true;
+        });
+
+        QVERIFY(QTest::qWaitFor([&replied](){ return replied == true; }));
+        if (expectedReply == VLC_EGENERIC)
+            QCOMPARE_LT(reply, 0);
+        else
+            QCOMPARE(reply, expectedReply);
+
+        if (reply == 1) {
+            QCOMPARE(repliedUsername, "username");
+            QCOMPARE(repliedPassword, "hunter2");
+            QCOMPARE(repliedStore, false);
+        }
+
+        QCOMPARE(m_questionSpy->count(), 0);
+        QCOMPARE(m_progressSpy->count(), 0);
+        QCOMPARE(m_progressUpdatedSpy->count(), 0);
+        QCOMPARE(m_canceledSpy->count(), 0);
+    }
+
+    void testQuestion_data() {
+        QTest::addColumn<UserAnswer>("userReply");
+        QTest::addColumn<int>("expectedReply");
+
+        QTest::newRow("action1") << PostAction1 << 1;
+        QTest::newRow("action2") << PostAction2 << 2;
+        QTest::newRow("dismiss") << Dismiss << 0;
+        QTest::newRow("wrongAnswer") << PostLogin << VLC_EGENERIC;
+    }
+
+    //failed state when no renderer manager is found
+    void testQuestion() {
+        QFETCH(UserAnswer, userReply);
+        QFETCH(int, expectedReply);
+
+        QObject::connect(m_dialog, &VLCDialog::question, this,
+                         [&](DialogId dialogId, const QString& title,
+                            const QString& text, VLCDialog::QuestionType type,
+                            const QString& cancel, const QString& action1, const QString& action2){
+
+            QCOMPARE(action1, "action1 txt");
+            QCOMPARE(action2, "action2 txt");
+            QCOMPARE(cancel, "cancel txt");
+            QCOMPARE(title, "title");
+            QCOMPARE(text, "message");
+            QCOMPARE(type, VLCDialog::QuestionType::QUESTION_WARNING);
+
+            switch(userReply)
+            {
+            case PostAction1:
+                m_dialog->post_action1(dialogId);
+                break;
+            case PostAction2:
+                m_dialog->post_action2(dialogId);
+                break;
+            case Dismiss:
+                m_dialog->dismiss(dialogId);
+                break;
+            case PostLogin:
+                m_dialog->post_login(dialogId, "not", "expected"); //user answer the type
+                break;
+            }
+        });
+
+        VLCDialogModel::getInstance<false>()->setProvider(m_dialog);
+
+        std::atomic<bool> replied = false;
+        int reply = -1;
+        QThreadPool::globalInstance()->start([&](){
+            reply = vlc_dialog_wait_question(m_intf, VLC_DIALOG_QUESTION_WARNING,
+                                             "cancel txt", "action1 txt",
+                                             "action2 txt", "title", "message");
+            replied = true;
+        });
+        QVERIFY(QTest::qWaitFor([&replied](){ return replied == true; }));
+        if (expectedReply == VLC_EGENERIC)
+            QCOMPARE_LT(reply, 0);
+        else
+            QCOMPARE(reply, expectedReply);
+
+        QCOMPARE(m_loginSpy->count(), 0);
+        QCOMPARE(m_progressSpy->count(), 0);
+        QCOMPARE(m_progressUpdatedSpy->count(), 0);
+        QCOMPARE(m_canceledSpy->count(), 0);
+    }
+
+    //failed state when no renderer manager is found
+    void testProgress() {
+        QObject::connect(m_dialog, &VLCDialog::progress, this,
+                         [&](DialogId, const QString& title, const QString& text,
+                                bool b_indeterminate, float f_position, const QString& cancel){
+
+            //QCOMPARE performs is fuzzy for floats
+            QCOMPARE(b_indeterminate, false);
+            QCOMPARE(f_position, 0.f);
+            QCOMPARE(cancel, "cancel txt");
+            QCOMPARE(title, "title");
+            QCOMPARE(text, "message");
+        });
+
+        QObject::connect(m_dialog, &VLCDialog::progressUpdated, this,
+                         [&](DialogId, float f_position, const QString& text){
+
+            //QCOMPARE performs is fuzzy for floats
+            QCOMPARE(f_position, 0.3f);
+            QCOMPARE(text, "updated text");
+        });
+
+        VLCDialogModel::getInstance<false>()->setProvider(m_dialog);
+
+        std::atomic<bool> replied = false;
+        vlc_dialog_id* dialogId;
+        QThreadPool::globalInstance()->start([&](){
+            dialogId = vlc_dialog_display_progress(m_intf,
+                                                   false, 0.f,
+                                                   "cancel txt", "title", "message");
+            replied = true;
+        });
+        QVERIFY(QTest::qWaitFor([&replied](){ return replied == true; }));
+        QVERIFY(dialogId != nullptr);
+
+        replied = false;
+        int reply;
+        QThreadPool::globalInstance()->start([&](){
+            reply = vlc_dialog_update_progress_text(m_intf, dialogId, 0.3f, "updated text");
+            replied = true;
+        });
+        QVERIFY(QTest::qWaitFor([&replied](){ return replied == true; }));
+        QCOMPARE(reply, VLC_SUCCESS);
+        QCOMPARE(m_canceledSpy->count(), 0);
+
+        replied = false;
+        QThreadPool::globalInstance()->start([&](){
+            vlc_dialog_release(m_intf, dialogId);
+            replied = true;
+        });
+        QVERIFY(QTest::qWaitFor([&replied](){ return replied == true; }));
+        QCOMPARE(m_canceledSpy->count(), 1);
+
+        QCOMPARE(m_loginSpy->count(), 0);
+        QCOMPARE(m_questionSpy->count(), 0);
+    }
+
+    void testLateBinding() {
+        QObject::connect(m_dialog, &VLCDialog::question, this,
+                         [&](DialogId dialogId, const QString& ,
+                             const QString& , VLCDialog::QuestionType ,
+                             const QString& , const QString& , const QString& ){
+            m_dialog->post_action1(dialogId);
+        });
+
+        std::atomic<bool> replied = false;
+        int reply = -1;
+        QThreadPool::globalInstance()->start([&](){
+            reply = vlc_dialog_wait_question(m_intf, VLC_DIALOG_QUESTION_WARNING,
+                                             "cancel txt", "action1 txt",
+                                             "action2 txt", "title", "message");
+            replied = true;
+        });
+        //wait for the question to be asked
+        //the dialog should block until VLCDialog is provided
+        QTest::qWait(20);
+
+        //set the dialog afterwards
+        VLCDialogModel::getInstance<false>()->setProvider(m_dialog);
+
+        QVERIFY(QTest::qWaitFor([&replied](){ return replied == true; }));
+        //user replied with Action1
+        QCOMPARE(reply, 1);
+
+        QCOMPARE(m_loginSpy->count(), 0);
+        QCOMPARE(m_progressSpy->count(), 0);
+        QCOMPARE(m_progressUpdatedSpy->count(), 0);
+        QCOMPARE(m_canceledSpy->count(), 0);
+    }
+
+    void testModelDestruction() {
+        std::atomic<bool> replied = false;
+        int reply = -1;
+        QThreadPool::globalInstance()->start([&](){
+            reply = vlc_dialog_wait_question(m_intf, VLC_DIALOG_QUESTION_WARNING,
+                                             "cancel txt", "action1 txt",
+                                             "action2 txt", "title", "message");
+            replied = true;
+        });
+        //wait for the question to be asked
+        //the dialog should block until VLCDialog is provided
+        QTest::qWait(20);
+
+        VLCDialogModel::killInstance();
+
+        QVERIFY(QTest::qWaitFor([&replied](){ return replied == true; }));
+
+        QCOMPARE(reply, 0);
+    }
+
+    void testSameThreadWithoutDialog()
+    {
+        int reply = vlc_dialog_wait_question(m_intf, VLC_DIALOG_QUESTION_WARNING,
+                                             "cancel txt", "action1 txt",
+                                             "action2 txt", "title", "message");
+        QCOMPARE(reply, 0);
+    }
+
+    void testSameThreadWithDialog()
+    {
+        QObject::connect(m_dialog, &VLCDialog::question, this,
+                         [&](DialogId dialogId, const QString& ,
+                             const QString& , VLCDialog::QuestionType ,
+                             const QString& , const QString& , const QString& ){
+            m_dialog->post_action1(dialogId);
+        });
+        VLCDialogModel::getInstance<false>()->setProvider(m_dialog);
+
+        int reply = vlc_dialog_wait_question(m_intf, VLC_DIALOG_QUESTION_WARNING,
+                                             "cancel txt", "action1 txt",
+                                             "action2 txt", "title", "message");
+        QCOMPARE(reply, 1);
+    }
+
+private:
+    std::unique_ptr<VLCTestingEnv> m_env;
+    qt_intf_t* m_intf = nullptr;
+
+    VLCDialog* m_dialog = nullptr;
+
+    std::unique_ptr<QSignalSpy> m_loginSpy;
+    std::unique_ptr<QSignalSpy> m_questionSpy;
+    std::unique_ptr<QSignalSpy> m_progressSpy;
+    std::unique_ptr<QSignalSpy> m_progressUpdatedSpy;
+    std::unique_ptr<QSignalSpy> m_canceledSpy;
+};
+
+QTEST_GUILESS_MAIN(TestVLCDialogModel)
+#include "test_vlc_dialog_model.moc"


=====================================
modules/gui/qt/tests/vlc_stub_modules.cpp
=====================================
@@ -0,0 +1,182 @@
+/*****************************************************************************
+ * Copyright (C) 2024 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* Define builtin modules for mocked parts */
+#define MODULE_NAME module_faker
+#undef VLC_DYNAMIC_PLUGIN
+
+#include <vlc/vlc.h>
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_playlist.h>
+#include <vlc_services_discovery.h>
+#include <vlc_renderer_discovery.h>
+#include <vlc_probe.h>
+#include <vlc_interface.h>
+#include <vlc_player.h>
+
+#include "../../../../lib/libvlc_internal.h"
+
+#include "qt.hpp"
+
+
+#include "vlc_stub_modules.hpp"
+
+static VLCTestingEnv* testenv = nullptr;
+
+///RenderDiscovery module
+
+static int OpenRD( vlc_object_t* p_this )
+{
+    testenv->renderDiscovery = (vlc_renderer_discovery_t *)p_this;
+    return VLC_SUCCESS;
+}
+
+static void CloseRD( vlc_object_t* )
+{
+    testenv->renderDiscovery = nullptr;
+}
+
+static int vlc_rd_probe_open(vlc_object_t *obj) {
+    auto probe = (struct vlc_probe_t *)obj;
+
+    if (testenv->renderDiscoveryProbeEnabled)
+        vlc_rd_probe_add(probe, "rd", "a fake renderer for testing purpose");
+    //only probe ourself
+    return VLC_PROBE_STOP;
+}
+
+///Interface module
+
+namespace vlc {
+class Compositor {};
+}
+
+static int OpenIntf(vlc_object_t* p_this)
+{
+    auto intfThread = reinterpret_cast<intf_thread_t*>(p_this);
+    libvlc_int_t* libvlc = vlc_object_instance( p_this );
+
+    /* Ensure initialization of objects in qt_intf_t. */
+    qt_intf_t* qt_intf = vlc_object_create<qt_intf_t>( libvlc );
+    if (!qt_intf)
+        return VLC_ENOMEM;
+
+    qt_intf->obj.logger = vlc_LogHeaderCreate(libvlc->obj.logger, "qt");
+    if (!qt_intf->obj.logger)
+    {
+        vlc_object_delete(qt_intf);
+        return VLC_EGENERIC;
+    }
+
+    qt_intf->p_playlist = vlc_intf_GetMainPlaylist(intfThread);
+    qt_intf->p_player = vlc_playlist_GetPlayer(qt_intf->p_playlist);
+    qt_intf->intf = intfThread;
+
+    intfThread->p_sys = reinterpret_cast<intf_sys_t*>(qt_intf);
+
+    testenv->intf = qt_intf;
+
+    return VLC_SUCCESS;
+}
+
+static void CloseIntf( vlc_object_t *p_this )
+{
+    intf_thread_t* intfThread = (intf_thread_t*)(p_this);
+    auto qt_intf = reinterpret_cast<qt_intf_t*>(intfThread->p_sys);
+    if (!qt_intf)
+        return;
+    vlc_LogDestroy(qt_intf->obj.logger);
+    vlc_object_delete(qt_intf);
+
+    testenv->intf = nullptr;
+}
+
+//module declaration
+
+vlc_module_begin()
+    set_callbacks(OpenIntf, CloseIntf)
+    set_capability("interface", 0)
+add_submodule()
+    set_capability("renderer_discovery", 0)
+    add_shortcut("rd")
+    set_callbacks(OpenRD, CloseRD)
+add_submodule()
+    set_capability("renderer probe", 10000)
+    set_callback(vlc_rd_probe_open)
+vlc_module_end()
+
+extern "C" {
+
+const char vlc_module_name[] = MODULE_STRING;
+VLC_EXPORT vlc_plugin_cb vlc_static_modules[] = {
+    VLC_SYMBOL(vlc_entry),
+    NULL
+};
+}
+
+VLCTestingEnv::VLCTestingEnv()
+{
+    assert(testenv == nullptr);
+    testenv = this;
+
+}
+
+VLCTestingEnv::~VLCTestingEnv()
+{
+    libvlc_release(libvlc);
+    testenv = nullptr;
+}
+
+static const char * test_defaults_args[] = {
+    "-v", "--vout=vdummy", "--aout=adummy", "--text-renderer=tdummy",
+};
+
+static const int test_defaults_nargs =
+    sizeof (test_defaults_args) / sizeof (test_defaults_args[0]);
+
+
+bool VLCTestingEnv::init()
+{
+    //see test/libvlc/test.h
+    QByteArray alarm_timeout = qgetenv("VLC_TEST_TIMEOUT");
+    if (alarm_timeout.isEmpty())
+        qputenv("QTEST_FUNCTION_TIMEOUT", "5000");
+    else
+        qputenv("QTEST_FUNCTION_TIMEOUT", alarm_timeout);
+
+    setenv("VLC_PLUGIN_PATH", TOP_BUILDDIR"/modules", 1);
+    setenv("VLC_LIB_PATH", TOP_BUILDDIR, 1);
+
+    libvlc = libvlc_new(test_defaults_nargs, test_defaults_args);
+    if (!libvlc)
+        return false;
+    libvlc_InternalAddIntf(libvlc->p_libvlc_int, MODULE_STRING);
+    libvlc_InternalPlay(libvlc->p_libvlc_int);
+
+    if (!intf)
+        return false;
+
+    return true;
+}
+
+


=====================================
modules/gui/qt/tests/vlc_stub_modules.hpp
=====================================
@@ -0,0 +1,75 @@
+/*****************************************************************************
+ * Copyright (C) 2024 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifndef QT_VLC_STUB_MODULES
+#define QT_VLC_STUB_MODULES
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <vlc/vlc.h>
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_player.h>
+#include <vlc_playlist.h>
+#include <vlc_services_discovery.h>
+#include <vlc_renderer_discovery.h>
+#include <vlc_interface.h>
+#include <vlc_cxx_helpers.hpp>
+
+#include "qt.hpp"
+
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 4, 0)
+#define QCOMPARE_LT(a, b) QVERIFY((a) < (b))
+#define QCOMPARE_GT(a, b) QVERIFY((a) > (b))
+#define QCOMPARE_LE(a, b) QVERIFY((a) <= (b))
+#define QCOMPARE_GE(a, b) QVERIFY((a) >= (b))
+#endif
+
+/**
+ * This class allows to instanciate a libvlc instance with stubbed modules
+ * the modules instance can be reteived and manipulated using their pointer
+ * This allows testing models depending on vlc in a controlled environment
+ * validity of the pointers depends of the module usage
+ */
+struct VLCTestingEnv
+{
+    VLCTestingEnv();
+    ~VLCTestingEnv();
+
+    bool init();
+
+    //should be valid after init
+    libvlc_instance_t* libvlc = nullptr;
+
+    //pointer to the qt interface pointer
+    //no qt related objects are defined in it (no MainCtx, no Compositor ...)
+    //should be valid after init
+    qt_intf_t* intf = nullptr;
+
+    //render discovery instance (valid once created)
+    vlc_renderer_discovery_t* renderDiscovery = nullptr;
+
+    //if set to false no render discovery will be found
+    bool renderDiscoveryProbeEnabled = true;
+};
+
+#endif /* QT_VLC_STUB_MODULES */



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/e85ead7660e52ee721607b7bed7fa8a2e7696292...e4c67e4cec31015e80174e09ef880b18cfdfba49

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/e85ead7660e52ee721607b7bed7fa8a2e7696292...e4c67e4cec31015e80174e09ef880b18cfdfba49
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance


More information about the vlc-commits mailing list