Add cross-platform power monitor and macOS Vulkan
This commit is contained in:
parent
ad3524bd23
commit
f62f429158
|
|
@ -75,7 +75,11 @@ set(VULKAN_FOUND FALSE)
|
|||
|
||||
if(ENABLE_VULKAN)
|
||||
# Set Vulkan headers path
|
||||
set(VULKAN_HEADERS_DIR "$ENV{HOME}/sdk/Vulkan-Headers-1.3.302/include")
|
||||
if(APPLE)
|
||||
set(VULKAN_HEADERS_DIR "$ENV{HOME}/VulkanSDK/1.4.328.1/macOS/include")
|
||||
elseif(LINUX)
|
||||
set(VULKAN_HEADERS_DIR "$ENV{HOME}/sdk/Vulkan-Headers-1.3.302/include")
|
||||
endif()
|
||||
|
||||
# Check if Vulkan headers exist
|
||||
if(EXISTS "${VULKAN_HEADERS_DIR}/vulkan/vulkan.h")
|
||||
|
|
@ -145,6 +149,7 @@ set(SOURCES
|
|||
src/screenlockdetector.cpp
|
||||
src/screenlockdetector_base.cpp
|
||||
src/powermonitor.cpp
|
||||
src/powermonitor_base.cpp
|
||||
)
|
||||
|
||||
set(HEADERS
|
||||
|
|
@ -154,6 +159,7 @@ set(HEADERS
|
|||
src/screenlockdetector.h
|
||||
src/screenlockdetector_base.h
|
||||
src/powermonitor.h
|
||||
src/powermonitor_base.h
|
||||
)
|
||||
|
||||
# Add Vulkan widget if Vulkan is available
|
||||
|
|
@ -174,14 +180,20 @@ if(APPLE)
|
|||
# macOS specific files
|
||||
list(APPEND SOURCES
|
||||
src/screenlockdetector_macos.mm
|
||||
src/powermonitor_macos.mm
|
||||
src/vulkanwidget_macos.mm
|
||||
)
|
||||
list(APPEND HEADERS
|
||||
src/screenlockdetector_macos.h
|
||||
src/powermonitor_macos.h
|
||||
src/vulkanwidget_macos.h
|
||||
)
|
||||
|
||||
# Enable Objective-C++ for .mm files
|
||||
set_source_files_properties(
|
||||
src/screenlockdetector_macos.mm
|
||||
src/powermonitor_macos.mm
|
||||
src/vulkanwidget_macos.mm
|
||||
PROPERTIES
|
||||
COMPILE_FLAGS "-x objective-c++"
|
||||
)
|
||||
|
|
@ -189,9 +201,11 @@ elseif(UNIX)
|
|||
# Linux specific files
|
||||
list(APPEND SOURCES
|
||||
src/screenlockdetector_linux.cpp
|
||||
src/powermonitor_linux.cpp
|
||||
)
|
||||
list(APPEND HEADERS
|
||||
src/screenlockdetector_linux.h
|
||||
src/powermonitor_linux.h
|
||||
)
|
||||
endif()
|
||||
|
||||
|
|
|
|||
14
run_mac.sh
14
run_mac.sh
|
|
@ -21,6 +21,20 @@ if [[ "$OSTYPE" != "darwin"* ]]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
# Set up Vulkan SDK environment for MoltenVK
|
||||
VULKAN_SDK="$HOME/VulkanSDK/1.4.328.1/macOS"
|
||||
if [ -d "$VULKAN_SDK" ]; then
|
||||
echo -e "${GREEN}Found Vulkan SDK at: $VULKAN_SDK${NC}"
|
||||
export VULKAN_SDK
|
||||
export DYLD_LIBRARY_PATH="$VULKAN_SDK/lib:$DYLD_LIBRARY_PATH"
|
||||
export VK_ICD_FILENAMES="$VULKAN_SDK/share/vulkan/icd.d/MoltenVK_icd.json"
|
||||
export VK_ADD_LAYER_PATH="$VULKAN_SDK/share/vulkan/explicit_layer.d"
|
||||
else
|
||||
echo -e "${YELLOW}Warning: Vulkan SDK not found at $VULKAN_SDK${NC}"
|
||||
echo "Vulkan rendering may not work. Install from: https://vulkan.lunarg.com/sdk/home"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Check if executable exists
|
||||
EXECUTABLE="build/bin/ScreenLockDetector"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,54 +1,50 @@
|
|||
#include "powermonitor.h"
|
||||
|
||||
void PowerMonitor::initLogin1Dbus() {
|
||||
// 连接 org.freedesktop.login1 服务(系统总线)
|
||||
m_login1Interface = new QDBusInterface(
|
||||
"org.freedesktop.login1", // DBus 服务名
|
||||
"/org/freedesktop/login1", // 对象路径
|
||||
"org.freedesktop.login1.Manager", // 接口名
|
||||
QDBusConnection::systemBus(), // 系统总线(关键!不是会话总线)
|
||||
this
|
||||
);
|
||||
#ifdef Q_OS_LINUX
|
||||
#include "powermonitor_linux.h"
|
||||
#elif defined(Q_OS_MAC)
|
||||
#include "powermonitor_macos.h"
|
||||
#endif
|
||||
|
||||
// 检查接口是否有效
|
||||
if (m_login1Interface->isValid()) {
|
||||
// 监听 PrepareForSleep 信号(签名:b = bool)
|
||||
bool connectOk = QDBusConnection::systemBus().connect(
|
||||
"org.freedesktop.login1", // 服务名
|
||||
"/org/freedesktop/login1", // 对象路径
|
||||
"org.freedesktop.login1.Manager", // 接口名
|
||||
"PrepareForSleep", // 信号名
|
||||
this,
|
||||
SLOT(onPrepareForSleep(bool)) // 接收信号的槽函数
|
||||
);
|
||||
#include <QDebug>
|
||||
|
||||
if (connectOk) {
|
||||
qDebug() << "=================================================";
|
||||
qDebug() << "✅ 成功连接 login1 电源管理接口";
|
||||
qDebug() << "=================================================";
|
||||
} else {
|
||||
qDebug() << "=================================================";
|
||||
qDebug() << "❌ 连接 login1 信号失败:" << QDBusConnection::systemBus().lastError().message();
|
||||
qDebug() << "=================================================";
|
||||
}
|
||||
} else {
|
||||
qDebug() << "=================================================";
|
||||
qDebug() << "❌ 无法创建 login1 接口:" << m_login1Interface->lastError().message();
|
||||
qDebug() << "=================================================";
|
||||
PowerMonitor::PowerMonitor(QObject *parent)
|
||||
: PowerMonitorBase(parent)
|
||||
, m_impl(nullptr)
|
||||
{
|
||||
#ifdef Q_OS_LINUX
|
||||
m_impl = new PowerMonitorLinux(this);
|
||||
qDebug() << "创建 Linux 电源监视器实例";
|
||||
#elif defined(Q_OS_MAC)
|
||||
m_impl = new PowerMonitorMacOS(this);
|
||||
qDebug() << "创建 macOS 电源监视器实例";
|
||||
#else
|
||||
qWarning() << "警告:当前平台不支持电源监视功能";
|
||||
#endif
|
||||
|
||||
if (m_impl) {
|
||||
// 转发平台实现的信号到工厂类
|
||||
connect(m_impl, &PowerMonitorBase::aboutToSleep,
|
||||
this, &PowerMonitor::aboutToSleep);
|
||||
connect(m_impl, &PowerMonitorBase::aboutToWakeUp,
|
||||
this, &PowerMonitor::aboutToWakeUp);
|
||||
connect(m_impl, &PowerMonitorBase::powerStateChanged,
|
||||
this, &PowerMonitor::powerStateChanged);
|
||||
|
||||
// 自动初始化
|
||||
m_impl->initialize();
|
||||
}
|
||||
}
|
||||
|
||||
// 槽函数:处理睡眠/唤醒事件
|
||||
void PowerMonitor::onPrepareForSleep(bool enteringSleep) {
|
||||
if (enteringSleep) {
|
||||
// 系统即将进入 休眠/待机 状态
|
||||
qDebug() << "=================================================";
|
||||
qDebug() << "\n📥 系统即将进入睡眠(休眠/待机)";
|
||||
emit aboutToSleep();
|
||||
} else {
|
||||
// 系统从睡眠中唤醒
|
||||
qDebug() << "📤 系统已从睡眠中唤醒\n";
|
||||
qDebug() << "=================================================";
|
||||
emit aboutToWakeUp();
|
||||
}
|
||||
PowerMonitor::~PowerMonitor()
|
||||
{
|
||||
// m_impl 会被 Qt 的父对象机制自动删除
|
||||
}
|
||||
|
||||
bool PowerMonitor::initialize()
|
||||
{
|
||||
if (m_impl) {
|
||||
return m_impl->initialize();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1,39 +1,38 @@
|
|||
#pragma once
|
||||
#include <QObject>
|
||||
#include <QDBusInterface>
|
||||
#include <QDBusConnection>
|
||||
#include <QDebug>
|
||||
#ifndef POWERMONITOR_H
|
||||
#define POWERMONITOR_H
|
||||
|
||||
class PowerMonitor : public QObject
|
||||
#include "powermonitor_base.h"
|
||||
|
||||
/**
|
||||
* @brief 跨平台电源监视器工厂类
|
||||
*
|
||||
* 根据编译平台自动选择合适的电源监视器实现
|
||||
* 提供统一的接口用于监听系统睡眠/唤醒事件
|
||||
*/
|
||||
class PowerMonitor : public PowerMonitorBase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PowerMonitor(QObject *parent = nullptr) : QObject(parent) { initLogin1Dbus(); }
|
||||
|
||||
signals:
|
||||
/*
|
||||
* @brief 发送即将唤醒的信号
|
||||
/**
|
||||
* @brief 创建平台特定的电源监视器实例
|
||||
* @param parent 父对象
|
||||
*
|
||||
* 根据编译时的平台宏自动创建对应的实现:
|
||||
* - Linux: PowerMonitorLinux (使用 DBus login1)
|
||||
* - macOS: PowerMonitorMacOS (使用 NSWorkspace 通知)
|
||||
*/
|
||||
void aboutToWakeUp();
|
||||
explicit PowerMonitor(QObject *parent = nullptr);
|
||||
~PowerMonitor() override;
|
||||
|
||||
/*
|
||||
* @brief 发送即将进入睡眠的信号
|
||||
/**
|
||||
* @brief 初始化电源监视器
|
||||
* @return true 如果初始化成功,否则返回 false
|
||||
*/
|
||||
void aboutToSleep();
|
||||
|
||||
private slots:
|
||||
/*
|
||||
* @brief 处理睡眠/唤醒信号的槽函数
|
||||
* @param enteringSleep 是否进入睡眠状态
|
||||
*/
|
||||
void onPrepareForSleep(bool enteringSleep);
|
||||
bool initialize() override;
|
||||
|
||||
private:
|
||||
/*
|
||||
* @brief 初始化Login1 DBus接口
|
||||
*/
|
||||
void initLogin1Dbus();
|
||||
|
||||
QDBusInterface *m_login1Interface = nullptr;
|
||||
PowerMonitorBase *m_impl; // 平台特定的实现
|
||||
};
|
||||
|
||||
#endif // POWERMONITOR_H
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
#include "powermonitor_base.h"
|
||||
#include <QDebug>
|
||||
|
||||
PowerMonitorBase::PowerMonitorBase(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
PowerMonitorBase::~PowerMonitorBase()
|
||||
{
|
||||
}
|
||||
|
||||
void PowerMonitorBase::emitAboutToSleep()
|
||||
{
|
||||
qDebug() << "=================================================";
|
||||
qDebug() << "\n📥 系统即将进入睡眠(休眠/待机)";
|
||||
emit aboutToSleep();
|
||||
emit powerStateChanged(true);
|
||||
}
|
||||
|
||||
void PowerMonitorBase::emitAboutToWakeUp()
|
||||
{
|
||||
qDebug() << "📤 系统已从睡眠中唤醒\n";
|
||||
qDebug() << "=================================================";
|
||||
emit aboutToWakeUp();
|
||||
emit powerStateChanged(false);
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
#ifndef POWERMONITOR_BASE_H
|
||||
#define POWERMONITOR_BASE_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
/**
|
||||
* @brief 电源监视器抽象基类
|
||||
*
|
||||
* 定义跨平台电源监视的公共接口
|
||||
* 各平台的具体实现需要继承此类并实现纯虚函数
|
||||
*/
|
||||
class PowerMonitorBase : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PowerMonitorBase(QObject *parent = nullptr);
|
||||
virtual ~PowerMonitorBase();
|
||||
|
||||
/**
|
||||
* @brief 初始化平台特定的电源监视
|
||||
* @return true 如果初始化成功,否则返回 false
|
||||
*
|
||||
* 纯虚函数,由子类实现平台特定的初始化逻辑
|
||||
*/
|
||||
virtual bool initialize() = 0;
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief 发送即将唤醒的信号
|
||||
* 当系统从睡眠/休眠状态唤醒时发出
|
||||
*/
|
||||
void aboutToWakeUp();
|
||||
|
||||
/**
|
||||
* @brief 发送即将进入睡眠的信号
|
||||
* 当系统即将进入睡眠/休眠状态时发出
|
||||
*/
|
||||
void aboutToSleep();
|
||||
|
||||
/**
|
||||
* @brief 电源状态改变信号
|
||||
* @param sleeping true表示进入睡眠,false表示从睡眠唤醒
|
||||
*/
|
||||
void powerStateChanged(bool sleeping);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief 触发睡眠事件
|
||||
* 由子类调用以发出即将进入睡眠的信号
|
||||
*/
|
||||
void emitAboutToSleep();
|
||||
|
||||
/**
|
||||
* @brief 触发唤醒事件
|
||||
* 由子类调用以发出即将唤醒的信号
|
||||
*/
|
||||
void emitAboutToWakeUp();
|
||||
};
|
||||
|
||||
#endif // POWERMONITOR_BASE_H
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
#include "powermonitor_linux.h"
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
PowerMonitorLinux::PowerMonitorLinux(QObject *parent)
|
||||
: PowerMonitorBase(parent)
|
||||
, m_login1Interface(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
PowerMonitorLinux::~PowerMonitorLinux()
|
||||
{
|
||||
if (m_login1Interface) {
|
||||
delete m_login1Interface;
|
||||
m_login1Interface = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool PowerMonitorLinux::initialize()
|
||||
{
|
||||
return initLogin1Dbus();
|
||||
}
|
||||
|
||||
bool PowerMonitorLinux::initLogin1Dbus()
|
||||
{
|
||||
// 连接 org.freedesktop.login1 服务(系统总线)
|
||||
m_login1Interface = new QDBusInterface(
|
||||
"org.freedesktop.login1", // DBus 服务名
|
||||
"/org/freedesktop/login1", // 对象路径
|
||||
"org.freedesktop.login1.Manager", // 接口名
|
||||
QDBusConnection::systemBus(), // 系统总线(关键!不是会话总线)
|
||||
this
|
||||
);
|
||||
|
||||
// 检查接口是否有效
|
||||
if (!m_login1Interface->isValid()) {
|
||||
qDebug() << "=================================================";
|
||||
qDebug() << "❌ 无法创建 login1 接口:" << m_login1Interface->lastError().message();
|
||||
qDebug() << "=================================================";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 监听 PrepareForSleep 信号(签名:b = bool)
|
||||
bool connectOk = QDBusConnection::systemBus().connect(
|
||||
"org.freedesktop.login1", // 服务名
|
||||
"/org/freedesktop/login1", // 对象路径
|
||||
"org.freedesktop.login1.Manager", // 接口名
|
||||
"PrepareForSleep", // 信号名
|
||||
this,
|
||||
SLOT(onPrepareForSleep(bool)) // 接收信号的槽函数
|
||||
);
|
||||
|
||||
if (connectOk) {
|
||||
qDebug() << "=================================================";
|
||||
qDebug() << "✅ 成功连接 login1 电源管理接口(Linux)";
|
||||
qDebug() << "=================================================";
|
||||
return true;
|
||||
} else {
|
||||
qDebug() << "=================================================";
|
||||
qDebug() << "❌ 连接 login1 信号失败:" << QDBusConnection::systemBus().lastError().message();
|
||||
qDebug() << "=================================================";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void PowerMonitorLinux::onPrepareForSleep(bool enteringSleep)
|
||||
{
|
||||
if (enteringSleep) {
|
||||
// 系统即将进入 休眠/待机 状态
|
||||
emitAboutToSleep();
|
||||
} else {
|
||||
// 系统从睡眠中唤醒
|
||||
emitAboutToWakeUp();
|
||||
}
|
||||
}
|
||||
|
||||
#endif // Q_OS_LINUX
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
#ifndef POWERMONITOR_LINUX_H
|
||||
#define POWERMONITOR_LINUX_H
|
||||
|
||||
#include "powermonitor_base.h"
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
|
||||
#include <QDBusInterface>
|
||||
#include <QDBusConnection>
|
||||
|
||||
/**
|
||||
* @brief Linux 电源监视器类
|
||||
*
|
||||
* 通过监听 DBus 的 login1 服务来检测系统睡眠/唤醒状态
|
||||
* 使用 org.freedesktop.login1.Manager 的 PrepareForSleep 信号
|
||||
*/
|
||||
class PowerMonitorLinux : public PowerMonitorBase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PowerMonitorLinux(QObject *parent = nullptr);
|
||||
~PowerMonitorLinux() override;
|
||||
|
||||
/**
|
||||
* @brief 初始化 Linux 平台的电源监视
|
||||
* @return true 如果初始化成功,否则返回 false
|
||||
*/
|
||||
bool initialize() override;
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* @brief 处理睡眠/唤醒信号的槽函数
|
||||
* @param enteringSleep 是否进入睡眠状态
|
||||
*/
|
||||
void onPrepareForSleep(bool enteringSleep);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 初始化Login1 DBus接口
|
||||
* @return true 如果初始化成功
|
||||
*/
|
||||
bool initLogin1Dbus();
|
||||
|
||||
private:
|
||||
QDBusInterface *m_login1Interface; // Login1 DBus 接口
|
||||
};
|
||||
|
||||
#endif // Q_OS_LINUX
|
||||
|
||||
#endif // POWERMONITOR_LINUX_H
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
#ifndef POWERMONITOR_MACOS_H
|
||||
#define POWERMONITOR_MACOS_H
|
||||
|
||||
#include "powermonitor_base.h"
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
|
||||
/**
|
||||
* @brief macOS 电源监视器类
|
||||
*
|
||||
* 通过监听 macOS 的 IOKit 电源管理通知来检测系统睡眠/唤醒状态
|
||||
* 使用 NSWorkspace 的分布式通知中心监听系统睡眠和唤醒事件
|
||||
*/
|
||||
class PowerMonitorMacOS : public PowerMonitorBase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PowerMonitorMacOS(QObject *parent = nullptr);
|
||||
~PowerMonitorMacOS() override;
|
||||
|
||||
/**
|
||||
* @brief 初始化 macOS 平台的电源监视
|
||||
* @return true 如果初始化成功,否则返回 false
|
||||
*/
|
||||
bool initialize() override;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief 处理系统将要睡眠通知的回调
|
||||
*/
|
||||
void handleWillSleep();
|
||||
|
||||
/**
|
||||
* @brief 处理系统已经唤醒通知的回调
|
||||
*/
|
||||
void handleDidWake();
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 注册电源管理通知观察者
|
||||
* @return true 如果注册成功
|
||||
*/
|
||||
bool registerPowerNotifications();
|
||||
|
||||
/**
|
||||
* @brief 注销电源管理通知观察者
|
||||
*/
|
||||
void unregisterPowerNotifications();
|
||||
|
||||
private:
|
||||
void *m_workspaceObserver; // NSWorkspace 通知观察者(不透明指针)
|
||||
bool m_initialized; // 是否已初始化
|
||||
};
|
||||
|
||||
#endif // Q_OS_MAC
|
||||
|
||||
#endif // POWERMONITOR_MACOS_H
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
#include "powermonitor_macos.h"
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AppKit/AppKit.h>
|
||||
#include <QDebug>
|
||||
|
||||
PowerMonitorMacOS::PowerMonitorMacOS(QObject *parent)
|
||||
: PowerMonitorBase(parent)
|
||||
, m_workspaceObserver(nullptr)
|
||||
, m_initialized(false)
|
||||
{
|
||||
}
|
||||
|
||||
PowerMonitorMacOS::~PowerMonitorMacOS()
|
||||
{
|
||||
unregisterPowerNotifications();
|
||||
}
|
||||
|
||||
bool PowerMonitorMacOS::initialize()
|
||||
{
|
||||
return registerPowerNotifications();
|
||||
}
|
||||
|
||||
bool PowerMonitorMacOS::registerPowerNotifications()
|
||||
{
|
||||
if (m_initialized) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@autoreleasepool {
|
||||
NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
|
||||
NSNotificationCenter *center = [workspace notificationCenter];
|
||||
|
||||
if (!center) {
|
||||
qDebug() << "=================================================";
|
||||
qDebug() << "❌ 无法获取 NSWorkspace 通知中心";
|
||||
qDebug() << "=================================================";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取 PowerMonitorMacOS 实例指针
|
||||
PowerMonitorMacOS *monitor = this;
|
||||
|
||||
// 注册系统将要睡眠通知
|
||||
id sleepObserver = [center addObserverForName:NSWorkspaceWillSleepNotification
|
||||
object:workspace
|
||||
queue:nil
|
||||
usingBlock:^(NSNotification *notification) {
|
||||
Q_UNUSED(notification);
|
||||
monitor->handleWillSleep();
|
||||
}];
|
||||
|
||||
// 注册系统已经唤醒通知
|
||||
id wakeObserver = [center addObserverForName:NSWorkspaceDidWakeNotification
|
||||
object:workspace
|
||||
queue:nil
|
||||
usingBlock:^(NSNotification *notification) {
|
||||
Q_UNUSED(notification);
|
||||
monitor->handleDidWake();
|
||||
}];
|
||||
|
||||
if (sleepObserver && wakeObserver) {
|
||||
// 将观察者保存到数组中
|
||||
NSMutableArray *observers = [NSMutableArray arrayWithCapacity:2];
|
||||
[observers addObject:sleepObserver];
|
||||
[observers addObject:wakeObserver];
|
||||
|
||||
// 保持观察者数组的引用 (手动 retain)
|
||||
m_workspaceObserver = (void *)CFBridgingRetain(observers);
|
||||
|
||||
m_initialized = true;
|
||||
|
||||
qDebug() << "=================================================";
|
||||
qDebug() << "✅ 成功注册 macOS 电源管理通知";
|
||||
qDebug() << "=================================================";
|
||||
return true;
|
||||
} else {
|
||||
qDebug() << "=================================================";
|
||||
qDebug() << "❌ 注册 macOS 电源管理通知失败";
|
||||
qDebug() << "=================================================";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PowerMonitorMacOS::unregisterPowerNotifications()
|
||||
{
|
||||
if (!m_initialized || !m_workspaceObserver) {
|
||||
return;
|
||||
}
|
||||
|
||||
@autoreleasepool {
|
||||
NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
|
||||
NSNotificationCenter *center = [workspace notificationCenter];
|
||||
|
||||
// 转换回 NSArray (手动 release)
|
||||
NSArray *observers = (NSArray *)CFBridgingRelease(m_workspaceObserver);
|
||||
|
||||
// 移除所有观察者
|
||||
for (id observer in observers) {
|
||||
[center removeObserver:observer];
|
||||
}
|
||||
|
||||
m_workspaceObserver = nullptr;
|
||||
m_initialized = false;
|
||||
|
||||
qDebug() << "=================================================";
|
||||
qDebug() << "✅ 已注销 macOS 电源管理通知观察者";
|
||||
qDebug() << "=================================================";
|
||||
}
|
||||
}
|
||||
|
||||
void PowerMonitorMacOS::handleWillSleep()
|
||||
{
|
||||
emitAboutToSleep();
|
||||
}
|
||||
|
||||
void PowerMonitorMacOS::handleDidWake()
|
||||
{
|
||||
emitAboutToWakeUp();
|
||||
}
|
||||
|
||||
#endif // Q_OS_MAC
|
||||
|
|
@ -15,7 +15,11 @@
|
|||
|
||||
// Include volk for Vulkan function loading
|
||||
#define VK_NO_PROTOTYPES
|
||||
#include "../../third_party/volk/volk.h"
|
||||
#include "third_party/volk/volk.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include "vulkanwidget_macos.h"
|
||||
#endif
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
|
|
@ -38,6 +42,7 @@ VulkanWidget::VulkanWidget(QWidget *parent)
|
|||
, m_swapchain(VK_NULL_HANDLE)
|
||||
, m_commandPool(VK_NULL_HANDLE)
|
||||
, m_x11Display(nullptr)
|
||||
, m_metalLayer(nullptr)
|
||||
, m_initialized(false)
|
||||
, m_renderingEnabled(false)
|
||||
, m_needsResize(false)
|
||||
|
|
@ -306,9 +311,19 @@ bool VulkanWidget::createInstance()
|
|||
createInfo.ppEnabledExtensionNames = extensions.data();
|
||||
createInfo.enabledLayerCount = 0;
|
||||
|
||||
#ifdef __APPLE__
|
||||
// macOS (MoltenVK) requires portability enumeration flag
|
||||
createInfo.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
|
||||
qDebug() << "macOS: Enabling portability enumeration for MoltenVK";
|
||||
#endif
|
||||
|
||||
VkResult result = vkCreateInstance(&createInfo, nullptr, &m_instance);
|
||||
if (result != VK_SUCCESS) {
|
||||
qDebug() << "Failed to create Vulkan instance, error code:" << result;
|
||||
#ifdef __APPLE__
|
||||
qDebug() << "macOS Hint: Make sure MoltenVK is installed and DYLD_LIBRARY_PATH is set";
|
||||
qDebug() << "Expected path: $VULKAN_SDK/lib (e.g., ~/VulkanSDK/1.4.328.1/macOS/lib)";
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -367,9 +382,19 @@ bool VulkanWidget::createSurface()
|
|||
|
||||
#elif defined(__APPLE__)
|
||||
// macOS requires MoltenVK and Metal
|
||||
// Get the native view handle
|
||||
void* nativeView = reinterpret_cast<void*>(window->winId());
|
||||
|
||||
// Setup CAMetalLayer using helper function
|
||||
m_metalLayer = createMetalLayer(nativeView);
|
||||
if (!m_metalLayer) {
|
||||
qDebug() << "Failed to create CAMetalLayer";
|
||||
return false;
|
||||
}
|
||||
|
||||
VkMetalSurfaceCreateInfoEXT createInfo = {};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
|
||||
createInfo.pLayer = nullptr; // This needs proper Metal layer setup
|
||||
createInfo.pLayer = m_metalLayer;
|
||||
|
||||
VkResult result = vkCreateMetalSurfaceEXT(m_instance, &createInfo, nullptr, &m_surface);
|
||||
#else
|
||||
|
|
@ -1094,6 +1119,14 @@ void VulkanWidget::cleanupVulkan()
|
|||
}
|
||||
#endif
|
||||
|
||||
// Release CAMetalLayer if it was created (macOS only)
|
||||
#ifdef __APPLE__
|
||||
if (m_metalLayer) {
|
||||
releaseMetalLayer(m_metalLayer);
|
||||
m_metalLayer = nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Cleanup instance
|
||||
if (m_instance != VK_NULL_HANDLE) {
|
||||
vkDestroyInstance(m_instance, nullptr);
|
||||
|
|
@ -1214,6 +1247,10 @@ std::vector<const char*> VulkanWidget::getRequiredInstanceExtensions()
|
|||
extensions.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
|
||||
#elif defined(__APPLE__)
|
||||
extensions.push_back(VK_EXT_METAL_SURFACE_EXTENSION_NAME);
|
||||
// MoltenVK requires portability enumeration extension
|
||||
extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
|
||||
extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
|
||||
qDebug() << "macOS: Adding MoltenVK required extensions";
|
||||
#endif
|
||||
|
||||
return extensions;
|
||||
|
|
@ -1223,6 +1260,13 @@ std::vector<const char*> VulkanWidget::getRequiredDeviceExtensions()
|
|||
{
|
||||
std::vector<const char*> extensions;
|
||||
extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
|
||||
|
||||
#ifdef __APPLE__
|
||||
// MoltenVK requires portability subset extension for device
|
||||
extensions.push_back("VK_KHR_portability_subset");
|
||||
qDebug() << "macOS: Adding portability_subset device extension for MoltenVK";
|
||||
#endif
|
||||
|
||||
return extensions;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -215,6 +215,7 @@ private:
|
|||
VkSwapchainKHR m_swapchain;
|
||||
VkCommandPool m_commandPool;
|
||||
void* m_x11Display; // X11 Display pointer (Linux only), stored as void* to avoid X11 header dependency
|
||||
void* m_metalLayer; // CAMetalLayer pointer (macOS only), stored as void* to avoid Objective-C header dependency
|
||||
std::vector<VkCommandBuffer> m_commandBuffers;
|
||||
std::vector<VkSemaphore> m_imageAvailableSemaphores;
|
||||
std::vector<VkSemaphore> m_renderFinishedSemaphores;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
#ifndef VULKANWIDGET_MACOS_H
|
||||
#define VULKANWIDGET_MACOS_H
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Create a CAMetalLayer for the given native view
|
||||
* @param nativeViewPtr Pointer to NSView (cast to void*)
|
||||
* @return Retained pointer to CAMetalLayer (must be released with releaseMetalLayer)
|
||||
*/
|
||||
void* createMetalLayer(void* nativeViewPtr);
|
||||
|
||||
/**
|
||||
* @brief Release the CAMetalLayer
|
||||
* @param metalLayerPtr Pointer to CAMetalLayer (cast to void*)
|
||||
*/
|
||||
void releaseMetalLayer(void* metalLayerPtr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // __APPLE__
|
||||
|
||||
#endif // VULKANWIDGET_MACOS_H
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
#include "vulkanwidget_macos.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
#import <AppKit/NSWindow.h>
|
||||
#import <AppKit/NSView.h>
|
||||
#include <QDebug>
|
||||
|
||||
void* createMetalLayer(void* nativeViewPtr)
|
||||
{
|
||||
@autoreleasepool {
|
||||
NSView* view = (__bridge NSView*)nativeViewPtr;
|
||||
if (!view) {
|
||||
qDebug() << "createMetalLayer: Invalid NSView";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Create CAMetalLayer
|
||||
CAMetalLayer* metalLayer = [CAMetalLayer layer];
|
||||
if (!metalLayer) {
|
||||
qDebug() << "createMetalLayer: Failed to create CAMetalLayer";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Configure the metal layer
|
||||
metalLayer.pixelFormat = MTLPixelFormatBGRA8Unorm;
|
||||
metalLayer.framebufferOnly = YES;
|
||||
|
||||
// Get the content scale factor for retina displays
|
||||
NSWindow* window = [view window];
|
||||
CGFloat scaleFactor = window ? [window backingScaleFactor] : 1.0;
|
||||
metalLayer.contentsScale = scaleFactor;
|
||||
|
||||
// Set the layer's drawable size to match the view
|
||||
CGSize drawableSize = view.bounds.size;
|
||||
drawableSize.width *= scaleFactor;
|
||||
drawableSize.height *= scaleFactor;
|
||||
metalLayer.drawableSize = drawableSize;
|
||||
|
||||
// Set view properties
|
||||
[view setWantsLayer:YES];
|
||||
[view setLayer:metalLayer];
|
||||
|
||||
qDebug() << "createMetalLayer: Successfully created CAMetalLayer"
|
||||
<< "size:" << drawableSize.width << "x" << drawableSize.height
|
||||
<< "scale:" << scaleFactor;
|
||||
|
||||
// Return retained pointer (caller must release)
|
||||
return (void*)CFBridgingRetain(metalLayer);
|
||||
}
|
||||
}
|
||||
|
||||
void releaseMetalLayer(void* metalLayerPtr)
|
||||
{
|
||||
if (metalLayerPtr) {
|
||||
CFRelease(metalLayerPtr);
|
||||
qDebug() << "releaseMetalLayer: CAMetalLayer released";
|
||||
}
|
||||
}
|
||||
|
||||
#endif // __APPLE__
|
||||
Loading…
Reference in New Issue