SineStriker
2025-02-11 e9f790d3533ebaee1f782e91b6e13b2c6375c86a
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>
@@ -9,6 +13,9 @@
#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 {
@@ -24,12 +31,16 @@
struct QWK_NSWindowDelegate {
public:
    enum NSEventType {
        WillEnterFullScreen,
        DidEnterFullScreen,
        WillExitFullScreen,
        DidExitFullScreen,
        DidResize,
    };
    virtual ~QWK_NSWindowDelegate() = default;
    virtual void windowWillEnterFullScreen(){};
    virtual void windowDidEnterFullScreen(){};
    virtual void windowWillExitFullScreen(){};
    virtual void windowDidExitFullScreen(){};
    virtual void windowDidResize(){};
    virtual void windowEvent(NSEventType eventType) = 0;
};
//
@@ -77,35 +88,40 @@
- (void)windowWillEnterFullScreen:(NSNotification *)notification {
    auto nswindow = reinterpret_cast<NSWindow *>(notification.object);
    if (auto proxy = QWK::g_proxyIndexes->value(nswindow)) {
        reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowWillEnterFullScreen();
        reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowEvent(
            QWK_NSWindowDelegate::WillEnterFullScreen);
    }
}
- (void)windowDidEnterFullScreen:(NSNotification *)notification {
    auto nswindow = reinterpret_cast<NSWindow *>(notification.object);
    if (auto proxy = QWK::g_proxyIndexes->value(nswindow)) {
        reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowDidEnterFullScreen();
        reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowEvent(
            QWK_NSWindowDelegate::DidEnterFullScreen);
    }
}
- (void)windowWillExitFullScreen:(NSNotification *)notification {
    auto nswindow = reinterpret_cast<NSWindow *>(notification.object);
    if (auto proxy = QWK::g_proxyIndexes->value(nswindow)) {
        reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowWillExitFullScreen();
        reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowEvent(
            QWK_NSWindowDelegate::WillExitFullScreen);
    }
}
- (void)windowDidExitFullScreen:(NSNotification *)notification {
    auto nswindow = reinterpret_cast<NSWindow *>(notification.object);
    if (auto proxy = QWK::g_proxyIndexes->value(nswindow)) {
        reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowDidExitFullScreen();
        reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowEvent(
            QWK_NSWindowDelegate::DidExitFullScreen);
    }
}
- (void)windowDidResize:(NSNotification *)notification {
    auto nswindow = reinterpret_cast<NSWindow *>(notification.object);
    if (auto proxy = QWK::g_proxyIndexes->value(nswindow)) {
        reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowDidResize();
        reinterpret_cast<QWK_NSWindowDelegate *>(proxy)->windowEvent(
            QWK_NSWindowDelegate::DidResize);
    }
}
@@ -118,6 +134,12 @@
namespace QWK {
    struct NSWindowProxy : public QWK_NSWindowDelegate {
        enum class BlurMode {
            Dark,
            Light,
            None,
        };
        NSWindowProxy(NSWindow *macWindow) {
            nswindow = macWindow;
            g_proxyIndexes->insert(nswindow, this);
@@ -127,74 +149,89 @@
            g_proxyIndexes->remove(nswindow);
        }
        void windowWillEnterFullScreen() override {
        }
        // Delegate
        void windowEvent(NSEventType eventType) override {
            switch (eventType) {
                case WillExitFullScreen: {
                    if (!screenRectCallback || !systemButtonVisible)
                        return;
        void windowDidEnterFullScreen() override {
        }
                    // 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;
                }
        void windowWillExitFullScreen() override {
            if (systemButtonRect.isEmpty() || !systemButtonVisible)
                return;
                case DidExitFullScreen: {
                    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;
                    for (const auto &button : systemButtons()) {
                        button.hidden = false;
                    }
                    updateSystemButtonRect();
                    break;
                }
                case DidResize: {
                    if (!screenRectCallback || !systemButtonVisible) {
                        return;
                    }
                    updateSystemButtonRect();
                    break;
                }
                default:
                    break;
            }
        }
        void windowDidExitFullScreen() override {
            if (systemButtonRect.isEmpty() || !systemButtonVisible)
                return;
            for (const auto &button : systemButtons()) {
                button.hidden = false;
            }
            updateSystemButtonRect();
        }
        void windowDidResize() override {
            if (systemButtonRect.isEmpty() || !systemButtonVisible) {
                return;
            }
            updateSystemButtonRect();
        }
        // System buttons visibility
        void setSystemButtonVisible(bool visible) {
            systemButtonVisible = visible;
            for (const auto &button : systemButtons()) {
                button.hidden = !visible;
            }
            if (systemButtonRect.isEmpty() || !visible) {
            if (!screenRectCallback || !visible) {
                return;
            }
            updateSystemButtonRect();
        }
        void setSystemButtonRect(const QRect &rect) {
            systemButtonRect = rect;
        // System buttons area
        void setScreenRectCallback(const ScreenRectCallback &callback) {
            screenRectCallback = callback;
            if (rect.isEmpty() || !systemButtonVisible) {
            if (!callback || !systemButtonVisible) {
                return;
            }
            updateSystemButtonRect();
        }
        void updateSystemButtonRect() {
            // 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)/
            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;
            QPoint center = systemButtonRect.center();
            auto viewSize =
                nswindow.contentView ? nswindow.contentView.frame.size : nswindow.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 = {
@@ -225,6 +262,54 @@
            return {closeBtn, minimizeBtn, zoomBtn};
        }
        inline int titleBarHeight() const {
            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;
            NSView *const view = [nswindow contentView];
            for (NSView *subview in [[view 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) {
            NSView *nsview = [nswindow contentView];
            if (!nsview) {
@@ -241,17 +326,13 @@
            nswindow.titlebarAppearsTransparent = (visible ? NO : YES);
            nswindow.titleVisibility = (visible ? NSWindowTitleVisible : NSWindowTitleHidden);
            nswindow.hasShadow = YES;
            nswindow.showsToolbarButton = NO;
            // nswindow.showsToolbarButton = NO;
            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() {
@@ -310,6 +391,8 @@
            [windowObserver release];
            windowObserver = nil;
        }
        static inline const Class windowClass = [NSWindow class];
    protected:
        static BOOL canBecomeKeyWindow(id obj, SEL sel) {
@@ -377,15 +460,13 @@
#endif
        }
        static inline const Class windowClass = [NSWindow class];
    private:
        Q_DISABLE_COPY(NSWindowProxy)
        NSWindow *nswindow = nil;
        bool systemButtonVisible = true;
        QRect systemButtonRect;
        ScreenRectCallback screenRectCallback;
        static inline QWK_NSWindowObserver *windowObserver = nil;
@@ -428,7 +509,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;
@@ -549,10 +633,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 {
@@ -576,7 +661,7 @@
    }
    CocoaWindowContext::~CocoaWindowContext() {
        releaseWindowProxy(windowId);
        releaseWindowProxy(m_windowId);
    }
    QString CocoaWindowContext::key() const {
@@ -586,7 +671,7 @@
    void CocoaWindowContext::virtual_hook(int id, void *data) {
        switch (id) {
            case SystemButtonAreaChangedHook: {
                ensureWindowProxy(windowId)->setSystemButtonRect(m_systemButtonArea);
                ensureWindowProxy(m_windowId)->setScreenRectCallback(m_systemButtonAreaCallback);
                return;
            }
@@ -596,32 +681,67 @@
        AbstractWindowContext::virtual_hook(id, data);
    }
    void CocoaWindowContext::winIdChanged() {
    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 (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);
        ensureWindowProxy(winId)->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;
        }
        if (key == QStringLiteral("blur-effect")) {
            auto mode = NSWindowProxy::BlurMode::None;
            if (attribute.type() == QVariant::Bool) {
                if (attribute.toBool()) {
                    NSString *osxMode =
                        [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"];
                    mode = [osxMode isEqualToString:@"Dark"] ? NSWindowProxy::BlurMode::Dark
                                                             : NSWindowProxy::BlurMode::Light;
                }
            } else if (attribute.type() == QVariant::String) {
                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;
    }