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

View File

@@ -0,0 +1,192 @@
qm_import(Preprocess)
set(QWINDOWKIT_GENERATED_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/../include)
set(QWINDOWKIT_BUILD_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/../etc/include)
set(QWINDOWKIT_ENABLED_TARGETS)
set(QWINDOWKIT_ENABLED_SUBDIRECTORIES)
# ----------------------------------
# Configurations
# ----------------------------------
set(QMSETUP_DEFINITION_SCOPE DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
set(QMSETUP_DEFINITION_NUMERICAL on)
qm_add_definition(QWINDOWKIT_FORCE_QT_WINDOW_CONTEXT
CONDITION QWINDOWKIT_FORCE_QT_WINDOW_CONTEXT
)
qm_add_definition(QWINDOWKIT_ENABLE_STYLE_AGENT
CONDITION QWINDOWKIT_ENABLE_STYLE_AGENT
)
qm_add_definition(QWINDOWKIT_ENABLE_WINDOWS_SYSTEM_BORDERS
CONDITION QWINDOWKIT_ENABLE_WINDOWS_SYSTEM_BORDERS
)
qm_generate_config(${QWINDOWKIT_BUILD_INCLUDE_DIR}/QWKCore/qwkconfig.h)
if(QWINDOWKIT_INSTALL)
install(FILES ${QWINDOWKIT_BUILD_INCLUDE_DIR}/QWKCore/qwkconfig.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${QWINDOWKIT_INSTALL_NAME}/QWKCore
)
endif()
# ----------------------------------
# CMake API
# ----------------------------------
macro(qwk_add_library _target)
set(options AUTOGEN NO_SYNC_INCLUDE NO_WIN_RC)
set(oneValueArgs SYNC_INCLUDE_PREFIX PREFIX)
set(multiValueArgs SYNC_INCLUDE_OPTIONS)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(FUNC_AUTOGEN)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
endif()
if(QWINDOWKIT_BUILD_STATIC)
set(_type STATIC)
else()
set(_type SHARED)
endif()
add_library(${_target} ${_type})
if(WIN32 AND NOT FUNC_NO_WIN_RC AND(${_type} STREQUAL "SHARED"))
qm_add_win_rc(${_target}
NAME ${QWINDOWKIT_INSTALL_NAME}
DESCRIPTION ${QWINDOWKIT_DESCRIPTION}
COPYRIGHT ${QWINDOWKIT_COPYRIGHT}
)
endif()
if(FUNC_PREFIX)
set(_prefix_option PREFIX ${FUNC_PREFIX})
else()
set(_prefix_option)
endif()
# Set global definitions
qm_export_defines(${_target} ${_prefix_option})
# Configure target
qm_configure_target(${_target} ${FUNC_UNPARSED_ARGUMENTS})
# Add include directories
target_include_directories(${_target} PRIVATE ${QWINDOWKIT_BUILD_INCLUDE_DIR})
target_include_directories(${_target} PRIVATE .)
# Library name
if(${_target} MATCHES "^QWK(.+)")
set(_name ${CMAKE_MATCH_1})
set_target_properties(${_target} PROPERTIES EXPORT_NAME ${_name})
else()
set(_name ${_target})
endif()
add_library(${QWINDOWKIT_INSTALL_NAME}::${_name} ALIAS ${_target})
if(FUNC_SYNC_INCLUDE_PREFIX)
set(_inc_name ${FUNC_SYNC_INCLUDE_PREFIX})
else()
set(_inc_name ${_target})
endif()
set(_install_options)
if(QWINDOWKIT_INSTALL)
install(TARGETS ${_target}
EXPORT ${QWINDOWKIT_INSTALL_NAME}Targets
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" OPTIONAL
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" OPTIONAL
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" OPTIONAL
)
target_include_directories(${_target} PUBLIC
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${QWINDOWKIT_INSTALL_NAME}>"
)
set(_install_options
INSTALL_DIR "${CMAKE_INSTALL_INCLUDEDIR}/${QWINDOWKIT_INSTALL_NAME}/${_inc_name}"
)
endif()
if(NOT FUNC_NO_SYNC_INCLUDE)
# Generate a standard include directory in build directory
qm_sync_include(. "${QWINDOWKIT_GENERATED_INCLUDE_DIR}/${_inc_name}" ${_install_options}
${FUNC_SYNC_INCLUDE_OPTIONS} FORCE
)
target_include_directories(${_target} PUBLIC
"$<BUILD_INTERFACE:${QWINDOWKIT_GENERATED_INCLUDE_DIR}>"
)
endif()
endmacro()
# ----------------------------------
# Main Project
# ----------------------------------
add_subdirectory(core)
if(QWINDOWKIT_BUILD_WIDGETS)
add_subdirectory(widgets)
endif()
if(QWINDOWKIT_BUILD_QUICK)
add_subdirectory(quick)
endif()
# ----------------------------------
# Documentation
# ----------------------------------
if(QWINDOWKIT_BUILD_DOCUMENTATIONS)
if(NOT DOXYGEN_EXECUTABLE)
find_package(Doxygen REQUIRED)
endif()
set(_install_options)
if(QWINDOWKIT_INSTALL)
set(_install_options INSTALL_DIR share/doc/${QWINDOWKIT_INSTALL_NAME})
endif()
set(_doc_targets ${QWINDOWKIT_ENABLED_TARGETS})
set(QWINDOWKIT_DOXYGEN_TAGFILE
${CMAKE_BUILD_SHARE_DIR}/doc/${QWINDOWKIT_INSTALL_NAME}/${QWINDOWKIT_INSTALL_NAME}_tagfile.xml
CACHE FILEPATH "QWindowKit doxygen tag file path" FORCE
)
qm_import(Doxygen)
qm_setup_doxygen(QWindowKit_RunDoxygen
NAME "QWindowKit"
DESCRIPTION "${QWINDOWKIT_DESCRIPTION}"
MDFILE ../README.md
OUTPUT_DIR ${CMAKE_BUILD_SHARE_DIR}/doc/${QWINDOWKIT_INSTALL_NAME}
INPUT ${QWINDOWKIT_ENABLED_SUBDIRECTORIES}
TARGETS ${_doc_targets}
DEPENDS ${_doc_targets}
NO_EXPAND_MACROS Q_OBJECT Q_GADGET Q_DECLARE_TR_FUNCTIONS
COMPILE_DEFINITIONS Q_SIGNALS=Q_SIGNALS Q_SLOTS=Q_SLOTS
GENERATE_TAGFILE "${QWINDOWKIT_DOXYGEN_TAGFILE}"
${_install_options}
)
endif()
# ----------------------------------
# Install
# ----------------------------------
if(QWINDOWKIT_INSTALL)
qm_basic_install(
NAME ${QWINDOWKIT_INSTALL_NAME}
VERSION ${QWINDOWKIT_VERSION}
INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/${QWINDOWKIT_INSTALL_NAME}
CONFIG_TEMPLATE "${QWINDOWKIT_INSTALL_NAME}Config.cmake.in"
NAMESPACE ${QWINDOWKIT_INSTALL_NAME}::
EXPORT ${QWINDOWKIT_INSTALL_NAME}Targets
WRITE_CONFIG_OPTIONS NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
# Install shared files
include("../share/install.cmake")
endif()

View File

@@ -0,0 +1,18 @@
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
find_dependency(QT NAMES Qt6 Qt5 COMPONENTS Core Gui REQUIRED)
find_dependency(Qt${QT_VERSION_MAJOR} COMPONENTS Core Gui REQUIRED)
if ("Widgets" IN_LIST QWindowKit_FIND_COMPONENTS)
find_dependency(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED)
find_dependency(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED)
endif()
if ("Quick" IN_LIST QWindowKit_FIND_COMPONENTS)
find_dependency(QT NAMES Qt6 Qt5 COMPONENTS Quick REQUIRED)
find_dependency(Qt${QT_VERSION_MAJOR} COMPONENTS Quick REQUIRED)
endif()
include("${CMAKE_CURRENT_LIST_DIR}/QWindowKitTargets.cmake")

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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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 "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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

View File

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

View File

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

View File

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

View File

@@ -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.
*/
}

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

View File

@@ -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() {
}
}

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,33 @@
project(QWKQuick
VERSION ${QWINDOWKIT_VERSION}
LANGUAGES CXX
)
set(_src
qwkquickglobal.h
qwkquickglobal.cpp
quickitemdelegate_p.h
quickitemdelegate.cpp
quickwindowagent.h
quickwindowagent_p.h
quickwindowagent.cpp
)
if(WIN32)
list(APPEND _src quickwindowagent_win.cpp)
elseif(APPLE)
list(APPEND _src quickwindowagent_mac.cpp)
endif()
qwk_add_library(${PROJECT_NAME} AUTOGEN
SOURCES ${_src}
FEATURES cxx_std_17
LINKS QWKCore
QT_LINKS Core Gui Quick
QT_INCLUDE_PRIVATE Core Gui Quick
INCLUDE_PRIVATE *
PREFIX QWK_QUICK
)
set(QWINDOWKIT_ENABLED_TARGETS ${QWINDOWKIT_ENABLED_TARGETS} ${PROJECT_NAME} PARENT_SCOPE)
set(QWINDOWKIT_ENABLED_SUBDIRECTORIES ${QWINDOWKIT_ENABLED_SUBDIRECTORIES} quick PARENT_SCOPE)

