Browse Source
this menu is usually shown by window manager as contextual menu in the topbarpull/175/head
committed by
Steve Lhomme
7 changed files with 479 additions and 1 deletions
@ -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 = 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); |
|||
} |
|||
|
|||
@ -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
|
|||
@ -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
|
|||
Loading…
Reference in new issue