qt: introduce CSD menu

this menu is usually shown by window manager as contextual menu in the topbar
This commit is contained in:
Pierre Lamot 2024-10-11 15:15:29 +02:00 committed by Steve Lhomme
parent 63f076b594
commit 7986a170c4
7 changed files with 479 additions and 1 deletions

View File

@ -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 \

View File

@ -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);
}

View File

@ -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',

View File

@ -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 = &notifyMenuVisible;
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 = 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);
}

View File

@ -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

View File

@ -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

View File

@ -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