以ReviveOverlay为例分析:
trayiconcontroller.h
#ifndef CTRAYICONCONTROLLER_H
#define CTRAYICONCONTROLLER_H
#include <QObject>
#include <QMenu>
#include <QSystemTrayIcon>
enum ETrayInfo
{
TrayInfo_OculusLibraryNotFound,
TrayInfo_AutoLaunchEnabled,
TrayInfo_AutoLaunchFailed
};
class CTrayIconController : public QObject
{
Q_OBJECT
typedef QObject BaseClass;
public:
static CTrayIconController *SharedInstance();
public:
CTrayIconController();
~CTrayIconController();
bool Init();
void ShowInformation(ETrayInfo info);
public slots:
void quit();
protected slots:
void inject();
void patch();
void showHelp();
void messageClicked();
private:
QSystemTrayIcon m_trayIcon;
QMenu m_trayIconMenu;
ETrayInfo m_LastInfo;
QString openDialog();
};
#endif // CTRAYICONCONTROLLER_H
trayiconcontroller.cpp
#include "trayiconcontroller.h"
#include <windowsservices.h>
#include <qt_windows.h>
#include <winsparkle.h>
#include <QCoreApplication>
#include <QDesktopServices>
#include <QFileDialog>
#include <QFileInfo>
#include <QIcon>
#include <QProcess>
#include <QUrl>
CTrayIconController *s_pSharedTrayController = NULL;
CTrayIconController *CTrayIconController::SharedInstance()
{
if ( !s_pSharedTrayController )
{
s_pSharedTrayController = new CTrayIconController();
}
return s_pSharedTrayController;
}
CTrayIconController::CTrayIconController()
: BaseClass()
, m_trayIcon(QIcon(":/revive_white.ico"))
{
QObject::connect(&m_trayIcon, SIGNAL(messageClicked()), this, SLOT(messageClicked()));
}
CTrayIconController::~CTrayIconController()
{
}
bool CTrayIconController::Init()
{
m_trayIconMenu.addAction("&Inject...", this, SLOT(inject()));
m_trayIconMenu.addAction("&Patch...", this, SLOT(patch()));
m_trayIconMenu.addAction("&Help", this, SLOT(showHelp()));
m_trayIconMenu.addSeparator();
m_trayIconMenu.addAction("Check for &updates", win_sparkle_check_update_with_ui);
m_trayIconMenu.addAction("&Quit", this, SLOT(quit()));
m_trayIcon.setContextMenu(&m_trayIconMenu);
m_trayIcon.setToolTip("Revive Dashboard");
m_trayIcon.show();
return true;
}
void CTrayIconController::ShowInformation(ETrayInfo info)
{
m_LastInfo = info;
switch (info)
{
case TrayInfo_AutoLaunchEnabled:
m_trayIcon.showMessage("Revive succesfully installed",
"Revive will automatically add Oculus Store games to your library while SteamVR is running.",
QSystemTrayIcon::Information);
break;
case TrayInfo_AutoLaunchFailed:
m_trayIcon.showMessage("Revive did not start correctly",
"Unable to set the auto-launch flag, please report this to the Revive issue tracker.",
QSystemTrayIcon::Critical);
break;
case TrayInfo_OculusLibraryNotFound:
m_trayIcon.showMessage("Revive did not start correctly",
"No Oculus Library was found, please install the Oculus Software from oculus.com/setup.",
QSystemTrayIcon::Warning);
break;
}
}
void CTrayIconController::quit()
{
m_trayIcon.setVisible(false);
QCoreApplication::quit();
}
void CTrayIconController::inject()
{
QString file = openDialog();
if (file.isNull())
return;
QStringList args;
args.append(file);
QProcess::execute(QCoreApplication::applicationDirPath() + "/Revive/ReviveInjector_x64.exe", args);
}
void CTrayIconController::patch()
{
QString file = openDialog();
if (file.isNull())
return;
DWORD type;
if (!GetBinaryType(qUtf16Printable(file), &type))
return;
QString dir = QCoreApplication::applicationDirPath();
QStringList files;
if (type == SCS_32BIT_BINARY)
{
files.append(dir + "/Revive/x86/LibRevive32_0_7.dll");
files.append(dir + "/Revive/x86/LibRevive32_0_7.dll");
files.append(dir + "/Revive/x86/openvr_api.dll");
}
if (type == SCS_64BIT_BINARY)
{
files.append(dir + "/Revive/x64/LibRevive64_0_7.dll");
files.append(dir + "/Revive/x64/LibRevive64_0_7.dll");
files.append(dir + "/Revive/x64/openvr_api.dll");
}
QStringList names = { "xinput1_3.dll", "xinput9_1_0.dll", "openvr_api.dll" };
QFileInfo info(file);
WindowsServices::CopyFiles(files, info.absolutePath(), names);
}
void CTrayIconController::showHelp()
{
QDesktopServices::openUrl(QUrl("https://github.com/LibreVR/Revive/wiki"));
}
void CTrayIconController::messageClicked()
{
switch (m_LastInfo)
{
case TrayInfo_AutoLaunchFailed:
QDesktopServices::openUrl(QUrl("https://github.com/LibreVR/Revive/issues"));
break;
case TrayInfo_OculusLibraryNotFound:
QDesktopServices::openUrl(QUrl("https://oculus.com/setup"));
break;
}
}
QString CTrayIconController::openDialog()
{
return QFileDialog::getOpenFileName(
nullptr, "Revive",
QStandardPaths::writableLocation(QStandardPaths::DesktopLocation),
"Application (*.exe)");
}
1.CTrayIconController::Init()所实现的功能如下所示:
2.m_trayIconMenu.addAction("&Inject...", this, SLOT(inject())); //添加菜单项,点击Inject...时,触发信号槽inject()。在inject()中打开对话框openDialog如下:
再选中一个oculus的游戏exe打开,通过openDialog返回文件名,通过init中的QProcess::execute(QCoreApplication::applicationDirPath() + "/Revive/ReviveInjector_x64.exe", args);启动该程序。Inject的游戏必须为不是从Oculus Home下载的独立游戏。
QString CTrayIconController::openDialog()
{
return QFileDialog::getOpenFileName(
nullptr, "Revive",
QStandardPaths::writableLocation(QStandardPaths::DesktopLocation),
"Application (*.exe)");
}
QStandardPaths::writableLocation(QStandardPaths::DesktopLocation):权限可写的默认目录位置为QStandardPaths::DesktopLocation,也就是桌面位置。
文件名后缀为:Application (*.exe)
对话框左上角名为Revive
3.m_trayIconMenu.addAction("&Patch...", this, SLOT(patch()));:同上一样,点击Patch...触发信号槽patch()。
patch()中,如2一样,先openDialog,选中一个文件并返回文件名,之后通过GetBinaryType判断该文件是否为可执行文件,并获取其类型:为32位还是64位。
另外再获取当前应用程序所在目录路径QString dir = QCoreApplication::applicationDirPath();如下:
之后files.append将需要打包的dll文件路径与前面获取的路径拼接起来。
进入CopyFiles内:
1. Microsoft::WRL::ComPtr<IFileOperation> pfo;声明一个指向模板参数为IFileOperation接口的智能指针pfo。ComPtr类为潜在的接口指针自动维护一个索引数
,且在索引数为0时自动释放该接口。IFileOperation是一个COM库接口。
2. HRESULT hr = CoCreateInstance(CLSID_FileOperation, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&pfo));用一个指定的CLSID_FileOperation创建一个对象IFileOperation,pfo返回。
3.QString dstDir = QDir::toNativeSeparators(destination);将路径中的左斜杠\替换为右斜杠/
4.hr = SHCreateItemFromParsingName(qUtf16Printable(dstDir),
NULL,
IID_PPV_ARGS(&psiTo));
根据所提供的源路径dstDir创建一个IShellItem。
5.hr = pfo->CopyItem(
psiFrom.Get(),
psiTo.Get(),
name->isNull() ? nullptr : qUtf16Printable(*name),
nullptr);
将psiFrom.Get()复制到psiTo.Get()内,重命名为name.
6.hr = pfo->PerformOperations();
为什么要patch?
是因为通过Steam平台发布的Oculus游戏不能inject,只有通过patch将两个dll文件:文件LibRevive64_0_7.dll以及openvr_api.dll打包并
重名字为"xinput1_3.dll", "xinput9_1_0.dll", "openvr_api.dll",拷贝到Oculus游戏exe所在目录,这可保证当通过Steam启动Oculus游戏时,一定能够
加载Revive。比如最终打包的文件拷贝位置:
4.void CTrayIconController::showHelp() 对应菜单项Help
{
QDesktopServices::openUrl(QUrl("https://github.com/LibreVR/Revive/wiki"));
}
在用户桌面环境打开给定的URL地址所指向的网页。
5.m_trayIconMenu.addAction("Check for &updates", win_sparkle_check_update_with_ui);对应菜单Check for update,点击后弹出软件更新窗口
winsparkle 参考网页
后面的quit类似
m_trayIcon.setToolTip("Revive Dashboard"); 设置显示名字,当鼠标移动到上面是显示的,如下: