Explorar o código

更新 qwindowkit

zhuizhu hai 9 meses
pai
achega
bef708f45f
Modificáronse 53 ficheiros con 931 adicións e 687 borrados
  1. 2 1
      .vscode/settings.json
  2. 9 1
      AvPlayer2/PlayWidget.cpp
  3. 3 0
      AvPlayer2/playercontroller.cpp
  4. 4 1
      AvPlayer2/playercontroller.h
  5. 0 1
      AvRecorder/encoder/audio_encoder.cpp
  6. 8 8
      AvRecorder/encoder/audio_mixer.cpp
  7. 109 1
      AvRecorder/ui/opengl_video_widget.cpp
  8. 13 0
      AvRecorder/ui/opengl_video_widget.h
  9. 2 0
      LearningSmartClient.pro
  10. 94 0
      MainPanel.cpp
  11. 24 0
      MainPanel.h
  12. 12 0
      main.cpp
  13. 3 113
      mainwindow.cpp
  14. 2 1
      mainwindow.h
  15. 1 5
      qwindowkit/examples/mainwindow/CMakeLists.txt
  16. 4 0
      qwindowkit/examples/mainwindow/main.cpp
  17. 5 0
      qwindowkit/examples/mainwindow/mainwindow.cpp
  18. 1 5
      qwindowkit/examples/qml/CMakeLists.txt
  19. 9 245
      qwindowkit/examples/qml/main.qml
  20. 1 0
      qwindowkit/examples/qml/qml.qrc
  21. 1 5
      qwindowkit/examples/shared/widgetframe/CMakeLists.txt
  22. 2 2
      qwindowkit/qmsetup/README.md
  23. 171 84
      qwindowkit/qmsetup/cmake/QMSetupAPI.cmake
  24. 19 11
      qwindowkit/qmsetup/cmake/find-modules/VC-LTL.cmake
  25. 31 27
      qwindowkit/qmsetup/cmake/modules/CompilerOptions.cmake
  26. 52 7
      qwindowkit/qmsetup/cmake/modules/Deploy.cmake
  27. 29 4
      qwindowkit/qmsetup/cmake/modules/Translate.cmake
  28. 5 3
      qwindowkit/qmsetup/cmake/windows/WinManifest.manifest.in
  29. 1 1
      qwindowkit/qmsetup/include/qmsetup/qmsetup_global.h
  30. 5 0
      qwindowkit/qmsetup/src/corecmd/CMakeLists.txt
  31. 2 2
      qwindowkit/qmsetup/src/corecmd/main.cpp
  32. 16 2
      qwindowkit/qmsetup/src/syscmdline/include/syscmdline/argument.h
  33. 7 1
      qwindowkit/qmsetup/src/syscmdline/include/syscmdline/global.h
  34. 0 1
      qwindowkit/qmsetup/src/syscmdline/include/syscmdline/sharedbase.h
  35. 14 4
      qwindowkit/qmsetup/src/syscmdline/src/argument.cpp
  36. 1 1
      qwindowkit/qmsetup/src/syscmdline/src/argument_p.h
  37. 10 1
      qwindowkit/qmsetup/src/syscmdline/src/parser.cpp
  38. 3 7
      qwindowkit/src/CMakeLists.txt
  39. 15 64
      qwindowkit/src/core/contexts/abstractwindowcontext.cpp
  40. 4 9
      qwindowkit/src/core/contexts/abstractwindowcontext_p.h
  41. 103 27
      qwindowkit/src/core/contexts/cocoawindowcontext.mm
  42. 24 8
      qwindowkit/src/core/contexts/win32windowcontext.cpp
  43. 8 0
      qwindowkit/src/core/qwindowkit_windows.h
  44. 7 0
      qwindowkit/src/core/qwkglobal.h
  45. 39 7
      qwindowkit/src/core/shared/qwkwindowsextra_p.h
  46. 17 5
      qwindowkit/src/core/shared/systemwindow_p.h
  47. 22 5
      qwindowkit/src/core/shared/windows10borderhandler_p.h
  48. 1 1
      qwindowkit/src/core/style/styleagent_win.cpp
  49. 1 1
      qwindowkit/src/core/windowagentbase.h
  50. 12 11
      qwindowkit/src/quick/quickwindowagent_win.cpp
  51. 0 1
      qwindowkit/src/widgets/widgetitemdelegate_p.h
  52. 1 1
      qwindowkit/src/widgets/widgetwindowagent.cpp
  53. 2 2
      qwindowkit/src/widgets/widgetwindowagent_win.cpp

+ 2 - 1
.vscode/settings.json

@@ -5,5 +5,6 @@
     "optional": "cpp",
     "system_error": "cpp",
     "regex": "cpp"
-  }
+  },
+  "cmake.sourceDirectory": "F:/A_QT/im/LearningSmartClient/qwindowkit/examples/mainwindow"
 }

+ 9 - 1
AvPlayer2/PlayWidget.cpp

@@ -85,8 +85,16 @@ void PlayWidget::setPlayerController(PlayerController* controller)
     });
     // 音频可视化信号
     connect(m_playerController, &PlayerController::audioData, this, [this](const AudioData& data) {
-        if (m_audioEffect) m_audioEffect->paint_data(data);
+        if (m_audioEffect)
+            m_audioEffect->paint_data(data);
     });
+    connect(m_playerController, &PlayerController::videoStopped, this, [this]() {
+        if (m_videoWidget)
+            m_videoWidget->showEndTip("播放结束啦~");
+    });
+
+    // VideoPlayThread* videoPlayThread = m_playerController->videoPlayThread();
+
     // 进度/时间信号
     // connect(m_playerController, &PlayerController::updatePlayTime, this, [this]() {
     //     if (!m_playerController) return;

+ 3 - 0
AvPlayer2/playercontroller.cpp

@@ -209,12 +209,15 @@ void PlayerController::audioPlayStopped()
 {
     m_audioPlayThread.reset();
     qDebug("************* Audio play thread stopped.");
+    emit audioStopped(); // 音频结束
 }
 
 void PlayerController::videoPlayStopped()
 {
     m_videoPlayThread.reset();
     qDebug("************* Video play thread stopped.");
+
+    emit videoStopped(); // 视频结束
 }
 
 // 线程管理槽函数

+ 4 - 1
AvPlayer2/playercontroller.h

@@ -58,7 +58,7 @@ public:
     float deviceVolume() const;
     void setDeviceVolume(float volume);
 
-    // 线程访问接口
+    // // 线程访问接口
     AudioPlayThread* audioPlayThread() const { return m_audioPlayThread.get(); }
     VideoPlayThread* videoPlayThread() const { return m_videoPlayThread.get(); }
     VideoStateData* videoStateData() const { return m_videoState.get(); }
@@ -91,6 +91,9 @@ signals:
     void waitStopAudioPlayThread();
     void waitStopVideoPlayThread();
 
+    void audioStopped(); // 音频结束
+    void videoStopped(); // 视频结束
+
     // 多媒体数据处理
     void frameReady(AVFrame*);
     void audioData(const AudioData& data);

+ 0 - 1
AvRecorder/encoder/audio_encoder.cpp

@@ -1,4 +1,3 @@
-
 #include "audio_encoder.h"
 
 bool Encoder<MediaType::AUDIO>::Open(const Param& audioParma, AVFormatContext* fmtCtx)

+ 8 - 8
AvRecorder/encoder/audio_mixer.cpp

@@ -166,14 +166,14 @@ AVFrame* AudioMixer::Convert(uint32_t index, uint8_t* inBuf, uint32_t size)
     std::lock_guard<std::mutex> locker(_mutex);
     auto iter = _audioInputInfos.find(index);
     __CheckNullptr(iter != _audioInputInfos.end());
-    
-    // 添加调试信息
-    static int debugCounter = 0;
-    if (++debugCounter % 100 == 0) {
-        qDebug() << "AudioMixer::Convert - Input size:" << size 
-                 << "bytes, Input index: " << index;
-    }
-    
+
+    // // 添加调试信息
+    // static int debugCounter = 0;
+    // if (++debugCounter % 100 == 0) {
+    //     qDebug() << "AudioMixer::Convert - Input size:" << size
+    //              << "bytes, Input index: " << index;
+    // }
+
     __CheckNullptr(iter->second.resampler->Convert(inBuf, size));
     return _AdjustVolume() ? _outputFrame : nullptr;
 }

+ 109 - 1
AvRecorder/ui/opengl_video_widget.cpp

@@ -1,5 +1,9 @@
 #include "opengl_video_widget.h"
 #include <QDebug>
+#include <QFont>
+#include <QFontMetrics>
+#include <QPainter>
+#include <QTimer>
 
 OpenGLVideoWidget::OpenGLVideoWidget(QWidget* parent)
     : QOpenGLWidget(parent)
@@ -26,6 +30,9 @@ OpenGLVideoWidget::OpenGLVideoWidget(QWidget* parent)
     , m_contrast(1.0f)
     , m_brightness(0.0f)
     , m_mirror(false)
+    , m_noVideoTip(QStringLiteral("视频未开始"))
+    , m_tipTexture(0)
+    , m_tipAngle(0.0f)
 {
     // 设置顶点坐标
     m_vertices[0] = -1.0f; m_vertices[1] = -1.0f;
@@ -38,6 +45,20 @@ OpenGLVideoWidget::OpenGLVideoWidget(QWidget* parent)
     m_texCoords[2] = 1.0f; m_texCoords[3] = 1.0f;
     m_texCoords[4] = 0.0f; m_texCoords[5] = 0.0f;
     m_texCoords[6] = 1.0f; m_texCoords[7] = 0.0f;
+
+    // 3D文本旋转动画定时器
+    m_tipTimer = new QTimer(this);
+    connect(m_tipTimer, &QTimer::timeout, this, [this](){
+        m_tipAngle += 2.0f;
+        if (m_tipAngle > 360.0f) m_tipAngle -= 360.0f;
+        update();
+    });
+    m_tipTimer->start(30);
+    // 初始化时生成一次纹理,防止初次显示时未生成
+    m_frameData = nullptr;
+    m_frameUpdated = false;
+    m_tipImage = QImage();
+    m_tipTexture = 0;
 }
 
 OpenGLVideoWidget::~OpenGLVideoWidget()
@@ -192,12 +213,73 @@ void OpenGLVideoWidget::resizeGL(int width, int height)
     glViewport(0, 0, width, height);
 }
 
+// 文本转OpenGL纹理
+void OpenGLVideoWidget::updateTipTexture()
+{
+    QFont font;
+    font.setPointSize(48);
+    font.setBold(true);
+    QFontMetrics fm(font);
+    int w = fm.horizontalAdvance(m_noVideoTip) + 40;
+    int h = fm.height() + 40;
+    QImage img(w, h, QImage::Format_ARGB32_Premultiplied);
+    img.fill(Qt::transparent);
+    QPainter p(&img);
+    p.setFont(font);
+    p.setPen(Qt::white);
+    p.setRenderHint(QPainter::Antialiasing);
+    p.drawText(img.rect(), Qt::AlignCenter, m_noVideoTip);
+    p.end();
+    m_tipImage = img;
+    if (m_tipTexture) {
+        glDeleteTextures(1, &m_tipTexture);
+    }
+    glGenTextures(1, &m_tipTexture);
+    glBindTexture(GL_TEXTURE_2D, m_tipTexture);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img.width(), img.height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, img.bits());
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+    glBindTexture(GL_TEXTURE_2D, 0);
+}
+
+// 3D绘制方法
+void OpenGLVideoWidget::drawNoVideoTip3D()
+{
+    glClearColor(0, 0, 0, 1);
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+    glEnable(GL_DEPTH_TEST);
+    glEnable(GL_TEXTURE_2D);
+    glBindTexture(GL_TEXTURE_2D, m_tipTexture);
+    glMatrixMode(GL_PROJECTION);
+    glLoadIdentity();
+    float aspect = float(width()) / float(height());
+    glOrtho(-aspect, aspect, -1, 1, -10, 10);
+    glMatrixMode(GL_MODELVIEW);
+    glLoadIdentity();
+    glTranslatef(0, 0, 0);
+    glRotatef(m_tipAngle, 0, 1, 0);
+    float w = float(m_tipImage.width()) / width();
+    float h = float(m_tipImage.height()) / height();
+    glBegin(GL_QUADS);
+    glTexCoord2f(0, 1); glVertex3f(-w, -h, 0);
+    glTexCoord2f(1, 1); glVertex3f(w, -h, 0);
+    glTexCoord2f(1, 0); glVertex3f(w, h, 0);
+    glTexCoord2f(0, 0); glVertex3f(-w, h, 0);
+    glEnd();
+    glBindTexture(GL_TEXTURE_2D, 0);
+    glDisable(GL_TEXTURE_2D);
+    glDisable(GL_DEPTH_TEST);
+}
+
 void OpenGLVideoWidget::paintGL()
 {
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
     QMutexLocker locker(&m_mutex);
-    if (!m_frameData || m_frameWidth <= 0 || m_frameHeight <= 0 || !m_frameUpdated)
+    if (!m_frameData || m_frameWidth <= 0 || m_frameHeight <= 0 || !m_frameUpdated) {
+        if (m_tipTexture == 0) updateTipTexture();
+        drawNoVideoTip3D();
         return;
+    }
     // 绑定纹理并更新数据
     glBindTexture(GL_TEXTURE_2D, m_textureId);
     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_frameWidth, m_frameHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_frameData);
