Browse Source

qt: fix accessiblity with DirectComposition compositor

The method is basically the same as commit
41926e08d73ea6c4bbfc87a1dd52d2cdbc435c27 from QtDeclarative but applied to our
composition architecture

in a nutshell

- the offscreen QQuickWindow must not report the child interfaces
- the render QWindow must report the child interfaces
- The child interfaces must report the render window as the parent

We implement QAccessibleObject for both window implementing theses policies.

Focus events are forwarded to the right window (from qml to the render
window and vice-versa).

We also need to fake the visibility of the offscreen window to make it behave as
visible without creating an actual window
pull/144/head
Pierre Lamot 4 years ago
committed by Steve Lhomme
parent
commit
8e725d6e17
  1. 2
      modules/gui/qt/Makefile.am
  2. 2
      modules/gui/qt/maininterface/compositor.hpp
  3. 268
      modules/gui/qt/maininterface/compositor_accessibility.cpp
  4. 46
      modules/gui/qt/maininterface/compositor_accessibility.hpp
  5. 2
      modules/gui/qt/maininterface/compositor_dcomp.cpp
  6. 2
      modules/gui/qt/maininterface/compositor_dcomp.hpp
  7. 93
      modules/gui/qt/maininterface/compositor_dcomp_uisurface.cpp
  8. 31
      modules/gui/qt/maininterface/compositor_dcomp_uisurface.hpp
  9. 2
      modules/gui/qt/meson.build

2
modules/gui/qt/Makefile.am

@ -166,6 +166,8 @@ libqt_plugin_la_SOURCES = \
gui/qt/maininterface/compositor.cpp \
gui/qt/maininterface/compositor_common.hpp \
gui/qt/maininterface/compositor_common.cpp \
gui/qt/maininterface/compositor_accessibility.cpp \
gui/qt/maininterface/compositor_accessibility.hpp \
gui/qt/maininterface/compositor_dummy.hpp \
gui/qt/maininterface/compositor_dummy.cpp \
gui/qt/maininterface/interface_window_handler.cpp \

2
modules/gui/qt/maininterface/compositor.hpp

@ -43,6 +43,7 @@ class QQmlComponent;
class QWindow;
class QQuickItem;
class QQuickView;
class QQuickWindow;
namespace vlc {
@ -100,6 +101,7 @@ public:
virtual QQuickItem * activeFocusItem() const = 0;
};
public:
explicit CompositorVideo(qt_intf_t* p_intf, QObject* parent = nullptr);
virtual ~CompositorVideo();

268
modules/gui/qt/maininterface/compositor_accessibility.cpp

@ -0,0 +1,268 @@
/*****************************************************************************
* Copyright (C) 2023 the VideoLAN team
*
* 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 <QWindow>
#include <QGuiApplication>
#include <QQuickItem>
#if !defined(QT_NO_ACCESSIBILITY) && defined(QT5_DECLARATIVE_PRIVATE)
#include <QAccessibleObject>
#include <private/qquickitem_p.h>
#include "compositor_accessibility.hpp"
#ifdef HAVE_DCOMP_H
# include "compositor_dcomp_uisurface.hpp"
#endif
namespace vlc {
/*
* we could have use QAccessibleQuickWindow like in QAccessibleQuickWidget
* but QAccessibleQuickWindow is not publicly exposed in the library, so we
* mimic the behavior of QAccessibleQuickWindow directly
*/
static void unignoredChildren(QQuickItem *item, QList<QQuickItem *> *items)
{
const QList<QQuickItem*> childItems = item->childItems();
for (QQuickItem *child : childItems)
{
if (QQuickItemPrivate::get(child)->isAccessible)
items->append(child);
else
unignoredChildren(child, items);
}
}
static QList<QQuickItem *> accessibleUnignoredChildren(QQuickItem *item)
{
QList<QQuickItem *> items;
unignoredChildren(item, &items);
return items;
}
class QAccessibleRenderWindow: public QAccessibleObject
{
public:
QAccessibleRenderWindow(QWindow* window, AccessibleRenderWindow* renderWindow)
: QAccessibleObject(window)
, m_window(renderWindow->getOffscreenWindow())
{
}
QAccessibleInterface* parent() const override
{
// we assume to be a top level window...
return QAccessible::queryAccessibleInterface(qApp);
}
QList<QQuickItem *> rootItems() const
{
if (QQuickItem *ci = m_window->contentItem())
return accessibleUnignoredChildren(ci);
return QList<QQuickItem *>();
}
QAccessibleInterface* child(int index) const override
{
const QList<QQuickItem*> &kids = rootItems();
if (index >= 0 && index < kids.count())
return QAccessible::queryAccessibleInterface(kids.at(index));
return nullptr;
}
int childCount() const override
{
return rootItems().count();
}
int indexOfChild(const QAccessibleInterface *iface) const override
{
int i = -1;
if (iface)
{
const QList<QQuickItem *> &roots = rootItems();
i = roots.count() - 1;
while (i >= 0)
{
if (iface->object() == roots.at(i))
break;
--i;
}
}
return i;
}
QAccessibleInterface *childAt(int x, int y) const override
{
for (int i = childCount() - 1; i >= 0; --i)
{
QAccessibleInterface *childIface = child(i);
if (childIface && !childIface->state().invisible)
{
if (QAccessibleInterface *iface = childIface->childAt(x, y))
return iface;
if (childIface->rect().contains(x, y))
return childIface;
}
}
return nullptr;
}
QString text(QAccessible::Text) const override
{
return window()->title();
}
QRect rect() const override
{
return QRect(window()->x(), window()->y(), window()->width(), window()->height());
}
QAccessible::Role role() const override
{
return QAccessible::Window;
}
QAccessible::State state() const override
{
QAccessible::State st;
if (window() == QGuiApplication::focusWindow())
st.active = true;
if (!window()->isVisible())
st.invisible = true;
return st;
}
QWindow* window() const override
{
return static_cast<QWindow*>(object());
}
private:
QQuickWindow* m_window;
};
/*
* DCompOffscreenWindow is a top window, mark it as unacessible so it won't
* be introspected by a11y, the QML scene is actually accessible though QAccessibleRenderWindow
*/
class QAccessibleOffscreenWindow: public QAccessibleObject
{
public:
QAccessibleOffscreenWindow(QQuickWindow *window)
: QAccessibleObject(window)
{
}
QAccessibleInterface* parent() const override
{
// we assume to be a top level window...
return QAccessible::queryAccessibleInterface(qApp);
}
QAccessibleInterface* child(int) const override
{
return nullptr;
}
int childCount() const override
{
return 0;
}
int indexOfChild(const QAccessibleInterface *) const override
{
return -1;
}
QAccessibleInterface *childAt(int, int) const override
{
return nullptr;
}
QAccessibleInterface *focusChild() const override
{
return nullptr;
}
QString text(QAccessible::Text) const override
{
return window()->title();
}
QRect rect() const override
{
return QRect(window()->x(), window()->y(), window()->width(), window()->height());
}
QAccessible::Role role() const override
{
return QAccessible::Window;
}
QAccessible::State state() const override
{
QAccessible::State st;
if (window() == QGuiApplication::focusWindow())
st.active = true;
if (!window()->isVisible())
st.invisible = true;
return st;
}
private:
QWindow* window() const override
{
return static_cast<QWindow*>(object());
}
};
QAccessibleInterface* compositionAccessibleFactory(const QString &classname, QObject *object)
{
#ifdef HAVE_DCOMP_H
if (classname == QLatin1String("vlc::DCompRenderWindow"))
{
DCompRenderWindow* renderWindow = qobject_cast<DCompRenderWindow *>(object);
assert(renderWindow);
return new QAccessibleRenderWindow(renderWindow, renderWindow);
}
#endif
if (classname == QLatin1String("vlc::CompositorOffscreenWindow")
|| (classname == QLatin1String("vlc::DCompOffscreenWindow")) )
{
return new QAccessibleOffscreenWindow(qobject_cast<QQuickWindow *>(object));
}
return nullptr;
}
}
#endif

46
modules/gui/qt/maininterface/compositor_accessibility.hpp

@ -0,0 +1,46 @@
/*****************************************************************************
* Copyright (C) 2023 the VideoLAN team
*
* 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 COMPOSITOR_ACCESSIBLITY_H
#define COMPOSITOR_ACCESSIBLITY_H
#include <QString>
class QObject;
class QAccessibleInterface;
class QQuickWindow;
namespace vlc {
class AccessibleRenderWindow
{
public:
virtual ~AccessibleRenderWindow() = default;
virtual QQuickWindow* getOffscreenWindow() const = 0;
};
#if !defined(QT_NO_ACCESSIBILITY) && defined(QT5_DECLARATIVE_PRIVATE)
QAccessibleInterface* compositionAccessibleFactory(const QString &classname, QObject *object);
#endif
}
#endif /* COMPOSITOR_ACCESSIBLITY_H */