View File

@@ -0,0 +1,84 @@
// 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 "quickitemdelegate_p.h"
#include <QtQuick/QQuickItem>
#include <QtQuick/QQuickWindow>
namespace QWK {
QuickItemDelegate::QuickItemDelegate() : WindowItemDelegate() {
}
QuickItemDelegate::~QuickItemDelegate() = default;
QWindow *QuickItemDelegate::window(const QObject *obj) const {
return static_cast<const QQuickItem *>(obj)->window();
}
bool QuickItemDelegate::isEnabled(const QObject *obj) const {
return static_cast<const QQuickItem *>(obj)->isEnabled();
}
bool QuickItemDelegate::isVisible(const QObject *obj) const {
return static_cast<const QQuickItem *>(obj)->isVisible();
}
QRect QuickItemDelegate::mapGeometryToScene(const QObject *obj) const {
auto item = static_cast<const QQuickItem *>(obj);
const QPointF originPoint = item->mapToScene(QPointF(0.0, 0.0));
const QSizeF size = item->size();
return QRectF(originPoint, size).toRect();
}
QWindow *QuickItemDelegate::hostWindow(const QObject *host) const {
return static_cast<QQuickWindow *>(const_cast<QObject *>(host));
}
bool QuickItemDelegate::isWindowActive(const QObject *host) const {
return static_cast<const QQuickWindow *>(host)->isActive();
}
Qt::WindowStates QuickItemDelegate::getWindowState(const QObject *host) const {
return static_cast<const QQuickWindow *>(host)->windowStates();
}
void QuickItemDelegate::setWindowState(QObject *host, Qt::WindowStates state) const {
static_cast<QQuickWindow *>(host)->setWindowStates(state);
}
void QuickItemDelegate::setCursorShape(QObject *host, const Qt::CursorShape shape) const {
static_cast<QQuickWindow *>(host)->setCursor(QCursor(shape));
}
void QuickItemDelegate::restoreCursorShape(QObject *host) const {
static_cast<QQuickWindow *>(host)->unsetCursor();
}
Qt::WindowFlags QuickItemDelegate::getWindowFlags(const QObject *host) const {
return static_cast<const QQuickWindow *>(host)->flags();
}
QRect QuickItemDelegate::getGeometry(const QObject *host) const {
return static_cast<const QQuickWindow *>(host)->geometry();
}
void QuickItemDelegate::setWindowFlags(QObject *host, Qt::WindowFlags flags) const {
static_cast<QQuickWindow *>(host)->setFlags(flags);
}
void QuickItemDelegate::setWindowVisible(QObject *host, bool visible) const {
static_cast<QQuickWindow *>(host)->setVisible(visible);
}
void QuickItemDelegate::setGeometry(QObject *host, const QRect &rect) {
static_cast<QQuickWindow *>(host)->setGeometry(rect);
}
void QuickItemDelegate::bringWindowToTop(QObject *host) const {
static_cast<QQuickWindow *>(host)->raise();
}
}

View File

@@ -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 QUICKITEMDELEGATE_P_H
#define QUICKITEMDELEGATE_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 <QtGui/QWindow>
#include <QWKCore/private/windowitemdelegate_p.h>
#include <QWKQuick/qwkquickglobal.h>
namespace QWK {
class QWK_QUICK_EXPORT QuickItemDelegate : public WindowItemDelegate {
public:
QuickItemDelegate();
~QuickItemDelegate() override;
public:
QWindow *window(const QObject *obj) const override;
bool isEnabled(const QObject *obj) const override;
bool isVisible(const QObject *obj) const override;
QRect mapGeometryToScene(const QObject *obj) const override;
QWindow *hostWindow(const QObject *host) const override;
bool isWindowActive(const QObject *host) const override;
Qt::WindowStates getWindowState(const QObject *host) const override;
Qt::WindowFlags getWindowFlags(const QObject *host) const override;
QRect getGeometry(const QObject *host) const override;
void setWindowState(QObject *host, Qt::WindowStates state) const override;
void setCursorShape(QObject *host, Qt::CursorShape shape) const override;
void restoreCursorShape(QObject *host) const override;
void setWindowFlags(QObject *host, Qt::WindowFlags flags) const override;
void setWindowVisible(QObject *host, bool visible) const override;
void setGeometry(QObject *host, const QRect &rect) override;
void bringWindowToTop(QObject *host) const override;
};
}
#endif // QUICKITEMDELEGATE_P_H