@@ -474,3 +556,29 @@ void OpenGLVideoWidget::setContrastBright(bool on, float contrast, float brightn
 void OpenGLVideoWidget::setMirror(bool on) {
     if (m_mirror != on) { m_mirror = on; update(); }
 }
+
+void OpenGLVideoWidget::setNoVideoTip(const QString& tip)
+{
+    if (m_noVideoTip != tip) {
+        m_noVideoTip = tip;
+        updateTipTexture();
+        update();
+    }
+}
+
+void OpenGLVideoWidget::showEndTip(const QString& tip)
+{
+    QMutexLocker locker(&m_mutex);
+    m_noVideoTip = tip;
+    if (m_tipTexture) {
+        glDeleteTextures(1, &m_tipTexture);
+        m_tipTexture = 0;
+    }
+    m_frameUpdated = false;
+    if (m_frameData) {
+        delete[] m_frameData;
+        m_frameData = nullptr;
+    }
+    updateTipTexture();
+    update();
+}

+ 13 - 0
AvRecorder/ui/opengl_video_widget.h

@@ -5,6 +5,8 @@
 #include <QOpenGLShaderProgram>
 #include <QOpenGLTexture>
 #include <QOpenGLWidget>
+#include <QTimer>
+#include <QImage>
 
 extern "C" {
 #include <libavutil/frame.h>
@@ -51,6 +53,9 @@ public:
     void setContrastBright(bool on, float contrast = 1.0f, float brightness = 0.0f);
     void setMirror(bool on);
 
+    void setNoVideoTip(const QString& tip);
+    void showEndTip(const QString& tip);
+
 protected:
     void initializeGL() override;
     void paintGL() override;
@@ -92,4 +97,12 @@ private:
     float m_brightness = 0.0f;
 
     bool m_mirror = false;
+
+    QString m_noVideoTip;
+    QImage m_tipImage;
+    GLuint m_tipTexture;
+    float m_tipAngle;
+    QTimer* m_tipTimer;
+    void updateTipTexture();
+    void drawNoVideoTip3D();
 };

+ 2 - 0
LearningSmartClient.pro

@@ -12,6 +12,7 @@ QT += opengl
 
 
 SOURCES += \
+    MainPanel.cpp \
     api/chatapi.cpp \
     api/loginapi.cpp \
     api/roomapi.cpp \
@@ -42,6 +43,7 @@ SOURCES += \
     widgets/userprofilewidget.cpp
 
 HEADERS += \
+    MainPanel.h \
     api/chatapi.h \
     api/loginapi.h \
     api/roomapi.h \

+ 94 - 0
MainPanel.cpp

@@ -0,0 +1,94 @@
+#include "MainPanel.h"
+#include <QSplitter>
+#include <QVBoxLayout>
+#include <QListWidget>
+#include "widgets/userprofilewidget.h"
+#include "widgets/chatView/chatwindow.h"
+#include "widgets/bubbletip.h"
+#include "api/roomapi.h"
+#include "api/userapi.h"
+#include "appevent.h"
+#include "ui/av_recorder.h"
+#include "AvPlayer2/PlayWidget.h"
+#include <QDebug>
+
+MainPanel::MainPanel(QWidget *parent)
+    : QWidget(parent)
+    , userProfile(nullptr)
+    , chatView(nullptr)
+{
+    // setupUI
+    userProfile = new UserProfileWidget(this);
+    chatView = new ChatWindow();
+    chatView->setMinimumWidth(400);
+
+    QWidget *rightWidget = new QWidget;
+    QVBoxLayout *vbox = new QVBoxLayout(rightWidget);
+    vbox->setContentsMargins(0, 0, 0, 0);
+    vbox->addWidget(userProfile, 0);
+    vbox->addWidget(chatView, 1);
+
+    roomListWidget = new QListWidget;
+    splitter = new QSplitter(Qt::Horizontal, this);
+    playerContainer = new QWidget(this);
+    splitter->addWidget(roomListWidget);
+    splitter->addWidget(playerContainer);
+    splitter->addWidget(rightWidget);
+    splitter->setStretchFactor(0, 10);
+    splitter->setStretchFactor(1, 60);
+    splitter->setStretchFactor(2, 30);
+    QVBoxLayout *mainLayout = new QVBoxLayout(this);
+    mainLayout->addWidget(splitter, 1);
+    mainLayout->setContentsMargins(0, 0, 0, 0);
+    mainLayout->setSpacing(0);
+    setLayout(mainLayout);
+
+    // initConnect
+    connect(AppEvent::instance(), &AppEvent::connectionStateChanged, this, [this](bool connected) {
+        if (userProfile) {
+            userProfile->setStatus(connected ? "在线" : "离线");
+        }
+    });
+}
+
+MainPanel::~MainPanel()
+{
+    if (userProfile) {
+        delete userProfile;
+        userProfile = nullptr;
+    }
+}
+
+void MainPanel::setPlayerWidget(QWidget *newPlayer)
+{
+    if (playerWidget) {
+        playerWidget->setParent(nullptr);
+        playerWidget->deleteLater();
+    }
+    playerWidget = newPlayer;
+    playerWidget->setParent(playerContainer);
+    QLayout *oldLayout = playerContainer->layout();
+    if (oldLayout) {
+        QLayoutItem *item;
+        while ((item = oldLayout->takeAt(0)) != nullptr) {
+            if (item->widget())
+                item->widget()->setParent(nullptr);
+            delete item;
+        }
+        delete oldLayout;
+    }
+    QVBoxLayout *vbox = new QVBoxLayout(playerContainer);
+    vbox->setContentsMargins(0, 0, 0, 0);
+    vbox->addWidget(playerWidget);
+}
+
+void MainPanel::setRole(const QStringList &roleList)
+{
+    QWidget *newPlayer = nullptr;
+    if (roleList.contains("role.admin")) {
+        newPlayer = new AvRecorder(this);
+    } else {
+        newPlayer = new PlayWidget(this);
+    }
+    setPlayerWidget(newPlayer);
+} 

+ 24 - 0
MainPanel.h

@@ -0,0 +1,24 @@
+#pragma once
+#include <QWidget>
+#include <QStringList>
+class QSplitter;
+class QListWidget;
+class UserProfileWidget;
+class ChatWindow;
+
+class MainPanel : public QWidget
+{
+    Q_OBJECT
+public:
+    explicit MainPanel(QWidget *parent = nullptr);
+    ~MainPanel();
+    void setPlayerWidget(QWidget *newPlayer);
+    void setRole(const QStringList &roleList);
+
+    QSplitter *splitter = nullptr;
+    QWidget *playerContainer = nullptr;
+    QWidget *playerWidget = nullptr;
+    UserProfileWidget *userProfile = nullptr;
+    ChatWindow *chatView = nullptr;
+    QListWidget *roomListWidget = nullptr;
+}; 

+ 12 - 0
main.cpp

@@ -60,6 +60,18 @@ int main(int argc, char* argv[])
 
     // std::freopen(nullptr, "w", stdout);
     setvbuf(stdout, nullptr, _IONBF, 0);
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+    QGuiApplication::setHighDpiScaleFactorRoundingPolicy(
+        Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
+#endif
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+    QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
+#endif
+
+    QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
+
     QApplication a(argc, argv);
 
     ThemeManager::instance().setThemeMode(ThemeManager::Light);

+ 3 - 113
mainwindow.cpp

@@ -39,121 +39,11 @@
 #include <qtpromise/qpromisehelpers.h>
 
 #include "AvPlayer2/PlayWidget.h"
+#include "MainPanel.h"
 
 #include <QListWidget>
 
-class MainWidget : public QWidget
-{
-public:
-    explicit MainWidget(QWidget *parent = nullptr)
-        : QWidget(parent)
-        , userProfile(nullptr)
-        , chatView(nullptr)
-    {
-        setupUI();
-        initConnect();
-    }
-
-    ~MainWidget()
-    {
-        if (userProfile) {
-            delete userProfile;
-            userProfile = nullptr;
-        }
-    }
-    void setupUI()
-    {
-        // 只创建一次布局和静态控件
-        userProfile = new UserProfileWidget(this);
-        chatView = new ChatWindow();
-        chatView->setMinimumWidth(400);
-
-        QWidget *rightWidget = new QWidget;
-        QVBoxLayout *vbox = new QVBoxLayout(rightWidget);
-        vbox->setContentsMargins(0, 0, 0, 0);
-        vbox->addWidget(userProfile, 0);
-        vbox->addWidget(chatView, 1);
-
-        // 这里先实现一个房间的出来
-        roomListWidget = new QListWidget;
-
-        splitter = new QSplitter(Qt::Horizontal, this);
-        // 先插入一个占位控件
-        playerContainer = new QWidget(this);
-        splitter->addWidget(roomListWidget);
-        splitter->addWidget(playerContainer);
-        splitter->addWidget(rightWidget);
-
-        splitter->setStretchFactor(0, 10);
-        splitter->setStretchFactor(1, 60);
-        splitter->setStretchFactor(2, 30);
-
-        QVBoxLayout *mainLayout = new QVBoxLayout(this);
-        mainLayout->addWidget(splitter, 1);
-        mainLayout->setContentsMargins(0, 0, 0, 0);
-        mainLayout->setSpacing(0);
-
-        setLayout(mainLayout);
-    }
-    void initConnect()
-    {
-        connect(AppEvent::instance(),
-                &AppEvent::connectionStateChanged,
-                this,
-                [this](bool connected) {
-                    if (userProfile) {
-                        userProfile->setStatus(connected ? "在线" : "离线");
-                    }
-                });
-    }
-
-    // 动态插入或替换播放器控件
-    void setPlayerWidget(QWidget *newPlayer)
-    {
-        if (playerWidget) {
-            playerWidget->setParent(nullptr);
-            playerWidget->deleteLater();
-        }
-        playerWidget = newPlayer;
-        playerWidget->setParent(playerContainer);
-
-        // 清空 playerContainer 的布局
-        QLayout *oldLayout = playerContainer->layout();
-        if (oldLayout) {
-            QLayoutItem *item;
-            while ((item = oldLayout->takeAt(0)) != nullptr) {
-                if (item->widget())
-                    item->widget()->setParent(nullptr);
-                delete item;
-            }
-            delete oldLayout;
-        }
-        QVBoxLayout *vbox = new QVBoxLayout(playerContainer);
-        vbox->setContentsMargins(0, 0, 0, 0);
-        vbox->addWidget(playerWidget);
-    }
-
-    // 角色切换时调用
-    void setRole(const QStringList &roleList)
-    {
-        QWidget *newPlayer = nullptr;
-        if (roleList.contains("role.admin")) {
-            newPlayer = new AvRecorder(this);
-        } else {
-            newPlayer = new PlayWidget(this);
-        }
-        setPlayerWidget(newPlayer);
-    }
-
-    QString currentRole;
-
-    QSplitter *splitter = nullptr;
-    QWidget *playerContainer = nullptr;
-    QWidget *playerWidget = nullptr;
-    UserProfileWidget *userProfile = nullptr;
-    ChatWindow *chatView = nullptr;
-    QListWidget *roomListWidget = nullptr;
-};
+// MainWidget 类已移除,相关实现迁移到 MainPanel.cpp/h
 
 MainWindow::MainWindow(QWidget *parent)
     : TMainWindow(parent)
@@ -190,7 +80,7 @@ void MainWindow::createMainWindow()
         mainWidget = nullptr;
     }
     // 主窗口
-    mainWidget = new MainWidget(this);
+    mainWidget = new MainPanel(this);
     stackedWidget->addWidget(mainWidget);
 
     // 退出处理

+ 2 - 1
mainwindow.h

@@ -3,6 +3,7 @@
 
 #include <QMainWindow>
 #include "widgets/framelessbase.h"
+#include "MainPanel.h"
 
 class QHBoxLayout;
 class QStackedWidget;
@@ -26,7 +27,7 @@ private:
 
     QVBoxLayout *layout;
     class LoginWidget *loginWidget;
-    class MainWidget *mainWidget;
+    MainPanel *mainWidget;
 
     QStackedWidget *stackedWidget;
 };

+ 1 - 5
qwindowkit/examples/mainwindow/CMakeLists.txt

@@ -3,12 +3,8 @@ project(QWKExample_MainWindow)
 file(GLOB _src *.h *.cpp)
 
 qwk_add_example(${PROJECT_NAME}
+    FEATURES cxx_std_17
     SOURCES ${_src} mainwindow.qrc ../shared/resources/shared.qrc
     QT_LINKS Core Gui Widgets # MultimediaWidgets
     LINKS QWKWidgets WidgetFrame
-)
-
-set_target_properties(${PROJECT_NAME} PROPERTIES
-    CXX_STANDARD 17
-    CXX_STANDARD_REQUIRED TRUE
 )

+ 4 - 0
qwindowkit/examples/mainwindow/main.cpp

@@ -19,6 +19,10 @@ int main(int argc, char *argv[]) {
     QGuiApplication::setHighDpiScaleFactorRoundingPolicy(
         Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
 #endif
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+    QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
+#endif
 
     QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
     QApplication a(argc, argv);

+ 5 - 0
qwindowkit/examples/mainwindow/mainwindow.cpp

@@ -210,6 +210,11 @@ void MainWindow::installWindowAgent() {
                 });
 
 #elif defined(Q_OS_MAC)
+        // Set whether to use system buttons (close/minimize/zoom)
+        // - true:  Hide system buttons (use custom UI controls)
+        // - false: Show native system buttons (default behavior)
+        windowAgent->setWindowAttribute(QStringLiteral("no-system-buttons"), false);
+
         auto darkBlurAction = new QAction(tr("Dark blur"), menuBar);
         darkBlurAction->setCheckable(true);
         connect(darkBlurAction, &QAction::toggled, this, [this](bool checked) {

+ 1 - 5
qwindowkit/examples/qml/CMakeLists.txt

@@ -3,12 +3,8 @@ project(QWKExample_QML)
 file(GLOB _src *.h *.cpp *.qrc)
 
 qwk_add_example(${PROJECT_NAME}
+    FEATURES cxx_std_17
     SOURCES ${_src} ../shared/resources/shared.qrc
     QT_LINKS Core Gui Qml Quick
     LINKS QWKQuick
-)
-
-set_target_properties(${PROJECT_NAME} PROPERTIES
-    CXX_STANDARD 17
-    CXX_STANDARD_REQUIRED TRUE
 )

+ 9 - 245
qwindowkit/examples/qml/main.qml

@@ -1,255 +1,19 @@
 import QtQuick 2.15
 import QtQuick.Window 2.15
 import QtQuick.Controls 2.15
-import Qt.labs.platform 1.1
-import QWindowKit 1.0
 
-Window {
-    id: window
-    width: 800
-    height: 600
-    color: darkStyle.windowBackgroundColor
-    title: qsTr("Hello, world!")
-    Component.onCompleted: {
-        windowAgent.setup(window)
-        windowAgent.setWindowAttribute("dark-mode", true)
-        window.visible = true
+FramelessWindow {
+    property FramelessWindow childWindow: FramelessWindow {
+        showWhenReady: false
     }
 
-    QtObject {
-        id: lightStyle
-    }
-
-    QtObject {
-        id: darkStyle
-        readonly property color windowBackgroundColor: "#1E1E1E"
-    }
-
-    Timer {
-        interval: 100
-        running: true
-        repeat: true
-        onTriggered: timeLabel.text = Qt.formatTime(new Date(), "hh:mm:ss")
-    }
-
-    WindowAgent {
-        id: windowAgent
-    }
-
-    TapHandler {
-        acceptedButtons: Qt.RightButton
-        onTapped: contextMenu.open()
-    }
-
-    Rectangle {
-        id: titleBar
+    Button {
         anchors {
-            top: parent.top
-            left: parent.left
-            right: parent.right
-        }
-        height: 32
-        //color: window.active ? "#3C3C3C" : "#505050"
-        color: "transparent"
-        Component.onCompleted: windowAgent.setTitleBar(titleBar)
-
-        Image {
-            id: iconButton
-            anchors {
-                verticalCenter: parent.verticalCenter
-                left: parent.left
-                leftMargin: 10
-            }
-            width: 18
-            height: 18
-            mipmap: true
-            source: "qrc:///app/example.png"
-            fillMode: Image.PreserveAspectFit
-            Component.onCompleted: windowAgent.setSystemButton(WindowAgent.WindowIcon, iconButton)
-        }
-
-        Text {
-            anchors {
-                verticalCenter: parent.verticalCenter
-                left: iconButton.right
-                leftMargin: 10
-            }
-            horizontalAlignment: Text.AlignHCenter
-            verticalAlignment: Text.AlignVCenter
-            text: window.title
-            font.pixelSize: 14
-            color: "#ECECEC"
-        }
-
-        Row {
-            anchors {
-                top: parent.top
-                right: parent.right
-            }
-            height: parent.height
-
-            QWKButton {
-                id: minButton
-                height: parent.height
-                source: "qrc:///window-bar/minimize.svg"
-                onClicked: window.showMinimized()
-                Component.onCompleted: windowAgent.setSystemButton(WindowAgent.Minimize, minButton)
-            }
-
-            QWKButton {
-                id: maxButton
-                height: parent.height
-                source: window.visibility === Window.Maximized ? "qrc:///window-bar/restore.svg" : "qrc:///window-bar/maximize.svg"
-                onClicked: {
-                    if (window.visibility === Window.Maximized) {
-                        window.showNormal()
-                    } else {
-                        window.showMaximized()
-                    }
-                }
-                Component.onCompleted: windowAgent.setSystemButton(WindowAgent.Maximize, maxButton)
-            }
-
-            QWKButton {
-                id: closeButton
-                height: parent.height
-                source: "qrc:///window-bar/close.svg"
-                background: Rectangle {
-                    color: {
-                        if (!closeButton.enabled) {
-                            return "gray";
-                        }
-                        if (closeButton.pressed) {
-                            return "#e81123";
-                        }
-                        if (closeButton.hovered) {
-                            return "#e81123";
-                        }
-                        return "transparent";
-                    }
-                }
-                onClicked: window.close()
-                Component.onCompleted: windowAgent.setSystemButton(WindowAgent.Close, closeButton)
-            }
-        }
-    }
-
-    Label {
-        id: timeLabel
-        anchors.centerIn: parent
-        font {
-            pointSize: 75
-            bold: true
-        }
-        color: "#FEFEFE"
-        Component.onCompleted: {
-            if ($curveRenderingAvailable) {
-                console.log("Curve rendering for text is available.")
-                timeLabel.renderType = Text.CurveRendering
-            }
-        }
-    }
-
-    Menu {
-        id: contextMenu
-
-        Menu {
-            id: themeMenu
-            title: qsTr("Theme")
-
-            MenuItemGroup {
-                id: themeMenuGroup
-                items: themeMenu.items
-            }
-
-            MenuItem {
-                text: qsTr("Light")
-                checkable: true
-                onTriggered: windowAgent.setWindowAttribute("dark-mode", false)
-            }
-
-            MenuItem {
-                text: qsTr("Dark")
-                checkable: true
-                checked: true
-                onTriggered: windowAgent.setWindowAttribute("dark-mode", true)
-            }
-        }
-
-        Menu {
-            id: specialEffectMenu
-            title: qsTr("Special effect")
-
-            MenuItemGroup {
-                id: specialEffectMenuGroup
-                items: specialEffectMenu.items
-            }
-
-            MenuItem {
-                enabled: Qt.platform.os === "windows"
-                text: qsTr("None")
-                checkable: true
-                checked: true
-                onTriggered: {
-                    window.color = darkStyle.windowBackgroundColor
-                    windowAgent.setWindowAttribute("dwm-blur", false)
-                    windowAgent.setWindowAttribute("acrylic-material", false)
-                    windowAgent.setWindowAttribute("mica", false)
-                    windowAgent.setWindowAttribute("mica-alt", false)
-                }
-            }
-
-            MenuItem {
-                enabled: Qt.platform.os === "windows"
-                text: qsTr("DWM blur")
-                checkable: true
-                onTriggered: {
-                    window.color = "transparent"
-                    windowAgent.setWindowAttribute("acrylic-material", false)
-                    windowAgent.setWindowAttribute("mica", false)
-                    windowAgent.setWindowAttribute("mica-alt", false)
-                    windowAgent.setWindowAttribute("dwm-blur", true)
-                }
-            }
-
-            MenuItem {
-                enabled: Qt.platform.os === "windows"
-                text: qsTr("Acrylic material")
-                checkable: true
-                onTriggered: {
-                    window.color = "transparent"
-                    windowAgent.setWindowAttribute("dwm-blur", false)
-                    windowAgent.setWindowAttribute("mica", false)
-                    windowAgent.setWindowAttribute("mica-alt", false)
-                    windowAgent.setWindowAttribute("acrylic-material", true)
-                }
-            }
-
-            MenuItem {
-                enabled: Qt.platform.os === "windows"
-                text: qsTr("Mica")
-                checkable: true
-                onTriggered: {
-                    window.color = "transparent"
-                    windowAgent.setWindowAttribute("dwm-blur", false)
-                    windowAgent.setWindowAttribute("acrylic-material", false)
-                    windowAgent.setWindowAttribute("mica-alt", false)
-                    windowAgent.setWindowAttribute("mica", true)
-                }
-            }
-
-            MenuItem {
-                enabled: Qt.platform.os === "windows"
-                text: qsTr("Mica Alt")
-                checkable: true
-                onTriggered: {
-                    window.color = "transparent"
-                    windowAgent.setWindowAttribute("dwm-blur", false)
-                    windowAgent.setWindowAttribute("acrylic-material", false)
-                    windowAgent.setWindowAttribute("mica", false)
-                    windowAgent.setWindowAttribute("mica-alt", true)
-                }
-            }
+            horizontalCenter: parent.horizontalCenter
+            bottom: parent.bottom
+            bottomMargin: 20
         }
+        text: qsTr("Open Child Window")
+        onClicked: childWindow.visible = true
     }
 }

+ 1 - 0
qwindowkit/examples/qml/qml.qrc

@@ -2,5 +2,6 @@
     <qresource prefix="/">
         <file>main.qml</file>
         <file>QWKButton.qml</file>
+        <file>FramelessWindow.qml</file>
     </qresource>
 </RCC>

+ 1 - 5
qwindowkit/examples/shared/widgetframe/CMakeLists.txt

@@ -9,13 +9,9 @@ file(GLOB _src *.h *.cpp)
 add_library(${PROJECT_NAME} STATIC)
 
 qm_configure_target(${PROJECT_NAME}
+    FEATURES cxx_std_17
     SOURCES ${_src}
     QT_LINKS Core Gui Widgets
 )
 
-set_target_properties(${PROJECT_NAME} PROPERTIES
-    CXX_STANDARD 17
-    CXX_STANDARD_REQUIRED TRUE
-)
-
 target_include_directories(${PROJECT_NAME} PUBLIC . ..)

+ 2 - 2
qwindowkit/qmsetup/README.md

@@ -79,8 +79,8 @@ git clone --recursive git@github.com:stdware/qmsetup.git
 ```sh
 cmake -B build -DCMAKE_BUILD_TYPE=Release \
                -DCMAKE_INSTALL_PREFIX=/path/to
-cmake -B build --target all
-cmake -B build --target install
+cmake --build build --target all
+cmake --build build --target install
 ```
 
 #### Import

+ 171 - 84
qwindowkit/qmsetup/cmake/QMSetupAPI.cmake

@@ -211,18 +211,39 @@ endfunction()
 #[[
     Find Qt libraries. Don't wrap it in any functions.
 
-    qm_find_qt(<modules...>)
+    qm_find_qt(<modules...> [QUIET | REQUIRED | EXACT])
 #]]
 macro(qm_find_qt)
-    foreach(_module ${ARGN})
+    set(options QUIET REQUIRED EXACT)
+    set(oneValueArgs)
+    set(multiValueArgs)
+    cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+
+    set(_qm_find_qt_options)
+
+    if(FUNC_QUIET)
+        list(APPEND _qm_find_qt_options QUIET)
+    elseif(FUNC_REQUIRED)
+        list(APPEND _qm_find_qt_options REQUIRED)
+    elseif(FUNC_EXACT)
+        list(APPEND _qm_find_qt_options EXACT)
+    endif()
+
+    if(NOT _qm_find_qt_options)
+        set(_qm_find_qt_options REQUIRED)
+    endif()
+
+    foreach(_module ${FUNC_UNPARSED_ARGUMENTS})
         if(NOT QT_VERSION_MAJOR)
-            find_package(QT NAMES ${QMSETUP_FIND_QT_ORDER} COMPONENTS ${_module} REQUIRED)
+            find_package(QT NAMES ${QMSETUP_FIND_QT_ORDER} COMPONENTS ${_module} ${_qm_find_qt_options})
         endif()
 
         if(NOT TARGET Qt${QT_VERSION_MAJOR}::${_module})
-            find_package(Qt${QT_VERSION_MAJOR} COMPONENTS ${_module} REQUIRED)
+            find_package(Qt${QT_VERSION_MAJOR} COMPONENTS ${_module} ${_qm_find_qt_options})
         endif()
     endforeach()
+
+    unset(_qm_find_qt_options)
 endmacro()
 
 #[[
@@ -244,8 +265,13 @@ endmacro()
 #]]
 macro(qm_include_qt_private _target _scope)
     foreach(_module ${ARGN})
-        qm_find_qt(${_module})
-        target_include_directories(${_target} ${_scope} ${Qt${QT_VERSION_MAJOR}${_module}_PRIVATE_INCLUDE_DIRS})
+        qm_find_qt(${_module}Private QUIET)
+        if (TARGET Qt${QT_VERSION_MAJOR}::${_module}Private)
+            target_link_libraries(${_target} ${_scope} Qt${QT_VERSION_MAJOR}::${_module}Private)
+        else()
+            qm_find_qt(${_module})
+            target_include_directories(${_target} ${_scope} ${Qt${QT_VERSION_MAJOR}${_module}_PRIVATE_INCLUDE_DIRS})
+        endif()
     endforeach()
 endmacro()
 
@@ -253,31 +279,40 @@ endmacro()
     Helper to set or append all kinds of attributes to a target. Don't wrap it in any functions.
 
     qm_configure_target(<target>
-        [SOURCES          <files>]
+        [SOURCES           <files>]
 
-        [LINKS            <libs>]
-        [LINKS_PRIVATE    <libs>]
+        [LINKS             <libs>]
+        [LINKS_INTERFACE   <libs>]
+        [LINKS_PRIVATE     <libs>]
 
-        [INCLUDE          <dirs>]
-        [INCLUDE_PRIVATE  <dirs>]
+        [INCLUDE           <dirs>]
+        [INCLUDE_INTERFACE <dirs>]
+        [INCLUDE_PRIVATE   <dirs>]
 
-        [LINKDIR          <dirs>]
-        [LINKDIR_PRIVATE  <dirs>]
+        [LINKDIR           <dirs>]
+        [LINKDIR_INTERFACE <dirs>]
+        [LINKDIR_PRIVATE   <dirs>]
 
-        [DEFINES          <defs>]
-        [DEFINES_PRIVATE  <defs>]
+        [DEFINES           <defs>]
+        [DEFINES_INTERFACE <defs>]
+        [DEFINES_PRIVATE   <defs>]
 
-        [FEATURES          <features>]
-        [FEATURES_PRIVATE  <features>]
+        [FEATURES           <features>]
+        [FEATURES_INTERFACE <features>]
+        [FEATURES_PRIVATE   <features>]
 
-        [CCFLAGS          <flags>]
-        [CCFLAGS_PUBLIC   <flags>]
+        [CCFLAGS           <flags>]
+        [CCFLAGS_INTERFACE <flags>]
+        [CCFLAGS_PUBLIC    <flags>]
 
-        [LDFLAGS          <flags>]
-        [LDFLAGS_PUBLIC   <flags>]
+        [LDFLAGS           <flags>]
+        [LDFLAGS_INTERFACE <flags>]
+        [LDFLAGS_PUBLIC    <flags>]
 
         [QT_LINKS            <modules>]
+        [QT_LINKS_INTERFACE  <modules>]
         [QT_LINKS_PRIVATE    <modules>]
+
         [QT_INCLUDE_PRIVATE  <modules>]
 
         [SKIP_AUTOMOC   <dir/file...>]
@@ -291,98 +326,96 @@ macro(qm_configure_target _target)
     set(oneValueArgs)
     set(multiValueArgs
         SOURCES
-        LINKS LINKS_PRIVATE
-        INCLUDE INCLUDE_PRIVATE
-        LINKDIR LINKDIR_PRIVATE
-        DEFINES DEFINES_PRIVATE
-        FEATURES FEATURES_PRIVATE
-        CCFLAGS CCFLAGS_PUBLIC
-        LDFLAGS LDFLAGS_PUBLIC
-        QT_LINKS QT_LINKS_PRIVATE QT_INCLUDE_PRIVATE
+        LINKS LINKS_INTERFACE LINKS_PRIVATE
+        INCLUDE INCLUDE_INTERFACE INCLUDE_PRIVATE
+        LINKDIR LINKDIR_INTERFACE LINKDIR_PRIVATE
+        DEFINES DEFINES_INTERFACE DEFINES_PRIVATE
+        FEATURES FEATURES_INTERFACE FEATURES_PRIVATE
+        CCFLAGS CCFLAGS_INTERFACE CCFLAGS_PUBLIC
+        LDFLAGS LDFLAGS_INTERFACE LDFLAGS_PUBLIC
+        QT_LINKS QT_LINKS_PRIVATE QT_LINKS_INTERFACE
+        QT_INCLUDE_PRIVATE
         SKIP_AUTOMOC
     )
     cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
 
-    macro(_resolve_dir_helper _dirs _out)
-        set(${_out})
-
-        foreach(_item IN LISTS ${_dirs})
-            if(_item STREQUAL "*")
-                set(_cur_dir ".")
-                file(GLOB _subdirs LIST_DIRECTORIES true "*")
-            elseif(_item STREQUAL "**")
-                set(_cur_dir ".")
-                file(GLOB_RECURSE _subdirs LIST_DIRECTORIES true "*")
-            elseif(_item MATCHES "(.+)/\\*$")
-                set(_cur_dir ${CMAKE_MATCH_1})
-                file(GLOB _subdirs LIST_DIRECTORIES true "${_cur_dir}/*")
-            elseif(_item MATCHES "(.+)/\\*\\*$")
-                set(_cur_dir ${CMAKE_MATCH_1})
-                file(GLOB_RECURSE _subdirs LIST_DIRECTORIES true "${_cur_dir}/*")
-            else()
-                list(APPEND ${_out} ${_item})
-                continue()
-            endif()
-
-            list(APPEND ${_out} ${_cur_dir})
-
-            foreach(_subdir IN LISTS _subdirs)
-                if(IS_DIRECTORY ${_subdir})
-                    get_filename_component(_subdir ${_subdir} ABSOLUTE)
-                    list(APPEND ${_out} ${_subdir})
-                endif()
-            endforeach()
-        endforeach()
-    endmacro()
-
     target_sources(${_target} PRIVATE ${FUNC_SOURCES})
 
     target_link_libraries(${_target} PUBLIC ${FUNC_LINKS})
     target_link_libraries(${_target} PRIVATE ${FUNC_LINKS_PRIVATE})
 
     if(FUNC_INCLUDE)
-        _resolve_dir_helper(FUNC_INCLUDE _temp_dirs)
+        _qm_resolve_dir_helper("${FUNC_INCLUDE}" _temp_dirs)
         target_include_directories(${_target} PUBLIC ${_temp_dirs})
         unset(_temp_dirs)
     endif()
 
+    if(FUNC_INCLUDE_INTERFACE)
+        _qm_resolve_dir_helper("${FUNC_INCLUDE_INTERFACE}" _temp_dirs)
+        target_include_directories(${_target} INTERFACE ${_temp_dirs})
+        unset(_temp_dirs)
+    endif()
+
     if(FUNC_INCLUDE_PRIVATE)
-        _resolve_dir_helper(FUNC_INCLUDE_PRIVATE _temp_dirs)
+        _qm_resolve_dir_helper("${FUNC_INCLUDE_PRIVATE}" _temp_dirs)
         target_include_directories(${_target} PRIVATE ${_temp_dirs})
         unset(_temp_dirs)
     endif()
 
     if(FUNC_LINKDIR)
-        _resolve_dir_helper(FUNC_LINKDIR _temp_dirs)
+        _qm_resolve_dir_helper("${FUNC_LINKDIR}" _temp_dirs)
         target_link_directories(${_target} PUBLIC ${_temp_dirs})
         unset(_temp_dirs)
     endif()
 
+    if(FUNC_LINKDIR_INTERFACE)
+        _qm_resolve_dir_helper("${FUNC_LINKDIR}" _temp_dirs)
+        target_link_directories(${_target} INTERFACE ${_temp_dirs})
+        unset(_temp_dirs)
+    endif()
+
     if(FUNC_LINKDIR_PRIVATE)
-        _resolve_dir_helper(FUNC_LINKDIR_PRIVATE _temp_dirs)
+        _qm_resolve_dir_helper("${FUNC_LINKDIR_PRIVATE}" _temp_dirs)
         target_link_directories(${_target} PRIVATE ${_temp_dirs})
         unset(_temp_dirs)
     endif()
 
     target_compile_definitions(${_target} PUBLIC ${FUNC_DEFINES})
+    target_compile_definitions(${_target} INTERFACE ${FUNC_DEFINES_INTERFACE})
     target_compile_definitions(${_target} PRIVATE ${FUNC_DEFINES_PRIVATE})
 
     target_compile_features(${_target} PUBLIC ${FUNC_FEATURES})
+    target_compile_features(${_target} INTERFACE ${FUNC_FEATURES_INTERFACE})
     target_compile_features(${_target} PRIVATE ${FUNC_FEATURES_PRIVATE})
 
+    # CMake won't add language standard flag if the compiler default mode supports the standard,
+    # however, if the -std argument is not explicitly specified, the clang language server will
+    # not work properly.
+    # https://discourse.cmake.org/t/cmake-does-not-set-the-compiler-option-std-to-gnu17-or-c-17-although-i-set-the-target-compile-features-to-cxx-std-17/3299/8
+    foreach(_item IN LISTS FUNC_FEATURES FUNC_FEATURES_PRIVATE)
+        if(_item MATCHES "cxx_std_(.+)")
+            set_property(TARGET APPEND PROPERTY CXX_STANDARD ${CMAKE_MATCH_1})
+        elseif(_item MATCHES "c_std_(.+)")
+            set_property(TARGET APPEND PROPERTY C_STANDARD ${CMAKE_MATCH_1})
+        endif()
+    endforeach()
+
     target_compile_options(${_target} PUBLIC ${FUNC_CCFLAGS_PUBLIC})
+    target_compile_options(${_target} INTERFACE ${FUNC_CCFLAGS_INTERFACE})
     target_compile_options(${_target} PRIVATE ${FUNC_CCFLAGS})
 
     target_link_options(${_target} PUBLIC ${FUNC_LDFLAGS_PUBLIC})
+    target_link_options(${_target} INTERFACE ${FUNC_LDFLAGS_INTERFACE})
     target_link_options(${_target} PRIVATE ${FUNC_LDFLAGS})
 
     qm_link_qt(${_target} PUBLIC ${FUNC_QT_LINKS})
+    qm_link_qt(${_target} INTERFACE ${FUNC_QT_LINKS_INTERFACE})
     qm_link_qt(${_target} PRIVATE ${FUNC_QT_LINKS_PRIVATE})
 
     qm_include_qt_private(${_target} PRIVATE ${FUNC_QT_INCLUDE_PRIVATE})
 
     if(FUNC_SKIP_AUTOMOC)
-        _resolve_dir_helper(FUNC_SKIP_AUTOMOC _temp_files)
+        _qm_resolve_file_helper("${FUNC_SKIP_AUTOMOC}" _temp_files)
         qm_skip_automoc(${_temp_files})
         unset(_temp_files)
     endif()
@@ -623,20 +656,27 @@ function(qm_add_win_manifest _target)
     set(_out_path "${_out_dir}/${_target}_manifest.exe.manifest")
     configure_file("${QMSETUP_MODULES_DIR}/windows/WinManifest.manifest.in" ${_out_path} @ONLY)
 
-    # https://cmake.org/cmake/help/latest/release/3.4.html#other
-    # CMake learned to honor *.manifest source files with MSVC tools. Manifest files named as sources
-    # of .exe and .dll targets will be merged with linker-generated manifests and embedded in the binary.
-    # NOTE: CMake will automatically generate a default manifest file and embed it into the binary file
-    # when we are using MSVC toolchain, so it will conflict with the one we embed in the RC file. So in
-    # this case, we just follow the CMake documentation, add the manifest file into the sources and let
-    # CMake handle it. What's more, CMake can automatically merge all manifest files so we can actually
-    # add multiple manifest files, eg. each manifest file contains a separate section.
     if(MSVC)
-        target_sources(${_target} PRIVATE ${_out_path})
-        
-        # The manifest file contains a UAC field, we should prevent MSVC from embedding the
-        # automatically generated UAC field
-        target_link_options(${_target} PRIVATE "/manifestuac:no")
+        if(CMAKE_GENERATOR MATCHES "Visual Studio")
+            # Visual Studio
+            target_link_options(${_target} PRIVATE "/manifest" "/manifestinput:${_out_path}")
+
+            # The manifest file contains a UAC field, we should prevent Ninja from embedding the
+            # automatically generated UAC field
+            target_link_options(${_target} PRIVATE "/manifest" "/manifestuac:no")
+        else() # Ninja
+            # https://cmake.org/cmake/help/latest/release/3.4.html#other
+            # CMake learned to honor *.manifest source files with MSVC tools. Manifest files named as sources
+            # of .exe and .dll targets will be merged with linker-generated manifests and embedded in the binary.
+
+            # NOTE: CMake will automatically generate a default manifest file and embed it into the binary file
+            # when we are using MSVC toolchain, so it will conflict with the one we embed in the RC file. So in
+            # this case, we just follow the CMake documentation, add the manifest file into the sources and let
+            # CMake handle it. What's more, CMake can automatically merge all manifest files so we can actually
+            # add multiple manifest files, eg. each manifest file contains a separate section.
+            target_sources(${_target} PRIVATE ${_out_path})
+            target_link_options(${_target} PRIVATE "/manifestuac:no")
+        endif()
     else()
         # For non-MSVC toolchains we can only embed the manifest file into the RC file, sadly we have
         # to merge all manifest files into one by ourself if we have multiple of them, but luckily
@@ -856,23 +896,23 @@ function(qm_get_subdirs _var)
         set(_dir ${CMAKE_CURRENT_SOURCE_DIR})
     endif()
 
-    file(GLOB _subdirs LIST_DIRECTORIES true RELATIVE ${_dir} "${_dir}/*")
+    file(GLOB _entries LIST_DIRECTORIES true RELATIVE ${_dir} "${_dir}/*")
 
     if(FUNC_EXCLUDE)
         foreach(_exclude_dir IN LISTS FUNC_EXCLUDE)
-            list(REMOVE_ITEM _subdirs ${_exclude_dir})
+            list(REMOVE_ITEM _entries ${_exclude_dir})
         endforeach()
     endif()
 
     if(FUNC_REGEX_INCLUDE)
         foreach(_exp IN LISTS FUNC_REGEX_INCLUDE)
-            list(FILTER _subdirs INCLUDE REGEX ${_exp})
+            list(FILTER _entries INCLUDE REGEX ${_exp})
         endforeach()
     endif()
 
     if(FUNC_REGEX_EXCLUDE)
         foreach(_exp IN LISTS FUNC_REGEX_EXCLUDE)
-            list(FILTER _subdirs EXCLUDE REGEX ${_exp})
+            list(FILTER _entries EXCLUDE REGEX ${_exp})
         endforeach()
     endif()
 
@@ -884,7 +924,7 @@ function(qm_get_subdirs _var)
         set(_relative)
     endif()
 
-    foreach(_sub IN LISTS _subdirs)
+    foreach(_sub IN LISTS _entries)
         if(IS_DIRECTORY ${_dir}/${_sub})
             if(FUNC_ABSOLUTE)
                 list(APPEND _res ${_dir}/${_sub})
@@ -1003,3 +1043,50 @@ macro(_qm_check_target_type_helper _target _type)
     unset(_tmp_target_type)
     unset(_tmp_target_type_list)
 endmacro()
+
+function(_qm_resolve_file_helper _dirs _out)
+    set(_res)
+
+    foreach(_item ${_dirs})
+        if(_item STREQUAL "*")
+            set(_cur_dir ".")
+            file(GLOB _files LIST_DIRECTORIES true "*")
+        elseif(_item STREQUAL "**")
+            set(_cur_dir ".")
+            file(GLOB_RECURSE _files LIST_DIRECTORIES true "*")
+        elseif(_item MATCHES "(.+)[/\\]\\*$")
+            set(_cur_dir ${CMAKE_MATCH_1})
+            file(GLOB _files LIST_DIRECTORIES true "${_cur_dir}/*")
+        elseif(_item MATCHES "(.+)[/\\]\\*\\*$")
+            set(_cur_dir ${CMAKE_MATCH_1})
+            file(GLOB_RECURSE _files LIST_DIRECTORIES true "${_cur_dir}/*")
+        else()
+            get_filename_component(_item ${_item} ABSOLUTE)
+            list(APPEND _res ${_item})
+            continue()
+        endif()
+
+        get_filename_component(_cur_dir ${_cur_dir} ABSOLUTE)
+        list(APPEND _res ${_cur_dir})
+
+        foreach(_item IN LISTS _files)
+            get_filename_component(_item ${_item} ABSOLUTE)
+            list(APPEND _res ${_item})
+        endforeach()
+    endforeach()
+
+    set(${_out} ${_res} PARENT_SCOPE)
+endfunction()
+
+function(_qm_resolve_dir_helper _dirs _out)
+    set(_files)
+    _qm_resolve_file_helper("${_dirs}" _files)
+
+    foreach(_item IN LISTS _files)
+        if(IS_DIRECTORY ${_item})
+            list(APPEND _res ${_item})
+        endif()
+    endforeach()
+
+    set(${_out} ${_res} PARENT_SCOPE)
+endfunction()

+ 19 - 11
qwindowkit/qmsetup/cmake/find-modules/VC-LTL.cmake

@@ -1,4 +1,11 @@
-# https://github.com/Chuyu-Team/VC-LTL5
+# https://github.com/Chuyu-Team/VC-LTL5
+
+cmake_minimum_required(VERSION 3.13)
+
+if(NOT MSVC OR DEFINED __VC_LTL_CMAKE_INCLUDE_GUARD)
+    return()
+endif()
+set(__VC_LTL_CMAKE_INCLUDE_GUARD 1)
 
 #
 #  VC-LTL自动化加载配置,建议你将此文件单独复制到你的工程再使用,该文件能自动识别当前环境是否存在VC-LTL,并且自动应用。
@@ -19,23 +26,24 @@
 #  如果你对默认搜索顺序不满,你可以修改此文件。你也可以直接指定${VC_LTL_Root}宏更加任性的去加载VC-LTL。
 #
 
-if(NOT MSVC OR DEFINED __VC_LTL_CMAKE_INCLUDE_GUARD)
-    return()
-endif()
-set(__VC_LTL_CMAKE_INCLUDE_GUARD 1)
-
 #####################################################################VC-LTL设置#####################################################################
 
-#控制TargetPlatform版本,目前可用版本为5.1.2600.0     6.0.6000.0(默认)    6.2.9200.0     10.0.10240.0    10.0.19041.0
+#控制最小兼容系统版本,目前可用版本为5.1.2600.0     6.0.6000.0(默认)    6.2.9200.0     10.0.10240.0    10.0.19041.0
+#注意:VC-LTL依赖YY-Thunks,否则可能无法兼容早期系统。如果需要支持Windows XP,该值必须为5.1.2600.0。
 if(NOT DEFINED WindowsTargetPlatformMinVersion)
     set(WindowsTargetPlatformMinVersion "10.0.19041.0" CACHE STRING "" FORCE)
 endif()
 
-#启用干净的导入表,消除 ucrt apiset(如:api-ms-win-crt-time-l1-1-0.dll),满足强迫症患者。
-if(NOT DEFINED CleanImport)
-    set(CleanImport "true" CACHE STRING "" FORCE)
-endif()
+#VC-LTL使用的CRT模式,SupportLTL可能值为:
+#  * false:禁用VC_LTL
+#  * true:默认值,让VC-LTL自动适应。当最小兼容版本>=10.0时使用ucrt模式,其他系统使用msvcrt模式。
+#  * msvcrt:使用msvcrt.dll作为CRT。注意:msvcrt模式可能不完全支持所有ucrt的新功能。比如setloacl不支持UTF8。
+#  * ucrt:使用ucrtbase.dll作为CRT。注意:早期系统可能需要下载VC-LTL.Redist.Dlls.zip,感谢msvcr14x项目提供兼容XP系统的ucrtbase.dll。
+#如果需要兼容XP时也使用ucrt,请指定SupportLTL=ucrt。
+#set(SupportLTL "ucrt")
 
+#(PR#70 引入),默认关,开启后将使用cmake `INTERFACE`能力,然后单独`target_link_directories(工程名称 VC_LTL)` 即可引用
+#option(VC_LTL_EnableCMakeInterface "VC_LTL_EnableCMakeInterface" on)
 ####################################################################################################################################################
 
 if(NOT VC_LTL_Root)

+ 31 - 27
qwindowkit/qmsetup/cmake/modules/CompilerOptions.cmake

@@ -6,32 +6,27 @@ include_guard(DIRECTORY)
     qm_compiler_no_warnings()
 ]] #
 macro(qm_compiler_no_warnings)
-    if(NOT "x${CMAKE_C_FLAGS}" STREQUAL "x")
-        if(MSVC)
-            string(REGEX REPLACE "[/|-]W[0|1|2|3|4]" " " CMAKE_C_FLAGS ${CMAKE_C_FLAGS})
-        else()
-            string(REGEX REPLACE "-W[all|extra]" " " CMAKE_C_FLAGS ${CMAKE_C_FLAGS})
-            string(REGEX REPLACE "-[W]?pedantic" " " CMAKE_C_FLAGS ${CMAKE_C_FLAGS})
-        endif()
-    endif()
-    if(NOT "x${CMAKE_CXX_FLAGS}" STREQUAL "x")
-        if(MSVC)
-            string(REGEX REPLACE "[/|-]W[0|1|2|3|4]" " " CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
-        else()
-            string(REGEX REPLACE "-W[all|extra]" " " CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
-            string(REGEX REPLACE "-[W]?pedantic" " " CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
+    foreach(__lang C CXX)
+        if(NOT "x${CMAKE_${__lang}_FLAGS}" STREQUAL "x")
+            if(MSVC)
+                string(REGEX REPLACE " [/-]W[01234] " " " CMAKE_${__lang}_FLAGS ${CMAKE_${__lang}_FLAGS})
+            else()
+                string(REGEX REPLACE " -W(all)?(extra)? " " " CMAKE_${__lang}_FLAGS ${CMAKE_${__lang}_FLAGS})
+                string(REGEX REPLACE " -[W]?pedantic " " " CMAKE_${__lang}_FLAGS ${CMAKE_${__lang}_FLAGS})
+            endif()
         endif()
-    endif()
-    string(APPEND CMAKE_C_FLAGS " -w ")
-    string(APPEND CMAKE_CXX_FLAGS " -w ")
+        string(APPEND CMAKE_${__lang}_FLAGS " -w ")
+    endforeach()
     if(MSVC)
         add_compile_definitions(-D_CRT_NON_CONFORMING_SWPRINTFS)
         add_compile_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_SECURE_NO_DEPRECATE)
         add_compile_definitions(-D_CRT_NONSTDC_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE)
         add_compile_definitions(-D_SCL_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_DEPRECATE)
+        add_compile_definitions(-D_SILENCE_ALL_MS_EXT_DEPRECATION_WARNINGS)
     else()
-        string(APPEND CMAKE_C_FLAGS " -fpermissive ")
-        string(APPEND CMAKE_CXX_FLAGS " -fpermissive ")
+        foreach(__lang C CXX)
+            string(APPEND CMAKE_${__lang}_FLAGS " -fpermissive ")
+        endforeach()
     endif()
 endmacro()
 
@@ -139,6 +134,9 @@ macro(qm_compiler_enable_secure_code)
                 add_link_options(-guard:ehcont)
             endif()
         endif()
+        if(MSVC_VERSION GREATER_EQUAL 1930) # Visual Studio 2022 version 17.0
+            add_compile_options(-Qspectre-jmp)
+        endif()
     elseif(MINGW)
         if("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xClang")
             add_compile_options(-mguard=cf)
@@ -146,7 +144,10 @@ macro(qm_compiler_enable_secure_code)
         else()
         endif()
     else()
-        add_compile_options(-mshstk)
+        add_compile_options(-mshstk -ftrivial-auto-var-init=pattern
+            -fstack-protector-strong -fstack-clash-protection
+            -fcf-protection=full)
+        add_link_options(-Wl,-z,relro,-z,now)
         if("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xClang")
             add_compile_options(-mretpoline -mspeculative-load-hardening)
             if(NOT APPLE)
@@ -190,23 +191,26 @@ function(qm_compiler_enable_strict_qt)
             QT_NO_CAST_FROM_BYTEARRAY
             QT_NO_URL_CAST_FROM_STRING
             QT_NO_NARROWING_CONVERSIONS_IN_CONNECT
-            QT_NO_FOREACH
             QT_NO_JAVA_STYLE_ITERATORS
-            QT_NO_AS_CONST
-            QT_NO_QEXCHANGE
+            QT_NO_FOREACH QT_NO_QFOREACH
+            QT_NO_AS_CONST QT_NO_QASCONST
+            QT_NO_EXCHANGE QT_NO_QEXCHANGE
+            QT_NO_QPAIR
+            QT_NO_INTEGRAL_STRINGS
             QT_NO_USING_NAMESPACE
             QT_NO_CONTEXTLESS_CONNECT
             QT_EXPLICIT_QFILE_CONSTRUCTION_FROM_PATH
+            QT_USE_NODISCARD_FILE_OPEN
             QT_USE_QSTRINGBUILDER
             QT_USE_FAST_OPERATOR_PLUS
             QT_DEPRECATED_WARNINGS # Have no effect since 5.13
-            QT_DEPRECATED_WARNINGS_SINCE=0x070000 # Deprecated since 6.5
-            QT_WARN_DEPRECATED_UP_TO=0x070000 # Available since 6.5
+            QT_DEPRECATED_WARNINGS_SINCE=0x0A0000 # Deprecated since 6.5
+            QT_WARN_DEPRECATED_UP_TO=0x0A0000 # Available since 6.5
         )
         if(arg_NO_DEPRECATED_API)
             target_compile_definitions(${_target} PRIVATE
-                QT_DISABLE_DEPRECATED_BEFORE=0x070000 # Deprecated since 6.5
-                QT_DISABLE_DEPRECATED_UP_TO=0x070000 # Available since 6.5
+                QT_DISABLE_DEPRECATED_BEFORE=0x0A0000 # Deprecated since 6.5
+                QT_DISABLE_DEPRECATED_UP_TO=0x0A0000 # Available since 6.5
             )
         endif()
         # On Windows enabling this flag requires us re-compile Qt with this flag enabled,

+ 52 - 7
qwindowkit/qmsetup/cmake/modules/Deploy.cmake

@@ -195,6 +195,7 @@ endfunction()
     qm_deploy_directory(<install_dir>
         [FORCE] [STANDARD] [VERBOSE]
         [LIBRARY_DIR <dir>]
+        [EXTRA_LIBRARIES <path>...]
         [EXTRA_PLUGIN_PATHS <path>...]
         [EXTRA_SEARCHING_PATHS <path>...]
 
@@ -214,19 +215,20 @@ endfunction()
     EXTRA_PLUGIN_PATHS: Extra Qt plugins searching paths
     QML: Qt qml directories
     QML_DIR: Qt qml destination
-    LIBRARY_DIR: Extra library destination
+    LIBRARY_DIR: Library destination
+    EXTRA_LIBRARIES: Extra library names list to deploy
     EXTRA_SEARCHING_PATHS: Extra library searching paths
 ]] #
 function(qm_deploy_directory _install_dir)
     set(options FORCE STANDARD VERBOSE)
     set(oneValueArgs LIBRARY_DIR PLUGIN_DIR QML_DIR COMMENT)
-    set(multiValueArgs EXTRA_PLUGIN_PATHS PLUGINS QML WIN_TARGETS EXTRA_SEARCHING_PATHS)
+    set(multiValueArgs EXTRA_PLUGIN_PATHS PLUGINS QML WIN_TARGETS EXTRA_SEARCHING_PATHS EXTRA_LIBRARIES)
     cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
 
     # Get qmake
     if((FUNC_PLUGINS OR FUNC_QML) AND NOT DEFINED QT_QMAKE_EXECUTABLE)
         if(TARGET Qt${QT_VERSION_MAJOR}::qmake)
-            get_target_property(QT_QMAKE_EXECUTABLE Qt${QT_VERSION_MAJOR}::qmake IMPORTED_LOCATION)
+            _get_executable_location(Qt${QT_VERSION_MAJOR}::qmake QT_QMAKE_EXECUTABLE)
         elseif((FUNC_PLUGINS AND NOT FUNC_EXTRA_PLUGIN_PATHS) OR FUNC_QML)
             message(FATAL_ERROR "qm_deploy_directory: qmake not defined. Add find_package(Qt5 COMPONENTS Core) to CMake to enable.")
         endif()
@@ -245,6 +247,7 @@ function(qm_deploy_directory _install_dir)
         --libdir "${_lib_dir}"
         --qmldir "${_qml_dir}"
     )
+    set(_searching_paths)
 
     if(QT_QMAKE_EXECUTABLE)
         list(APPEND _args --qmake "${QT_QMAKE_EXECUTABLE}")
@@ -267,7 +270,8 @@ function(qm_deploy_directory _install_dir)
 
     # Add extra searching paths
     foreach(_item IN LISTS FUNC_EXTRA_SEARCHING_PATHS)
-        list(APPEND _args -L "${_item}")
+        get_filename_component(_item ${_item} ABSOLUTE)
+        list(APPEND _searching_paths ${_item})
     endforeach()
 
     # Add global extra searching paths
@@ -277,21 +281,25 @@ function(qm_deploy_directory _install_dir)
         if(QMSETUP_APPLOCAL_DEPS_PATHS_${_build_type_upper})
             foreach(_item IN LISTS QMSETUP_APPLOCAL_DEPS_PATHS_${_build_type_upper})
                 get_filename_component(_item ${_item} ABSOLUTE BASE_DIR ${CMAKE_SOURCE_DIR})
-                list(APPEND _args -L "${_item}")
+                list(APPEND _searching_paths ${_item})
             endforeach()
         elseif(QMSETUP_APPLOCAL_DEPS_PATHS)
             foreach(_item IN LISTS QMSETUP_APPLOCAL_DEPS_PATHS)
                 get_filename_component(_item ${_item} ABSOLUTE BASE_DIR ${CMAKE_SOURCE_DIR})
-                list(APPEND _args -L "${_item}")
+                list(APPEND _searching_paths ${_item})
             endforeach()
         endif()
     else()
         foreach(_item IN LISTS QMSETUP_APPLOCAL_DEPS_PATHS)
             get_filename_component(_item ${_item} ABSOLUTE BASE_DIR ${CMAKE_SOURCE_DIR})
-            list(APPEND _args -L "${_item}")
+            list(APPEND _searching_paths ${_item})
         endforeach()
     endif()
 
+    foreach(_item IN LISTS _searching_paths)
+        list(APPEND _args -L "${_item}")
+    endforeach()
+
     if(WIN32)
         set(_dep_files)
 
@@ -308,6 +316,17 @@ function(qm_deploy_directory _install_dir)
         set(_script_quoted "bash \"${QMSETUP_MODULES_DIR}/scripts/unixdeps.sh\"")
     endif()
 
+    # Add extra libraries
+    foreach(_item IN LISTS _searching_paths)
+        foreach(_lib IN LISTS FUNC_EXTRA_LIBRARIES)
+            set(_path "${_item}/${_lib}")
+
+            if((EXISTS ${_path}) AND(NOT IS_DIRECTORY ${_path}))
+                list(APPEND _args --copy ${_path} ${_lib_dir})
+            endif()
+        endforeach()
+    endforeach()
+
     # Add options
     if(FUNC_FORCE)
         list(APPEND _args "-f")
@@ -399,3 +418,29 @@ function(_qm_win_get_all_dep_files _out)
 
     set(${_out} ${_dep_files} PARENT_SCOPE)
 endfunction()
+
+function(_get_executable_location _target _var)
+    get_target_property(_path ${_target} IMPORTED_LOCATION)
+
+    if(NOT _path)
+        get_target_property(_path ${_target} IMPORTED_LOCATION_RELEASE)
+    endif()
+
+    if(NOT _path)
+        get_target_property(_path ${_target} IMPORTED_LOCATION_MINSIZEREL)
+    endif()
+
+    if(NOT _path)
+        get_target_property(_path ${_target} IMPORTED_LOCATION_RELWITHDEBINFO)
+    endif()
+
+    if(NOT _path)
+        get_target_property(_path ${_target} IMPORTED_LOCATION_DEBUG)
+    endif()
+
+    if(NOT _path)
+        message(FATAL_ERROR "Could not find imported location of target: ${_target}")
+    endif()
+
+    set(${_var} ${_path} PARENT_SCOPE)
+endfunction()

+ 29 - 4
qwindowkit/qmsetup/cmake/modules/Translate.cmake

@@ -232,7 +232,7 @@ function(_qm_add_lupdate_target _target)
     set(_my_tsfiles ${_LUPDATE_OUTPUT})
 
     add_custom_target(${_target} DEPENDS ${_lupdate_deps})
-    get_target_property(_lupdate_exe Qt${QT_VERSION_MAJOR}::lupdate IMPORTED_LOCATION)
+    _get_executable_location(Qt${QT_VERSION_MAJOR}::lupdate _lupdate_exe)
 
     set(_create_once_warning)
     set(_create_once_warning_printed off)
@@ -306,11 +306,10 @@ function(_qm_add_lupdate_target _target)
         endif()
 
         add_custom_command(
-            TARGET ${_target}
+            TARGET ${_target} POST_BUILD
             COMMAND ${_lupdate_exe}
             ARGS ${_LUPDATE_OPTIONS} "@${_ts_lst_file}" -ts ${_ts_file}
             WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
-            DEPENDS ${_my_sources}
             BYPRODUCTS ${_ts_lst_file}
             VERBATIM
         )
@@ -328,7 +327,7 @@ function(_qm_add_lrelease_target _target)
     set(_lrelease_files ${_LRELEASE_INPUT})
     set(_lrelease_deps ${_LRELEASE_DEPENDS})
 
-    get_target_property(_lrelease_exe Qt${QT_VERSION_MAJOR}::lrelease IMPORTED_LOCATION)
+    _get_executable_location(Qt${QT_VERSION_MAJOR}::lrelease _lrelease_exe)
 
     set(_qm_files)
 
@@ -367,4 +366,30 @@ function(_qm_add_lrelease_target _target)
     if(_LRELEASE_OUTPUT)
         set(${_LRELEASE_OUTPUT} ${_qm_files} PARENT_SCOPE)
     endif()
+endfunction()
+
+function(_get_executable_location _target _var)
+    get_target_property(_path ${_target} IMPORTED_LOCATION)
+
+    if(NOT _path)
+        get_target_property(_path ${_target} IMPORTED_LOCATION_RELEASE)
+    endif()
+
+    if(NOT _path)
+        get_target_property(_path ${_target} IMPORTED_LOCATION_MINSIZEREL)
+    endif()
+
+    if(NOT _path)
+        get_target_property(_path ${_target} IMPORTED_LOCATION_RELWITHDEBINFO)
+    endif()
+
+    if(NOT _path)
+        get_target_property(_path ${_target} IMPORTED_LOCATION_DEBUG)
+    endif()
+
+    if(NOT _path)
+        message(FATAL_ERROR "Could not find imported location of target: ${_target}")
+    endif()
+
+    set(${_var} ${_path} PARENT_SCOPE)
 endfunction()

+ 5 - 3
qwindowkit/qmsetup/cmake/windows/WinManifest.manifest.in

@@ -45,14 +45,16 @@
             <maxversiontested Id="10.0.19043.0"/>
             <!-- Windows 10 Version 21H2 (November 2021 Update) -->
             <maxversiontested Id="10.0.19044.0"/>
-            <!-- Windows 10 Version 22H2 (October 2022 Update) -->
+            <!-- Windows 10 Version 22H2 -->
             <maxversiontested Id="10.0.19045.0"/>
             <!-- Windows 11 Version 21H2 -->
             <maxversiontested Id="10.0.22000.0"/>
-            <!-- Windows 11 Version 22H2 (October 2022 Update) -->
+            <!-- Windows 11 Version 22H2 -->
             <maxversiontested Id="10.0.22621.0"/>
-            <!-- Windows 11 Version 23H2 (November 2023 Update) -->
+            <!-- Windows 11 Version 23H2 -->
             <maxversiontested Id="10.0.22631.0"/>
+            <!-- Windows 11 Version 24H2 -->
+            <maxversiontested Id="10.0.26100.0"/>
             <!-- Windows Vista and Windows Server 2008 -->
             <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
             <!-- Windows 7 and Windows Server 2008 R2 -->

+ 1 - 1
qwindowkit/qmsetup/include/qmsetup/qmsetup_global.h

@@ -2,7 +2,7 @@
 #define QMSETUP_GLOBAL_H
 
 // Export define
-#ifdef _MSC_VER
+#ifdef _WIN32
 #  define QMSETUP_DECL_EXPORT __declspec(dllexport)
 #  define QMSETUP_DECL_IMPORT __declspec(dllimport)
 #else

+ 5 - 0
qwindowkit/qmsetup/src/corecmd/CMakeLists.txt

@@ -29,6 +29,11 @@ set_target_properties(${PROJECT_NAME} PROPERTIES
     CXX_STANDARD_REQUIRED ON
 )
 
+# Compat with gcc 8
+if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9")
+    target_link_libraries(${PROJECT_NAME} PRIVATE stdc++fs)
+endif()
+
 if(WIN32)
     target_link_libraries(${PROJECT_NAME} PRIVATE shlwapi)
     target_compile_definitions(${PROJECT_NAME} PRIVATE _CRT_SECURE_NO_WARNINGS)

+ 2 - 2
qwindowkit/qmsetup/src/corecmd/main.cpp

@@ -169,7 +169,7 @@ static void copyDirectory(const fs::path &srcRootDir, const fs::path &srcDir,
 
             // Copy if symlink points inside the source directory
             copyFile(entryPath, destDir,
-                     linkPath.string().starts_with(srcRootDir.string())
+                     Utils::starts_with(linkPath.string(), srcRootDir.string())
                          ? fs::relative(linkPath, fs::canonical(entryPath.parent_path())).string()
                          : std::string(),
                      force, verbose);
@@ -192,7 +192,7 @@ static std::string standardError(int code = errno) {
 
 #ifdef __APPLE__
 static inline bool isFramework(const fs::path &path) {
-    return path.extension() == ".framework";
+    return Utils::toLower(path.extension().string()) == ".framework";
 }
 
 static fs::path lib2framework(fs::path path, const fs::path &fallback = {}) {

+ 16 - 2
qwindowkit/qmsetup/src/syscmdline/include/syscmdline/argument.h

@@ -40,6 +40,11 @@ namespace SysCmdLine {
     class SYSCMDLINE_EXPORT Argument : public Symbol {
         SYSCMDLINE_DECL_PRIVATE(Argument)
     public:
+        enum Number {
+            Single,
+            MultiValue,
+            Remainder,
+        };
         using Validator = std::function<bool /* result */ (
             const std::string & /* token */, Value * /* out */, std::string * /* errorMessage */)>;
 
@@ -72,7 +77,10 @@ namespace SysCmdLine {
         void setExpectedValues(const std::vector<Value> &expectedValues);
 
         bool multiValueEnabled() const;
-        void setMultiValueEnabled(bool on);
+        SYSCMDLINE_DECL_DEPRECATED void setMultiValueEnabled(bool on);
+
+        Number number() const;
+        void setNumber(Number valuePolicy);
 
         Validator validator() const;
         void setValidator(const Validator &validator);
@@ -82,8 +90,9 @@ namespace SysCmdLine {
         inline Argument &required(bool required = true);
         inline Argument &default_value(const Value &value);
         inline Argument &expect(const std::vector<Value> &expectedValues);
-        inline Argument &multi(bool multiValueEnabled = true);
+        SYSCMDLINE_DECL_DEPRECATED inline Argument &multi(bool multiValueEnabled = true);
         inline Argument &validate(const Validator &validator);
+        inline Argument &nargs(Number valuePolicy);
     };
 
     inline bool Argument::isOptional() const {
@@ -124,6 +133,11 @@ namespace SysCmdLine {
         return *this;
     }
 
+    inline Argument &Argument::nargs(const Argument::Number valuePolicy) {
+        setNumber(valuePolicy);
+        return *this;
+    }
+
     class ArgumentHolderPrivate;
 
     class SYSCMDLINE_EXPORT ArgumentHolder : public Symbol {

+ 7 - 1
qwindowkit/qmsetup/src/syscmdline/include/syscmdline/global.h

@@ -28,7 +28,7 @@
 #define GLOBAL_H
 
 // Export define
-#ifdef _MSC_VER
+#ifdef _WIN32
 #  define SYSCMDLINE_DECL_EXPORT __declspec(dllexport)
 #  define SYSCMDLINE_DECL_IMPORT __declspec(dllimport)
 #else
@@ -46,6 +46,12 @@
 #  endif
 #endif
 
+#ifdef _WIN32
+#  define SYSCMDLINE_DECL_DEPRECATED __declspec(deprecated)
+#else
+#  define SYSCMDLINE_DECL_DEPRECATED __attribute__((__deprecated__))
+#endif
+
 // Utils
 #define SYSCMDLINE_UNUSED(X) (void) X;
 

+ 0 - 1
qwindowkit/qmsetup/src/syscmdline/include/syscmdline/sharedbase.h

@@ -30,7 +30,6 @@
 #include <utility>
 
 #include <syscmdline/global.h>
-
 namespace SysCmdLine {
 
     class SharedBasePrivate;

+ 14 - 4
qwindowkit/qmsetup/src/syscmdline/src/argument.cpp

@@ -17,7 +17,7 @@ namespace SysCmdLine {
     ArgumentPrivate::ArgumentPrivate(std::string name, const std::string &desc, bool required,
                                      Value defaultValue)
         : SymbolPrivate(Symbol::ST_Argument, desc), name(std::move(name)), required(required),
-          defaultValue(std::move(defaultValue)), multiple(false) {
+          defaultValue(std::move(defaultValue)), number(Argument::Single) {
     }
 
     SymbolPrivate *ArgumentPrivate::clone() const {
@@ -47,7 +47,7 @@ namespace SysCmdLine {
 
         switch (pos) {
             case Symbol::HP_Usage: {
-                return displayedText() + (d->multiple ? "..." : "");
+                return displayedText() + ((d->number != Single) ? "..." : "");
             }
             case Symbol::HP_SecondColumn: {
                 auto textProvider = reinterpret_cast<Parser::TextProvider>(extra);
@@ -148,12 +148,22 @@ namespace SysCmdLine {
 
     bool Argument::multiValueEnabled() const {
         Q_D2(Argument);
-        return d->multiple;
+        return d->number != Single;
     }
 
     void Argument::setMultiValueEnabled(bool on) {
         Q_D(Argument);
-        d->multiple = on;
+        d->number = on ? MultiValue : Single;
+    }
+
+    Argument::Number Argument::number() const {
+        Q_D2(Argument);
+        return d->number;
+    }
+
+    void Argument::setNumber(Number number) {
+        Q_D(Argument);
+        d->number = number;
     }
 
     Argument::Validator Argument::validator() const {

+ 1 - 1
qwindowkit/qmsetup/src/syscmdline/src/argument_p.h

@@ -19,7 +19,7 @@ namespace SysCmdLine {
         Value defaultValue;
         std::vector<Value> expectedValues;
         std::string displayName;
-        bool multiple;
+        Argument::Number number;
         Argument::Validator validator;
     };
 

+ 10 - 1
qwindowkit/qmsetup/src/syscmdline/src/parser.cpp

@@ -372,8 +372,17 @@ namespace SysCmdLine {
                                 break;
                             }
 
+                            bool hasRemainder =
+                                optData.multiValueArgIndex >= 0 &&
+                                dd->arguments[optData.multiValueArgIndex].number() ==
+                                    Argument::Remainder;
                             auto end = std::min(params.size(), i + maxArgCount + 1);
-                            for (; j < end; ++j) {
+                            for (int argIndex = minArgCount; j < end; ++j, ++argIndex) {
+                                if (hasRemainder && argIndex >= optData.multiValueArgIndex) {
+                                    j = end;
+                                    break;
+                                }
+
                                 const auto &curToken = params[j];
 
                                 // Break at next option

+ 3 - 7
qwindowkit/src/CMakeLists.txt

@@ -1,9 +1,5 @@
 qm_import(Preprocess)
 
-string(TIMESTAMP _current_year "%Y")
-
-set(QWINDOWKIT_PROJECT_DESCRIPTION "Cross-platform window customization framework")
-set(QWINDOWKIT_PROJECT_COPYRIGHT "Copyright 2023-${_current_year} Stdware Collections")
 set(QWINDOWKIT_GENERATED_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/../include)
 set(QWINDOWKIT_BUILD_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/../etc/include)
 
@@ -60,8 +56,8 @@ macro(qwk_add_library _target)
     if(WIN32 AND NOT FUNC_NO_WIN_RC AND(${_type} STREQUAL "SHARED"))
         qm_add_win_rc(${_target}
             NAME ${QWINDOWKIT_INSTALL_NAME}
-            DESCRIPTION ${QWINDOWKIT_PROJECT_DESCRIPTION}
-            COPYRIGHT ${QWINDOWKIT_PROJECT_COPYRIGHT}
+            DESCRIPTION ${QWINDOWKIT_DESCRIPTION}
+            COPYRIGHT ${QACTIONKIT_COPYRIGHT}
         )
     endif()
 
@@ -164,7 +160,7 @@ if(QWINDOWKIT_BUILD_DOCUMENTATIONS)
     qm_import(Doxygen)
     qm_setup_doxygen(QWindowKit_RunDoxygen
         NAME "QWindowKit"
-        DESCRIPTION "${QWINDOWKIT_PROJECT_DESCRIPTION}"
+        DESCRIPTION "${QWINDOWKIT_DESCRIPTION}"
         MDFILE ../README.md
         OUTPUT_DIR ${CMAKE_BUILD_SHARE_DIR}/doc/${QWINDOWKIT_INSTALL_NAME}
         INPUT ${QWINDOWKIT_ENABLED_SUBDIRECTORIES}

+ 15 - 64
qwindowkit/src/core/contexts/abstractwindowcontext.cpp

@@ -32,21 +32,16 @@ namespace QWK {
             return false;
         }
 
-        auto it = m_hitTestVisibleItems.find(obj);
         if (visible) {
-            if (it != m_hitTestVisibleItems.end()) {
-                return true;
-            }
-            connect(obj, &QObject::destroyed, this,
-                    &AbstractWindowContext::_q_hitTestVisibleItemDestroyed);
-            m_hitTestVisibleItems.insert(obj);
+            m_hitTestVisibleItems.removeAll(nullptr);
+            m_hitTestVisibleItems.removeAll(obj);
+            m_hitTestVisibleItems.append(obj);
         } else {
-            if (it == m_hitTestVisibleItems.end()) {
-                return false;
+            for (auto &item : m_hitTestVisibleItems) {
+                if (item == obj) {
+                    item = nullptr;
+                }
             }
-            disconnect(obj, &QObject::destroyed, this,
-                       &AbstractWindowContext::_q_hitTestVisibleItemDestroyed);
-            m_hitTestVisibleItems.erase(it);
         }
         return true;
     }
@@ -60,16 +55,7 @@ namespace QWK {
 
         auto org = m_systemButtons[button];
         if (org == obj) {
-            return true;
-        }
-
-        if (org) {
-            disconnect(org, &QObject::destroyed, this,
-                       &AbstractWindowContext::_q_systemButtonDestroyed);
-        }
-        if (obj) {
-            connect(obj, &QObject::destroyed, this,
-                    &AbstractWindowContext::_q_systemButtonDestroyed);
+            return false;
         }
         m_systemButtons[button] = obj;
         return true;
@@ -85,11 +71,6 @@ namespace QWK {
         if (org) {
             // Since the title bar is changed, all items inside it should be dereferenced right away
             removeSystemButtonsAndHitTestItems();
-            disconnect(org, &QObject::destroyed, this,
-                       &AbstractWindowContext::_q_titleBarDistroyed);
-        }
-        if (item) {
-            connect(item, &QObject::destroyed, this, &AbstractWindowContext::_q_titleBarDistroyed);
         }
         m_titleBar = item;
         return true;
@@ -107,8 +88,7 @@ namespace QWK {
         *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)) {
+            if (!currentButton || !m_delegate->isVisible(currentButton)) {
                 continue;
             }
             if (m_delegate->mapGeometryToScene(currentButton).contains(pos)) {
@@ -141,22 +121,17 @@ namespace QWK {
             return false;
         }
 
-        for (int i = WindowAgentBase::WindowIcon; i <= WindowAgentBase::Close; ++i) {
-            auto currentButton = m_systemButtons[i];
-            if (currentButton && m_delegate->isVisible(currentButton) &&
-                m_delegate->isEnabled(currentButton) &&
-                m_delegate->mapGeometryToScene(currentButton).contains(pos)) {
-                return false;
-            }
+        WindowAgentBase::SystemButton button;
+        if (isInSystemButtons(pos, &button)) {
+            return false;
         }
 
-        for (auto widget : m_hitTestVisibleItems) {
-            if (widget && m_delegate->isVisible(widget) && m_delegate->isEnabled(widget) &&
-                m_delegate->mapGeometryToScene(widget).contains(pos)) {
+        for (auto item : m_hitTestVisibleItems) {
+            if (item && m_delegate->isVisible(item) &&
+                m_delegate->mapGeometryToScene(item).contains(pos)) {
                 return false;
             }
         }
-
         return true;
     }
 
@@ -315,33 +290,9 @@ namespace QWK {
             if (!button) {
                 continue;
             }
-            disconnect(button, &QObject::destroyed, this,
-                       &AbstractWindowContext::_q_systemButtonDestroyed);
             button = nullptr;
         }
-        for (auto &item : m_hitTestVisibleItems) {
-            disconnect(item, &QObject::destroyed, this,
-                       &AbstractWindowContext::_q_hitTestVisibleItemDestroyed);
-        }
         m_hitTestVisibleItems.clear();
     }
 
-    void AbstractWindowContext::_q_titleBarDistroyed(QObject *obj) {
-        Q_UNUSED(obj)
-        removeSystemButtonsAndHitTestItems();
-        m_titleBar = nullptr;
-    }
-
-    void AbstractWindowContext::_q_hitTestVisibleItemDestroyed(QObject *obj) {
-        m_hitTestVisibleItems.remove(obj);
-    }
-
-    void AbstractWindowContext::_q_systemButtonDestroyed(QObject *obj) {
-        for (auto &item : m_systemButtons) {
-            if (item == obj) {
-                item = nullptr;
-            }
-        }
-    }
-
 }

+ 4 - 9
qwindowkit/src/core/contexts/abstractwindowcontext_p.h

@@ -117,13 +117,13 @@ namespace QWK {
         QPointer<QWindow> m_windowHandle;
         WId m_windowId{};
 
-        QSet<const QObject *> m_hitTestVisibleItems;
+        QVector<QPointer<QObject>> m_hitTestVisibleItems;
 #ifdef Q_OS_MAC
         ScreenRectCallback m_systemButtonAreaCallback;
 #endif
 
-        QObject *m_titleBar{};
-        std::array<QObject *, WindowAgentBase::Close + 1> m_systemButtons{};
+        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;
@@ -131,11 +131,6 @@ namespace QWK {
         std::unique_ptr<WinIdChangeEventFilter> m_winIdChangeEventFilter;
 
         void removeSystemButtonsAndHitTestItems();
-
-    private:
-        void _q_titleBarDistroyed(QObject *obj);
-        void _q_hitTestVisibleItemDestroyed(QObject *obj);
-        void _q_systemButtonDestroyed(QObject *obj);
     };
 
     inline QObject *AbstractWindowContext::host() const {
@@ -155,7 +150,7 @@ namespace QWK {
     }
 
     inline bool AbstractWindowContext::isHitTestVisible(const QObject *obj) const {
-        return m_hitTestVisibleItems.contains(obj);
+        return m_hitTestVisibleItems.contains(const_cast<QObject *>(obj));
     }
 
     inline QObject *

+ 103 - 27
qwindowkit/src/core/contexts/cocoawindowcontext.mm

@@ -23,10 +23,6 @@ namespace QWK {
 
     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 {
@@ -87,7 +83,8 @@ public:
 
 - (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);
     }
@@ -95,7 +92,8 @@ public:
 
 - (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);
     }
@@ -103,7 +101,8 @@ public:
 
 - (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);
     }
@@ -111,7 +110,8 @@ public:
 
 - (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);
     }
@@ -119,7 +119,8 @@ public:
 
 - (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);
     }
@@ -127,6 +128,10 @@ public:
 
 @end
 
+@interface QWK_NSViewObserver : NSObject
+- (instancetype)initWithProxy:(QWK::NSWindowProxy*)proxy;
+@end
+
 //
 // Objective C++ End
 //
@@ -140,13 +145,19 @@ namespace QWK {
             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
@@ -213,6 +224,9 @@ namespace QWK {
         }
 
         void updateSystemButtonRect() {
+            if (!screenRectCallback || !systemButtonVisible) {
+                return;
+            }
             const auto &buttons = systemButtons();
             const auto &leftButton = buttons[0];
             const auto &midButton = buttons[1];
@@ -225,8 +239,7 @@ namespace QWK {
             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
@@ -256,6 +269,10 @@ namespace QWK {
         }
 
         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];
@@ -263,6 +280,10 @@ namespace QWK {
         }
 
         inline int titleBarHeight() const {
+            auto nswindow = [nsview window];
+            if (!nswindow) {
+                return 0;
+            }
             NSButton *closeBtn = [nswindow standardWindowButton:NSWindowCloseButton];
             return closeBtn.superview.frame.size.height;
         }
@@ -274,8 +295,7 @@ namespace QWK {
                 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);
                 }
@@ -311,8 +331,8 @@ namespace QWK {
 
         // System title bar
         void setSystemTitleBarVisible(const bool visible) {
-            NSView *nsview = [nswindow contentView];
-            if (!nsview) {
+            auto nswindow = [nsview window];
+            if (!nswindow) {
                 return;
             }
 
@@ -396,7 +416,9 @@ namespace QWK {
 
     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 @@ namespace QWK {
         }
 
         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 @@ namespace QWK {
         }
 
         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 @@ namespace QWK {
         }
 
         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 @@ namespace QWK {
     private:
         Q_DISABLE_COPY(NSWindowProxy)
 
-        NSWindow *nswindow = nil;
+        NSView *nsview = nil;
+        QWK_NSViewObserver* observer = nil;
 
         bool systemButtonVisible = true;
         ScreenRectCallback screenRectCallback;
@@ -500,8 +529,8 @@ namespace QWK {
 
         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();
@@ -701,7 +730,12 @@ namespace QWK {
         }
 
         // Allocate new resources
-        ensureWindowProxy(winId)->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,
@@ -711,7 +745,11 @@ namespace QWK {
         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;
@@ -719,14 +757,22 @@ namespace QWK {
 
         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;
@@ -746,3 +792,33 @@ namespace QWK {
     }
 
 }
+
+@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

+ 24 - 8
qwindowkit/src/core/contexts/win32windowcontext.cpp

@@ -37,6 +37,14 @@
 #  error Current Qt version has a critical bug which will break QWK functionality. Please upgrade to > 6.6.1 or downgrade to < 6.6.0
 #endif
 
+#ifndef DWM_BB_ENABLE
+#  define DWM_BB_ENABLE 0x00000001
+#endif
+
+#ifndef ABM_GETAUTOHIDEBAREX
+#  define ABM_GETAUTOHIDEBAREX 0x0000000b
+#endif
+
 namespace QWK {
 
     enum IconButtonClickLevelFlag {
@@ -920,13 +928,8 @@ namespace QWK {
 
         const DynamicApis &apis = DynamicApis::instance();
         const auto &extendMargins = [this, &apis, hwnd]() {
-            // For some unknown reason, the window background is totally black when the host object
-            // is a QWidget. And extending the window frame into the client area seems to fix it
-            // magically.
-            // We don't need the following *HACK* for QtQuick windows.
-            if (!m_host->isWidgetType()) {
-                return;
-            }
+            // For some unknown reason, the window background is totally black and extending
+            // the window frame into the client area seems to fix it magically.
             // After many times of trying, we found that the Acrylic/Mica/Mica Alt background
             // only appears on the native Win32 window's background, so naturally we want to
             // extend the window frame into the whole client area to be able to let the special
@@ -955,6 +958,7 @@ namespace QWK {
 
         const auto &effectBugWorkaround = [this, hwnd]() {
             // We don't need the following *HACK* for QWidget windows.
+            // Completely based on actual experiments, root reason is totally unknown.
             if (m_host->isWidgetType()) {
                 return;
             }
@@ -1905,6 +1909,18 @@ namespace QWK {
                 break;
             }
 
+            case WM_SHOWWINDOW: {
+                if (!wParam || !isWindowNoState(hWnd) || isFullScreen(hWnd)) {
+                    break;
+                }
+                RECT windowRect{};
+                ::GetWindowRect(hWnd, &windowRect);
+                static constexpr const auto swpFlags = SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED | SWP_NOOWNERZORDER;
+                ::SetWindowPos(hWnd, nullptr, 0, 0, RECT_WIDTH(windowRect) + 1, RECT_HEIGHT(windowRect) + 1, swpFlags);
+                ::SetWindowPos(hWnd, nullptr, 0, 0, RECT_WIDTH(windowRect), RECT_HEIGHT(windowRect), swpFlags);
+                break;
+            }
+
             default:
                 break;
         }
@@ -2325,7 +2341,7 @@ namespace QWK {
                 if (!mouseHook) {
                     mouseHook = ::SetWindowsHookExW(
                         WH_MOUSE,
-                        [](int nCode, WPARAM wParam, LPARAM lParam) {
+                        [](int nCode, WPARAM wParam, LPARAM lParam) -> LRESULT {
                             if (nCode >= 0) {
                                 switch (wParam) {
                                     case WM_LBUTTONDBLCLK:

+ 8 - 0
qwindowkit/src/core/qwindowkit_windows.h

@@ -5,6 +5,14 @@
 #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>
 

+ 7 - 0
qwindowkit/src/core/qwkglobal.h

@@ -34,6 +34,13 @@ using QT_ENTER_EVENT_TYPE = QEvent;
 #  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 &)>;

+ 39 - 7
qwindowkit/src/core/shared/qwkwindowsextra_p.h

@@ -14,10 +14,6 @@
 // version without notice, or may even be removed.
 //
 
-#include <shellscalingapi.h>
-#include <dwmapi.h>
-#include <timeapi.h>
-
 #include <core/qwindowkit_windows.h>
 
 #include <QtCore/QtMath>
@@ -29,6 +25,42 @@
 
 // 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 {
@@ -160,7 +192,7 @@ namespace QWK {
     namespace {
 
         struct DynamicApis {
-            static const DynamicApis &instance() {
+            static inline const DynamicApis &instance() {
                 static const DynamicApis inst;
                 return inst;
             }
@@ -188,7 +220,7 @@ namespace QWK {
             SetPreferredAppModePtr pSetPreferredAppMode = nullptr;
 
         private:
-            DynamicApis() {
+            inline DynamicApis() {
 #define DYNAMIC_API_RESOLVE(DLL, NAME)                                                             \
     p##NAME = reinterpret_cast<decltype(p##NAME)>(DLL.resolve(#NAME))
 
@@ -224,7 +256,7 @@ namespace QWK {
 #undef UNDOC_API_RESOLVE
             }
 
-            ~DynamicApis() = default;
+            inline ~DynamicApis() = default;
 
             Q_DISABLE_COPY(DynamicApis)
         };

+ 17 - 5
qwindowkit/src/core/shared/systemwindow_p.h

@@ -24,13 +24,17 @@ namespace QWK {
     class WindowMoveManipulator : public QObject {
     public:
         explicit WindowMoveManipulator(QWindow *targetWindow)
-            : QObject(targetWindow), target(targetWindow), initialMousePosition(QCursor::pos()),
+            : 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);
@@ -43,8 +47,9 @@ namespace QWK {
                     if (target->y() < 0) {
                         target->setPosition(target->x(), 0);
                     }
-                    target->removeEventFilter(this);
+                    operationComplete = true;
                     deleteLater();
+                    break;
                 }
 
                 default:
@@ -55,6 +60,7 @@ namespace QWK {
 
     private:
         QWindow *target;
+        bool operationComplete;
         QPoint initialMousePosition;
         QPoint initialWindowPosition;
     };
@@ -62,13 +68,17 @@ namespace QWK {
     class WindowResizeManipulator : public QObject {
     public:
         WindowResizeManipulator(QWindow *targetWindow, Qt::Edges edges)
-            : QObject(targetWindow), target(targetWindow), resizeEdges(edges),
-              initialMousePosition(QCursor::pos()), initialWindowRect(target->geometry()) {
+            : 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);
@@ -97,8 +107,9 @@ namespace QWK {
                 }
 
                 case QEvent::MouseButtonRelease: {
-                    target->removeEventFilter(this);
+                    operationComplete = true;
                     deleteLater();
+                    break;
                 }
 
                 default:
@@ -109,6 +120,7 @@ namespace QWK {
 
     private:
         QWindow *target;
+        bool operationComplete;
         QPoint initialMousePosition;
         QRect initialWindowRect;
         Qt::Edges resizeEdges;

+ 22 - 5
qwindowkit/src/core/shared/windows10borderhandler_p.h

@@ -31,10 +31,12 @@ namespace QWK {
         }
 
         inline void setupNecessaryAttributes() {
-            // 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);
+            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);
@@ -45,7 +47,17 @@ namespace QWK {
                      (Qt::WindowMinimized | Qt::WindowMaximized | Qt::WindowFullScreen));
         }
 
-        inline void drawBorder() {
+        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);
         }
 
@@ -54,6 +66,11 @@ namespace QWK {
         }
 
         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));

+ 1 - 1
qwindowkit/src/core/style/styleagent_win.cpp

@@ -98,4 +98,4 @@ namespace QWK {
         }
     }
 
-}
+}

+ 1 - 1
qwindowkit/src/core/windowagentbase.h

@@ -47,4 +47,4 @@ namespace QWK {
 
 }
 
-#endif // WINDOWAGENTBASE_H
+#endif // WINDOWAGENTBASE_H

+ 12 - 11
qwindowkit/src/quick/quickwindowagent_win.cpp

@@ -47,7 +47,11 @@ namespace QWK {
 
     bool BorderItem::shouldEnableEmulatedPainter() const {
 #  if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
-        auto api = window()->rendererInterface()->graphicsApi();
+        const QQuickWindow* win = window();
+        if (!win) {
+            return true;
+        }
+        auto api = win->rendererInterface()->graphicsApi();
         switch (api) {
             case QSGRendererInterface::OpenGL:
                 // FIXME: experimental, try to find the exact fixed version.
@@ -97,21 +101,18 @@ namespace QWK {
     BorderItem::~BorderItem() = default;
 
     void BorderItem::updateGeometry() {
-        setHeight(borderThickness());
+        const QQuickWindow* win = window();
+        if (!win) {
+            return;
+        }
+        setHeight(borderThickness() / win->effectiveDevicePixelRatio());
         setVisible(isNormalWindow());
     }
 
     void BorderItem::paint(QPainter *painter) {
         Q_UNUSED(painter)
         if (shouldEnableEmulatedPainter()) {
-            QRect rect(QPoint(0, 0), size().toSize());
-            QRegion region(rect);
-            void *args[] = {
-                painter,
-                &rect,
-                &region,
-            };
-            ctx->virtual_hook(AbstractWindowContext::DrawWindows10BorderHook_Emulated, args);
+            drawBorderEmulated(painter, QRect({0, 0}, size().toSize()));
         } else {
             needPaint = true;
         }
@@ -170,7 +171,7 @@ namespace QWK {
     void BorderItem::_q_afterSynchronizing() {
         if (needPaint) {
             needPaint = false;
-            drawBorder();
+            drawBorderNative();
         }
     }
 

+ 0 - 1
qwindowkit/src/widgets/widgetitemdelegate_p.h

@@ -20,7 +20,6 @@
 #include <core/windowitemdelegate_p.h>
 
 #include <widgets/qwkwidgetsglobal.h>
-
 namespace QWK {
 
     class QWK_WIDGETS_EXPORT WidgetItemDelegate : public WindowItemDelegate {

+ 1 - 1
qwindowkit/src/widgets/widgetwindowagent.cpp

@@ -61,7 +61,7 @@ namespace QWK {
         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: Remove
+        //w->setAttribute(Qt::WA_NativeWindow); // ### FIXME: Check
 
         d->setup(w, new WidgetItemDelegate());
         d->hostWidget = w;

+ 2 - 2
qwindowkit/src/widgets/widgetwindowagent_win.cpp

@@ -83,7 +83,7 @@ namespace QWK {
             // 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.
-            drawBorder();
+            drawBorderNative();
         }
 
         inline void forwardEventToWindowAndDraw(QWindow *window, QEvent *event) {
@@ -97,7 +97,7 @@ namespace QWK {
 
             // 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.
-            drawBorder();
+            drawBorderNative();
         }
 
     protected: