[vlc-commits] [Git][videolan/vlc][master] 9 commits: qt: introduce CSD menu

Steve Lhomme (@robUx4) gitlab at videolan.org
Thu Nov 21 10:05:17 UTC 2024



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
7986a170 by Pierre Lamot at 2024-11-21T09:28:37+00:00
qt: introduce CSD menu

this menu is usually shown by window manager as contextual menu in the topbar

- - - - -
1470c852 by Pierre Lamot at 2024-11-21T09:28:37+00:00
qml: use CSDMenu to handle clicks in the CSD bar and on the banner icon

- - - - -
2a04c86e by Pierre Lamot at 2024-11-21T09:28:37+00:00
qml: lock the topbar when the CSD menu is visible

- - - - -
0d908dc1 by Pierre Lamot at 2024-11-21T09:28:37+00:00
qt: remove setSystemMenuButton from the CSDButtonModel

behavior is now handled by CSDMenu

- - - - -
33f214a5 by Pierre Lamot at 2024-11-21T09:28:37+00:00
qt: move DummyWidget out of compositor X11 util

compositor_x11_utils no longer depends on Qt and can be reused in other modules

- - - - -
f112486e by Pierre Lamot at 2024-11-21T09:28:37+00:00
qt: fix XCB_DAMAGE cflags

- - - - -
61deb09b by Pierre Lamot at 2024-11-21T09:28:37+00:00
qt: add csdmenu module for X11

- - - - -
9bd833f3 by Pierre Lamot at 2024-11-21T09:28:37+00:00
qt: add CSD menu module for wayland

- - - - -
62ff2e00 by Pierre Lamot at 2024-11-21T09:28:37+00:00
qt: add csdmenu module for windows

- - - - -


28 changed files:

- modules/gui/qt/Makefile.am
- modules/gui/qt/maininterface/compositor_wayland_module.c
- modules/gui/qt/maininterface/compositor_wayland_module.h
- modules/gui/qt/maininterface/compositor_x11.cpp
- modules/gui/qt/maininterface/compositor_x11.hpp
- modules/gui/qt/maininterface/compositor_x11_utils.cpp
- modules/gui/qt/maininterface/compositor_x11_utils.hpp
- modules/gui/qt/maininterface/mainctx_win32.cpp
- modules/gui/qt/maininterface/mainui.cpp
- + modules/gui/qt/maininterface/qt_wayland_plugin.c
- + modules/gui/qt/maininterface/qt_x11_plugin.c
- modules/gui/qt/meson.build
- modules/gui/qt/player/qml/TopBar.qml
- modules/gui/qt/qt.cpp
- modules/gui/qt/util/csdbuttonmodel.cpp
- modules/gui/qt/util/csdbuttonmodel.hpp
- + modules/gui/qt/util/csdmenu.cpp
- + modules/gui/qt/util/csdmenu.hpp
- + modules/gui/qt/util/csdmenu_module.h
- + modules/gui/qt/util/csdmenu_wayland.c
- + modules/gui/qt/util/csdmenu_wayland.h
- + modules/gui/qt/util/csdmenu_win32.c
- + modules/gui/qt/util/csdmenu_win32.h
- + modules/gui/qt/util/csdmenu_x11.cpp
- + modules/gui/qt/util/csdmenu_x11.h
- modules/gui/qt/widgets/qml/BannerCone.qml
- modules/gui/qt/widgets/qml/CSDTitlebarTapNDrapHandler.qml
- po/POTFILES.in


Changes:

=====================================
modules/gui/qt/Makefile.am
=====================================
@@ -296,6 +296,7 @@ libqt_plugin_la_SOURCES = \
 	util/covergenerator.hpp \
 	util/imageluminanceextractor.cpp util/imageluminanceextractor.hpp \
 	util/csdbuttonmodel.cpp util/csdbuttonmodel.hpp \
+	util/csdmenu.cpp util/csdmenu.hpp \
 	util/imagehelper.cpp util/imagehelper.hpp \
 	util/keyhelper.cpp util/keyhelper.hpp \
 	util/listcache.hxx util/listcache.hpp \
@@ -470,6 +471,7 @@ nodist_libqt_plugin_la_SOURCES = \
 	util/vlcaccess_image_provider.moc.cpp \
 	util/imageluminanceextractor.moc.cpp \
 	util/csdbuttonmodel.moc.cpp \
+	util/csdmenu.moc.cpp \
 	util/keyhelper.moc.cpp \
 	util/listcache.moc.cpp \
 	util/locallistcacheloader.moc.cpp \
@@ -531,7 +533,9 @@ libqt_plugin_la_SOURCES += \
 	maininterface/compositor_win7.hpp \
 	style/windowsthemeprovider.cpp \
 	maininterface/win32windoweffects_module.cpp \
-	maininterface/win32windoweffects_module.hpp
+	maininterface/win32windoweffects_module.hpp \
+	util/csdmenu_win32.c \
+	util/csdmenu_win32.h
 
 nodist_libqt_plugin_la_SOURCES += maininterface/mainctx_win32.moc.cpp \
 	maininterface/compositor_win7.moc.cpp
@@ -559,7 +563,7 @@ if HAVE_XCB_DAMAGE
 if HAVE_XCB_XFIXES
 
 libqt_plugin_la_CXXFLAGS += -DQT_HAS_X11_COMPOSITOR \
-	$(QT_X11_CFLAGS) $(XCB_RENDER_CFLAGS) $(XCB_COMPOSITE_CFLAGS) $(XLCB_DAMAGE_CFLAGS) $(XCB_XFIXES_CFLAGS)
+	$(QT_X11_CFLAGS) $(XCB_RENDER_CFLAGS) $(XCB_COMPOSITE_CFLAGS) $(XCB_DAMAGE_CFLAGS) $(XCB_XFIXES_CFLAGS)
 
 libqt_plugin_la_LIBADD += $(QT_X11_LIBS) $(XCB_RENDER_LIBS) $(XCB_COMPOSITE_LIBS) $(XCB_DAMAGE_LIBS) $(XCB_XFIXES_LIBS)
 
@@ -579,8 +583,24 @@ nodist_libqt_plugin_la_SOURCES += \
 	maininterface/compositor_x11.moc.cpp \
 	maininterface/compositor_x11_renderclient.moc.cpp \
 	maininterface/compositor_x11_renderwindow.moc.cpp \
-	maininterface/compositor_x11_uisurface.moc.cpp \
-	maininterface/compositor_x11_utils.moc.cpp
+	maininterface/compositor_x11_uisurface.moc.cpp
+
+#extra X11 features
+
+libqt_x11_plugin_la_CXXFLAGS = $(AM_CXXFLAGS) $(XCB_CFLAGS) $(XCB_RENDER_CFLAGS) $(XCB_COMPOSITE_CFLAGS) $(XCB_DAMAGE_CFLAGS) $(XCB_XFIXES_CFLAGS)
+libqt_x11_plugin_la_LIBADD = $(XCB_LIBS) $(XCB_RENDER_LIBS) $(XCB_COMPOSITE_LIBS) $(XCB_DAMAGE_LIBS) $(XCB_XFIXES_LIBS)
+libqt_x11_plugin_la_SOURCES = \
+	maininterface/qt_x11_plugin.c \
+	maininterface/compositor_x11_utils.hpp \
+	maininterface/compositor_x11_utils.cpp \
+	util/csdmenu_module.h \
+	util/csdmenu_x11.cpp \
+	util/csdmenu_x11.h
+
+if ENABLE_QT
+gui_LTLIBRARIES += libqt_x11_plugin.la
+endif
+
 
 endif
 endif
@@ -605,6 +625,7 @@ nodist_libqt_plugin_la_SOURCES += \
 libqt_wayland_plugin_la_CFLAGS = $(AM_CFLAGS) $(WAYLAND_CLIENT_CFLAGS)
 libqt_wayland_plugin_la_LIBADD =  $(WAYLAND_CLIENT_LIBS)
 libqt_wayland_plugin_la_SOURCES = \
+	maininterface/qt_wayland_plugin.c \
 	maininterface/compositor_wayland_module.c \
 	maininterface/compositor_wayland_module.h
 
@@ -612,9 +633,16 @@ libqt_wayland_plugin_la_SOURCES = \
 #if WAYLAND_SCANNER
 libqt_wayland_plugin_la_CFLAGS += -DQT_HAS_WAYLAND_PROTOCOLS -I$(builddir)/maininterface
 
+libqt_wayland_plugin_la_SOURCES += \
+	util/csdmenu_wayland.c \
+	util/csdmenu_wayland.h \
+	util/csdmenu_module.h
+
 nodist_libqt_wayland_plugin_la_SOURCES = \
 	maininterface/viewporter-client-protocol.h \
-	maininterface/viewporter-protocol.c
+	maininterface/viewporter-protocol.c \
+	maininterface/xdg-shell-client-protocol.h \
+	maininterface/xdg-shell-protocol.c
 
 BUILT_SOURCES += $(nodist_libqt_wayland_plugin_la_SOURCES)
 
@@ -627,6 +655,12 @@ maininterface/viewporter-protocol.c: \
 		maininterface/viewporter-client-protocol.h
 	$(AM_V_GEN)$(WAYLAND_SCANNER) private-code $< $@
 
+maininterface/xdg-shell-client-protocol.h: $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml Makefile.am
+	$(AM_V_GEN)$(WAYLAND_SCANNER) client-header $< $@
+
+maininterface/xdg-shell-protocol.c: $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml Makefile.am
+	$(AM_V_GEN)$(WAYLAND_SCANNER) private-code $< $@
+
 #endif
 #endif
 


=====================================
modules/gui/qt/maininterface/compositor_wayland_module.c
=====================================
@@ -286,7 +286,7 @@ error:
     return false;
 }
 
-static int Open(vlc_object_t* p_this)
+int OpenCompositor(vlc_object_t* p_this)
 {
     qtwayland_t* obj = (qtwayland_t*)p_this;
 
@@ -323,10 +323,3 @@ static int Open(vlc_object_t* p_this)
 
     return VLC_SUCCESS;
 }
-
-vlc_module_begin()
-    set_shortname(N_("QtWayland"))
-    set_description(N_(" calls for compositing with Qt"))
-    set_capability("qtwayland", 10)
-    set_callback(Open)
-vlc_module_end()


=====================================
modules/gui/qt/maininterface/compositor_wayland_module.h
=====================================
@@ -43,5 +43,6 @@ typedef struct qtwayland_t
     void (*close)(struct qtwayland_t*);
 } qtwayland_t;
 
+int OpenCompositor(vlc_object_t* p_this);
 
 #endif // COMPOSITOR_WAYLAND_MODULE_H


=====================================
modules/gui/qt/maininterface/compositor_x11.cpp
=====================================
@@ -258,3 +258,36 @@ QQuickItem * CompositorX11::activeFocusItem() const /* override */
 {
     return m_qmlView->activeFocusItem();
 }
+
+///////// DummyNativeWidget
+
+DummyNativeWidget::DummyNativeWidget(QWidget* parent, Qt::WindowFlags f)
+    : QWidget(parent, f)
+{
+    setAttribute(Qt::WA_NativeWindow, true);
+    setAttribute(Qt::WA_OpaquePaintEvent, true);
+    setAttribute(Qt::WA_PaintOnScreen, true);
+    setAttribute(Qt::WA_MouseTracking, true);
+    setAttribute(Qt::WA_TranslucentBackground, false);
+    QWindow* w =  window()->windowHandle();
+    assert(w);
+    /*
+     * force the window not to have an alpha channel, the  parent window
+     * may have an alpha channel and child widget would inhertit the format
+     * even if we set Qt::WA_TranslucentBackground to false. having an alpha
+     * in this surface would lead to the video begin semi-tranparent.
+     */
+    QSurfaceFormat format = w->format();
+    format.setAlphaBufferSize(0);
+    w->setFormat(format);
+}
+
+DummyNativeWidget::~DummyNativeWidget()
+{
+
+}
+
+QPaintEngine* DummyNativeWidget::paintEngine() const
+{
+    return nullptr;
+}


=====================================
modules/gui/qt/maininterface/compositor_x11.hpp
=====================================
@@ -22,6 +22,8 @@
 #include "videosurface.hpp"
 #include <memory>
 
+#include <QWidget>
+
 #include <xcb/xcb.h>
 
 class QObject;
@@ -68,6 +70,18 @@ private:
     std::unique_ptr<CompositorX11RenderWindow> m_renderWindow;
 };
 
+class DummyNativeWidget : public QWidget
+{
+    Q_OBJECT
+public:
+    DummyNativeWidget(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
+    virtual ~DummyNativeWidget();
+
+    //override paintEnging to suppress warning
+    QPaintEngine* paintEngine() const override;
+};
+
+
 }
 
 #endif // COMPOSITORX11_HPP


=====================================
modules/gui/qt/maininterface/compositor_x11_utils.cpp
=====================================
@@ -15,44 +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.
  *****************************************************************************/
-#include <xcb/xfixes.h>
-#include <vlc_cxx_helpers.hpp>
-#include "compositor_x11_utils.hpp"
-
-#include <QWindow>
+#include <vector>
+#include <algorithm>
+#include <cstring>
 
-namespace vlc {
-
-DummyNativeWidget::DummyNativeWidget(QWidget* parent, Qt::WindowFlags f)
-    : QWidget(parent, f)
-{
-    setAttribute(Qt::WA_NativeWindow, true);
-    setAttribute(Qt::WA_OpaquePaintEvent, true);
-    setAttribute(Qt::WA_PaintOnScreen, true);
-    setAttribute(Qt::WA_MouseTracking, true);
-    setAttribute(Qt::WA_TranslucentBackground, false);
-    QWindow* w =  window()->windowHandle();
-    assert(w);
-    /*
-     * force the window not to have an alpha channel, the  parent window
-     * may have an alpha channel and child widget would inhertit the format
-     * even if we set Qt::WA_TranslucentBackground to false. having an alpha
-     * in this surface would lead to the video begin semi-tranparent.
-     */
-    QSurfaceFormat format = w->format();
-    format.setAlphaBufferSize(0);
-    w->setFormat(format);
-}
+#include <vlc_cxx_helpers.hpp>
 
-DummyNativeWidget::~DummyNativeWidget()
-{
+#include <xcb/xfixes.h>
 
-}
+#include "compositor_x11_utils.hpp"
 
-QPaintEngine* DummyNativeWidget::paintEngine() const
-{
-    return nullptr;
-}
+namespace vlc {
 
 bool queryExtension(xcb_connection_t* conn, const char* name, uint8_t* first_event_out, uint8_t* first_error_out)
 {
@@ -133,10 +106,10 @@ void setTransparentForMouseEvent(xcb_connection_t* conn, xcb_window_t window)
 //see https://specifications.freedesktop.org/wm-spec/wm-spec-latest.html#idm45894597940912
 bool wmScreenHasCompositor(xcb_connection_t* conn, int screen)
 {
-     QByteArray propName("_NET_WM_CM_S");
-     propName += QByteArray::number(screen);
+     std::string propName("_NET_WM_CM_S");
+    propName += std::to_string(screen);
 
-     xcb_atom_t atom = getInternAtom(conn, propName.constData());
+     xcb_atom_t atom = getInternAtom(conn, propName.c_str());
      if (atom == 0)
         return false;
 


=====================================
modules/gui/qt/maininterface/compositor_x11_utils.hpp
=====================================
@@ -19,26 +19,12 @@
 #define COMPOSITOR_X11_UTILS_HPP
 #include <memory>
 
-#include <QWidget>
-
 #include <xcb/xcb.h>
 #include <xcb/render.h>
 #include <xcb/composite.h>
 
-
 namespace vlc {
 
-class DummyNativeWidget : public QWidget
-{
-    Q_OBJECT
-public:
-    DummyNativeWidget(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
-    virtual ~DummyNativeWidget();
-
-    //override paintEnging to suppress warning
-    QPaintEngine* paintEngine() const override;
-};
-
 template<typename T, typename R, R RELEASE>
 class X11Resource {
 public:


=====================================
modules/gui/qt/maininterface/mainctx_win32.cpp
=====================================
@@ -117,103 +117,6 @@ HWND WinId( QWindow *windowHandle )
         return 0;
 }
 
-
-bool isWindowFixedSize(const QWindow *window)
-{
-    if (window->flags() & Qt::MSWindowsFixedSizeDialogHint)
-        return true;
-
-    const auto minSize = window->minimumSize();
-    const auto maxSize = window->maximumSize();
-
-    return minSize.isValid() && maxSize.isValid() && minSize == maxSize;
-}
-
-
-class WinSystemMenuButton : public SystemMenuButton
-{
-public:
-    WinSystemMenuButton(QWindow *window, QObject *parent)
-        : SystemMenuButton {parent}
-        , m_window {window}
-    {
-        connect(this, &CSDButton::clicked, this, &WinSystemMenuButton::handleClick);
-        connect(this, &CSDButton::doubleClicked, this, &WinSystemMenuButton::handleDoubleClick);
-    }
-
-    void showSystemMenu(const QPoint &windowpos) override
-    {
-        HWND hwnd = (HWND)m_window->winId();
-        HMENU hmenu = ::GetSystemMenu(hwnd, FALSE);
-        if (!hmenu)
-            return;
-
-        // Tweak the menu items according to the current window status.
-        const auto winState = m_window->windowStates();
-        const bool maxOrFull = (winState.testFlag(Qt::WindowMaximized) || winState.testFlag(Qt::WindowFullScreen));
-        const bool fixedSize = isWindowFixedSize(m_window);
-
-        EnableMenuItem(hmenu, SC_MOVE, (MF_BYCOMMAND | (!maxOrFull ? MFS_ENABLED : MFS_DISABLED)));
-        EnableMenuItem(hmenu, SC_SIZE, (MF_BYCOMMAND | ((!maxOrFull && !fixedSize) ? MFS_ENABLED : MFS_DISABLED)));
-
-        EnableMenuItem(hmenu, SC_RESTORE, (MF_BYCOMMAND | ((maxOrFull && !fixedSize) ? MFS_ENABLED : MFS_DISABLED)));
-        EnableMenuItem(hmenu, SC_MINIMIZE, (MF_BYCOMMAND | MFS_ENABLED));
-        EnableMenuItem(hmenu, SC_MAXIMIZE, (MF_BYCOMMAND | ((!maxOrFull && !fixedSize) ? MFS_ENABLED : MFS_DISABLED)));
-        EnableMenuItem(hmenu, SC_CLOSE, (MF_BYCOMMAND | MFS_ENABLED));
-
-        // map pos to screen points and convert according to device DPR, required on HI-DPI displays
-        const auto screenPoints = m_window->mapToGlobal(windowpos) * m_window->devicePixelRatio();
-
-        const auto alignment = (QGuiApplication::isRightToLeft() ? TPM_RIGHTALIGN : TPM_LEFTALIGN);
-
-        // show menu
-        emit systemMenuVisibilityChanged(true);
-
-        const int action = TrackPopupMenu(hmenu, (TPM_RETURNCMD | alignment)
-                                          , screenPoints.x(), screenPoints.y()
-                                          , 0, hwnd, nullptr);
-
-        // unlike native system menu which sends WM_SYSCOMMAND, TrackPopupMenu sends WM_COMMAND
-        // imitate native system menu by sending the action manually as WM_SYSCOMMAND
-        PostMessageW(hwnd, WM_SYSCOMMAND, action, 0);
-
-        emit systemMenuVisibilityChanged(false);
-    }
-
-private:
-    // target window
-    QWindow *m_window = {};
-
-    // used to reject click() incase a doubleClick() is followed
-    bool m_triggerSystemMenu = false;
-
-    void handleClick()
-    {
-        // delay the show of sytem menu to check if this 'click' is
-        // a double click, 'm_triggerSystemMenu' is used to reject the
-        // queued 'showSystemMenu' call in case this is a double click
-
-        m_triggerSystemMenu = true;
-        QTimer::singleShot(100, this, [this]()
-        {
-            if (!m_triggerSystemMenu)
-                return;
-
-            // show system menu 'margin' below the rect
-            constexpr QPoint margin {0, 4};
-            showSystemMenu(rect().bottomLeft() + margin);
-        });
-    }
-
-    void handleDoubleClick()
-    {
-        // reject any queued showSystemMenu call
-        m_triggerSystemMenu = false;
-
-        m_window->close();
-    }
-};
-
 class CSDWin32EventHandler : public QObject, public QAbstractNativeEventFilter
 {
 public:
@@ -827,9 +730,6 @@ InterfaceWindowHandlerWin32::InterfaceWindowHandlerWin32(qt_intf_t *_p_intf, Mai
         m_disableVolumeKeys = disable;
     });
 
-    auto systemMenuButton = std::make_shared<WinSystemMenuButton>(mainCtx->intfMainWindow(), nullptr);
-    mainCtx->csdButtonModel()->setSystemMenuButton(systemMenuButton);
-
     QApplication::instance()->installNativeEventFilter(this);
 }
 


=====================================
modules/gui/qt/maininterface/mainui.cpp
=====================================
@@ -33,6 +33,7 @@
 #include "playlist/playlist_model.hpp"
 #include "playlist/playlist_controller.hpp"
 
+#include "util/csdmenu.hpp"
 #include "util/item_key_event_filter.hpp"
 #include "util/imageluminanceextractor.hpp"
 #include "util/keyhelper.hpp"
@@ -182,8 +183,8 @@ void MainUI::registerQMLTypes()
         qmlRegisterUncreatableType<VLCVarChoiceModel>(uri, versionMajor, versionMinor, "VLCVarChoiceModel", "generic variable with choice model" );
         qmlRegisterUncreatableType<CSDButton>(uri, versionMajor, versionMinor, "CSDButton", "");
         qmlRegisterUncreatableType<CSDButtonModel>(uri, versionMajor, versionMinor, "CSDButtonModel", "has CSD buttons and provides for communicating CSD events between UI and backend");
+        qmlRegisterTypesAndRevisions<CSDMenu>( uri, versionMajor);
         qmlRegisterUncreatableType<NavigationAttached>( uri, versionMajor, versionMinor, "Navigation", "Navigation is only available via attached properties");
-
         qmlRegisterModule(uri, versionMajor, versionMinor);
         qmlProtectModule(uri, versionMajor);
     }


=====================================
modules/gui/qt/maininterface/qt_wayland_plugin.c
=====================================
@@ -0,0 +1,35 @@
+/*****************************************************************************
+ * 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 "compositor_wayland_module.h"
+
+#ifdef QT_HAS_WAYLAND_PROTOCOLS
+#include "util/csdmenu_wayland.h"
+#endif
+
+vlc_module_begin()
+    set_shortname(N_("QtWayland"))
+    set_description(N_(" calls for compositing with Qt"))
+    set_capability("qtwayland", 10)
+    set_callback(OpenCompositor)
+#ifdef QT_HAS_WAYLAND_PROTOCOLS
+add_submodule()
+    set_capability("qtcsdmenu", 10)
+    set_callbacks(WaylandCSDMenuOpen, WaylandCSDMenuClose)
+#endif
+vlc_module_end()


=====================================
modules/gui/qt/maininterface/qt_x11_plugin.c
=====================================
@@ -0,0 +1,25 @@
+/*****************************************************************************
+ * 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 "util/csdmenu_x11.h"
+
+vlc_module_begin()
+    set_shortname(N_("QtX11"))
+    set_description(N_("Qt extra X11 features"))
+    set_capability("qtcsdmenu", 10)
+    set_callback(X11CSDMenuOpen)
+vlc_module_end()


=====================================
modules/gui/qt/meson.build
=====================================
@@ -128,6 +128,7 @@ moc_headers = files(
     'util/color_svg_image_provider.hpp',
     'util/vlcaccess_image_provider.hpp',
     'util/csdbuttonmodel.hpp',
+    'util/csdmenu.hpp',
     'util/imageluminanceextractor.hpp',
     'util/keyhelper.hpp',
     'util/listcache.hpp',
@@ -431,6 +432,8 @@ some_sources = files(
     'util/covergenerator.hpp',
     'util/csdbuttonmodel.cpp',
     'util/csdbuttonmodel.hpp',
+    'util/csdmenu.cpp',
+    'util/csdmenu.hpp',
     'util/imageluminanceextractor.cpp',
     'util/imageluminanceextractor.hpp',
     'util/imagehelper.cpp',
@@ -507,6 +510,8 @@ if host_system == 'windows'
         'maininterface/compositor_win7.cpp',
         'maininterface/compositor_win7.hpp',
         'style/windowsthemeprovider.cpp',
+        'util/csdmenu_win32.c',
+        'util/csdmenu_win32.h',
     )
 
     if cdata.has('HAVE_DCOMP_H')
@@ -1056,6 +1061,23 @@ if qt6_dep.found()
         endif
     endif
 
+    if (xcb_dep.found() and xcb_render_dep.found() and xcb_xfixes_dep.found())
+
+        vlc_modules += {
+            'name' : 'qt_x11',
+            'include_directories' : qt_include_dir,
+            'sources' : files(
+                'maininterface/qt_x11_plugin.c',
+                'maininterface/compositor_x11_utils.hpp',
+                'maininterface/compositor_x11_utils.cpp',
+                'util/csdmenu_module.h',
+                'util/csdmenu_x11.cpp',
+                'util/csdmenu_x11.h'
+            ),
+            'dependencies' : [xcb_dep, xcb_render_dep, xcb_xfixes_dep]
+        }
+    endif
+
     # TODO support qmlcachegen
 
     vlc_modules += {


=====================================
modules/gui/qt/player/qml/TopBar.qml
=====================================
@@ -198,6 +198,14 @@ FocusScope{
         anchors.fill: parent
         active: root.showCSD
         source: "qrc:///qt/qml/VLC/Widgets/CSDTitlebarTapNDrapHandler.qml"
+
+        Connections {
+            target: tapNDrag.item
+            enabled: tapNDrag.status === Loader.Ready
+            function onCsdMenuVisibleChanged() {
+                root.requestLockUnlockAutoHide(tapNDrag.item.csdMenuVisible)
+            }
+        }
     }
 
     // Components -
@@ -276,13 +284,7 @@ FocusScope{
 
                 color: theme.accent
 
-                Connections {
-                    target: logo.button
-
-                    function onSystemMenuVisibilityChanged() {
-                        root.requestLockUnlockAutoHide(visible)
-                    }
-                }
+                onCsdMenuVisibleChanged: root.requestLockUnlockAutoHide(csdMenuVisible)
             }
         }
 


=====================================
modules/gui/qt/qt.cpp
=====================================
@@ -72,6 +72,7 @@ extern "C" char **environ;
 #ifdef _WIN32
 # include "maininterface/mainctx_win32.hpp"
 #include "maininterface/win32windoweffects_module.hpp"
+#include "util/csdmenu_win32.h"
 #else
 # include "maininterface/mainctx.hpp"   /* MainCtx creation */
 #endif
@@ -469,6 +470,11 @@ vlc_module_begin ()
         set_description( "Provides window effects on Windows." )
         set_capability( "qtwindoweffects", 10 )
         set_callback( QtWin32WindowEffectsOpen )
+    add_submodule ()
+        add_shortcut( "QtWin32CSDMenu" )
+        set_description( "Provides csd menu on Windows." )
+        set_capability( "qtcsdmenu", 10 )
+        set_callback( QtWin32CSDMenuOpen )
 #endif
     add_submodule()
         set_capability("qt theme provider", 1)


=====================================
modules/gui/qt/util/csdbuttonmodel.cpp
=====================================
@@ -127,16 +127,6 @@ QList<CSDButton *> CSDButtonModel::windowCSDButtons() const
     return m_windowCSDButtons;
 }
 
-CSDButton *CSDButtonModel::systemMenuButton() const
-{
-    return m_systemMenuButton.get();
-}
-
-void CSDButtonModel::setSystemMenuButton(std::shared_ptr<SystemMenuButton> button)
-{
-    m_systemMenuButton = std::move(button);
-}
-
 
 void CSDButtonModel::minimizeButtonClicked()
 {


=====================================
modules/gui/qt/util/csdbuttonmodel.hpp
=====================================
@@ -90,21 +90,6 @@ private:
     bool m_externalPressed = false;
 };
 
-
-class SystemMenuButton : public CSDButton
-{
-    Q_OBJECT
-
-public:
-    SystemMenuButton(QObject *parent = nullptr) : CSDButton {SystemMenuButton::SystemMenu, parent} {}
-
-    Q_INVOKABLE virtual void showSystemMenu(const QPoint &windowpos) = 0;
-
-signals:
-    void systemMenuVisibilityChanged(bool visible);
-};
-
-
 class MainCtx;
 
 
@@ -113,17 +98,10 @@ class CSDButtonModel : public QObject
     Q_OBJECT
     Q_PROPERTY(QList<CSDButton *> windowCSDButtons READ windowCSDButtons CONSTANT)
 
-    Q_PROPERTY(CSDButton *systemMenuButton READ systemMenuButton CONSTANT)
-
 public:
     CSDButtonModel(MainCtx *mainCtx, QObject *parent = nullptr);
 
     QList<CSDButton *> windowCSDButtons() const;
-    CSDButton *systemMenuButton() const;
-
-    // set by internal implmentation
-    // all the actions are also handled by implementation
-    void setSystemMenuButton(std::shared_ptr<SystemMenuButton> button);
 
 private slots:
     void minimizeButtonClicked();
@@ -135,9 +113,6 @@ private:
 
     // CSD window action buttons i.e minimize, maximize, close
     QList<CSDButton *> m_windowCSDButtons;
-
-    // sysmenu button, available on windows only
-    std::shared_ptr<SystemMenuButton> m_systemMenuButton = nullptr;
 };
 
 


=====================================
modules/gui/qt/util/csdmenu.cpp
=====================================
@@ -0,0 +1,323 @@
+/*****************************************************************************
+ * 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 "csdmenu.hpp"
+
+#include <QApplication>
+#include <QAction>
+#include <QWindow>
+
+#ifdef QT_GUI_PRIVATE
+#include <QtGui/qpa/qplatformnativeinterface.h>
+#include <QtGui/qpa/qplatformwindow.h>
+#include <QtGui/qpa/qplatformwindow_p.h>
+#endif
+
+#include "maininterface/mainctx.hpp"
+#include "util/csdmenu_module.h"
+#include "menus/menus.hpp"
+
+#include <vlc_common.h>
+#include <vlc_modules.h>
+
+struct xdg_surface;
+
+namespace {
+
+//vlc module callback
+static int csdMenuInfoOpen(void *func, bool forced, va_list ap)
+{
+    VLC_UNUSED(forced);
+    qt_csd_menu_open open = reinterpret_cast<qt_csd_menu_open>(func);
+    qt_csd_menu_t* obj = va_arg(ap, qt_csd_menu_t*);
+    qt_csd_menu_info* info = va_arg(ap, qt_csd_menu_info*);
+    return open(obj, info);
+}
+
+bool isWindowFixedSize(const QWindow *window)
+{
+    if (window->flags() & Qt::MSWindowsFixedSizeDialogHint)
+        return true;
+
+    const auto minSize = window->minimumSize();
+    const auto maxSize = window->maximumSize();
+
+    return minSize.isValid() && maxSize.isValid() && minSize == maxSize;
+}
+
+}
+
+class CSDMenuPrivate
+{
+    Q_DECLARE_PUBLIC(CSDMenu)
+public:
+
+    CSDMenuPrivate(CSDMenu* parent)
+        : q_ptr(parent)
+    {
+        const QString& platform = qApp->platformName();;
+        if (platform == QLatin1String("windows") || platform == QLatin1String("direct2d"))
+            m_plateform = QT_CSD_PLATFORM_WINDOWS;
+        else if (platform.startsWith( QLatin1String("wayland")))
+            m_plateform = QT_CSD_PLATFORM_WAYLAND;
+        else if (platform == QLatin1String("xcb"))
+            m_plateform = QT_CSD_PLATFORM_X11;
+    }
+
+    static void notifyMenuVisible(void* data, bool visible)
+    {
+        auto that = static_cast<CSDMenuPrivate*>(data);
+        that->m_menuVisible = visible;
+        emit that->q_func()->menuVisibleChanged(visible);
+    }
+
+    void fallbackMenu(QPoint pos)
+    {
+        auto menu = new VLCMenu(m_ctx->getIntf());
+        menu->setAttribute(Qt::WA_DeleteOnClose);
+
+        QObject::connect(menu, &QMenu::aboutToShow, q_ptr, [this](){
+            notifyMenuVisible(this, true);
+        });
+
+        QObject::connect(menu, &QMenu::aboutToHide, q_ptr, [this](){
+            notifyMenuVisible(this, false);
+        });
+
+        QWindow::Visibility visibility = m_ctx->interfaceVisibility();
+        QAction* action;
+        action = menu->addAction(qtr("Restore"));
+        action->setEnabled(visibility != QWindow::Windowed);
+        action->connect(action, &QAction::triggered, q_ptr, [this]() {
+            m_ctx->requestInterfaceNormal();
+        });
+        action = menu->addAction(qtr("Minimize"));
+        action->connect(action, &QAction::triggered, q_ptr, [this]() {
+            m_ctx->requestInterfaceMinimized();
+        });
+        action = menu->addAction(qtr("Maximized"));
+        action->setEnabled(visibility != QWindow::Maximized);
+        action->connect(action, &QAction::triggered, q_ptr, [this]() {
+            m_ctx->requestInterfaceMaximized();
+        });
+        action = menu->addAction(qtr("Always on top"));
+        action->setCheckable(true);
+        action->setChecked(m_ctx->isInterfaceAlwaysOnTop());
+        action->connect(action, &QAction::triggered, q_ptr, [this](bool checked) {
+            m_ctx->setInterfaceAlwaysOnTop(checked);
+        });
+        action = menu->addAction(qtr("Close"));
+        action->connect(action, &QAction::triggered, q_ptr, [this]() {
+            m_ctx->intfMainWindow()->close();
+        });
+
+        /* Ideally we should allow moving and resizing the window through startSystemXXX
+         * But, startSystemXXX is usually active from a mouse press until a mouse release,
+         * so calling it from a menu is broken with some desktop environments
+         */
+
+        menu->popup(pos);
+    }
+
+    void loadModule()
+    {
+        QWindow* window = m_ctx->intfMainWindow();
+        assert(window);
+
+        qt_csd_menu_info info = {};
+        info.platform = QT_CSD_PLATFORM_UNKNOWN;
+#ifdef _WIN32
+        if (m_plateform == QT_CSD_PLATFORM_WINDOWS)
+        {
+            info.platform = QT_CSD_PLATFORM_WINDOWS;
+        };
+#else
+#ifdef QT_GUI_PRIVATE
+        if (m_plateform == QT_CSD_PLATFORM_X11)
+        {
+            QPlatformNativeInterface* native = qApp->platformNativeInterface();
+            info.platform = QT_CSD_PLATFORM_X11;
+            info.data.x11.rootwindow = reinterpret_cast<intptr_t>(native->nativeResourceForIntegration(QByteArrayLiteral("rootwindow")));
+            info.data.x11.connection = reinterpret_cast<struct xcb_connection_t*>(native->nativeResourceForIntegration(QByteArrayLiteral("connection")));
+        }
+
+#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
+        if (m_plateform == QT_CSD_PLATFORM_WAYLAND)
+        {
+            info.platform = QT_CSD_PLATFORM_WAYLAND;
+
+            auto appNative = qApp->nativeInterface<QNativeInterface::QWaylandApplication>();
+            auto windowNative = window->nativeInterface<QNativeInterface::Private::QWaylandWindow>();
+
+            info.data.wayland.display = appNative->display();
+            info.data.wayland.toplevel = windowNative->surfaceRole<xdg_toplevel>();
+        }
+#endif // QT 6.5
+#endif // QT_GUI_PRIVATE
+#endif // _WIN32
+        info.isRtl = QGuiApplication::isRightToLeft();
+
+        info.userData = this;
+        info.notifyMenuVisible = ¬ifyMenuVisible;
+
+        m_systemMenuState = UNAVAILABLE;
+        m_systemMenu = vlc_object_create<qt_csd_menu_t>(m_ctx->getIntf());
+        if (!m_systemMenu)
+            return;
+        m_module = vlc_module_load(vlc_object_logger(m_systemMenu), "qtcsdmenu", nullptr, false, &csdMenuInfoOpen, m_systemMenu, &info);
+        if (!m_module)
+        {
+            vlc_object_delete(m_systemMenu);
+            m_systemMenu = nullptr;
+            return;
+        }
+        m_systemMenuState = AVAILABLE;
+    }
+
+    void unloadModule()
+    {
+        if (m_module)
+        {
+            module_unneed(m_systemMenu, m_module);
+            m_module = nullptr;
+        }
+        if (m_systemMenu)
+        {
+            vlc_object_delete(m_systemMenu);
+            m_systemMenu = nullptr;
+        }
+        m_systemMenuState = CSDMenuPrivate::NEED_INITIALISATION;
+    }
+
+    enum {
+        NEED_INITIALISATION,
+        UNAVAILABLE,
+        AVAILABLE
+    } m_systemMenuState = NEED_INITIALISATION;
+
+    module_t* m_module = nullptr;
+    qt_csd_menu_t* m_systemMenu = nullptr;
+
+    qt_csd_menu_platform m_plateform = QT_CSD_PLATFORM_UNKNOWN;
+    MainCtx* m_ctx = nullptr;
+    bool m_menuVisible = false;
+
+    CSDMenu* q_ptr;
+};
+
+CSDMenu::CSDMenu(QObject* parent)
+    : QObject(parent)
+    , d_ptr(new CSDMenuPrivate(this))
+{
+}
+
+CSDMenu::~CSDMenu()
+{
+    Q_D(CSDMenu);
+    d->unloadModule();
+}
+
+MainCtx* CSDMenu::getCtx() const
+{
+    Q_D(const CSDMenu);
+    return d->m_ctx;
+}
+
+void CSDMenu::setCtx(MainCtx* ctx)
+{
+    Q_D(CSDMenu);
+
+    if (d->m_ctx == ctx)
+        return;
+    d->m_ctx = ctx;
+    emit ctxChanged(ctx);
+}
+
+bool CSDMenu::getMenuVisible() const
+{
+    Q_D(const CSDMenu);
+    return d->m_menuVisible;
+}
+
+void CSDMenu::popup(const QPoint &pos)
+{
+    Q_D(CSDMenu);
+
+    assert(d->m_ctx);
+
+    QWindow* window = d->m_ctx->intfMainWindow();
+    assert(window);
+
+    if (d->m_systemMenuState == CSDMenuPrivate::NEED_INITIALISATION)
+        d->loadModule();
+
+    if (d->m_systemMenuState == CSDMenuPrivate::AVAILABLE) {
+        assert(d->m_systemMenu);
+
+        qreal qtDpr = window->devicePixelRatio();
+        QPlatformWindow* nativeWindow = window->handle();
+        qreal nativeDpr = nativeWindow->devicePixelRatio();
+
+        qt_csd_menu_event event = {};
+        event.platform = QT_CSD_PLATFORM_UNKNOWN;
+        event.x = (pos.x() * qtDpr) / nativeDpr;
+        event.y = (pos.y() * qtDpr) / nativeDpr;
+
+        const auto winState = window->windowStates();
+        int windowFlags = (winState.testFlag(Qt::WindowMaximized) ? QT_CSD_WINDOW_MAXIMIZED : 0)
+            | (winState.testFlag(Qt::WindowFullScreen) ? QT_CSD_WINDOW_FULLSCREEN : 0)
+            | (winState.testFlag(Qt::WindowMinimized) ? QT_CSD_WINDOW_MINIMIZED: 0)
+            | (isWindowFixedSize(window) ? QT_CSD_WINDOW_FIXED_SIZE : 0);
+
+        event.windowState = static_cast<qt_csd_menu_window_state>(windowFlags);
+
+#ifdef _WIN32
+        if (d->m_plateform == QT_CSD_PLATFORM_WINDOWS)
+        {
+            event.platform = QT_CSD_PLATFORM_WINDOWS;
+            event.data.win32.hwnd = reinterpret_cast<void*>(window->winId());
+        }
+#else
+
+#ifdef QT_GUI_PRIVATE
+        if (d->m_plateform == QT_CSD_PLATFORM_X11)
+        {
+            event.platform = QT_CSD_PLATFORM_X11;
+            event.data.x11.window = window->winId();
+        }
+
+#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
+        if (d->m_plateform == QT_CSD_PLATFORM_WAYLAND)
+        {
+            event.platform = QT_CSD_PLATFORM_WAYLAND;
+            auto appNative = qApp->nativeInterface<QNativeInterface::QWaylandApplication>();
+            assert(appNative);
+            event.data.wayland.seat = appNative->lastInputSeat();
+            event.data.wayland.serial = appNative->lastInputSerial();
+        }
+#endif // Qt 6.5
+#endif // QT_GUI_PRIVATE
+#endif // _WIN32
+
+        bool ret = d->m_systemMenu->popup(d->m_systemMenu, &event);
+        if (ret)
+            return;
+    }
+
+    d->fallbackMenu(pos);
+}
+


=====================================
modules/gui/qt/util/csdmenu.hpp
=====================================
@@ -0,0 +1,57 @@
+/*****************************************************************************
+ * 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 CSDMENU_HPP
+#define CSDMENU_HPP
+
+#include <QQmlEngine>
+#include <QMenu>
+
+Q_MOC_INCLUDE("maininterface/mainctx.hpp")
+class MainCtx;
+
+class CSDMenuPrivate;
+class CSDMenu : public QObject
+{
+    Q_OBJECT
+    QML_ELEMENT
+
+    Q_PROPERTY(MainCtx* ctx READ getCtx WRITE setCtx NOTIFY ctxChanged FINAL)
+    Q_PROPERTY(bool menuVisible READ getMenuVisible NOTIFY menuVisibleChanged FINAL)
+
+public:
+    explicit CSDMenu(QObject* parent = nullptr);
+    ~CSDMenu();
+
+public:
+    Q_INVOKABLE void popup(const QPoint &windowpos);
+
+    MainCtx* getCtx() const;
+    void setCtx(MainCtx* ctx);
+
+    bool getMenuVisible() const;
+
+signals:
+    void ctxChanged(MainCtx*);
+    void menuVisibleChanged(bool);
+
+protected:
+    QScopedPointer<CSDMenuPrivate> d_ptr;
+    Q_DECLARE_PRIVATE(CSDMenu)
+};
+
+#endif // CSDMENU_HPP


=====================================
modules/gui/qt/util/csdmenu_module.h
=====================================
@@ -0,0 +1,91 @@
+#ifndef QTCSDMENU_MODULE_H
+#define QTCSDMENU_MODULE_H
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_objects.h>
+
+#include <stdint.h>
+
+struct xcb_connection_t;
+
+struct wl_display;
+struct wl_seat;
+struct xdg_toplevel;
+
+typedef enum {
+    QT_CSD_PLATFORM_UNKNOWN,
+    QT_CSD_PLATFORM_WAYLAND,
+    QT_CSD_PLATFORM_X11,
+    QT_CSD_PLATFORM_WINDOWS
+} qt_csd_menu_platform;
+
+typedef enum {
+    QT_CSD_WINDOW_FULLSCREEN = (1 << 0),
+    QT_CSD_WINDOW_MAXIMIZED  = (1 << 1),
+    QT_CSD_WINDOW_MINIMIZED  = (1 << 2),
+    QT_CSD_WINDOW_FIXED_SIZE = (1 << 3)
+} qt_csd_menu_window_state;
+
+
+typedef void (*notifyMenuVisibleCallback)(void* userData, bool visible);
+
+//information about the window, plateform dependent
+typedef struct {
+    qt_csd_menu_platform platform;
+    union {
+        struct {
+            struct xcb_connection_t* connection;
+            uint32_t rootwindow;
+        } x11;
+        struct {
+            struct wl_display* display;
+            struct xdg_toplevel* toplevel;
+        } wayland;
+        struct {
+            void* hwnd;
+        } windows;
+    } data;
+
+    //is the UI rtl or ltr
+    bool isRtl;
+
+    //callback called to notify that the menu is visible/hidden
+    notifyMenuVisibleCallback notifyMenuVisible;
+    //user data passed to callbacks
+    void* userData;
+} qt_csd_menu_info;
+
+typedef struct {
+    qt_csd_menu_platform platform;
+    union {
+        struct {
+            struct wl_seat* seat;
+            uint32_t serial;
+        } wayland;
+        struct {
+            uint32_t window;
+        } x11;
+        struct {
+            void* hwnd;
+        } win32;
+    } data;
+    int x;
+    int y;
+    qt_csd_menu_window_state windowState;
+} qt_csd_menu_event;
+
+typedef struct qt_csd_menu_t {
+    struct vlc_object_t obj;
+    void* p_sys;
+
+    bool (*popup)(struct qt_csd_menu_t* menu, qt_csd_menu_event* event);
+} qt_csd_menu_t;
+
+typedef int (*qt_csd_menu_open)(qt_csd_menu_t* obj, qt_csd_menu_info* info);
+
+#endif // QTCSDMENU_MODULE_H


=====================================
modules/gui/qt/util/csdmenu_wayland.c
=====================================
@@ -0,0 +1,127 @@
+/*****************************************************************************
+ * 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 "csdmenu_wayland.h"
+
+#include <assert.h>
+#include <wayland-client.h>
+#include "xdg-shell-client-protocol.h"
+
+typedef struct {
+    struct wl_display* display;
+    struct wl_event_queue* queue;
+    struct xdg_wm_base *wm_base;
+    struct xdg_toplevel *toplevel;
+    bool supportWindowMenu;
+} csd_menu_priv_t;
+
+static void csd_registry_global_cb(void* data, struct wl_registry* registry,
+                                   uint32_t id, const char* iface, uint32_t version)
+{
+    qt_csd_menu_t* obj = (qt_csd_menu_t*)data;
+    csd_menu_priv_t* sys = (csd_menu_priv_t*)obj->p_sys;
+
+    if (!strcmp(iface, "xdg_wm_base")) {
+        sys->wm_base = (struct xdg_wm_base*)wl_registry_bind(registry, id, &xdg_wm_base_interface, version);
+    }
+}
+
+static void csd_registry_global_remove_cb(void* data, struct wl_registry* registry, uint32_t id)
+{
+    VLC_UNUSED(data);
+    VLC_UNUSED(registry);
+    VLC_UNUSED(id);
+}
+
+static const struct wl_registry_listener csd_registry_cbs = {
+    csd_registry_global_cb,
+    csd_registry_global_remove_cb,
+};
+
+static bool CSDMenuPopup(qt_csd_menu_t* p_this, qt_csd_menu_event* event)
+{
+    csd_menu_priv_t* sys = (csd_menu_priv_t*)p_this->p_sys;
+    assert (event->platform == QT_CSD_PLATFORM_WAYLAND);
+
+    if (!sys->supportWindowMenu)
+        return false;
+
+    xdg_toplevel_show_window_menu(sys->toplevel, event->data.wayland.seat, event->data.wayland.serial, event->x, event->y);
+
+    return true;
+}
+
+void WaylandCSDMenuClose(vlc_object_t* obj)
+{
+    qt_csd_menu_t* menu = (qt_csd_menu_t*)obj;
+    csd_menu_priv_t* sys = (csd_menu_priv_t*)menu->p_sys;
+    if (sys->wm_base)
+        xdg_wm_base_destroy(sys->wm_base);
+    if (sys->queue)
+        wl_event_queue_destroy(sys->queue);
+}
+
+int WaylandCSDMenuOpen(qt_csd_menu_t* p_this, qt_csd_menu_info* info)
+{
+    if (info->platform != QT_CSD_PLATFORM_WAYLAND) {
+        return VLC_EGENERIC;
+    }
+
+    if (info->data.wayland.display == NULL || info->data.wayland.toplevel == NULL) {
+        msg_Warn(p_this, "wayland display or surface not provided");
+        return VLC_EGENERIC;
+    }
+
+    csd_menu_priv_t* sys = (csd_menu_priv_t*)vlc_obj_calloc(p_this, 1, sizeof(csd_menu_priv_t));
+    if (!sys)
+        return VLC_ENOMEM;
+
+    p_this->p_sys = sys;
+
+    sys->display = info->data.wayland.display;
+
+    sys->queue = wl_display_create_queue(sys->display);
+    void* wrapper = wl_proxy_create_wrapper(sys->display);
+    wl_proxy_set_queue((struct wl_proxy*)wrapper, sys->queue);
+
+    struct wl_registry* registry = wl_display_get_registry((struct wl_display*)wrapper);
+    wl_proxy_wrapper_destroy(wrapper);
+
+    wl_registry_add_listener(registry, &csd_registry_cbs, p_this);
+    wl_display_roundtrip_queue(sys->display, sys->queue);
+
+    wl_registry_destroy(registry);
+
+    if (!sys->wm_base) {
+        msg_Dbg(p_this, "wm_base not found");
+        goto error;
+    }
+
+    sys->toplevel = info->data.wayland.toplevel;
+
+    //module functions
+    p_this->popup = CSDMenuPopup;
+
+    return VLC_SUCCESS;
+
+error:
+    p_this->p_sys = NULL;
+    WaylandCSDMenuClose(VLC_OBJECT(p_this));
+    vlc_obj_free(p_this, sys);
+    return VLC_EGENERIC;
+}
+


=====================================
modules/gui/qt/util/csdmenu_wayland.h
=====================================
@@ -0,0 +1,26 @@
+/*****************************************************************************
+ * 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 CSDMENU_WAYLAND_HPP
+#define CSDMENU_WAYLAND_HPP
+
+#include "csdmenu_module.h"
+
+int WaylandCSDMenuOpen(qt_csd_menu_t* obj, qt_csd_menu_info* info);
+void WaylandCSDMenuClose(vlc_object_t* obj);
+
+#endif // CSDMENU_WAYLAND_HPP


=====================================
modules/gui/qt/util/csdmenu_win32.c
=====================================
@@ -0,0 +1,92 @@
+/*****************************************************************************
+ * 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 <windows.h>
+#include <winuser.h>
+#include <assert.h>
+#include "csdmenu_win32.h"
+
+typedef struct
+{
+    bool isRtl;
+    notifyMenuVisibleCallback notifyMenuVisible;
+    void* userData;
+} qt_csd_menu_priv_t;
+
+static bool Popup(struct qt_csd_menu_t* menu, qt_csd_menu_event* event)
+{
+    qt_csd_menu_priv_t* sys = (qt_csd_menu_priv_t*)menu->p_sys;
+
+    assert(event->platform == QT_CSD_PLATFORM_WINDOWS);
+
+    HWND hwnd = event->data.win32.hwnd;
+    HMENU hmenu = GetSystemMenu(hwnd, FALSE);
+    if (!hmenu)
+        return false;
+
+    // Tweak the menu items according to the current window status.
+    const bool maxOrFull = event->windowState & (QT_CSD_WINDOW_FULLSCREEN |  QT_CSD_WINDOW_MAXIMIZED);
+    const bool fixedSize = event->windowState & QT_CSD_WINDOW_FIXED_SIZE;
+
+    EnableMenuItem(hmenu, SC_MOVE, (MF_BYCOMMAND | (!maxOrFull ? MFS_ENABLED : MFS_DISABLED)));
+    EnableMenuItem(hmenu, SC_SIZE, (MF_BYCOMMAND | ((!maxOrFull && !fixedSize) ? MFS_ENABLED : MFS_DISABLED)));
+
+    EnableMenuItem(hmenu, SC_RESTORE, (MF_BYCOMMAND | ((maxOrFull && !fixedSize) ? MFS_ENABLED : MFS_DISABLED)));
+    EnableMenuItem(hmenu, SC_MINIMIZE, (MF_BYCOMMAND | MFS_ENABLED));
+    EnableMenuItem(hmenu, SC_MAXIMIZE, (MF_BYCOMMAND | ((!maxOrFull && !fixedSize) ? MFS_ENABLED : MFS_DISABLED)));
+    EnableMenuItem(hmenu, SC_CLOSE, (MF_BYCOMMAND | MFS_ENABLED));
+
+
+    const unsigned alignment = sys->isRtl ? TPM_RIGHTALIGN : TPM_LEFTALIGN;
+
+    // show menu
+
+    if (sys->notifyMenuVisible)
+        sys->notifyMenuVisible(sys->userData, true);
+
+    const int action = TrackPopupMenu(hmenu, (TPM_RETURNCMD | alignment)
+                                      , event->x, event->y
+                                      , 0, hwnd, NULL);
+
+    // unlike native system menu which sends WM_SYSCOMMAND, TrackPopupMenu sends WM_COMMAND
+    // imitate native system menu by sending the action manually as WM_SYSCOMMAND
+    PostMessageW(hwnd, WM_SYSCOMMAND, action, 0);
+
+    if (sys->notifyMenuVisible)
+        sys->notifyMenuVisible(sys->userData, false);
+
+    return true;
+}
+
+int QtWin32CSDMenuOpen(qt_csd_menu_t* p_this, qt_csd_menu_info* info)
+{
+    if (info->platform != QT_CSD_PLATFORM_WINDOWS)
+        return VLC_EGENERIC;
+
+    qt_csd_menu_priv_t* sys = vlc_obj_malloc(p_this, sizeof(qt_csd_menu_priv_t));
+    if (!sys)
+        return VLC_ENOMEM;
+
+    sys->isRtl = info->isRtl;
+
+    sys->notifyMenuVisible = info->notifyMenuVisible;
+    sys->userData = info->userData;
+
+    p_this->p_sys = sys;
+    p_this->popup = Popup;
+    return VLC_SUCCESS;
+}


=====================================
modules/gui/qt/util/csdmenu_win32.h
=====================================
@@ -0,0 +1,33 @@
+/*****************************************************************************
+ * 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 CSDMENU_WIN32_H
+#define CSDMENU_WIN32_H
+
+#include "csdmenu_module.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int QtWin32CSDMenuOpen(qt_csd_menu_t* obj, qt_csd_menu_info* info);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // CSDMENU_WIN32_H


=====================================
modules/gui/qt/util/csdmenu_x11.cpp
=====================================
@@ -0,0 +1,93 @@
+/*****************************************************************************
+ * 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 "csdmenu_x11.h"
+#include <cassert>
+#include "maininterface/compositor_x11_utils.hpp"
+
+typedef struct {
+    xcb_connection_t* connection;
+    xcb_atom_t gtkShowWindowMenuAtom;
+    xcb_window_t rootWindow;
+} csd_menu_priv_t;
+
+extern "C" {
+
+static bool CSDMenuPopup(qt_csd_menu_t* p_this, qt_csd_menu_event* event)
+{
+    csd_menu_priv_t* sys = static_cast<csd_menu_priv_t*>(p_this->p_sys);
+
+    assert(event->platform == QT_CSD_PLATFORM_X11);
+
+    xcb_client_message_event_t x11event;
+    x11event.response_type = XCB_CLIENT_MESSAGE;
+    x11event.type = sys->gtkShowWindowMenuAtom;
+    x11event.window = event->data.x11.window;
+    x11event.sequence = 0;
+    x11event.format = 32;
+    x11event.data.data32[0] = 0; //device id ignored
+    x11event.data.data32[1] = event->x;
+    x11event.data.data32[2] = event->y;
+    x11event.data.data32[3] = 0;
+    x11event.data.data32[4] = 0;
+
+    // ungrab pointer to allow the window manager to grab them
+    xcb_ungrab_pointer(sys->connection, XCB_CURRENT_TIME);
+    xcb_send_event(sys->connection, false, sys->rootWindow, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY, reinterpret_cast<const char*>(&x11event));
+    return true;
+}
+
+int X11CSDMenuOpen(qt_csd_menu_t* p_this, qt_csd_menu_info* info)
+{
+    if (info->platform != QT_CSD_PLATFORM_X11) {
+        return VLC_EGENERIC;
+    }
+
+    if (info->data.x11.connection == NULL || info->data.x11.rootwindow == 0) {
+        msg_Warn(p_this, "X11 connection or root window missing");
+        return VLC_EGENERIC;
+    }
+
+    csd_menu_priv_t* sys = (csd_menu_priv_t*)vlc_obj_calloc(p_this, 1, sizeof(csd_menu_priv_t));
+    if (!sys)
+        return VLC_ENOMEM;
+
+    sys->connection = info->data.x11.connection;
+    sys->rootWindow = info->data.x11.rootwindow;
+
+    sys->gtkShowWindowMenuAtom = vlc::getInternAtom(sys->connection, "_GTK_SHOW_WINDOW_MENU");
+
+    if (sys->gtkShowWindowMenuAtom == 0)
+        goto error;
+
+    if (!vlc::wmNetSupport(sys->connection, info->data.x11.rootwindow, sys->gtkShowWindowMenuAtom))
+        goto error;
+
+
+    //module functions
+    p_this->popup = CSDMenuPopup;
+    p_this->p_sys = sys;
+
+    return VLC_SUCCESS;
+
+error:
+    p_this->p_sys = NULL;
+    vlc_obj_free(p_this, sys);
+    return VLC_EGENERIC;
+}
+
+}


=====================================
modules/gui/qt/util/csdmenu_x11.h
=====================================
@@ -0,0 +1,33 @@
+/*****************************************************************************
+ * 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 CSD_MENU_X11_H
+#define CSD_MENU_X11_H
+
+#include "csdmenu_module.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int X11CSDMenuOpen(qt_csd_menu_t* obj, qt_csd_menu_info* info);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // CSD_MENU_X11_H


=====================================
modules/gui/qt/widgets/qml/BannerCone.qml
=====================================
@@ -27,8 +27,8 @@ import VLC.Util
 Image {
     id: root
 
-    property var button: MainCtx.csdButtonModel.systemMenuButton
     required property color color
+    property alias csdMenuVisible: csdMenu.menuVisible
 
     sourceSize.width: VLCStyle.icon_normal
     sourceSize.height: VLCStyle.icon_normal
@@ -39,41 +39,39 @@ Image {
 
     focus: false
 
-    Loader {
-        anchors.fill: root
-        enabled: MainCtx.clientSideDecoration && root.button
-
-        sourceComponent: MouseArea {
-            onClicked: { root.button.click() }
-            onDoubleClicked: { root.button.doubleClick() }
-
-            Connections {
-                // don't target MouseArea for position since we
-                // need position updates of cone, inresepect of BannerSources
-                // to correctly track cone's global position
-                target: root
-
-                // handles VLCStyle.scale changes
-                function onXChanged() { Qt.callLater(root.updateRect) }
-                function onYChanged() { Qt.callLater(root.updateRect) }
-                function onWidthChanged() { Qt.callLater(root.updateRect) }
-                function onHeightChanged() { Qt.callLater(root.updateRect) }
-            }
+    TapHandler {
+        enabled:  MainCtx.clientSideDecoration
+
+        gesturePolicy: TapHandler.WithinBounds
 
-            Connections {
-                target: VLCStyle
+        onSingleTapped: (eventPoint, button) => {
+            if (button === Qt.LeftButton){
+                doubleTapFilter.start()
+            }
+        }
 
-                // handle window resize
-                function onAppWidthChanged() { Qt.callLater(root.updateRect) }
-                function onAppHeightChanged() { Qt.callLater(root.updateRect) }
+        onDoubleTapped: (eventPoint, button) => {
+            if (button === Qt.LeftButton) {
+                doubleTapFilter.stop()
+                MainCtx.intfMainWindow.close()
             }
         }
     }
 
-    function updateRect() {
-        const rect = root.mapToItem(null, 0, 0, width, height)
+    CSDMenu {
+        id: csdMenu
+        ctx: MainCtx
+    }
+
+    //SingleTapped is always notified, perform the action only if the double tap doesn't occur
+    Timer {
+        id: doubleTapFilter
 
-        if (button)
-            button.rect = rect
+        interval: VLCStyle.duration_short
+
+        onTriggered: {
+            //popup below the widget
+            csdMenu.popup(root.mapToGlobal(0, root.height + VLCStyle.margin_xxsmall))
+        }
     }
 }


=====================================
modules/gui/qt/widgets/qml/CSDTitlebarTapNDrapHandler.qml
=====================================
@@ -18,20 +18,18 @@
 
 //CSD is only supported on Qt 5.15
 import QtQuick
-import QtQuick.Window
 
 import VLC.MainInterface
 
 Item {
+    property alias csdMenuVisible: csdMenu.menuVisible
+
     TapHandler {
         acceptedButtons: Qt.LeftButton | Qt.RightButton
 
         onSingleTapped: (eventPoint, button) => {
             if (button & Qt.RightButton) {
-                const systemButton = MainCtx.csdButtonModel.systemMenuButton
-                if (systemButton) {
-                    systemButton.showSystemMenu(eventPoint.position)
-                }
+                csdMenu.popup(parent.mapToGlobal(eventPoint.position.x, eventPoint.position.y))
             }
         }
 
@@ -65,4 +63,8 @@ Item {
         }
     }
 
+    CSDMenu {
+        id: csdMenu
+        ctx: MainCtx
+    }
 }


=====================================
po/POTFILES.in
=====================================
@@ -884,6 +884,7 @@ modules/gui/qt/qt.hpp
 modules/gui/qt/util/color_scheme_model.cpp
 modules/gui/qt/util/covergenerator.cpp
 modules/gui/qt/util/covergenerator.hpp
+modules/gui/qt/util/csdmenu.cpp
 modules/gui/qt/util/imagehelper.cpp
 modules/gui/qt/util/imagehelper.hpp
 modules/gui/qt/util/qt_dirs.cpp



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/63f076b59416262a860724c3ea638e5079bccc19...62ff2e006c63f691e635f2dada66161f30d207b5

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/63f076b59416262a860724c3ea638e5079bccc19...62ff2e006c63f691e635f2dada66161f30d207b5
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