View File

@@ -0,0 +1,107 @@
// 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 "quickwindowagent.h"
#include "quickwindowagent_p.h"
#include <QtQuick/QQuickWindow>
#include <QtQuick/private/qquickanchors_p.h>
#include "quickitemdelegate_p.h"
namespace QWK {
/*!
\class QuickWindowAgent
\brief QuickWindowAgent is the window agent for QtQuick.
It provides interfaces for QtQuick and processes some Qt events related to the QQuickItem
instance. The usage of all APIs is consistent with the \a Widgets module.
*/
QuickWindowAgentPrivate::QuickWindowAgentPrivate() = default;
QuickWindowAgentPrivate::~QuickWindowAgentPrivate() = default;
void QuickWindowAgentPrivate::init() {
}
QuickWindowAgent::QuickWindowAgent(QObject *parent)
: QuickWindowAgent(*new QuickWindowAgentPrivate(), parent) {
}
QuickWindowAgent::~QuickWindowAgent() = default;
bool QuickWindowAgent::setup(QQuickWindow *window) {
Q_ASSERT(window);
if (!window) {
return false;
}
Q_D(QuickWindowAgent);
if (d->hostWindow) {
return false;
}
d->setup(window, new QuickItemDelegate());
d->hostWindow = window;
#if defined(Q_OS_WINDOWS) && QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS)
d->setupWindows10BorderWorkaround();
#endif
return true;
}
QQuickItem *QuickWindowAgent::titleBar() const {
Q_D(const QuickWindowAgent);
return static_cast<QQuickItem *>(d->context->titleBar());
}
void QuickWindowAgent::setTitleBar(QQuickItem *item) {
Q_D(QuickWindowAgent);
if (!d->context->setTitleBar(item)) {
return;
}
#ifdef Q_OS_MAC
setSystemButtonArea(nullptr);
#endif
Q_EMIT titleBarWidgetChanged(item);
}
QQuickItem *QuickWindowAgent::systemButton(SystemButton button) const {
Q_D(const QuickWindowAgent);
return static_cast<QQuickItem *>(d->context->systemButton(button));
}
void QuickWindowAgent::setSystemButton(SystemButton button, QQuickItem *item) {
Q_D(QuickWindowAgent);
if (!d->context->setSystemButton(button, item)) {
return;
}
Q_EMIT systemButtonChanged(button, item);
}
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
bool QuickWindowAgent::isHitTestVisible(QQuickItem *item) const {
#else
bool QuickWindowAgent::isHitTestVisible(const QQuickItem *item) const {
#endif
Q_D(const QuickWindowAgent);
return d->context->isHitTestVisible(item);
}
void QuickWindowAgent::setHitTestVisible(QQuickItem *item, bool visible) {
Q_D(QuickWindowAgent);
d->context->setHitTestVisible(item, visible);
}
/*!
\internal
*/
QuickWindowAgent::QuickWindowAgent(QuickWindowAgentPrivate &d, QObject *parent)
: WindowAgentBase(d, parent) {
d.init();
}
}

View File

@@ -0,0 +1,60 @@
// 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 QUICKWINDOWAGENT_H
#define QUICKWINDOWAGENT_H
#include <QtQuick/QQuickItem>
#include <QtQuick/QQuickWindow>
#include <QWKCore/windowagentbase.h>
#include <QWKQuick/qwkquickglobal.h>
namespace QWK {
class QuickWindowAgentPrivate;
class QWK_QUICK_EXPORT QuickWindowAgent : public WindowAgentBase {
Q_OBJECT
Q_DECLARE_PRIVATE(QuickWindowAgent)
public:
explicit QuickWindowAgent(QObject *parent = nullptr);
~QuickWindowAgent() override;
public:
Q_INVOKABLE bool setup(QQuickWindow *window);
Q_INVOKABLE QQuickItem *titleBar() const;
Q_INVOKABLE void setTitleBar(QQuickItem *item);
Q_INVOKABLE QQuickItem *systemButton(SystemButton button) const;
Q_INVOKABLE void setSystemButton(SystemButton button, QQuickItem *item);
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
Q_INVOKABLE bool isHitTestVisible(QQuickItem *item) const;
#else
Q_INVOKABLE bool isHitTestVisible(const QQuickItem *item) const;
#endif
Q_INVOKABLE void setHitTestVisible(QQuickItem *item, bool visible = true);
#ifdef Q_OS_MAC
// The system button area APIs are experimental, very likely to change in the future.
Q_INVOKABLE QQuickItem *systemButtonArea() const;
Q_INVOKABLE void setSystemButtonArea(QQuickItem *item);
Q_INVOKABLE ScreenRectCallback systemButtonAreaCallback() const;
Q_INVOKABLE void setSystemButtonAreaCallback(const ScreenRectCallback &callback);
#endif
Q_SIGNALS:
void titleBarWidgetChanged(QQuickItem *item);
void systemButtonChanged(SystemButton button, QQuickItem *item);
protected:
QuickWindowAgent(QuickWindowAgentPrivate &d, QObject *parent = nullptr);
};
}
#endif // QUICKWINDOWAGENT_H

View File

@@ -0,0 +1,75 @@
// 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 "quickwindowagent_p.h"
namespace QWK {
class SystemButtonAreaItemHandler : public QObject {
public:
SystemButtonAreaItemHandler(QQuickItem *item, AbstractWindowContext *ctx,
QObject *parent = nullptr);
~SystemButtonAreaItemHandler() override = default;
void updateSystemButtonArea();
protected:
QQuickItem *item;
AbstractWindowContext *ctx;
};
SystemButtonAreaItemHandler::SystemButtonAreaItemHandler(QQuickItem *item,
AbstractWindowContext *ctx,
QObject *parent)
: QObject(parent), item(item), ctx(ctx) {
connect(item, &QQuickItem::xChanged, this,
&SystemButtonAreaItemHandler::updateSystemButtonArea);
connect(item, &QQuickItem::yChanged, this,
&SystemButtonAreaItemHandler::updateSystemButtonArea);
connect(item, &QQuickItem::widthChanged, this,
&SystemButtonAreaItemHandler::updateSystemButtonArea);
connect(item, &QQuickItem::heightChanged, this,
&SystemButtonAreaItemHandler::updateSystemButtonArea);
ctx->setSystemButtonAreaCallback([item](const QSize &) {
return QRectF(item->mapToScene(QPointF(0, 0)), item->size()).toRect(); //
});
}
void SystemButtonAreaItemHandler::updateSystemButtonArea() {
ctx->virtual_hook(AbstractWindowContext::SystemButtonAreaChangedHook, nullptr);
}
QQuickItem *QuickWindowAgent::systemButtonArea() const {
Q_D(const QuickWindowAgent);
return d->systemButtonAreaItem;
}
void QuickWindowAgent::setSystemButtonArea(QQuickItem *item) {
Q_D(QuickWindowAgent);
if (d->systemButtonAreaItem == item)
return;
auto ctx = d->context.get();
d->systemButtonAreaItem = item;
if (!item) {
d->systemButtonAreaItemHandler.reset();
ctx->setSystemButtonAreaCallback({});
return;
}
d->systemButtonAreaItemHandler = std::make_unique<SystemButtonAreaItemHandler>(item, ctx);
}
ScreenRectCallback QuickWindowAgent::systemButtonAreaCallback() const {
Q_D(const QuickWindowAgent);
return d->systemButtonAreaItem ? nullptr : d->context->systemButtonAreaCallback();
}
void QuickWindowAgent::setSystemButtonAreaCallback(const ScreenRectCallback &callback) {
Q_D(QuickWindowAgent);
setSystemButtonArea(nullptr);
d->context->setSystemButtonAreaCallback(callback);
}
}

View File

@@ -0,0 +1,46 @@
// 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 QUICKWINDOWAGENTPRIVATE_H
#define QUICKWINDOWAGENTPRIVATE_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/qwkconfig.h>
#include <QWKCore/private/windowagentbase_p.h>
#include <QWKQuick/quickwindowagent.h>
namespace QWK {
class QuickWindowAgentPrivate : public WindowAgentBasePrivate {
Q_DECLARE_PUBLIC(QuickWindowAgent)
public:
QuickWindowAgentPrivate();
~QuickWindowAgentPrivate() override;
void init();
// Host
QQuickWindow *hostWindow{};
#ifdef Q_OS_MAC
QQuickItem *systemButtonAreaItem{};
std::unique_ptr<QObject> systemButtonAreaItemHandler;
#endif
#if defined(Q_OS_WINDOWS) && QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS)
void setupWindows10BorderWorkaround();
#endif
};
}
#endif // QUICKWINDOWAGENTPRIVATE_H

View File

@@ -0,0 +1,203 @@
// 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 "quickwindowagent_p.h"
#include <QtQuick/QQuickPaintedItem>
#include <QtQuick/private/qquickitem_p.h>
#include <QWKCore/qwindowkit_windows.h>
#include <QWKCore/private/windows10borderhandler_p.h>
namespace QWK {
static inline bool isWindows1022H2OrGreater() {
QWK_OSVERSIONINFOW rovi = Private::GetRealOSVersion();
return (rovi.dwMajorVersion > 10) ||
(rovi.dwMajorVersion == 10 &&
(rovi.dwMinorVersion > 0 || rovi.dwBuildNumber >= 19045));
}
#if QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS)
class BorderItem : public QQuickPaintedItem, public Windows10BorderHandler {
public:
explicit BorderItem(QQuickItem *parent, AbstractWindowContext *context);
~BorderItem() override;
bool shouldEnableEmulatedPainter() const;
void updateGeometry() override;
public:
void paint(QPainter *painter) override;
void itemChange(ItemChange change, const ItemChangeData &data) override;
protected:
bool sharedEventFilter(QObject *obj, QEvent *event) override;
bool nativeEventFilter(const QByteArray &eventType, void *message,
QT_NATIVE_EVENT_RESULT_TYPE *result) override;
private:
bool needNativePaint = false;
void _q_afterSynchronizing();
void _q_windowActivityChanged();
};
bool BorderItem::shouldEnableEmulatedPainter() const {
# if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
const QQuickWindow *win = window();
if (!win) {
return true;
}
auto api = win->rendererInterface()->graphicsApi();
switch (api) {
case QSGRendererInterface::OpenGL:
// FIXME: may be wrong in earlier Windows 10.
return false;
case QSGRendererInterface::Direct3D11:
# if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
case QSGRendererInterface::Direct3D12:
# endif
return false;
default:
break;
}
# endif
return true;
}
BorderItem::BorderItem(QQuickItem *parent, AbstractWindowContext *context)
: QQuickPaintedItem(parent), Windows10BorderHandler(context) {
setAntialiasing(true); // We need anti-aliasing to give us better result.
setFillColor(Qt::transparent); // Will improve the performance a little bit.
setOpaquePainting(true); // Will also improve the performance, we don't draw
// semi-transparent borders of course.
auto parentPri = QQuickItemPrivate::get(parent);
auto anchors = QQuickItemPrivate::get(this)->anchors();
// Workaround for top border
// anchors->setTop(parentPri->top());
anchors->setLeft(parentPri->left());
anchors->setRight(parentPri->right());
setZ(std::numeric_limits<qreal>::max()); // Make sure our fake border always above
// everything in the window.
# if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
connect(window(), &QQuickWindow::afterSynchronizing, this,
&BorderItem::_q_afterSynchronizing, Qt::DirectConnection);
# endif
connect(window(), &QQuickWindow::activeChanged, this,
&BorderItem::_q_windowActivityChanged);
// First update
if (context->windowId()) {
setupNecessaryAttributes();
}
BorderItem::updateGeometry();
}
BorderItem::~BorderItem() = default;
void BorderItem::updateGeometry() {
const QQuickWindow *win = window();
if (!win) {
return;
}
# if QT_VERSION_MAJOR < 6
setHeight(1);
# else
// Workaround for top border
// When the height is less than 0.5, it will be regarded as invisible, we apply this
// workaround to make it slightly exposed. When the height is too big, a transparent gap
// will appear on the upper frame.
setHeight(0.5);
setY(-0.49);
# endif
setVisible(isNormalWindow());
}
void BorderItem::paint(QPainter *painter) {
Q_UNUSED(painter)
if (shouldEnableEmulatedPainter()) {
drawBorderEmulated(painter, QRect({0, 0}, size().toSize()));
} else {
needNativePaint = true;
}
}
void BorderItem::itemChange(ItemChange change, const ItemChangeData &data) {
QQuickPaintedItem::itemChange(change, data);
switch (change) {
case ItemVisibleHasChanged:
case ItemDevicePixelRatioHasChanged: {
updateGeometry();
break;
}
default:
break;
}
}
bool BorderItem::sharedEventFilter(QObject *obj, QEvent *event) {
Q_UNUSED(obj)
switch (event->type()) {
case QEvent::WindowStateChange: {
updateGeometry();
}
default:
break;
}
return Windows10BorderHandler::sharedEventFilter(obj, event);
}
bool BorderItem::nativeEventFilter(const QByteArray &eventType, void *message,
QT_NATIVE_EVENT_RESULT_TYPE *result) {
const auto msg = static_cast<const MSG *>(message);
switch (msg->message) {
case WM_THEMECHANGED:
case WM_SYSCOLORCHANGE:
case WM_DWMCOLORIZATIONCOLORCHANGED: {
update();
break;
}
case WM_SETTINGCHANGE: {
if (isImmersiveColorSetChange(msg->wParam, msg->lParam)) {
update();
}
break;
}
default:
break;
}
return Windows10BorderHandler::nativeEventFilter(eventType, message, result);
}
void BorderItem::_q_afterSynchronizing() {
if (needNativePaint) {
needNativePaint = false;
drawBorderNative();
}
}
void BorderItem::_q_windowActivityChanged() {
update();
}
void QuickWindowAgentPrivate::setupWindows10BorderWorkaround() {
// Install painting hook
auto ctx = context.get();
if (ctx->windowAttribute(QStringLiteral("win10-border-needed")).toBool()) {
std::ignore = new BorderItem(hostWindow->contentItem(), ctx);
}
}
#endif
}

View File

@@ -0,0 +1,29 @@
// 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 "qwkquickglobal.h"
#include <QtQml/QQmlEngine>
#include "quickwindowagent.h"
namespace QWK {
static constexpr const char kModuleUri[] = "QWindowKit";
void registerTypes(QQmlEngine *engine) {
Q_UNUSED(engine);
static bool once = false;
if (once) {
return;
}
once = true;
// @uri QWindowKit
qmlRegisterType<QuickWindowAgent>(kModuleUri, 1, 0, "WindowAgent");
qmlRegisterModule(kModuleUri, 1, 0);
}
}

View File

@@ -0,0 +1,32 @@
// 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 QWKQUICKGLOBAL_H
#define QWKQUICKGLOBAL_H
#include <QtCore/QtGlobal>
#ifndef QWK_QUICK_EXPORT
# ifdef QWK_QUICK_STATIC
# define QWK_QUICK_EXPORT
# else
# ifdef QWK_QUICK_LIBRARY
# define QWK_QUICK_EXPORT Q_DECL_EXPORT
# else
# define QWK_QUICK_EXPORT Q_DECL_IMPORT
# endif
# endif
#endif
QT_BEGIN_NAMESPACE
class QQmlEngine;
QT_END_NAMESPACE
namespace QWK {
QWK_QUICK_EXPORT void registerTypes(QQmlEngine *engine);
}
#endif // QWKQUICKGLOBAL_H

View File

@@ -0,0 +1,32 @@
project(QWKWidgets
VERSION ${QWINDOWKIT_VERSION}
LANGUAGES CXX
)
set(_src
qwkwidgetsglobal.h
widgetitemdelegate_p.h
widgetitemdelegate.cpp
widgetwindowagent.h
widgetwindowagent_p.h
widgetwindowagent.cpp
)
if(WIN32)
list(APPEND _src widgetwindowagent_win.cpp)
elseif(APPLE)
list(APPEND _src widgetwindowagent_mac.cpp)
endif()
qwk_add_library(${PROJECT_NAME} AUTOGEN
SOURCES ${_src}
FEATURES cxx_std_17
LINKS QWKCore
QT_LINKS Core Gui Widgets
QT_INCLUDE_PRIVATE Core Gui Widgets
INCLUDE_PRIVATE *
PREFIX QWK_WIDGETS
)
set(QWINDOWKIT_ENABLED_TARGETS ${QWINDOWKIT_ENABLED_TARGETS} ${PROJECT_NAME} PARENT_SCOPE)
set(QWINDOWKIT_ENABLED_SUBDIRECTORIES ${QWINDOWKIT_ENABLED_SUBDIRECTORIES} widgets PARENT_SCOPE)

View File

@@ -0,0 +1,22 @@
// 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 QWKWIDGETSGLOBAL_H
#define QWKWIDGETSGLOBAL_H
#include <QtCore/QtGlobal>
#ifndef QWK_WIDGETS_EXPORT
# ifdef QWK_WIDGETS_STATIC
# define QWK_WIDGETS_EXPORT
# else
# ifdef QWK_WIDGETS_LIBRARY
# define QWK_WIDGETS_EXPORT Q_DECL_EXPORT
# else
# define QWK_WIDGETS_EXPORT Q_DECL_IMPORT
# endif
# endif
#endif
#endif // QWKWIDGETSGLOBAL_H

View File

@@ -0,0 +1,131 @@
// 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 "widgetitemdelegate_p.h"
#include <QtGui/QMouseEvent>
#include <QtWidgets/QApplication>
#include <QtWidgets/QWidget>
#include <QWKCore/private/abstractwindowcontext_p.h>
extern Q_DECL_IMPORT QWidget *qt_button_down;
namespace QWK {
class WidgetWinIdChangeEventFilter : public WinIdChangeEventFilter {
public:
explicit WidgetWinIdChangeEventFilter(QObject *host, AbstractWindowContext *ctx)
: WinIdChangeEventFilter(host, ctx), widget(static_cast<QWidget *>(host)) {
widget->installEventFilter(this);
}
WId winId() const override {
return widget->effectiveWinId();
}
protected:
bool eventFilter(QObject *obj, QEvent *event) override {
Q_UNUSED(obj)
if (event->type() == QEvent::WinIdChange) {
context->notifyWinIdChange();
}
return false;
}
QWidget *widget;
};
WidgetItemDelegate::WidgetItemDelegate() = default;
WidgetItemDelegate::~WidgetItemDelegate() = default;
QWindow *WidgetItemDelegate::window(const QObject *obj) const {
return static_cast<const QWidget *>(obj)->windowHandle();
}
bool WidgetItemDelegate::isEnabled(const QObject *obj) const {
return static_cast<const QWidget *>(obj)->isEnabled();
}
bool WidgetItemDelegate::isVisible(const QObject *obj) const {
return static_cast<const QWidget *>(obj)->isVisible();
}
QRect WidgetItemDelegate::mapGeometryToScene(const QObject *obj) const {
auto widget = static_cast<const QWidget *>(obj);
const QPoint originPoint = widget->mapTo(widget->window(), QPoint(0, 0));
const QSize size = widget->size();
return {originPoint, size};
}
QWindow *WidgetItemDelegate::hostWindow(const QObject *host) const {
return static_cast<const QWidget *>(host)->windowHandle();
}
bool WidgetItemDelegate::isWindowActive(const QObject *host) const {
return static_cast<const QWidget *>(host)->isActiveWindow();
}
void WidgetItemDelegate::resetQtGrabbedControl(QObject *host) const {
Q_UNUSED(host);
if (!qt_button_down) {
return;
}
static constexpr const auto invalidPos =
QPoint{std::numeric_limits<int>::lowest(), std::numeric_limits<int>::lowest()};
const auto event = new QMouseEvent(
QEvent::MouseButtonRelease, invalidPos, invalidPos, invalidPos, Qt::LeftButton,
QGuiApplication::mouseButtons() ^ Qt::LeftButton, QGuiApplication::keyboardModifiers());
QCoreApplication::postEvent(qt_button_down, event);
qt_button_down = nullptr;
}
Qt::WindowStates WidgetItemDelegate::getWindowState(const QObject *host) const {
return static_cast<const QWidget *>(host)->windowState();
}
void WidgetItemDelegate::setWindowState(QObject *host, Qt::WindowStates state) const {
static_cast<QWidget *>(host)->setWindowState(state);
}
void WidgetItemDelegate::setCursorShape(QObject *host, Qt::CursorShape shape) const {
static_cast<QWidget *>(host)->setCursor(QCursor(shape));
}
void WidgetItemDelegate::restoreCursorShape(QObject *host) const {
static_cast<QWidget *>(host)->unsetCursor();
}
Qt::WindowFlags WidgetItemDelegate::getWindowFlags(const QObject *host) const {
return static_cast<const QWidget *>(host)->windowFlags();
}
QRect WidgetItemDelegate::getGeometry(const QObject *host) const {
return static_cast<const QWidget *>(host)->geometry();
}
void WidgetItemDelegate::setWindowFlags(QObject *host, Qt::WindowFlags flags) const {
static_cast<QWidget *>(host)->setWindowFlags(flags);
}
void WidgetItemDelegate::setWindowVisible(QObject *host, bool visible) const {
static_cast<QWidget *>(host)->setVisible(visible);
}
void WidgetItemDelegate::setGeometry(QObject *host, const QRect &rect) {
static_cast<QWidget *>(host)->setGeometry(rect);
}
void WidgetItemDelegate::bringWindowToTop(QObject *host) const {
static_cast<QWidget *>(host)->raise();
}
WinIdChangeEventFilter *
WidgetItemDelegate::createWinIdEventFilter(QObject *host,
AbstractWindowContext *context) const {
return new WidgetWinIdChangeEventFilter(host, context);
}
}

View File

@@ -0,0 +1,57 @@
// 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 WIDGETITEMDELEGATE_P_H
#define WIDGETITEMDELEGATE_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 <QtGui/QWindow>
#include <QWKCore/private/windowitemdelegate_p.h>
#include <QWKWidgets/qwkwidgetsglobal.h>
namespace QWK {
class QWK_WIDGETS_EXPORT WidgetItemDelegate : public WindowItemDelegate {
public:
WidgetItemDelegate();
~WidgetItemDelegate() override;
public:
QWindow *window(const QObject *obj) const override;
bool isEnabled(const QObject *obj) const override;
bool isVisible(const QObject *obj) const override;
QRect mapGeometryToScene(const QObject *obj) const override;
QWindow *hostWindow(const QObject *host) const override;
bool isWindowActive(const QObject *host) const override;
Qt::WindowStates getWindowState(const QObject *host) const override;
Qt::WindowFlags getWindowFlags(const QObject *host) const override;
QRect getGeometry(const QObject *host) const override;
void resetQtGrabbedControl(QObject *host) const override;
void setWindowState(QObject *host, Qt::WindowStates state) const override;
void setCursorShape(QObject *host, Qt::CursorShape shape) const override;
void restoreCursorShape(QObject *host) const override;
void setWindowFlags(QObject *host, Qt::WindowFlags flags) const override;
void setWindowVisible(QObject *host, bool visible) const override;
void setGeometry(QObject *host, const QRect &rect) override;
void bringWindowToTop(QObject *host) const override;
WinIdChangeEventFilter *
createWinIdEventFilter(QObject *host, AbstractWindowContext *context) const override;
};
}
#endif // WIDGETITEMDELEGATE_P_H

View File

@@ -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
#include "widgetwindowagent.h"
#include "widgetwindowagent_p.h"
#include <QtGui/QtEvents>
#include <QtGui/QPainter>
#include <QtCore/QDebug>
#include "widgetitemdelegate_p.h"
namespace QWK {
/*!
\class WidgetWindowAgent
\brief WindowAgentBase is the window agent for QtWidgets.
It provides interfaces for QtWidgets and processes some Qt events related to the QWidget
instance.
*/
WidgetWindowAgentPrivate::WidgetWindowAgentPrivate() = default;
WidgetWindowAgentPrivate::~WidgetWindowAgentPrivate() = default;
void WidgetWindowAgentPrivate::init() {
}
/*!
Constructs a widget agent, it's better to set the widget to setup as \a parent.
*/
WidgetWindowAgent::WidgetWindowAgent(QObject *parent)
: WidgetWindowAgent(*new WidgetWindowAgentPrivate(), parent) {
}
/*!
Destructor.
*/
WidgetWindowAgent::~WidgetWindowAgent() = default;
/*!
Installs the window agent on the widget. The window agent will take over some of the window
events, making the window look frameless.
*/
bool WidgetWindowAgent::setup(QWidget *w) {
Q_ASSERT(w);
if (!w) {
return false;
}
#if defined(_DEBUG) || !defined(NDEBUG)
if (w->inherits("QDockWidget")) {
qWarning()
<< "QWK::WidgetWindowAgent: QWindowKit does not support QDockWidget as top window.";
}
#endif
Q_D(WidgetWindowAgent);
if (d->hostWidget) {
return false;
}
// Qt will create invisible native window container for native QWidget
// without this attribute, and this behavior will break QWK functionality.
// So far enabling this attribute is a must for QWK users.
w->setAttribute(Qt::WA_DontCreateNativeAncestors);
// Make sure the native window handle is actually created before we apply
// various hooks.
// w->setAttribute(Qt::WA_NativeWindow); // ### FIXME: Check
d->setup(w, new WidgetItemDelegate());
d->hostWidget = w;
#if defined(Q_OS_WINDOWS) && QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS)
d->setupWindows10BorderWorkaround();
#endif
return true;
}
/*!
Returns the title bar widget.
*/
QWidget *WidgetWindowAgent::titleBar() const {
Q_D(const WidgetWindowAgent);
return static_cast<QWidget *>(d->context->titleBar());
}
/*!
Sets the title bar widget, all system button and hit-test visible widget references that
have been set will be removed.
*/
void WidgetWindowAgent::setTitleBar(QWidget *w) {
Q_D(WidgetWindowAgent);
if (!d->context->setTitleBar(w)) {
return;
}
#ifdef Q_OS_MAC
setSystemButtonArea(nullptr);
#endif
Q_EMIT titleBarChanged(w);
}
/*!
Returns the system button of the given type.
*/
QWidget *WidgetWindowAgent::systemButton(SystemButton button) const {
Q_D(const WidgetWindowAgent);
return static_cast<QWidget *>(d->context->systemButton(button));
}
/*!
Sets the system button of the given type, the system buttons always receive mouse events so
you don't need to call \c setHitTestVisible for them.
*/
void WidgetWindowAgent::setSystemButton(SystemButton button, QWidget *w) {
Q_D(WidgetWindowAgent);
if (!d->context->setSystemButton(button, w)) {
return;
}
Q_EMIT systemButtonChanged(button, w);
}
/*!
Returns \a true if the widget can receive mouse events on title bar.
*/
bool WidgetWindowAgent::isHitTestVisible(const QWidget *w) const {
Q_D(const WidgetWindowAgent);
return d->context->isHitTestVisible(w);
}
/*!
Makes the widget able to receive mouse events on title bar if \a visible is \c true.
You're supposed to make sure that the specified widget \a w is a child or descendant
of the title bar widget.
*/
void WidgetWindowAgent::setHitTestVisible(QWidget *w, bool visible) {
Q_D(WidgetWindowAgent);
d->context->setHitTestVisible(w, visible);
}
/*!
\internal
*/
WidgetWindowAgent::WidgetWindowAgent(WidgetWindowAgentPrivate &d, QObject *parent)
: WindowAgentBase(d, parent) {
d.init();
}
/*!
\fn void WidgetWindowAgent::titleBarChanged(const QWidget *w)
This signal is emitted when the title bar widget is replaced.
*/
/*!
\fn void WidgetWindowAgent::systemButtonChanged(SystemButton button, const QWidget *w)
This signal is emitted when a system button is replaced.
*/
}

View File

@@ -0,0 +1,55 @@
// 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 WIDGETWINDOWAGENT_H
#define WIDGETWINDOWAGENT_H
#include <QtWidgets/QWidget>
#include <QWKCore/windowagentbase.h>
#include <QWKWidgets/qwkwidgetsglobal.h>
namespace QWK {
class WidgetWindowAgentPrivate;
class QWK_WIDGETS_EXPORT WidgetWindowAgent : public WindowAgentBase {
Q_OBJECT
Q_DECLARE_PRIVATE(WidgetWindowAgent)
public:
explicit WidgetWindowAgent(QObject *parent = nullptr);
~WidgetWindowAgent() override;
public:
bool setup(QWidget *w);
QWidget *titleBar() const;
void setTitleBar(QWidget *w);
QWidget *systemButton(SystemButton button) const;
void setSystemButton(SystemButton button, QWidget *w);
#ifdef Q_OS_MAC
// The system button area APIs are experimental, very likely to change in the future.
QWidget *systemButtonArea() const;
void setSystemButtonArea(QWidget *widget);
ScreenRectCallback systemButtonAreaCallback() const;
void setSystemButtonAreaCallback(const ScreenRectCallback &callback);
#endif
bool isHitTestVisible(const QWidget *w) const;
void setHitTestVisible(QWidget *w, bool visible = true);
Q_SIGNALS:
void titleBarChanged(QWidget *w);
void systemButtonChanged(SystemButton button, QWidget *w);
protected:
WidgetWindowAgent(WidgetWindowAgentPrivate &d, QObject *parent = nullptr);
};
}
#endif // WIDGETWINDOWAGENT_H

View File

@@ -0,0 +1,98 @@
// 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 "widgetwindowagent_p.h"
#include <QtGui/QtEvents>
namespace QWK {
static inline QRect getWidgetSceneRect(QWidget *widget) {
return {widget->mapTo(widget->window(), QPoint()), widget->size()};
}
class SystemButtonAreaWidgetEventFilter : public QObject {
public:
SystemButtonAreaWidgetEventFilter(QWidget *widget, AbstractWindowContext *ctx,
QObject *parent = nullptr)
: QObject(parent), widget(widget), ctx(ctx) {
widget->installEventFilter(this);
ctx->setSystemButtonAreaCallback([widget](const QSize &) {
return getWidgetSceneRect(widget); //
});
}
~SystemButtonAreaWidgetEventFilter() override = default;
protected:
bool eventFilter(QObject *obj, QEvent *event) override {
Q_UNUSED(obj)
switch (event->type()) {
case QEvent::Move:
case QEvent::Resize: {
ctx->virtual_hook(AbstractWindowContext::SystemButtonAreaChangedHook, nullptr);
break;
}
default:
break;
}
return false;
}
protected:
QWidget *widget;
AbstractWindowContext *ctx;
};
/*!
Returns the widget that acts as the system button area.
*/
QWidget *WidgetWindowAgent::systemButtonArea() const {
Q_D(const WidgetWindowAgent);
return d->systemButtonAreaWidget;
}
/*!
Sets the widget that acts as the system button area. The system button will be centered in
its area, it is recommended to place the widget in a layout and set a fixed size policy.
The system button will be visible in the system title bar area.
*/
void WidgetWindowAgent::setSystemButtonArea(QWidget *widget) {
Q_D(WidgetWindowAgent);
if (d->systemButtonAreaWidget == widget)
return;
auto ctx = d->context.get();
d->systemButtonAreaWidget = widget;
if (!widget) {
d->context->setSystemButtonAreaCallback({});
d->systemButtonAreaWidgetEventFilter.reset();
return;
}
d->systemButtonAreaWidgetEventFilter =
std::make_unique<SystemButtonAreaWidgetEventFilter>(widget, ctx);
}
/*!
Returns the the system button area callback.
*/
ScreenRectCallback WidgetWindowAgent::systemButtonAreaCallback() const {
Q_D(const WidgetWindowAgent);
return d->systemButtonAreaWidget ? nullptr : d->context->systemButtonAreaCallback();
}
/*!
Sets the the system button area callback, the \c size of the callback is the native title
bar size.
The system button position will be updated when the window resizes.
*/
void WidgetWindowAgent::setSystemButtonAreaCallback(const ScreenRectCallback &callback) {
Q_D(WidgetWindowAgent);
setSystemButtonArea(nullptr);
d->context->setSystemButtonAreaCallback(callback);
}
}

View File

@@ -0,0 +1,47 @@
// 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 WIDGETWINDOWAGENTPRIVATE_H
#define WIDGETWINDOWAGENTPRIVATE_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/qwkconfig.h>
#include <QWKCore/private/windowagentbase_p.h>
#include <QWKWidgets/widgetwindowagent.h>
namespace QWK {
class WidgetWindowAgentPrivate : public WindowAgentBasePrivate {
Q_DECLARE_PUBLIC(WidgetWindowAgent)
public:
WidgetWindowAgentPrivate();
~WidgetWindowAgentPrivate();
void init();
// Host
QWidget *hostWidget{};
#ifdef Q_OS_MAC
QWidget *systemButtonAreaWidget{};
std::unique_ptr<QObject> systemButtonAreaWidgetEventFilter;
#endif
#if defined(Q_OS_WINDOWS) && QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS)
void setupWindows10BorderWorkaround();
std::unique_ptr<QObject> borderHandler;
#endif
};
}
#endif // WIDGETWINDOWAGENTPRIVATE_H

View File

@@ -0,0 +1,179 @@
// 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 "widgetwindowagent_p.h"
#include <QtCore/QDebug>
#include <QtCore/QDateTime>
#include <QtGui/QPainter>
#include <QtCore/private/qcoreapplication_p.h>
#include <QWKCore/qwindowkit_windows.h>
#include <QWKCore/private/qwkglobal_p.h>
#include <QWKCore/private/windows10borderhandler_p.h>
namespace QWK {
#if QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS)
// https://github.com/qt/qtbase/blob/e26a87f1ecc40bc8c6aa5b889fce67410a57a702/src/plugins/platforms/windows/qwindowsbackingstore.cpp#L42
// In QtWidgets applications, when repainting happens, QPA at the last calls
// QWindowsBackingStore::flush() to draw the contents of the buffer to the screen, we need to
// call GDI drawing the top border after that.
// After debugging, we know that there are two situations that will lead to repaint.
//
// 1. Windows sends a WM_PAINT message, after which Qt immediately generates a QExposeEvent or
// QResizeEvent and send it to the corresponding QWidgetWindow instance, calling "flush" at the
// end of its handler.
//
// 2. When a timer or user input triggers Qt to repaint spontaneously, the corresponding
// QWidget receives a QEvent::UpdateRequest event and also calls "flush" at the end of its
// handler.
//
// The above two cases are mutually exclusive, so we just need to intercept the two events
// separately and draw the border area after the "flush" is called.
// https://github.com/qt/qtbase/blob/e26a87f1ecc40bc8c6aa5b889fce67410a57a702/src/plugins/platforms/windows/qwindowswindow.cpp#L2440
// Note that we can not draw the border right after WM_PAINT comes or right before the WndProc
// returns, because Qt calls BeginPaint() and EndPaint() itself. We should make sure that we
// draw the top border between these two calls, otherwise some display exceptions may arise.
class WidgetBorderHandler : public QObject, public Windows10BorderHandler {
public:
explicit WidgetBorderHandler(QWidget *widget, AbstractWindowContext *ctx,
QObject *parent = nullptr)
: QObject(parent), Windows10BorderHandler(ctx), widget(widget) {
widget->installEventFilter(this);
// First update
if (ctx->windowId()) {
setupNecessaryAttributes();
}
WidgetBorderHandler::updateGeometry();
}
void updateGeometry() override {
// The window top border is manually painted by QWK so we want to give
// some margins to avoid it covering real window contents, however, we
// found that there are some rounding issues for the thin border and
// thus this small trick doesn't work very well when the DPR is not
// integer. So far we haven't found a perfect solution, so just don't
// set any margins. In theory the window content will only be covered
// by 1px or so, it should not be a serious issue in the real world.
//
// widget->setContentsMargins(isNormalWindow() ? QMargins(0, borderThickness(), 0, 0)
// : QMargins());
}
bool isWindowActive() const override {
return widget->isActiveWindow();
}
inline void forwardEventToWidgetAndDraw(QWidget *w, QEvent *event) {
// https://github.com/qt/qtbase/blob/e26a87f1ecc40bc8c6aa5b889fce67410a57a702/src/widgets/kernel/qapplication.cpp#L3286
// Deliver the event
if (!forwardObjectEventFilters(this, w, event)) {
// Let the widget paint first
std::ignore = static_cast<QObject *>(w)->event(event);
QCoreApplicationPrivate::setEventSpontaneous(event, false);
}
// Due to the timer or user action, Qt will repaint some regions spontaneously,
// even if there is no WM_PAINT message, we must wait for it to finish painting
// and then update the top border area.
drawBorderNative();
}
inline void forwardEventToWindowAndDraw(QWindow *window, QEvent *event) {
// https://github.com/qt/qtbase/blob/e26a87f1ecc40bc8c6aa5b889fce67410a57a702/src/widgets/kernel/qapplication.cpp#L3286
// Deliver the event
if (!forwardObjectEventFilters(ctx, window, event)) {
// Let Qt paint first
std::ignore = static_cast<QObject *>(window)->event(event);
QCoreApplicationPrivate::setEventSpontaneous(event, false);
}
// Upon receiving the WM_PAINT message, Qt will repaint the entire view, and we
// must wait for it to finish painting before drawing this top border area.
drawBorderNative();
}
protected:
bool sharedEventFilter(QObject *obj, QEvent *event) override {
Q_UNUSED(obj)
switch (event->type()) {
case QEvent::Expose: {
// Qt will absolutely send a QExposeEvent or QResizeEvent to the QWindow when it
// receives a WM_PAINT message. When the control flow enters the expose handler,
// Qt must have already called BeginPaint() and it's the best time for us to
// draw the top border.
// Since a QExposeEvent will be sent immediately after the QResizeEvent, we can
// simply ignore it.
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
struct ExposeEvent : public QExposeEvent {
inline const QRegion &getRegion() const { return m_region; }
};
auto ee = static_cast<ExposeEvent *>(event);
bool exposeRegionValid = !ee->getRegion().isNull();
#else
auto ee = static_cast<QExposeEvent *>(event);
bool exposeRegionValid = !ee->region().isNull();
#endif
auto window = widget->windowHandle();
if (window->isExposed() && isNormalWindow() && exposeRegionValid) {
forwardEventToWindowAndDraw(window, event);
return true;
}
break;
}
default:
break;
}
return Windows10BorderHandler::sharedEventFilter(obj, event);
}
bool eventFilter(QObject *obj, QEvent *event) override {
Q_UNUSED(obj)
switch (event->type()) {
case QEvent::UpdateRequest: {
if (!isNormalWindow())
break;
forwardEventToWidgetAndDraw(widget, event);
return true;
}
case QEvent::WindowStateChange: {
updateGeometry();
break;
}
case QEvent::WindowActivate:
case QEvent::WindowDeactivate: {
widget->update();
break;
}
default:
break;
}
return false;
}
QWidget *widget;
};
void WidgetWindowAgentPrivate::setupWindows10BorderWorkaround() {
// Install painting hook
auto ctx = context.get();
if (ctx->windowAttribute(QStringLiteral("win10-border-needed")).toBool()) {
borderHandler = std::make_unique<WidgetBorderHandler>(hostWidget, ctx);
}
}
#endif
}