2
modules/gui/qt/maininterface/compositor_dcomp.cpp

@ -252,7 +252,7 @@ bool CompositorDirectComposition::makeMainInterface(MainCtx* mainCtx)
bool ret;
m_mainCtx = mainCtx;
m_rootWindow = new QWindow();
m_rootWindow = new DCompRenderWindow();
m_videoWindowHandler = std::make_unique<VideoWindowHandler>(m_intf);
m_videoWindowHandler->setWindow( m_rootWindow );

2
modules/gui/qt/maininterface/compositor_dcomp.hpp

@ -70,7 +70,7 @@ protected:
void windowDestroy() override;
private:
QWindow* m_rootWindow = nullptr;
DCompRenderWindow* m_rootWindow = nullptr;
std::unique_ptr<WinTaskbarWidget> m_taskbarWidget;

93
modules/gui/qt/maininterface/compositor_dcomp_uisurface.cpp

@ -33,6 +33,9 @@
#include <d3dcompiler.h>
#include <comdef.h>
#include "compositor_common.hpp""
#include "compositor_accessibility.hpp"
namespace vlc {
using namespace Microsoft::WRL;
@ -138,17 +141,35 @@ private:
HINSTANCE m_compiler_dll = nullptr;
};
QAccessibleInterface* DCompRenderWindow::accessibleRoot() const
{
QAccessibleInterface* iface = QAccessible::queryAccessibleInterface(
const_cast<DCompRenderWindow*>(this));
return iface;
}
void DCompRenderWindow::setOffscreenWindow(CompositorOffscreenWindow* window)
{
m_offscreenWindow = window;
}
QQuickWindow* DCompRenderWindow::getOffscreenWindow() const
{
return m_offscreenWindow;
}
CompositorDCompositionUISurface::CompositorDCompositionUISurface(qt_intf_t* p_intf,
QWindow* window,
DCompRenderWindow* window,
Microsoft::WRL::ComPtr<IDCompositionVisual> dcVisual,
QObject* parent)
: QObject(parent)
, m_intf(p_intf)
, m_dcUiVisual(dcVisual)
, m_rootWindow(window)
, m_renderWindow(window)
{
}
bool CompositorDCompositionUISurface::init()
{
EGLBoolean eglRet;
@ -161,7 +182,7 @@ bool CompositorDCompositionUISurface::init()
format.setAlphaBufferSize(8);
m_context = new QOpenGLContext(this);
m_context->setScreen(m_rootWindow->screen());
m_context->setScreen(m_renderWindow->screen());
m_context->setFormat(format);
ret = m_context->create();
if (!ret || !m_context->isValid())
@ -198,13 +219,16 @@ bool CompositorDCompositionUISurface::init()
m_uiOffscreenSurface->setFormat(format);;
m_uiOffscreenSurface->create();
m_uiRenderControl = new CompositorDCompositionRenderControl(m_rootWindow);
m_uiRenderControl = new CompositorDCompositionRenderControl(m_renderWindow);
m_uiWindow = new CompositorOffscreenWindow(m_uiRenderControl);
m_uiWindow = new QQuickWindow(m_uiRenderControl);
m_uiWindow->setDefaultAlphaBuffer(true);
m_uiWindow->setFormat(format);
m_uiWindow->setClearBeforeRendering(false);
m_renderWindow->setOffscreenWindow(m_uiWindow);
m_d3dCompiler = std::make_shared<OurD3DCompiler>();
ret = m_d3dCompiler->init(VLC_OBJECT(m_intf));
if (!ret) {
@ -212,8 +236,8 @@ bool CompositorDCompositionUISurface::init()
return false;
}
qreal dpr = m_rootWindow->devicePixelRatio();
ret = initialiseD3DSwapchain(dpr * m_rootWindow->width(), dpr * m_rootWindow->height());
qreal dpr = m_renderWindow->devicePixelRatio();
ret = initialiseD3DSwapchain(dpr * m_renderWindow->width(), dpr * m_renderWindow->height());
if (!ret)
return false;
@ -225,10 +249,15 @@ bool CompositorDCompositionUISurface::init()
connect(m_uiWindow, &QQuickWindow::sceneGraphInitialized, this, &CompositorDCompositionUISurface::createFbo);
connect(m_uiWindow, &QQuickWindow::sceneGraphInvalidated, this, &CompositorDCompositionUISurface::destroyFbo);
connect(m_uiWindow, &QQuickWindow::focusObjectChanged, this, &CompositorDCompositionUISurface::forwardFocusObjectChanged);
connect(m_uiRenderControl, &QQuickRenderControl::renderRequested, this, &CompositorDCompositionUISurface::requestUpdate);
connect(m_uiRenderControl, &QQuickRenderControl::sceneChanged, this, &CompositorDCompositionUISurface::requestUpdate);
m_rootWindow->installEventFilter(this);
QAccessible::installFactory(&compositionAccessibleFactory);
m_renderWindow->installEventFilter(this);
return true;
}
@ -525,7 +554,7 @@ void CompositorDCompositionUISurface::render()
{
EGLBoolean eglRet;
QSize realSize = m_rootWindow->size() * m_rootWindow->devicePixelRatio();
QSize realSize = m_renderWindow->size() * m_renderWindow->devicePixelRatio();
if (realSize != m_surfaceSize)
{
m_surfaceSize = realSize;
@ -609,22 +638,36 @@ static void remapInputMethodQueryEvent(QObject *object, QInputMethodQueryEvent *
}
}
bool CompositorDCompositionUISurface::eventFilter(QObject* object, QEvent* event)
{
if (object != m_rootWindow)
if (object != m_renderWindow)
return false;
switch (event->type()) {
case QEvent::Move:
case QEvent::Show:
//offscreen window won't be really visible
m_uiWindow->setPseudoVisible(true);
updatePosition();
break;
case QEvent::Hide:
m_uiWindow->setPseudoVisible(false);
break;
case QEvent::Resize:
updateSizes();
forceRender();
break;
case QEvent::FocusAboutToChange:
return QCoreApplication::sendEvent(m_uiWindow, event);
case QEvent::WindowStateChange:
m_uiWindow->setWindowStateExt(m_renderWindow->windowState());
break;
case QEvent::WindowActivate:
case QEvent::WindowDeactivate:
case QEvent::Leave:
@ -639,6 +682,10 @@ bool CompositorDCompositionUISurface::eventFilter(QObject* object, QEvent* event
return ret;
}
case QEvent::FocusIn:
case QEvent::FocusOut:
return QCoreApplication::sendEvent(m_uiWindow, event);
case QEvent::InputMethod:
return QCoreApplication::sendEvent(m_uiWindow->focusObject(), event);
case QEvent::InputMethodQuery:
@ -665,6 +712,9 @@ bool CompositorDCompositionUISurface::eventFilter(QObject* object, QEvent* event
QCoreApplication::sendEvent(m_uiWindow, &mappedEvent);
return true;
}
case QEvent::ShortcutOverride:
case QEvent::Wheel:
case QEvent::HoverEnter:
case QEvent::HoverLeave:
@ -689,8 +739,9 @@ bool CompositorDCompositionUISurface::eventFilter(QObject* object, QEvent* event
return QCoreApplication::sendEvent(m_uiWindow, event);
case QEvent::ScreenChangeInternal:
m_uiWindow->setScreen(m_rootWindow->screen());
m_uiWindow->setScreen(m_renderWindow->screen());
break;
default:
break;
}
@ -700,7 +751,7 @@ bool CompositorDCompositionUISurface::eventFilter(QObject* object, QEvent* event
void CompositorDCompositionUISurface::createFbo()
{
//write to the immediate context
m_uiWindow->setRenderTarget(0, m_rootWindow->size());
m_uiWindow->setRenderTarget(0, m_renderWindow->size());
}
void CompositorDCompositionUISurface::destroyFbo()
@ -709,8 +760,8 @@ void CompositorDCompositionUISurface::destroyFbo()
void CompositorDCompositionUISurface::updateSizes()
{
qreal dpr = m_rootWindow->devicePixelRatio();
QSize windowSize = m_rootWindow->size();
qreal dpr = m_renderWindow->devicePixelRatio();
QSize windowSize = m_renderWindow->size();
resizeSwapchain(windowSize.width() * dpr, windowSize.height() * dpr);
updateSharedTexture(windowSize.width() * dpr, windowSize.height() * dpr);
@ -722,7 +773,7 @@ void CompositorDCompositionUISurface::updateSizes()
void CompositorDCompositionUISurface::updatePosition()
{
QPoint windowPosition = m_rootWindow->mapToGlobal(QPoint(0,0));
QPoint windowPosition = m_renderWindow->mapToGlobal(QPoint(0,0));
if (m_uiWindow->position() != windowPosition)
m_uiWindow->setPosition(windowPosition);
}
@ -736,11 +787,19 @@ void CompositorDCompositionUISurface::requestUpdate()
}
}
QQuickWindow* CompositorDCompositionUISurface::getOffscreenWindow() const {
return m_uiWindow;
}
void CompositorDCompositionUISurface::handleScreenChange()
{
msg_Info(m_intf, "handle screen change");
m_uiWindow->setGeometry(0, 0, m_rootWindow->width(), m_rootWindow->height());;
m_uiWindow->setGeometry(0, 0, m_renderWindow->width(), m_renderWindow->height());;
requestUpdate();
}
void CompositorDCompositionUISurface::forwardFocusObjectChanged(QObject* object)
{
m_renderWindow->focusObjectChanged(object);
}
}

31
modules/gui/qt/maininterface/compositor_dcomp_uisurface.hpp

@ -59,9 +59,29 @@
#include "qt.hpp"
#include "compositor.hpp"
#include "compositor_accessibility.hpp"
namespace vlc {
class CompositorOffscreenWindow;
//on screen window
class DCompRenderWindow : public QWindow, public AccessibleRenderWindow
{
Q_OBJECT
public:
#ifndef QT_NO_ACCESSIBILITY
QAccessibleInterface* accessibleRoot() const override;
#endif
void setOffscreenWindow(CompositorOffscreenWindow* window);
QQuickWindow* getOffscreenWindow() const override;
private:
CompositorOffscreenWindow* m_offscreenWindow = nullptr;
};
class CompositorDCompositionRenderControl : public QQuickRenderControl
{
Q_OBJECT
@ -85,7 +105,7 @@ class CompositorDCompositionUISurface : public QObject, public CompositorVideo::
Q_OBJECT
public:
explicit CompositorDCompositionUISurface(qt_intf_t* p_intf,
QWindow* window,
DCompRenderWindow* window,
Microsoft::WRL::ComPtr<IDCompositionVisual> dcVisual,
QObject *parent = nullptr);
@ -94,6 +114,7 @@ public:
bool init();
QQmlEngine* engine() const override { return m_qmlEngine; }
QQuickWindow* getOffscreenWindow() const;
void setContent(QQmlComponent* component, QQuickItem* rootItem) override;
@ -113,6 +134,7 @@ private:
void forceRender();
void handleScreenChange();
void forwardFocusObjectChanged(QObject* object);
void createFbo();
void destroyFbo();
@ -121,6 +143,8 @@ private:
void updatePosition();
private:
friend class DCompRenderWindow;
qt_intf_t* m_intf = nullptr;
class OurD3DCompiler;
@ -171,9 +195,10 @@ private:
CompositorDCompositionRenderControl* m_uiRenderControl = nullptr;
//the actual window where we render
QWindow* m_rootWindow = nullptr;
DCompRenderWindow* m_renderWindow = nullptr;
QQuickWindow* m_uiWindow = nullptr;
//offscreen window for QML content
CompositorOffscreenWindow* m_uiWindow = nullptr;
QQmlEngine* m_qmlEngine = nullptr;
QQmlComponent* m_qmlComponent = nullptr;
QQuickItem* m_rootItem = nullptr;

2
modules/gui/qt/meson.build

@ -275,6 +275,8 @@ some_sources = files(
'maininterface/compositor.cpp',
'maininterface/compositor_common.hpp',
'maininterface/compositor_common.cpp',
'maininterface/compositor_accessibility.hpp',
'maininterface/compositor_accessibility.cpp',
'maininterface/compositor_dummy.hpp',
'maininterface/compositor_dummy.cpp',
'maininterface/interface_window_handler.cpp',

Loading…
Cancel
Save