Skip to content

Instantly share code, notes, and snippets.

@vadi2
Created July 9, 2022 08:42
Embed
What would you like to do?
How to do call UiaRaiseNotificationEvent on Windows
/***************************************************************************
* Copyright 2019-2022 Leonard de Ruijter, James Teh - OSARA *
* Copyright 2017 The Qt Company Ltd. *
* Copyright (C) 2022 by Vadim Peretokin - vadim.peretokin@mudlet.org *
* *
* 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., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef ANNOUNCER_H
#define ANNOUNCER_H
#include <QAccessible>
#include <QAccessibleInterface>
#include <QAccessibleWidget>
#include <QLibrary>
#include <QObject>
#include <QWidget>
#if defined(Q_OS_LINUX)
class InvisibleNotification : public QWidget {
Q_OBJECT
public:
Q_DISABLE_COPY(InvisibleNotification)
explicit InvisibleNotification(QWidget *parent);
void setText(const QString &text);
QString text();
private:
QString mText;
};
// create a new class InvisibleStatusbar based on QWidget
class InvisibleStatusbar : public QWidget {
Q_OBJECT
public:
Q_DISABLE_COPY(InvisibleStatusbar)
explicit InvisibleStatusbar(QWidget *parent);
};
class InvisibleAccessibleNotification : public QAccessibleWidget {
public:
explicit InvisibleAccessibleNotification(QWidget *w)
: QAccessibleWidget(w, QAccessible::Role::Notification) {}
private:
InvisibleNotification *notification() const;
protected:
QString text(QAccessible::Text t) const override;
};
class FakeAccessibleStatusbar : public QAccessibleWidget {
public:
explicit FakeAccessibleStatusbar(QWidget *w)
: QAccessibleWidget(w, QAccessible::Role::StatusBar) {}
};
#endif
class Announcer : public QWidget {
Q_OBJECT
public:
Q_DISABLE_COPY_MOVE(Announcer)
explicit Announcer(QWidget *parent = nullptr);
void announce(const QString text);
#if defined(Q_OS_LINUX)
static QAccessibleInterface *accessibleFactory(const QString &classname,
QObject *object) {
// mingw compilation breaks without this
#undef interface
QAccessibleInterface *interface = nullptr;
if (classname == QLatin1String("InvisibleNotification") && object &&
object->isWidgetType()) {
interface =
new InvisibleAccessibleNotification(static_cast<QWidget *>(object));
} else if (classname == QLatin1String("InvisibleStatusbar") && object &&
object->isWidgetType()) {
interface = new FakeAccessibleStatusbar(static_cast<QWidget *>(object));
}
return interface;
}
#endif
private:
#if defined(Q_OS_LINUX)
InvisibleNotification *notification;
InvisibleStatusbar *statusbar;
#endif
#if defined(Q_OS_WIN)
QScopedPointer<QLibrary> mpUiaLibrary;
bool initializeUia();
class UiaProvider;
UiaProvider *uiaProvider{};
#endif
};
#endif // ANNOUNCER_H
/***************************************************************************
* Copyright 2019-2022 Leonard de Ruijter, James Teh - OSARA *
* Copyright 2017 The Qt Company Ltd. *
* Copyright (C) 2022 by Vadim Peretokin - vadim.peretokin@mudlet.org *
* *
* 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., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "Announcer.h"
#include "uiawrapper.h"
#include "utils.h"
#include <QAccessible>
#include <QDebug>
#include <QLibrary>
#include <memory>
#include <ole2.h>
#include <tlhelp32.h>
#include <uiautomation.h>
#include <utility>
#include <oleacc.h>
#include <uiautomationclient.h>
#include <uiautomationcore.h>
#include <uiautomationcoreapi.h>
// mingw 7.30's uiautomationclient.h is outdated, lacks this define
#define UIA_CustomControlTypeId (50025)
// this class is largely inspired by OSARA's UiaProvider:
// https://github.com/jcsteh/osara/blob/master/src/uia.cpp
class Announcer::UiaProvider : public IRawElementProviderSimple {
public:
UiaProvider(_In_ HWND hwnd) : refCount(0), controlHWnd(hwnd) {}
ULONG STDMETHODCALLTYPE AddRef() { return InterlockedIncrement(&refCount); }
ULONG STDMETHODCALLTYPE Release() {
long val = InterlockedDecrement(&refCount);
if (val == 0) {
delete this;
}
return val;
}
HRESULT STDMETHODCALLTYPE QueryInterface(_In_ REFIID riid,
_Outptr_ void **ppInterface) {
if (ppInterface) {
return E_INVALIDARG;
}
if (riid == __uuidof(IUnknown)) {
*ppInterface = static_cast<IRawElementProviderSimple *>(this);
} else if (riid == __uuidof(IRawElementProviderSimple)) {
*ppInterface = static_cast<IRawElementProviderSimple *>(this);
} else {
*ppInterface = nullptr;
return E_NOINTERFACE;
}
(static_cast<IUnknown *>(*ppInterface))->AddRef();
return S_OK;
}
HRESULT STDMETHODCALLTYPE
get_ProviderOptions(_Out_ ProviderOptions *pRetVal) {
if (!pRetVal) {
return E_INVALIDARG;
}
*pRetVal = static_cast<ProviderOptions>(ProviderOptions_ServerSideProvider |
ProviderOptions_UseComThreading);
return S_OK;
}
HRESULT STDMETHODCALLTYPE GetPatternProvider(
PATTERNID patternId, _Outptr_result_maybenull_ IUnknown **pRetVal) {
qDebug() << "UIA requested patternId " << patternId;
*pRetVal = NULL;
return S_OK;
}
HRESULT STDMETHODCALLTYPE GetPropertyValue(PROPERTYID propertyId,
_Out_ VARIANT *pRetVal) {
switch (propertyId) {
case UIA_ControlTypePropertyId:
// Stop Narrator from ever speaking this as a window
pRetVal->vt = VT_I4;
pRetVal->lVal = UIA_CustomControlTypeId;
break;
case UIA_IsControlElementPropertyId:
case UIA_IsContentElementPropertyId:
case UIA_IsKeyboardFocusablePropertyId:
pRetVal->vt = VT_BOOL;
pRetVal->boolVal = VARIANT_FALSE;
break;
case UIA_ProviderDescriptionPropertyId:
pRetVal->vt = VT_BSTR;
pRetVal->bstrVal = SysAllocString(L"Mudlet");
break;
default:
pRetVal->vt = VT_EMPTY;
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE
get_HostRawElementProvider(IRawElementProviderSimple **pRetVal) {
return UiaWrapper::self()->hostProviderFromHwnd(controlHWnd, pRetVal);
}
private:
virtual ~UiaProvider() {}
ULONG refCount;
HWND controlHWnd;
};
bool Announcer::initializeUia() {
// Constructor initializes refcount to 0, assignment to a CComPtr
// takes it to 1.
uiaProvider = new UiaProvider((HWND)this->winId());
// as we are not using CComPtr, ensure refcount is incremented to prevent the provider from being deleted early
uiaProvider->AddRef();
return true;
}
Announcer::Announcer(QWidget *parent) : QWidget{parent} { initializeUia(); }
BSTR bStrFromQString(const QString &value) {
return SysAllocString(reinterpret_cast<const wchar_t *>(value.utf16()));
}
void Announcer::announce(const QString text) {
qDebug() << "announce" << text;
BSTR displayString = bStrFromQString(text);
BSTR activityId = bStrFromQString(qsl("Mudlet"));
UiaWrapper::self()->raiseNotificationEvent(
uiaProvider, NotificationKind_ItemAdded, NotificationProcessing_All,
displayString, activityId);
::SysFreeString(displayString);
::SysFreeString(activityId);
}
/***************************************************************************
* Copyright 2019-2022 Leonard de Ruijter, James Teh - OSARA *
* Copyright 2017 The Qt Company Ltd. *
* Copyright (C) 2022 by Vadim Peretokin - vadim.peretokin@mudlet.org *
* *
* 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., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "uiawrapper.h"
#include "utils.h"
#include <QDebug>
#include <QLibrary>
// this class is largely inspired from Qt's QWindowsUiaWrapper:
// https://github.com/qt/qtbase/blob/dev/src/gui/accessible/windows/apisupport/qwindowsuiawrapper.cpp
UiaWrapper::UiaWrapper() {
QLibrary uiaLibrary(qsl("UIAutomationCore"));
if (uiaLibrary.load()) {
m_pUiaRaiseNotificationEvent =
reinterpret_cast<PtrUiaRaiseNotificationEvent>(
uiaLibrary.resolve("UiaRaiseNotificationEvent"));
m_pUiaDisconnectAllProviders =
reinterpret_cast<PtrUiaDisconnectAllProviders>(
uiaLibrary.resolve("UiaDisconnectAllProviders"));
m_pUiaDisconnectProvider = reinterpret_cast<PtrUiaDisconnectProvider>(
uiaLibrary.resolve("UiaDisconnectProvider"));
m_pUiaHostProviderFromHwnd = reinterpret_cast<PtrUiaHostProviderFromHwnd>(
uiaLibrary.resolve("UiaHostProviderFromHwnd"));
m_pUiaClientsAreListening = reinterpret_cast<PtrUiaClientsAreListening>(
uiaLibrary.resolve("UiaClientsAreListening"));
}
}
UiaWrapper::~UiaWrapper(){};
UiaWrapper *UiaWrapper::self() {
static UiaWrapper wrapper;
return &wrapper;
}
BOOL UiaWrapper::ready() {
return m_pUiaRaiseNotificationEvent && m_pUiaDisconnectAllProviders &&
m_pUiaDisconnectProvider && m_pUiaHostProviderFromHwnd &&
m_pUiaClientsAreListening;
}
HRESULT UiaWrapper::raiseNotificationEvent(
IRawElementProviderSimple *provider, NotificationKind notificationKind,
NotificationProcessing notificationProcessing, BSTR displayString,
BSTR activityId) {
if (!m_pUiaRaiseNotificationEvent) {
return UIA_E_NOTSUPPORTED;
}
qDebug() << "calling m_pUiaRaiseNotificationEvent" << provider;
return m_pUiaRaiseNotificationEvent(provider, notificationKind,
notificationProcessing, displayString,
activityId);
}
HRESULT UiaWrapper::disconnectAllProviders() {
if (!m_pUiaDisconnectAllProviders) {
return UIA_E_NOTSUPPORTED;
}
return m_pUiaDisconnectAllProviders();
}
HRESULT UiaWrapper::disconnectProvider(IRawElementProviderSimple *pProvider) {
if (!m_pUiaDisconnectProvider) {
return UIA_E_NOTSUPPORTED;
}
return m_pUiaDisconnectProvider(pProvider);
}
BOOL UiaWrapper::clientsAreListening() {
if (!m_pUiaClientsAreListening) {
return FALSE;
}
return m_pUiaClientsAreListening();
}
HRESULT
UiaWrapper::hostProviderFromHwnd(HWND hwnd,
IRawElementProviderSimple **ppProvider) {
if (!m_pUiaHostProviderFromHwnd) {
return UIA_E_NOTSUPPORTED;
}
return m_pUiaHostProviderFromHwnd(hwnd, ppProvider);
}
/***************************************************************************
* Copyright 2019-2022 Leonard de Ruijter, James Teh - OSARA *
* Copyright 2017 The Qt Company Ltd. *
* Copyright (C) 2022 by Vadim Peretokin - vadim.peretokin@mudlet.org *
* *
* 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., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef UIAWRAPPER_H
#define UIAWRAPPER_H
#include <memory>
#include <ole2.h>
#include <tlhelp32.h>
#include <uiautomation.h>
#include <utility>
#include <oleacc.h>
#include <uiautomationclient.h>
#include <uiautomationcore.h>
#include <uiautomationcoreapi.h>
enum NotificationProcessing {
NotificationProcessing_ImportantAll = 0,
NotificationProcessing_ImportantMostRecent = 1,
NotificationProcessing_All = 2,
NotificationProcessing_MostRecent = 3,
NotificationProcessing_CurrentThenMostRecent = 4
};
enum NotificationKind {
NotificationKind_ItemAdded = 0,
NotificationKind_ItemRemoved = 1,
NotificationKind_ActionCompleted = 2,
NotificationKind_ActionAborted = 3,
NotificationKind_Other = 4
};
class UiaWrapper {
UiaWrapper();
virtual ~UiaWrapper();
public:
static UiaWrapper *self();
BOOL ready();
LRESULT returnRawElementProvider(HWND hwnd, WPARAM wParam, LPARAM lParam,
IRawElementProviderSimple *el);
HRESULT raiseNotificationEvent(IRawElementProviderSimple *provider,
NotificationKind notificationKind,
NotificationProcessing notificationProcessing,
BSTR displayString, BSTR activityId);
HRESULT disconnectAllProviders();
HRESULT disconnectProvider(IRawElementProviderSimple *pProvider);
HRESULT hostProviderFromHwnd(HWND hwnd,
IRawElementProviderSimple **ppProvider);
BOOL clientsAreListening();
private:
typedef HRESULT(WINAPI *PtrUiaRaiseNotificationEvent)(
IRawElementProviderSimple *, NotificationKind, NotificationProcessing,
BSTR, BSTR);
typedef HRESULT(WINAPI *PtrUiaDisconnectAllProviders)();
typedef HRESULT(WINAPI *PtrUiaDisconnectProvider)(
IRawElementProviderSimple *);
typedef HRESULT(WINAPI *PtrUiaHostProviderFromHwnd)(
HWND, IRawElementProviderSimple **);
typedef BOOL(WINAPI *PtrUiaClientsAreListening)();
PtrUiaRaiseNotificationEvent m_pUiaRaiseNotificationEvent = nullptr;
PtrUiaDisconnectAllProviders m_pUiaDisconnectAllProviders = nullptr;
PtrUiaDisconnectProvider m_pUiaDisconnectProvider = nullptr;
PtrUiaHostProviderFromHwnd m_pUiaHostProviderFromHwnd = nullptr;
PtrUiaClientsAreListening m_pUiaClientsAreListening = nullptr;
};
#endif // UIAWRAPPER_H
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment