chore: baseline usable version

This commit is contained in:
dela
2026-06-10 11:17:51 +08:00
commit a2f82a86b6
932 changed files with 172551 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
project(QWKCore
VERSION ${QWINDOWKIT_VERSION}
LANGUAGES CXX
)
set(_src
qwkglobal.h
qwkglobal_p.h
qwkglobal.cpp
windowagentbase.h
windowagentbase_p.h
windowagentbase.cpp
windowitemdelegate_p.h
windowitemdelegate.cpp
kernel/nativeeventfilter_p.h
kernel/nativeeventfilter.cpp
kernel/sharedeventfilter_p.h
kernel/sharedeventfilter.cpp
kernel/winidchangeeventfilter_p.h
kernel/winidchangeeventfilter.cpp
shared/systemwindow_p.h
contexts/abstractwindowcontext_p.h
contexts/abstractwindowcontext.cpp
contexts/qtwindowcontext_p.h
contexts/qtwindowcontext.cpp
)
set(_links_private)
if(WIN32)
list(APPEND _src
qwindowkit_windows.h
qwindowkit_windows.cpp
shared/qwkwindowsextra_p.h
shared/windows10borderhandler_p.h
)
elseif(APPLE)
else()
list(APPEND _src
qwindowkit_linux.h
qwindowkit_linux.cpp
)
endif()
if(WIN32)
list(APPEND _src
contexts/win32windowcontext_p.h
contexts/win32windowcontext.cpp
)
list(APPEND _links_private uxtheme)
elseif(APPLE)
list(APPEND _src
contexts/cocoawindowcontext_p.h
contexts/cocoawindowcontext.mm
)
list(APPEND _links_private
"-framework Foundation"
"-framework Cocoa"
"-framework AppKit"
)
else()
list(APPEND _src
contexts/linuxx11context_p.h
contexts/linuxx11context.cpp
contexts/linuxwaylandcontext_p.h
contexts/linuxwaylandcontext.cpp
)
endif()
set(_sync_include_options)
if(QWINDOWKIT_ENABLE_STYLE_AGENT)
list(APPEND _src
style/styleagent.h
style/styleagent_p.h
style/styleagent.cpp
)
if(WIN32)
list(APPEND _src style/styleagent_win.cpp)
elseif(APPLE)
list(APPEND _src style/styleagent_mac.mm)
else()
list(APPEND _src style/styleagent_linux.cpp)
endif()
else()
list(APPEND _sync_include_options EXCLUDE "src/core/style/\\.+")
endif()
qwk_add_library(${PROJECT_NAME} AUTOGEN
SOURCES ${_src}
FEATURES cxx_std_17
LINKS_PRIVATE ${_links_private}
QT_LINKS Core Gui
QT_INCLUDE_PRIVATE Core Gui
INCLUDE_PRIVATE *
PREFIX QWK_CORE
SYNC_INCLUDE_OPTIONS ${_sync_include_options}
)
set(QWINDOWKIT_ENABLED_TARGETS ${QWINDOWKIT_ENABLED_TARGETS} ${PROJECT_NAME} PARENT_SCOPE)
set(QWINDOWKIT_ENABLED_SUBDIRECTORIES ${QWINDOWKIT_ENABLED_SUBDIRECTORIES} core PARENT_SCOPE)
@@ -0,0 +1,298 @@
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// SPDX-License-Identifier: Apache-2.0
#include "abstractwindowcontext_p.h"
#include <QtGui/QPen>
#include <QtGui/QPainter>
#include <QtGui/QScreen>
#include "qwkglobal_p.h"
namespace QWK {
AbstractWindowContext::AbstractWindowContext() = default;
AbstractWindowContext::~AbstractWindowContext() = default;
void AbstractWindowContext::setup(QObject *host, WindowItemDelegate *delegate) {
if (m_host || !host || !delegate) {
return;
}
m_host = host;
m_delegate.reset(delegate);
m_winIdChangeEventFilter.reset(delegate->createWinIdEventFilter(host, this));
notifyWinIdChange();
}
bool AbstractWindowContext::setHitTestVisible(QObject *obj, bool visible) {
Q_ASSERT(obj);
if (!obj) {
return false;
}
if (visible) {
m_hitTestVisibleItems.removeAll(nullptr);
m_hitTestVisibleItems.removeAll(obj);
m_hitTestVisibleItems.append(obj);
} else {
for (auto &item : m_hitTestVisibleItems) {
if (item == obj) {
item = nullptr;
}
}
}
return true;
}
bool AbstractWindowContext::setSystemButton(WindowAgentBase::SystemButton button,
QObject *obj) {
Q_ASSERT(button != WindowAgentBase::Unknown);
if (button == WindowAgentBase::Unknown) {
return false;
}
auto org = m_systemButtons[button];
if (org == obj) {
return false;
}
m_systemButtons[button] = obj;
return true;
}
bool AbstractWindowContext::setTitleBar(QObject *item) {
Q_ASSERT(item);
auto org = m_titleBar;
if (org == item) {
return false;
}
if (org) {
// Since the title bar is changed, all items inside it should be dereferenced right away
removeSystemButtonsAndHitTestItems();
}
m_titleBar = item;
return true;
}
#ifdef Q_OS_MAC
void AbstractWindowContext::setSystemButtonAreaCallback(const ScreenRectCallback &callback) {
m_systemButtonAreaCallback = callback;
virtual_hook(SystemButtonAreaChangedHook, nullptr);
}
#endif
bool AbstractWindowContext::isInSystemButtons(const QPoint &pos,
WindowAgentBase::SystemButton *button) const {
*button = WindowAgentBase::Unknown;
for (int i = WindowAgentBase::WindowIcon; i <= WindowAgentBase::Close; ++i) {
auto currentButton = m_systemButtons[i];
if (!currentButton || !m_delegate->isVisible(currentButton) || !m_delegate->isEnabled(currentButton)) {
continue;
}
if (m_delegate->mapGeometryToScene(currentButton).contains(pos)) {
*button = static_cast<WindowAgentBase::SystemButton>(i);
return true;
}
}
return false;
}
bool AbstractWindowContext::isInTitleBarDraggableArea(const QPoint &pos) const {
if (!m_titleBar) {
// There's no title bar at all, the mouse will always be in the client area.
return false;
}
if (!m_delegate->isVisible(m_titleBar) || !m_delegate->isEnabled(m_titleBar)) {
// The title bar is hidden or disabled for some reason, treat it as there's
// no title bar.
return false;
}
QRect windowRect = {QPoint(0, 0), m_windowHandle->size()};
QRect titleBarRect = m_delegate->mapGeometryToScene(m_titleBar);
if (!titleBarRect.intersects(windowRect)) {
// The title bar is totally outside the window for some reason,
// also treat it as there's no title bar.
return false;
}
if (!titleBarRect.contains(pos)) {
return false;
}
WindowAgentBase::SystemButton button;
if (isInSystemButtons(pos, &button)) {
return false;
}
for (auto &&item : std::as_const(m_hitTestVisibleItems)) {
if (item && m_delegate->isVisible(item) &&
m_delegate->mapGeometryToScene(item).contains(pos)) {
return false;
}
}
return true;
}
QString AbstractWindowContext::key() const {
return {};
}
QWK_USED static constexpr const struct {
const quint32 activeLight = MAKE_RGBA_COLOR(210, 233, 189, 226);
const quint32 activeDark = MAKE_RGBA_COLOR(177, 205, 190, 240);
const quint32 inactiveLight = MAKE_RGBA_COLOR(193, 195, 211, 203);
const quint32 inactiveDark = MAKE_RGBA_COLOR(240, 240, 250, 255);
} kSampleColorSet;
void AbstractWindowContext::virtual_hook(int id, void *data) {
switch (id) {
case CentralizeHook: {
if (!m_windowId)
return;
QRect windowGeometry = m_delegate->getGeometry(m_host);
QRect screenGeometry = m_windowHandle->screen()->geometry();
int x = (screenGeometry.width() - windowGeometry.width()) / 2;
int y = (screenGeometry.height() - windowGeometry.height()) / 2;
QPoint pos(x, y);
pos += screenGeometry.topLeft();
m_delegate->setGeometry(m_host, QRect(pos, windowGeometry.size()));
return;
}
case RaiseWindowHook: {
if (!m_windowId)
return;
m_delegate->setWindowVisible(m_host, true);
Qt::WindowStates state = m_delegate->getWindowState(m_host);
if (state & Qt::WindowMinimized) {
m_delegate->setWindowState(m_host, state & ~Qt::WindowMinimized);
}
m_delegate->bringWindowToTop(m_host);
return;
}
case DefaultColorsHook: {
auto &map = *static_cast<QMap<QString, QColor> *>(data);
map.clear();
map.insert(QStringLiteral("activeLight"), kSampleColorSet.activeLight);
map.insert(QStringLiteral("activeDark"), kSampleColorSet.activeDark);
map.insert(QStringLiteral("inactiveLight"), kSampleColorSet.inactiveLight);
map.insert(QStringLiteral("inactiveDark"), kSampleColorSet.inactiveDark);
return;
}
default:
break;
}
}
void AbstractWindowContext::showSystemMenu(const QPoint &pos) {
virtual_hook(ShowSystemMenuHook, &const_cast<QPoint &>(pos));
}
void AbstractWindowContext::notifyWinIdChange() {
auto oldWinId = m_windowId;
m_windowId = m_winIdChangeEventFilter->winId();
// In Qt6, after QWidget::close() is called, the related QWindow's all surfaces and the
// platform window will be removed, and the WinId will be set to 0. After that, when the
// QWidget is shown again, the whole things will be recreated again.
// As a result, we must update our WindowContext each time the WinId changes.
if (m_windowHandle) {
removeEventFilter(m_windowHandle);
}
m_windowHandle = m_delegate->hostWindow(m_host);
if (m_windowHandle) {
m_windowHandle->installEventFilter(this);
}
if (oldWinId != m_windowId) {
winIdChanged(m_windowId, oldWinId);
if (m_windowId) {
// Refresh window attributes
for (auto it = m_windowAttributesOrder.begin();
it != m_windowAttributesOrder.end();) {
if (!windowAttributeChanged(it->first, it->second, {})) {
m_windowAttributes.remove(it->first);
it = m_windowAttributesOrder.erase(it);
continue;
}
++it;
}
}
// Send to shared dispatchers
QEvent e(QEvent::WinIdChange);
sharedDispatch(m_host, &e);
}
}
QVariant AbstractWindowContext::windowAttribute(const QString &key) const {
auto it = m_windowAttributes.find(key);
if (it == m_windowAttributes.end()) {
return {};
}
return it.value()->second;
}
bool AbstractWindowContext::setWindowAttribute(const QString &key, const QVariant &attribute) {
auto it = m_windowAttributes.find(key);
if (it == m_windowAttributes.end()) {
if (!attribute.isValid()) {
return true;
}
if (m_windowId && !windowAttributeChanged(key, attribute, {})) {
return false;
}
m_windowAttributes.insert(
key, m_windowAttributesOrder.insert(m_windowAttributesOrder.end(),
std::make_pair(key, attribute)));
return true;
}
auto &listIter = it.value();
auto &oldAttr = listIter->second;
if (m_windowId && !windowAttributeChanged(key, attribute, oldAttr)) {
return false;
}
if (attribute.isValid()) {
oldAttr = attribute;
m_windowAttributesOrder.splice(m_windowAttributesOrder.end(), m_windowAttributesOrder,
listIter);
} else {
m_windowAttributesOrder.erase(listIter);
m_windowAttributes.erase(it);
}
return true;
}
bool AbstractWindowContext::eventFilter(QObject *obj, QEvent *event) {
if (obj == m_windowHandle && sharedDispatch(obj, event)) {
return true;
}
return QObject::eventFilter(obj, event);
}
bool AbstractWindowContext::windowAttributeChanged(const QString &key,
const QVariant &attribute,
const QVariant &oldAttribute) {
return false;
}
void AbstractWindowContext::removeSystemButtonsAndHitTestItems() {
for (auto &button : m_systemButtons) {
if (!button) {
continue;
}
button = nullptr;
}
m_hitTestVisibleItems.clear();
}
}
@@ -0,0 +1,172 @@
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// SPDX-License-Identifier: Apache-2.0
#ifndef ABSTRACTWINDOWCONTEXT_P_H
#define ABSTRACTWINDOWCONTEXT_P_H
//
// W A R N I N G !!!
// -----------------
//
// This file is not part of the QWindowKit API. It is used purely as an
// implementation detail. This header file may change from version to
// version without notice, or may even be removed.
//
#include <array>
#include <list>
#include <memory>
#include <utility>
#include <QtCore/QSet>
#include <QtCore/QPointer>
#include <QtGui/QRegion>
#include <QtGui/QWindow>
#include <QWKCore/windowagentbase.h>
#include <QWKCore/private/nativeeventfilter_p.h>
#include <QWKCore/private/sharedeventfilter_p.h>
#include <QWKCore/private/windowitemdelegate_p.h>
#include <QWKCore/private/winidchangeeventfilter_p.h>
namespace QWK {
class QWK_CORE_EXPORT AbstractWindowContext : public QObject,
public NativeEventDispatcher,
public SharedEventDispatcher {
Q_OBJECT
public:
AbstractWindowContext();
~AbstractWindowContext() override;
public:
void setup(QObject *host, WindowItemDelegate *delegate);
inline QObject *host() const;
inline QWindow *window() const;
inline WId windowId() const;
inline WindowItemDelegate *delegate() const;
inline bool isHitTestVisible(const QObject *obj) const;
bool setHitTestVisible(QObject *obj, bool visible);
inline QObject *systemButton(WindowAgentBase::SystemButton button) const;
bool setSystemButton(WindowAgentBase::SystemButton button, QObject *obj);
inline QObject *titleBar() const;
bool setTitleBar(QObject *obj);
#ifdef Q_OS_MAC
inline ScreenRectCallback systemButtonAreaCallback() const;
void setSystemButtonAreaCallback(const ScreenRectCallback &callback);
#endif
bool isInSystemButtons(const QPoint &pos, WindowAgentBase::SystemButton *button) const;
bool isInTitleBarDraggableArea(const QPoint &pos) const;
inline bool isHostWidthFixed() const {
return m_windowHandle
? ((m_windowHandle->flags() & Qt::MSWindowsFixedSizeDialogHint) ||
m_windowHandle->minimumWidth() == m_windowHandle->maximumWidth())
: false;
}
inline bool isHostHeightFixed() const {
return m_windowHandle
? ((m_windowHandle->flags() & Qt::MSWindowsFixedSizeDialogHint) ||
m_windowHandle->minimumHeight() == m_windowHandle->maximumHeight())
: false;
}
inline bool isHostSizeFixed() const {
return m_windowHandle ? ((m_windowHandle->flags() & Qt::MSWindowsFixedSizeDialogHint) ||
m_windowHandle->minimumSize() == m_windowHandle->maximumSize())
: false;
}
virtual QString key() const;
enum WindowContextHook {
CentralizeHook = 1,
RaiseWindowHook,
ShowSystemMenuHook,
DefaultColorsHook,
DrawWindows10BorderHook_Emulated, // Only works on Windows 10, emulated workaround
DrawWindows10BorderHook_Native, // Only works on Windows 10, native workaround
SystemButtonAreaChangedHook, // Only works on Mac
};
virtual void virtual_hook(int id, void *data);
void showSystemMenu(const QPoint &pos);
void notifyWinIdChange();
virtual QVariant windowAttribute(const QString &key) const;
virtual bool setWindowAttribute(const QString &key, const QVariant &attribute);
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
protected:
virtual void winIdChanged(WId winId, WId oldWinId) = 0;
virtual bool windowAttributeChanged(const QString &key, const QVariant &attribute,
const QVariant &oldAttribute);
protected:
QObject *m_host{};
std::unique_ptr<WindowItemDelegate> m_delegate;
QPointer<QWindow> m_windowHandle;
WId m_windowId{};
QVector<QPointer<QObject>> m_hitTestVisibleItems;
#ifdef Q_OS_MAC
ScreenRectCallback m_systemButtonAreaCallback;
#endif
QPointer<QObject> m_titleBar{};
std::array<QPointer<QObject>, WindowAgentBase::Close + 1> m_systemButtons{};
std::list<std::pair<QString, QVariant>> m_windowAttributesOrder;
QHash<QString, decltype(m_windowAttributesOrder)::iterator> m_windowAttributes;
std::unique_ptr<WinIdChangeEventFilter> m_winIdChangeEventFilter;
void removeSystemButtonsAndHitTestItems();
};
inline QObject *AbstractWindowContext::host() const {
return m_host;
}
inline QWindow *AbstractWindowContext::window() const {
return m_windowHandle;
}
inline WId AbstractWindowContext::windowId() const {
return m_windowId;
}
inline WindowItemDelegate *AbstractWindowContext::delegate() const {
return m_delegate.get();
}
inline bool AbstractWindowContext::isHitTestVisible(const QObject *obj) const {
return m_hitTestVisibleItems.contains(const_cast<QObject *>(obj));
}
inline QObject *
AbstractWindowContext::systemButton(WindowAgentBase::SystemButton button) const {
return m_systemButtons[button];
}
inline QObject *AbstractWindowContext::titleBar() const {
return m_titleBar;
}
#ifdef Q_OS_MAC
inline ScreenRectCallback AbstractWindowContext::systemButtonAreaCallback() const {
return m_systemButtonAreaCallback;
}
#endif
}
#endif // ABSTRACTWINDOWCONTEXT_P_H
@@ -0,0 +1,831 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#include "cocoawindowcontext_p.h"
#include <objc/runtime.h>
#include <AppKit/AppKit.h>
#include <Cocoa/Cocoa.h>
#include <QtGui/QGuiApplication>
#include "qwkglobal_p.h"
#include "systemwindow_p.h"
// https://forgetsou.github.io/2020/11/06/macos%E5%BC%80%E5%8F%91-%E5%85%B3%E9%97%AD-%E6%9C%80%E5%B0%8F%E5%8C%96-%E5%85%A8%E5%B1%8F%E5%B1%85%E4%B8%AD%E5%A4%84%E7%90%86(%E4%BB%BFMac%20QQ)/
// https://nyrra33.com/2019/03/26/changing-titlebars-height/
namespace QWK {
struct NSWindowProxy;
using ProxyList = QHash<WId, NSWindowProxy *>;
Q_GLOBAL_STATIC(ProxyList, g_proxyList);
}
struct QWK_NSWindowDelegate {
public:
enum NSEventType {
WillEnterFullScreen,
DidEnterFullScreen,
WillExitFullScreen,
DidExitFullScreen,
DidResize,
};
virtual ~QWK_NSWindowDelegate() = default;
virtual void windowEvent(NSEventType eventType) = 0;
};
//
// Objective C++ Begin
//
@interface QWK_NSWindowObserver : NSObject {
}
@end
@implementation QWK_NSWindowObserver
- (id)init {
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(windowWillEnterFullScreen:)
name:NSWindowWillEnterFullScreenNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(windowDidEnterFullScreen:)
name:NSWindowDidEnterFullScreenNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(windowWillExitFullScreen:)
name:NSWindowWillExitFullScreenNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(windowDidExitFullScreen:)
name:NSWindowDidExitFullScreenNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(windowDidResize:)
name:NSWindowDidResizeNotification
object:nil];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (void)windowWillEnterFullScreen:(NSNotification *)notification {
auto nswindow = reinterpret_cast<NSWindow *>(notification.object);
auto nsview = [nswindow contentView];
if (auto proxy = QWK::g_proxyList->value(reinterpret_cast<WId>(nsview))) {
reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowEvent(
QWK_NSWindowDelegate::WillEnterFullScreen);
}
}
- (void)windowDidEnterFullScreen:(NSNotification *)notification {
auto nswindow = reinterpret_cast<NSWindow *>(notification.object);
auto nsview = [nswindow contentView];
if (auto proxy = QWK::g_proxyList->value(reinterpret_cast<WId>(nsview))) {
reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowEvent(
QWK_NSWindowDelegate::DidEnterFullScreen);
}
}
- (void)windowWillExitFullScreen:(NSNotification *)notification {
auto nswindow = reinterpret_cast<NSWindow *>(notification.object);
auto nsview = [nswindow contentView];
if (auto proxy = QWK::g_proxyList->value(reinterpret_cast<WId>(nsview))) {
reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowEvent(
QWK_NSWindowDelegate::WillExitFullScreen);
}
}
- (void)windowDidExitFullScreen:(NSNotification *)notification {
auto nswindow = reinterpret_cast<NSWindow *>(notification.object);
auto nsview = [nswindow contentView];
if (auto proxy = QWK::g_proxyList->value(reinterpret_cast<WId>(nsview))) {
reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowEvent(
QWK_NSWindowDelegate::DidExitFullScreen);
}
}
- (void)windowDidResize:(NSNotification *)notification {
auto nswindow = reinterpret_cast<NSWindow *>(notification.object);
auto nsview = [nswindow contentView];
if (auto proxy = QWK::g_proxyList->value(reinterpret_cast<WId>(nsview))) {
reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowEvent(
QWK_NSWindowDelegate::DidResize);
}
}
@end
@interface QWK_NSViewObserver : NSObject
- (instancetype)initWithProxy:(QWK::NSWindowProxy*)proxy;
@end
//
// Objective C++ End
//
namespace QWK {
struct NSWindowProxy : public QWK_NSWindowDelegate {
enum class BlurMode {
Dark,
Light,
None,
};
NSWindowProxy(NSView *macView) {
nsview = macView;
observer = [[QWK_NSViewObserver alloc] initWithProxy:this];
[nsview addObserver:observer
forKeyPath:@"window"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:nil];
}
~NSWindowProxy() override {
[nsview removeObserver:observer forKeyPath:@"window"];
[observer release];
}
// Delegate
void windowEvent(NSEventType eventType) override {
switch (eventType) {
case WillExitFullScreen: {
auto nswindow = [nsview window];
nswindow.titleVisibility = NSWindowTitleHidden;
if (!screenRectCallback || !systemButtonVisible)
return;
// The system buttons will stuck at their default positions when the
// exit-fullscreen animation is running, we need to hide them until the
// animation finishes
for (const auto &button : systemButtons()) {
button.hidden = true;
}
break;
}
case DidExitFullScreen: {
if (!screenRectCallback || !systemButtonVisible)
return;
for (const auto &button : systemButtons()) {
button.hidden = false;
}
updateSystemButtonRect();
break;
}
case DidResize: {
if (!screenRectCallback || !systemButtonVisible) {
return;
}
updateSystemButtonRect();
break;
}
case DidEnterFullScreen: {
auto nswindow = [nsview window];
nswindow.titleVisibility = NSWindowTitleVisible;
break;
}
default:
break;
}
}
// System buttons visibility
void setSystemButtonVisible(bool visible) {
systemButtonVisible = visible;
for (const auto &button : systemButtons()) {
button.hidden = !visible;
}
if (!screenRectCallback || !visible) {
return;
}
updateSystemButtonRect();
}
// System buttons area
void setScreenRectCallback(const ScreenRectCallback &callback) {
screenRectCallback = callback;
if (!callback || !systemButtonVisible) {
return;
}
updateSystemButtonRect();
}
void updateSystemButtonRect() {
if (!screenRectCallback || !systemButtonVisible) {
return;
}
const auto &buttons = systemButtons();
const auto &leftButton = buttons[0];
const auto &midButton = buttons[1];
const auto &rightButton = buttons[2];
auto titlebar = rightButton.superview;
int titlebarHeight = titlebar.frame.size.height;
auto spacing = midButton.frame.origin.x - leftButton.frame.origin.x;
auto width = midButton.frame.size.width;
auto height = midButton.frame.size.height;
auto viewSize = nsview.frame.size;
QPoint center = screenRectCallback(QSize(viewSize.width, titlebarHeight)).center();
// The origin of the NSWindow coordinate system is in the lower left corner, we
// do the necessary transformations
center.ry() = titlebarHeight - center.y();
// Mid button
NSPoint centerOrigin = {
center.x() - width / 2,
center.y() - height / 2,
};
[midButton setFrameOrigin:centerOrigin];
// Left button
NSPoint leftOrigin = {
centerOrigin.x - spacing,
centerOrigin.y,
};
[leftButton setFrameOrigin:leftOrigin];
// Right button
NSPoint rightOrigin = {
centerOrigin.x + spacing,
centerOrigin.y,
};
[rightButton setFrameOrigin:rightOrigin];
}
inline std::array<NSButton *, 3> systemButtons() {
auto nswindow = [nsview window];
if (!nswindow) {
return {nullptr, nullptr, nullptr};
}
NSButton *closeBtn = [nswindow standardWindowButton:NSWindowCloseButton];
NSButton *minimizeBtn = [nswindow standardWindowButton:NSWindowMiniaturizeButton];
NSButton *zoomBtn = [nswindow standardWindowButton:NSWindowZoomButton];
return {closeBtn, minimizeBtn, zoomBtn};
}
inline int titleBarHeight() const {
auto nswindow = [nsview window];
if (!nswindow) {
return 0;
}
NSButton *closeBtn = [nswindow standardWindowButton:NSWindowCloseButton];
return closeBtn.superview.frame.size.height;
}
// Blur effect
bool setBlurEffect(BlurMode mode) {
static Class visualEffectViewClass = NSClassFromString(@"NSVisualEffectView");
if (!visualEffectViewClass)
return false;
NSVisualEffectView *effectView = nil;
for (NSView *subview in [[nsview superview] subviews]) {
if ([subview isKindOfClass:visualEffectViewClass]) {
effectView = reinterpret_cast<NSVisualEffectView *>(subview);
}
}
if (effectView == nil) {
return false;
}
static const auto originalMaterial = effectView.material;
static const auto originalBlendingMode = effectView.blendingMode;
static const auto originalState = effectView.state;
if (mode == BlurMode::None) {
effectView.material = originalMaterial;
effectView.blendingMode = originalBlendingMode;
effectView.state = originalState;
effectView.appearance = nil;
} else {
effectView.material = NSVisualEffectMaterialUnderWindowBackground;
effectView.blendingMode = NSVisualEffectBlendingModeBehindWindow;
effectView.state = NSVisualEffectStateFollowsWindowActiveState;
if (mode == BlurMode::Dark) {
effectView.appearance =
[NSAppearance appearanceNamed:@"NSAppearanceNameVibrantDark"];
} else {
effectView.appearance =
[NSAppearance appearanceNamed:@"NSAppearanceNameVibrantLight"];
}
}
return true;
}
// System title bar
void setSystemTitleBarVisible(const bool visible) {
auto nswindow = [nsview window];
if (!nswindow) {
return;
}
nsview.wantsLayer = YES;
nswindow.styleMask |= NSWindowStyleMaskResizable;
if (visible) {
nswindow.styleMask &= ~NSWindowStyleMaskFullSizeContentView;
} else {
nswindow.styleMask |= NSWindowStyleMaskFullSizeContentView;
}
nswindow.titlebarAppearsTransparent = (visible ? NO : YES);
nswindow.titleVisibility = (visible || (nswindow.styleMask & NSWindowStyleMaskFullScreen) ? NSWindowTitleVisible : NSWindowTitleHidden);
nswindow.hasShadow = YES;
// nswindow.showsToolbarButton = NO;
nswindow.movableByWindowBackground = NO;
nswindow.movable = NO; // This line causes the window in the wrong position when
// become fullscreen.
[nswindow standardWindowButton:NSWindowCloseButton].hidden = NO;
[nswindow standardWindowButton:NSWindowMiniaturizeButton].hidden = NO;
[nswindow standardWindowButton:NSWindowZoomButton].hidden = NO;
}
static void replaceImplementations() {
Method method = class_getInstanceMethod(windowClass, @selector(setStyleMask:));
oldSetStyleMask = reinterpret_cast<setStyleMaskPtr>(
method_setImplementation(method, reinterpret_cast<IMP>(setStyleMask)));
method =
class_getInstanceMethod(windowClass, @selector(setTitlebarAppearsTransparent:));
oldSetTitlebarAppearsTransparent =
reinterpret_cast<setTitlebarAppearsTransparentPtr>(method_setImplementation(
method, reinterpret_cast<IMP>(setTitlebarAppearsTransparent)));
#if 0
method = class_getInstanceMethod(windowClass, @selector(canBecomeKeyWindow));
oldCanBecomeKeyWindow = reinterpret_cast<canBecomeKeyWindowPtr>(method_setImplementation(method, reinterpret_cast<IMP>(canBecomeKeyWindow)));
method = class_getInstanceMethod(windowClass, @selector(canBecomeMainWindow));
oldCanBecomeMainWindow = reinterpret_cast<canBecomeMainWindowPtr>(method_setImplementation(method, reinterpret_cast<IMP>(canBecomeMainWindow)));
#endif
method = class_getInstanceMethod(windowClass, @selector(sendEvent:));
oldSendEvent = reinterpret_cast<sendEventPtr>(
method_setImplementation(method, reinterpret_cast<IMP>(sendEvent)));
// Alloc
windowObserver = [[QWK_NSWindowObserver alloc] init];
}
static void restoreImplementations() {
Method method = class_getInstanceMethod(windowClass, @selector(setStyleMask:));
method_setImplementation(method, reinterpret_cast<IMP>(oldSetStyleMask));
oldSetStyleMask = nil;
method =
class_getInstanceMethod(windowClass, @selector(setTitlebarAppearsTransparent:));
method_setImplementation(method,
reinterpret_cast<IMP>(oldSetTitlebarAppearsTransparent));
oldSetTitlebarAppearsTransparent = nil;
#if 0
method = class_getInstanceMethod(windowClass, @selector(canBecomeKeyWindow));
method_setImplementation(method, reinterpret_cast<IMP>(oldCanBecomeKeyWindow));
oldCanBecomeKeyWindow = nil;
method = class_getInstanceMethod(windowClass, @selector(canBecomeMainWindow));
method_setImplementation(method, reinterpret_cast<IMP>(oldCanBecomeMainWindow));
oldCanBecomeMainWindow = nil;
#endif
method = class_getInstanceMethod(windowClass, @selector(sendEvent:));
method_setImplementation(method, reinterpret_cast<IMP>(oldSendEvent));
oldSendEvent = nil;
// Delete
[windowObserver release];
windowObserver = nil;
}
static inline const Class windowClass = [NSWindow class];
protected:
static BOOL canBecomeKeyWindow(id obj, SEL sel) {
auto nswindow = reinterpret_cast<NSWindow *>(obj);
auto nsview = [nswindow contentView];
if (g_proxyList->contains(reinterpret_cast<WId>(nsview))) {
return YES;
}
if (oldCanBecomeKeyWindow) {
return oldCanBecomeKeyWindow(obj, sel);
}
return YES;
}
static BOOL canBecomeMainWindow(id obj, SEL sel) {
auto nswindow = reinterpret_cast<NSWindow *>(obj);
auto nsview = [nswindow contentView];
if (g_proxyList->contains(reinterpret_cast<WId>(nsview))) {
return YES;
}
if (oldCanBecomeMainWindow) {
return oldCanBecomeMainWindow(obj, sel);
}
return YES;
}
static void setStyleMask(id obj, SEL sel, NSWindowStyleMask styleMask) {
auto nswindow = reinterpret_cast<NSWindow *>(obj);
auto nsview = [nswindow contentView];
if (g_proxyList->contains(reinterpret_cast<WId>(nsview))) {
styleMask |= NSWindowStyleMaskFullSizeContentView;
}
if (oldSetStyleMask) {
oldSetStyleMask(obj, sel, styleMask);
}
}
static void setTitlebarAppearsTransparent(id obj, SEL sel, BOOL transparent) {
auto nswindow = reinterpret_cast<NSWindow *>(obj);
auto nsview = [nswindow contentView];
if (g_proxyList->contains(reinterpret_cast<WId>(nsview))) {
transparent = YES;
}
if (oldSetTitlebarAppearsTransparent) {
oldSetTitlebarAppearsTransparent(obj, sel, transparent);
}
}
static void sendEvent(id obj, SEL sel, NSEvent *event) {
if (oldSendEvent) {
oldSendEvent(obj, sel, event);
}
#if 0
const auto nswindow = reinterpret_cast<NSWindow *>(obj);
const auto it = instances.find(nswindow);
if (it == instances.end()) {
return;
}
NSWindowProxy *proxy = it.value();
if (event.type == NSEventTypeLeftMouseDown) {
proxy->lastMouseDownEvent = event;
QCoreApplication::processEvents();
proxy->lastMouseDownEvent = nil;
}
#endif
}
private:
Q_DISABLE_COPY(NSWindowProxy)
NSView *nsview = nil;
QWK_NSViewObserver* observer = nil;
bool systemButtonVisible = true;
ScreenRectCallback screenRectCallback;
static inline QWK_NSWindowObserver *windowObserver = nil;
// NSEvent *lastMouseDownEvent = nil;
using setStyleMaskPtr = void (*)(id, SEL, NSWindowStyleMask);
static inline setStyleMaskPtr oldSetStyleMask = nil;
using setTitlebarAppearsTransparentPtr = void (*)(id, SEL, BOOL);
static inline setTitlebarAppearsTransparentPtr oldSetTitlebarAppearsTransparent = nil;
using canBecomeKeyWindowPtr = BOOL (*)(id, SEL);
static inline canBecomeKeyWindowPtr oldCanBecomeKeyWindow = nil;
using canBecomeMainWindowPtr = BOOL (*)(id, SEL);
static inline canBecomeMainWindowPtr oldCanBecomeMainWindow = nil;
using sendEventPtr = void (*)(id, SEL, NSEvent *);
static inline sendEventPtr oldSendEvent = nil;
};
static inline NSWindow *mac_getNSWindow(const WId windowId) {
const auto nsview = reinterpret_cast<NSView *>(windowId);
return [nsview window];
}
static inline NSWindowProxy *ensureWindowProxy(const WId windowId) {
if (g_proxyList->isEmpty()) {
NSWindowProxy::replaceImplementations();
}
auto it = g_proxyList->find(windowId);
if (it == g_proxyList->end()) {
NSView *nsview = reinterpret_cast<NSView *>(windowId);
const auto proxy = new NSWindowProxy(nsview);
it = g_proxyList->insert(windowId, proxy);
}
return it.value();
}
static inline void releaseWindowProxy(const WId windowId) {
if (auto proxy = g_proxyList->take(windowId)) {
// TODO: Determine if the window is valid
// The window has been destroyed
// proxy->setSystemTitleBarVisible(true);
delete proxy;
} else {
return;
}
if (g_proxyList->isEmpty()) {
NSWindowProxy::restoreImplementations();
}
}
class CocoaWindowEventFilter : public SharedEventFilter {
public:
explicit CocoaWindowEventFilter(AbstractWindowContext *context);
~CocoaWindowEventFilter() override;
enum WindowStatus {
Idle,
WaitingRelease,
PreparingMove,
Moving,
};
protected:
bool sharedEventFilter(QObject *object, QEvent *event) override;
private:
AbstractWindowContext *m_context;
bool m_cursorShapeChanged;
WindowStatus m_windowStatus;
};
CocoaWindowEventFilter::CocoaWindowEventFilter(AbstractWindowContext *context)
: m_context(context), m_cursorShapeChanged(false), m_windowStatus(Idle) {
m_context->installSharedEventFilter(this);
}
CocoaWindowEventFilter::~CocoaWindowEventFilter() = default;
bool CocoaWindowEventFilter::sharedEventFilter(QObject *obj, QEvent *event) {
Q_UNUSED(obj)
auto type = event->type();
if (type < QEvent::MouseButtonPress || type > QEvent::MouseMove) {
return false;
}
auto host = m_context->host();
auto window = m_context->window();
auto delegate = m_context->delegate();
auto me = static_cast<const QMouseEvent *>(event);
QPoint scenePos = getMouseEventScenePos(me);
QPoint globalPos = getMouseEventGlobalPos(me);
bool inTitleBar = m_context->isInTitleBarDraggableArea(scenePos);
switch (type) {
case QEvent::MouseButtonPress: {
switch (me->button()) {
case Qt::LeftButton: {
if (inTitleBar) {
// If we call startSystemMove() now but release the mouse without actual
// movement, there will be no MouseReleaseEvent, so we defer it when the
// mouse is actually moving for the first time
m_windowStatus = PreparingMove;
event->accept();
return true;
}
break;
}
case Qt::RightButton: {
m_context->showSystemMenu(globalPos);
break;
}
default:
break;
}
m_windowStatus = WaitingRelease;
break;
}
case QEvent::MouseButtonRelease: {
switch (m_windowStatus) {
case PreparingMove:
case Moving: {
m_windowStatus = Idle;
event->accept();
return true;
}
case WaitingRelease: {
m_windowStatus = Idle;
break;
}
default: {
if (inTitleBar) {
event->accept();
return true;
}
break;
}
}
break;
}
case QEvent::MouseMove: {
switch (m_windowStatus) {
case Moving: {
return true;
}
case PreparingMove: {
m_windowStatus = Moving;
startSystemMove(window);
event->accept();
return true;
}
default:
break;
}
break;
}
case QEvent::MouseButtonDblClick: {
if (me->button() == Qt::LeftButton && inTitleBar && !m_context->isHostSizeFixed()) {
Qt::WindowFlags windowFlags = delegate->getWindowFlags(host);
Qt::WindowStates windowState = delegate->getWindowState(host);
if (!(windowState & Qt::WindowFullScreen)) {
if (windowState & Qt::WindowMaximized) {
delegate->setWindowState(host, windowState & ~Qt::WindowMaximized);
} else {
delegate->setWindowState(host, windowState | Qt::WindowMaximized);
}
event->accept();
return true;
}
}
break;
}
default:
break;
}
return false;
}
CocoaWindowContext::CocoaWindowContext() : AbstractWindowContext() {
cocoaWindowEventFilter = std::make_unique<CocoaWindowEventFilter>(this);
}
CocoaWindowContext::~CocoaWindowContext() {
releaseWindowProxy(m_windowId);
}
QString CocoaWindowContext::key() const {
return QStringLiteral("cocoa");
}
void CocoaWindowContext::virtual_hook(int id, void *data) {
switch (id) {
case SystemButtonAreaChangedHook: {
ensureWindowProxy(m_windowId)->setScreenRectCallback(m_systemButtonAreaCallback);
return;
}
default:
break;
}
AbstractWindowContext::virtual_hook(id, data);
}
QVariant CocoaWindowContext::windowAttribute(const QString &key) const {
if (key == QStringLiteral("title-bar-height")) {
if (!m_windowId)
return {};
return ensureWindowProxy(m_windowId)->titleBarHeight();
}
return AbstractWindowContext::windowAttribute(key);
}
void CocoaWindowContext::winIdChanged(WId winId, WId oldWinId) {
// If the original window id is valid, remove all resources related
if (oldWinId) {
releaseWindowProxy(oldWinId);
}
if (!winId) {
return;
}
// Allocate new resources
const auto proxy = ensureWindowProxy(winId);
if (proxy) {
proxy->setSystemButtonVisible(!windowAttribute(QStringLiteral("no-system-buttons")).toBool());
proxy->setScreenRectCallback(m_systemButtonAreaCallback);
proxy->setSystemTitleBarVisible(false);
}
}
bool CocoaWindowContext::windowAttributeChanged(const QString &key, const QVariant &attribute,
const QVariant &oldAttribute) {
Q_UNUSED(oldAttribute)
Q_ASSERT(m_windowId);
if (key == QStringLiteral("no-system-buttons")) {
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
if (attribute.type() != QVariant::Bool)
#else
if (attribute.typeId() != QMetaType::Type::Bool)
#endif
return false;
ensureWindowProxy(m_windowId)->setSystemButtonVisible(!attribute.toBool());
return true;
}
if (key == QStringLiteral("blur-effect")) {
auto mode = NSWindowProxy::BlurMode::None;
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
if (attribute.type() == QVariant::Bool) {
#else
if (attribute.typeId() == QMetaType::Type::Bool) {
#endif
if (attribute.toBool()) {
NSString *osxMode =
[[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"];
mode = [osxMode isEqualToString:@"Dark"] ? NSWindowProxy::BlurMode::Dark
: NSWindowProxy::BlurMode::Light;
}
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
} else if (attribute.type() == QVariant::String) {
#else
} else if (attribute.typeId() == QMetaType::Type::QString) {
#endif
auto value = attribute.toString();
if (value == QStringLiteral("dark")) {
mode = NSWindowProxy::BlurMode::Dark;
} else if (value == QStringLiteral("light")) {
mode = NSWindowProxy::BlurMode::Light;
} else if (value == QStringLiteral("none")) {
// ...
} else {
return false;
}
} else {
return false;
}
return ensureWindowProxy(m_windowId)->setBlurEffect(mode);
}
return false;
}
}
@implementation QWK_NSViewObserver {
QWK::NSWindowProxy* _proxy; // Weak reference
}
- (instancetype)initWithProxy:(QWK::NSWindowProxy*)proxy {
if (self = [super init]) {
_proxy = proxy;
}
return self;
}
// Using QEvent::Show to call setSystemTitleBarVisible/updateSystemButtonRect could also work,
// but observing the window property change via KVO provides more immediate notification when
// the NSWindow becomes available, making this approach more natural and reliable.
- (void)observeValueForKeyPath:(NSString*)keyPath
ofObject:(id)object
change:(NSDictionary*)change
context:(void*)context {
if ([keyPath isEqualToString:@"window"]) {
NSWindow* newWindow = change[NSKeyValueChangeNewKey];
// NSWindow* oldWindow = change[NSKeyValueChangeOldKey];
if (newWindow) {
_proxy->setSystemTitleBarVisible(false);
_proxy->updateSystemButtonRect();
}
}
}
@end
@@ -0,0 +1,43 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#ifndef COCOAWINDOWCONTEXT_P_H
#define COCOAWINDOWCONTEXT_P_H
//
// W A R N I N G !!!
// -----------------
//
// This file is not part of the QWindowKit API. It is used purely as an
// implementation detail. This header file may change from version to
// version without notice, or may even be removed.
//
#include <QWKCore/private/abstractwindowcontext_p.h>
namespace QWK {
class CocoaWindowContext : public AbstractWindowContext {
Q_OBJECT
public:
CocoaWindowContext();
~CocoaWindowContext() override;
QString key() const override;
void virtual_hook(int id, void *data) override;
QVariant windowAttribute(const QString &key) const override;
protected:
void winIdChanged(WId winId, WId oldWinId) override;
bool windowAttributeChanged(const QString &key, const QVariant &attribute,
const QVariant &oldAttribute) override;
protected:
std::unique_ptr<SharedEventFilter> cocoaWindowEventFilter;
};
}
#endif // COCOAWINDOWCONTEXT_P_H
@@ -0,0 +1,67 @@
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2025-2027 Wing-summer (wingsummer)
// SPDX-License-Identifier: Apache-2.0
#include "linuxwaylandcontext_p.h"
#include "qwindowkit_linux.h"
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#include <QtGui/qpa/qplatformnativeinterface.h>
namespace QWK {
static inline void xdg_toplevel_show_window_menu(struct xdg_toplevel *xdg_toplevel,
struct wl_seat *seat, uint32_t serial,
int32_t x, int32_t y) {
constexpr auto XDG_TOPLEVEL_SHOW_WINDOW_MENU = 4;
const auto &api = QWK::Private::waylandAPI();
Q_ASSERT(api.isValid());
api.wl_proxy_marshal_flags(
reinterpret_cast<struct wl_proxy *>(xdg_toplevel), XDG_TOPLEVEL_SHOW_WINDOW_MENU,
nullptr, api.wl_proxy_get_version(reinterpret_cast<struct wl_proxy *>(xdg_toplevel)), 0,
seat, serial, x, y);
}
LinuxWaylandContext::LinuxWaylandContext() : QtWindowContext() {
}
LinuxWaylandContext::~LinuxWaylandContext() = default;
QString LinuxWaylandContext::key() const {
return QStringLiteral("wayland");
}
void LinuxWaylandContext::virtual_hook(int id, void *data) {
if (id == ShowSystemMenuHook) {
auto *waylandApp = qApp->nativeInterface<QNativeInterface::QWaylandApplication>();
if (!waylandApp) {
return;
}
uint serial = waylandApp->lastInputSerial();
wl_seat *seat = waylandApp->lastInputSeat();
if (serial == 0 || !seat) {
return;
}
auto toplevel = static_cast<xdg_toplevel *>(
QGuiApplication::platformNativeInterface()->nativeResourceForWindow(
"xdg_toplevel", m_windowHandle));
if (!toplevel) {
return;
}
auto pos = static_cast<const QPoint *>(data);
xdg_toplevel_show_window_menu(toplevel, seat, serial, pos->x(), pos->y());
wl_display *d = waylandApp->display();
if (d) {
const auto &api = QWK::Private::waylandAPI();
Q_ASSERT(api.isValid());
api.wl_display_flush(d);
}
} else {
QtWindowContext::virtual_hook(id, data);
}
}
}
#endif // QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
@@ -0,0 +1,37 @@
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2025-2027 Wing-summer (wingsummer)
// SPDX-License-Identifier: Apache-2.0
#ifndef LINUXWAYLANDCONTEXT_P_H
#define LINUXWAYLANDCONTEXT_P_H
//
// W A R N I N G !!!
// -----------------
//
// This file is not part of the QWindowKit API. It is used purely as an
// implementation detail. This header file may change from version to
// version without notice, or may even be removed.
//
#include "qtwindowcontext_p.h"
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
namespace QWK {
class LinuxWaylandContext : public QtWindowContext {
Q_OBJECT
public:
LinuxWaylandContext();
~LinuxWaylandContext() override;
QString key() const override;
void virtual_hook(int id, void *data) override;
};
}
#endif // QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#endif // LINUXWAYLANDCONTEXT_P_H
@@ -0,0 +1,194 @@
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2025-2027 Wing-summer (wingsummer)
// SPDX-License-Identifier: Apache-2.0
#include "linuxx11context_p.h"
#include "qwindowkit_linux.h"
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
// copy from X11 library and simplify it
typedef struct _XExtData XExtData;
struct ScreenFormat;
struct Depth;
struct Visual;
typedef XID Colormap;
typedef struct {
XExtData *ext_data; /* hook for extension to hang data */
struct _XDisplay *display; /* back pointer to display structure */
Window root; /* Root window id. */
int width, height; /* width and height of screen */
int mwidth, mheight; /* width and height of in millimeters */
int ndepths; /* number of depths possible */
Depth *depths; /* list of allowable depths on the screen */
int root_depth; /* bits per pixel */
Visual *root_visual; /* root visual */
// typedef struct _XGC
// #ifdef XLIB_ILLEGAL_ACCESS
// {
// XExtData *ext_data; /* hook for extension to hang data */
// GContext gid; /* protocol ID for graphics context */
// /* there is more to this structure, but it is private to Xlib */
// }
// #endif
// *GC;
void * /*GC*/ default_gc; /* GC for the root root visual */
Colormap cmap; /* default color map */
unsigned long white_pixel;
unsigned long black_pixel; /* White and Black pixel values */
int max_maps, min_maps; /* max and min color maps */
int backing_store; /* Never, WhenMapped, Always */
Bool save_unders;
long root_input_mask; /* initial root input mask */
} Screen;
typedef char *XPointer;
typedef struct {
XExtData *ext_data; /* hook for extension to hang data */
struct _XPrivate *private1;
int fd; /* Network socket. */
int private2;
int proto_major_version; /* major version of server's X protocol */
int proto_minor_version; /* minor version of servers X protocol */
char *vendor; /* vendor of the server hardware */
XID private3;
XID private4;
XID private5;
int private6;
XID (*resource_alloc)(/* allocator function */
struct _XDisplay *);
int byte_order; /* screen byte order, LSBFirst, MSBFirst */
int bitmap_unit; /* padding and data requirements */
int bitmap_pad; /* padding requirements on bitmaps */
int bitmap_bit_order; /* LeastSignificant or MostSignificant */
int nformats; /* number of pixmap formats in list */
ScreenFormat *pixmap_format; /* pixmap format list */
int private8;
int release; /* release of the server */
struct _XPrivate *private9, *private10;
int qlen; /* Length of input event queue */
unsigned long last_request_read; /* seq number of last event read */
unsigned long request; /* sequence number of last request. */
XPointer private11;
XPointer private12;
XPointer private13;
XPointer private14;
unsigned max_request_size; /* maximum number 32 bit words in request*/
struct _XrmHashBucketRec *db;
int (*private15)(struct _XDisplay *);
char *display_name; /* "host:display" string used on this connect*/
int default_screen; /* default screen for operations */
int nscreens; /* number of screens on this server*/
Screen *screens; /* pointer to list of screens */
unsigned long motion_buffer; /* size of motion buffer */
unsigned long private16;
int min_keycode; /* minimum defined keycode */
int max_keycode; /* maximum defined keycode */
XPointer private17;
XPointer private18;
int private19;
char *xdefaults; /* contents of defaults from server */
/* there is more to this structure, but it is private to Xlib */
} *_XPrivDisplay;
typedef struct {
int type;
unsigned long serial; /* # of last request processed by server */
Bool send_event; /* true if this came from a SendEvent request */
Display *display; /* Display the event was read from */
Window window;
Atom message_type;
int format;
union {
char b[20];
short s[10];
long l[5];
} data;
} XClientMessageEvent;
/*
* this union is defined so Xlib can always use the same sized
* event structure internally, to avoid memory fragmentation.
*/
union _XEvent {
// delete so many members but size unchanged because of pad
XClientMessageEvent xclient;
long pad[24];
};
#define ScreenOfDisplay(dpy, scr) (&((_XPrivDisplay) (dpy))->screens[scr])
#define DefaultScreen(dpy) (((_XPrivDisplay) (dpy))->default_screen)
#define DefaultRootWindow(dpy) (ScreenOfDisplay(dpy, DefaultScreen(dpy))->root)
namespace QWK {
LinuxX11Context::LinuxX11Context() : QtWindowContext() {
}
LinuxX11Context::~LinuxX11Context() = default;
QString LinuxX11Context::key() const {
return QStringLiteral("xcb");
}
void LinuxX11Context::virtual_hook(int id, void *data) {
if (id == ShowSystemMenuHook) {
auto *x11app = qApp->nativeInterface<QNativeInterface::QX11Application>();
if (!x11app) {
return;
}
auto display = x11app->display();
if (!display) {
return;
}
const auto &api = QWK::Private::x11API();
Q_ASSERT(api.isValid());
// some marcos to constexpr in X11
constexpr auto None = 0L;
constexpr auto ClientMessage = 33;
constexpr auto False = 0;
constexpr auto Button3 = 3;
constexpr auto SubstructureNotifyMask = 1L << 19;
constexpr auto SubstructureRedirectMask = 1L << 20;
// use window id (XID)
auto xwin = static_cast<Window>(m_windowId);
Atom atom = api.XInternAtom(display, "_GTK_SHOW_WINDOW_MENU", False);
if (atom == None)
return; // WM might not support this atom
auto pos = static_cast<const QPoint *>(data);
XEvent ev{};
ev.xclient.type = ClientMessage;
ev.xclient.window = xwin;
ev.xclient.message_type = atom;
// The format member is set to 8, 16, or 32
// and specifies whether the data should be viewed as
// a list of bytes, shorts, or longs - typeof(xclient.data).
ev.xclient.format = 32;
qreal dpr = m_windowHandle->devicePixelRatio();
int root_x = qRound(pos->x() * dpr);
int root_y = qRound(pos->y() * dpr);
ev.xclient.data.l[0] = Button3; // right button
ev.xclient.data.l[1] = root_x;
ev.xclient.data.l[2] = root_y;
Window root = DefaultRootWindow(display);
api.XUngrabPointer(display, 0L);
api.XSendEvent(display, root, False, SubstructureRedirectMask | SubstructureNotifyMask,
&ev);
api.XFlush(display);
} else {
QtWindowContext::virtual_hook(id, data);
}
}
}
#endif // QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
@@ -0,0 +1,35 @@
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2025-2027 Wing-summer (wingsummer)
// SPDX-License-Identifier: Apache-2.0
#ifndef LINUXX11CONTEXT_P_H
#define LINUXX11CONTEXT_P_H
//
// W A R N I N G !!!
// -----------------
//
// This file is not part of the QWindowKit API. It is used purely as an
// implementation detail. This header file may change from version to
// version without notice, or may even be removed.
//
#include "qtwindowcontext_p.h"
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
namespace QWK {
class LinuxX11Context : public QtWindowContext {
Q_OBJECT
public:
LinuxX11Context();
~LinuxX11Context() override;
QString key() const override;
void virtual_hook(int id, void *data) override;
};
}
#endif // QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#endif // LINUXX11CONTEXT_P_H
@@ -0,0 +1,284 @@
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// SPDX-License-Identifier: Apache-2.0
#include "qtwindowcontext_p.h"
#include <QtCore/QDebug>
#include "qwkglobal_p.h"
#include "systemwindow_p.h"
namespace QWK {
static constexpr const quint8 kDefaultResizeBorderThickness = 8;
static Qt::CursorShape calculateCursorShape(const QWindow *window, const QPoint &pos) {
#ifdef Q_OS_MACOS
Q_UNUSED(window);
Q_UNUSED(pos);
return Qt::ArrowCursor;
#else
Q_ASSERT(window);
if (!window) {
return Qt::ArrowCursor;
}
if (window->visibility() != QWindow::Windowed) {
return Qt::ArrowCursor;
}
const int x = pos.x();
const int y = pos.y();
const int w = window->width();
const int h = window->height();
if (((x < kDefaultResizeBorderThickness) && (y < kDefaultResizeBorderThickness)) ||
((x >= (w - kDefaultResizeBorderThickness)) &&
(y >= (h - kDefaultResizeBorderThickness)))) {
return Qt::SizeFDiagCursor;
}
if (((x >= (w - kDefaultResizeBorderThickness)) && (y < kDefaultResizeBorderThickness)) ||
((x < kDefaultResizeBorderThickness) && (y >= (h - kDefaultResizeBorderThickness)))) {
return Qt::SizeBDiagCursor;
}
if ((x < kDefaultResizeBorderThickness) || (x >= (w - kDefaultResizeBorderThickness))) {
return Qt::SizeHorCursor;
}
if ((y < kDefaultResizeBorderThickness) || (y >= (h - kDefaultResizeBorderThickness))) {
return Qt::SizeVerCursor;
}
return Qt::ArrowCursor;
#endif
}
static inline Qt::Edges calculateWindowEdges(const QWindow *window, const QPoint &pos) {
#ifdef Q_OS_MACOS
Q_UNUSED(window);
Q_UNUSED(pos);
return {};
#else
Q_ASSERT(window);
if (!window) {
return {};
}
if (window->visibility() != QWindow::Windowed) {
return {};
}
Qt::Edges edges = {};
const int x = pos.x();
const int y = pos.y();
if (x < kDefaultResizeBorderThickness) {
edges |= Qt::LeftEdge;
}
if (x >= (window->width() - kDefaultResizeBorderThickness)) {
edges |= Qt::RightEdge;
}
if (y < kDefaultResizeBorderThickness) {
edges |= Qt::TopEdge;
}
if (y >= (window->height() - kDefaultResizeBorderThickness)) {
edges |= Qt::BottomEdge;
}
return edges;
#endif
}
class QtWindowEventFilter : public SharedEventFilter {
public:
explicit QtWindowEventFilter(AbstractWindowContext *context);
~QtWindowEventFilter() override;
enum WindowStatus {
Idle,
WaitingRelease,
PreparingMove,
Moving,
Resizing,
};
protected:
bool sharedEventFilter(QObject *object, QEvent *event) override;
private:
AbstractWindowContext *m_context;
bool m_cursorShapeChanged;
WindowStatus m_windowStatus;
};
QtWindowEventFilter::QtWindowEventFilter(AbstractWindowContext *context)
: m_context(context), m_cursorShapeChanged(false), m_windowStatus(Idle) {
m_context->installSharedEventFilter(this);
}
QtWindowEventFilter::~QtWindowEventFilter() = default;
bool QtWindowEventFilter::sharedEventFilter(QObject *obj, QEvent *event) {
Q_UNUSED(obj)
auto type = event->type();
if (type < QEvent::MouseButtonPress || type > QEvent::MouseMove) {
return false;
}
auto host = m_context->host();
auto window = m_context->window();
auto delegate = m_context->delegate();
auto me = static_cast<const QMouseEvent *>(event);
bool fixedSize = m_context->isHostSizeFixed();
QPoint scenePos = getMouseEventScenePos(me);
QPoint globalPos = getMouseEventGlobalPos(me);
bool inTitleBar = m_context->isInTitleBarDraggableArea(scenePos);
const auto& updateCursorShape{ [&](){
if (fixedSize) {
return;
}
const Qt::CursorShape shape = calculateCursorShape(window, scenePos);
if (shape == Qt::ArrowCursor) {
if (m_cursorShapeChanged) {
delegate->restoreCursorShape(host);
m_cursorShapeChanged = false;
}
} else {
delegate->setCursorShape(host, shape);
m_cursorShapeChanged = true;
}
} };
bool handled = false;
switch (type) {
case QEvent::MouseButtonPress: {
m_windowStatus = WaitingRelease;
switch (me->button()) {
case Qt::LeftButton: {
if (!fixedSize) {
Qt::Edges edges = calculateWindowEdges(window, scenePos);
if (edges != Qt::Edges()) {
startSystemResize(window, edges);
m_windowStatus = Resizing;
handled = true;
break;
}
}
if (inTitleBar) {
// If we call startSystemMove() now but release the mouse without actual
// movement, there will be no MouseReleaseEvent, so we defer it when the
// mouse is actually moving for the first time
m_windowStatus = PreparingMove;
handled = true;
}
break;
}
case Qt::RightButton: {
if (inTitleBar) {
m_context->showSystemMenu(globalPos);
m_windowStatus = Idle;
handled = true;
}
break;
}
default:
break;
}
break;
}
case QEvent::MouseButtonRelease: {
switch (m_windowStatus) {
case Idle: {
if (inTitleBar) {
handled = true;
}
break;
}
case WaitingRelease:
break;
case PreparingMove:
case Moving:
case Resizing: {
handled = true;
break;
}
}
m_windowStatus = Idle;
break;
}
case QEvent::MouseMove: {
switch (m_windowStatus) {
case Idle:
case WaitingRelease: {
updateCursorShape();
break;
}
case PreparingMove: {
startSystemMove(window);
m_windowStatus = Moving;
handled = true;
break;
}
case Moving:
case Resizing: {
handled = true;
break;
}
}
break;
}
case QEvent::MouseButtonDblClick: {
if (me->button() == Qt::LeftButton && inTitleBar && !fixedSize) {
Qt::WindowFlags windowFlags = delegate->getWindowFlags(host);
Qt::WindowStates windowState = delegate->getWindowState(host);
if ((windowFlags & Qt::WindowMaximizeButtonHint) &&
!(windowState & Qt::WindowFullScreen)) {
if (windowState & Qt::WindowMaximized) {
delegate->setWindowState(host, windowState & ~Qt::WindowMaximized);
} else {
delegate->setWindowState(host, windowState | Qt::WindowMaximized);
}
handled = true;
}
}
break;
}
default:
break;
}
if (handled) {
event->accept();
return true;
}
return false;
}
QtWindowContext::QtWindowContext() : AbstractWindowContext() {
qtWindowEventFilter = std::make_unique<QtWindowEventFilter>(this);
}
QtWindowContext::~QtWindowContext() = default;
QString QtWindowContext::key() const {
return QStringLiteral("qt");
}
void QtWindowContext::virtual_hook(int id, void *data) {
AbstractWindowContext::virtual_hook(id, data);
}
void QtWindowContext::winIdChanged(WId winId, WId oldWinId) {
if (!m_windowHandle) {
m_delegate->setWindowFlags(m_host, m_delegate->getWindowFlags(m_host) &
~Qt::FramelessWindowHint);
return;
}
// Allocate new resources
m_delegate->setWindowFlags(m_host,
m_delegate->getWindowFlags(m_host) | Qt::FramelessWindowHint);
}
}
@@ -0,0 +1,39 @@
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// SPDX-License-Identifier: Apache-2.0
#ifndef QTWINDOWCONTEXT_P_H
#define QTWINDOWCONTEXT_P_H
//
// W A R N I N G !!!
// -----------------
//
// This file is not part of the QWindowKit API. It is used purely as an
// implementation detail. This header file may change from version to
// version without notice, or may even be removed.
//
#include <QWKCore/private/abstractwindowcontext_p.h>
namespace QWK {
class QtWindowContext : public AbstractWindowContext {
Q_OBJECT
public:
QtWindowContext();
~QtWindowContext() override;
QString key() const override;
void virtual_hook(int id, void *data) override;
protected:
void winIdChanged(WId winId, WId oldWinId) override;
protected:
std::unique_ptr<SharedEventFilter> qtWindowEventFilter;
};
}
#endif // QTWINDOWCONTEXT_P_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,86 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#ifndef WIN32WINDOWCONTEXT_P_H
#define WIN32WINDOWCONTEXT_P_H
//
// W A R N I N G !!!
// -----------------
//
// This file is not part of the QWindowKit API. It is used purely as an
// implementation detail. This header file may change from version to
// version without notice, or may even be removed.
//
#include <QWKCore/qwindowkit_windows.h>
#include <QWKCore/private/abstractwindowcontext_p.h>
namespace QWK {
class Win32WindowContext : public AbstractWindowContext {
Q_OBJECT
public:
Win32WindowContext();
~Win32WindowContext() override;
enum WindowPart {
Outside,
ClientArea,
ChromeButton,
ResizeBorder,
FixedBorder,
TitleBar,
};
Q_ENUM(WindowPart)
QString key() const override;
void virtual_hook(int id, void *data) override;
QVariant windowAttribute(const QString &key) const override;
protected:
void winIdChanged(WId winId, WId oldWinId) override;
bool windowAttributeChanged(const QString &key, const QVariant &attribute,
const QVariant &oldAttribute) override;
public:
bool windowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT *result);
bool systemMenuHandler(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam,
LRESULT *result);
// In order to perfectly apply Windows 11 Snap Layout into the Qt window, we need to
// intercept and emulate most of the mouse events, so that the processing logic
// is quite complex. Simultaneously, in order to make the handling code of other
// Windows messages clearer, we have separated them into this function.
bool snapLayoutHandler(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam,
LRESULT *result);
bool customWindowHandler(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam,
LRESULT *result);
bool nonClientCalcSizeHandler(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam,
LRESULT *result);
protected:
// The last hit test result, helpful to handle WM_MOUSEMOVE and WM_NCMOUSELEAVE.
WindowPart lastHitTestResult = WindowPart::Outside;
int lastHitTestResultRaw = HTNOWHERE;
// Whether the last mouse leave message is blocked, mainly for handling the unexpected
// WM_MOUSELEAVE.
bool mouseLeaveBlocked = false;
// For emulating traditional icon button behavior
uint64_t iconButtonClickTime = 0;
int iconButtonClickLevel = 0;
// Attributes
bool noSystemMenu = false;
};
}
#endif // WIN32WINDOWCONTEXT_P_H
@@ -0,0 +1,90 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#include "nativeeventfilter_p.h"
#include <QtCore/QAbstractNativeEventFilter>
#include <QtCore/QCoreApplication>
namespace QWK {
NativeEventFilter::NativeEventFilter() : m_nativeDispatcher(nullptr) {
}
NativeEventFilter::~NativeEventFilter() {
if (m_nativeDispatcher)
m_nativeDispatcher->removeNativeEventFilter(this);
}
NativeEventDispatcher::NativeEventDispatcher() = default;
NativeEventDispatcher::~NativeEventDispatcher() {
for (const auto &observer : std::as_const(m_nativeEventFilters)) {
observer->m_nativeDispatcher = nullptr;
}
}
bool NativeEventDispatcher::nativeDispatch(const QByteArray &eventType, void *message,
QT_NATIVE_EVENT_RESULT_TYPE *result) {
for (const auto &ef : std::as_const(m_nativeEventFilters)) {
if (ef->nativeEventFilter(eventType, message, result))
return true;
}
return false;
}
void NativeEventDispatcher::installNativeEventFilter(NativeEventFilter *filter) {
if (!filter || filter->m_nativeDispatcher)
return;
m_nativeEventFilters.append(filter);
filter->m_nativeDispatcher = this;
}
void NativeEventDispatcher::removeNativeEventFilter(NativeEventFilter *filter) {
if (!m_nativeEventFilters.removeOne(filter)) {
return;
}
filter->m_nativeDispatcher = nullptr;
}
// Avoid adding multiple global native event filters to QGuiApplication
// in this library.
class AppMasterNativeEventFilter : public QAbstractNativeEventFilter,
public NativeEventDispatcher {
public:
AppMasterNativeEventFilter() {
qApp->installNativeEventFilter(this);
}
// The base class removes automatically
~AppMasterNativeEventFilter() override = default;
bool nativeEventFilter(const QByteArray &eventType, void *message,
QT_NATIVE_EVENT_RESULT_TYPE *result) override {
return nativeDispatch(eventType, message, result);
}
static inline AppMasterNativeEventFilter *instance = nullptr;
friend class AppNativeEventFilter;
};
AppNativeEventFilter::AppNativeEventFilter() {
if (!AppMasterNativeEventFilter::instance) {
AppMasterNativeEventFilter::instance = new AppMasterNativeEventFilter();
}
AppMasterNativeEventFilter::instance->installNativeEventFilter(this);
}
AppNativeEventFilter::~AppNativeEventFilter() {
AppMasterNativeEventFilter::instance->removeNativeEventFilter(this);
if (AppMasterNativeEventFilter::instance->m_nativeEventFilters.isEmpty()) {
delete AppMasterNativeEventFilter::instance;
AppMasterNativeEventFilter::instance = nullptr;
}
}
}
@@ -0,0 +1,70 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#ifndef NATIVEEVENTFILTER_P_H
#define NATIVEEVENTFILTER_P_H
//
// W A R N I N G !!!
// -----------------
//
// This file is not part of the QWindowKit API. It is used purely as an
// implementation detail. This header file may change from version to
// version without notice, or may even be removed.
//
#include <QWKCore/qwkglobal.h>
namespace QWK {
class NativeEventFilter;
class QWK_CORE_EXPORT NativeEventDispatcher {
public:
NativeEventDispatcher();
virtual ~NativeEventDispatcher();
public:
virtual bool nativeDispatch(const QByteArray &eventType, void *message,
QT_NATIVE_EVENT_RESULT_TYPE *result);
public:
void installNativeEventFilter(NativeEventFilter *filter);
void removeNativeEventFilter(NativeEventFilter *filter);
protected:
QList<NativeEventFilter *> m_nativeEventFilters;
friend class NativeEventFilter;
Q_DISABLE_COPY(NativeEventDispatcher)
};
class QWK_CORE_EXPORT NativeEventFilter {
public:
NativeEventFilter();
virtual ~NativeEventFilter();
public:
virtual bool nativeEventFilter(const QByteArray &eventType, void *message,
QT_NATIVE_EVENT_RESULT_TYPE *result) = 0;
protected:
NativeEventDispatcher *m_nativeDispatcher;
friend class NativeEventDispatcher;
Q_DISABLE_COPY(NativeEventFilter)
};
// Automatically install to QCoreApplication at construction
class QWK_CORE_EXPORT AppNativeEventFilter : public NativeEventFilter {
public:
AppNativeEventFilter();
~AppNativeEventFilter() override;
};
}
#endif // NATIVEEVENTFILTER_P_H
@@ -0,0 +1,48 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#include "sharedeventfilter_p.h"
namespace QWK {
SharedEventFilter::SharedEventFilter() : m_sharedDispatcher(nullptr) {
}
SharedEventFilter::~SharedEventFilter() {
if (m_sharedDispatcher)
m_sharedDispatcher->removeSharedEventFilter(this);
}
SharedEventDispatcher::SharedEventDispatcher() = default;
SharedEventDispatcher::~SharedEventDispatcher() {
for (const auto &observer : std::as_const(m_sharedEventFilters)) {
observer->m_sharedDispatcher = nullptr;
}
}
bool SharedEventDispatcher::sharedDispatch(QObject *obj, QEvent *event) {
for (const auto &ef : std::as_const(m_sharedEventFilters)) {
if (ef->sharedEventFilter(obj, event))
return true;
}
return false;
}
void SharedEventDispatcher::installSharedEventFilter(SharedEventFilter *filter) {
if (!filter || filter->m_sharedDispatcher)
return;
m_sharedEventFilters.append(filter);
filter->m_sharedDispatcher = this;
}
void SharedEventDispatcher::removeSharedEventFilter(SharedEventFilter *filter) {
if (!m_sharedEventFilters.removeOne(filter)) {
return;
}
filter->m_sharedDispatcher = nullptr;
}
}
@@ -0,0 +1,61 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#ifndef SHAREDEVENTFILTER_P_H
#define SHAREDEVENTFILTER_P_H
//
// W A R N I N G !!!
// -----------------
//
// This file is not part of the QWindowKit API. It is used purely as an
// implementation detail. This header file may change from version to
// version without notice, or may even be removed.
//
#include <QWKCore/qwkglobal.h>
namespace QWK {
class SharedEventFilter;
class QWK_CORE_EXPORT SharedEventDispatcher {
public:
SharedEventDispatcher();
virtual ~SharedEventDispatcher();
public:
virtual bool sharedDispatch(QObject *obj, QEvent *event);
public:
void installSharedEventFilter(SharedEventFilter *filter);
void removeSharedEventFilter(SharedEventFilter *filter);
protected:
QList<SharedEventFilter *> m_sharedEventFilters;
friend class SharedEventFilter;
Q_DISABLE_COPY(SharedEventDispatcher)
};
class QWK_CORE_EXPORT SharedEventFilter {
public:
SharedEventFilter();
virtual ~SharedEventFilter();
public:
virtual bool sharedEventFilter(QObject *obj, QEvent *event) = 0;
protected:
SharedEventDispatcher *m_sharedDispatcher;
friend class SharedEventDispatcher;
Q_DISABLE_COPY(SharedEventFilter)
};
}
#endif // SHAREDEVENTFILTER_P_H
@@ -0,0 +1,43 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#include "winidchangeeventfilter_p.h"
#include <QtGui/qpa/qplatformwindow.h>
#include "abstractwindowcontext_p.h"
namespace QWK {
WindowWinIdChangeEventFilter::WindowWinIdChangeEventFilter(QWindow *host,
AbstractWindowContext *context)
: WinIdChangeEventFilter(host, context), win(host), isAboutToBeDestroyed(false) {
host->installEventFilter(this);
}
WId WindowWinIdChangeEventFilter::winId() const {
auto win = static_cast<QWindow *>(host);
if (isAboutToBeDestroyed)
return 0;
if (auto platformWindow = win->handle())
return platformWindow->winId();
return 0;
}
bool WindowWinIdChangeEventFilter::eventFilter(QObject *obj, QEvent *event) {
Q_UNUSED(obj)
if (event->type() == QEvent::PlatformSurface) {
auto e = static_cast<QPlatformSurfaceEvent *>(event);
if (e->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) {
isAboutToBeDestroyed = true;
context->notifyWinIdChange();
isAboutToBeDestroyed = false;
} else {
context->notifyWinIdChange();
}
}
return false;
}
}
@@ -0,0 +1,53 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#ifndef WINIDCHANGEEVENTFILTER_P_H
#define WINIDCHANGEEVENTFILTER_P_H
//
// W A R N I N G !!!
// -----------------
//
// This file is not part of the QWindowKit API. It is used purely as an
// implementation detail. This header file may change from version to
// version without notice, or may even be removed.
//
#include <QtGui/QWindow>
#include <QWKCore/qwkglobal.h>
namespace QWK {
class AbstractWindowContext;
class WinIdChangeEventFilter : public QObject {
public:
WinIdChangeEventFilter(QObject *host, AbstractWindowContext *context)
: host(host), context(context) {
}
virtual WId winId() const = 0;
protected:
QObject *host;
AbstractWindowContext *context;
};
class QWK_CORE_EXPORT WindowWinIdChangeEventFilter : public WinIdChangeEventFilter {
public:
WindowWinIdChangeEventFilter(QWindow *host, AbstractWindowContext *context);
WId winId() const override;
protected:
QWindow *win;
bool isAboutToBeDestroyed;
bool eventFilter(QObject *obj, QEvent *event) override;
};
}
#endif // WINIDCHANGEEVENTFILTER_P_H
@@ -0,0 +1,87 @@
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2025-2027 Wing-summer (wingsummer)
// SPDX-License-Identifier: Apache-2.0
#include "qwindowkit_linux.h"
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#include <QGuiApplication>
#include <QLibrary>
namespace QWK {
namespace Private {
bool isX11Platform() {
static const bool isX11 = QGuiApplication::platformName().startsWith(
QStringLiteral("xcb"), Qt::CaseInsensitive);
return isX11;
}
bool isWaylandPlatform() {
static const bool isWayland = QGuiApplication::platformName().startsWith(
QStringLiteral("wayland"), Qt::CaseInsensitive);
return isWayland;
}
const LinuxWaylandAPI &waylandAPI() {
static LinuxWaylandAPI api;
static bool guard = true;
if (guard && isWaylandPlatform()) {
QLibrary waylib(QStringLiteral("libwayland-client.so"));
bool loaded = false;
if (waylib.load()) {
loaded = true;
} else {
waylib.setFileName(QStringLiteral("libwayland-client.so.0"));
if (waylib.load()) {
loaded = true;
}
}
if (loaded) {
api.wl_display_flush = reinterpret_cast<LinuxWaylandAPI::wl_display_flush_fn>(
waylib.resolve("wl_display_flush"));
api.wl_proxy_marshal_flags =
reinterpret_cast<LinuxWaylandAPI::wl_proxy_marshal_flags_fn>(
waylib.resolve("wl_proxy_marshal_flags"));
api.wl_proxy_get_version =
reinterpret_cast<LinuxWaylandAPI::wl_proxy_get_version_fn>(
waylib.resolve("wl_proxy_get_version"));
}
}
guard = false;
return api;
}
const LinuxX11API &x11API() {
static LinuxX11API api;
static bool guard = true;
if (guard && isX11Platform()) {
QString libName = QStringLiteral(
#if defined(__CYGWIN__)
"libX11-6.so"
#elif defined(__OpenBSD__) || defined(__NetBSD__)
"libX11.so"
#else
"libX11.so.6"
#endif
);
QLibrary x11lib(libName);
if (x11lib.load()) {
api.XInternAtom =
reinterpret_cast<LinuxX11API::XInternAtomFn>(x11lib.resolve("XInternAtom"));
api.XSendEvent =
reinterpret_cast<LinuxX11API::XSendEventFn>(x11lib.resolve("XSendEvent"));
api.XFlush = reinterpret_cast<LinuxX11API::XFlushFn>(x11lib.resolve("XFlush"));
api.XUngrabPointer = reinterpret_cast<LinuxX11API::XUngrabPointerFn>(
x11lib.resolve("XUngrabPointer"));
}
}
guard = false;
return api;
}
}
}
#endif // QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
@@ -0,0 +1,86 @@
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2025-2027 Wing-summer (wingsummer)
// SPDX-License-Identifier: Apache-2.0
#ifndef QWINDOWKIT_LINUX_H
#define QWINDOWKIT_LINUX_H
//
// W A R N I N G !!!
// -----------------
//
// This file is not part of the QWindowKit API. It is used purely as an
// implementation detail. This header file may change from version to
// version without notice, or may even be removed.
//
#include <QtCore/qglobal.h>
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#include <qguiapplication_platform.h>
// some declarations about x11
using Atom = unsigned long;
using Bool = int;
using XID = unsigned long;
using Window = XID;
union _XEvent;
using XEvent = union _XEvent;
// for wayland
struct wl_proxy;
namespace QWK {
namespace Private {
struct LinuxX11API {
LinuxX11API() = default;
Q_DISABLE_COPY(LinuxX11API)
using XInternAtomFn = Atom (*)(Display *, const char *, Bool);
using XSendEventFn = int (*)(Display *, Window, Bool, long, XEvent *);
using XFlushFn = int (*)(Display *);
using XUngrabPointerFn = int (*)(Display *, unsigned long);
XInternAtomFn XInternAtom = nullptr;
XSendEventFn XSendEvent = nullptr;
XFlushFn XFlush = nullptr;
XUngrabPointerFn XUngrabPointer = nullptr;
inline bool isValid() const {
return XInternAtom && XSendEvent && XFlush && XUngrabPointer;
}
};
struct LinuxWaylandAPI {
LinuxWaylandAPI() = default;
Q_DISABLE_COPY(LinuxWaylandAPI)
using wl_display_flush_fn = int (*)(struct wl_display *);
using wl_proxy_marshal_flags_fn = void (*)(struct wl_proxy *, uint32_t,
const struct wl_interface *, uint32_t,
uint32_t, ...);
using wl_proxy_get_version_fn = int (*)(struct wl_proxy *);
wl_display_flush_fn wl_display_flush = nullptr;
wl_proxy_marshal_flags_fn wl_proxy_marshal_flags = nullptr;
wl_proxy_get_version_fn wl_proxy_get_version = nullptr;
inline bool isValid() const {
return wl_display_flush && wl_proxy_marshal_flags && wl_proxy_get_version;
}
};
bool isWaylandPlatform();
bool isX11Platform();
const LinuxX11API &x11API();
const LinuxWaylandAPI &waylandAPI();
}
}
#endif // QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#endif // QWINDOWKIT_LINUX_H
@@ -0,0 +1,93 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#include "qwindowkit_windows.h"
using QWK_NTSTATUS = long;
namespace QWK {
static QWK_OSVERSIONINFOW GetRealOSVersionImpl() {
HMODULE hMod = ::GetModuleHandleW(L"ntdll.dll");
Q_ASSERT(hMod);
using RtlGetVersionPtr = QWK_NTSTATUS(WINAPI *)(QWK_OSVERSIONINFOW*);
auto pRtlGetVersion =
reinterpret_cast<RtlGetVersionPtr>(::GetProcAddress(hMod, "RtlGetVersion"));
Q_ASSERT(pRtlGetVersion);
QWK_OSVERSIONINFOW rovi{};
rovi.dwOSVersionInfoSize = sizeof(rovi);
pRtlGetVersion(&rovi);
return rovi;
}
namespace Private {
QWK_OSVERSIONINFOW GetRealOSVersion() {
static const auto result = GetRealOSVersionImpl();
return result;
}
}
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
WindowsRegistryKey::WindowsRegistryKey(HKEY parentHandle, QStringView subKey,
REGSAM permissions, REGSAM access) {
if (::RegOpenKeyExW(parentHandle, reinterpret_cast<const wchar_t *>(subKey.utf16()), 0,
permissions | access, &m_key) != ERROR_SUCCESS) {
m_key = nullptr;
}
}
WindowsRegistryKey::~WindowsRegistryKey() {
close();
}
void WindowsRegistryKey::close() {
if (isValid()) {
::RegCloseKey(m_key);
m_key = nullptr;
}
}
QString WindowsRegistryKey::stringValue(QStringView subKey) const {
QString result;
if (!isValid())
return result;
DWORD type;
DWORD size;
auto subKeyC = reinterpret_cast<const wchar_t *>(subKey.utf16());
if (::RegQueryValueExW(m_key, subKeyC, nullptr, &type, nullptr, &size) != ERROR_SUCCESS ||
(type != REG_SZ && type != REG_EXPAND_SZ) || size <= 2) {
return result;
}
// Reserve more for rare cases where trailing '\0' are missing in registry.
// Rely on 0-termination since strings of size 256 padded with 0 have been
// observed (QTBUG-84455).
size += 2;
QVarLengthArray<unsigned char> buffer(static_cast<int>(size));
std::fill(buffer.data(), buffer.data() + size, 0u);
if (::RegQueryValueExW(m_key, subKeyC, nullptr, &type, buffer.data(), &size) ==
ERROR_SUCCESS)
result = QString::fromWCharArray(reinterpret_cast<const wchar_t *>(buffer.constData()));
return result;
}
std::pair<DWORD, bool> WindowsRegistryKey::dwordValue(QStringView subKey) const {
if (!isValid())
return std::make_pair(0, false);
DWORD type;
auto subKeyC = reinterpret_cast<const wchar_t *>(subKey.utf16());
if (::RegQueryValueExW(m_key, subKeyC, nullptr, &type, nullptr, nullptr) != ERROR_SUCCESS ||
type != REG_DWORD) {
return std::make_pair(0, false);
}
DWORD value = 0;
DWORD size = sizeof(value);
const bool ok =
::RegQueryValueExW(m_key, subKeyC, nullptr, nullptr,
reinterpret_cast<unsigned char *>(&value), &size) == ERROR_SUCCESS;
return std::make_pair(value, ok);
}
#endif
}
@@ -0,0 +1,279 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#ifndef QWINDOWKIT_WINDOWS_H
#define QWINDOWKIT_WINDOWS_H
#ifndef _USER32_
# define _USER32_
#endif
#ifndef _DWMAPI_
# define _DWMAPI_
#endif
#include <QtCore/qt_windows.h>
#include <QtCore/qglobal.h>
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
# include <QtCore/private/qwinregistry_p.h>
#endif
#include <QWKCore/qwkglobal.h>
#ifndef GET_X_LPARAM
# define GET_X_LPARAM(lp) (static_cast<int>(static_cast<short>(LOWORD(lp))))
#endif
#ifndef GET_Y_LPARAM
# define GET_Y_LPARAM(lp) (static_cast<int>(static_cast<short>(HIWORD(lp))))
#endif
#ifndef RECT_WIDTH
# define RECT_WIDTH(rect) ((rect).right - (rect).left)
#endif
#ifndef RECT_HEIGHT
# define RECT_HEIGHT(rect) ((rect).bottom - (rect).top)
#endif
#ifndef USER_DEFAULT_SCREEN_DPI
# define USER_DEFAULT_SCREEN_DPI (96)
#endif
// Maybe undocumented Windows messages
// https://github.com/tinysec/public/blob/master/win32k/MessageTable.md
// https://ulib.sourceforge.io/doxy/a00239.html
#ifndef WM_UAHDESTROYWINDOW
# define WM_UAHDESTROYWINDOW (0x0090)
#endif
#ifndef WM_UNREGISTER_WINDOW_SERVICES
# define WM_UNREGISTER_WINDOW_SERVICES (0x0272)
#endif
#ifndef WM_NCUAHDRAWCAPTION
# define WM_NCUAHDRAWCAPTION (0x00AE)
#endif
#ifndef WM_NCUAHDRAWFRAME
# define WM_NCUAHDRAWFRAME (0x00AF)
#endif
using QWK_OSVERSIONINFOW = struct _QWK_OSVERSIONINFOW {
DWORD dwOSVersionInfoSize;
DWORD dwMajorVersion;
DWORD dwMinorVersion;
DWORD dwBuildNumber;
DWORD dwPlatformId;
wchar_t szCSDVersion[128];
};
namespace QWK {
namespace Private {
QWK_CORE_EXPORT QWK_OSVERSIONINFOW GetRealOSVersion();
inline bool IsWindows1122H2OrGreater_Real() {
QWK_OSVERSIONINFOW rovi = GetRealOSVersion();
return (rovi.dwMajorVersion > 10) ||
(rovi.dwMajorVersion == 10 &&
(rovi.dwMinorVersion > 0 || rovi.dwBuildNumber >= 22621));
}
inline bool IsWindows11OrGreater_Real() {
QWK_OSVERSIONINFOW rovi = GetRealOSVersion();
return (rovi.dwMajorVersion > 10) ||
(rovi.dwMajorVersion == 10 &&
(rovi.dwMinorVersion > 0 || rovi.dwBuildNumber >= 22000));
}
inline bool IsWindows1020H1OrGreater_Real() {
QWK_OSVERSIONINFOW rovi = GetRealOSVersion();
return (rovi.dwMajorVersion > 10) ||
(rovi.dwMajorVersion == 10 &&
(rovi.dwMinorVersion > 0 || rovi.dwBuildNumber >= 19041));
}
inline bool IsWindows102004OrGreater_Real() {
return IsWindows1020H1OrGreater_Real();
}
inline bool IsWindows101903OrGreater_Real() {
QWK_OSVERSIONINFOW rovi = GetRealOSVersion();
return (rovi.dwMajorVersion > 10) ||
(rovi.dwMajorVersion == 10 &&
(rovi.dwMinorVersion > 0 || rovi.dwBuildNumber >= 18362));
}
inline bool IsWindows1019H1OrGreater_Real() {
return IsWindows101903OrGreater_Real();
}
inline bool IsWindows101809OrGreater_Real() {
QWK_OSVERSIONINFOW rovi = GetRealOSVersion();
return (rovi.dwMajorVersion > 10) ||
(rovi.dwMajorVersion == 10 &&
(rovi.dwMinorVersion > 0 || rovi.dwBuildNumber >= 17763));
}
inline bool IsWindows10RS5OrGreater_Real() {
return IsWindows101809OrGreater_Real();
}
inline bool IsWindows10OrGreater_Real() {
QWK_OSVERSIONINFOW rovi = GetRealOSVersion();
return rovi.dwMajorVersion >= 10;
}
inline bool IsWindows8Point1OrGreater_Real() {
QWK_OSVERSIONINFOW rovi = GetRealOSVersion();
return (rovi.dwMajorVersion > 6) ||
(rovi.dwMajorVersion == 6 && rovi.dwMinorVersion >= 3);
}
inline bool IsWindows8OrGreater_Real() {
QWK_OSVERSIONINFOW rovi = GetRealOSVersion();
return (rovi.dwMajorVersion > 6) ||
(rovi.dwMajorVersion == 6 && rovi.dwMinorVersion >= 2);
}
inline bool IsWindows10Only_Real() {
return IsWindows10OrGreater_Real() && !IsWindows11OrGreater_Real();
}
}
//
// Registry Helpers
//
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
class QWK_CORE_EXPORT WindowsRegistryKey {
public:
WindowsRegistryKey(HKEY parentHandle, QStringView subKey, REGSAM permissions = KEY_READ,
REGSAM access = 0);
~WindowsRegistryKey();
bool isValid() const;
void close();
QString stringValue(QStringView subKey) const;
std::pair<DWORD, bool> dwordValue(QStringView subKey) const;
private:
HKEY m_key;
Q_DISABLE_COPY(WindowsRegistryKey)
};
inline bool WindowsRegistryKey::isValid() const {
return m_key != nullptr;
}
#elif QT_VERSION < QT_VERSION_CHECK(6, 8, 1)
using WindowsRegistryKey = QWinRegistryKey;
#else
class WindowsRegistryKey : public QWinRegistryKey {
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 1)
using SubKeyType = const wchar_t*;
#else
using SubKeyType = QStringView;
#endif
public:
WindowsRegistryKey(HKEY parentHandle, SubKeyType subKey,
REGSAM permissions = KEY_READ, REGSAM access = 0)
: QWinRegistryKey(parentHandle, subKey, permissions, access) {
}
inline std::pair<DWORD, bool> dwordValue(SubKeyType subKey) const;
};
inline std::pair<DWORD, bool> WindowsRegistryKey::dwordValue(SubKeyType subKey) const {
const auto val = value<DWORD>(subKey);
if (!val) {
return {0, false};
}
return {val.value(), true};
}
#endif
//
// Version Helpers
//
inline bool isWin8OrGreater() {
static const bool result = Private::IsWindows8OrGreater_Real();
return result;
}
inline bool isWin8Point1OrGreater() {
static const bool result = Private::IsWindows8Point1OrGreater_Real();
return result;
}
inline bool isWin10OrGreater() {
static const bool result = Private::IsWindows10OrGreater_Real();
return result;
}
inline bool isWin101809OrGreater() {
static const bool result = Private::IsWindows101809OrGreater_Real();
return result;
}
inline bool isWin10RS5OrGreater() {
return isWin101809OrGreater();
}
inline bool isWin101903OrGreater() {
static const bool result = Private::IsWindows101903OrGreater_Real();
return result;
}
inline bool isWin1019H1OrGreater() {
return isWin101903OrGreater();
}
inline bool isWin1020H1OrGreater() {
static const bool result = Private::IsWindows1020H1OrGreater_Real();
return result;
}
inline bool isWin102004OrGreater() {
return isWin1020H1OrGreater();
}
inline bool isWin11OrGreater() {
static const bool result = Private::IsWindows11OrGreater_Real();
return result;
}
inline bool isWin1122H2OrGreater() {
static const bool result = Private::IsWindows1122H2OrGreater_Real();
return result;
}
inline bool isWin10Only() {
static const bool result = Private::IsWindows10Only_Real();
return result;
};
//
// Native Event Helpers
//
inline bool isImmersiveColorSetChange(WPARAM wParam, LPARAM lParam) {
return !wParam && lParam &&
std::wcscmp(reinterpret_cast<LPCWSTR>(lParam), L"ImmersiveColorSet") == 0;
}
}
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
Q_DECLARE_METATYPE(QMargins)
#endif
#endif // QWINDOWKIT_WINDOWS_H
+48
View File
@@ -0,0 +1,48 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#include "qwkglobal_p.h"
#include <QtCore/QCoreApplication>
#include <QtCore/private/qobject_p.h>
namespace QWK {
bool forwardObjectEventFilters(QObject *currentFilter, QObject *receiver, QEvent *event) {
// https://github.com/qt/qtbase/blob/e26a87f1ecc40bc8c6aa5b889fce67410a57a702/src/corelib/kernel/qcoreapplication.cpp#L1244
// Send the event through the rest event filters
auto d = QObjectPrivate::get(receiver);
bool findCurrent = false;
if (receiver != QCoreApplication::instance() && d->extraData) {
for (qsizetype i = 0; i < d->extraData->eventFilters.size(); ++i) {
QObject *obj = d->extraData->eventFilters.at(i);
if (!findCurrent) {
if (obj == currentFilter) {
findCurrent = true; // Will start to filter from the next one
}
continue;
}
if (!obj)
continue;
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
if (QObjectPrivate::get(obj)->threadData.loadRelaxed() !=
d->threadData.loadRelaxed())
#else
if (QObjectPrivate::get(obj)->threadData != d->threadData)
#endif
{
qWarning("QCoreApplication: Object event filter cannot be in a different "
"thread.");
continue;
}
if (obj->eventFilter(receiver, event))
return true;
}
}
return false;
}
}
+50
View File
@@ -0,0 +1,50 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#ifndef QWKGLOBAL_H
#define QWKGLOBAL_H
#include <functional>
#include <QtCore/QEvent>
#include <QtGui/QtEvents>
#ifndef QWK_CORE_EXPORT
# ifdef QWK_CORE_STATIC
# define QWK_CORE_EXPORT
# else
# ifdef QWK_CORE_LIBRARY
# define QWK_CORE_EXPORT Q_DECL_EXPORT
# else
# define QWK_CORE_EXPORT Q_DECL_IMPORT
# endif
# endif
#endif
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
using QT_NATIVE_EVENT_RESULT_TYPE = qintptr;
using QT_ENTER_EVENT_TYPE = QEnterEvent;
#else
using QT_NATIVE_EVENT_RESULT_TYPE = long;
using QT_ENTER_EVENT_TYPE = QEvent;
#endif
#ifndef QWINDOWKIT_CONFIG
# define QWINDOWKIT_CONFIG(feature) ((1 / QWINDOWKIT_##feature) == 1)
#endif
#if defined(__GNUC__) || defined(__clang__)
# define QWINDOWKIT_PRINTF_FORMAT(fmtpos, attrpos) \
__attribute__((__format__(__printf__, fmtpos, attrpos)))
#else
# define QWINDOWKIT_PRINTF_FORMAT(fmtpos, attrpos)
#endif
namespace QWK {
using ScreenRectCallback = std::function<QRect(const QSize &)>;
}
#endif // QWKGLOBAL_H
+71
View File
@@ -0,0 +1,71 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#ifndef QWKGLOBAL_P_H
#define QWKGLOBAL_P_H
//
// W A R N I N G !!!
// -----------------
//
// This file is not part of the QWindowKit API. It is used purely as an
// implementation detail. This header file may change from version to
// version without notice, or may even be removed.
//
#include <QtCore/QObject>
#include <QtCore/QLoggingCategory>
#include <QtGui/QMouseEvent>
#include <QWKCore/qwkglobal.h>
QWK_CORE_EXPORT Q_DECLARE_LOGGING_CATEGORY(qWindowKitLog)
#define QWK_INFO qCInfo(qWindowKitLog)
#define QWK_DEBUG qCDebug(qWindowKitLog)
#define QWK_WARNING qCWarning(qWindowKitLog)
#define QWK_CRITICAL qCCritical(qWindowKitLog)
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
# define QWK_FATAL qCFatal(qWindowKitLog)
#endif
#define MAKE_RGB_COLOR(r, g, b) ((quint32) (((r) &0xFF) << 16) | (((g) &0xFF) << 8) | ((b) &0xFF))
#define MAKE_RGBA_COLOR(r, g, b, a) \
((quint32) (((a) &0xFF) << 24) | (((r) &0xFF) << 16) | (((g) &0xFF) << 8) | ((b) &0xFF))
#if defined(Q_CC_MSVC)
# define QWK_NOINLINE __declspec(noinline)
# define QWK_INLINE __forceinline
# define QWK_USED
#else
# define QWK_NOINLINE __attribute__((noinline))
# define QWK_INLINE __attribute__((always_inline))
# define QWK_USED __attribute__((used))
#endif
namespace QWK {
inline QPoint getMouseEventScenePos(const QMouseEvent *event) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return event->scenePosition().toPoint();
#else
return event->windowPos().toPoint();
#endif
}
inline QPoint getMouseEventGlobalPos(const QMouseEvent *event) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return event->globalPosition().toPoint();
#else
return event->screenPos().toPoint();
#endif
}
// Be careful when apply this function to a widget
QWK_CORE_EXPORT bool forwardObjectEventFilters(QObject *currentFilter, QObject *receiver,
QEvent *event);
}
#endif // QWKGLOBAL_P_H
@@ -0,0 +1,513 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#ifndef QWKWINDOWSEXTRA_P_H
#define QWKWINDOWSEXTRA_P_H
//
// W A R N I N G !!!
// -----------------
//
// This file is not part of the QWindowKit API. It is used purely as an
// implementation detail. This header file may change from version to
// version without notice, or may even be removed.
//
#include <QWKCore/qwindowkit_windows.h>
#include <QtCore/QtMath>
#include <QtGui/QGuiApplication>
#include <QtGui/QStyleHints>
#include <QtGui/QPalette>
#include <QtCore/private/qsystemlibrary_p.h>
// Don't include this header in any header files.
typedef struct _MARGINS
{
int cxLeftWidth;
int cxRightWidth;
int cyTopHeight;
int cyBottomHeight;
} MARGINS, *PMARGINS;
typedef enum MONITOR_DPI_TYPE {
MDT_EFFECTIVE_DPI = 0,
MDT_ANGULAR_DPI = 1,
MDT_RAW_DPI = 2,
MDT_DEFAULT = MDT_EFFECTIVE_DPI
} MONITOR_DPI_TYPE;
typedef struct _DWM_BLURBEHIND
{
DWORD dwFlags;
BOOL fEnable;
HRGN hRgnBlur;
BOOL fTransitionOnMaximized;
} DWM_BLURBEHIND, *PDWM_BLURBEHIND;
extern "C" {
UINT WINAPI GetDpiForWindow(HWND);
int WINAPI GetSystemMetricsForDpi(int, UINT);
BOOL WINAPI AdjustWindowRectExForDpi(LPRECT, DWORD, BOOL, DWORD, UINT);
HRESULT WINAPI GetDpiForMonitor(HMONITOR, MONITOR_DPI_TYPE, UINT *, UINT *);
HRESULT WINAPI DwmFlush();
HRESULT WINAPI DwmIsCompositionEnabled(BOOL*);
HRESULT WINAPI DwmGetWindowAttribute(HWND, DWORD, PVOID, DWORD);
HRESULT WINAPI DwmSetWindowAttribute(HWND, DWORD, LPCVOID, DWORD);
HRESULT WINAPI DwmExtendFrameIntoClientArea(HWND, const MARGINS*);
HRESULT WINAPI DwmEnableBlurBehindWindow(HWND, const DWM_BLURBEHIND*);
} // extern "C"
namespace QWK {
enum _DWMWINDOWATTRIBUTE {
// [set] BOOL, Allows the use of host backdrop brushes for the window.
_DWMWA_USE_HOSTBACKDROPBRUSH = 17,
// Undocumented, the same with DWMWA_USE_IMMERSIVE_DARK_MODE, but available on systems
// before Win10 20H1.
_DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19,
// [set] BOOL, Allows a window to either use the accent color, or dark, according to the
// user Color Mode preferences.
_DWMWA_USE_IMMERSIVE_DARK_MODE = 20,
// [set] WINDOW_CORNER_PREFERENCE, Controls the policy that rounds top-level window corners
_DWMWA_WINDOW_CORNER_PREFERENCE = 33,
// [get] UINT, width of the visible border around a thick frame window
_DWMWA_VISIBLE_FRAME_BORDER_THICKNESS = 37,
// [get, set] SYSTEMBACKDROP_TYPE, Controls the system-drawn backdrop material of a window,
// including behind the non-client area.
_DWMWA_SYSTEMBACKDROP_TYPE = 38,
// Undocumented, use this value to enable Mica material on Win11 21H2. You should use
// DWMWA_SYSTEMBACKDROP_TYPE instead on Win11 22H2 and newer.
_DWMWA_MICA_EFFECT = 1029
};
// Types used with DWMWA_SYSTEMBACKDROP_TYPE
enum _DWM_SYSTEMBACKDROP_TYPE {
_DWMSBT_AUTO, // [Default] Let DWM automatically decide the system-drawn backdrop for this
// window.
_DWMSBT_NONE, // [Disable] Do not draw any system backdrop.
_DWMSBT_MAINWINDOW, // [Mica] Draw the backdrop material effect corresponding to a
// long-lived window.
_DWMSBT_TRANSIENTWINDOW, // [Acrylic] Draw the backdrop material effect corresponding to a
// transient window.
_DWMSBT_TABBEDWINDOW, // [Mica Alt] Draw the backdrop material effect corresponding to a
// window with a tabbed title bar.
};
enum WINDOWCOMPOSITIONATTRIB {
WCA_UNDEFINED = 0,
WCA_NCRENDERING_ENABLED = 1,
WCA_NCRENDERING_POLICY = 2,
WCA_TRANSITIONS_FORCEDISABLED = 3,
WCA_ALLOW_NCPAINT = 4,
WCA_CAPTION_BUTTON_BOUNDS = 5,
WCA_NONCLIENT_RTL_LAYOUT = 6,
WCA_FORCE_ICONIC_REPRESENTATION = 7,
WCA_EXTENDED_FRAME_BOUNDS = 8,
WCA_HAS_ICONIC_BITMAP = 9,
WCA_THEME_ATTRIBUTES = 10,
WCA_NCRENDERING_EXILED = 11,
WCA_NCADORNMENTINFO = 12,
WCA_EXCLUDED_FROM_LIVEPREVIEW = 13,
WCA_VIDEO_OVERLAY_ACTIVE = 14,
WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15,
WCA_DISALLOW_PEEK = 16,
WCA_CLOAK = 17,
WCA_CLOAKED = 18,
WCA_ACCENT_POLICY = 19,
WCA_FREEZE_REPRESENTATION = 20,
WCA_EVER_UNCLOAKED = 21,
WCA_VISUAL_OWNER = 22,
WCA_HOLOGRAPHIC = 23,
WCA_EXCLUDED_FROM_DDA = 24,
WCA_PASSIVEUPDATEMODE = 25,
WCA_USEDARKMODECOLORS = 26,
WCA_CORNER_STYLE = 27,
WCA_PART_COLOR = 28,
WCA_DISABLE_MOVESIZE_FEEDBACK = 29,
WCA_LAST = 30
};
enum ACCENT_STATE {
ACCENT_DISABLED = 0,
ACCENT_ENABLE_GRADIENT = 1,
ACCENT_ENABLE_TRANSPARENTGRADIENT = 2,
ACCENT_ENABLE_BLURBEHIND = 3, // Traditional DWM blur
ACCENT_ENABLE_ACRYLICBLURBEHIND = 4, // RS4 1803
ACCENT_ENABLE_HOST_BACKDROP = 5, // RS5 1809
ACCENT_INVALID_STATE = 6 // Using this value will remove the window background
};
enum ACCENT_FLAG {
ACCENT_NONE = 0,
ACCENT_ENABLE_ACRYLIC = 1,
ACCENT_ENABLE_ACRYLIC_WITH_LUMINOSITY = 482
};
struct ACCENT_POLICY {
DWORD dwAccentState;
DWORD dwAccentFlags;
DWORD dwGradientColor; // #AABBGGRR
DWORD dwAnimationId;
};
using PACCENT_POLICY = ACCENT_POLICY *;
struct WINDOWCOMPOSITIONATTRIBDATA {
WINDOWCOMPOSITIONATTRIB Attrib;
PVOID pvData;
SIZE_T cbData;
};
using PWINDOWCOMPOSITIONATTRIBDATA = WINDOWCOMPOSITIONATTRIBDATA *;
enum PREFERRED_APP_MODE {
PAM_DEFAULT = 0, // Default behavior on systems before Win10 1809. It indicates the
// application doesn't support dark mode at all.
PAM_AUTO =
1, // Available since Win10 1809, let system decide whether to enable dark mode or not.
PAM_DARK = 2, // Available since Win10 1903, force dark mode regardless of the system theme.
PAM_LIGHT =
3, // Available since Win10 1903, force light mode regardless of the system theme.
PAM_MAX = 4
};
using SetWindowCompositionAttributePtr = BOOL(WINAPI *)(HWND, PWINDOWCOMPOSITIONATTRIBDATA);
// Win10 1809 (10.0.17763)
using RefreshImmersiveColorPolicyStatePtr = VOID(WINAPI *)(VOID); // Ordinal 104
using AllowDarkModeForWindowPtr = BOOL(WINAPI *)(HWND, BOOL); // Ordinal 133
using AllowDarkModeForAppPtr = BOOL(WINAPI *)(BOOL); // Ordinal 135
using FlushMenuThemesPtr = VOID(WINAPI *)(VOID); // Ordinal 136
// Win10 1903 (10.0.18362)
using SetPreferredAppModePtr = PREFERRED_APP_MODE(WINAPI *)(PREFERRED_APP_MODE); // Ordinal 135
namespace {
struct DynamicApis {
static inline const DynamicApis &instance() {
static const DynamicApis inst;
return inst;
}
#define DYNAMIC_API_DECLARE(NAME) decltype(&::NAME) p##NAME = nullptr
DYNAMIC_API_DECLARE(DwmFlush);
DYNAMIC_API_DECLARE(DwmIsCompositionEnabled);
DYNAMIC_API_DECLARE(DwmGetWindowAttribute);
DYNAMIC_API_DECLARE(DwmSetWindowAttribute);
DYNAMIC_API_DECLARE(DwmExtendFrameIntoClientArea);
DYNAMIC_API_DECLARE(DwmEnableBlurBehindWindow);
DYNAMIC_API_DECLARE(GetDpiForWindow);
DYNAMIC_API_DECLARE(GetSystemMetricsForDpi);
DYNAMIC_API_DECLARE(AdjustWindowRectExForDpi);
DYNAMIC_API_DECLARE(GetDpiForMonitor);
#undef DYNAMIC_API_DECLARE
SetWindowCompositionAttributePtr pSetWindowCompositionAttribute = nullptr;
RefreshImmersiveColorPolicyStatePtr pRefreshImmersiveColorPolicyState = nullptr;
AllowDarkModeForWindowPtr pAllowDarkModeForWindow = nullptr;
AllowDarkModeForAppPtr pAllowDarkModeForApp = nullptr;
FlushMenuThemesPtr pFlushMenuThemes = nullptr;
SetPreferredAppModePtr pSetPreferredAppMode = nullptr;
private:
inline DynamicApis() {
#define DYNAMIC_API_RESOLVE(DLL, NAME) \
p##NAME = reinterpret_cast<decltype(p##NAME)>(DLL.resolve(#NAME))
QSystemLibrary user32(QStringLiteral("user32"));
DYNAMIC_API_RESOLVE(user32, GetDpiForWindow);
DYNAMIC_API_RESOLVE(user32, GetSystemMetricsForDpi);
DYNAMIC_API_RESOLVE(user32, SetWindowCompositionAttribute);
DYNAMIC_API_RESOLVE(user32, AdjustWindowRectExForDpi);
QSystemLibrary shcore(QStringLiteral("shcore"));
DYNAMIC_API_RESOLVE(shcore, GetDpiForMonitor);
QSystemLibrary dwmapi(QStringLiteral("dwmapi"));
DYNAMIC_API_RESOLVE(dwmapi, DwmFlush);
DYNAMIC_API_RESOLVE(dwmapi, DwmIsCompositionEnabled);
DYNAMIC_API_RESOLVE(dwmapi, DwmGetWindowAttribute);
DYNAMIC_API_RESOLVE(dwmapi, DwmSetWindowAttribute);
DYNAMIC_API_RESOLVE(dwmapi, DwmExtendFrameIntoClientArea);
DYNAMIC_API_RESOLVE(dwmapi, DwmEnableBlurBehindWindow);
#undef DYNAMIC_API_RESOLVE
#define UNDOC_API_RESOLVE(DLL, NAME, ORDINAL) \
p##NAME = reinterpret_cast<decltype(p##NAME)>(DLL.resolve(MAKEINTRESOURCEA(ORDINAL)))
QSystemLibrary uxtheme(QStringLiteral("uxtheme"));
UNDOC_API_RESOLVE(uxtheme, RefreshImmersiveColorPolicyState, 104);
UNDOC_API_RESOLVE(uxtheme, AllowDarkModeForWindow, 133);
UNDOC_API_RESOLVE(uxtheme, AllowDarkModeForApp, 135);
UNDOC_API_RESOLVE(uxtheme, FlushMenuThemes, 136);
UNDOC_API_RESOLVE(uxtheme, SetPreferredAppMode, 135);
#undef UNDOC_API_RESOLVE
}
inline ~DynamicApis() = default;
Q_DISABLE_COPY(DynamicApis)
};
}
inline constexpr bool operator==(const POINT &lhs, const POINT &rhs) noexcept {
return ((lhs.x == rhs.x) && (lhs.y == rhs.y));
}
inline constexpr bool operator!=(const POINT &lhs, const POINT &rhs) noexcept {
return !operator==(lhs, rhs);
}
inline constexpr bool operator==(const SIZE &lhs, const SIZE &rhs) noexcept {
return ((lhs.cx == rhs.cx) && (lhs.cy == rhs.cy));
}
inline constexpr bool operator!=(const SIZE &lhs, const SIZE &rhs) noexcept {
return !operator==(lhs, rhs);
}
inline constexpr bool operator>(const SIZE &lhs, const SIZE &rhs) noexcept {
return ((lhs.cx * lhs.cy) > (rhs.cx * rhs.cy));
}
inline constexpr bool operator>=(const SIZE &lhs, const SIZE &rhs) noexcept {
return (operator>(lhs, rhs) || operator==(lhs, rhs));
}
inline constexpr bool operator<(const SIZE &lhs, const SIZE &rhs) noexcept {
return (operator!=(lhs, rhs) && !operator>(lhs, rhs));
}
inline constexpr bool operator<=(const SIZE &lhs, const SIZE &rhs) noexcept {
return (operator<(lhs, rhs) || operator==(lhs, rhs));
}
inline constexpr bool operator==(const RECT &lhs, const RECT &rhs) noexcept {
return ((lhs.left == rhs.left) && (lhs.top == rhs.top) && (lhs.right == rhs.right) &&
(lhs.bottom == rhs.bottom));
}
inline constexpr bool operator!=(const RECT &lhs, const RECT &rhs) noexcept {
return !operator==(lhs, rhs);
}
inline constexpr QPoint point2qpoint(const POINT &point) {
return QPoint{int(point.x), int(point.y)};
}
inline constexpr POINT qpoint2point(const QPoint &point) {
return POINT{LONG(point.x()), LONG(point.y())};
}
inline constexpr QSize size2qsize(const SIZE &size) {
return QSize{int(size.cx), int(size.cy)};
}
inline constexpr SIZE qsize2size(const QSize &size) {
return SIZE{LONG(size.width()), LONG(size.height())};
}
inline constexpr QRect rect2qrect(const RECT &rect) {
return QRect{
QPoint{int(rect.left), int(rect.top) },
QSize{int(RECT_WIDTH(rect)), int(RECT_HEIGHT(rect))}
};
}
inline constexpr RECT qrect2rect(const QRect &qrect) {
return RECT{LONG(qrect.left()), LONG(qrect.top()), LONG(qrect.right()),
LONG(qrect.bottom())};
}
inline constexpr QMargins margins2qmargins(const MARGINS &margins) {
return {margins.cxLeftWidth, margins.cyTopHeight, margins.cxRightWidth,
margins.cyBottomHeight};
}
inline constexpr MARGINS qmargins2margins(const QMargins &qmargins) {
return {qmargins.left(), qmargins.right(), qmargins.top(), qmargins.bottom()};
}
inline /*constexpr*/ QString hwnd2str(const WId windowId) {
// NULL handle is allowed here.
return QLatin1String("0x") +
QString::number(windowId, 16).toUpper().rightJustified(8, u'0');
}
inline /*constexpr*/ QString hwnd2str(HWND hwnd) {
// NULL handle is allowed here.
return hwnd2str(reinterpret_cast<WId>(hwnd));
}
inline bool isDwmCompositionEnabled() {
if (isWin8OrGreater()) {
return true;
}
const DynamicApis &apis = DynamicApis::instance();
if (!apis.pDwmIsCompositionEnabled) {
return false;
}
BOOL enabled = FALSE;
return SUCCEEDED(apis.pDwmIsCompositionEnabled(&enabled)) && enabled;
}
inline bool isWindowFrameBorderColorized() {
WindowsRegistryKey registry(HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\DWM)");
if (!registry.isValid()) {
return false;
}
auto value = registry.dwordValue(L"ColorPrevalence");
if (!value.second) {
return false;
}
return value.first;
}
inline bool isHighContrastModeEnabled() {
HIGHCONTRASTW hc{};
hc.cbSize = sizeof(hc);
::SystemParametersInfoW(SPI_GETHIGHCONTRAST, sizeof(hc), &hc, FALSE);
return (hc.dwFlags & HCF_HIGHCONTRASTON);
}
inline bool isDarkThemeActive() {
if (!isWin101809OrGreater()) {
return false;
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
return QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark;
#else
WindowsRegistryKey registry(
HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)");
if (!registry.isValid()) {
return false;
}
auto value = registry.dwordValue(L"AppsUseLightTheme");
if (!value.second) {
return false;
}
return !value.first;
#endif
}
inline bool isDarkWindowFrameEnabled(HWND hwnd) {
if (!isWin101809OrGreater()) {
return false;
}
BOOL enabled = FALSE;
const DynamicApis &apis = DynamicApis::instance();
const auto attr = isWin1020H1OrGreater() ? _DWMWA_USE_IMMERSIVE_DARK_MODE
: _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1;
return SUCCEEDED(apis.pDwmGetWindowAttribute(hwnd, attr, &enabled, sizeof(enabled))) &&
enabled;
}
inline QColor getAccentColor() {
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
return QGuiApplication::palette().color(QPalette::Accent);
#else
WindowsRegistryKey registry(HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\DWM)");
if (!registry.isValid()) {
return {};
}
auto value = registry.dwordValue(L"AccentColor");
if (!value.second) {
return {};
}
// The retrieved value is in the #AABBGGRR format, we need to
// convert it to the #AARRGGBB format which Qt expects.
QColor color = QColor::fromRgba(value.first);
if (!color.isValid()) {
return {};
}
return QColor::fromRgb(color.blue(), color.green(), color.red(), color.alpha());
#endif
}
inline quint32 getDpiForWindow(HWND hwnd) {
const DynamicApis &apis = DynamicApis::instance();
if (apis.pGetDpiForWindow) { // Win10
return apis.pGetDpiForWindow(hwnd);
} else if (apis.pGetDpiForMonitor) { // Win8.1
HMONITOR monitor = ::MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
UINT dpiX{0};
UINT dpiY{0};
apis.pGetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY);
return dpiX;
} else { // Win2K
HDC hdc = ::GetDC(nullptr);
const int dpiX = ::GetDeviceCaps(hdc, LOGPIXELSX);
// const int dpiY = ::GetDeviceCaps(hdc, LOGPIXELSY);
::ReleaseDC(nullptr, hdc);
return quint32(dpiX);
}
}
inline quint32 getSystemMetricsForDpi(int index, quint32 dpi) {
const DynamicApis &apis = DynamicApis::instance();
if (apis.pGetSystemMetricsForDpi) {
return apis.pGetSystemMetricsForDpi(index, dpi);
}
const int result = ::GetSystemMetrics(index);
// GetSystemMetrics() always give you scaled value.
if (dpi != USER_DEFAULT_SCREEN_DPI) {
return result;
}
const qreal dpr = qreal(dpi) / qreal(USER_DEFAULT_SCREEN_DPI);
// ### Not sure how Windows itself rounds non-integer value.
return qFloor(qreal(result) / dpr);
}
inline quint32 getWindowFrameBorderThickness(HWND hwnd) {
const DynamicApis &apis = DynamicApis::instance();
if (isWin11OrGreater()) {
UINT result = 0;
if (SUCCEEDED(apis.pDwmGetWindowAttribute(hwnd, _DWMWA_VISIBLE_FRAME_BORDER_THICKNESS,
&result, sizeof(result)))) {
return result;
}
}
if (isWin10OrGreater()) {
const quint32 dpi = getDpiForWindow(hwnd);
// When DPI is 96, it should be 1px.
return getSystemMetricsForDpi(SM_CXBORDER, dpi);
}
// There's no such thing (a visible frame border line) before Win10.
return 0;
}
inline quint32 getResizeBorderThickness(HWND hwnd) {
const quint32 dpi = getDpiForWindow(hwnd);
// When DPI is 96, SM_CXSIZEFRAME is 4px, SM_CXPADDEDBORDER is also 4px,
// so the result should be 8px. This result won't be affected by OS version,
// it's 8px in Win7, and so in Win11.
return getSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) +
getSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
}
inline quint32 getTitleBarHeight(HWND hwnd) {
const quint32 dpi = getDpiForWindow(hwnd);
// When DPI is 96, SM_CYCAPTION is 23px, so the result should be 31px.
// However, according to latest MS design manual, the title bar height
// should be 32px, maybe there's some rounding issue.
return getSystemMetricsForDpi(SM_CYCAPTION, dpi) +
getSystemMetricsForDpi(SM_CYSIZEFRAME, dpi) +
getSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
}
}
#endif // QWKWINDOWSEXTRA_P_H
@@ -0,0 +1,163 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#ifndef SYSTEMWINDOW_P_H
#define SYSTEMWINDOW_P_H
//
// W A R N I N G !!!
// -----------------
//
// This file is not part of the QWindowKit API. It is used purely as an
// implementation detail. This header file may change from version to
// version without notice, or may even be removed.
//
#include <QtGui/QWindow>
#include <QtGui/QMouseEvent>
#include <QWKCore/private/qwkglobal_p.h>
namespace QWK {
class WindowMoveManipulator : public QObject {
public:
explicit WindowMoveManipulator(QWindow *targetWindow)
: QObject(targetWindow), target(targetWindow), operationComplete(false),
initialMousePosition(QCursor::pos()),
initialWindowPosition(targetWindow->position()) {
target->installEventFilter(this);
}
protected:
bool eventFilter(QObject *obj, QEvent *event) override {
if (operationComplete) {
return false;
}
switch (event->type()) {
case QEvent::MouseMove: {
auto mouseEvent = static_cast<QMouseEvent *>(event);
QPoint delta = getMouseEventGlobalPos(mouseEvent) - initialMousePosition;
target->setPosition(initialWindowPosition + delta);
return true;
}
case QEvent::MouseButtonRelease: {
if (target->y() < 0) {
target->setPosition(target->x(), 0);
}
operationComplete = true;
deleteLater();
break;
}
default:
break;
}
return false;
}
private:
QWindow *target;
bool operationComplete;
QPoint initialMousePosition;
QPoint initialWindowPosition;
};
class WindowResizeManipulator : public QObject {
public:
WindowResizeManipulator(QWindow *targetWindow, Qt::Edges edges)
: QObject(targetWindow), target(targetWindow), operationComplete(false),
initialMousePosition(QCursor::pos()), initialWindowRect(target->geometry()),
resizeEdges(edges) {
target->installEventFilter(this);
}
protected:
bool eventFilter(QObject *obj, QEvent *event) override {
if (operationComplete) {
return false;
}
switch (event->type()) {
case QEvent::MouseMove: {
auto mouseEvent = static_cast<QMouseEvent *>(event);
QPoint globalMousePos = getMouseEventGlobalPos(mouseEvent);
QRect windowRect = initialWindowRect;
if (resizeEdges & Qt::LeftEdge) {
int delta = globalMousePos.x() - initialMousePosition.x();
windowRect.setLeft(initialWindowRect.left() + delta);
}
if (resizeEdges & Qt::RightEdge) {
int delta = globalMousePos.x() - initialMousePosition.x();
windowRect.setRight(initialWindowRect.right() + delta);
}
if (resizeEdges & Qt::TopEdge) {
int delta = globalMousePos.y() - initialMousePosition.y();
windowRect.setTop(initialWindowRect.top() + delta);
}
if (resizeEdges & Qt::BottomEdge) {
int delta = globalMousePos.y() - initialMousePosition.y();
windowRect.setBottom(initialWindowRect.bottom() + delta);
}
target->setGeometry(windowRect);
return true;
}
case QEvent::MouseButtonRelease: {
operationComplete = true;
deleteLater();
break;
}
default:
break;
}
return false;
}
private:
QWindow *target;
bool operationComplete;
QPoint initialMousePosition;
QRect initialWindowRect;
Qt::Edges resizeEdges;
};
// QWindow::startSystemMove() and QWindow::startSystemResize() is first supported at Qt 5.15
// QWindow::startSystemResize() returns false on macOS
// QWindow::startSystemMove() and QWindow::startSystemResize() returns false on Linux Unity DE
// When the new API fails, we emulate the window actions using the classical API.
inline void startSystemMove(QWindow *window) {
#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
std::ignore = new WindowMoveManipulator(window);
#elif defined(Q_OS_LINUX)
if (window->startSystemMove()) {
return;
}
std::ignore = new WindowMoveManipulator(window);
#else
window->startSystemMove();
#endif
}
inline void startSystemResize(QWindow *window, Qt::Edges edges) {
#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
std::ignore = new WindowResizeManipulator(window, edges);
#elif defined(Q_OS_MAC) || defined(Q_OS_LINUX)
if (window->startSystemResize(edges)) {
return;
}
std::ignore = new WindowResizeManipulator(window, edges);
#else
window->startSystemResize(edges);
#endif
}
}
#endif // SYSTEMWINDOW_P_H
@@ -0,0 +1,152 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#ifndef WINDOWS10BORDERHANDLER_P_H
#define WINDOWS10BORDERHANDLER_P_H
//
// W A R N I N G !!!
// -----------------
//
// This file is not part of the QWindowKit API. It is used purely as an
// implementation detail. This header file may change from version to
// version without notice, or may even be removed.
//
#include <QtGui/QWindow>
#include <QtGui/QMouseEvent>
#include <QWKCore/qwindowkit_windows.h>
#include <QWKCore/private/qwkglobal_p.h>
#include <QWKCore/private/abstractwindowcontext_p.h>
namespace QWK {
class Windows10BorderHandler : public NativeEventFilter, public SharedEventFilter {
public:
inline Windows10BorderHandler(AbstractWindowContext *ctx) : ctx(ctx) {
ctx->installNativeEventFilter(this);
ctx->installSharedEventFilter(this);
}
inline void setupNecessaryAttributes() {
if (!isWin11OrGreater()) {
// https://github.com/microsoft/terminal/blob/71a6f26e6ece656084e87de1a528c4a8072eeabd/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp#L940
// Must extend top frame to client area
static QVariant defaultMargins = QVariant::fromValue(QMargins(0, 1, 0, 0));
ctx->setWindowAttribute(QStringLiteral("extra-margins"), defaultMargins);
}
// Enable dark mode by default, otherwise the system borders are white
ctx->setWindowAttribute(QStringLiteral("dark-mode"), true);
}
inline bool isNormalWindow() const {
return !(ctx->window()->windowStates() &
(Qt::WindowMinimized | Qt::WindowMaximized | Qt::WindowFullScreen));
}
inline void drawBorderEmulated(QPainter *painter, const QRect &rect) {
QRegion region(rect);
void *args[] = {
painter,
const_cast<QRect *>(&rect),
&region,
};
ctx->virtual_hook(AbstractWindowContext::DrawWindows10BorderHook_Emulated, args);
}
inline void drawBorderNative() {
ctx->virtual_hook(AbstractWindowContext::DrawWindows10BorderHook_Native, nullptr);
}
inline int borderThickness() const {
return ctx->windowAttribute(QStringLiteral("border-thickness")).toInt();
}
inline void updateExtraMargins(bool windowActive) {
if (isWin11OrGreater()) {
return;
}
// ### FIXME: transparent seam
if (windowActive) {
// Restore margins when the window is active
static QVariant defaultMargins = QVariant::fromValue(QMargins(0, 1, 0, 0));
ctx->setWindowAttribute(QStringLiteral("extra-margins"), defaultMargins);
return;
}
// https://github.com/microsoft/terminal/blob/71a6f26e6ece656084e87de1a528c4a8072eeabd/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp#L904
// When the window is inactive, there is a transparency bug in the top
// border, and we need to extend the non-client area to the whole title
// bar.
QRect frame = ctx->windowAttribute(QStringLiteral("window-rect")).toRect();
QMargins margins{0, -frame.top(), 0, 0};
ctx->setWindowAttribute(QStringLiteral("extra-margins"), QVariant::fromValue(margins));
}
virtual void updateGeometry() = 0;
virtual bool isWindowActive() const {
return ctx->window()->isActive();
}
protected:
bool nativeEventFilter(const QByteArray &eventType, void *message,
QT_NATIVE_EVENT_RESULT_TYPE *result) override {
Q_UNUSED(eventType)
const auto msg = static_cast<const MSG *>(message);
switch (msg->message) {
case WM_DPICHANGED: {
updateGeometry();
updateExtraMargins(isWindowActive());
break;
}
case WM_ACTIVATE: {
updateExtraMargins(LOWORD(msg->wParam) != WA_INACTIVE);
break;
}
case WM_THEMECHANGED:
case WM_SYSCOLORCHANGE:
case WM_DWMCOLORIZATIONCOLORCHANGED: {
// If we do not refresh this property, the native border will turn white
// permanently (like the dark mode is turned off) after the user changes
// the accent color in system personalization settings.
// So we need this ugly hack to re-apply dark mode to get rid of this
// strange Windows bug.
if (ctx->windowAttribute(QStringLiteral("dark-mode")).toBool()) {
ctx->setWindowAttribute(QStringLiteral("dark-mode"), true);
}
break;
}
default:
break;
}
return false;
}
bool sharedEventFilter(QObject *obj, QEvent *event) override {
Q_UNUSED(obj)
if (event->type() == QEvent::WinIdChange) {
if (ctx->windowId()) {
setupNecessaryAttributes();
updateGeometry();
}
}
return false;
}
protected:
AbstractWindowContext *ctx;
};
}
#endif // WINDOWS10BORDERHANDLER_P_H
@@ -0,0 +1,99 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#include "styleagent.h"
#include "styleagent_p.h"
namespace QWK {
/*!
\class StyleAgent
\brief StyleAgent provides some features related to system theme.
Qt6.6 started to support system theme detection, this class is intended as an auxiliary
support for lower versions of Qt. If your Qt already supports it, it is recommended that
you don't include this class in your build system.
*/
StyleAgentPrivate::StyleAgentPrivate() {
}
StyleAgentPrivate::~StyleAgentPrivate() {
removeSystemThemeHook();
}
void StyleAgentPrivate::init() {
setupSystemThemeHook();
}
void StyleAgentPrivate::notifyThemeChanged(StyleAgent::SystemTheme theme) {
if (theme == systemTheme)
return;
systemTheme = theme;
Q_Q(StyleAgent);
Q_EMIT q->systemThemeChanged();
}
void StyleAgentPrivate::notifyAccentColorChanged(const QColor &color) {
Q_ASSERT(color.isValid());
if (color == systemAccentColor)
return;
systemAccentColor = color;
Q_Q(StyleAgent);
Q_EMIT q->systemAccentColorChanged();
}
/*!
Constructor. Since it is not related to a concrete window instance, it is better to be used
as a singleton.
*/
StyleAgent::StyleAgent(QObject *parent) : StyleAgent(*new StyleAgentPrivate(), parent) {
}
/*!
Destructor.
*/
StyleAgent::~StyleAgent() {
}
/*!
Returns the system theme.
*/
StyleAgent::SystemTheme StyleAgent::systemTheme() const {
Q_D(const StyleAgent);
return d->systemTheme;
}
/*!
Returns the system accent color.
*/
QColor StyleAgent::systemAccentColor() const {
Q_D(const StyleAgent);
return d->systemAccentColor;
}
/*!
\internal
*/
StyleAgent::StyleAgent(StyleAgentPrivate &d, QObject *parent) : QObject(parent), d_ptr(&d) {
d.q_ptr = this;
d.init();
}
/*!
\fn void StyleAgent::systemThemeChanged()
This signal is emitted when the system theme changes.
*/
/*!
\fn void StyleAgent::systemAccentColorChanged()
This signal is emitted when the system accent color changes.
*/
}
@@ -0,0 +1,50 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#ifndef STYLEAGENT_H
#define STYLEAGENT_H
#include <memory>
#include <QtCore/QObject>
#include <QtGui/QColor>
#include <QWKCore/qwkglobal.h>
namespace QWK {
class StyleAgentPrivate;
class QWK_CORE_EXPORT StyleAgent : public QObject {
Q_OBJECT
Q_DECLARE_PRIVATE(StyleAgent)
public:
explicit StyleAgent(QObject *parent = nullptr);
~StyleAgent() override;
enum SystemTheme {
Unknown,
Light,
Dark,
HighContrast,
};
Q_ENUM(SystemTheme)
public:
SystemTheme systemTheme() const;
QColor systemAccentColor() const;
Q_SIGNALS:
void systemThemeChanged();
void systemAccentColorChanged();
protected:
StyleAgent(StyleAgentPrivate &d, QObject *parent = nullptr);
const std::unique_ptr<StyleAgentPrivate> d_ptr;
};
}
#endif // STYLEAGENT_H
@@ -0,0 +1,23 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#include "styleagent_p.h"
#include <QtGui/QPalette>
#include <QtGui/QGuiApplication>
namespace QWK {
void StyleAgentPrivate::setupSystemThemeHook() {
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
systemAccentColor = QGuiApplication::palette().color(QPalette::Accent);
#else
systemAccentColor = QGuiApplication::palette().color(QPalette::Highlight);
#endif
}
void StyleAgentPrivate::removeSystemThemeHook() {
}
}
@@ -0,0 +1,114 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#include "styleagent_p.h"
#include <Cocoa/Cocoa.h>
namespace QWK {
static StyleAgent::SystemTheme getSystemTheme() {
NSString *osxMode =
[[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"];
bool isDark = [osxMode isEqualToString:@"Dark"];
return isDark ? StyleAgent::Dark : StyleAgent::Light;
}
static QColor getAccentColor() {
if (@available(macOS 10.14, *)) {
NSColor *color = [NSColor controlAccentColor];
NSColor *rgbColor = [color colorUsingColorSpace:[NSColorSpace sRGBColorSpace]];
if (rgbColor) {
return QColor::fromRgbF(rgbColor.redComponent, rgbColor.greenComponent, rgbColor.blueComponent, rgbColor.alphaComponent);
}
}
return {};
}
static void notifyAllStyleAgents();
}
//
// Objective C++ Begin
//
@interface QWK_SystemThemeObserver : NSObject {
}
@end
@implementation QWK_SystemThemeObserver
- (id)init {
self = [super init];
if (self) {
NSDistributedNotificationCenter *center = [NSDistributedNotificationCenter defaultCenter];
[center addObserver:self
selector:@selector(interfaceModeChanged:)
name:@"AppleInterfaceThemeChangedNotification"
object:nil];
[center addObserver:self
selector:@selector(interfaceModeChanged:)
name:@"AppleColorPreferencesChangedNotification"
object:nil];
}
return self;
}
- (void)dealloc {
[[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (void)interfaceModeChanged:(NSNotification *)notification {
QWK::notifyAllStyleAgents();
}
@end
//
// Objective C++ End
//
namespace QWK {
using StyleAgentSet = QSet<StyleAgentPrivate *>;
Q_GLOBAL_STATIC(StyleAgentSet, g_styleAgentSet)
static QWK_SystemThemeObserver *g_systemThemeObserver = nil;
void notifyAllStyleAgents() {
auto theme = getSystemTheme();
auto color = getAccentColor();
for (auto &&ap : std::as_const(*g_styleAgentSet())) {
ap->notifyThemeChanged(theme);
ap->notifyAccentColorChanged(color);
}
}
void StyleAgentPrivate::setupSystemThemeHook() {
systemTheme = getSystemTheme();
systemAccentColor = getAccentColor();
// Alloc
if (g_styleAgentSet->isEmpty()) {
g_systemThemeObserver = [[QWK_SystemThemeObserver alloc] init];
}
g_styleAgentSet->insert(this);
}
void StyleAgentPrivate::removeSystemThemeHook() {
if (!g_styleAgentSet->remove(this))
return;
if (g_styleAgentSet->isEmpty()) {
// Delete
[g_systemThemeObserver release];
g_systemThemeObserver = nil;
}
}
}
@@ -0,0 +1,43 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#ifndef STYLEAGENTPRIVATE_H
#define STYLEAGENTPRIVATE_H
//
// W A R N I N G !!!
// -----------------
//
// This file is not part of the QWindowKit API. It is used purely as an
// implementation detail. This header file may change from version to
// version without notice, or may even be removed.
//
#include <QWKCore/styleagent.h>
namespace QWK {
class StyleAgentPrivate : public QObject {
Q_DECLARE_PUBLIC(StyleAgent)
public:
StyleAgentPrivate();
~StyleAgentPrivate() override;
void init();
StyleAgent *q_ptr;
StyleAgent::SystemTheme systemTheme = StyleAgent::Unknown;
QColor systemAccentColor;
void setupSystemThemeHook();
void removeSystemThemeHook();
void notifyThemeChanged(StyleAgent::SystemTheme theme);
void notifyAccentColorChanged(const QColor &color);
};
}
#endif // STYLEAGENTPRIVATE_H
@@ -0,0 +1,102 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#include "styleagent_p.h"
#include <QtCore/QSet>
#include <QWKCore/private/qwkwindowsextra_p.h>
#include <QWKCore/private/nativeeventfilter_p.h>
namespace QWK {
using StyleAgentSet = QSet<StyleAgentPrivate *>;
Q_GLOBAL_STATIC(StyleAgentSet, g_styleAgentSet)
static StyleAgent::SystemTheme getSystemTheme() {
if (isHighContrastModeEnabled()) {
return StyleAgent::HighContrast;
} else if (isDarkThemeActive()) {
return StyleAgent::Dark;
} else {
return StyleAgent::Light;
}
}
static void notifyAllStyleAgents() {
auto theme = getSystemTheme();
auto color = getAccentColor();
for (auto &&ap : std::as_const(*g_styleAgentSet())) {
ap->notifyThemeChanged(theme);
ap->notifyAccentColorChanged(color);
}
}
class SystemSettingEventFilter : public AppNativeEventFilter {
public:
bool nativeEventFilter(const QByteArray &eventType, void *message,
QT_NATIVE_EVENT_RESULT_TYPE *result) override {
Q_UNUSED(eventType)
if (!result) {
return false;
}
const auto msg = static_cast<const MSG *>(message);
switch (msg->message) {
case WM_THEMECHANGED:
case WM_SYSCOLORCHANGE:
case WM_DWMCOLORIZATIONCOLORCHANGED: {
notifyAllStyleAgents();
break;
}
case WM_SETTINGCHANGE: {
if (isImmersiveColorSetChange(msg->wParam, msg->lParam)) {
notifyAllStyleAgents();
}
break;
}
default:
break;
}
return false;
}
static inline SystemSettingEventFilter *instance = nullptr;
static inline void install() {
if (instance) {
return;
}
instance = new SystemSettingEventFilter();
}
static inline void uninstall() {
if (!instance) {
return;
}
delete instance;
instance = nullptr;
}
};
void StyleAgentPrivate::setupSystemThemeHook() {
systemTheme = getSystemTheme();
systemAccentColor = getAccentColor();
g_styleAgentSet->insert(this);
SystemSettingEventFilter::install();
}
void StyleAgentPrivate::removeSystemThemeHook() {
if (!g_styleAgentSet->remove(this))
return;
if (g_styleAgentSet->isEmpty()) {
SystemSettingEventFilter::uninstall();
}
}
}
@@ -0,0 +1,169 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#include "windowagentbase.h"
#include "windowagentbase_p.h"
#include <QWKCore/qwkconfig.h>
#include "qwkglobal_p.h"
#if defined(Q_OS_WINDOWS)
# include "win32windowcontext_p.h"
#elif defined(Q_OS_MAC)
# include "cocoawindowcontext_p.h"
#elif defined(Q_OS_LINUX) && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
# include "qwindowkit_linux.h"
# include "linuxwaylandcontext_p.h"
# include "linuxx11context_p.h"
#endif
#include "qtwindowcontext_p.h"
Q_LOGGING_CATEGORY(qWindowKitLog, "qwindowkit")
namespace QWK {
/*!
\namespace QWK
\brief QWindowKit namespace
*/
/*!
\class WindowAgentBase
\brief WindowAgentBase is the base class of the specifiy window agent for QtWidgets and
QtQuick.
It processes some system events to remove the window's default title bar, and provides some
shared methods for derived classes to call.
*/
WindowAgentBasePrivate::WindowContextFactoryMethod
WindowAgentBasePrivate::windowContextFactoryMethod = nullptr;
WindowAgentBasePrivate::WindowAgentBasePrivate() : q_ptr(nullptr), context(nullptr) {
}
WindowAgentBasePrivate::~WindowAgentBasePrivate() = default;
void WindowAgentBasePrivate::init() {
}
AbstractWindowContext *WindowAgentBasePrivate::createContext() const {
if (windowContextFactoryMethod) {
return windowContextFactoryMethod();
}
#if QWINDOWKIT_CONFIG(FORCE_QT_WINDOW_CONTEXT)
return new QtWindowContext();
#else
# if defined(Q_OS_WINDOWS)
return new Win32WindowContext();
# elif defined(Q_OS_MAC)
return new CocoaWindowContext();
# elif defined(Q_OS_LINUX) && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
if (Private::isWaylandPlatform() && Private::waylandAPI().isValid()) {
return new LinuxWaylandContext();
}
if (Private::isX11Platform() && Private::x11API().isValid()) {
return new LinuxX11Context();
}
# endif
// Final fallback, no native features.
return new QtWindowContext();
#endif
}
void WindowAgentBasePrivate::setup(QObject *host, WindowItemDelegate *delegate) {
auto ctx = createContext();
ctx->setup(host, delegate);
context.reset(ctx);
}
/*!
Destructor.
*/
WindowAgentBase::~WindowAgentBase() = default;
/*!
Returns the window attribute value.
\sa setWindowAttribute()
*/
QVariant WindowAgentBase::windowAttribute(const QString &key) const {
Q_D(const WindowAgentBase);
return d->context->windowAttribute(key);
}
/*!
Sets the platform-related attribute for the window. Available attributes:
On Windows,
\li \c no-system-menu: Specify a boolean value to disable the system menu.
\li \c dwm-blur: Specify a boolean value to enable or disable dwm blur effect, this
attribute is available on Windows 10 or later.
\li \c dark-mode: Specify a boolean value to enable or disable the dark mode, it is
enabled by default on Windows 10 if the system borders config is enabled. This
attribute is available on Windows 10 or later.
\li \c acrylic-material: Specify a boolean value to enable or disable acrylic material,
this attribute is only available on Windows 11.
\li \c mica: Specify a boolean value to enable or disable mica material,
this attribute is only available on Windows 11.
\li \c mica-alt: Specify a boolean value to enable or disable mica-alt material,
this attribute is only available on Windows 11.
\li \c extra-margins: Specify a margin value to change the \c dwm extended area
geometry, you shouldn't change this attribute because it may break the
internal state.
\li \c border-thickness: Returns the system border thickness. (Readonly)
\li \c title-bar-height: Returns the system title bar height, some system features may
be related to this property so that it is recommended to set the custom title bar
height to this value. (Readonly)
On macOS,
\li \c no-system-buttons: Specify a boolean value to set the system buttons'
visibility.
\li \c blur-effect: You can specify a string value, "dark" to enable dark mode, "light"
to set enable mode, "none" to disable. You can also specify a boolean value,
\c true to enable current theme mode, \c false to disable.
\li \c title-bar-height: Returns the system title bar height, the system button display
area will be limited to this height. (Readonly)
*/
bool WindowAgentBase::setWindowAttribute(const QString &key, const QVariant &attribute) {
Q_D(WindowAgentBase);
return d->context->setWindowAttribute(key, attribute);
}
/*!
Shows the system menu, it's only implemented on Windows.
*/
void WindowAgentBase::showSystemMenu(const QPoint &pos) {
Q_D(WindowAgentBase);
d->context->showSystemMenu(pos);
}
/*!
Makes the window show in center of the current screen.
*/
void WindowAgentBase::centralize() {
Q_D(WindowAgentBase);
d->context->virtual_hook(AbstractWindowContext::CentralizeHook, nullptr);
}
/*!
Brings the window to top.
*/
void WindowAgentBase::raise() {
Q_D(WindowAgentBase);
d->context->virtual_hook(AbstractWindowContext::RaiseWindowHook, nullptr);
}
/*!
\internal
*/
WindowAgentBase::WindowAgentBase(WindowAgentBasePrivate &d, QObject *parent)
: QObject(parent), d_ptr(&d) {
d.q_ptr = this;
d.init();
}
}
@@ -0,0 +1,50 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#ifndef WINDOWAGENTBASE_H
#define WINDOWAGENTBASE_H
#include <memory>
#include <QtCore/QObject>
#include <QWKCore/qwkglobal.h>
namespace QWK {
class WindowAgentBasePrivate;
class QWK_CORE_EXPORT WindowAgentBase : public QObject {
Q_OBJECT
Q_DECLARE_PRIVATE(WindowAgentBase)
public:
~WindowAgentBase() override;
enum SystemButton {
Unknown,
WindowIcon,
Help,
Minimize,
Maximize,
Close,
};
Q_ENUM(SystemButton)
QVariant windowAttribute(const QString &key) const;
Q_INVOKABLE bool setWindowAttribute(const QString &key, const QVariant &attribute);
public Q_SLOTS:
void showSystemMenu(const QPoint &pos); // Only available on Windows now
void centralize();
void raise();
protected:
explicit WindowAgentBase(WindowAgentBasePrivate &d, QObject *parent = nullptr);
const std::unique_ptr<WindowAgentBasePrivate> d_ptr;
};
}
#endif // WINDOWAGENTBASE_H
@@ -0,0 +1,49 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#ifndef WINDOWAGENTBASEPRIVATE_H
#define WINDOWAGENTBASEPRIVATE_H
//
// W A R N I N G !!!
// -----------------
//
// This file is not part of the QWindowKit API. It is used purely as an
// implementation detail. This header file may change from version to
// version without notice, or may even be removed.
//
#include <QWKCore/windowagentbase.h>
#include <QWKCore/private/abstractwindowcontext_p.h>
namespace QWK {
class QWK_CORE_EXPORT WindowAgentBasePrivate {
Q_DECLARE_PUBLIC(WindowAgentBase)
public:
WindowAgentBasePrivate();
virtual ~WindowAgentBasePrivate();
void init();
WindowAgentBase *q_ptr; // no need to initialize
virtual AbstractWindowContext *createContext() const;
void setup(QObject *host, WindowItemDelegate *delegate);
std::unique_ptr<AbstractWindowContext> context;
public:
using WindowContextFactoryMethod = AbstractWindowContext *(*) ();
static WindowContextFactoryMethod windowContextFactoryMethod;
private:
Q_DISABLE_COPY(WindowAgentBasePrivate)
};
}
#endif // WINDOWAGENTBASEPRIVATE_H
@@ -0,0 +1,23 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#include "windowitemdelegate_p.h"
namespace QWK {
WindowItemDelegate::WindowItemDelegate() = default;
WindowItemDelegate::~WindowItemDelegate() = default;
void WindowItemDelegate::resetQtGrabbedControl(QObject *host) const {
Q_UNUSED(host);
}
WinIdChangeEventFilter *
WindowItemDelegate::createWinIdEventFilter(QObject *host,
AbstractWindowContext *context) const {
return new WindowWinIdChangeEventFilter(static_cast<QWindow *>(host), context);
}
}
@@ -0,0 +1,65 @@
// Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware)
// Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao)
// SPDX-License-Identifier: Apache-2.0
#ifndef WINDOWITEMDELEGATE_P_H
#define WINDOWITEMDELEGATE_P_H
//
// W A R N I N G !!!
// -----------------
//
// This file is not part of the QWindowKit API. It is used purely as an
// implementation detail. This header file may change from version to
// version without notice, or may even be removed.
//
#include <QtCore/QObject>
#include <QtCore/QPoint>
#include <QtGui/QWindow>
#include <QWKCore/private/winidchangeeventfilter_p.h>
#include <QWKCore/qwkglobal.h>
namespace QWK {
class QWK_CORE_EXPORT WindowItemDelegate {
public:
WindowItemDelegate();
virtual ~WindowItemDelegate();
public:
// Item property query
virtual QWindow *window(const QObject *obj) const = 0;
virtual bool isEnabled(const QObject *obj) const = 0;
virtual bool isVisible(const QObject *obj) const = 0;
virtual QRect mapGeometryToScene(const QObject *obj) const = 0;
// Host property query
virtual QWindow *hostWindow(const QObject *host) const = 0;
virtual bool isWindowActive(const QObject *host) const = 0;
virtual Qt::WindowStates getWindowState(const QObject *host) const = 0;
virtual Qt::WindowFlags getWindowFlags(const QObject *host) const = 0;
virtual QRect getGeometry(const QObject *host) const = 0;
// Callbacks
virtual void resetQtGrabbedControl(QObject *host) const;
virtual void setWindowState(QObject *host, Qt::WindowStates state) const = 0;
virtual void setCursorShape(QObject *host, Qt::CursorShape shape) const = 0;
virtual void restoreCursorShape(QObject *host) const = 0;
virtual void setWindowFlags(QObject *host, Qt::WindowFlags flags) const = 0;
virtual void setWindowVisible(QObject *host, bool visible) const = 0;
virtual void setGeometry(QObject *host, const QRect &rect) = 0;
virtual void bringWindowToTop(QObject *host) const = 0;
// Factories
virtual WinIdChangeEventFilter *
createWinIdEventFilter(QObject *host, AbstractWindowContext *context) const;
private:
Q_DISABLE_COPY(WindowItemDelegate)
};
}
#endif // WINDOWITEMDELEGATE_P_H