李威 wei.li
2025-04-21 469c975a708ed86c834b59ed751a4548c693a7b9
src/core/contexts/cocoawindowcontext.mm
@@ -1,3 +1,7 @@
// 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>
@@ -19,10 +23,6 @@
    using ProxyList = QHash<WId, NSWindowProxy *>;
    Q_GLOBAL_STATIC(ProxyList, g_proxyList);
    using ProxyList2 = QHash<NSWindow *, NSWindowProxy *>;
    Q_GLOBAL_STATIC(ProxyList2, g_proxyIndexes);
}
struct QWK_NSWindowDelegate {
@@ -83,7 +83,8 @@
- (void)windowWillEnterFullScreen:(NSNotification *)notification {
    auto nswindow = reinterpret_cast<NSWindow *>(notification.object);
    if (auto proxy = QWK::g_proxyIndexes->value(nswindow)) {
    auto nsview = [nswindow contentView];
    if (auto proxy = QWK::g_proxyList->value(reinterpret_cast<WId>(nsview))) {
        reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowEvent(
            QWK_NSWindowDelegate::WillEnterFullScreen);
    }
@@ -91,7 +92,8 @@
- (void)windowDidEnterFullScreen:(NSNotification *)notification {
    auto nswindow = reinterpret_cast<NSWindow *>(notification.object);
    if (auto proxy = QWK::g_proxyIndexes->value(nswindow)) {
    auto nsview = [nswindow contentView];
    if (auto proxy = QWK::g_proxyList->value(reinterpret_cast<WId>(nsview))) {
        reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowEvent(
            QWK_NSWindowDelegate::DidEnterFullScreen);
    }
@@ -99,7 +101,8 @@
- (void)windowWillExitFullScreen:(NSNotification *)notification {
    auto nswindow = reinterpret_cast<NSWindow *>(notification.object);
    if (auto proxy = QWK::g_proxyIndexes->value(nswindow)) {
    auto nsview = [nswindow contentView];
    if (auto proxy = QWK::g_proxyList->value(reinterpret_cast<WId>(nsview))) {
        reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowEvent(
            QWK_NSWindowDelegate::WillExitFullScreen);
    }
@@ -107,7 +110,8 @@
- (void)windowDidExitFullScreen:(NSNotification *)notification {
    auto nswindow = reinterpret_cast<NSWindow *>(notification.object);
    if (auto proxy = QWK::g_proxyIndexes->value(nswindow)) {
    auto nsview = [nswindow contentView];
    if (auto proxy = QWK::g_proxyList->value(reinterpret_cast<WId>(nsview))) {
        reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowEvent(
            QWK_NSWindowDelegate::DidExitFullScreen);
    }
@@ -115,12 +119,17 @@
- (void)windowDidResize:(NSNotification *)notification {
    auto nswindow = reinterpret_cast<NSWindow *>(notification.object);
    if (auto proxy = QWK::g_proxyIndexes->value(nswindow)) {
    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
//
@@ -136,13 +145,19 @@
            None,
        };
        NSWindowProxy(NSWindow *macWindow) {
            nswindow = macWindow;
            g_proxyIndexes->insert(nswindow, this);
        NSWindowProxy(NSView *macView) {
            nsview = macView;
            observer = [[QWK_NSViewObserver alloc] initWithProxy:this];
            [nsview addObserver:observer
                     forKeyPath:@"window"
                        options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
                        context:nil];
        }
        ~NSWindowProxy() override {
            g_proxyIndexes->remove(nswindow);
            [nsview removeObserver:observer forKeyPath:@"window"];
            [observer release];
        }
        // Delegate
@@ -209,6 +224,9 @@
        }
        void updateSystemButtonRect() {
            if (!screenRectCallback || !systemButtonVisible) {
                return;
            }
            const auto &buttons = systemButtons();
            const auto &leftButton = buttons[0];
            const auto &midButton = buttons[1];
@@ -221,8 +239,7 @@
            auto width = midButton.frame.size.width;
            auto height = midButton.frame.size.height;
            auto viewSize =
                nswindow.contentView ? nswindow.contentView.frame.size : nswindow.frame.size;
            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
@@ -252,6 +269,10 @@
        }
        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];
@@ -259,6 +280,10 @@
        }
        inline int titleBarHeight() const {
            auto nswindow = [nsview window];
            if (!nswindow) {
                return 0;
            }
            NSButton *closeBtn = [nswindow standardWindowButton:NSWindowCloseButton];
            return closeBtn.superview.frame.size.height;
        }
@@ -270,8 +295,7 @@
                return false;
            NSVisualEffectView *effectView = nil;
            NSView *const view = [nswindow contentView];
            for (NSView *subview in [[view superview] subviews]) {
            for (NSView *subview in [[nsview superview] subviews]) {
                if ([subview isKindOfClass:visualEffectViewClass]) {
                    effectView = reinterpret_cast<NSVisualEffectView *>(subview);
                }
@@ -307,8 +331,8 @@
        // System title bar
        void setSystemTitleBarVisible(const bool visible) {
            NSView *nsview = [nswindow contentView];
            if (!nsview) {
            auto nswindow = [nsview window];
            if (!nswindow) {
                return;
            }
@@ -326,13 +350,9 @@
            nswindow.movableByWindowBackground = NO;
            nswindow.movable = NO; // This line causes the window in the wrong position when
                                   // become fullscreen.
            //  For some unknown reason, we don't need the following hack in Qt versions below or
            //  equal to 6.2.4.
#if (QT_VERSION > QT_VERSION_CHECK(6, 2, 4))
            [nswindow standardWindowButton:NSWindowCloseButton].hidden = (visible ? NO : YES);
            [nswindow standardWindowButton:NSWindowMiniaturizeButton].hidden = (visible ? NO : YES);
            [nswindow standardWindowButton:NSWindowZoomButton].hidden = (visible ? NO : YES);
#endif
            [nswindow standardWindowButton:NSWindowCloseButton].hidden = NO;
            [nswindow standardWindowButton:NSWindowMiniaturizeButton].hidden = NO;
            [nswindow standardWindowButton:NSWindowZoomButton].hidden = NO;
        }
        static void replaceImplementations() {
@@ -396,7 +416,9 @@
    protected:
        static BOOL canBecomeKeyWindow(id obj, SEL sel) {
            if (g_proxyIndexes->contains(reinterpret_cast<NSWindow *>(obj))) {
            auto nswindow = reinterpret_cast<NSWindow *>(obj);
            auto nsview = [nswindow contentView];
            if (g_proxyList->contains(reinterpret_cast<WId>(nsview))) {
                return YES;
            }
@@ -408,7 +430,9 @@
        }
        static BOOL canBecomeMainWindow(id obj, SEL sel) {
            if (g_proxyIndexes->contains(reinterpret_cast<NSWindow *>(obj))) {
            auto nswindow = reinterpret_cast<NSWindow *>(obj);
            auto nsview = [nswindow contentView];
            if (g_proxyList->contains(reinterpret_cast<WId>(nsview))) {
                return YES;
            }
@@ -420,7 +444,9 @@
        }
        static void setStyleMask(id obj, SEL sel, NSWindowStyleMask styleMask) {
            if (g_proxyIndexes->contains(reinterpret_cast<NSWindow *>(obj))) {
            auto nswindow = reinterpret_cast<NSWindow *>(obj);
            auto nsview = [nswindow contentView];
            if (g_proxyList->contains(reinterpret_cast<WId>(nsview))) {
                styleMask |= NSWindowStyleMaskFullSizeContentView;
            }
@@ -430,7 +456,9 @@
        }
        static void setTitlebarAppearsTransparent(id obj, SEL sel, BOOL transparent) {
            if (g_proxyIndexes->contains(reinterpret_cast<NSWindow *>(obj))) {
            auto nswindow = reinterpret_cast<NSWindow *>(obj);
            auto nsview = [nswindow contentView];
            if (g_proxyList->contains(reinterpret_cast<WId>(nsview))) {
                transparent = YES;
            }
@@ -463,7 +491,8 @@
    private:
        Q_DISABLE_COPY(NSWindowProxy)
        NSWindow *nswindow = nil;
        NSView *nsview = nil;
        QWK_NSViewObserver* observer = nil;
        bool systemButtonVisible = true;
        ScreenRectCallback screenRectCallback;
@@ -500,8 +529,8 @@
        auto it = g_proxyList->find(windowId);
        if (it == g_proxyList->end()) {
            NSWindow *nswindow = mac_getNSWindow(windowId);
            const auto proxy = new NSWindowProxy(nswindow);
            NSView *nsview = reinterpret_cast<NSView *>(windowId);
            const auto proxy = new NSWindowProxy(nsview);
            it = g_proxyList->insert(windowId, proxy);
        }
        return it.value();
@@ -509,7 +538,10 @@
    static inline void releaseWindowProxy(const WId windowId) {
        if (auto proxy = g_proxyList->take(windowId)) {
            proxy->setSystemTitleBarVisible(true);
            // TODO: Determine if the window is valid
            // The window has been destroyed
            // proxy->setSystemTitleBarVisible(true);
            delete proxy;
        } else {
            return;
@@ -630,10 +662,11 @@
            }
            case QEvent::MouseButtonDblClick: {
                if (me->button() == Qt::LeftButton && inTitleBar &&
                    !delegate->isHostSizeFixed(host)) {
                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 ((windowFlags & Qt::WindowMaximizeButtonHint) &&
                        !(windowState & Qt::WindowFullScreen)) {
                        if (windowState & Qt::WindowMaximized) {
                            delegate->setWindowState(host, windowState & ~Qt::WindowMaximized);
                        } else {
@@ -657,7 +690,7 @@
    }
    CocoaWindowContext::~CocoaWindowContext() {
        releaseWindowProxy(windowId);
        releaseWindowProxy(m_windowId);
    }
    QString CocoaWindowContext::key() const {
@@ -667,7 +700,7 @@
    void CocoaWindowContext::virtual_hook(int id, void *data) {
        switch (id) {
            case SystemButtonAreaChangedHook: {
                ensureWindowProxy(windowId)->setScreenRectCallback(m_systemButtonAreaCallback);
                ensureWindowProxy(m_windowId)->setScreenRectCallback(m_systemButtonAreaCallback);
                return;
            }
@@ -679,37 +712,42 @@
    QVariant CocoaWindowContext::windowAttribute(const QString &key) const {
        if (key == QStringLiteral("title-bar-height")) {
            if (!m_windowHandle)
                return 0;
            return ensureWindowProxy(windowId)->titleBarHeight();
            if (!m_windowId)
                return {};
            return ensureWindowProxy(m_windowId)->titleBarHeight();
        }
        return AbstractWindowContext::windowAttribute(key);
    }
    void CocoaWindowContext::winIdChanged() {
    void CocoaWindowContext::winIdChanged(WId winId, WId oldWinId) {
        // If the original window id is valid, remove all resources related
        if (windowId) {
            releaseWindowProxy(windowId);
            windowId = 0;
        if (oldWinId) {
            releaseWindowProxy(oldWinId);
        }
        if (!m_windowHandle) {
        if (!winId) {
            return;
        }
        // Allocate new resources
        windowId = m_windowHandle->winId();
        ensureWindowProxy(windowId)->setSystemTitleBarVisible(false);
        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 (attribute.type() != QVariant::Bool)
                return false;
            ensureWindowProxy(windowId)->setSystemButtonVisible(!attribute.toBool());
            ensureWindowProxy(m_windowId)->setSystemButtonVisible(!attribute.toBool());
            return true;
        }
@@ -736,10 +774,39 @@
            } else {
                return false;
            }
            return ensureWindowProxy(windowId)->setBlurEffect(mode);
            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