first push
This commit is contained in:
40
APP/APP_GlassCard.cpp
Normal file
40
APP/APP_GlassCard.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#include "APP/APP_GlassCard.h"
|
||||
|
||||
#include <QtGui/QPainter>
|
||||
|
||||
namespace APP {
|
||||
|
||||
APP_GlassCard::APP_GlassCard(QWidget* parent)
|
||||
: QFrame(parent)
|
||||
{
|
||||
// 交给我们自己统一绘制卡片外观,不使用 QFrame 默认边框。
|
||||
setFrameShape(QFrame::NoFrame);
|
||||
// 背景由 paintEvent 自绘,这里不走样式表背景。
|
||||
setAttribute(Qt::WA_StyledBackground, false);
|
||||
}
|
||||
|
||||
void APP_GlassCard::paintEvent(QPaintEvent* event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
|
||||
/*
|
||||
* 卡片外观刻意保持简单,方便教学时理解自绘流程:
|
||||
* 1. 先画一个带圆角的深色底板
|
||||
* 2. 再画一圈细边框
|
||||
*/
|
||||
const QRectF BodyRect = rect().adjusted(1.0, 1.0, -1.0, -1.0);
|
||||
const qreal Radius = 22.0;
|
||||
const QColor FillColor(30, 35, 43);
|
||||
const QColor BorderColor(82, 92, 104);
|
||||
|
||||
QPainter Painter(this);
|
||||
Painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
// 先画卡片主体。
|
||||
Painter.setPen(QPen(BorderColor, 1.0));
|
||||
Painter.setBrush(FillColor);
|
||||
Painter.drawRoundedRect(BodyRect, Radius, Radius);
|
||||
|
||||
}
|
||||
|
||||
} // namespace APP
|
||||
27
APP/APP_GlassCard.h
Normal file
27
APP/APP_GlassCard.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtWidgets/QFrame>
|
||||
|
||||
namespace APP {
|
||||
|
||||
/*
|
||||
* 这是项目里所有“卡片容器”的基础控件。
|
||||
*
|
||||
* 它只负责统一外观,不负责任何业务逻辑:
|
||||
* 1. 统一圆角卡片风格
|
||||
* 2. 统一边框和暗色底板
|
||||
*
|
||||
* 上层像主页卡片、调试卡片都直接继承它。
|
||||
*/
|
||||
class APP_GlassCard : public QFrame
|
||||
{
|
||||
public:
|
||||
// 构造一个带统一外观的卡片容器。
|
||||
explicit APP_GlassCard(QWidget* parent = nullptr);
|
||||
|
||||
protected:
|
||||
// 卡片背景和圆角边框都在这里自绘。
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
};
|
||||
|
||||
} // namespace APP
|
||||
171
APP/APP_KeyButton.cpp
Normal file
171
APP/APP_KeyButton.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
#include "APP/APP_KeyButton.h"
|
||||
|
||||
#include "APP/APP_Theme.h"
|
||||
#include <QtCore/QtMath>
|
||||
#include <QtGui/QPainter>
|
||||
|
||||
namespace {
|
||||
|
||||
QColor App_Func_MixColor(const QColor& Left, const QColor& Right, qreal Value)
|
||||
{
|
||||
const qreal Rate = qBound(0.0, Value, 1.0);
|
||||
|
||||
return QColor(
|
||||
qRound(Left.red() + (Right.red() - Left.red()) * Rate),
|
||||
qRound(Left.green() + (Right.green() - Left.green()) * Rate),
|
||||
qRound(Left.blue() + (Right.blue() - Left.blue()) * Rate),
|
||||
qRound(Left.alpha() + (Right.alpha() - Left.alpha()) * Rate));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace APP {
|
||||
|
||||
APP_KeyButton::APP_KeyButton(const APP_KeyInfo& KeyInfo, QWidget* parent)
|
||||
: QPushButton(parent),
|
||||
appKeyInfo(KeyInfo)
|
||||
{
|
||||
appHintText = appKeyInfo.hint;
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
setFlat(true);
|
||||
setMinimumSize(78, 78);
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
}
|
||||
|
||||
void APP_KeyButton::App_Func_SetLatched(bool IsLatched)
|
||||
{
|
||||
if (appIsLatched == IsLatched)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
appIsLatched = IsLatched;
|
||||
update();
|
||||
}
|
||||
|
||||
void APP_KeyButton::App_Func_SetPressed(bool IsPressed)
|
||||
{
|
||||
if (appIsPressed == IsPressed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
appIsPressed = IsPressed;
|
||||
update();
|
||||
}
|
||||
|
||||
void APP_KeyButton::App_Func_SetHintText(const QString& HintText)
|
||||
{
|
||||
if (appHintText == HintText)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
appHintText = HintText;
|
||||
update();
|
||||
}
|
||||
|
||||
void APP_KeyButton::paintEvent(QPaintEvent* event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
|
||||
const QRectF ButtonRect = rect().adjusted(6.0, 6.0, -6.0, -6.0);
|
||||
const qreal Radius = 14.0;
|
||||
|
||||
QColor FillColor = App_Func_GetBackgroundColor();
|
||||
QColor OutlineColor = App_Func_GetBorderColor();
|
||||
|
||||
if (isDown())
|
||||
{
|
||||
FillColor = FillColor.darker(108);
|
||||
OutlineColor = OutlineColor.darker(112);
|
||||
}
|
||||
|
||||
QPainter Painter(this);
|
||||
Painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
Painter.setRenderHint(QPainter::TextAntialiasing, true);
|
||||
Painter.setPen(QPen(OutlineColor, 1.2));
|
||||
Painter.setBrush(FillColor);
|
||||
Painter.drawRoundedRect(ButtonRect, Radius, Radius);
|
||||
|
||||
if (!appHintText.isEmpty())
|
||||
{
|
||||
Painter.setFont(APP_Theme::App_Func_GetKeyHintFont());
|
||||
Painter.setPen(App_Func_GetTextColor().lighter(115));
|
||||
Painter.drawText(
|
||||
ButtonRect.adjusted(10.0, 8.0, -10.0, -8.0),
|
||||
Qt::AlignLeft | Qt::AlignTop,
|
||||
appHintText.toUpper());
|
||||
}
|
||||
|
||||
QFont LabelFont = APP_Theme::App_Func_GetKeyLabelFont();
|
||||
if (appKeyInfo.label.size() > 2)
|
||||
{
|
||||
LabelFont.setPointSize(LabelFont.pointSize() - 4);
|
||||
}
|
||||
else if (appKeyInfo.label.size() == 2)
|
||||
{
|
||||
LabelFont.setPointSize(LabelFont.pointSize() - 2);
|
||||
}
|
||||
|
||||
Painter.setFont(LabelFont);
|
||||
Painter.setPen(App_Func_GetTextColor());
|
||||
Painter.drawText(ButtonRect, Qt::AlignCenter, appKeyInfo.label);
|
||||
}
|
||||
|
||||
QColor APP_KeyButton::App_Func_GetAccentColor() const
|
||||
{
|
||||
switch (appKeyInfo.tone)
|
||||
{
|
||||
case APP_KeyTone::Aqua:
|
||||
return QColor(72, 184, 162);
|
||||
case APP_KeyTone::Amber:
|
||||
return QColor(224, 172, 76);
|
||||
case APP_KeyTone::Blue:
|
||||
return QColor(103, 146, 224);
|
||||
case APP_KeyTone::Normal:
|
||||
default:
|
||||
return QColor(150, 168, 196);
|
||||
}
|
||||
}
|
||||
|
||||
QColor APP_KeyButton::App_Func_GetBackgroundColor() const
|
||||
{
|
||||
const QColor BaseColor(55, 61, 70);
|
||||
|
||||
if (appIsPressed)
|
||||
{
|
||||
return App_Func_MixColor(BaseColor, App_Func_GetAccentColor(), 0.56);
|
||||
}
|
||||
|
||||
if (appKeyInfo.tone != APP_KeyTone::Normal || appIsLatched)
|
||||
{
|
||||
return App_Func_MixColor(BaseColor, App_Func_GetAccentColor(), appIsLatched ? 0.35 : 0.18);
|
||||
}
|
||||
|
||||
return BaseColor;
|
||||
}
|
||||
|
||||
QColor APP_KeyButton::App_Func_GetBorderColor() const
|
||||
{
|
||||
const QColor BaseColor(104, 114, 126);
|
||||
|
||||
if (appIsPressed)
|
||||
{
|
||||
return App_Func_MixColor(BaseColor, App_Func_GetAccentColor(), 0.72);
|
||||
}
|
||||
|
||||
if (appKeyInfo.tone != APP_KeyTone::Normal || appIsLatched)
|
||||
{
|
||||
return App_Func_MixColor(BaseColor, App_Func_GetAccentColor(), appIsLatched ? 0.45 : 0.25);
|
||||
}
|
||||
|
||||
return BaseColor;
|
||||
}
|
||||
|
||||
QColor APP_KeyButton::App_Func_GetTextColor() const
|
||||
{
|
||||
return QColor(238, 242, 247);
|
||||
}
|
||||
|
||||
} // namespace APP
|
||||
33
APP/APP_KeyButton.h
Normal file
33
APP/APP_KeyButton.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "APP/APP_KeypadModel.h"
|
||||
#include <QtGui/QColor>
|
||||
#include <QtWidgets/QPushButton>
|
||||
|
||||
namespace APP {
|
||||
|
||||
class APP_KeyButton : public QPushButton
|
||||
{
|
||||
public:
|
||||
explicit APP_KeyButton(const APP_KeyInfo& KeyInfo, QWidget* parent = nullptr);
|
||||
|
||||
void App_Func_SetLatched(bool IsLatched);
|
||||
void App_Func_SetPressed(bool IsPressed);
|
||||
void App_Func_SetHintText(const QString& HintText);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
|
||||
private:
|
||||
QColor App_Func_GetAccentColor() const;
|
||||
QColor App_Func_GetBackgroundColor() const;
|
||||
QColor App_Func_GetBorderColor() const;
|
||||
QColor App_Func_GetTextColor() const;
|
||||
|
||||
APP_KeyInfo appKeyInfo;
|
||||
QString appHintText;
|
||||
bool appIsLatched = false;
|
||||
bool appIsPressed = false;
|
||||
};
|
||||
|
||||
} // namespace APP
|
||||
119
APP/APP_KeypadModel.cpp
Normal file
119
APP/APP_KeypadModel.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
#include "APP/APP_KeypadModel.h"
|
||||
|
||||
namespace APP {
|
||||
|
||||
APP_KeypadModel::APP_KeypadModel()
|
||||
{
|
||||
appKeyList = {
|
||||
{QStringLiteral("num"), QStringLiteral("Num"), QString(), 0x0053, 0, 0, 1, 1, APP_KeyTone::Aqua},
|
||||
{QStringLiteral("divide"), QStringLiteral("/"), QString(), 0x0054, 0, 1, 1, 1, APP_KeyTone::Normal},
|
||||
{QStringLiteral("multiply"), QStringLiteral("*"), QString(), 0x0055, 0, 2, 1, 1, APP_KeyTone::Normal},
|
||||
{QStringLiteral("minus"), QStringLiteral("-"), QString(), 0x0056, 0, 3, 1, 1, APP_KeyTone::Amber},
|
||||
{QStringLiteral("7"), QStringLiteral("7"), QStringLiteral("Home"), 0x005F, 1, 0, 1, 1, APP_KeyTone::Normal},
|
||||
{QStringLiteral("8"), QStringLiteral("8"), QStringLiteral("Up"), 0x0060, 1, 1, 1, 1, APP_KeyTone::Normal},
|
||||
{QStringLiteral("9"), QStringLiteral("9"), QStringLiteral("PgUp"), 0x0061, 1, 2, 1, 1, APP_KeyTone::Normal},
|
||||
{QStringLiteral("plus"), QStringLiteral("+"), QString(), 0x0057, 1, 3, 2, 1, APP_KeyTone::Aqua},
|
||||
{QStringLiteral("4"), QStringLiteral("4"), QStringLiteral("Left"), 0x005C, 2, 0, 1, 1, APP_KeyTone::Normal},
|
||||
{QStringLiteral("5"), QStringLiteral("5"), QString(), 0x005D, 2, 1, 1, 1, APP_KeyTone::Normal},
|
||||
{QStringLiteral("6"), QStringLiteral("6"), QStringLiteral("Right"), 0x005E, 2, 2, 1, 1, APP_KeyTone::Normal},
|
||||
{QStringLiteral("1"), QStringLiteral("1"), QStringLiteral("End"), 0x0059, 3, 0, 1, 1, APP_KeyTone::Normal},
|
||||
{QStringLiteral("2"), QStringLiteral("2"), QStringLiteral("Down"), 0x005A, 3, 1, 1, 1, APP_KeyTone::Normal},
|
||||
{QStringLiteral("3"), QStringLiteral("3"), QStringLiteral("PgDn"), 0x005B, 3, 2, 1, 1, APP_KeyTone::Normal},
|
||||
{QStringLiteral("enter"), QStringLiteral("Enter"), QString(), 0x0058, 3, 3, 2, 1, APP_KeyTone::Blue},
|
||||
{QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("Ins"), 0x0062, 4, 0, 1, 2, APP_KeyTone::Normal},
|
||||
{QStringLiteral("dot"), QStringLiteral("."), QStringLiteral("Del"), 0x0063, 4, 2, 1, 1, APP_KeyTone::Normal}
|
||||
};
|
||||
}
|
||||
|
||||
const QVector<APP_KeyInfo>& APP_KeypadModel::App_Func_GetKeyList() const
|
||||
{
|
||||
return appKeyList;
|
||||
}
|
||||
|
||||
bool APP_KeypadModel::App_Func_IsLatched(const QString& KeyId) const
|
||||
{
|
||||
return (KeyId == QStringLiteral("num")) && appNumLockOn;
|
||||
}
|
||||
|
||||
bool APP_KeypadModel::App_Func_IsPressed(const QString& KeyId) const
|
||||
{
|
||||
return appPressedKeyIdList.contains(KeyId);
|
||||
}
|
||||
|
||||
quint16 APP_KeypadModel::App_Func_GetUsageFromKeyId(const QString& KeyId) const
|
||||
{
|
||||
for (const APP_KeyInfo& KeyInfo : appKeyList)
|
||||
{
|
||||
if (KeyInfo.id == KeyId)
|
||||
{
|
||||
return KeyInfo.usage;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
QString APP_KeypadModel::App_Func_GetKeyIdFromUsage(quint16 Usage) const
|
||||
{
|
||||
for (const APP_KeyInfo& KeyInfo : appKeyList)
|
||||
{
|
||||
if (KeyInfo.usage == Usage)
|
||||
{
|
||||
return KeyInfo.id;
|
||||
}
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString APP_KeypadModel::App_Func_GetLabelFromUsage(quint16 Usage) const
|
||||
{
|
||||
for (const APP_KeyInfo& KeyInfo : appKeyList)
|
||||
{
|
||||
if (KeyInfo.usage == Usage)
|
||||
{
|
||||
return KeyInfo.label;
|
||||
}
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString APP_KeypadModel::App_Func_GetDefaultHint(const QString& KeyId) const
|
||||
{
|
||||
for (const APP_KeyInfo& KeyInfo : appKeyList)
|
||||
{
|
||||
if (KeyInfo.id == KeyId)
|
||||
{
|
||||
return KeyInfo.hint;
|
||||
}
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
void APP_KeypadModel::App_Func_SetNumLockOn(bool IsOn)
|
||||
{
|
||||
appNumLockOn = IsOn;
|
||||
}
|
||||
|
||||
void APP_KeypadModel::App_Func_ClearPressedKeys()
|
||||
{
|
||||
appPressedKeyIdList.clear();
|
||||
}
|
||||
|
||||
void APP_KeypadModel::App_Func_SetPressedKeysFromUsageList(const QVector<quint16>& UsageList)
|
||||
{
|
||||
appPressedKeyIdList.clear();
|
||||
|
||||
for (quint16 Usage : UsageList)
|
||||
{
|
||||
const QString KeyId = App_Func_GetKeyIdFromUsage(Usage);
|
||||
if (!KeyId.isEmpty() && !appPressedKeyIdList.contains(KeyId))
|
||||
{
|
||||
appPressedKeyIdList.append(KeyId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace APP
|
||||
52
APP/APP_KeypadModel.h
Normal file
52
APP/APP_KeypadModel.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QVector>
|
||||
|
||||
namespace APP {
|
||||
|
||||
enum class APP_KeyTone
|
||||
{
|
||||
Normal,
|
||||
Aqua,
|
||||
Amber,
|
||||
Blue
|
||||
};
|
||||
|
||||
struct APP_KeyInfo
|
||||
{
|
||||
QString id;
|
||||
QString label;
|
||||
QString hint;
|
||||
quint16 usage = 0;
|
||||
int row = 0;
|
||||
int column = 0;
|
||||
int rowSpan = 1;
|
||||
int columnSpan = 1;
|
||||
APP_KeyTone tone = APP_KeyTone::Normal;
|
||||
};
|
||||
|
||||
class APP_KeypadModel
|
||||
{
|
||||
public:
|
||||
APP_KeypadModel();
|
||||
|
||||
const QVector<APP_KeyInfo>& App_Func_GetKeyList() const;
|
||||
bool App_Func_IsLatched(const QString& KeyId) const;
|
||||
bool App_Func_IsPressed(const QString& KeyId) const;
|
||||
quint16 App_Func_GetUsageFromKeyId(const QString& KeyId) const;
|
||||
QString App_Func_GetKeyIdFromUsage(quint16 Usage) const;
|
||||
QString App_Func_GetLabelFromUsage(quint16 Usage) const;
|
||||
QString App_Func_GetDefaultHint(const QString& KeyId) const;
|
||||
void App_Func_SetNumLockOn(bool IsOn);
|
||||
void App_Func_ClearPressedKeys();
|
||||
void App_Func_SetPressedKeysFromUsageList(const QVector<quint16>& UsageList);
|
||||
|
||||
private:
|
||||
QVector<APP_KeyInfo> appKeyList;
|
||||
QStringList appPressedKeyIdList;
|
||||
bool appNumLockOn = false;
|
||||
};
|
||||
|
||||
} // namespace APP
|
||||
131
APP/APP_Theme.cpp
Normal file
131
APP/APP_Theme.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
#include "APP/APP_Theme.h"
|
||||
|
||||
#include <QtGui/QFontDatabase>
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
namespace APP {
|
||||
|
||||
QPalette APP_Theme::App_Func_GetPalette()
|
||||
{
|
||||
/*
|
||||
* 不用样式表时,Qt 最稳妥的统一美化方式就是调色板:
|
||||
* 1. 先选 Fusion 风格
|
||||
* 2. 再给标准控件一组统一颜色
|
||||
*/
|
||||
QPalette Palette;
|
||||
|
||||
const QColor WindowColor(20, 25, 33);
|
||||
const QColor BaseColor(28, 34, 42);
|
||||
const QColor AltBaseColor(34, 40, 49);
|
||||
const QColor ButtonColor(38, 44, 54);
|
||||
const QColor BorderHintColor(86, 96, 108);
|
||||
const QColor HighlightColor(72, 184, 162);
|
||||
const QColor TextColor(238, 242, 247);
|
||||
const QColor DimTextColor(162, 170, 182);
|
||||
|
||||
Palette.setColor(QPalette::Window, WindowColor);
|
||||
Palette.setColor(QPalette::WindowText, TextColor);
|
||||
Palette.setColor(QPalette::Base, BaseColor);
|
||||
Palette.setColor(QPalette::AlternateBase, AltBaseColor);
|
||||
Palette.setColor(QPalette::Text, TextColor);
|
||||
Palette.setColor(QPalette::Button, ButtonColor);
|
||||
Palette.setColor(QPalette::ButtonText, TextColor);
|
||||
Palette.setColor(QPalette::BrightText, QColor(255, 255, 255));
|
||||
Palette.setColor(QPalette::Light, BorderHintColor.lighter(120));
|
||||
Palette.setColor(QPalette::Midlight, BorderHintColor);
|
||||
Palette.setColor(QPalette::Mid, BorderHintColor.darker(120));
|
||||
Palette.setColor(QPalette::Dark, WindowColor.darker(140));
|
||||
Palette.setColor(QPalette::Shadow, QColor(0, 0, 0, 140));
|
||||
Palette.setColor(QPalette::Highlight, HighlightColor);
|
||||
Palette.setColor(QPalette::HighlightedText, TextColor);
|
||||
Palette.setColor(QPalette::ToolTipBase, BaseColor);
|
||||
Palette.setColor(QPalette::ToolTipText, TextColor);
|
||||
Palette.setColor(QPalette::Link, QColor(103, 146, 224));
|
||||
Palette.setColor(QPalette::PlaceholderText, QColor(170, 178, 188, 170));
|
||||
Palette.setColor(QPalette::Disabled, QPalette::WindowText, DimTextColor);
|
||||
Palette.setColor(QPalette::Disabled, QPalette::Text, DimTextColor);
|
||||
Palette.setColor(QPalette::Disabled, QPalette::ButtonText, DimTextColor);
|
||||
|
||||
return Palette;
|
||||
}
|
||||
|
||||
QFont APP_Theme::App_Func_GetBodyFont()
|
||||
{
|
||||
// 正文用相对稳妥、系统常见的字体候选。
|
||||
QFont Font(App_Func_PickFontFamily(QStringList()
|
||||
<< QStringLiteral("Segoe UI Variable Text")
|
||||
<< QStringLiteral("Microsoft YaHei UI")
|
||||
<< QStringLiteral("Segoe UI")
|
||||
<< QStringLiteral("Bahnschrift")));
|
||||
Font.setPointSize(10);
|
||||
Font.setWeight(QFont::Medium);
|
||||
return Font;
|
||||
}
|
||||
|
||||
QFont APP_Theme::App_Func_GetTitleFont()
|
||||
{
|
||||
// 页面标题字号更大、字重更高。
|
||||
QFont Font(App_Func_PickFontFamily(QStringList()
|
||||
<< QStringLiteral("Segoe UI Variable Display Semibold")
|
||||
<< QStringLiteral("Microsoft YaHei UI")
|
||||
<< QStringLiteral("Bahnschrift SemiBold")));
|
||||
Font.setPointSize(21);
|
||||
Font.setWeight(QFont::DemiBold);
|
||||
return Font;
|
||||
}
|
||||
|
||||
QFont APP_Theme::App_Func_GetMetricFont()
|
||||
{
|
||||
// 指标类标题用比正文更有力度的字重。
|
||||
QFont Font(App_Func_PickFontFamily(QStringList()
|
||||
<< QStringLiteral("Bahnschrift SemiBold")
|
||||
<< QStringLiteral("Segoe UI Semibold")
|
||||
<< QStringLiteral("Microsoft YaHei UI")));
|
||||
Font.setPointSize(12);
|
||||
Font.setWeight(QFont::DemiBold);
|
||||
return Font;
|
||||
}
|
||||
|
||||
QFont APP_Theme::App_Func_GetKeyLabelFont()
|
||||
{
|
||||
// 按键主文字字号较大,保证小键盘一眼能看清。
|
||||
QFont Font(App_Func_PickFontFamily(QStringList()
|
||||
<< QStringLiteral("Bahnschrift SemiBold")
|
||||
<< QStringLiteral("Segoe UI Semibold")
|
||||
<< QStringLiteral("Microsoft YaHei UI")));
|
||||
Font.setPointSize(22);
|
||||
return Font;
|
||||
}
|
||||
|
||||
QFont APP_Theme::App_Func_GetKeyHintFont()
|
||||
{
|
||||
// 按键 hint 放左上角,所以字号更小。
|
||||
QFont Font(App_Func_PickFontFamily(QStringList()
|
||||
<< QStringLiteral("Segoe UI Semibold")
|
||||
<< QStringLiteral("Bahnschrift SemiBold")
|
||||
<< QStringLiteral("Microsoft YaHei UI")));
|
||||
Font.setPointSize(8);
|
||||
Font.setLetterSpacing(QFont::AbsoluteSpacing, 1.0);
|
||||
return Font;
|
||||
}
|
||||
|
||||
QString APP_Theme::App_Func_PickFontFamily(const QStringList& FamilyList)
|
||||
{
|
||||
// 从候选字体里依次挑选系统真实存在的字体。
|
||||
const QFontDatabase Database;
|
||||
const QStringList AvailableFamilyList = Database.families();
|
||||
|
||||
for (int Index = 0; Index < FamilyList.size(); ++Index)
|
||||
{
|
||||
const QString& Family = FamilyList.at(Index);
|
||||
if (AvailableFamilyList.contains(Family))
|
||||
{
|
||||
return Family;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果都不存在,就退回 Qt 当前默认字体。
|
||||
return QApplication::font().family();
|
||||
}
|
||||
|
||||
} // namespace APP
|
||||
38
APP/APP_Theme.h
Normal file
38
APP/APP_Theme.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtGui/QFont>
|
||||
#include <QtGui/QPalette>
|
||||
|
||||
namespace APP {
|
||||
|
||||
/*
|
||||
* 主题模块现在只保留一套固定暗色风格。
|
||||
*
|
||||
* - 不参与 DRI 枚举
|
||||
* - 不参与协议解析
|
||||
* - 不参与业务判断
|
||||
*/
|
||||
class APP_Theme
|
||||
{
|
||||
public:
|
||||
// 返回标准控件使用的统一调色板。
|
||||
static QPalette App_Func_GetPalette();
|
||||
|
||||
// 正文说明文字的默认字体。
|
||||
static QFont App_Func_GetBodyFont();
|
||||
// 页面标题字体。
|
||||
static QFont App_Func_GetTitleFont();
|
||||
// 指标、卡片主标题使用的强调字体。
|
||||
static QFont App_Func_GetMetricFont();
|
||||
// 键帽中央主文字字体。
|
||||
static QFont App_Func_GetKeyLabelFont();
|
||||
// 键帽角落提示文字字体。
|
||||
static QFont App_Func_GetKeyHintFont();
|
||||
|
||||
private:
|
||||
// 从候选字体列表中挑出当前系统真实存在的一项。
|
||||
static QString App_Func_PickFontFamily(const QStringList& FamilyList);
|
||||
};
|
||||
|
||||
} // namespace APP
|
||||
420
APP/APP_UIWindow.cpp
Normal file
420
APP/APP_UIWindow.cpp
Normal file
@@ -0,0 +1,420 @@
|
||||
#include "APP/APP_UIWindow.h"
|
||||
|
||||
#include "APP/APP_GlassCard.h"
|
||||
#include "APP/APP_KeyButton.h"
|
||||
#include "APP/APP_Theme.h"
|
||||
#include "APP/APP_UIWindow_Private.h"
|
||||
#include "LOGIC/Lgc_Func_Button.h"
|
||||
#include <QtCore/QSignalBlocker>
|
||||
#include <QtGui/QPainter>
|
||||
#include <QtWidgets/QComboBox>
|
||||
#include <QtWidgets/QFormLayout>
|
||||
#include <QtWidgets/QGridLayout>
|
||||
#include <QtWidgets/QHeaderView>
|
||||
#include <QtWidgets/QHBoxLayout>
|
||||
#include <QtWidgets/QLabel>
|
||||
#include <QtWidgets/QLineEdit>
|
||||
#include <QtWidgets/QPushButton>
|
||||
#include <QtWidgets/QSizePolicy>
|
||||
#include <QtWidgets/QStackedWidget>
|
||||
#include <QtWidgets/QTabWidget>
|
||||
#include <QtWidgets/QTableWidget>
|
||||
#include <QtWidgets/QTableWidgetItem>
|
||||
#include <QtWidgets/QVBoxLayout>
|
||||
|
||||
namespace APP {
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
QLabel* App_Func_CreateLabel(
|
||||
QWidget* parent,
|
||||
const QString& Text,
|
||||
const QFont& Font,
|
||||
bool WordWrap = false)
|
||||
{
|
||||
QLabel* p_Label = new QLabel(Text, parent);
|
||||
p_Label->setFont(Font);
|
||||
p_Label->setAttribute(Qt::WA_TranslucentBackground, true);
|
||||
p_Label->setWordWrap(WordWrap);
|
||||
return p_Label;
|
||||
}
|
||||
|
||||
APP_GlassCard* App_Func_CreateCard(QWidget* parent, QVBoxLayout** pp_Layout)
|
||||
{
|
||||
APP_GlassCard* p_Card = new APP_GlassCard(parent);
|
||||
QVBoxLayout* p_Layout = new QVBoxLayout(p_Card);
|
||||
p_Layout->setContentsMargins(20, 20, 20, 20);
|
||||
p_Layout->setSpacing(14);
|
||||
*pp_Layout = p_Layout;
|
||||
return p_Card;
|
||||
}
|
||||
|
||||
void App_Func_SetGridStretch(QGridLayout* p_Grid, int ColumnCount, int RowCount)
|
||||
{
|
||||
for (int Column = 0; Column < ColumnCount; ++Column)
|
||||
{
|
||||
p_Grid->setColumnStretch(Column, 1);
|
||||
}
|
||||
for (int Row = 0; Row < RowCount; ++Row)
|
||||
{
|
||||
p_Grid->setRowStretch(Row, 1);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int App_Func_GetFeatureStackIndex(Lgc_FunctionFeature_Type Type)
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case Lgc_FunctionFeature_Type::KeyCombination:
|
||||
case Lgc_FunctionFeature_Type::KeySequence:
|
||||
return 0;
|
||||
|
||||
case Lgc_FunctionFeature_Type::Website:
|
||||
return 1;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool App_Func_IsKeyRecordFeatureType(Lgc_FunctionFeature_Type Type)
|
||||
{
|
||||
return (Type == Lgc_FunctionFeature_Type::KeyCombination) ||
|
||||
(Type == Lgc_FunctionFeature_Type::KeySequence);
|
||||
}
|
||||
|
||||
QString App_Func_GetWebsiteEditText(const QString& UrlText)
|
||||
{
|
||||
const QString DisplayText = UrlText.trimmed();
|
||||
return DisplayText.isEmpty() ? QStringLiteral("https://") : DisplayText;
|
||||
}
|
||||
|
||||
App_UIWindow::App_UIWindow(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
App_Func_InitWindow();
|
||||
App_Func_InitUi();
|
||||
App_Func_InitConnect();
|
||||
App_Func_InitLogic();
|
||||
App_Func_RefreshUi();
|
||||
}
|
||||
|
||||
App_UIWindow::~App_UIWindow()
|
||||
{
|
||||
Lgc_Core_Close(&appLgcState);
|
||||
}
|
||||
|
||||
void App_UIWindow::paintEvent(QPaintEvent* event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
QPainter Painter(this);
|
||||
Painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
Painter.fillRect(rect(), palette().color(QPalette::Window));
|
||||
}
|
||||
|
||||
void App_UIWindow::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
App_Func_UpdateFeatureEditorHeight();
|
||||
}
|
||||
|
||||
bool App_UIWindow::nativeEvent(const QByteArray& EventType, void* p_Message, long* p_Result)
|
||||
{
|
||||
Q_UNUSED(EventType);
|
||||
App_Func_HandleSequenceRecordMessage(p_Message);
|
||||
Lgc_Core_HandleNativeMessage(&appLgcState, p_Message);
|
||||
return QWidget::nativeEvent(EventType, p_Message, p_Result);
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_InitWindow()
|
||||
{
|
||||
setWindowTitle(QStringLiteral("数字键盘上位机"));
|
||||
#if APP_ENABLE_DEBUG_WINDOW
|
||||
setMinimumSize(820, 960);
|
||||
resize(900, 1040);
|
||||
#else
|
||||
setMinimumSize(760, 820);
|
||||
resize(820, 900);
|
||||
#endif
|
||||
setAttribute(Qt::WA_StyledBackground, true);
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_InitUi()
|
||||
{
|
||||
QVBoxLayout* p_RootLayout = new QVBoxLayout(this);
|
||||
p_RootLayout->setContentsMargins(26, 22, 26, 24);
|
||||
p_RootLayout->setSpacing(14);
|
||||
|
||||
appPageTab = new QTabWidget(this);
|
||||
appPageTab->setDocumentMode(true);
|
||||
appPageTab->setMovable(false);
|
||||
|
||||
appPageTab->addTab(App_Func_CreatePadCard(), QStringLiteral("按键映射"));
|
||||
appFeaturePageIndex = appPageTab->addTab(App_Func_CreateFunctionConfigCard(), QStringLiteral("功能表"));
|
||||
#if APP_ENABLE_DEBUG_WINDOW
|
||||
appPageTab->addTab(App_Func_CreateDebugCard(), QStringLiteral("调试"));
|
||||
#endif
|
||||
|
||||
p_RootLayout->addWidget(appPageTab, 1);
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_InitConnect()
|
||||
{
|
||||
connect(&appTimerPoll, &QTimer::timeout, this, &App_UIWindow::App_Func_OnPollTimer);
|
||||
connect(&appTimerAutoRefreshDevice, &QTimer::timeout, this, [this]()
|
||||
{
|
||||
if (appLgcState.IsConnected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const QString OldTextLog = appLgcState.TextLog;
|
||||
Lgc_Core_RefreshDevice(&appLgcState);
|
||||
if (!appLgcState.IsConnected)
|
||||
{
|
||||
appLgcState.TextLog = OldTextLog;
|
||||
}
|
||||
App_Func_RefreshAfterLogicChange();
|
||||
});
|
||||
|
||||
connect(appFeatureAddButton, &QPushButton::clicked, this, &App_UIWindow::App_Func_AddFeature);
|
||||
connect(appFeatureDeleteButton, &QPushButton::clicked, this, &App_UIWindow::App_Func_DeleteFeature);
|
||||
|
||||
connect(appFeatureTable, &QTableWidget::itemSelectionChanged, this, [this]()
|
||||
{
|
||||
const QModelIndexList IndexList = appFeatureTable->selectionModel()->selectedRows();
|
||||
if (IndexList.isEmpty())
|
||||
{
|
||||
App_Func_SelectFeature(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const int Row = IndexList.first().row();
|
||||
const QTableWidgetItem* p_Item = appFeatureTable->item(Row, 0);
|
||||
App_Func_SelectFeature(p_Item == nullptr ? 0 : p_Item->data(Qt::UserRole).toInt());
|
||||
});
|
||||
|
||||
const auto ConnectSaveSignal = [this](auto Sender, auto Signal)
|
||||
{
|
||||
connect(Sender, Signal, this, [this]()
|
||||
{
|
||||
if (!appIsUpdatingFeatureUi)
|
||||
{
|
||||
App_Func_SaveFeatureFromUi();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
ConnectSaveSignal(appFeatureNameEdit, &QLineEdit::textChanged);
|
||||
ConnectSaveSignal(appFeatureDescriptionEdit, &QLineEdit::textChanged);
|
||||
ConnectSaveSignal(appFeatureTypeCombo, qOverload<int>(&QComboBox::currentIndexChanged));
|
||||
ConnectSaveSignal(appFeatureSequenceEdit, &QLineEdit::textChanged);
|
||||
ConnectSaveSignal(appFeatureWebsiteEdit, &QLineEdit::textChanged);
|
||||
|
||||
connect(appFeatureSequenceRecordStartButton, &QPushButton::clicked, this, &App_UIWindow::App_Func_StartSequenceRecording);
|
||||
connect(appFeatureSequenceRecordStopButton, &QPushButton::clicked, this, &App_UIWindow::App_Func_StopSequenceRecording);
|
||||
|
||||
#if APP_ENABLE_DEBUG_WINDOW
|
||||
connect(appDebugPanel->Debug_Func_GetRefreshButton(), &QPushButton::clicked, this, &App_UIWindow::App_Func_OnRefreshDeviceClicked);
|
||||
connect(appDebugPanel->Debug_Func_GetClearButton(), &QPushButton::clicked, this, &App_UIWindow::App_Func_OnClearLogClicked);
|
||||
connect(appDebugPanel->Debug_Func_GetApplyConfigButton(), &QPushButton::clicked, this, &App_UIWindow::App_Func_OnApplyDeviceConfigClicked);
|
||||
connect(appDebugPanel->Debug_Func_GetSyncTimeButton(), &QPushButton::clicked, this, &App_UIWindow::App_Func_OnSyncTimeClicked);
|
||||
connect(appDebugPanel->Debug_Func_GetModeSwitchButton(), &QPushButton::clicked, this, &App_UIWindow::App_Func_OnModeSwitchClicked);
|
||||
#endif
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_InitLogic()
|
||||
{
|
||||
Lgc_Core_Init(&appLgcState);
|
||||
Lgc_Core_SetWindowHandle(&appLgcState, reinterpret_cast<void*>(winId()));
|
||||
|
||||
App_Func_RefreshFeatureTable();
|
||||
#if APP_ENABLE_DEBUG_WINDOW
|
||||
App_Func_RefreshDeviceConfigFromState();
|
||||
#endif
|
||||
Lgc_Core_Start(&appLgcState);
|
||||
appTimerPoll.setInterval(30);
|
||||
appTimerPoll.start();
|
||||
appTimerAutoRefreshDevice.setInterval(1500);
|
||||
appTimerAutoRefreshDevice.start();
|
||||
App_Func_RefreshAfterLogicChange();
|
||||
}
|
||||
|
||||
QWidget* App_UIWindow::App_Func_CreatePadCard()
|
||||
{
|
||||
QVBoxLayout* p_Layout = nullptr;
|
||||
APP_GlassCard* p_Card = App_Func_CreateCard(this, &p_Layout);
|
||||
p_Layout->addWidget(App_Func_CreateLabel(
|
||||
p_Card,
|
||||
QStringLiteral("按键映射"),
|
||||
APP_Theme::App_Func_GetMetricFont()));
|
||||
p_Layout->addWidget(App_Func_CreateLabel(
|
||||
p_Card,
|
||||
QStringLiteral("左键直接模拟真实小键盘按下/抬起;右键把当前按键绑定到某个功能。绑定后,键帽左上角会显示功能名,悬停会显示功能简介。"),
|
||||
APP_Theme::App_Func_GetBodyFont(),
|
||||
true));
|
||||
|
||||
QGridLayout* p_Grid = new QGridLayout();
|
||||
p_Grid->setSpacing(14);
|
||||
|
||||
for (const APP_KeyInfo& Key : appKeypadModel.App_Func_GetKeyList())
|
||||
{
|
||||
APP_KeyButton* p_Button = new APP_KeyButton(Key, p_Card);
|
||||
p_Button->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(p_Button, &QPushButton::pressed, this, [this, Key]() { App_Func_HandleUiKeyPressed(Key.usage); });
|
||||
connect(p_Button, &QPushButton::released, this, [this, Key]() { App_Func_HandleUiKeyReleased(Key.usage); });
|
||||
connect(p_Button, &QWidget::customContextMenuRequested, this, [this, p_Button, Key](const QPoint&)
|
||||
{
|
||||
App_Func_ShowKeyMenu(
|
||||
Key.usage,
|
||||
p_Button->mapToGlobal(QPoint(p_Button->width() / 2, p_Button->height() / 2)));
|
||||
});
|
||||
|
||||
appKeypadButtonMap.insert(Key.id, p_Button);
|
||||
p_Grid->addWidget(p_Button, Key.row, Key.column, Key.rowSpan, Key.columnSpan);
|
||||
}
|
||||
|
||||
App_Func_SetGridStretch(p_Grid, 4, 5);
|
||||
p_Layout->addLayout(p_Grid, 1);
|
||||
return p_Card;
|
||||
}
|
||||
|
||||
QWidget* App_UIWindow::App_Func_CreateFunctionConfigCard()
|
||||
{
|
||||
QVBoxLayout* p_Layout = nullptr;
|
||||
APP_GlassCard* p_Card = App_Func_CreateCard(this, &p_Layout);
|
||||
const auto AddRow = [p_Card](QFormLayout* p_Form, const QString& LabelText, QWidget* p_Field)
|
||||
{
|
||||
p_Form->addRow(App_Func_CreateLabel(p_Card, LabelText, APP_Theme::App_Func_GetBodyFont()), p_Field);
|
||||
};
|
||||
|
||||
p_Layout->addWidget(App_Func_CreateLabel(
|
||||
p_Card,
|
||||
QStringLiteral("功能表"),
|
||||
APP_Theme::App_Func_GetMetricFont()));
|
||||
p_Layout->addWidget(App_Func_CreateLabel(
|
||||
p_Card,
|
||||
QStringLiteral("功能表默认为空,请先添加功能,再把按钮绑定到某个功能。当前支持“快捷键”“快捷键序列”和“打开网址”三种功能类型。"),
|
||||
APP_Theme::App_Func_GetBodyFont(),
|
||||
true));
|
||||
|
||||
QHBoxLayout* p_TopRow = new QHBoxLayout();
|
||||
p_TopRow->setContentsMargins(0, 0, 0, 0);
|
||||
p_TopRow->setSpacing(10);
|
||||
appFeatureAddButton = new QPushButton(QStringLiteral("添加功能"), p_Card);
|
||||
appFeatureDeleteButton = new QPushButton(QStringLiteral("删除功能"), p_Card);
|
||||
appFeatureDeleteButton->setEnabled(false);
|
||||
p_TopRow->addWidget(appFeatureAddButton);
|
||||
p_TopRow->addWidget(appFeatureDeleteButton);
|
||||
p_TopRow->addStretch(1);
|
||||
p_Layout->addLayout(p_TopRow);
|
||||
|
||||
appFeatureTable = new QTableWidget(p_Card);
|
||||
appFeatureTable->setColumnCount(3);
|
||||
appFeatureTable->setHorizontalHeaderLabels({ QStringLiteral("功能名"), QStringLiteral("功能简介"), QStringLiteral("类型") });
|
||||
appFeatureTable->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
appFeatureTable->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
appFeatureTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
appFeatureTable->setAlternatingRowColors(true);
|
||||
appFeatureTable->verticalHeader()->setVisible(false);
|
||||
appFeatureTable->horizontalHeader()->setStretchLastSection(false);
|
||||
appFeatureTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
|
||||
appFeatureTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
|
||||
appFeatureTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
|
||||
appFeatureTable->setMinimumHeight(220);
|
||||
p_Layout->addWidget(appFeatureTable);
|
||||
|
||||
QFormLayout* p_Form = new QFormLayout();
|
||||
p_Form->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
|
||||
p_Form->setLabelAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
p_Form->setHorizontalSpacing(10);
|
||||
p_Form->setVerticalSpacing(10);
|
||||
|
||||
appFeatureNameEdit = new QLineEdit(p_Card);
|
||||
appFeatureNameEdit->setPlaceholderText(QStringLiteral("例如:功能1 / 打开4399 / 前缀补码"));
|
||||
AddRow(p_Form, QStringLiteral("功能名"), appFeatureNameEdit);
|
||||
|
||||
appFeatureDescriptionEdit = new QLineEdit(p_Card);
|
||||
appFeatureDescriptionEdit->setPlaceholderText(QStringLiteral("例如:打开 4399 首页"));
|
||||
AddRow(p_Form, QStringLiteral("功能简介"), appFeatureDescriptionEdit);
|
||||
|
||||
appFeatureTypeCombo = new QComboBox(p_Card);
|
||||
appFeatureTypeCombo->addItem(
|
||||
Lgc_FunctionButton_GetFeatureTypeText(Lgc_FunctionFeature_Type::KeyCombination),
|
||||
static_cast<int>(Lgc_FunctionFeature_Type::KeyCombination));
|
||||
appFeatureTypeCombo->addItem(
|
||||
Lgc_FunctionButton_GetFeatureTypeText(Lgc_FunctionFeature_Type::KeySequence),
|
||||
static_cast<int>(Lgc_FunctionFeature_Type::KeySequence));
|
||||
appFeatureTypeCombo->addItem(
|
||||
Lgc_FunctionButton_GetFeatureTypeText(Lgc_FunctionFeature_Type::Website),
|
||||
static_cast<int>(Lgc_FunctionFeature_Type::Website));
|
||||
AddRow(p_Form, QStringLiteral("功能类型"), appFeatureTypeCombo);
|
||||
|
||||
appFeatureEditorStack = new QStackedWidget(p_Card);
|
||||
appFeatureEditorStack->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
|
||||
QWidget* p_SequencePage = new QWidget(appFeatureEditorStack);
|
||||
QFormLayout* p_SequenceForm = new QFormLayout(p_SequencePage);
|
||||
p_SequenceForm->setContentsMargins(0, 0, 0, 0);
|
||||
p_SequenceForm->setHorizontalSpacing(10);
|
||||
p_SequenceForm->setVerticalSpacing(10);
|
||||
|
||||
QWidget* p_SequenceEditor = new QWidget(p_SequencePage);
|
||||
QHBoxLayout* p_SequenceEditorLayout = new QHBoxLayout(p_SequenceEditor);
|
||||
p_SequenceEditorLayout->setContentsMargins(0, 0, 0, 0);
|
||||
p_SequenceEditorLayout->setSpacing(8);
|
||||
appFeatureSequenceEdit = new QLineEdit(p_SequenceEditor);
|
||||
appFeatureSequenceEdit->setPlaceholderText(QStringLiteral("例如:Ctrl+C"));
|
||||
appFeatureSequenceRecordStartButton = new QPushButton(QStringLiteral("开始录入"), p_SequenceEditor);
|
||||
appFeatureSequenceRecordStopButton = new QPushButton(QStringLiteral("退出录入"), p_SequenceEditor);
|
||||
appFeatureSequenceRecordStopButton->setEnabled(false);
|
||||
p_SequenceEditorLayout->addWidget(appFeatureSequenceEdit, 1);
|
||||
p_SequenceEditorLayout->addWidget(appFeatureSequenceRecordStartButton);
|
||||
p_SequenceEditorLayout->addWidget(appFeatureSequenceRecordStopButton);
|
||||
p_SequenceForm->addRow(QStringLiteral("快捷键"), p_SequenceEditor);
|
||||
|
||||
QWidget* p_WebsitePage = new QWidget(appFeatureEditorStack);
|
||||
QVBoxLayout* p_WebsiteLayout = new QVBoxLayout(p_WebsitePage);
|
||||
p_WebsiteLayout->setContentsMargins(0, 0, 0, 0);
|
||||
p_WebsiteLayout->setSpacing(0);
|
||||
appFeatureWebsiteEdit = new QLineEdit(p_WebsitePage);
|
||||
appFeatureWebsiteEdit->setPlaceholderText(QStringLiteral("例如直接输入 4399.com 或 www.4399.com"));
|
||||
p_WebsiteLayout->addWidget(appFeatureWebsiteEdit);
|
||||
|
||||
appFeatureEditorStack->addWidget(p_SequencePage);
|
||||
appFeatureEditorStack->addWidget(p_WebsitePage);
|
||||
AddRow(p_Form, QStringLiteral("功能参数"), appFeatureEditorStack);
|
||||
|
||||
appFeatureBindingSummaryLabel = App_Func_CreateLabel(
|
||||
p_Card,
|
||||
QStringLiteral("当前还没有功能,请点击“添加功能”。"),
|
||||
APP_Theme::App_Func_GetBodyFont(),
|
||||
true);
|
||||
AddRow(p_Form, QStringLiteral("绑定情况"), appFeatureBindingSummaryLabel);
|
||||
|
||||
appFunctionLabelStatus = App_Func_CreateLabel(
|
||||
p_Card,
|
||||
QStringLiteral("等待按键动作。"),
|
||||
APP_Theme::App_Func_GetBodyFont(),
|
||||
true);
|
||||
AddRow(p_Form, QStringLiteral("最近一次动作"), appFunctionLabelStatus);
|
||||
|
||||
p_Layout->addLayout(p_Form);
|
||||
p_Layout->addStretch(1);
|
||||
return p_Card;
|
||||
}
|
||||
|
||||
#if APP_ENABLE_DEBUG_WINDOW
|
||||
QWidget* App_UIWindow::App_Func_CreateDebugCard()
|
||||
{
|
||||
appDebugPanel = new DEBUG::Debug_Panel(this);
|
||||
appDebugPanel->Debug_Func_SetConnectionText(QStringLiteral("未连接,等待枚举设备。"), false);
|
||||
appDebugPanel->Debug_Func_SetLogText(QStringLiteral("等待收到输入包。"));
|
||||
return appDebugPanel;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace APP
|
||||
125
APP/APP_UIWindow.h
Normal file
125
APP/APP_UIWindow.h
Normal file
@@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
|
||||
#include "APP/APP_KeypadModel.h"
|
||||
#include "DEBUG/Debug_Config.h"
|
||||
#include "LOGIC/Lgc_Core.h"
|
||||
#include <QtCore/QHash>
|
||||
#include <QtCore/QSet>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
#if APP_ENABLE_DEBUG_WINDOW
|
||||
#include "DEBUG/Debug_Panel.h"
|
||||
#endif
|
||||
|
||||
class QLabel;
|
||||
class QComboBox;
|
||||
class QLineEdit;
|
||||
class QPushButton;
|
||||
class QResizeEvent;
|
||||
class QStackedWidget;
|
||||
class QTabWidget;
|
||||
class QTableWidget;
|
||||
|
||||
namespace APP {
|
||||
|
||||
class APP_KeyButton;
|
||||
|
||||
class App_UIWindow : public QWidget
|
||||
{
|
||||
public:
|
||||
explicit App_UIWindow(QWidget* parent = nullptr);
|
||||
~App_UIWindow() override;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
bool nativeEvent(const QByteArray& EventType, void* p_Message, long* p_Result) override;
|
||||
|
||||
private:
|
||||
void App_Func_InitWindow();
|
||||
void App_Func_InitUi();
|
||||
void App_Func_InitConnect();
|
||||
void App_Func_InitLogic();
|
||||
void App_Func_RefreshUi();
|
||||
void App_Func_RefreshKeypadState();
|
||||
void App_Func_RefreshKeypadButtons();
|
||||
void App_Func_RefreshFeatureTable();
|
||||
void App_Func_RefreshFunctionStatus();
|
||||
void App_Func_RefreshDebugView();
|
||||
void App_Func_RefreshAfterLogicChange();
|
||||
void App_Func_SelectFeature(int FeatureId, bool SwitchToFeaturePage = false);
|
||||
void App_Func_SaveFeatureFromUi();
|
||||
void App_Func_UpdateFeatureEditorState();
|
||||
void App_Func_UpdateFeatureEditorHeight();
|
||||
void App_Func_AddFeature();
|
||||
void App_Func_DeleteFeature();
|
||||
void App_Func_AssignFeatureToUsage(quint16 Usage, int FeatureId);
|
||||
void App_Func_HandleUiKeyPressed(quint16 Usage);
|
||||
void App_Func_HandleUiKeyReleased(quint16 Usage);
|
||||
void App_Func_ShowKeyMenu(quint16 Usage, const QPoint& GlobalPos);
|
||||
void App_Func_StartSequenceRecording();
|
||||
void App_Func_StopSequenceRecording();
|
||||
void App_Func_UpdateSequenceRecordingUi();
|
||||
bool App_Func_HandleSequenceRecordMessage(void* p_Message);
|
||||
void App_Func_AppendRecordedSequenceToken(const QString& Token);
|
||||
QString App_Func_GetKeyHintText(const QString& KeyId) const;
|
||||
QString App_Func_GetFeatureNameById(int FeatureId) const;
|
||||
QString App_Func_GetFeatureDescriptionById(int FeatureId) const;
|
||||
QString App_Func_GetFeatureDescriptionForUsage(quint16 Usage) const;
|
||||
QString App_Func_GetDefaultKeyDescription(quint16 Usage) const;
|
||||
QString App_Func_GetFeatureBindingSummary(int FeatureId) const;
|
||||
void App_Func_OnPollTimer();
|
||||
|
||||
|
||||
#if APP_ENABLE_DEBUG_WINDOW
|
||||
void App_Func_OnApplyDeviceConfigClicked();
|
||||
void App_Func_OnRefreshDeviceClicked();
|
||||
void App_Func_OnClearLogClicked();
|
||||
void App_Func_OnSyncTimeClicked();
|
||||
void App_Func_OnModeSwitchClicked();
|
||||
void App_Func_RefreshDeviceConfigFromState();
|
||||
#endif
|
||||
|
||||
QWidget* App_Func_CreatePadCard();
|
||||
QWidget* App_Func_CreateFunctionConfigCard();
|
||||
|
||||
#if APP_ENABLE_DEBUG_WINDOW
|
||||
QWidget* App_Func_CreateDebugCard();
|
||||
#endif
|
||||
|
||||
APP_KeypadModel appKeypadModel;
|
||||
QHash<QString, APP_KeyButton*> appKeypadButtonMap;
|
||||
QTabWidget* appPageTab = nullptr;
|
||||
int appFeaturePageIndex = 0;
|
||||
|
||||
QLabel* appFunctionLabelStatus = nullptr;
|
||||
QTableWidget* appFeatureTable = nullptr;
|
||||
QPushButton* appFeatureAddButton = nullptr;
|
||||
QPushButton* appFeatureDeleteButton = nullptr;
|
||||
QLabel* appFeatureParameterHintLabel = nullptr;
|
||||
QLabel* appFeatureBindingSummaryLabel = nullptr;
|
||||
QLineEdit* appFeatureNameEdit = nullptr;
|
||||
QLineEdit* appFeatureDescriptionEdit = nullptr;
|
||||
QComboBox* appFeatureTypeCombo = nullptr;
|
||||
QStackedWidget* appFeatureEditorStack = nullptr;
|
||||
QLineEdit* appFeatureSequenceEdit = nullptr;
|
||||
QLineEdit* appFeatureWebsiteEdit = nullptr;
|
||||
QPushButton* appFeatureSequenceRecordStartButton = nullptr;
|
||||
QPushButton* appFeatureSequenceRecordStopButton = nullptr;
|
||||
bool appIsUpdatingFeatureUi = false;
|
||||
bool appIsSequenceRecording = false;
|
||||
int appSelectedFeatureId = 0;
|
||||
QSet<quint16> appUiPressedUsageSet;
|
||||
QSet<QString> appSequenceRecordingPressedKeySet;
|
||||
|
||||
#if APP_ENABLE_DEBUG_WINDOW
|
||||
DEBUG::Debug_Panel* appDebugPanel = nullptr;
|
||||
#endif
|
||||
|
||||
QTimer appTimerPoll;
|
||||
QTimer appTimerAutoRefreshDevice;
|
||||
Lgc_Core_Struct_State appLgcState;
|
||||
};
|
||||
|
||||
} // namespace APP
|
||||
519
APP/APP_UIWindow_Feature.cpp
Normal file
519
APP/APP_UIWindow_Feature.cpp
Normal file
@@ -0,0 +1,519 @@
|
||||
#include "APP/APP_UIWindow.h"
|
||||
|
||||
#include "APP/APP_KeyButton.h"
|
||||
#include "APP/APP_UIWindow_Private.h"
|
||||
#include "DEBUG/Debug_Panel.h"
|
||||
#include <QtCore/QSignalBlocker>
|
||||
#include <QtWidgets/QComboBox>
|
||||
#include <QtWidgets/QLabel>
|
||||
#include <QtWidgets/QLayout>
|
||||
#include <QtWidgets/QLineEdit>
|
||||
#include <QtWidgets/QPushButton>
|
||||
#include <QtWidgets/QStackedWidget>
|
||||
#include <QtWidgets/QTableWidget>
|
||||
#include <QtWidgets/QTableWidgetItem>
|
||||
|
||||
namespace APP {
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bool App_Func_HasDebugSendRoute(const Lgc_Core_Struct_State& State)
|
||||
{
|
||||
return State.DriVendorPort.ReadPort.IsOpened || State.DriBlePort.IsConnected;
|
||||
}
|
||||
|
||||
QString App_Func_GetDebugSendModeText(const Lgc_Core_Struct_State& State)
|
||||
{
|
||||
if (State.DriBlePort.IsConnected || State.DriVendorPort.IsBluetoothTransport)
|
||||
{
|
||||
return QStringLiteral("蓝牙模式");
|
||||
}
|
||||
|
||||
if (State.DriVendorPort.ReadPort.IsOpened)
|
||||
{
|
||||
return QStringLiteral("USB 模式");
|
||||
}
|
||||
|
||||
return QStringLiteral("未连接");
|
||||
}
|
||||
|
||||
QString App_Func_GetDebugConfigStatusText(const Lgc_Core_Struct_State& State)
|
||||
{
|
||||
return QStringLiteral("目标 USB 0x%1:0x%2 / 当前发包模式:%3")
|
||||
.arg(State.DeviceConfig.VendorId, 4, 16, QLatin1Char('0'))
|
||||
.arg(State.DeviceConfig.ProductId, 4, 16, QLatin1Char('0'))
|
||||
.arg(App_Func_GetDebugSendModeText(State))
|
||||
.toUpper();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void App_UIWindow::App_Func_RefreshUi()
|
||||
{
|
||||
App_Func_RefreshKeypadButtons();
|
||||
App_Func_RefreshFunctionStatus();
|
||||
update();
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_RefreshKeypadState()
|
||||
{
|
||||
appKeypadModel.App_Func_SetNumLockOn(appLgcState.IsSystemNumLockOn);
|
||||
|
||||
const QVector<quint16>* p_UsageList = appLgcState.IsPhysicalKeyStateValid
|
||||
? &appLgcState.PhysicalUsageList
|
||||
: (appLgcState.IsVisibleKeyStateValid ? &appLgcState.VisibleUsageList : nullptr);
|
||||
if (p_UsageList != nullptr)
|
||||
{
|
||||
appKeypadModel.App_Func_SetPressedKeysFromUsageList(*p_UsageList);
|
||||
}
|
||||
else
|
||||
{
|
||||
appKeypadModel.App_Func_ClearPressedKeys();
|
||||
}
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_RefreshKeypadButtons()
|
||||
{
|
||||
for (auto It = appKeypadButtonMap.begin(); It != appKeypadButtonMap.end(); ++It)
|
||||
{
|
||||
const QString KeyId = It.key();
|
||||
const quint16 Usage = appKeypadModel.App_Func_GetUsageFromKeyId(KeyId);
|
||||
const bool HasFeature =
|
||||
Lgc_FunctionButton_HasUsageFeature(appLgcState.FunctionButtonConfig, Usage);
|
||||
APP_KeyButton* p_Button = It.value();
|
||||
p_Button->App_Func_SetLatched(appKeypadModel.App_Func_IsLatched(KeyId) || HasFeature);
|
||||
p_Button->App_Func_SetPressed(appKeypadModel.App_Func_IsPressed(KeyId));
|
||||
p_Button->App_Func_SetHintText(App_Func_GetKeyHintText(KeyId));
|
||||
p_Button->setToolTip(App_Func_GetFeatureDescriptionForUsage(Usage));
|
||||
}
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_RefreshFeatureTable()
|
||||
{
|
||||
if (appFeatureTable == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const QVector<int> FeatureIdList = Lgc_FunctionButton_GetFeatureIdList(appLgcState.FunctionButtonConfig);
|
||||
QSignalBlocker Blocker(appFeatureTable);
|
||||
appFeatureTable->clearContents();
|
||||
appFeatureTable->setRowCount(FeatureIdList.size());
|
||||
|
||||
int TargetRow = -1;
|
||||
for (int Row = 0; Row < FeatureIdList.size(); ++Row)
|
||||
{
|
||||
const int FeatureId = FeatureIdList.at(Row);
|
||||
const Lgc_FunctionFeature_Definition Feature = Lgc_FunctionButton_GetFeature(appLgcState.FunctionButtonConfig, FeatureId);
|
||||
QTableWidgetItem* p_NameItem = new QTableWidgetItem(Lgc_FunctionButton_GetFeatureName(Feature));
|
||||
p_NameItem->setData(Qt::UserRole, FeatureId);
|
||||
appFeatureTable->setItem(Row, 0, p_NameItem);
|
||||
appFeatureTable->setItem(Row, 1, new QTableWidgetItem(App_Func_GetFeatureDescriptionById(FeatureId)));
|
||||
appFeatureTable->setItem(Row, 2, new QTableWidgetItem(Lgc_FunctionButton_GetFeatureTypeText(Feature.Type)));
|
||||
if (FeatureId == appSelectedFeatureId)
|
||||
{
|
||||
TargetRow = Row;
|
||||
}
|
||||
}
|
||||
|
||||
appSelectedFeatureId =
|
||||
TargetRow >= 0 ? appSelectedFeatureId : (FeatureIdList.isEmpty() ? 0 : FeatureIdList.first());
|
||||
TargetRow = TargetRow >= 0 ? TargetRow : (FeatureIdList.isEmpty() ? -1 : 0);
|
||||
if (TargetRow >= 0)
|
||||
{
|
||||
appFeatureTable->selectRow(TargetRow);
|
||||
}
|
||||
else
|
||||
{
|
||||
appFeatureTable->clearSelection();
|
||||
}
|
||||
App_Func_UpdateFeatureEditorState();
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_RefreshFunctionStatus()
|
||||
{
|
||||
if (appFunctionLabelStatus != nullptr)
|
||||
{
|
||||
appFunctionLabelStatus->setText(
|
||||
appLgcState.TextFunctionStatus.isEmpty()
|
||||
? QStringLiteral("等待按键动作。")
|
||||
: appLgcState.TextFunctionStatus);
|
||||
}
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_RefreshDebugView()
|
||||
{
|
||||
#if APP_ENABLE_DEBUG_WINDOW
|
||||
appDebugPanel->Debug_Func_SetConfigStatusText(
|
||||
App_Func_GetDebugConfigStatusText(appLgcState),
|
||||
App_Func_HasDebugSendRoute(appLgcState));
|
||||
appDebugPanel->Debug_Func_SetConnectionText(
|
||||
appLgcState.IsConnected ? QStringLiteral("连接成功") : QStringLiteral("连接失败"),
|
||||
appLgcState.IsConnected);
|
||||
appDebugPanel->Debug_Func_SetLogText(appLgcState.TextLog);
|
||||
#endif
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_RefreshAfterLogicChange()
|
||||
{
|
||||
App_Func_RefreshKeypadState();
|
||||
App_Func_RefreshDebugView();
|
||||
App_Func_RefreshUi();
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_SelectFeature(int FeatureId, bool SwitchToFeaturePage)
|
||||
{
|
||||
if (appIsSequenceRecording && (FeatureId != appSelectedFeatureId))
|
||||
{
|
||||
App_Func_StopSequenceRecording();
|
||||
}
|
||||
|
||||
appSelectedFeatureId =
|
||||
appLgcState.FunctionButtonConfig.FeatureMap.contains(FeatureId) ? FeatureId : 0;
|
||||
|
||||
if ((appFeatureTable != nullptr) && (appSelectedFeatureId > 0))
|
||||
{
|
||||
for (int Row = 0; Row < appFeatureTable->rowCount(); ++Row)
|
||||
{
|
||||
QTableWidgetItem* p_Item = appFeatureTable->item(Row, 0);
|
||||
if ((p_Item != nullptr) && (p_Item->data(Qt::UserRole).toInt() == appSelectedFeatureId))
|
||||
{
|
||||
QSignalBlocker Blocker(appFeatureTable);
|
||||
appFeatureTable->selectRow(Row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
App_Func_UpdateFeatureEditorState();
|
||||
if (SwitchToFeaturePage && (appPageTab != nullptr))
|
||||
{
|
||||
appPageTab->setCurrentIndex(appFeaturePageIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_SaveFeatureFromUi()
|
||||
{
|
||||
if (appSelectedFeatureId <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Lgc_FunctionFeature_Definition Feature =
|
||||
Lgc_FunctionButton_GetFeature(appLgcState.FunctionButtonConfig, appSelectedFeatureId);
|
||||
if (Feature.Id <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Feature.Name = appFeatureNameEdit->text();
|
||||
Feature.Description = appFeatureDescriptionEdit->text();
|
||||
Feature.Type = static_cast<Lgc_FunctionFeature_Type>(appFeatureTypeCombo->currentData().toInt());
|
||||
Feature.SequenceText = appFeatureSequenceEdit->text();
|
||||
Feature.WebsiteUrl = appFeatureWebsiteEdit->text();
|
||||
if ((Feature.Type == Lgc_FunctionFeature_Type::Website) &&
|
||||
Feature.WebsiteUrl.trimmed().isEmpty())
|
||||
{
|
||||
Feature.WebsiteUrl = QStringLiteral("https://");
|
||||
QSignalBlocker Blocker(appFeatureWebsiteEdit);
|
||||
appFeatureWebsiteEdit->setText(Feature.WebsiteUrl);
|
||||
}
|
||||
|
||||
Lgc_FunctionButton_SetFeature(&appLgcState.FunctionButtonConfig, Feature);
|
||||
|
||||
appFeatureEditorStack->setCurrentIndex(App_Func_GetFeatureStackIndex(Feature.Type));
|
||||
App_Func_UpdateFeatureEditorHeight();
|
||||
App_Func_RefreshFeatureTable();
|
||||
App_Func_RefreshUi();
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_UpdateFeatureEditorHeight()
|
||||
{
|
||||
if (appFeatureEditorStack == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QWidget* p_CurrentPage = appFeatureEditorStack->currentWidget();
|
||||
if (p_CurrentPage == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int AvailableWidth = qMax(
|
||||
p_CurrentPage->contentsRect().width(),
|
||||
appFeatureEditorStack->contentsRect().width());
|
||||
int Height = p_CurrentPage->minimumSizeHint().height();
|
||||
if (QLayout* p_Layout = p_CurrentPage->layout())
|
||||
{
|
||||
if ((AvailableWidth > 0) && p_Layout->hasHeightForWidth())
|
||||
{
|
||||
Height = qMax(Height, p_Layout->totalHeightForWidth(AvailableWidth));
|
||||
}
|
||||
Height = qMax(Height, p_Layout->sizeHint().height());
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((AvailableWidth > 0) && p_CurrentPage->hasHeightForWidth())
|
||||
{
|
||||
Height = qMax(Height, p_CurrentPage->heightForWidth(AvailableWidth));
|
||||
}
|
||||
Height = qMax(Height, p_CurrentPage->sizeHint().height());
|
||||
}
|
||||
|
||||
if (appFeatureEditorStack->height() != Height)
|
||||
{
|
||||
appFeatureEditorStack->setFixedHeight(Height);
|
||||
}
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_UpdateFeatureEditorState()
|
||||
{
|
||||
const Lgc_FunctionFeature_Definition Feature =
|
||||
Lgc_FunctionButton_GetFeature(appLgcState.FunctionButtonConfig, appSelectedFeatureId);
|
||||
const bool HasFeature = Feature.Id > 0;
|
||||
|
||||
if (appIsSequenceRecording && (!HasFeature || !App_Func_IsKeyRecordFeatureType(Feature.Type)))
|
||||
{
|
||||
App_Func_StopSequenceRecording();
|
||||
}
|
||||
|
||||
appFeatureNameEdit->setEnabled(HasFeature);
|
||||
appFeatureDescriptionEdit->setEnabled(HasFeature);
|
||||
appFeatureTypeCombo->setEnabled(HasFeature);
|
||||
appFeatureEditorStack->setEnabled(HasFeature);
|
||||
if (appFeatureDeleteButton != nullptr)
|
||||
{
|
||||
appFeatureDeleteButton->setEnabled(HasFeature);
|
||||
}
|
||||
|
||||
appIsUpdatingFeatureUi = true;
|
||||
if (!HasFeature)
|
||||
{
|
||||
appFeatureNameEdit->clear();
|
||||
appFeatureDescriptionEdit->clear();
|
||||
appFeatureSequenceEdit->clear();
|
||||
appFeatureWebsiteEdit->clear();
|
||||
appFeatureTypeCombo->setCurrentIndex(0);
|
||||
appFeatureEditorStack->setCurrentIndex(0);
|
||||
App_Func_UpdateFeatureEditorHeight();
|
||||
appFeatureBindingSummaryLabel->setText(QStringLiteral("当前还没有功能,请点击“添加功能”。"));
|
||||
appIsUpdatingFeatureUi = false;
|
||||
App_Func_UpdateSequenceRecordingUi();
|
||||
return;
|
||||
}
|
||||
|
||||
appFeatureNameEdit->setText(Feature.Name);
|
||||
appFeatureDescriptionEdit->setText(Feature.Description);
|
||||
const int TypeIndex = appFeatureTypeCombo->findData(static_cast<int>(Feature.Type));
|
||||
if (TypeIndex >= 0)
|
||||
{
|
||||
appFeatureTypeCombo->setCurrentIndex(TypeIndex);
|
||||
}
|
||||
appFeatureSequenceEdit->setText(Feature.SequenceText);
|
||||
appFeatureWebsiteEdit->setText(App_Func_GetWebsiteEditText(Feature.WebsiteUrl));
|
||||
appFeatureEditorStack->setCurrentIndex(App_Func_GetFeatureStackIndex(Feature.Type));
|
||||
appFeatureSequenceEdit->setPlaceholderText(
|
||||
Feature.Type == Lgc_FunctionFeature_Type::KeySequence
|
||||
? QStringLiteral("例如:Ctrl+C -> Ctrl+A")
|
||||
: QStringLiteral("例如:Ctrl+C"));
|
||||
App_Func_UpdateFeatureEditorHeight();
|
||||
appFeatureBindingSummaryLabel->setText(App_Func_GetFeatureBindingSummary(Feature.Id));
|
||||
appIsUpdatingFeatureUi = false;
|
||||
App_Func_UpdateSequenceRecordingUi();
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_AddFeature()
|
||||
{
|
||||
const int FeatureId = Lgc_FunctionButton_AddFeature(&appLgcState.FunctionButtonConfig);
|
||||
App_Func_RefreshFeatureTable();
|
||||
App_Func_SelectFeature(FeatureId, true);
|
||||
App_Func_RefreshUi();
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_DeleteFeature()
|
||||
{
|
||||
if (appSelectedFeatureId <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (appIsSequenceRecording)
|
||||
{
|
||||
App_Func_StopSequenceRecording();
|
||||
}
|
||||
|
||||
const QString FeatureName = App_Func_GetFeatureNameById(appSelectedFeatureId);
|
||||
Lgc_FunctionButton_RemoveFeature(&appLgcState.FunctionButtonConfig, appSelectedFeatureId);
|
||||
appSelectedFeatureId = 0;
|
||||
Lgc_Core_ApplyFunctionConfig(&appLgcState);
|
||||
appLgcState.TextFunctionStatus = QStringLiteral("已删除功能:%1").arg(FeatureName);
|
||||
App_Func_RefreshFeatureTable();
|
||||
App_Func_RefreshUi();
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_AssignFeatureToUsage(quint16 Usage, int FeatureId)
|
||||
{
|
||||
if (FeatureId > 0 && !appLgcState.FunctionButtonConfig.FeatureMap.contains(FeatureId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Lgc_FunctionButton_SetUsageFeatureId(&appLgcState.FunctionButtonConfig, Usage, FeatureId);
|
||||
Lgc_Core_ApplyFunctionConfig(&appLgcState);
|
||||
appLgcState.TextFunctionStatus = FeatureId > 0
|
||||
? QStringLiteral("已将按键 %1 绑定到 %2。")
|
||||
.arg(Lgc_FunctionButton_GetUsageShortText(Usage), App_Func_GetFeatureNameById(FeatureId))
|
||||
: QStringLiteral("已清除按键 %1 的功能绑定。")
|
||||
.arg(Lgc_FunctionButton_GetUsageShortText(Usage));
|
||||
App_Func_RefreshFeatureTable();
|
||||
App_Func_RefreshUi();
|
||||
}
|
||||
|
||||
QString App_UIWindow::App_Func_GetKeyHintText(const QString& KeyId) const
|
||||
{
|
||||
const quint16 Usage = appKeypadModel.App_Func_GetUsageFromKeyId(KeyId);
|
||||
const int FeatureId = Lgc_FunctionButton_GetUsageFeatureId(appLgcState.FunctionButtonConfig, Usage);
|
||||
return FeatureId > 0 ? App_Func_GetFeatureNameById(FeatureId) : appKeypadModel.App_Func_GetDefaultHint(KeyId);
|
||||
}
|
||||
|
||||
QString App_UIWindow::App_Func_GetFeatureNameById(int FeatureId) const
|
||||
{
|
||||
const Lgc_FunctionFeature_Definition Feature =
|
||||
Lgc_FunctionButton_GetFeature(appLgcState.FunctionButtonConfig, FeatureId);
|
||||
return Feature.Id > 0 ? Lgc_FunctionButton_GetFeatureName(Feature) : QStringLiteral("无功能");
|
||||
}
|
||||
|
||||
QString App_UIWindow::App_Func_GetFeatureDescriptionById(int FeatureId) const
|
||||
{
|
||||
const Lgc_FunctionFeature_Definition Feature =
|
||||
Lgc_FunctionButton_GetFeature(appLgcState.FunctionButtonConfig, FeatureId);
|
||||
if (Feature.Id <= 0)
|
||||
{
|
||||
return QStringLiteral("未绑定功能。");
|
||||
}
|
||||
|
||||
if (!Feature.Description.trimmed().isEmpty())
|
||||
{
|
||||
return Feature.Description.trimmed();
|
||||
}
|
||||
|
||||
switch (Feature.Type)
|
||||
{
|
||||
case Lgc_FunctionFeature_Type::KeyCombination:
|
||||
return Feature.SequenceText.trimmed().isEmpty()
|
||||
? QStringLiteral("输出一次快捷键,当前还没有配置快捷键。")
|
||||
: QStringLiteral("输出快捷键:%1").arg(Feature.SequenceText.trimmed());
|
||||
|
||||
case Lgc_FunctionFeature_Type::KeySequence:
|
||||
return Feature.SequenceText.trimmed().isEmpty()
|
||||
? QStringLiteral("按顺序输出多组快捷键,当前还没有配置序列。")
|
||||
: QStringLiteral("输出快捷键序列:%1").arg(Feature.SequenceText.trimmed());
|
||||
|
||||
case Lgc_FunctionFeature_Type::Website:
|
||||
return Feature.WebsiteUrl.trimmed().isEmpty()
|
||||
? QStringLiteral("打开网址,当前还没有配置链接。")
|
||||
: QStringLiteral("打开网址:%1").arg(Feature.WebsiteUrl.trimmed());
|
||||
|
||||
default:
|
||||
return QStringLiteral("未知功能。");
|
||||
}
|
||||
}
|
||||
|
||||
QString App_UIWindow::App_Func_GetFeatureDescriptionForUsage(quint16 Usage) const
|
||||
{
|
||||
const int FeatureId = Lgc_FunctionButton_GetUsageFeatureId(appLgcState.FunctionButtonConfig, Usage);
|
||||
return FeatureId > 0 ? App_Func_GetFeatureDescriptionById(FeatureId) : App_Func_GetDefaultKeyDescription(Usage);
|
||||
}
|
||||
|
||||
QString App_UIWindow::App_Func_GetDefaultKeyDescription(quint16 Usage) const
|
||||
{
|
||||
return QStringLiteral("默认小键盘按键:%1。左键模拟真实按下,右键可以绑定功能。")
|
||||
.arg(Lgc_FunctionButton_GetUsageShortText(Usage));
|
||||
}
|
||||
|
||||
QString App_UIWindow::App_Func_GetFeatureBindingSummary(int FeatureId) const
|
||||
{
|
||||
if (FeatureId <= 0)
|
||||
{
|
||||
return QStringLiteral("当前未绑定任何按键。");
|
||||
}
|
||||
|
||||
QStringList KeyList;
|
||||
for (quint16 Usage : Lgc_FunctionButton_GetConfigurableUsages())
|
||||
{
|
||||
if (Lgc_FunctionButton_GetUsageFeatureId(appLgcState.FunctionButtonConfig, Usage) == FeatureId)
|
||||
{
|
||||
KeyList.append(Lgc_FunctionButton_GetUsageShortText(Usage));
|
||||
}
|
||||
}
|
||||
|
||||
return KeyList.isEmpty()
|
||||
? QStringLiteral("当前未绑定任何按键。")
|
||||
: QStringLiteral("当前绑定按键:%1").arg(KeyList.join(QStringLiteral(", ")));
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_OnPollTimer()
|
||||
{
|
||||
if (Lgc_Core_Poll(&appLgcState))
|
||||
{
|
||||
App_Func_RefreshAfterLogicChange();
|
||||
}
|
||||
}
|
||||
|
||||
#if APP_ENABLE_DEBUG_WINDOW
|
||||
void App_UIWindow::App_Func_RefreshDeviceConfigFromState()
|
||||
{
|
||||
appDebugPanel->Debug_Func_SetDeviceConfigText(
|
||||
appLgcState.DeviceConfig.VendorId,
|
||||
appLgcState.DeviceConfig.ProductId);
|
||||
appDebugPanel->Debug_Func_SetConfigStatusText(
|
||||
App_Func_GetDebugConfigStatusText(appLgcState),
|
||||
App_Func_HasDebugSendRoute(appLgcState));
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_OnApplyDeviceConfigClicked()
|
||||
{
|
||||
quint16 VendorId = 0;
|
||||
quint16 ProductId = 0;
|
||||
if (!appDebugPanel->Debug_Func_TryGetDeviceConfig(&VendorId, &ProductId))
|
||||
{
|
||||
appDebugPanel->Debug_Func_SetConfigStatusText(
|
||||
QStringLiteral("VID / PID 输入无效。请使用十六进制。"),
|
||||
false);
|
||||
return;
|
||||
}
|
||||
|
||||
appLgcState.DeviceConfig.VendorId = VendorId;
|
||||
appLgcState.DeviceConfig.ProductId = ProductId;
|
||||
App_Func_OnRefreshDeviceClicked();
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_OnRefreshDeviceClicked()
|
||||
{
|
||||
Lgc_Core_RefreshDevice(&appLgcState);
|
||||
App_Func_RefreshDeviceConfigFromState();
|
||||
App_Func_RefreshAfterLogicChange();
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_OnClearLogClicked()
|
||||
{
|
||||
Lgc_Core_ClearLog(&appLgcState);
|
||||
App_Func_RefreshDebugView();
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_OnSyncTimeClicked()
|
||||
{
|
||||
Lgc_Core_SendTimeSync(&appLgcState);
|
||||
App_Func_RefreshAfterLogicChange();
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_OnModeSwitchClicked()
|
||||
{
|
||||
Lgc_Core_SendThemeSwitch(&appLgcState);
|
||||
App_Func_RefreshAfterLogicChange();
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace APP
|
||||
12
APP/APP_UIWindow_Private.h
Normal file
12
APP/APP_UIWindow_Private.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "LOGIC/Lgc_Func_Button.h"
|
||||
#include <QtCore/QString>
|
||||
|
||||
namespace APP {
|
||||
|
||||
int App_Func_GetFeatureStackIndex(Lgc_FunctionFeature_Type Type);
|
||||
bool App_Func_IsKeyRecordFeatureType(Lgc_FunctionFeature_Type Type);
|
||||
QString App_Func_GetWebsiteEditText(const QString& UrlText);
|
||||
|
||||
} // namespace APP
|
||||
558
APP/APP_UIWindow_Record.cpp
Normal file
558
APP/APP_UIWindow_Record.cpp
Normal file
@@ -0,0 +1,558 @@
|
||||
#include "APP/APP_UIWindow.h"
|
||||
|
||||
#include "APP/APP_UIWindow_Private.h"
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QSignalBlocker>
|
||||
#include <QtWidgets/QComboBox>
|
||||
#include <QtWidgets/QLineEdit>
|
||||
#include <QtWidgets/QMenu>
|
||||
#include <QtWidgets/QPushButton>
|
||||
#include <QtWidgets/QTableWidget>
|
||||
#include <Windows.h>
|
||||
|
||||
namespace APP {
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bool App_Func_IsModifierRecordToken(const QString& Token)
|
||||
{
|
||||
return (Token == QStringLiteral("Ctrl")) ||
|
||||
(Token == QStringLiteral("Shift")) ||
|
||||
(Token == QStringLiteral("Alt")) ||
|
||||
(Token == QStringLiteral("Win"));
|
||||
}
|
||||
|
||||
QStringList App_Func_GetOrderedRecordedModifierTokens(const QSet<QString>& PressedKeySet)
|
||||
{
|
||||
const QStringList ModifierOrder = {
|
||||
QStringLiteral("Ctrl"),
|
||||
QStringLiteral("Shift"),
|
||||
QStringLiteral("Alt"),
|
||||
QStringLiteral("Win")
|
||||
};
|
||||
|
||||
QStringList Result;
|
||||
for (const QString& ModifierToken : ModifierOrder)
|
||||
{
|
||||
if (PressedKeySet.contains(ModifierToken))
|
||||
{
|
||||
Result.append(ModifierToken);
|
||||
}
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
QString App_Func_GetRecordedCombinationText(
|
||||
const QSet<QString>& PressedKeySet,
|
||||
const QString& TriggerToken)
|
||||
{
|
||||
QStringList TokenList = App_Func_GetOrderedRecordedModifierTokens(PressedKeySet);
|
||||
if (!TriggerToken.isEmpty() && !App_Func_IsModifierRecordToken(TriggerToken))
|
||||
{
|
||||
TokenList.append(TriggerToken);
|
||||
}
|
||||
return TokenList.join(QStringLiteral("+"));
|
||||
}
|
||||
|
||||
bool App_Func_TryGetRawKeyboard(void* p_Message, RAWKEYBOARD* p_Keyboard)
|
||||
{
|
||||
if ((p_Message == nullptr) || (p_Keyboard == nullptr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
MSG* p_Msg = reinterpret_cast<MSG*>(p_Message);
|
||||
if (p_Msg->message != WM_INPUT)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UINT NeedSize = 0;
|
||||
GetRawInputData(
|
||||
reinterpret_cast<HRAWINPUT>(p_Msg->lParam),
|
||||
RID_INPUT,
|
||||
nullptr,
|
||||
&NeedSize,
|
||||
sizeof(RAWINPUTHEADER));
|
||||
if (NeedSize == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray Buffer(static_cast<int>(NeedSize), 0);
|
||||
if (GetRawInputData(
|
||||
reinterpret_cast<HRAWINPUT>(p_Msg->lParam),
|
||||
RID_INPUT,
|
||||
Buffer.data(),
|
||||
&NeedSize,
|
||||
sizeof(RAWINPUTHEADER)) == static_cast<UINT>(-1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const RAWINPUT* p_Input = reinterpret_cast<const RAWINPUT*>(Buffer.constData());
|
||||
if (p_Input->header.dwType != RIM_TYPEKEYBOARD)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
*p_Keyboard = p_Input->data.keyboard;
|
||||
return true;
|
||||
}
|
||||
|
||||
QString App_Func_GetRecordedKeyToken(const RAWKEYBOARD& Keyboard)
|
||||
{
|
||||
const bool IsE0 = (Keyboard.Flags & RI_KEY_E0) != 0;
|
||||
const USHORT VirtualKey = Keyboard.VKey;
|
||||
|
||||
if ((VirtualKey >= 'A') && (VirtualKey <= 'Z'))
|
||||
{
|
||||
return QString(QChar(static_cast<char16_t>(VirtualKey)));
|
||||
}
|
||||
if ((VirtualKey >= '0') && (VirtualKey <= '9'))
|
||||
{
|
||||
return QString(QChar(static_cast<char16_t>(VirtualKey)));
|
||||
}
|
||||
if ((VirtualKey >= VK_F1) && (VirtualKey <= VK_F24))
|
||||
{
|
||||
return QStringLiteral("F%1").arg(VirtualKey - VK_F1 + 1);
|
||||
}
|
||||
|
||||
switch (VirtualKey)
|
||||
{
|
||||
case VK_CONTROL:
|
||||
case VK_LCONTROL:
|
||||
case VK_RCONTROL:
|
||||
return QStringLiteral("Ctrl");
|
||||
|
||||
case VK_SHIFT:
|
||||
case VK_LSHIFT:
|
||||
case VK_RSHIFT:
|
||||
return QStringLiteral("Shift");
|
||||
|
||||
case VK_MENU:
|
||||
case VK_LMENU:
|
||||
case VK_RMENU:
|
||||
return QStringLiteral("Alt");
|
||||
|
||||
case VK_LWIN:
|
||||
case VK_RWIN:
|
||||
return QStringLiteral("Win");
|
||||
|
||||
case VK_RETURN:
|
||||
return IsE0 ? QStringLiteral("NumEnter") : QStringLiteral("Enter");
|
||||
|
||||
case VK_SPACE:
|
||||
return QStringLiteral("Space");
|
||||
|
||||
case VK_TAB:
|
||||
return QStringLiteral("Tab");
|
||||
|
||||
case VK_ESCAPE:
|
||||
return QStringLiteral("Esc");
|
||||
|
||||
case VK_BACK:
|
||||
return QStringLiteral("Backspace");
|
||||
|
||||
case VK_DELETE:
|
||||
return QStringLiteral("Delete");
|
||||
|
||||
case VK_INSERT:
|
||||
return QStringLiteral("Insert");
|
||||
|
||||
case VK_HOME:
|
||||
return QStringLiteral("Home");
|
||||
|
||||
case VK_END:
|
||||
return QStringLiteral("End");
|
||||
|
||||
case VK_PRIOR:
|
||||
return QStringLiteral("PageUp");
|
||||
|
||||
case VK_NEXT:
|
||||
return QStringLiteral("PageDown");
|
||||
|
||||
case VK_LEFT:
|
||||
return QStringLiteral("Left");
|
||||
|
||||
case VK_RIGHT:
|
||||
return QStringLiteral("Right");
|
||||
|
||||
case VK_UP:
|
||||
return QStringLiteral("Up");
|
||||
|
||||
case VK_DOWN:
|
||||
return QStringLiteral("Down");
|
||||
|
||||
case VK_CAPITAL:
|
||||
return QStringLiteral("CapsLock");
|
||||
|
||||
case VK_SNAPSHOT:
|
||||
return QStringLiteral("PrintScreen");
|
||||
|
||||
case VK_SCROLL:
|
||||
return QStringLiteral("ScrollLock");
|
||||
|
||||
case VK_PAUSE:
|
||||
return QStringLiteral("Pause");
|
||||
|
||||
case VK_NUMPAD0:
|
||||
return QStringLiteral("Num0");
|
||||
|
||||
case VK_NUMPAD1:
|
||||
return QStringLiteral("Num1");
|
||||
|
||||
case VK_NUMPAD2:
|
||||
return QStringLiteral("Num2");
|
||||
|
||||
case VK_NUMPAD3:
|
||||
return QStringLiteral("Num3");
|
||||
|
||||
case VK_NUMPAD4:
|
||||
return QStringLiteral("Num4");
|
||||
|
||||
case VK_NUMPAD5:
|
||||
return QStringLiteral("Num5");
|
||||
|
||||
case VK_NUMPAD6:
|
||||
return QStringLiteral("Num6");
|
||||
|
||||
case VK_NUMPAD7:
|
||||
return QStringLiteral("Num7");
|
||||
|
||||
case VK_NUMPAD8:
|
||||
return QStringLiteral("Num8");
|
||||
|
||||
case VK_NUMPAD9:
|
||||
return QStringLiteral("Num9");
|
||||
|
||||
case VK_DIVIDE:
|
||||
return QStringLiteral("Num/");
|
||||
|
||||
case VK_MULTIPLY:
|
||||
return QStringLiteral("Num*");
|
||||
|
||||
case VK_SUBTRACT:
|
||||
return QStringLiteral("Num-");
|
||||
|
||||
case VK_ADD:
|
||||
return QStringLiteral("Num+");
|
||||
|
||||
case VK_DECIMAL:
|
||||
return QStringLiteral("Num.");
|
||||
|
||||
case VK_OEM_COMMA:
|
||||
return QStringLiteral("Comma");
|
||||
|
||||
case VK_OEM_PERIOD:
|
||||
return QStringLiteral("Period");
|
||||
|
||||
case VK_OEM_1:
|
||||
return QStringLiteral("Semicolon");
|
||||
|
||||
case VK_OEM_2:
|
||||
return QStringLiteral("Slash");
|
||||
|
||||
case VK_OEM_3:
|
||||
return QStringLiteral("Grave");
|
||||
|
||||
case VK_OEM_4:
|
||||
return QStringLiteral("LeftBracket");
|
||||
|
||||
case VK_OEM_5:
|
||||
return QStringLiteral("Backslash");
|
||||
|
||||
case VK_OEM_6:
|
||||
return QStringLiteral("RightBracket");
|
||||
|
||||
case VK_OEM_7:
|
||||
return QStringLiteral("Quote");
|
||||
|
||||
case VK_OEM_MINUS:
|
||||
return QStringLiteral("Minus");
|
||||
|
||||
case VK_OEM_PLUS:
|
||||
return QStringLiteral("Equal");
|
||||
|
||||
default:
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void App_UIWindow::App_Func_StartSequenceRecording()
|
||||
{
|
||||
const Lgc_FunctionFeature_Definition Feature =
|
||||
Lgc_FunctionButton_GetFeature(appLgcState.FunctionButtonConfig, appSelectedFeatureId);
|
||||
if (Feature.Id <= 0 || !App_Func_IsKeyRecordFeatureType(Feature.Type))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
appIsSequenceRecording = true;
|
||||
appSequenceRecordingPressedKeySet.clear();
|
||||
appLgcState.IsFunctionSequenceRecording = true;
|
||||
{
|
||||
QSignalBlocker Blocker(appFeatureSequenceEdit);
|
||||
appFeatureSequenceEdit->setText(QString());
|
||||
}
|
||||
App_Func_SaveFeatureFromUi();
|
||||
appLgcState.TextFunctionStatus =
|
||||
Feature.Type == Lgc_FunctionFeature_Type::KeySequence
|
||||
? QStringLiteral("已开始录入快捷键序列,接下来按下任意键盘按键即可按组追加。")
|
||||
: QStringLiteral("已开始录入快捷键,请按下目标组合键。");
|
||||
App_Func_UpdateSequenceRecordingUi();
|
||||
if (appFeatureSequenceEdit != nullptr)
|
||||
{
|
||||
appFeatureSequenceEdit->setFocus(Qt::OtherFocusReason);
|
||||
}
|
||||
App_Func_RefreshUi();
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_StopSequenceRecording()
|
||||
{
|
||||
appIsSequenceRecording = false;
|
||||
appSequenceRecordingPressedKeySet.clear();
|
||||
appLgcState.IsFunctionSequenceRecording = false;
|
||||
appLgcState.TextFunctionStatus = QStringLiteral("已退出录入,当前功能键配置已保存。");
|
||||
App_Func_UpdateSequenceRecordingUi();
|
||||
App_Func_RefreshUi();
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_UpdateSequenceRecordingUi()
|
||||
{
|
||||
const Lgc_FunctionFeature_Definition Feature =
|
||||
Lgc_FunctionButton_GetFeature(appLgcState.FunctionButtonConfig, appSelectedFeatureId);
|
||||
const bool HasFeature = Feature.Id > 0;
|
||||
const bool CanRecord =
|
||||
HasFeature && App_Func_IsKeyRecordFeatureType(Feature.Type);
|
||||
const bool IsLocked = appIsSequenceRecording;
|
||||
|
||||
if (appFeatureTable != nullptr)
|
||||
{
|
||||
appFeatureTable->setEnabled(!IsLocked);
|
||||
}
|
||||
if (appFeatureAddButton != nullptr)
|
||||
{
|
||||
appFeatureAddButton->setEnabled(!IsLocked);
|
||||
}
|
||||
if (appFeatureDeleteButton != nullptr)
|
||||
{
|
||||
appFeatureDeleteButton->setEnabled(HasFeature && !IsLocked);
|
||||
}
|
||||
if (appFeatureNameEdit != nullptr)
|
||||
{
|
||||
appFeatureNameEdit->setEnabled(HasFeature && !IsLocked);
|
||||
}
|
||||
if (appFeatureDescriptionEdit != nullptr)
|
||||
{
|
||||
appFeatureDescriptionEdit->setEnabled(HasFeature && !IsLocked);
|
||||
}
|
||||
if (appFeatureTypeCombo != nullptr)
|
||||
{
|
||||
appFeatureTypeCombo->setEnabled(HasFeature && !IsLocked);
|
||||
}
|
||||
if (appFeatureWebsiteEdit != nullptr)
|
||||
{
|
||||
appFeatureWebsiteEdit->setEnabled(HasFeature && !IsLocked);
|
||||
}
|
||||
if (appFeatureSequenceEdit != nullptr)
|
||||
{
|
||||
appFeatureSequenceEdit->setReadOnly(appIsSequenceRecording);
|
||||
}
|
||||
if (appFeatureSequenceRecordStartButton != nullptr)
|
||||
{
|
||||
appFeatureSequenceRecordStartButton->setEnabled(CanRecord && !appIsSequenceRecording);
|
||||
}
|
||||
if (appFeatureSequenceRecordStopButton != nullptr)
|
||||
{
|
||||
appFeatureSequenceRecordStopButton->setEnabled(CanRecord && appIsSequenceRecording);
|
||||
}
|
||||
}
|
||||
|
||||
bool App_UIWindow::App_Func_HandleSequenceRecordMessage(void* p_Message)
|
||||
{
|
||||
if (!appIsSequenceRecording)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RAWKEYBOARD Keyboard = {};
|
||||
if (!App_Func_TryGetRawKeyboard(p_Message, &Keyboard))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString Token = App_Func_GetRecordedKeyToken(Keyboard);
|
||||
if (Token.isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool IsPressed = (Keyboard.Flags & RI_KEY_BREAK) == 0;
|
||||
if (IsPressed)
|
||||
{
|
||||
if (appSequenceRecordingPressedKeySet.contains(Token))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
appSequenceRecordingPressedKeySet.insert(Token);
|
||||
if (!App_Func_IsModifierRecordToken(Token))
|
||||
{
|
||||
App_Func_AppendRecordedSequenceToken(Token);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
appSequenceRecordingPressedKeySet.remove(Token);
|
||||
return false;
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_AppendRecordedSequenceToken(const QString& Token)
|
||||
{
|
||||
if (Token.isEmpty() || appFeatureSequenceEdit == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const Lgc_FunctionFeature_Definition Feature =
|
||||
Lgc_FunctionButton_GetFeature(appLgcState.FunctionButtonConfig, appSelectedFeatureId);
|
||||
if (Feature.Id <= 0 || !App_Func_IsKeyRecordFeatureType(Feature.Type))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const QString CombinationText =
|
||||
App_Func_GetRecordedCombinationText(appSequenceRecordingPressedKeySet, Token);
|
||||
if (CombinationText.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const QString OldText = appFeatureSequenceEdit->text().trimmed();
|
||||
const QString NewText =
|
||||
Feature.Type == Lgc_FunctionFeature_Type::KeySequence
|
||||
? (OldText.isEmpty()
|
||||
? CombinationText
|
||||
: QStringLiteral("%1 -> %2").arg(OldText, CombinationText))
|
||||
: CombinationText;
|
||||
{
|
||||
QSignalBlocker Blocker(appFeatureSequenceEdit);
|
||||
appFeatureSequenceEdit->setText(NewText);
|
||||
appFeatureSequenceEdit->setCursorPosition(NewText.size());
|
||||
}
|
||||
|
||||
App_Func_SaveFeatureFromUi();
|
||||
appLgcState.TextFunctionStatus = QStringLiteral("录入中:%1").arg(NewText);
|
||||
App_Func_RefreshUi();
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_HandleUiKeyPressed(quint16 Usage)
|
||||
{
|
||||
QString TextStatus;
|
||||
if (Lgc_FunctionButton_HasUsageFeature(appLgcState.FunctionButtonConfig, Usage))
|
||||
{
|
||||
if (!Lgc_FunctionButton_RunBinding(&appLgcState, Usage, &TextStatus))
|
||||
{
|
||||
TextStatus = QStringLiteral("%1 当前没有可执行功能。")
|
||||
.arg(Lgc_FunctionButton_GetUsageShortText(Usage));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Lgc_FunctionButton_SendUsageToWindows(Usage, true))
|
||||
{
|
||||
appUiPressedUsageSet.insert(Usage);
|
||||
TextStatus = QStringLiteral("已模拟按下 %1。")
|
||||
.arg(Lgc_FunctionButton_GetUsageShortText(Usage));
|
||||
}
|
||||
else
|
||||
{
|
||||
TextStatus = QStringLiteral("模拟按下 %1 失败。")
|
||||
.arg(Lgc_FunctionButton_GetUsageShortText(Usage));
|
||||
}
|
||||
}
|
||||
|
||||
if (!TextStatus.isEmpty())
|
||||
{
|
||||
appLgcState.TextFunctionStatus = TextStatus;
|
||||
}
|
||||
App_Func_RefreshUi();
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_HandleUiKeyReleased(quint16 Usage)
|
||||
{
|
||||
if (!appUiPressedUsageSet.remove(Usage))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
appLgcState.TextFunctionStatus =
|
||||
Lgc_FunctionButton_SendUsageToWindows(Usage, false)
|
||||
? QStringLiteral("已模拟抬起 %1。").arg(Lgc_FunctionButton_GetUsageShortText(Usage))
|
||||
: QStringLiteral("模拟抬起 %1 失败。").arg(Lgc_FunctionButton_GetUsageShortText(Usage));
|
||||
App_Func_RefreshUi();
|
||||
}
|
||||
|
||||
void App_UIWindow::App_Func_ShowKeyMenu(quint16 Usage, const QPoint& GlobalPos)
|
||||
{
|
||||
const int CurrentFeatureId =
|
||||
Lgc_FunctionButton_GetUsageFeatureId(appLgcState.FunctionButtonConfig, Usage);
|
||||
|
||||
QMenu Menu(this);
|
||||
|
||||
QAction* p_NoneAction = Menu.addAction(QStringLiteral("无功能"));
|
||||
p_NoneAction->setCheckable(true);
|
||||
p_NoneAction->setChecked(CurrentFeatureId <= 0);
|
||||
p_NoneAction->setData(0);
|
||||
|
||||
const QVector<int> FeatureIdList =
|
||||
Lgc_FunctionButton_GetFeatureIdList(appLgcState.FunctionButtonConfig);
|
||||
if (!FeatureIdList.isEmpty())
|
||||
{
|
||||
Menu.addSeparator();
|
||||
for (int FeatureId : FeatureIdList)
|
||||
{
|
||||
QAction* p_Action = Menu.addAction(QStringLiteral("绑定到 %1").arg(App_Func_GetFeatureNameById(FeatureId)));
|
||||
p_Action->setCheckable(true);
|
||||
p_Action->setChecked(FeatureId == CurrentFeatureId);
|
||||
p_Action->setData(FeatureId);
|
||||
p_Action->setToolTip(App_Func_GetFeatureDescriptionById(FeatureId));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Menu.addSeparator();
|
||||
QAction* p_EmptyAction = Menu.addAction(QStringLiteral("暂无功能,请先到功能表添加"));
|
||||
p_EmptyAction->setEnabled(false);
|
||||
}
|
||||
|
||||
Menu.addSeparator();
|
||||
QAction* p_OpenFeaturePageAction = Menu.addAction(QStringLiteral("打开功能表"));
|
||||
p_OpenFeaturePageAction->setData(-1);
|
||||
|
||||
QAction* p_SelectedAction = Menu.exec(GlobalPos);
|
||||
if (p_SelectedAction == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int ActionData = p_SelectedAction->data().toInt();
|
||||
if (ActionData == -1)
|
||||
{
|
||||
App_Func_SelectFeature(CurrentFeatureId, true);
|
||||
return;
|
||||
}
|
||||
|
||||
App_Func_AssignFeatureToUsage(Usage, ActionData);
|
||||
if (ActionData > 0)
|
||||
{
|
||||
App_Func_SelectFeature(ActionData);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace APP
|
||||
25
DEBUG/Debug_Config.h
Normal file
25
DEBUG/Debug_Config.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
* 这是调试功能页面的总开关。
|
||||
*
|
||||
* 设为 1 时:
|
||||
* 1. APP 层会创建下方调试窗口
|
||||
* 2. APP 层会连接调试按钮
|
||||
* 3. APP 层会把 LGC 整理好的调试文本显示出来
|
||||
*
|
||||
* 设为 0 时:
|
||||
* 1. APP 层不显示调试窗口
|
||||
* 2. APP 层不连接调试按钮
|
||||
* 3. 但主界面的状态轮询仍然保留
|
||||
*
|
||||
* 也就是说,这个宏控制的是“调试页入口”,
|
||||
* 不是整个程序的主刷新节拍。
|
||||
*
|
||||
* 如果后续要整体删掉调试功能,
|
||||
* 就从 APP_UIWindow 里所有 APP_ENABLE_DEBUG_WINDOW 宏块开始删。
|
||||
*/
|
||||
#ifndef APP_ENABLE_DEBUG_WINDOW
|
||||
// 1 表示编译进调试页,0 表示完全隐藏调试页入口。
|
||||
#define APP_ENABLE_DEBUG_WINDOW 1
|
||||
#endif
|
||||
206
DEBUG/Debug_Panel.cpp
Normal file
206
DEBUG/Debug_Panel.cpp
Normal file
@@ -0,0 +1,206 @@
|
||||
#include "DEBUG/Debug_Panel.h"
|
||||
|
||||
#include "APP/APP_Theme.h"
|
||||
#include <QtCore/QRegularExpression>
|
||||
#include <QtGui/QFontDatabase>
|
||||
#include <QtGui/QRegularExpressionValidator>
|
||||
#include <QtWidgets/QFrame>
|
||||
#include <QtWidgets/QHBoxLayout>
|
||||
#include <QtWidgets/QLabel>
|
||||
#include <QtWidgets/QLineEdit>
|
||||
#include <QtWidgets/QPlainTextEdit>
|
||||
#include <QtWidgets/QPushButton>
|
||||
#include <QtWidgets/QScrollBar>
|
||||
#include <QtWidgets/QVBoxLayout>
|
||||
|
||||
namespace DEBUG {
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
void Debug_Func_SetLabelTextColor(QLabel* p_Label, const QColor& Color)
|
||||
{
|
||||
QPalette Palette = p_Label->palette();
|
||||
Palette.setColor(QPalette::WindowText, Color);
|
||||
p_Label->setPalette(Palette);
|
||||
}
|
||||
|
||||
bool Debug_Func_ParseHexField(QString Text, quint16* p_Value)
|
||||
{
|
||||
Text = Text.trimmed();
|
||||
if (Text.startsWith(QStringLiteral("0x"), Qt::CaseInsensitive))
|
||||
{
|
||||
Text.remove(0, 2);
|
||||
}
|
||||
|
||||
if (Text.isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsOk = false;
|
||||
const quint16 Value = static_cast<quint16>(Text.toUShort(&IsOk, 16));
|
||||
if (IsOk)
|
||||
{
|
||||
*p_Value = Value;
|
||||
}
|
||||
return IsOk;
|
||||
}
|
||||
|
||||
QLabel* Debug_Func_CreateLabel(QWidget* parent, const QString& Text)
|
||||
{
|
||||
QLabel* p_Label = new QLabel(Text, parent);
|
||||
p_Label->setWordWrap(true);
|
||||
p_Label->setFont(APP::APP_Theme::App_Func_GetBodyFont());
|
||||
p_Label->setAttribute(Qt::WA_TranslucentBackground, true);
|
||||
return p_Label;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Debug_Panel::Debug_Panel(QWidget* parent)
|
||||
: APP::APP_GlassCard(parent)
|
||||
{
|
||||
QVBoxLayout* p_RootLayout = new QVBoxLayout(this);
|
||||
p_RootLayout->setContentsMargins(20, 18, 20, 20);
|
||||
p_RootLayout->setSpacing(12);
|
||||
|
||||
QHBoxLayout* p_HeaderLayout = new QHBoxLayout();
|
||||
p_HeaderLayout->setContentsMargins(0, 0, 0, 0);
|
||||
p_HeaderLayout->setSpacing(10);
|
||||
|
||||
QLabel* p_TitleLabel = new QLabel(QStringLiteral("设备协议调试窗口"), this);
|
||||
p_TitleLabel->setWordWrap(true);
|
||||
p_TitleLabel->setFont(APP::APP_Theme::App_Func_GetMetricFont());
|
||||
p_TitleLabel->setAttribute(Qt::WA_TranslucentBackground, true);
|
||||
p_HeaderLayout->addWidget(p_TitleLabel, 1);
|
||||
|
||||
debugButtonRefresh = new QPushButton(QStringLiteral("刷新设备"), this);
|
||||
debugButtonClear = new QPushButton(QStringLiteral("清空日志"), this);
|
||||
p_HeaderLayout->addWidget(debugButtonRefresh);
|
||||
p_HeaderLayout->addWidget(debugButtonClear);
|
||||
p_RootLayout->addLayout(p_HeaderLayout);
|
||||
|
||||
const QRegularExpression HexPattern(QStringLiteral("^(?:0[xX])?[0-9A-Fa-f]{0,4}$"));
|
||||
|
||||
QHBoxLayout* p_ConfigLayout = new QHBoxLayout();
|
||||
p_ConfigLayout->setContentsMargins(0, 0, 0, 0);
|
||||
p_ConfigLayout->setSpacing(8);
|
||||
|
||||
p_ConfigLayout->addWidget(Debug_Func_CreateLabel(this, QStringLiteral("目标 VID")));
|
||||
debugEditVendorId = new QLineEdit(this);
|
||||
debugEditVendorId->setMaxLength(6);
|
||||
debugEditVendorId->setClearButtonEnabled(true);
|
||||
debugEditVendorId->setMinimumWidth(92);
|
||||
debugEditVendorId->setAlignment(Qt::AlignCenter);
|
||||
debugEditVendorId->setValidator(new QRegularExpressionValidator(HexPattern, debugEditVendorId));
|
||||
debugEditVendorId->setPlaceholderText(QStringLiteral("1209"));
|
||||
p_ConfigLayout->addWidget(debugEditVendorId);
|
||||
|
||||
p_ConfigLayout->addWidget(Debug_Func_CreateLabel(this, QStringLiteral("目标 PID")));
|
||||
debugEditProductId = new QLineEdit(this);
|
||||
debugEditProductId->setMaxLength(6);
|
||||
debugEditProductId->setClearButtonEnabled(true);
|
||||
debugEditProductId->setMinimumWidth(92);
|
||||
debugEditProductId->setAlignment(Qt::AlignCenter);
|
||||
debugEditProductId->setValidator(new QRegularExpressionValidator(HexPattern, debugEditProductId));
|
||||
debugEditProductId->setPlaceholderText(QStringLiteral("0001"));
|
||||
p_ConfigLayout->addWidget(debugEditProductId);
|
||||
|
||||
debugButtonApplyConfig = new QPushButton(QStringLiteral("应用目标"), this);
|
||||
p_ConfigLayout->addWidget(debugButtonApplyConfig);
|
||||
p_ConfigLayout->addStretch(1);
|
||||
p_RootLayout->addLayout(p_ConfigLayout);
|
||||
|
||||
QHBoxLayout* p_CommandLayout = new QHBoxLayout();
|
||||
p_CommandLayout->setContentsMargins(0, 0, 0, 0);
|
||||
p_CommandLayout->setSpacing(8);
|
||||
debugButtonSyncTime = new QPushButton(QStringLiteral("时间同步"), this);
|
||||
debugButtonModeSwitch = new QPushButton(QStringLiteral("切换颜色"), this);
|
||||
p_CommandLayout->addWidget(debugButtonSyncTime);
|
||||
p_CommandLayout->addWidget(debugButtonModeSwitch);
|
||||
p_CommandLayout->addStretch(1);
|
||||
p_RootLayout->addLayout(p_CommandLayout);
|
||||
|
||||
debugLabelConfigStatus = new QLabel(QStringLiteral("当前目标由上层初始化。"), this);
|
||||
debugLabelConfigStatus->setWordWrap(true);
|
||||
debugLabelConfigStatus->setFont(APP::APP_Theme::App_Func_GetBodyFont());
|
||||
debugLabelConfigStatus->setAttribute(Qt::WA_TranslucentBackground, true);
|
||||
p_RootLayout->addWidget(debugLabelConfigStatus);
|
||||
|
||||
debugLabelConnection = new QLabel(QStringLiteral("未连接。"), this);
|
||||
debugLabelConnection->setWordWrap(true);
|
||||
debugLabelConnection->setFont(APP::APP_Theme::App_Func_GetBodyFont());
|
||||
debugLabelConnection->setAttribute(Qt::WA_TranslucentBackground, true);
|
||||
p_RootLayout->addWidget(debugLabelConnection);
|
||||
|
||||
QLabel* p_LogTitleLabel = new QLabel(QStringLiteral("调试日志(原始包 + 语义解析)"), this);
|
||||
p_LogTitleLabel->setWordWrap(true);
|
||||
p_LogTitleLabel->setFont(APP::APP_Theme::App_Func_GetTitleFont());
|
||||
p_LogTitleLabel->setAttribute(Qt::WA_TranslucentBackground, true);
|
||||
p_RootLayout->addWidget(p_LogTitleLabel);
|
||||
|
||||
debugEditLog = new QPlainTextEdit(this);
|
||||
debugEditLog->setReadOnly(true);
|
||||
debugEditLog->setMinimumHeight(300);
|
||||
debugEditLog->setLineWrapMode(QPlainTextEdit::NoWrap);
|
||||
debugEditLog->setFrameShape(QFrame::NoFrame);
|
||||
debugEditLog->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
|
||||
debugEditLog->setPlainText(QStringLiteral("等待收到输入包。"));
|
||||
p_RootLayout->addWidget(debugEditLog, 1);
|
||||
}
|
||||
|
||||
QPushButton* Debug_Panel::Debug_Func_GetRefreshButton() const { return debugButtonRefresh; }
|
||||
QPushButton* Debug_Panel::Debug_Func_GetClearButton() const { return debugButtonClear; }
|
||||
QPushButton* Debug_Panel::Debug_Func_GetApplyConfigButton() const { return debugButtonApplyConfig; }
|
||||
QPushButton* Debug_Panel::Debug_Func_GetSyncTimeButton() const { return debugButtonSyncTime; }
|
||||
QPushButton* Debug_Panel::Debug_Func_GetModeSwitchButton() const { return debugButtonModeSwitch; }
|
||||
|
||||
void Debug_Panel::Debug_Func_SetDeviceConfigText(
|
||||
quint16 VendorId,
|
||||
quint16 ProductId)
|
||||
{
|
||||
debugEditVendorId->setText(QStringLiteral("%1").arg(VendorId, 4, 16, QLatin1Char('0')).toUpper());
|
||||
debugEditProductId->setText(QStringLiteral("%1").arg(ProductId, 4, 16, QLatin1Char('0')).toUpper());
|
||||
}
|
||||
|
||||
bool Debug_Panel::Debug_Func_TryGetDeviceConfig(
|
||||
quint16* p_VendorId,
|
||||
quint16* p_ProductId) const
|
||||
{
|
||||
return Debug_Func_ParseHexField(debugEditVendorId->text(), p_VendorId) &&
|
||||
Debug_Func_ParseHexField(debugEditProductId->text(), p_ProductId);
|
||||
}
|
||||
|
||||
void Debug_Panel::Debug_Func_SetConfigStatusText(const QString& Text, bool IsOk)
|
||||
{
|
||||
debugLabelConfigStatus->setText(Text);
|
||||
Debug_Func_SetLabelTextColor(
|
||||
debugLabelConfigStatus,
|
||||
IsOk ? QColor(98, 198, 164) : QColor(214, 110, 102));
|
||||
}
|
||||
|
||||
void Debug_Panel::Debug_Func_SetConnectionText(const QString& Text, bool IsConnected)
|
||||
{
|
||||
debugLabelConnection->setText(Text);
|
||||
Debug_Func_SetLabelTextColor(
|
||||
debugLabelConnection,
|
||||
IsConnected ? QColor(98, 198, 164) : QColor(214, 110, 102));
|
||||
}
|
||||
|
||||
void Debug_Panel::Debug_Func_SetLogText(const QString& Text)
|
||||
{
|
||||
const QString DisplayText = Text.isEmpty()
|
||||
? QStringLiteral("等待收到输入包。")
|
||||
: Text;
|
||||
|
||||
if (debugEditLog->toPlainText() == DisplayText)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
debugEditLog->setPlainText(DisplayText);
|
||||
debugEditLog->verticalScrollBar()->setValue(debugEditLog->verticalScrollBar()->maximum());
|
||||
}
|
||||
|
||||
} // namespace DEBUG
|
||||
46
DEBUG/Debug_Panel.h
Normal file
46
DEBUG/Debug_Panel.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "APP/APP_GlassCard.h"
|
||||
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QPlainTextEdit;
|
||||
class QPushButton;
|
||||
|
||||
namespace DEBUG {
|
||||
|
||||
class Debug_Panel : public APP::APP_GlassCard
|
||||
{
|
||||
public:
|
||||
explicit Debug_Panel(QWidget* parent = nullptr);
|
||||
|
||||
QPushButton* Debug_Func_GetRefreshButton() const;
|
||||
QPushButton* Debug_Func_GetClearButton() const;
|
||||
QPushButton* Debug_Func_GetApplyConfigButton() const;
|
||||
QPushButton* Debug_Func_GetSyncTimeButton() const;
|
||||
QPushButton* Debug_Func_GetModeSwitchButton() const;
|
||||
|
||||
void Debug_Func_SetDeviceConfigText(
|
||||
quint16 VendorId,
|
||||
quint16 ProductId);
|
||||
bool Debug_Func_TryGetDeviceConfig(
|
||||
quint16* p_VendorId,
|
||||
quint16* p_ProductId) const;
|
||||
void Debug_Func_SetConfigStatusText(const QString& Text, bool IsOk);
|
||||
void Debug_Func_SetConnectionText(const QString& Text, bool IsConnected);
|
||||
void Debug_Func_SetLogText(const QString& Text);
|
||||
|
||||
private:
|
||||
QLabel* debugLabelConnection = nullptr;
|
||||
QLabel* debugLabelConfigStatus = nullptr;
|
||||
QLineEdit* debugEditVendorId = nullptr;
|
||||
QLineEdit* debugEditProductId = nullptr;
|
||||
QPlainTextEdit* debugEditLog = nullptr;
|
||||
QPushButton* debugButtonApplyConfig = nullptr;
|
||||
QPushButton* debugButtonRefresh = nullptr;
|
||||
QPushButton* debugButtonClear = nullptr;
|
||||
QPushButton* debugButtonSyncTime = nullptr;
|
||||
QPushButton* debugButtonModeSwitch = nullptr;
|
||||
};
|
||||
|
||||
} // namespace DEBUG
|
||||
817
DRI/Dri_Ble.cpp
Normal file
817
DRI/Dri_Ble.cpp
Normal file
@@ -0,0 +1,817 @@
|
||||
#include "DRI/Dri_Ble.h"
|
||||
#include "DRI/Dri_Hid.h"
|
||||
#include "MID/Mid_Ble.h"
|
||||
#include <QtCore/QHash>
|
||||
#include <QtCore/QList>
|
||||
#include <QtCore/QMutex>
|
||||
#include <QtCore/QMutexLocker>
|
||||
#include <QtCore/QRegularExpression>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QUuid>
|
||||
#include <Windows.h>
|
||||
#include <SetupAPI.h>
|
||||
#include <bluetoothleapis.h>
|
||||
#include <bthledef.h>
|
||||
#include <hidsdi.h>
|
||||
#include <hidpi.h>
|
||||
#include <atomic>
|
||||
|
||||
#pragma comment(lib, "BluetoothAPIs.lib")
|
||||
#pragma comment(lib, "hid.lib")
|
||||
#pragma comment(lib, "setupapi.lib")
|
||||
|
||||
struct Dri_Ble_Struct_ServiceContext;
|
||||
struct Dri_Ble_Struct_HidPort;
|
||||
struct Dri_Ble_Struct_Subscription
|
||||
{
|
||||
Dri_Ble_Struct_Port* Port = nullptr;
|
||||
Dri_Ble_Struct_ServiceContext* Service = nullptr;
|
||||
BLUETOOTH_GATT_EVENT_HANDLE Event = nullptr;
|
||||
QString DeviceLabel;
|
||||
QString ServiceUuid;
|
||||
QString CharUuid;
|
||||
};
|
||||
struct Dri_Ble_Struct_Context
|
||||
{
|
||||
QMutex Mutex;
|
||||
QList<Mid_Struct_RawPacket> Queue;
|
||||
QVector<Dri_Ble_Struct_ServiceContext*> Services;
|
||||
QVector<Dri_Ble_Struct_HidPort*> Hids;
|
||||
std::atomic<long> CallbackCount { 0 };
|
||||
};
|
||||
struct Dri_Ble_Struct_ServiceContext
|
||||
{
|
||||
Dri_Ble_Struct_Context* Ctx = nullptr;
|
||||
HANDLE Handle = INVALID_HANDLE_VALUE;
|
||||
QString Uuid;
|
||||
QVector<Dri_Ble_Struct_Subscription*> Subs;
|
||||
};
|
||||
struct Dri_Ble_Struct_HidPort
|
||||
{
|
||||
Dri_Hid_Struct_ReadPort ReadPort;
|
||||
HANDLE WriteHandle = INVALID_HANDLE_VALUE;
|
||||
quint16 UsagePage = 0;
|
||||
quint16 Usage = 0;
|
||||
quint16 OutputLength = 0;
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct ServiceIf { QString Path; QString InstanceId; QString Uuid; };
|
||||
struct DeviceIf { QString Path; QString InstanceId; QString Address; };
|
||||
struct HidIf
|
||||
{
|
||||
QString Path;
|
||||
QString InstanceId;
|
||||
quint16 VendorId = 0;
|
||||
quint16 ProductId = 0;
|
||||
quint16 UsagePage = 0;
|
||||
quint16 Usage = 0;
|
||||
quint16 InputLength = 0;
|
||||
quint16 OutputLength = 0;
|
||||
};
|
||||
struct PnpIf
|
||||
{
|
||||
QString Address;
|
||||
quint16 VendorId = 0;
|
||||
quint16 ProductId = 0;
|
||||
};
|
||||
const QString kSvcHidBrace = QStringLiteral("{00001812-0000-1000-8000-00805F9B34FB}");
|
||||
const QString kSvcCustom = QStringLiteral("0B7F5000-38D2-4F62-8F6F-36C4FD73A110");
|
||||
const QString kSvcDis = QStringLiteral("0000180A-0000-1000-8000-00805F9B34FB");
|
||||
const QString kChrPnpId = QStringLiteral("00002A50-0000-1000-8000-00805F9B34FB");
|
||||
const quint16 kUsagePageKeyboard = 0x0001;
|
||||
const quint16 kUsageKeyboard = 0x0006;
|
||||
|
||||
QString HResultText(HRESULT hr) { return QStringLiteral("0x%1").arg(static_cast<quint32>(hr), 8, 16, QLatin1Char('0')).toUpper(); }
|
||||
bool IsCommandHid(quint16 page, quint16 usage) { return (page == MID_CONST_USAGE_PAGE_VENDOR_COMMAND) && (usage == MID_CONST_USAGE_VENDOR_COMMAND); }
|
||||
bool IsUsefulHid(quint16 page, quint16 usage) { return ((page == kUsagePageKeyboard) && (usage == kUsageKeyboard)) || ((page == MID_CONST_USAGE_PAGE_CONSUMER) && (usage == MID_CONST_USAGE_CONSUMER)) || ((page == MID_CONST_USAGE_PAGE_VENDOR) && (usage == MID_CONST_USAGE_VENDOR)) || IsCommandHid(page, usage); }
|
||||
bool IsVendorHid(quint16 page, quint16 usage) { return (page == MID_CONST_USAGE_PAGE_VENDOR) && (usage == MID_CONST_USAGE_VENDOR); }
|
||||
Mid_Enum_RawPacketSource HidPacketSource(quint16 page, quint16 usage)
|
||||
{
|
||||
if ((page == kUsagePageKeyboard) && (usage == kUsageKeyboard)) return Mid_Enum_RawPacketSource_BleHidKeyboard;
|
||||
if ((page == MID_CONST_USAGE_PAGE_CONSUMER) && (usage == MID_CONST_USAGE_CONSUMER)) return Mid_Enum_RawPacketSource_BleHidConsumer;
|
||||
if (IsVendorHid(page, usage)) return Mid_Enum_RawPacketSource_BleHidVendor;
|
||||
if (IsCommandHid(page, usage)) return Mid_Enum_RawPacketSource_BleHidVendorCommand;
|
||||
return Mid_Enum_RawPacketSource_None;
|
||||
}
|
||||
QString ExtractAddress(const QString& text) { auto m = QRegularExpression(QStringLiteral("([0-9A-F]{12})(?=[\\\\&_]|$)")).match(text.toUpper()); return m.hasMatch() ? m.captured(1) : QString(); }
|
||||
QString ServiceUuidFromId(const QString& id) { auto m = QRegularExpression(QStringLiteral("\\{([0-9A-Fa-f-]{36})\\}")).match(id); return m.hasMatch() ? m.captured(1).toUpper() : QString(); }
|
||||
QString DeviceLabel(const QString& addr) { return addr.isEmpty() ? QStringLiteral("BLE") : QStringLiteral("BLE-%1").arg(addr); }
|
||||
QString FormatVidPidLabel(quint16 vendorId, quint16 productId)
|
||||
{
|
||||
return QStringLiteral("0x%1:0x%2")
|
||||
.arg(vendorId, 4, 16, QLatin1Char('0'))
|
||||
.arg(productId, 4, 16, QLatin1Char('0'))
|
||||
.toUpper();
|
||||
}
|
||||
QString PortName(const QString& dev, const QString& src) { const QString base = dev.isEmpty() ? QStringLiteral("Bluetooth") : QStringLiteral("Bluetooth(%1)").arg(dev); return src.isEmpty() ? base : QStringLiteral("%1/%2").arg(base, src); }
|
||||
QString HidSource(quint16 page, quint16 usage)
|
||||
{
|
||||
if ((page == kUsagePageKeyboard) && (usage == kUsageKeyboard)) return QStringLiteral("HID Keyboard");
|
||||
if ((page == MID_CONST_USAGE_PAGE_CONSUMER) && (usage == MID_CONST_USAGE_CONSUMER)) return QStringLiteral("HID Consumer");
|
||||
if ((page == MID_CONST_USAGE_PAGE_VENDOR) && (usage == MID_CONST_USAGE_VENDOR)) return QStringLiteral("HID Vendor");
|
||||
if (IsCommandHid(page, usage)) return QStringLiteral("HID Vendor Command");
|
||||
return QStringLiteral("HID UsagePage 0x%1 / Usage 0x%2").arg(page, 4, 16, QLatin1Char('0')).arg(usage, 4, 16, QLatin1Char('0')).toUpper();
|
||||
}
|
||||
QString DeviceInstanceId(HDEVINFO set, SP_DEVINFO_DATA* info)
|
||||
{
|
||||
DWORD need = 0;
|
||||
SetupDiGetDeviceInstanceIdW(set, info, nullptr, 0, &need);
|
||||
if (need == 0) return QString();
|
||||
QVector<wchar_t> buf(static_cast<int>(need) + 1, 0);
|
||||
return SetupDiGetDeviceInstanceIdW(set, info, buf.data(), static_cast<DWORD>(buf.size()), nullptr) ? QString::fromWCharArray(buf.constData()).trimmed() : QString();
|
||||
}
|
||||
bool InterfacePath(HDEVINFO set, SP_DEVICE_INTERFACE_DATA* itf, SP_DEVINFO_DATA* info, QString* path)
|
||||
{
|
||||
DWORD need = 0;
|
||||
SetupDiGetDeviceInterfaceDetailW(set, itf, nullptr, 0, &need, info);
|
||||
if (need == 0) return false;
|
||||
QByteArray buf(static_cast<int>(need), 0);
|
||||
auto* detail = reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA_W*>(buf.data());
|
||||
detail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
|
||||
if (!SetupDiGetDeviceInterfaceDetailW(set, itf, detail, need, nullptr, info)) return false;
|
||||
*path = QString::fromWCharArray(detail->DevicePath);
|
||||
return true;
|
||||
}
|
||||
QUuid QtUuid(const BTH_LE_UUID& uuid)
|
||||
{
|
||||
if (uuid.IsShortUuid) return QUuid(QStringLiteral("{%1-0000-1000-8000-00805F9B34FB}").arg(uuid.Value.ShortUuid, 4, 16, QLatin1Char('0')));
|
||||
const GUID& g = uuid.Value.LongUuid;
|
||||
return QUuid(g.Data1, g.Data2, g.Data3, g.Data4[0], g.Data4[1], g.Data4[2], g.Data4[3], g.Data4[4], g.Data4[5], g.Data4[6], g.Data4[7]);
|
||||
}
|
||||
QString CharText(const BTH_LE_GATT_CHARACTERISTIC& c)
|
||||
{
|
||||
const QString uuid = QtUuid(c.CharacteristicUuid).toString(QUuid::WithoutBraces).toUpper();
|
||||
return uuid == QStringLiteral("00000000-0000-0000-0000-000000000000") ? QStringLiteral("HANDLE 0x%1").arg(c.CharacteristicValueHandle, 4, 16, QLatin1Char('0')).toUpper() : uuid;
|
||||
}
|
||||
HANDLE OpenService(const QString& path)
|
||||
{
|
||||
HANDLE handle = CreateFileW(reinterpret_cast<LPCWSTR>(path.utf16()), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
|
||||
return handle != INVALID_HANDLE_VALUE ? handle : CreateFileW(reinterpret_cast<LPCWSTR>(path.utf16()), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
|
||||
}
|
||||
void QueuePacket(
|
||||
Dri_Ble_Struct_Context* ctx,
|
||||
Mid_Enum_RawPacketSource source,
|
||||
const QString& portName,
|
||||
const QByteArray& bytes)
|
||||
{
|
||||
if ((ctx == nullptr) || bytes.isEmpty()) return;
|
||||
Mid_Struct_RawPacket packet;
|
||||
packet.IsValid = true;
|
||||
packet.Source = source;
|
||||
packet.PortName = portName;
|
||||
packet.ByteArray = bytes;
|
||||
QMutexLocker lock(&ctx->Mutex); ctx->Queue.append(packet);
|
||||
}
|
||||
|
||||
QVector<ServiceIf> EnumServices()
|
||||
{
|
||||
QVector<ServiceIf> out;
|
||||
HDEVINFO set = SetupDiGetClassDevsW(&GUID_BLUETOOTH_GATT_SERVICE_DEVICE_INTERFACE, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
||||
if (set == INVALID_HANDLE_VALUE) return out;
|
||||
SP_DEVICE_INTERFACE_DATA itf = { sizeof(SP_DEVICE_INTERFACE_DATA) };
|
||||
for (DWORD i = 0; SetupDiEnumDeviceInterfaces(set, nullptr, &GUID_BLUETOOTH_GATT_SERVICE_DEVICE_INTERFACE, i, &itf); ++i)
|
||||
{
|
||||
SP_DEVINFO_DATA info = { sizeof(SP_DEVINFO_DATA) };
|
||||
QString path;
|
||||
if (!InterfacePath(set, &itf, &info, &path)) continue;
|
||||
ServiceIf item; item.Path = path; item.InstanceId = DeviceInstanceId(set, &info); item.Uuid = ServiceUuidFromId(item.InstanceId); out.append(item);
|
||||
}
|
||||
SetupDiDestroyDeviceInfoList(set);
|
||||
return out;
|
||||
}
|
||||
|
||||
QVector<DeviceIf> EnumBleDevices()
|
||||
{
|
||||
QVector<DeviceIf> out;
|
||||
HDEVINFO set = SetupDiGetClassDevsW(
|
||||
&GUID_BLUETOOTHLE_DEVICE_INTERFACE,
|
||||
nullptr,
|
||||
nullptr,
|
||||
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
||||
if (set == INVALID_HANDLE_VALUE) return out;
|
||||
|
||||
SP_DEVICE_INTERFACE_DATA itf = { sizeof(SP_DEVICE_INTERFACE_DATA) };
|
||||
for (DWORD i = 0; SetupDiEnumDeviceInterfaces(set, nullptr, &GUID_BLUETOOTHLE_DEVICE_INTERFACE, i, &itf); ++i)
|
||||
{
|
||||
SP_DEVINFO_DATA info = { sizeof(SP_DEVINFO_DATA) };
|
||||
QString path;
|
||||
if (!InterfacePath(set, &itf, &info, &path)) continue;
|
||||
|
||||
DeviceIf item;
|
||||
item.Path = path;
|
||||
item.InstanceId = DeviceInstanceId(set, &info);
|
||||
item.Address = ExtractAddress(item.InstanceId);
|
||||
if (item.Address.isEmpty()) continue;
|
||||
out.append(item);
|
||||
}
|
||||
|
||||
SetupDiDestroyDeviceInfoList(set);
|
||||
return out;
|
||||
}
|
||||
|
||||
QVector<HidIf> EnumBleHids()
|
||||
{
|
||||
QVector<HidIf> out;
|
||||
GUID hidGuid; HidD_GetHidGuid(&hidGuid);
|
||||
HDEVINFO set = SetupDiGetClassDevsW(&hidGuid, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
||||
if (set == INVALID_HANDLE_VALUE) return out;
|
||||
SP_DEVICE_INTERFACE_DATA itf = { sizeof(SP_DEVICE_INTERFACE_DATA) };
|
||||
for (DWORD i = 0; SetupDiEnumDeviceInterfaces(set, nullptr, &hidGuid, i, &itf); ++i)
|
||||
{
|
||||
SP_DEVINFO_DATA info = { sizeof(SP_DEVINFO_DATA) };
|
||||
QString path;
|
||||
if (!InterfacePath(set, &itf, &info, &path)) continue;
|
||||
const QString id = DeviceInstanceId(set, &info).toUpper();
|
||||
if (!id.contains(kSvcHidBrace)) continue;
|
||||
HANDLE handle = CreateFileW(reinterpret_cast<LPCWSTR>(path.utf16()), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
|
||||
if (handle == INVALID_HANDLE_VALUE) continue;
|
||||
HIDD_ATTRIBUTES attrs = {};
|
||||
attrs.Size = sizeof(attrs);
|
||||
PHIDP_PREPARSED_DATA prep = nullptr;
|
||||
HIDP_CAPS caps = {};
|
||||
const bool ok =
|
||||
HidD_GetAttributes(handle, &attrs) &&
|
||||
HidD_GetPreparsedData(handle, &prep) &&
|
||||
(HidP_GetCaps(prep, &caps) == HIDP_STATUS_SUCCESS) &&
|
||||
IsUsefulHid(caps.UsagePage, caps.Usage) &&
|
||||
(caps.InputReportByteLength > 0);
|
||||
if (prep != nullptr) HidD_FreePreparsedData(prep);
|
||||
CloseHandle(handle);
|
||||
if (!ok) continue;
|
||||
HidIf item;
|
||||
item.Path = path;
|
||||
item.InstanceId = id;
|
||||
item.VendorId = attrs.VendorID;
|
||||
item.ProductId = attrs.ProductID;
|
||||
item.UsagePage = caps.UsagePage;
|
||||
item.Usage = caps.Usage;
|
||||
item.InputLength = caps.InputReportByteLength;
|
||||
item.OutputLength = caps.OutputReportByteLength;
|
||||
out.append(item);
|
||||
}
|
||||
SetupDiDestroyDeviceInfoList(set);
|
||||
return out;
|
||||
}
|
||||
|
||||
QVector<BTH_LE_GATT_CHARACTERISTIC> Characteristics(HANDLE handle)
|
||||
{
|
||||
USHORT count = 0;
|
||||
HRESULT hr = BluetoothGATTGetCharacteristics(handle, nullptr, 0, nullptr, &count, BLUETOOTH_GATT_FLAG_NONE);
|
||||
if (FAILED(hr) && (hr != HRESULT_FROM_WIN32(ERROR_MORE_DATA))) return {};
|
||||
QVector<BTH_LE_GATT_CHARACTERISTIC> out(count);
|
||||
hr = BluetoothGATTGetCharacteristics(handle, nullptr, count, out.data(), &count, BLUETOOTH_GATT_FLAG_NONE);
|
||||
if (FAILED(hr)) return {};
|
||||
out.resize(count); return out;
|
||||
}
|
||||
|
||||
QVector<BTH_LE_GATT_DESCRIPTOR> Descriptors(HANDLE handle, const BTH_LE_GATT_CHARACTERISTIC& c)
|
||||
{
|
||||
USHORT count = 0;
|
||||
HRESULT hr = BluetoothGATTGetDescriptors(handle, const_cast<PBTH_LE_GATT_CHARACTERISTIC>(&c), 0, nullptr, &count, BLUETOOTH_GATT_FLAG_NONE);
|
||||
if (FAILED(hr) && (hr != HRESULT_FROM_WIN32(ERROR_MORE_DATA))) return {};
|
||||
QVector<BTH_LE_GATT_DESCRIPTOR> out(count);
|
||||
hr = BluetoothGATTGetDescriptors(handle, const_cast<PBTH_LE_GATT_CHARACTERISTIC>(&c), count, out.data(), &count, BLUETOOTH_GATT_FLAG_NONE);
|
||||
if (FAILED(hr)) return {};
|
||||
out.resize(count); return out;
|
||||
}
|
||||
|
||||
bool ReadValue(HANDLE handle, const BTH_LE_GATT_CHARACTERISTIC& c, QByteArray* out)
|
||||
{
|
||||
USHORT need = 0;
|
||||
HRESULT hr = BluetoothGATTGetCharacteristicValue(handle, const_cast<PBTH_LE_GATT_CHARACTERISTIC>(&c), 0, nullptr, &need, BLUETOOTH_GATT_FLAG_FORCE_READ_FROM_DEVICE);
|
||||
if (FAILED(hr) && (hr != HRESULT_FROM_WIN32(ERROR_MORE_DATA))) return false;
|
||||
if (need == 0) return false;
|
||||
QByteArray buffer(static_cast<int>(need), 0);
|
||||
auto* value = reinterpret_cast<PBTH_LE_GATT_CHARACTERISTIC_VALUE>(buffer.data());
|
||||
hr = BluetoothGATTGetCharacteristicValue(handle, const_cast<PBTH_LE_GATT_CHARACTERISTIC>(&c), need, value, nullptr, BLUETOOTH_GATT_FLAG_FORCE_READ_FROM_DEVICE);
|
||||
if (FAILED(hr)) return false;
|
||||
*out = QByteArray(reinterpret_cast<const char*>(value->Data), static_cast<int>(value->DataSize));
|
||||
return true;
|
||||
}
|
||||
|
||||
VOID CALLBACK OnGattEvent(BTH_LE_GATT_EVENT_TYPE type, PVOID out, PVOID context)
|
||||
{
|
||||
auto* sub = static_cast<Dri_Ble_Struct_Subscription*>(context);
|
||||
if ((sub == nullptr) || (sub->Service == nullptr) || (sub->Service->Ctx == nullptr)) return;
|
||||
Dri_Ble_Struct_Context* ctx = sub->Service->Ctx;
|
||||
ctx->CallbackCount.fetch_add(1, std::memory_order_acq_rel);
|
||||
if ((type == CharacteristicValueChangedEvent) && (out != nullptr) && (sub->Port != nullptr))
|
||||
{
|
||||
const auto* evt = static_cast<const BLUETOOTH_GATT_VALUE_CHANGED_EVENT*>(out);
|
||||
if ((evt->CharacteristicValue != nullptr) && (evt->CharacteristicValue->DataSize > 0)) QueuePacket(ctx, Mid_Enum_RawPacketSource_BleGatt, PortName(sub->DeviceLabel, QStringLiteral("SVC %1 / CHR %2").arg(sub->ServiceUuid, sub->CharUuid)), QByteArray(reinterpret_cast<const char*>(evt->CharacteristicValue->Data), static_cast<int>(evt->CharacteristicValue->DataSize)));
|
||||
}
|
||||
ctx->CallbackCount.fetch_sub(1, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
bool Subscribe(Dri_Ble_Struct_Port* port, Dri_Ble_Struct_ServiceContext* svc, const QString& deviceLabel, const BTH_LE_GATT_CHARACTERISTIC& c, QString* error)
|
||||
{
|
||||
auto* sub = new Dri_Ble_Struct_Subscription();
|
||||
sub->Port = port; sub->Service = svc; sub->DeviceLabel = deviceLabel; sub->ServiceUuid = svc->Uuid; sub->CharUuid = CharText(c);
|
||||
BLUETOOTH_GATT_VALUE_CHANGED_EVENT_REGISTRATION reg = {}; reg.NumCharacteristics = 1; reg.Characteristics[0] = c;
|
||||
HRESULT hr = BluetoothGATTRegisterEvent(svc->Handle, CharacteristicValueChangedEvent, ®, OnGattEvent, sub, &sub->Event, BLUETOOTH_GATT_FLAG_NONE);
|
||||
if (FAILED(hr)) { if (error != nullptr) *error = QStringLiteral("BLE notify register failed: %1 / %2 / %3").arg(svc->Uuid, sub->CharUuid, HResultText(hr)); delete sub; return false; }
|
||||
bool configured = false;
|
||||
for (const BTH_LE_GATT_DESCRIPTOR& d : Descriptors(svc->Handle, c))
|
||||
{
|
||||
if (d.DescriptorType != ClientCharacteristicConfiguration) continue;
|
||||
BTH_LE_GATT_DESCRIPTOR_VALUE value = {}; value.DescriptorType = ClientCharacteristicConfiguration;
|
||||
value.ClientCharacteristicConfiguration.IsSubscribeToNotification = c.IsNotifiable;
|
||||
value.ClientCharacteristicConfiguration.IsSubscribeToIndication = c.IsIndicatable;
|
||||
if (SUCCEEDED(BluetoothGATTSetDescriptorValue(svc->Handle, const_cast<PBTH_LE_GATT_DESCRIPTOR>(&d), &value, BLUETOOTH_GATT_FLAG_NONE))) { configured = true; break; }
|
||||
}
|
||||
if (!configured)
|
||||
{
|
||||
if (sub->Event != nullptr) BluetoothGATTUnregisterEvent(sub->Event, BLUETOOTH_GATT_FLAG_NONE);
|
||||
if (error != nullptr) *error = QStringLiteral("BLE notify enable failed: %1 / %2").arg(svc->Uuid, sub->CharUuid);
|
||||
delete sub;
|
||||
return false;
|
||||
}
|
||||
svc->Subs.append(sub);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AttachService(Dri_Ble_Struct_Port* port, Dri_Ble_Struct_Context* ctx, const ServiceIf& item, const QString& deviceLabel, int* charCount, int* subCount)
|
||||
{
|
||||
auto* svc = new Dri_Ble_Struct_ServiceContext();
|
||||
svc->Ctx = ctx; svc->Uuid = item.Uuid; svc->Handle = OpenService(item.Path);
|
||||
if (svc->Handle == INVALID_HANDLE_VALUE) { delete svc; return false; }
|
||||
const auto list = Characteristics(svc->Handle);
|
||||
if (list.isEmpty()) { CloseHandle(svc->Handle); delete svc; return false; }
|
||||
bool keep = false;
|
||||
for (const BTH_LE_GATT_CHARACTERISTIC& c : list)
|
||||
{
|
||||
if (!c.IsReadable && !c.IsNotifiable && !c.IsIndicatable) continue;
|
||||
const QString charUuid = CharText(c);
|
||||
++(*charCount);
|
||||
if (c.IsNotifiable || c.IsIndicatable) { if (Subscribe(port, svc, deviceLabel, c, nullptr)) { keep = true; ++(*subCount); } }
|
||||
if (c.IsReadable) { QByteArray bytes; if (ReadValue(svc->Handle, c, &bytes) && !bytes.isEmpty()) QueuePacket(ctx, Mid_Enum_RawPacketSource_BleGatt, PortName(deviceLabel, QStringLiteral("SVC %1 / CHR %2").arg(item.Uuid, charUuid)), bytes); }
|
||||
}
|
||||
if (keep) { ctx->Services.append(svc); return true; }
|
||||
CloseHandle(svc->Handle);
|
||||
delete svc;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CloseHidPort(Dri_Ble_Struct_HidPort* hid)
|
||||
{
|
||||
Dri_Hid_CloseReadPort(&hid->ReadPort);
|
||||
if ((hid->WriteHandle != nullptr) && (hid->WriteHandle != INVALID_HANDLE_VALUE)) CloseHandle(hid->WriteHandle);
|
||||
delete hid;
|
||||
}
|
||||
|
||||
bool OpenHidPort(Dri_Ble_Struct_Context* ctx, const HidIf& item, const QString& deviceLabel)
|
||||
{
|
||||
auto* hid = new Dri_Ble_Struct_HidPort();
|
||||
const QString source = HidSource(item.UsagePage, item.Usage);
|
||||
hid->UsagePage = item.UsagePage;
|
||||
hid->Usage = item.Usage;
|
||||
hid->OutputLength = item.OutputLength;
|
||||
QString textError;
|
||||
if (!Dri_Hid_InitReadPort(
|
||||
&hid->ReadPort,
|
||||
item.Path,
|
||||
item.InputLength,
|
||||
HidPacketSource(item.UsagePage, item.Usage),
|
||||
PortName(deviceLabel, source),
|
||||
&textError))
|
||||
{
|
||||
delete hid;
|
||||
return false;
|
||||
}
|
||||
if (item.OutputLength > 0)
|
||||
{
|
||||
hid->WriteHandle = Dri_Hid_OpenWriteHandle(item.Path, nullptr);
|
||||
}
|
||||
ctx->Hids.append(hid);
|
||||
return true;
|
||||
}
|
||||
|
||||
QString FormatAddressLabel(const QString& Address)
|
||||
{
|
||||
const QString Formatted = Mid_FormatBleAddress(Address);
|
||||
return Formatted.isEmpty() ? QStringLiteral("(unknown)") : Formatted;
|
||||
}
|
||||
|
||||
bool ParsePnpIdValue(const QByteArray& bytes, quint16* vendorId, quint16* productId)
|
||||
{
|
||||
if (bytes.size() < 7) return false;
|
||||
const quint8 vendorSource = static_cast<quint8>(bytes.at(0));
|
||||
// Only accept USB-IF sourced PnP IDs so the fields map directly to USB VID/PID.
|
||||
if (vendorSource != 0x02U) return false;
|
||||
*vendorId =
|
||||
static_cast<quint8>(bytes.at(1)) |
|
||||
(static_cast<quint16>(static_cast<quint8>(bytes.at(2))) << 8);
|
||||
*productId =
|
||||
static_cast<quint8>(bytes.at(3)) |
|
||||
(static_cast<quint16>(static_cast<quint8>(bytes.at(4))) << 8);
|
||||
return true;
|
||||
}
|
||||
|
||||
QVector<PnpIf> EnumPnps(const QVector<DeviceIf>& deviceList, QStringList* p_DebugList)
|
||||
{
|
||||
QVector<PnpIf> out;
|
||||
for (const DeviceIf& device : deviceList)
|
||||
{
|
||||
HANDLE handle = OpenService(device.Path);
|
||||
if (handle == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
if (p_DebugList != nullptr)
|
||||
{
|
||||
p_DebugList->append(
|
||||
QStringLiteral("PnP: 打开 BLE 设备失败 %1")
|
||||
.arg(FormatAddressLabel(device.Address)));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
USHORT serviceCount = 0;
|
||||
HRESULT hr = BluetoothGATTGetServices(
|
||||
handle,
|
||||
0,
|
||||
nullptr,
|
||||
&serviceCount,
|
||||
BLUETOOTH_GATT_FLAG_NONE);
|
||||
if (FAILED(hr) && (hr != HRESULT_FROM_WIN32(ERROR_MORE_DATA)))
|
||||
{
|
||||
if (p_DebugList != nullptr)
|
||||
{
|
||||
p_DebugList->append(
|
||||
QStringLiteral("PnP: 枚举服务失败 %1 / hr=%2")
|
||||
.arg(FormatAddressLabel(device.Address), HResultText(hr)));
|
||||
}
|
||||
CloseHandle(handle);
|
||||
continue;
|
||||
}
|
||||
|
||||
QVector<BTH_LE_GATT_SERVICE> services(serviceCount);
|
||||
hr = BluetoothGATTGetServices(
|
||||
handle,
|
||||
serviceCount,
|
||||
services.data(),
|
||||
&serviceCount,
|
||||
BLUETOOTH_GATT_FLAG_NONE);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
if (p_DebugList != nullptr)
|
||||
{
|
||||
p_DebugList->append(
|
||||
QStringLiteral("PnP: 读取服务列表失败 %1 / hr=%2")
|
||||
.arg(FormatAddressLabel(device.Address), HResultText(hr)));
|
||||
}
|
||||
CloseHandle(handle);
|
||||
continue;
|
||||
}
|
||||
services.resize(serviceCount);
|
||||
|
||||
QByteArray valueBytes;
|
||||
bool found = false;
|
||||
quint16 vendorId = 0;
|
||||
quint16 productId = 0;
|
||||
QStringList serviceUuidList;
|
||||
QStringList charUuidList;
|
||||
bool hasPnpChar = false;
|
||||
|
||||
for (const BTH_LE_GATT_SERVICE& service : services)
|
||||
{
|
||||
const QUuid serviceUuid = QtUuid(service.ServiceUuid);
|
||||
const QString serviceUuidText =
|
||||
serviceUuid.toString(QUuid::WithoutBraces).toUpper();
|
||||
serviceUuidList.append(serviceUuidText);
|
||||
|
||||
USHORT charCount = 0;
|
||||
hr = BluetoothGATTGetCharacteristics(
|
||||
handle,
|
||||
const_cast<PBTH_LE_GATT_SERVICE>(&service),
|
||||
0,
|
||||
nullptr,
|
||||
&charCount,
|
||||
BLUETOOTH_GATT_FLAG_NONE);
|
||||
if (FAILED(hr) && (hr != HRESULT_FROM_WIN32(ERROR_MORE_DATA)))
|
||||
{
|
||||
if (p_DebugList != nullptr)
|
||||
{
|
||||
p_DebugList->append(
|
||||
QStringLiteral("PnP: 枚举 180A 特征失败 %1 / hr=%2")
|
||||
.arg(FormatAddressLabel(device.Address), HResultText(hr)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
QVector<BTH_LE_GATT_CHARACTERISTIC> chars(charCount);
|
||||
hr = BluetoothGATTGetCharacteristics(
|
||||
handle,
|
||||
const_cast<PBTH_LE_GATT_SERVICE>(&service),
|
||||
charCount,
|
||||
chars.data(),
|
||||
&charCount,
|
||||
BLUETOOTH_GATT_FLAG_NONE);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
if (p_DebugList != nullptr)
|
||||
{
|
||||
p_DebugList->append(
|
||||
QStringLiteral("PnP: 读取 180A 特征失败 %1 / hr=%2")
|
||||
.arg(FormatAddressLabel(device.Address), HResultText(hr)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
chars.resize(charCount);
|
||||
|
||||
for (const BTH_LE_GATT_CHARACTERISTIC& characteristic : chars)
|
||||
{
|
||||
const QString charUuid = CharText(characteristic);
|
||||
charUuidList.append(QStringLiteral("%1/%2").arg(serviceUuidText, charUuid));
|
||||
if (charUuid != kChrPnpId) continue;
|
||||
hasPnpChar = true;
|
||||
if (!ReadValue(handle, characteristic, &valueBytes))
|
||||
{
|
||||
if (p_DebugList != nullptr)
|
||||
{
|
||||
p_DebugList->append(
|
||||
QStringLiteral("PnP: 发现 2A50 但读取失败 %1 / svc=%2")
|
||||
.arg(FormatAddressLabel(device.Address), serviceUuidText));
|
||||
}
|
||||
break;
|
||||
}
|
||||
found = ParsePnpIdValue(valueBytes, &vendorId, &productId);
|
||||
if (p_DebugList != nullptr)
|
||||
{
|
||||
p_DebugList->append(
|
||||
QStringLiteral("PnP: %1 / svc=%2 / raw=%3 / parsed=%4")
|
||||
.arg(FormatAddressLabel(device.Address))
|
||||
.arg(serviceUuidText)
|
||||
.arg(Mid_GetHexText(valueBytes))
|
||||
.arg(found
|
||||
? FormatVidPidLabel(vendorId, productId)
|
||||
: QStringLiteral("invalid")));
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
CloseHandle(handle);
|
||||
if (!hasPnpChar && (p_DebugList != nullptr))
|
||||
{
|
||||
p_DebugList->append(
|
||||
QStringLiteral("PnP: 未发现 2A50 %1 / services=%2 / chars=%3")
|
||||
.arg(FormatAddressLabel(device.Address))
|
||||
.arg(serviceUuidList.join(QStringLiteral(", ")))
|
||||
.arg(charUuidList.join(QStringLiteral(", "))));
|
||||
}
|
||||
if (!found) continue;
|
||||
|
||||
bool exists = false;
|
||||
for (const PnpIf& item : out)
|
||||
{
|
||||
if (item.Address == device.Address)
|
||||
{
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (exists) continue;
|
||||
|
||||
PnpIf item;
|
||||
item.Address = device.Address;
|
||||
item.VendorId = vendorId;
|
||||
item.ProductId = productId;
|
||||
out.append(item);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QStringList CollectPnpAddresses(
|
||||
const QVector<PnpIf>& pnpList,
|
||||
quint16 vendorId,
|
||||
quint16 productId)
|
||||
{
|
||||
QStringList addressList;
|
||||
for (const PnpIf& pnp : pnpList)
|
||||
{
|
||||
if ((pnp.VendorId == vendorId) && (pnp.ProductId == productId) &&
|
||||
!addressList.contains(pnp.Address))
|
||||
{
|
||||
addressList.append(pnp.Address);
|
||||
}
|
||||
}
|
||||
return addressList;
|
||||
}
|
||||
|
||||
QStringList CollectHidAddresses(const QVector<HidIf>& HidList)
|
||||
{
|
||||
QStringList AddressList;
|
||||
for (const HidIf& Hid : HidList)
|
||||
{
|
||||
const QString Address = ExtractAddress(Hid.InstanceId);
|
||||
if (!Address.isEmpty() && !AddressList.contains(Address)) AddressList.append(Address);
|
||||
}
|
||||
return AddressList;
|
||||
}
|
||||
|
||||
QString BuildCandidateSummary(
|
||||
const QVector<PnpIf>& pnpList,
|
||||
const QVector<HidIf>& HidList,
|
||||
const QVector<ServiceIf>& ServiceList,
|
||||
const QStringList& AllowedAddressList)
|
||||
{
|
||||
QHash<QString, int> HidCountMap;
|
||||
QHash<QString, int> GattCountMap;
|
||||
QHash<QString, QString> PnpTextMap;
|
||||
for (const PnpIf& pnp : pnpList)
|
||||
{
|
||||
PnpTextMap.insert(
|
||||
pnp.Address,
|
||||
FormatVidPidLabel(pnp.VendorId, pnp.ProductId));
|
||||
}
|
||||
for (const HidIf& Hid : HidList)
|
||||
{
|
||||
const QString Address = ExtractAddress(Hid.InstanceId);
|
||||
if (!Address.isEmpty()) ++HidCountMap[Address];
|
||||
}
|
||||
for (const ServiceIf& Service : ServiceList)
|
||||
{
|
||||
if (Service.Uuid != kSvcCustom) continue;
|
||||
const QString Address = ExtractAddress(Service.InstanceId);
|
||||
if (!Address.isEmpty() && AllowedAddressList.contains(Address)) ++GattCountMap[Address];
|
||||
}
|
||||
|
||||
QStringList AddressList = HidCountMap.keys();
|
||||
for (const QString& Address : GattCountMap.keys()) if (!AddressList.contains(Address)) AddressList.append(Address);
|
||||
if (AddressList.isEmpty()) return QStringLiteral("无");
|
||||
|
||||
std::sort(AddressList.begin(), AddressList.end());
|
||||
QStringList SummaryList;
|
||||
for (const QString& Address : AddressList)
|
||||
{
|
||||
SummaryList.append(
|
||||
QStringLiteral("%1 [pnp=%2, hid=%3, gatt=%4]")
|
||||
.arg(FormatAddressLabel(Address))
|
||||
.arg(PnpTextMap.value(Address, QStringLiteral("unknown")))
|
||||
.arg(HidCountMap.value(Address))
|
||||
.arg(GattCountMap.value(Address)));
|
||||
}
|
||||
return SummaryList.join(QLatin1Char('\n'));
|
||||
}
|
||||
|
||||
QString BuildPnpSummary(
|
||||
const QVector<PnpIf>& pnpList,
|
||||
quint16 vendorId,
|
||||
quint16 productId)
|
||||
{
|
||||
QStringList lineList;
|
||||
for (const PnpIf& pnp : pnpList)
|
||||
{
|
||||
lineList.append(
|
||||
QStringLiteral("%1 -> %2")
|
||||
.arg(FormatAddressLabel(pnp.Address))
|
||||
.arg(FormatVidPidLabel(pnp.VendorId, pnp.ProductId)));
|
||||
}
|
||||
|
||||
if (lineList.isEmpty())
|
||||
{
|
||||
return QStringLiteral(
|
||||
"PnP 扫描结果:未发现任何 DIS/PnP ID。");
|
||||
}
|
||||
|
||||
QString summary = QStringLiteral(
|
||||
"PnP 扫描结果(目标 %1):\n%2")
|
||||
.arg(FormatVidPidLabel(vendorId, productId))
|
||||
.arg(lineList.join(QLatin1Char('\n')));
|
||||
return summary;
|
||||
}
|
||||
|
||||
QString BuildPnpProbeSummary(const QStringList& debugList)
|
||||
{
|
||||
if (debugList.isEmpty())
|
||||
{
|
||||
return QStringLiteral("PnP 探测日志:无。");
|
||||
}
|
||||
|
||||
return QStringLiteral("PnP 探测日志:\n%1").arg(debugList.join(QLatin1Char('\n')));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Dri_Ble_Close(Dri_Ble_Struct_Port* port)
|
||||
{
|
||||
Dri_Ble_Struct_Context* ctx = port->p_Context;
|
||||
if (ctx != nullptr)
|
||||
{
|
||||
for (Dri_Ble_Struct_HidPort* hid : ctx->Hids) CloseHidPort(hid);
|
||||
for (Dri_Ble_Struct_ServiceContext* svc : ctx->Services) for (Dri_Ble_Struct_Subscription* sub : svc->Subs) { sub->Port = nullptr; if (sub->Event != nullptr) BluetoothGATTUnregisterEvent(sub->Event, BLUETOOTH_GATT_FLAG_NONE); }
|
||||
while (ctx->CallbackCount.load(std::memory_order_acquire) > 0) Sleep(1);
|
||||
for (Dri_Ble_Struct_ServiceContext* svc : ctx->Services)
|
||||
{
|
||||
for (Dri_Ble_Struct_Subscription* sub : svc->Subs) delete sub;
|
||||
if ((svc->Handle != nullptr) && (svc->Handle != INVALID_HANDLE_VALUE)) CloseHandle(svc->Handle);
|
||||
delete svc;
|
||||
}
|
||||
delete ctx;
|
||||
}
|
||||
*port = Dri_Ble_Struct_Port();
|
||||
}
|
||||
bool Dri_Ble_Init(Dri_Ble_Struct_Port* port, const Mid_Struct_DeviceConfig& deviceConfig, QString* textStatus)
|
||||
{
|
||||
Dri_Ble_Close(port);
|
||||
port->IsOpened = true;
|
||||
port->p_Context = new Dri_Ble_Struct_Context();
|
||||
|
||||
const QVector<DeviceIf> devices = EnumBleDevices();
|
||||
const QVector<HidIf> hids = EnumBleHids();
|
||||
const QVector<ServiceIf> services = EnumServices();
|
||||
QStringList pnpDebugList;
|
||||
const QVector<PnpIf> pnps = EnumPnps(devices, &pnpDebugList);
|
||||
const QStringList HidAddressList = CollectPnpAddresses(
|
||||
pnps,
|
||||
deviceConfig.VendorId,
|
||||
deviceConfig.ProductId);
|
||||
QString targetAddress;
|
||||
QString SelectStatus;
|
||||
if (HidAddressList.size() == 1)
|
||||
{
|
||||
targetAddress = HidAddressList.first();
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectStatus = HidAddressList.isEmpty()
|
||||
? QStringLiteral("未检测到匹配 PnP ID %1 的 BLE 设备。")
|
||||
.arg(FormatVidPidLabel(deviceConfig.VendorId, deviceConfig.ProductId))
|
||||
: QStringLiteral("检测到多个匹配 PnP ID %1 的 BLE 设备,无法唯一绑定。")
|
||||
.arg(FormatVidPidLabel(deviceConfig.VendorId, deviceConfig.ProductId));
|
||||
}
|
||||
QString deviceLabel = targetAddress.isEmpty() ? QString() : DeviceLabel(targetAddress);
|
||||
|
||||
int charCount = 0;
|
||||
int subCount = 0;
|
||||
if (!targetAddress.isEmpty())
|
||||
{
|
||||
for (const ServiceIf& item : services)
|
||||
{
|
||||
if ((item.Uuid == kSvcCustom) && (ExtractAddress(item.InstanceId) == targetAddress)) AttachService(port, port->p_Context, item, deviceLabel, &charCount, &subCount);
|
||||
}
|
||||
}
|
||||
|
||||
if (deviceLabel.isEmpty() && !targetAddress.isEmpty()) deviceLabel = DeviceLabel(targetAddress);
|
||||
|
||||
int hidCount = 0;
|
||||
if (!targetAddress.isEmpty())
|
||||
{
|
||||
for (const HidIf& hid : hids)
|
||||
{
|
||||
if ((ExtractAddress(hid.InstanceId) == targetAddress) && OpenHidPort(port->p_Context, hid, deviceLabel)) ++hidCount;
|
||||
}
|
||||
}
|
||||
|
||||
if (deviceLabel.isEmpty() && !hids.isEmpty()) deviceLabel = QStringLiteral("BLE");
|
||||
|
||||
port->IsConnected = (charCount > 0) || (hidCount > 0);
|
||||
|
||||
|
||||
return port->IsConnected;
|
||||
}
|
||||
bool Dri_Ble_Read(Dri_Ble_Struct_Port* port, Mid_Struct_RawPacket* packet, QString*)
|
||||
{
|
||||
*packet = Mid_Struct_RawPacket();
|
||||
packet->PortName = QStringLiteral("Bluetooth");
|
||||
if (!port->IsOpened || (port->p_Context == nullptr)) return false;
|
||||
for (Dri_Ble_Struct_HidPort* hid : port->p_Context->Hids)
|
||||
{
|
||||
Mid_Struct_RawPacket hidPacket;
|
||||
if (Dri_Hid_Read(&hid->ReadPort, &hidPacket, nullptr))
|
||||
{
|
||||
QMutexLocker lock(&port->p_Context->Mutex);
|
||||
port->p_Context->Queue.append(hidPacket);
|
||||
}
|
||||
}
|
||||
QMutexLocker lock(&port->p_Context->Mutex);
|
||||
if (port->p_Context->Queue.isEmpty()) return false;
|
||||
*packet = port->p_Context->Queue.takeFirst();
|
||||
return packet->IsValid;
|
||||
}
|
||||
bool Dri_Ble_Write(Dri_Ble_Struct_Port* port, const QByteArray& bytes, QString* textStatus)
|
||||
{
|
||||
if (!port->IsOpened || (port->p_Context == nullptr)) { if (textStatus != nullptr) *textStatus = QStringLiteral("Bluetooth port is not open, packet send was skipped."); return false; }
|
||||
if (bytes.isEmpty()) { if (textStatus != nullptr) *textStatus = QStringLiteral("Bluetooth packet is empty."); return false; }
|
||||
const auto TryWrite = [&](auto MatchFunc, const QString& OkText)
|
||||
{
|
||||
for (Dri_Ble_Struct_HidPort* hid : port->p_Context->Hids)
|
||||
{
|
||||
if (!MatchFunc(hid->UsagePage, hid->Usage) || (hid->WriteHandle == INVALID_HANDLE_VALUE)) continue;
|
||||
if (Dri_Hid_WritePacket(hid->WriteHandle, hid->OutputLength, bytes, nullptr))
|
||||
{
|
||||
if (textStatus != nullptr) *textStatus = OkText;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (TryWrite(IsCommandHid, QStringLiteral("Bluetooth Vendor command packet sent."))) return true;
|
||||
if (TryWrite(IsVendorHid, QStringLiteral("Bluetooth Vendor packet sent."))) return true;
|
||||
if (textStatus != nullptr) *textStatus = QStringLiteral("No writable Bluetooth Vendor endpoint was found.");
|
||||
return false;
|
||||
}
|
||||
|
||||
32
DRI/Dri_Ble.h
Normal file
32
DRI/Dri_Ble.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "MID/Mid_Def.h"
|
||||
#include <QtCore/QString>
|
||||
|
||||
struct Dri_Ble_Struct_Context;
|
||||
|
||||
// BLE port combines two channels:
|
||||
// 1. custom GATT notifications
|
||||
// 2. BLE HID reports and writes
|
||||
struct Dri_Ble_Struct_Port
|
||||
{
|
||||
bool IsOpened = false;
|
||||
bool IsConnected = false;
|
||||
QString TextEndpointSummary;
|
||||
Dri_Ble_Struct_Context* p_Context = nullptr;
|
||||
};
|
||||
|
||||
void Dri_Ble_Close(Dri_Ble_Struct_Port* p_Port);
|
||||
bool Dri_Ble_Init(
|
||||
Dri_Ble_Struct_Port* p_Port,
|
||||
const Mid_Struct_DeviceConfig& DeviceConfig,
|
||||
QString* p_TextStatus);
|
||||
bool Dri_Ble_Read(
|
||||
Dri_Ble_Struct_Port* p_Port,
|
||||
Mid_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus);
|
||||
bool Dri_Ble_Write(
|
||||
Dri_Ble_Struct_Port* p_Port,
|
||||
const QByteArray& ByteArray,
|
||||
QString* p_TextStatus);
|
||||
|
||||
52
DRI/Dri_Consumer.cpp
Normal file
52
DRI/Dri_Consumer.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#include "DRI/Dri_Consumer.h"
|
||||
|
||||
void Dri_Consumer_Close(Dri_Consumer_Struct_Port* p_Port)
|
||||
{
|
||||
Dri_Hid_CloseReadPort(&p_Port->ReadPort);
|
||||
}
|
||||
|
||||
bool Dri_Consumer_Init(
|
||||
Dri_Consumer_Struct_Port* p_Port,
|
||||
const Mid_Struct_DeviceConfig& DeviceConfig,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
Dri_Consumer_Close(p_Port);
|
||||
|
||||
Mid_Struct_DeviceMatch Match;
|
||||
Match.VendorId = DeviceConfig.VendorId;
|
||||
Match.ProductId = DeviceConfig.ProductId;
|
||||
Match.UsagePage = MID_CONST_USAGE_PAGE_CONSUMER;
|
||||
Match.Usage = MID_CONST_USAGE_CONSUMER;
|
||||
|
||||
QString DevicePath;
|
||||
quint16 InputLength = 0;
|
||||
if (!Mid_FindHidInterface(
|
||||
Match,
|
||||
&DevicePath,
|
||||
&InputLength,
|
||||
nullptr))
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("Consumer interface was not found: 000C / 0001.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return Dri_Hid_InitReadPort(
|
||||
&p_Port->ReadPort,
|
||||
DevicePath,
|
||||
InputLength,
|
||||
Mid_Enum_RawPacketSource_UsbConsumerHid,
|
||||
QStringLiteral("Consumer"),
|
||||
p_TextStatus);
|
||||
}
|
||||
|
||||
bool Dri_Consumer_Read(
|
||||
Dri_Consumer_Struct_Port* p_Port,
|
||||
Mid_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
return Dri_Hid_Read(&p_Port->ReadPort, p_Packet, p_TextStatus);
|
||||
}
|
||||
|
||||
20
DRI/Dri_Consumer.h
Normal file
20
DRI/Dri_Consumer.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "DRI/Dri_Hid.h"
|
||||
|
||||
// USB consumer report reader.
|
||||
struct Dri_Consumer_Struct_Port
|
||||
{
|
||||
Dri_Hid_Struct_ReadPort ReadPort;
|
||||
};
|
||||
|
||||
void Dri_Consumer_Close(Dri_Consumer_Struct_Port* p_Port);
|
||||
bool Dri_Consumer_Init(
|
||||
Dri_Consumer_Struct_Port* p_Port,
|
||||
const Mid_Struct_DeviceConfig& DeviceConfig,
|
||||
QString* p_TextStatus);
|
||||
bool Dri_Consumer_Read(
|
||||
Dri_Consumer_Struct_Port* p_Port,
|
||||
Mid_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus);
|
||||
|
||||
189
DRI/Dri_Hid.cpp
Normal file
189
DRI/Dri_Hid.cpp
Normal file
@@ -0,0 +1,189 @@
|
||||
#include "DRI/Dri_Hid.h"
|
||||
|
||||
#include <hidsdi.h>
|
||||
|
||||
#pragma comment(lib, "hid.lib")
|
||||
|
||||
namespace
|
||||
{
|
||||
bool Dri_Hid_BeginRead(Dri_Hid_Struct_ReadPort* p_Port, QString* p_TextStatus)
|
||||
{
|
||||
if (!p_Port->IsOpened || (p_Port->InputLength == 0)) return false;
|
||||
if (p_Port->IsReadPending) return true;
|
||||
|
||||
ResetEvent(p_Port->HandleEvent);
|
||||
p_Port->ReadBuffer.fill(0, p_Port->InputLength);
|
||||
p_Port->OverlappedRead = {};
|
||||
p_Port->OverlappedRead.hEvent = p_Port->HandleEvent;
|
||||
|
||||
DWORD BytesRead = 0;
|
||||
if (ReadFile(
|
||||
p_Port->HandleRead,
|
||||
p_Port->ReadBuffer.data(),
|
||||
p_Port->InputLength,
|
||||
&BytesRead,
|
||||
&p_Port->OverlappedRead) ||
|
||||
(GetLastError() == ERROR_IO_PENDING))
|
||||
{
|
||||
p_Port->IsReadPending = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p_TextStatus != nullptr) *p_TextStatus = QStringLiteral("%1 failed to start async read: %2").arg(p_Port->PortName).arg(GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Dri_Hid_CloseReadPort(Dri_Hid_Struct_ReadPort* p_Port)
|
||||
{
|
||||
if ((p_Port->HandleRead != INVALID_HANDLE_VALUE) && p_Port->IsReadPending) CancelIoEx(p_Port->HandleRead, &p_Port->OverlappedRead);
|
||||
if (p_Port->HandleRead != INVALID_HANDLE_VALUE) CloseHandle(p_Port->HandleRead);
|
||||
if (p_Port->HandleEvent != nullptr) CloseHandle(p_Port->HandleEvent);
|
||||
*p_Port = Dri_Hid_Struct_ReadPort();
|
||||
}
|
||||
|
||||
bool Dri_Hid_InitReadPort(
|
||||
Dri_Hid_Struct_ReadPort* p_Port,
|
||||
const QString& DevicePath,
|
||||
quint16 InputLength,
|
||||
Mid_Enum_RawPacketSource PacketSource,
|
||||
const QString& PortName,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
Dri_Hid_CloseReadPort(p_Port);
|
||||
|
||||
p_Port->HandleRead = CreateFileW(
|
||||
reinterpret_cast<LPCWSTR>(DevicePath.utf16()),
|
||||
GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_OVERLAPPED,
|
||||
nullptr);
|
||||
if (p_Port->HandleRead == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
if (p_TextStatus != nullptr) *p_TextStatus = QStringLiteral("%1 failed to open read handle: %2").arg(PortName).arg(GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->HandleEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
if (p_Port->HandleEvent == nullptr)
|
||||
{
|
||||
if (p_TextStatus != nullptr) *p_TextStatus = QStringLiteral("%1 failed to create event: %2").arg(PortName).arg(GetLastError());
|
||||
Dri_Hid_CloseReadPort(p_Port);
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->InputLength = InputLength;
|
||||
p_Port->PacketSource = PacketSource;
|
||||
p_Port->PortName = PortName;
|
||||
p_Port->ReadBuffer = QByteArray(InputLength, 0);
|
||||
p_Port->OverlappedRead.hEvent = p_Port->HandleEvent;
|
||||
p_Port->IsOpened = true;
|
||||
|
||||
if (!Dri_Hid_BeginRead(p_Port, p_TextStatus)) { Dri_Hid_CloseReadPort(p_Port); return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Dri_Hid_Read(
|
||||
Dri_Hid_Struct_ReadPort* p_Port,
|
||||
Mid_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
*p_Packet = Mid_Struct_RawPacket();
|
||||
p_Packet->Source = p_Port->PacketSource;
|
||||
p_Packet->PortName = p_Port->PortName;
|
||||
|
||||
if (!p_Port->IsOpened) return false;
|
||||
if (!p_Port->IsReadPending)
|
||||
{
|
||||
Dri_Hid_BeginRead(p_Port, p_TextStatus);
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD BytesRead = 0;
|
||||
if (!GetOverlappedResult(p_Port->HandleRead, &p_Port->OverlappedRead, &BytesRead, FALSE))
|
||||
{
|
||||
const DWORD ErrorCode = GetLastError();
|
||||
if (ErrorCode == ERROR_IO_INCOMPLETE) return false;
|
||||
if (p_TextStatus != nullptr) *p_TextStatus = QStringLiteral("%1 read packet failed: %2").arg(p_Port->PortName).arg(ErrorCode);
|
||||
Dri_Hid_CloseReadPort(p_Port);
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->IsReadPending = false;
|
||||
if (BytesRead > 0) { p_Packet->IsValid = true; p_Packet->ByteArray = p_Port->ReadBuffer.left(static_cast<int>(BytesRead)); }
|
||||
|
||||
Dri_Hid_BeginRead(p_Port, p_TextStatus);
|
||||
return p_Packet->IsValid;
|
||||
}
|
||||
|
||||
HANDLE Dri_Hid_OpenWriteHandle(const QString& DevicePath, QString* p_TextError)
|
||||
{
|
||||
HANDLE HandleWrite = CreateFileW(
|
||||
reinterpret_cast<LPCWSTR>(DevicePath.utf16()),
|
||||
GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
0,
|
||||
nullptr);
|
||||
if (HandleWrite != INVALID_HANDLE_VALUE) return HandleWrite;
|
||||
|
||||
const DWORD WriteError = GetLastError();
|
||||
HandleWrite = CreateFileW(
|
||||
reinterpret_cast<LPCWSTR>(DevicePath.utf16()),
|
||||
0,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
0,
|
||||
nullptr);
|
||||
if ((HandleWrite == INVALID_HANDLE_VALUE) && (p_TextError != nullptr)) *p_TextError = QStringLiteral("GENERIC_WRITE=%1, ZeroAccess=%2").arg(WriteError).arg(GetLastError());
|
||||
return HandleWrite;
|
||||
}
|
||||
|
||||
bool Dri_Hid_WritePacket(
|
||||
HANDLE HandleWrite,
|
||||
quint16 OutputLength,
|
||||
const QByteArray& Packet,
|
||||
QString* p_TextError)
|
||||
{
|
||||
if ((HandleWrite == INVALID_HANDLE_VALUE) || Packet.isEmpty()) return false;
|
||||
|
||||
QByteArray Buffer = Packet;
|
||||
if (HidD_SetOutputReport(HandleWrite, Buffer.data(), static_cast<ULONG>(Buffer.size()))) return true;
|
||||
|
||||
const DWORD SetReportError = GetLastError();
|
||||
DWORD BytesWritten = 0;
|
||||
if (WriteFile(HandleWrite, Buffer.constData(), static_cast<DWORD>(Buffer.size()), &BytesWritten, nullptr) && (BytesWritten == static_cast<DWORD>(Buffer.size()))) return true;
|
||||
|
||||
const DWORD WriteFileError = GetLastError();
|
||||
if ((OutputLength > 0) && (Buffer.size() < OutputLength))
|
||||
{
|
||||
Buffer.append(OutputLength - Buffer.size(), 0);
|
||||
|
||||
if (HidD_SetOutputReport(HandleWrite, Buffer.data(), static_cast<ULONG>(Buffer.size()))) return true;
|
||||
|
||||
const DWORD PaddedSetReportError = GetLastError();
|
||||
BytesWritten = 0;
|
||||
if (WriteFile(HandleWrite, Buffer.constData(), static_cast<DWORD>(Buffer.size()), &BytesWritten, nullptr) && (BytesWritten == static_cast<DWORD>(Buffer.size()))) return true;
|
||||
|
||||
const DWORD PaddedWriteFileError = GetLastError();
|
||||
if (p_TextError != nullptr)
|
||||
{
|
||||
*p_TextError = QStringLiteral(
|
||||
"SetOutputReport=%1, WriteFile=%2, PaddedSetOutputReport=%3, PaddedWriteFile=%4")
|
||||
.arg(SetReportError)
|
||||
.arg(WriteFileError)
|
||||
.arg(PaddedSetReportError)
|
||||
.arg(PaddedWriteFileError);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_TextError != nullptr) *p_TextError = QStringLiteral("SetOutputReport=%1, WriteFile=%2").arg(SetReportError).arg(WriteFileError);
|
||||
return false;
|
||||
}
|
||||
|
||||
41
DRI/Dri_Hid.h
Normal file
41
DRI/Dri_Hid.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "MID/Mid_Def.h"
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QString>
|
||||
#include <Windows.h>
|
||||
|
||||
// Shared HID async read/write helpers used by USB and BLE HID paths.
|
||||
struct Dri_Hid_Struct_ReadPort
|
||||
{
|
||||
HANDLE HandleRead = INVALID_HANDLE_VALUE;
|
||||
HANDLE HandleEvent = nullptr;
|
||||
OVERLAPPED OverlappedRead = {};
|
||||
bool IsOpened = false;
|
||||
bool IsReadPending = false;
|
||||
quint16 InputLength = 0;
|
||||
Mid_Enum_RawPacketSource PacketSource = Mid_Enum_RawPacketSource_None;
|
||||
QString PortName;
|
||||
QByteArray ReadBuffer;
|
||||
};
|
||||
|
||||
void Dri_Hid_CloseReadPort(Dri_Hid_Struct_ReadPort* p_Port);
|
||||
bool Dri_Hid_InitReadPort(
|
||||
Dri_Hid_Struct_ReadPort* p_Port,
|
||||
const QString& DevicePath,
|
||||
quint16 InputLength,
|
||||
Mid_Enum_RawPacketSource PacketSource,
|
||||
const QString& PortName,
|
||||
QString* p_TextStatus);
|
||||
bool Dri_Hid_Read(
|
||||
Dri_Hid_Struct_ReadPort* p_Port,
|
||||
Mid_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus);
|
||||
|
||||
HANDLE Dri_Hid_OpenWriteHandle(const QString& DevicePath, QString* p_TextError);
|
||||
bool Dri_Hid_WritePacket(
|
||||
HANDLE HandleWrite,
|
||||
quint16 OutputLength,
|
||||
const QByteArray& Packet,
|
||||
QString* p_TextError);
|
||||
|
||||
281
DRI/Dri_NkroRaw.cpp
Normal file
281
DRI/Dri_NkroRaw.cpp
Normal file
@@ -0,0 +1,281 @@
|
||||
#include "DRI/Dri_NkroRaw.h"
|
||||
|
||||
#include <QtCore/QVector>
|
||||
#include <Windows.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// Device filter and scancode-to-usage mapping.
|
||||
|
||||
QString Dri_NkroRaw_GetDevicePath(HANDLE DeviceHandle)
|
||||
{
|
||||
if (DeviceHandle == nullptr)
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
UINT NeedChars = 0;
|
||||
GetRawInputDeviceInfoW(DeviceHandle, RIDI_DEVICENAME, nullptr, &NeedChars);
|
||||
if (NeedChars == 0)
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
QVector<wchar_t> Buffer(static_cast<int>(NeedChars) + 1, 0);
|
||||
if (GetRawInputDeviceInfoW(DeviceHandle, RIDI_DEVICENAME, Buffer.data(), &NeedChars) == static_cast<UINT>(-1))
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
return QString::fromWCharArray(Buffer.constData()).trimmed();
|
||||
}
|
||||
|
||||
bool Dri_NkroRaw_IsTargetDevice(const QString& DevicePath, const Mid_Struct_DeviceConfig& DeviceConfig)
|
||||
{
|
||||
if (DevicePath.isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString UpperPath = DevicePath.toUpper();
|
||||
return UpperPath.contains(QStringLiteral("VID_%1").arg(DeviceConfig.VendorId, 4, 16, QLatin1Char('0')).toUpper()) &&
|
||||
UpperPath.contains(QStringLiteral("PID_%1").arg(DeviceConfig.ProductId, 4, 16, QLatin1Char('0')).toUpper());
|
||||
}
|
||||
|
||||
quint16 Dri_NkroRaw_GetUsage(const RAWKEYBOARD& Keyboard)
|
||||
{
|
||||
const bool IsE0 = (Keyboard.Flags & RI_KEY_E0) != 0;
|
||||
const bool IsE1 = (Keyboard.Flags & RI_KEY_E1) != 0;
|
||||
const USHORT ScanCode = Keyboard.MakeCode;
|
||||
|
||||
if (IsE1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (IsE0)
|
||||
{
|
||||
switch (ScanCode)
|
||||
{
|
||||
case 0x35: return 0x0054;
|
||||
case 0x1C: return 0x0058;
|
||||
case 0x1D: return 0x00E4;
|
||||
case 0x38: return 0x00E6;
|
||||
case 0x5B: return 0x00E3;
|
||||
case 0x5C: return 0x00E7;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
switch (ScanCode)
|
||||
{
|
||||
case 0x45: return 0x0053;
|
||||
case 0x37: return 0x0055;
|
||||
case 0x4A: return 0x0056;
|
||||
case 0x4E: return 0x0057;
|
||||
case 0x47: return 0x005F;
|
||||
case 0x48: return 0x0060;
|
||||
case 0x49: return 0x0061;
|
||||
case 0x4B: return 0x005C;
|
||||
case 0x4C: return 0x005D;
|
||||
case 0x4D: return 0x005E;
|
||||
case 0x4F: return 0x0059;
|
||||
case 0x50: return 0x005A;
|
||||
case 0x51: return 0x005B;
|
||||
case 0x52: return 0x0062;
|
||||
case 0x53: return 0x0063;
|
||||
case 0x1D: return 0x00E0;
|
||||
case 0x2A: return 0x00E1;
|
||||
case 0x36: return 0x00E5;
|
||||
case 0x38: return 0x00E2;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Dri_NkroRaw_Close(Dri_NkroRaw_Struct_Port* p_Port)
|
||||
{
|
||||
*p_Port = Dri_NkroRaw_Struct_Port();
|
||||
}
|
||||
|
||||
bool Dri_NkroRaw_Init(
|
||||
Dri_NkroRaw_Struct_Port* p_Port,
|
||||
const Mid_Struct_DeviceConfig& DeviceConfig,
|
||||
void* WindowHandle,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
Dri_NkroRaw_Close(p_Port);
|
||||
|
||||
if (WindowHandle == nullptr)
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("NKRO 原生输入链路打开失败:窗口句柄为空。");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
RAWINPUTDEVICE Device = {};
|
||||
Device.usUsagePage = MID_CONST_USAGE_PAGE_NKRO;
|
||||
Device.usUsage = MID_CONST_USAGE_NKRO;
|
||||
Device.dwFlags = RIDEV_INPUTSINK;
|
||||
Device.hwndTarget = reinterpret_cast<HWND>(WindowHandle);
|
||||
|
||||
if (!RegisterRawInputDevices(&Device, 1, sizeof(Device)))
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("NKRO 原生输入链路注册失败:%1").arg(GetLastError());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->IsOpened = true;
|
||||
p_Port->WindowHandle = WindowHandle;
|
||||
p_Port->DeviceConfig = DeviceConfig;
|
||||
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("已启用 NKRO 原生输入链路。");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Dri_NkroRaw_HandleMessage(
|
||||
Dri_NkroRaw_Struct_Port* p_Port,
|
||||
void* p_Message,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
if (!p_Port->IsOpened || (p_Message == nullptr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
MSG* p_Msg = reinterpret_cast<MSG*>(p_Message);
|
||||
if (p_Msg->message != WM_INPUT)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UINT NeedSize = 0;
|
||||
GetRawInputData(reinterpret_cast<HRAWINPUT>(p_Msg->lParam), RID_INPUT, nullptr, &NeedSize, sizeof(RAWINPUTHEADER));
|
||||
if (NeedSize == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray Buffer(static_cast<int>(NeedSize), 0);
|
||||
if (GetRawInputData(
|
||||
reinterpret_cast<HRAWINPUT>(p_Msg->lParam),
|
||||
RID_INPUT,
|
||||
Buffer.data(),
|
||||
&NeedSize,
|
||||
sizeof(RAWINPUTHEADER)) == static_cast<UINT>(-1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RAWINPUT* p_Input = reinterpret_cast<RAWINPUT*>(Buffer.data());
|
||||
if (p_Input->header.dwType != RIM_TYPEKEYBOARD)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString DevicePath = Dri_NkroRaw_GetDevicePath(p_Input->header.hDevice);
|
||||
if (!Dri_NkroRaw_IsTargetDevice(DevicePath, p_Port->DeviceConfig))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const quint16 Usage = Dri_NkroRaw_GetUsage(p_Input->data.keyboard);
|
||||
if (Usage == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool IsPressed = (p_Input->data.keyboard.Flags & RI_KEY_BREAK) == 0;
|
||||
bool IsChanged = false;
|
||||
|
||||
if ((Usage >= 0x00E0) && (Usage <= 0x00E7))
|
||||
{
|
||||
const quint8 BitMask = static_cast<quint8>(1U << (Usage - 0x00E0));
|
||||
const quint8 OldModifier = p_Port->Modifier;
|
||||
if (IsPressed)
|
||||
{
|
||||
p_Port->Modifier = static_cast<quint8>(p_Port->Modifier | BitMask);
|
||||
}
|
||||
else
|
||||
{
|
||||
p_Port->Modifier = static_cast<quint8>(p_Port->Modifier & static_cast<quint8>(~BitMask));
|
||||
}
|
||||
IsChanged = (OldModifier != p_Port->Modifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
const int ByteIndex = Usage / 8;
|
||||
const quint8 BitMask = static_cast<quint8>(1U << (Usage % 8));
|
||||
quint8 Value = static_cast<quint8>(p_Port->UsageBitmap.at(ByteIndex));
|
||||
const bool OldPressed = (Value & BitMask) != 0;
|
||||
if (OldPressed == IsPressed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Value = IsPressed ? static_cast<quint8>(Value | BitMask) : static_cast<quint8>(Value & static_cast<quint8>(~BitMask));
|
||||
p_Port->UsageBitmap[ByteIndex] = static_cast<char>(Value);
|
||||
IsChanged = true;
|
||||
}
|
||||
|
||||
if (!IsChanged)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_Port->DevicePath != DevicePath)
|
||||
{
|
||||
p_Port->DevicePath = DevicePath;
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("原生输入已命中目标设备:%1").arg(DevicePath);
|
||||
}
|
||||
}
|
||||
|
||||
Mid_Struct_RawPacket Packet;
|
||||
Packet.IsValid = true;
|
||||
Packet.Source = Mid_Enum_RawPacketSource_UsbNkroRaw;
|
||||
Packet.PortName = QStringLiteral("NKRO(原生输入)");
|
||||
Packet.ByteArray = QByteArray(MID_CONST_PACKET_SIZE_NKRO, 0);
|
||||
Packet.ByteArray[0] = static_cast<char>(Mid_Enum_ReportId_Nkro);
|
||||
Packet.ByteArray[1] = static_cast<char>(p_Port->Modifier);
|
||||
|
||||
for (int Index = 0; Index < MID_CONST_USAGE_BITMAP_SIZE; ++Index)
|
||||
{
|
||||
Packet.ByteArray[2 + Index] = p_Port->UsageBitmap.at(Index);
|
||||
}
|
||||
|
||||
p_Port->PacketQueue.append(Packet);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Dri_NkroRaw_Read(
|
||||
Dri_NkroRaw_Struct_Port* p_Port,
|
||||
Mid_Struct_RawPacket* p_Packet,
|
||||
QString*)
|
||||
{
|
||||
p_Packet->IsValid = false;
|
||||
p_Packet->Source = Mid_Enum_RawPacketSource_UsbNkroRaw;
|
||||
p_Packet->ByteArray.clear();
|
||||
p_Packet->PortName = QStringLiteral("NKRO(原生输入)");
|
||||
|
||||
if (!p_Port->IsOpened || p_Port->PacketQueue.isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
*p_Packet = p_Port->PacketQueue.takeFirst();
|
||||
return p_Packet->IsValid;
|
||||
}
|
||||
|
||||
31
DRI/Dri_NkroRaw.h
Normal file
31
DRI/Dri_NkroRaw.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "MID/Mid_Def.h"
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QList>
|
||||
#include <QtCore/QString>
|
||||
|
||||
// Windows RAWINPUT reader for the NKRO path.
|
||||
struct Dri_NkroRaw_Struct_Port
|
||||
{
|
||||
bool IsOpened = false;
|
||||
void* WindowHandle = nullptr;
|
||||
Mid_Struct_DeviceConfig DeviceConfig;
|
||||
quint8 Modifier = 0;
|
||||
QByteArray UsageBitmap = QByteArray(MID_CONST_USAGE_BITMAP_SIZE, 0);
|
||||
QList<Mid_Struct_RawPacket> PacketQueue;
|
||||
QString DevicePath;
|
||||
};
|
||||
|
||||
void Dri_NkroRaw_Close(Dri_NkroRaw_Struct_Port* p_Port);
|
||||
bool Dri_NkroRaw_Init(Dri_NkroRaw_Struct_Port* p_Port,
|
||||
const Mid_Struct_DeviceConfig& DeviceConfig,
|
||||
void* WindowHandle,
|
||||
QString* p_TextStatus);
|
||||
bool Dri_NkroRaw_HandleMessage(Dri_NkroRaw_Struct_Port* p_Port,
|
||||
void* p_Message,
|
||||
QString* p_TextStatus);
|
||||
bool Dri_NkroRaw_Read(Dri_NkroRaw_Struct_Port* p_Port,
|
||||
Mid_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus);
|
||||
|
||||
248
DRI/Dri_Vendor.cpp
Normal file
248
DRI/Dri_Vendor.cpp
Normal file
@@ -0,0 +1,248 @@
|
||||
#include "DRI/Dri_Vendor.h"
|
||||
|
||||
#include <QtCore/QStringList>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
void Dri_Vendor_CloseHandle(HANDLE* p_Handle)
|
||||
{
|
||||
if ((*p_Handle != nullptr) && (*p_Handle != INVALID_HANDLE_VALUE))
|
||||
{
|
||||
CloseHandle(*p_Handle);
|
||||
}
|
||||
*p_Handle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
bool Dri_Vendor_TryWrite(
|
||||
HANDLE HandleWrite,
|
||||
quint16 OutputLength,
|
||||
const QByteArray& Packet,
|
||||
const QString& SuccessText,
|
||||
const QString& ErrorPrefix,
|
||||
QString* p_TextStatus,
|
||||
QStringList* p_ErrorList)
|
||||
{
|
||||
QString TextError;
|
||||
if (Dri_Hid_WritePacket(HandleWrite, OutputLength, Packet, &TextError))
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = SuccessText;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!TextError.isEmpty())
|
||||
{
|
||||
p_ErrorList->append(ErrorPrefix.arg(TextError));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Dri_Vendor_Close(Dri_Vendor_Struct_Port* p_Port)
|
||||
{
|
||||
Dri_Hid_CloseReadPort(&p_Port->ReadPort);
|
||||
Dri_Vendor_CloseHandle(&p_Port->HandleWriteVendor);
|
||||
Dri_Vendor_CloseHandle(&p_Port->HandleWriteCommand);
|
||||
Dri_Vendor_CloseHandle(&p_Port->HandleWriteNkro);
|
||||
p_Port->VendorWriteOutputLength = 0;
|
||||
p_Port->CommandWriteOutputLength = 0;
|
||||
p_Port->NkroWriteOutputLength = 0;
|
||||
p_Port->IsBluetoothTransport = false;
|
||||
}
|
||||
|
||||
bool Dri_Vendor_Init(
|
||||
Dri_Vendor_Struct_Port* p_Port,
|
||||
const Mid_Struct_DeviceConfig& DeviceConfig,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
Dri_Vendor_Close(p_Port);
|
||||
|
||||
Mid_Struct_DeviceMatch VendorMatch;
|
||||
VendorMatch.VendorId = DeviceConfig.VendorId;
|
||||
VendorMatch.ProductId = DeviceConfig.ProductId;
|
||||
VendorMatch.UsagePage = MID_CONST_USAGE_PAGE_VENDOR;
|
||||
VendorMatch.Usage = MID_CONST_USAGE_VENDOR;
|
||||
|
||||
QString VendorPath;
|
||||
QString VendorInstanceId;
|
||||
quint16 InputLength = 0;
|
||||
quint16 VendorOutputLength = 0;
|
||||
if (!Mid_FindHidInterface(
|
||||
VendorMatch,
|
||||
&VendorPath,
|
||||
&InputLength,
|
||||
&VendorOutputLength,
|
||||
&VendorInstanceId))
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("Vendor interface was not found: FF00 / 0002.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->IsBluetoothTransport = Mid_IsBluetoothHidInstanceId(VendorInstanceId);
|
||||
const QString VendorPortName = p_Port->IsBluetoothTransport
|
||||
? QStringLiteral("Bluetooth/HID Vendor")
|
||||
: QStringLiteral("Vendor");
|
||||
|
||||
if (!Dri_Hid_InitReadPort(
|
||||
&p_Port->ReadPort,
|
||||
VendorPath,
|
||||
InputLength,
|
||||
Mid_Enum_RawPacketSource_UsbVendorHid,
|
||||
VendorPortName,
|
||||
p_TextStatus))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->HandleWriteVendor = Dri_Hid_OpenWriteHandle(VendorPath, nullptr);
|
||||
p_Port->VendorWriteOutputLength = VendorOutputLength;
|
||||
|
||||
QString CommandPath;
|
||||
quint16 CommandOutputLength = 0;
|
||||
Mid_Struct_DeviceMatch CommandMatch;
|
||||
CommandMatch.VendorId = DeviceConfig.VendorId;
|
||||
CommandMatch.ProductId = DeviceConfig.ProductId;
|
||||
CommandMatch.UsagePage = MID_CONST_USAGE_PAGE_VENDOR_COMMAND;
|
||||
CommandMatch.Usage = MID_CONST_USAGE_VENDOR_COMMAND;
|
||||
if (Mid_FindHidInterface(
|
||||
CommandMatch,
|
||||
&CommandPath,
|
||||
nullptr,
|
||||
&CommandOutputLength))
|
||||
{
|
||||
QString TextError;
|
||||
p_Port->HandleWriteCommand = Dri_Hid_OpenWriteHandle(CommandPath, &TextError);
|
||||
p_Port->CommandWriteOutputLength = CommandOutputLength;
|
||||
if ((p_Port->HandleWriteCommand == INVALID_HANDLE_VALUE) && (p_TextStatus != nullptr))
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("Vendor command write handle failed: %1").arg(TextError);
|
||||
}
|
||||
}
|
||||
else if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("Vendor command interface was not found: FF01 / 0005.");
|
||||
}
|
||||
|
||||
QString NkroPath;
|
||||
quint16 NkroOutputLength = 0;
|
||||
Mid_Struct_DeviceMatch NkroMatch;
|
||||
NkroMatch.VendorId = DeviceConfig.VendorId;
|
||||
NkroMatch.ProductId = DeviceConfig.ProductId;
|
||||
NkroMatch.UsagePage = MID_CONST_USAGE_PAGE_NKRO;
|
||||
NkroMatch.Usage = MID_CONST_USAGE_NKRO;
|
||||
if (Mid_FindHidInterface(
|
||||
NkroMatch,
|
||||
&NkroPath,
|
||||
nullptr,
|
||||
&NkroOutputLength))
|
||||
{
|
||||
QString TextError;
|
||||
p_Port->HandleWriteNkro = Dri_Hid_OpenWriteHandle(NkroPath, &TextError);
|
||||
p_Port->NkroWriteOutputLength = NkroOutputLength;
|
||||
if ((p_Port->HandleWriteNkro == INVALID_HANDLE_VALUE) && (p_TextStatus != nullptr))
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("NKRO write handle failed: %1").arg(TextError);
|
||||
}
|
||||
}
|
||||
else if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("NKRO write interface was not found.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Dri_Vendor_Read(
|
||||
Dri_Vendor_Struct_Port* p_Port,
|
||||
Mid_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
return Dri_Hid_Read(&p_Port->ReadPort, p_Packet, p_TextStatus);
|
||||
}
|
||||
|
||||
bool Dri_Vendor_Write(
|
||||
Dri_Vendor_Struct_Port* p_Port,
|
||||
const QByteArray& ByteArray,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
const QString RouteLabel = p_Port->IsBluetoothTransport
|
||||
? QStringLiteral("Bluetooth Vendor")
|
||||
: QStringLiteral("Vendor");
|
||||
|
||||
if (!p_Port->ReadPort.IsOpened)
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("%1 read path is not open, packet was not sent.").arg(RouteLabel);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (ByteArray.isEmpty())
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("%1 packet is empty.").arg(RouteLabel);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QStringList ErrorList;
|
||||
const quint8 ReportId = static_cast<quint8>(ByteArray.at(0));
|
||||
|
||||
if (ReportId == Mid_Enum_ReportId_VendorCommand)
|
||||
{
|
||||
if (Dri_Vendor_TryWrite(
|
||||
p_Port->HandleWriteCommand,
|
||||
p_Port->CommandWriteOutputLength,
|
||||
ByteArray,
|
||||
QStringLiteral("Packet sent to %1 command interface.").arg(RouteLabel),
|
||||
RouteLabel + QStringLiteral(" command write failed: %1"),
|
||||
p_TextStatus,
|
||||
&ErrorList))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Dri_Vendor_TryWrite(
|
||||
p_Port->HandleWriteNkro,
|
||||
p_Port->NkroWriteOutputLength,
|
||||
ByteArray,
|
||||
QStringLiteral("Packet sent to NKRO write interface."),
|
||||
QStringLiteral("NKRO write failed: %1"),
|
||||
p_TextStatus,
|
||||
&ErrorList))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Dri_Vendor_TryWrite(
|
||||
p_Port->HandleWriteVendor,
|
||||
p_Port->VendorWriteOutputLength,
|
||||
ByteArray,
|
||||
QStringLiteral("Packet sent to %1 write interface.").arg(RouteLabel),
|
||||
RouteLabel + QStringLiteral(" write failed: %1"),
|
||||
p_TextStatus,
|
||||
&ErrorList))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = ErrorList.isEmpty()
|
||||
? QStringLiteral("No writable output handle is available, packet send was skipped.")
|
||||
: ErrorList.join(QStringLiteral("\n"));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
31
DRI/Dri_Vendor.h
Normal file
31
DRI/Dri_Vendor.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "DRI/Dri_Hid.h"
|
||||
|
||||
// USB vendor reader plus three write routes: vendor, command, and NKRO.
|
||||
struct Dri_Vendor_Struct_Port
|
||||
{
|
||||
Dri_Hid_Struct_ReadPort ReadPort;
|
||||
HANDLE HandleWriteVendor = INVALID_HANDLE_VALUE;
|
||||
HANDLE HandleWriteCommand = INVALID_HANDLE_VALUE;
|
||||
HANDLE HandleWriteNkro = INVALID_HANDLE_VALUE;
|
||||
quint16 VendorWriteOutputLength = 0;
|
||||
quint16 CommandWriteOutputLength = 0;
|
||||
quint16 NkroWriteOutputLength = 0;
|
||||
bool IsBluetoothTransport = false;
|
||||
};
|
||||
|
||||
void Dri_Vendor_Close(Dri_Vendor_Struct_Port* p_Port);
|
||||
bool Dri_Vendor_Init(
|
||||
Dri_Vendor_Struct_Port* p_Port,
|
||||
const Mid_Struct_DeviceConfig& DeviceConfig,
|
||||
QString* p_TextStatus);
|
||||
bool Dri_Vendor_Read(
|
||||
Dri_Vendor_Struct_Port* p_Port,
|
||||
Mid_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus);
|
||||
bool Dri_Vendor_Write(
|
||||
Dri_Vendor_Struct_Port* p_Port,
|
||||
const QByteArray& ByteArray,
|
||||
QString* p_TextStatus);
|
||||
|
||||
127
LOGIC/Lgc_Core.cpp
Normal file
127
LOGIC/Lgc_Core.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
#include "LOGIC/Lgc_Core_Private.h"
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
void Lgc_Core_Init(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
*p_State = Lgc_Core_Struct_State();
|
||||
|
||||
p_State->DeviceConfig = Mid_Struct_DeviceConfig();
|
||||
p_State->TextConnection = QStringLiteral("未连接,等待枚举设备。");
|
||||
p_State->TextFunctionStatus = QStringLiteral("等待功能键动作。");
|
||||
|
||||
Lgc_Core_ClearAllKeyStates(p_State);
|
||||
|
||||
p_State->IsSystemNumLockOn = (GetKeyState(VK_NUMLOCK) & 0x0001) != 0;
|
||||
Lgc_Core_FillMaskAllEnabled(&p_State->FunctionMaskBitmap);
|
||||
Lgc_Core_FillMaskAllEnabled(&p_State->KeyboardMaskBitmap);
|
||||
|
||||
p_State->FunctionButtonConfig = Lgc_FunctionButton_Config();
|
||||
Lgc_Core_ApplyFunctionConfig(p_State);
|
||||
}
|
||||
|
||||
void Lgc_Core_SetWindowHandle(Lgc_Core_Struct_State* p_State, void* WindowHandle)
|
||||
{
|
||||
p_State->WindowHandle = WindowHandle;
|
||||
}
|
||||
|
||||
void Lgc_Core_HandleNativeMessage(Lgc_Core_Struct_State* p_State, void* p_Message)
|
||||
{
|
||||
QString TextStatus;
|
||||
Dri_NkroRaw_HandleMessage(&p_State->DriNkroPort, p_Message, &TextStatus);
|
||||
Lgc_Core_AppendStatusLog(p_State, TextStatus);
|
||||
}
|
||||
|
||||
void Lgc_Core_Start(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
if (p_State->IsStarted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
p_State->IsStarted = true;
|
||||
Lgc_Core_RefreshDevice(p_State);
|
||||
}
|
||||
|
||||
void Lgc_Core_Close(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
Lgc_Core_CloseAllPorts(p_State);
|
||||
|
||||
p_State->TextConnection = QStringLiteral("未连接,等待枚举设备。");
|
||||
p_State->TextFunctionStatus = QStringLiteral("等待功能键动作。");
|
||||
p_State->ActiveSendTransport = Lgc_Core_Enum_SendTransport_None;
|
||||
p_State->IsConnected = false;
|
||||
p_State->IsStarted = false;
|
||||
|
||||
Lgc_Core_ClearAllKeyStates(p_State);
|
||||
}
|
||||
|
||||
void Lgc_Core_RefreshDevice(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
QString TextStatus;
|
||||
|
||||
Lgc_Core_CloseAllPorts(p_State);
|
||||
Lgc_Core_ClearAllKeyStates(p_State);
|
||||
|
||||
TextStatus.clear();
|
||||
Dri_NkroRaw_Init(&p_State->DriNkroPort, p_State->DeviceConfig, p_State->WindowHandle, &TextStatus);
|
||||
Lgc_Core_AppendStatusLog(p_State, TextStatus);
|
||||
|
||||
TextStatus.clear();
|
||||
Dri_Consumer_Init(&p_State->DriConsumerPort, p_State->DeviceConfig, &TextStatus);
|
||||
Lgc_Core_AppendStatusLog(p_State, TextStatus);
|
||||
|
||||
TextStatus.clear();
|
||||
Dri_Vendor_Init(&p_State->DriVendorPort, p_State->DeviceConfig, &TextStatus);
|
||||
Lgc_Core_AppendStatusLog(p_State, TextStatus);
|
||||
|
||||
TextStatus.clear();
|
||||
Dri_Ble_Init(&p_State->DriBlePort, p_State->DeviceConfig, &TextStatus);
|
||||
Lgc_Core_AppendStatusLog(p_State, TextStatus);
|
||||
|
||||
Lgc_Core_NormalizeSendTransport(p_State);
|
||||
Lgc_Core_SendCurrentMask(p_State);
|
||||
|
||||
if (p_State->DriVendorPort.ReadPort.IsOpened)
|
||||
{
|
||||
Lgc_Core_SendTimeSync(p_State);
|
||||
}
|
||||
|
||||
Lgc_Core_SyncSystemState(p_State);
|
||||
}
|
||||
|
||||
void Lgc_Core_ClearLog(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
p_State->TextLog.clear();
|
||||
}
|
||||
|
||||
bool Lgc_Core_Poll(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
bool IsChanged = false;
|
||||
Mid_Struct_RawPacket Packet;
|
||||
const auto PollPort = [&](auto ReadFunc, auto HandleFunc, auto* p_Port)
|
||||
{
|
||||
QString TextStatus;
|
||||
if (ReadFunc(p_Port, &Packet, &TextStatus))
|
||||
{
|
||||
HandleFunc(p_State, Packet);
|
||||
IsChanged = true;
|
||||
}
|
||||
if (!TextStatus.isEmpty())
|
||||
{
|
||||
Lgc_Core_AppendStatusLog(p_State, TextStatus);
|
||||
IsChanged = true;
|
||||
}
|
||||
};
|
||||
|
||||
PollPort(Dri_NkroRaw_Read, Lgc_Core_HandleNkroPacket, &p_State->DriNkroPort);
|
||||
PollPort(Dri_Consumer_Read, Lgc_Core_HandleConsumerPacket, &p_State->DriConsumerPort);
|
||||
PollPort(Dri_Vendor_Read, Lgc_Core_HandleVendorPacket, &p_State->DriVendorPort);
|
||||
PollPort(Dri_Ble_Read, Lgc_Core_HandleBlePacket, &p_State->DriBlePort);
|
||||
|
||||
IsChanged |= Lgc_Core_HandleFunctionButtons(p_State);
|
||||
IsChanged |= Lgc_Core_SyncSystemState(p_State);
|
||||
return IsChanged;
|
||||
}
|
||||
|
||||
|
||||
65
LOGIC/Lgc_Core.h
Normal file
65
LOGIC/Lgc_Core.h
Normal file
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include "DRI/Dri_Ble.h"
|
||||
#include "DRI/Dri_Consumer.h"
|
||||
#include "DRI/Dri_NkroRaw.h"
|
||||
#include "DRI/Dri_Vendor.h"
|
||||
#include "LOGIC/Lgc_Func_Button.h"
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QVector>
|
||||
|
||||
enum Lgc_Core_Enum_SendTransport : quint8
|
||||
{
|
||||
Lgc_Core_Enum_SendTransport_None = 0,
|
||||
Lgc_Core_Enum_SendTransport_Usb,
|
||||
Lgc_Core_Enum_SendTransport_Ble
|
||||
};
|
||||
|
||||
struct Lgc_Core_Struct_State
|
||||
{
|
||||
Dri_Ble_Struct_Port DriBlePort;
|
||||
Dri_NkroRaw_Struct_Port DriNkroPort;
|
||||
Dri_Consumer_Struct_Port DriConsumerPort;
|
||||
Dri_Vendor_Struct_Port DriVendorPort;
|
||||
|
||||
Mid_Struct_DeviceConfig DeviceConfig;
|
||||
QString TextConnection;
|
||||
QString TextLog;
|
||||
QString TextFunctionStatus;
|
||||
|
||||
bool IsVisibleKeyStateValid = false;
|
||||
QVector<quint16> VisibleUsageList;
|
||||
|
||||
bool IsPhysicalKeyStateValid = false;
|
||||
QVector<quint16> PhysicalUsageList;
|
||||
QVector<quint16> LastPhysicalUsageList;
|
||||
|
||||
bool IsSystemNumLockOn = false;
|
||||
QByteArray FunctionMaskBitmap;
|
||||
QByteArray KeyboardMaskBitmap;
|
||||
|
||||
Lgc_FunctionButton_Config FunctionButtonConfig;
|
||||
bool IsAltThemeEnabled = false;
|
||||
|
||||
void* WindowHandle = nullptr;
|
||||
bool IsConnected = false;
|
||||
bool IsStarted = false;
|
||||
Lgc_Core_Enum_SendTransport ActiveSendTransport = Lgc_Core_Enum_SendTransport_None;
|
||||
bool IsFunctionSequenceRecording = false;
|
||||
};
|
||||
|
||||
void Lgc_Core_Init(Lgc_Core_Struct_State* p_State);
|
||||
void Lgc_Core_SetWindowHandle(Lgc_Core_Struct_State* p_State, void* WindowHandle);
|
||||
void Lgc_Core_HandleNativeMessage(Lgc_Core_Struct_State* p_State, void* p_Message);
|
||||
void Lgc_Core_Start(Lgc_Core_Struct_State* p_State);
|
||||
void Lgc_Core_Close(Lgc_Core_Struct_State* p_State);
|
||||
void Lgc_Core_RefreshDevice(Lgc_Core_Struct_State* p_State);
|
||||
void Lgc_Core_ClearLog(Lgc_Core_Struct_State* p_State);
|
||||
bool Lgc_Core_Poll(Lgc_Core_Struct_State* p_State);
|
||||
|
||||
bool Lgc_Core_ApplyFunctionConfig(Lgc_Core_Struct_State* p_State);
|
||||
bool Lgc_Core_SendTimeSync(Lgc_Core_Struct_State* p_State);
|
||||
bool Lgc_Core_SendThemeSwitch(Lgc_Core_Struct_State* p_State);
|
||||
|
||||
|
||||
320
LOGIC/Lgc_Core_Control.cpp
Normal file
320
LOGIC/Lgc_Core_Control.cpp
Normal file
@@ -0,0 +1,320 @@
|
||||
#include "LOGIC/Lgc_Core_Private.h"
|
||||
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QTimeZone>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct Lgc_Core_Struct_ThemeColor
|
||||
{
|
||||
quint8 Red;
|
||||
quint8 Green;
|
||||
quint8 Blue;
|
||||
};
|
||||
|
||||
QString Lgc_Core_FormatThemeColor(quint8 Red, quint8 Green, quint8 Blue)
|
||||
{
|
||||
return QStringLiteral("%1 %2 %3")
|
||||
.arg(Red, 2, 16, QLatin1Char('0'))
|
||||
.arg(Green, 2, 16, QLatin1Char('0'))
|
||||
.arg(Blue, 2, 16, QLatin1Char('0'))
|
||||
.toUpper();
|
||||
}
|
||||
|
||||
Lgc_Core_Struct_ThemeColor Lgc_Core_GetNextThemeColor(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
p_State->IsAltThemeEnabled = !p_State->IsAltThemeEnabled;
|
||||
|
||||
if (p_State->IsAltThemeEnabled)
|
||||
{
|
||||
return { 0xF7, 0x25, 0x85 };
|
||||
}
|
||||
|
||||
return { 0x4C, 0xC9, 0xF0 };
|
||||
}
|
||||
|
||||
bool Lgc_Core_IsUsageInRange(quint16 Usage)
|
||||
{
|
||||
return Usage <= MID_CONST_KEYBOARD_USAGE_MAX;
|
||||
}
|
||||
|
||||
bool Lgc_Core_IsUsageEnabled(const QByteArray& Bitmap, quint16 Usage)
|
||||
{
|
||||
if (!Lgc_Core_IsUsageInRange(Usage) || (Bitmap.size() < MID_CONST_USAGE_BITMAP_SIZE))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const int ByteIndex = Usage / 8;
|
||||
const quint8 BitMask = static_cast<quint8>(1U << (Usage % 8));
|
||||
const quint8 ByteValue = static_cast<quint8>(Bitmap.at(ByteIndex));
|
||||
return (ByteValue & BitMask) != 0;
|
||||
}
|
||||
|
||||
void Lgc_Core_SetUsageEnabled(QByteArray* p_Bitmap, quint16 Usage, bool IsEnabled)
|
||||
{
|
||||
if (!Lgc_Core_IsUsageInRange(Usage))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (p_Bitmap->size() < MID_CONST_USAGE_BITMAP_SIZE)
|
||||
{
|
||||
Lgc_Core_FillMaskAllEnabled(p_Bitmap);
|
||||
}
|
||||
|
||||
const int ByteIndex = Usage / 8;
|
||||
const quint8 BitMask = static_cast<quint8>(1U << (Usage % 8));
|
||||
quint8 ByteValue = static_cast<quint8>(p_Bitmap->at(ByteIndex));
|
||||
|
||||
if (IsEnabled)
|
||||
{
|
||||
ByteValue = static_cast<quint8>(ByteValue | BitMask);
|
||||
}
|
||||
else
|
||||
{
|
||||
ByteValue = static_cast<quint8>(ByteValue & ~BitMask);
|
||||
}
|
||||
|
||||
(*p_Bitmap)[ByteIndex] = static_cast<char>(ByteValue);
|
||||
}
|
||||
|
||||
void Lgc_Core_RebuildKeyboardMask(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
if (p_State->FunctionMaskBitmap.size() < MID_CONST_USAGE_BITMAP_SIZE)
|
||||
{
|
||||
Lgc_Core_FillMaskAllEnabled(&p_State->FunctionMaskBitmap);
|
||||
}
|
||||
|
||||
p_State->KeyboardMaskBitmap.resize(MID_CONST_USAGE_BITMAP_SIZE);
|
||||
for (int Index = 0; Index < MID_CONST_USAGE_BITMAP_SIZE; ++Index)
|
||||
{
|
||||
p_State->KeyboardMaskBitmap[Index] = p_State->FunctionMaskBitmap.at(Index);
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray Lgc_Core_BuildMaskPacket(const Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
QByteArray Packet(MID_CONST_PACKET_SIZE_VENDOR, 0);
|
||||
Packet[0] = static_cast<char>(Mid_Enum_ReportId_Vendor);
|
||||
for (int Index = 0; (Index < MID_CONST_USAGE_BITMAP_SIZE) && (Index < p_State->KeyboardMaskBitmap.size()); ++Index)
|
||||
{
|
||||
Packet[Index + 2] = p_State->KeyboardMaskBitmap.at(Index);
|
||||
}
|
||||
return Packet;
|
||||
}
|
||||
|
||||
QByteArray Lgc_Core_BuildCommandPacket(quint8 CommandId, const QByteArray& Payload)
|
||||
{
|
||||
QByteArray Packet(MID_CONST_PACKET_SIZE_VENDOR_COMMAND, 0);
|
||||
Packet[0] = static_cast<char>(Mid_Enum_ReportId_VendorCommand);
|
||||
Packet[1] = static_cast<char>(CommandId);
|
||||
for (int Index = 0; (Index < MID_CONST_PACKET_SIZE_VENDOR_COMMAND_DATA) && (Index < Payload.size()); ++Index)
|
||||
{
|
||||
Packet[Index + 2] = Payload.at(Index);
|
||||
}
|
||||
return Packet;
|
||||
}
|
||||
|
||||
bool Lgc_Core_SendPacket(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
const QByteArray& Packet,
|
||||
const QString& ExplainText)
|
||||
{
|
||||
QString RouteText;
|
||||
QString TextStatus;
|
||||
const auto AppendStatus = [&TextStatus](const QString& StatusText)
|
||||
{
|
||||
if (StatusText.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TextStatus.isEmpty())
|
||||
{
|
||||
TextStatus.append(QLatin1Char('\n'));
|
||||
}
|
||||
TextStatus.append(StatusText);
|
||||
};
|
||||
const auto AppendRoute = [&RouteText](const QString& RouteName)
|
||||
{
|
||||
if (RouteName.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!RouteText.isEmpty())
|
||||
{
|
||||
RouteText.append(QStringLiteral(" -> "));
|
||||
}
|
||||
RouteText.append(RouteName);
|
||||
};
|
||||
const auto TrySendUsb = [&]() -> bool
|
||||
{
|
||||
if (!p_State->DriVendorPort.ReadPort.IsOpened)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QString UsbStatus;
|
||||
const QString RouteName = p_State->DriVendorPort.ReadPort.PortName;
|
||||
if (Dri_Vendor_Write(&p_State->DriVendorPort, Packet, &UsbStatus))
|
||||
{
|
||||
Lgc_Core_AppendPacketLog(
|
||||
p_State,
|
||||
QStringLiteral("发送"),
|
||||
RouteName,
|
||||
Packet,
|
||||
ExplainText);
|
||||
Lgc_Core_AppendStatusLog(p_State, UsbStatus);
|
||||
return true;
|
||||
}
|
||||
|
||||
AppendRoute(RouteName);
|
||||
AppendStatus(UsbStatus);
|
||||
return false;
|
||||
};
|
||||
const auto TrySendBle = [&]() -> bool
|
||||
{
|
||||
if (!p_State->DriBlePort.IsConnected)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QString BleStatus;
|
||||
if (Dri_Ble_Write(&p_State->DriBlePort, Packet, &BleStatus))
|
||||
{
|
||||
Lgc_Core_AppendPacketLog(
|
||||
p_State,
|
||||
QStringLiteral("发送"),
|
||||
QStringLiteral("Bluetooth/HID Vendor"),
|
||||
Packet,
|
||||
ExplainText);
|
||||
Lgc_Core_AppendStatusLog(p_State, BleStatus);
|
||||
return true;
|
||||
}
|
||||
|
||||
AppendRoute(QStringLiteral("Bluetooth/HID Vendor"));
|
||||
AppendStatus(BleStatus);
|
||||
return false;
|
||||
};
|
||||
|
||||
if (p_State->DriBlePort.IsConnected)
|
||||
{
|
||||
if (TrySendBle() || TrySendUsb())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (TrySendUsb())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (RouteText.isEmpty())
|
||||
{
|
||||
RouteText = QStringLiteral("Vendor");
|
||||
}
|
||||
if (TextStatus.isEmpty())
|
||||
{
|
||||
TextStatus = QStringLiteral("No connected USB/Bluetooth device was found.");
|
||||
}
|
||||
|
||||
Lgc_Core_AppendPacketLog(
|
||||
p_State,
|
||||
QStringLiteral("发送失败"),
|
||||
RouteText,
|
||||
Packet,
|
||||
ExplainText);
|
||||
Lgc_Core_AppendStatusLog(p_State, TextStatus);
|
||||
return false;
|
||||
}
|
||||
|
||||
qint64 Lgc_Core_GetBeijingTimestampMs()
|
||||
{
|
||||
return QDateTime::currentDateTimeUtc()
|
||||
.toTimeZone(QTimeZone("Asia/Shanghai"))
|
||||
.toMSecsSinceEpoch() + (8LL * 60LL * 60LL * 1000LL);
|
||||
}
|
||||
|
||||
QString Lgc_Core_GetBeijingTimeText(qint64 TimestampMs)
|
||||
{
|
||||
const QTimeZone BeijingTimeZone("Asia/Shanghai");
|
||||
return QDateTime::fromMSecsSinceEpoch(TimestampMs, Qt::UTC)
|
||||
.toTimeZone(BeijingTimeZone)
|
||||
.toString(QStringLiteral("yyyy-MM-dd HH:mm:ss.zzz"));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Lgc_Core_FillMaskAllEnabled(QByteArray* p_UsageBitmap)
|
||||
{
|
||||
*p_UsageBitmap = QByteArray(MID_CONST_USAGE_BITMAP_SIZE, static_cast<char>(0xFF));
|
||||
}
|
||||
|
||||
bool Lgc_Core_SendCurrentMask(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
Lgc_Core_RebuildKeyboardMask(p_State);
|
||||
return Lgc_Core_SendPacket(
|
||||
p_State,
|
||||
Lgc_Core_BuildMaskPacket(p_State),
|
||||
QStringLiteral("0x04 键盘掩码同步"));
|
||||
}
|
||||
|
||||
bool Lgc_Core_ApplyFunctionConfig(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
Lgc_Core_FillMaskAllEnabled(&p_State->FunctionMaskBitmap);
|
||||
|
||||
const QVector<quint16> UsageList = Lgc_FunctionButton_GetConfigurableUsages();
|
||||
for (quint16 Usage : UsageList)
|
||||
{
|
||||
if (Lgc_FunctionButton_HasUsageFeature(p_State->FunctionButtonConfig, Usage))
|
||||
{
|
||||
Lgc_Core_SetUsageEnabled(&p_State->FunctionMaskBitmap, Usage, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (p_State->IsStarted &&
|
||||
(p_State->DriVendorPort.ReadPort.IsOpened || p_State->DriBlePort.IsConnected))
|
||||
{
|
||||
Lgc_Core_SendCurrentMask(p_State);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Lgc_Core_SendTimeSync(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
const qint64 TimestampMs = Lgc_Core_GetBeijingTimestampMs();
|
||||
QByteArray Payload(MID_CONST_PACKET_SIZE_VENDOR_COMMAND_DATA, 0);
|
||||
for (int Index = 0; Index < MID_CONST_PACKET_SIZE_VENDOR_COMMAND_DATA; ++Index)
|
||||
{
|
||||
Payload[Index] = static_cast<char>((static_cast<quint64>(TimestampMs) >> (Index * 8)) & 0xFF);
|
||||
}
|
||||
|
||||
return Lgc_Core_SendPacket(
|
||||
p_State,
|
||||
Lgc_Core_BuildCommandPacket(0x02, Payload),
|
||||
QStringLiteral("0x05 0x02 时间同步(北京时间毫秒值:%1,北京时间:%2)")
|
||||
.arg(QString::number(TimestampMs), Lgc_Core_GetBeijingTimeText(TimestampMs)));
|
||||
}
|
||||
|
||||
bool Lgc_Core_SendThemeSwitch(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
const Lgc_Core_Struct_ThemeColor ThemeColor = Lgc_Core_GetNextThemeColor(p_State);
|
||||
QByteArray Payload(MID_CONST_PACKET_SIZE_VENDOR_COMMAND_DATA, 0);
|
||||
Payload[0] = static_cast<char>(ThemeColor.Red);
|
||||
Payload[1] = static_cast<char>(ThemeColor.Green);
|
||||
Payload[2] = static_cast<char>(ThemeColor.Blue);
|
||||
|
||||
return Lgc_Core_SendPacket(
|
||||
p_State,
|
||||
Lgc_Core_BuildCommandPacket(0x01, Payload),
|
||||
QStringLiteral("0x05 0x01 theme switch (RGB:%1)")
|
||||
.arg(Lgc_Core_FormatThemeColor(
|
||||
ThemeColor.Red,
|
||||
ThemeColor.Green,
|
||||
ThemeColor.Blue)));
|
||||
}
|
||||
|
||||
372
LOGIC/Lgc_Core_Input.cpp
Normal file
372
LOGIC/Lgc_Core_Input.cpp
Normal file
@@ -0,0 +1,372 @@
|
||||
#include "LOGIC/Lgc_Core_Private.h"
|
||||
|
||||
#include "MID/Mid_Ble.h"
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QTimeZone>
|
||||
#include <Windows.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
QString Lgc_Core_GetTimeText()
|
||||
{
|
||||
return QDateTime::currentDateTimeUtc()
|
||||
.toTimeZone(QTimeZone("Asia/Shanghai"))
|
||||
.toString(QStringLiteral("HH:mm:ss.zzz"));
|
||||
}
|
||||
|
||||
void Lgc_Core_AppendLog(Lgc_Core_Struct_State* p_State, const QString& Text)
|
||||
{
|
||||
if (Text.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (p_State->TextLog.isEmpty())
|
||||
{
|
||||
p_State->TextLog = Text;
|
||||
}
|
||||
else
|
||||
{
|
||||
p_State->TextLog.append(QStringLiteral("\n\n"));
|
||||
p_State->TextLog.append(Text);
|
||||
}
|
||||
|
||||
if (p_State->TextLog.size() > 24000)
|
||||
{
|
||||
p_State->TextLog = p_State->TextLog.right(20000);
|
||||
}
|
||||
}
|
||||
|
||||
void Lgc_Core_CollectKeyboardUsage(
|
||||
const QByteArray& UsageBitmap,
|
||||
QVector<quint16>* p_UsageList,
|
||||
QStringList* p_UsageTextList)
|
||||
{
|
||||
p_UsageList->clear();
|
||||
p_UsageTextList->clear();
|
||||
|
||||
for (quint16 Usage = 0; Usage <= MID_CONST_KEYBOARD_USAGE_MAX; ++Usage)
|
||||
{
|
||||
const int ByteIndex = Usage / 8;
|
||||
const quint8 BitMask = static_cast<quint8>(1U << (Usage % 8));
|
||||
const quint8 ByteValue = static_cast<quint8>(UsageBitmap.at(ByteIndex));
|
||||
if ((ByteValue & BitMask) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
p_UsageList->append(Usage);
|
||||
p_UsageTextList->append(Mid_GetKeyboardUsageText(Usage));
|
||||
}
|
||||
}
|
||||
|
||||
bool Lgc_Core_HandleBleHidPacket(Lgc_Core_Struct_State* p_State, const Mid_Struct_RawPacket& Packet)
|
||||
{
|
||||
switch (Packet.Source)
|
||||
{
|
||||
case Mid_Enum_RawPacketSource_BleHidKeyboard:
|
||||
case Mid_Enum_RawPacketSource_BleHidConsumer:
|
||||
case Mid_Enum_RawPacketSource_BleHidVendor:
|
||||
case Mid_Enum_RawPacketSource_BleHidVendorCommand:
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Packet.ByteArray.isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (static_cast<quint8>(Packet.ByteArray.at(0)))
|
||||
{
|
||||
case Mid_Enum_ReportId_Nkro:
|
||||
Lgc_Core_HandleNkroPacket(p_State, Packet);
|
||||
return true;
|
||||
|
||||
case Mid_Enum_ReportId_Consumer:
|
||||
Lgc_Core_HandleConsumerPacket(p_State, Packet);
|
||||
return true;
|
||||
|
||||
case Mid_Enum_ReportId_Vendor:
|
||||
case Mid_Enum_ReportId_VendorCommand:
|
||||
Lgc_Core_HandleVendorPacket(p_State, Packet);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Lgc_Core_UpdateSendTransportByPacket(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
Mid_Enum_RawPacketSource Source)
|
||||
{
|
||||
switch (Source)
|
||||
{
|
||||
case Mid_Enum_RawPacketSource_UsbNkroRaw:
|
||||
case Mid_Enum_RawPacketSource_UsbConsumerHid:
|
||||
case Mid_Enum_RawPacketSource_UsbVendorHid:
|
||||
p_State->ActiveSendTransport = Lgc_Core_Enum_SendTransport_Usb;
|
||||
break;
|
||||
|
||||
case Mid_Enum_RawPacketSource_BleGatt:
|
||||
case Mid_Enum_RawPacketSource_BleHidKeyboard:
|
||||
case Mid_Enum_RawPacketSource_BleHidConsumer:
|
||||
case Mid_Enum_RawPacketSource_BleHidVendor:
|
||||
case Mid_Enum_RawPacketSource_BleHidVendorCommand:
|
||||
p_State->ActiveSendTransport = Lgc_Core_Enum_SendTransport_Ble;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Lgc_Core_NormalizeSendTransport(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
const bool HasUsb = p_State->DriVendorPort.ReadPort.IsOpened;
|
||||
const bool HasBle = p_State->DriBlePort.IsConnected;
|
||||
|
||||
if ((p_State->ActiveSendTransport == Lgc_Core_Enum_SendTransport_Usb) && HasUsb)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ((p_State->ActiveSendTransport == Lgc_Core_Enum_SendTransport_Ble) && HasBle)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasBle && !HasUsb)
|
||||
{
|
||||
p_State->ActiveSendTransport = Lgc_Core_Enum_SendTransport_Ble;
|
||||
}
|
||||
else if (HasUsb && !HasBle)
|
||||
{
|
||||
p_State->ActiveSendTransport = Lgc_Core_Enum_SendTransport_Usb;
|
||||
}
|
||||
else if (!HasUsb && !HasBle)
|
||||
{
|
||||
p_State->ActiveSendTransport = Lgc_Core_Enum_SendTransport_None;
|
||||
}
|
||||
}
|
||||
|
||||
void Lgc_Core_AppendStatusLog(Lgc_Core_Struct_State* p_State, const QString& Text)
|
||||
{
|
||||
if (!Text.isEmpty())
|
||||
{
|
||||
Lgc_Core_AppendLog(
|
||||
p_State,
|
||||
QStringLiteral("[%1] 状态\n%2").arg(Lgc_Core_GetTimeText(), Text));
|
||||
}
|
||||
}
|
||||
|
||||
void Lgc_Core_AppendPacketLog(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
const QString& ActionText,
|
||||
const QString& PortName,
|
||||
const QByteArray& Packet,
|
||||
const QString& ExplainText)
|
||||
{
|
||||
QString Text = QStringLiteral("[%1] %2\n端口: %3\nHEX: %4")
|
||||
.arg(Lgc_Core_GetTimeText(), ActionText, PortName, Mid_GetHexText(Packet));
|
||||
if (!ExplainText.isEmpty())
|
||||
{
|
||||
Text.append(QLatin1Char('\n'));
|
||||
Text.append(ExplainText);
|
||||
}
|
||||
Lgc_Core_AppendLog(p_State, Text);
|
||||
}
|
||||
|
||||
void Lgc_Core_ClearAllKeyStates(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
p_State->IsVisibleKeyStateValid = false;
|
||||
p_State->VisibleUsageList.clear();
|
||||
|
||||
p_State->IsPhysicalKeyStateValid = false;
|
||||
p_State->PhysicalUsageList.clear();
|
||||
p_State->LastPhysicalUsageList.clear();
|
||||
}
|
||||
|
||||
void Lgc_Core_CloseAllPorts(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
Dri_Ble_Close(&p_State->DriBlePort);
|
||||
Dri_NkroRaw_Close(&p_State->DriNkroPort);
|
||||
Dri_Consumer_Close(&p_State->DriConsumerPort);
|
||||
Dri_Vendor_Close(&p_State->DriVendorPort);
|
||||
}
|
||||
|
||||
bool Lgc_Core_SyncSystemState(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
const bool OldNumLock = p_State->IsSystemNumLockOn;
|
||||
const bool OldConnected = p_State->IsConnected;
|
||||
const QString OldConnection = p_State->TextConnection;
|
||||
const Lgc_Core_Enum_SendTransport OldSendTransport = p_State->ActiveSendTransport;
|
||||
|
||||
p_State->IsSystemNumLockOn = (GetKeyState(VK_NUMLOCK) & 0x0001) != 0;
|
||||
p_State->IsConnected = p_State->DriVendorPort.ReadPort.IsOpened || p_State->DriBlePort.IsConnected;
|
||||
Lgc_Core_NormalizeSendTransport(p_State);
|
||||
|
||||
QStringList Lines;
|
||||
if (p_State->DriVendorPort.ReadPort.IsOpened)
|
||||
{
|
||||
Lines.append(QStringLiteral("已连接 USB Vendor 接口。"));
|
||||
}
|
||||
if (!p_State->DriBlePort.TextEndpointSummary.isEmpty())
|
||||
{
|
||||
Lines.append(p_State->DriBlePort.TextEndpointSummary);
|
||||
}
|
||||
if (Lines.isEmpty())
|
||||
{
|
||||
Lines.append(QStringLiteral("未连接到目标设备。"));
|
||||
}
|
||||
|
||||
p_State->TextConnection = Lines.join(QLatin1Char('\n'));
|
||||
|
||||
return (OldNumLock != p_State->IsSystemNumLockOn) ||
|
||||
(OldConnected != p_State->IsConnected) ||
|
||||
(OldConnection != p_State->TextConnection) ||
|
||||
(OldSendTransport != p_State->ActiveSendTransport);
|
||||
}
|
||||
|
||||
void Lgc_Core_HandleNkroPacket(Lgc_Core_Struct_State* p_State, const Mid_Struct_RawPacket& Packet)
|
||||
{
|
||||
Lgc_Core_UpdateSendTransportByPacket(p_State, Packet.Source);
|
||||
|
||||
QString ExplainText = QStringLiteral("NKRO");
|
||||
if (!Packet.ByteArray.isEmpty() &&
|
||||
(static_cast<quint8>(Packet.ByteArray.at(0)) == Mid_Enum_ReportId_Nkro) &&
|
||||
(Packet.ByteArray.size() == MID_CONST_PACKET_SIZE_NKRO))
|
||||
{
|
||||
const quint8 Modifier = static_cast<quint8>(Packet.ByteArray.at(1));
|
||||
const QByteArray UsageBitmap = Packet.ByteArray.mid(2, MID_CONST_USAGE_BITMAP_SIZE);
|
||||
QVector<quint16> UsageList;
|
||||
QStringList UsageTextList;
|
||||
Lgc_Core_CollectKeyboardUsage(UsageBitmap, &UsageList, &UsageTextList);
|
||||
|
||||
p_State->IsVisibleKeyStateValid = true;
|
||||
p_State->VisibleUsageList = UsageList;
|
||||
ExplainText = QStringLiteral("NKRO %1 / %2")
|
||||
.arg(Mid_GetModifierText(Modifier),
|
||||
UsageTextList.isEmpty() ? QStringLiteral("无按键") : UsageTextList.join(QStringLiteral(", ")));
|
||||
}
|
||||
Lgc_Core_AppendPacketLog(p_State, QStringLiteral("收到"), Packet.PortName, Packet.ByteArray, ExplainText);
|
||||
}
|
||||
|
||||
void Lgc_Core_HandleConsumerPacket(Lgc_Core_Struct_State* p_State, const Mid_Struct_RawPacket& Packet)
|
||||
{
|
||||
Lgc_Core_UpdateSendTransportByPacket(p_State, Packet.Source);
|
||||
|
||||
QString ExplainText = QStringLiteral("Consumer");
|
||||
if (!Packet.ByteArray.isEmpty() &&
|
||||
(static_cast<quint8>(Packet.ByteArray.at(0)) == Mid_Enum_ReportId_Consumer) &&
|
||||
(Packet.ByteArray.size() == MID_CONST_PACKET_SIZE_CONSUMER))
|
||||
{
|
||||
const quint16 Usage =
|
||||
static_cast<quint8>(Packet.ByteArray.at(1)) |
|
||||
(static_cast<quint16>(static_cast<quint8>(Packet.ByteArray.at(2))) << 8);
|
||||
ExplainText = QStringLiteral("Consumer %1").arg(Mid_GetConsumerUsageText(Usage));
|
||||
}
|
||||
Lgc_Core_AppendPacketLog(p_State, QStringLiteral("收到"), Packet.PortName, Packet.ByteArray, ExplainText);
|
||||
}
|
||||
|
||||
void Lgc_Core_HandleVendorPacket(Lgc_Core_Struct_State* p_State, const Mid_Struct_RawPacket& Packet)
|
||||
{
|
||||
Lgc_Core_UpdateSendTransportByPacket(p_State, Packet.Source);
|
||||
|
||||
QString ExplainText = QStringLiteral("Vendor");
|
||||
if (Packet.ByteArray.isEmpty())
|
||||
{
|
||||
Lgc_Core_AppendPacketLog(p_State, QStringLiteral("收到"), Packet.PortName, Packet.ByteArray, ExplainText);
|
||||
return;
|
||||
}
|
||||
|
||||
const quint8 ReportId = static_cast<quint8>(Packet.ByteArray.at(0));
|
||||
if ((ReportId == Mid_Enum_ReportId_Vendor) && (Packet.ByteArray.size() == MID_CONST_PACKET_SIZE_VENDOR))
|
||||
{
|
||||
const quint8 Modifier = static_cast<quint8>(Packet.ByteArray.at(1));
|
||||
const QByteArray UsageBitmap = Packet.ByteArray.mid(2, MID_CONST_USAGE_BITMAP_SIZE);
|
||||
QVector<quint16> UsageList;
|
||||
QStringList UsageTextList;
|
||||
Lgc_Core_CollectKeyboardUsage(UsageBitmap, &UsageList, &UsageTextList);
|
||||
p_State->IsPhysicalKeyStateValid = true;
|
||||
p_State->PhysicalUsageList = UsageList;
|
||||
ExplainText = QStringLiteral("Vendor %1 / %2")
|
||||
.arg(Mid_GetModifierText(Modifier),
|
||||
UsageTextList.isEmpty() ? QStringLiteral("无按键") : UsageTextList.join(QStringLiteral(", ")));
|
||||
}
|
||||
else if ((ReportId == Mid_Enum_ReportId_VendorCommand) && (Packet.ByteArray.size() >= 2))
|
||||
{
|
||||
ExplainText = QStringLiteral("VendorCmd 0x%1")
|
||||
.arg(static_cast<quint8>(Packet.ByteArray.at(1)), 2, 16, QLatin1Char('0'))
|
||||
.toUpper();
|
||||
}
|
||||
Lgc_Core_AppendPacketLog(p_State, QStringLiteral("收到"), Packet.PortName, Packet.ByteArray, ExplainText);
|
||||
}
|
||||
|
||||
void Lgc_Core_HandleBlePacket(Lgc_Core_Struct_State* p_State, const Mid_Struct_RawPacket& Packet)
|
||||
{
|
||||
Lgc_Core_UpdateSendTransportByPacket(p_State, Packet.Source);
|
||||
|
||||
if (Lgc_Core_HandleBleHidPacket(p_State, Packet))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Packet.Source != Mid_Enum_RawPacketSource_BleGatt)
|
||||
{
|
||||
Lgc_Core_AppendPacketLog(p_State, QStringLiteral("收到"), Packet.PortName, Packet.ByteArray, QStringLiteral("BLE"));
|
||||
return;
|
||||
}
|
||||
|
||||
const QString ExplainText = Packet.ByteArray.isEmpty()
|
||||
? QStringLiteral("BLE")
|
||||
: QStringLiteral("BLE %1").arg(Mid_GetBleOpcodeText(static_cast<quint8>(Packet.ByteArray.at(0))));
|
||||
Lgc_Core_AppendPacketLog(p_State, QStringLiteral("收到"), Packet.PortName, Packet.ByteArray, ExplainText);
|
||||
}
|
||||
|
||||
bool Lgc_Core_HandleFunctionButtons(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
if (!p_State->IsPhysicalKeyStateValid)
|
||||
{
|
||||
p_State->LastPhysicalUsageList.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_State->IsFunctionSequenceRecording)
|
||||
{
|
||||
p_State->LastPhysicalUsageList = p_State->PhysicalUsageList;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsChanged = false;
|
||||
for (quint16 Usage : p_State->PhysicalUsageList)
|
||||
{
|
||||
if (p_State->LastPhysicalUsageList.contains(Usage) ||
|
||||
!Lgc_FunctionButton_HasUsageFeature(p_State->FunctionButtonConfig, Usage))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QString TextStatus;
|
||||
if (!Lgc_FunctionButton_RunBinding(p_State, Usage, &TextStatus) || TextStatus.isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
p_State->TextFunctionStatus = TextStatus;
|
||||
Lgc_Core_AppendStatusLog(p_State, TextStatus);
|
||||
IsChanged = true;
|
||||
}
|
||||
|
||||
p_State->LastPhysicalUsageList = p_State->PhysicalUsageList;
|
||||
return IsChanged;
|
||||
}
|
||||
|
||||
|
||||
30
LOGIC/Lgc_Core_Private.h
Normal file
30
LOGIC/Lgc_Core_Private.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "LOGIC/Lgc_Core.h"
|
||||
|
||||
void Lgc_Core_AppendStatusLog(Lgc_Core_Struct_State* p_State, const QString& Text);
|
||||
void Lgc_Core_AppendPacketLog(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
const QString& ActionText,
|
||||
const QString& PortName,
|
||||
const QByteArray& Packet,
|
||||
const QString& ExplainText);
|
||||
|
||||
void Lgc_Core_ClearAllKeyStates(Lgc_Core_Struct_State* p_State);
|
||||
void Lgc_Core_CloseAllPorts(Lgc_Core_Struct_State* p_State);
|
||||
void Lgc_Core_FillMaskAllEnabled(QByteArray* p_UsageBitmap);
|
||||
void Lgc_Core_UpdateSendTransportByPacket(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
Mid_Enum_RawPacketSource Source);
|
||||
void Lgc_Core_NormalizeSendTransport(Lgc_Core_Struct_State* p_State);
|
||||
|
||||
bool Lgc_Core_SendCurrentMask(Lgc_Core_Struct_State* p_State);
|
||||
bool Lgc_Core_SyncSystemState(Lgc_Core_Struct_State* p_State);
|
||||
|
||||
void Lgc_Core_HandleNkroPacket(Lgc_Core_Struct_State* p_State, const Mid_Struct_RawPacket& Packet);
|
||||
void Lgc_Core_HandleConsumerPacket(Lgc_Core_Struct_State* p_State, const Mid_Struct_RawPacket& Packet);
|
||||
void Lgc_Core_HandleVendorPacket(Lgc_Core_Struct_State* p_State, const Mid_Struct_RawPacket& Packet);
|
||||
void Lgc_Core_HandleBlePacket(Lgc_Core_Struct_State* p_State, const Mid_Struct_RawPacket& Packet);
|
||||
|
||||
bool Lgc_Core_HandleFunctionButtons(Lgc_Core_Struct_State* p_State);
|
||||
|
||||
239
LOGIC/Lgc_Func_Button.cpp
Normal file
239
LOGIC/Lgc_Func_Button.cpp
Normal file
@@ -0,0 +1,239 @@
|
||||
#include "LOGIC/Lgc_Func_Button.h"
|
||||
|
||||
#include "LOGIC/Lgc_Core.h"
|
||||
#include "LOGIC/Lgc_Func_Button_Private.h"
|
||||
#include "MID/Mid_Def.h"
|
||||
#include <algorithm>
|
||||
|
||||
QString Lgc_FunctionButton_GetUsageShortText(quint16 Usage)
|
||||
{
|
||||
switch (Usage)
|
||||
{
|
||||
case 0x0053: return QStringLiteral("Num");
|
||||
case 0x0054: return QStringLiteral("/");
|
||||
case 0x0055: return QStringLiteral("*");
|
||||
case 0x0056: return QStringLiteral("-");
|
||||
case 0x0057: return QStringLiteral("+");
|
||||
case 0x0058: return QStringLiteral("Enter");
|
||||
case 0x0059: return QStringLiteral("1");
|
||||
case 0x005A: return QStringLiteral("2");
|
||||
case 0x005B: return QStringLiteral("3");
|
||||
case 0x005C: return QStringLiteral("4");
|
||||
case 0x005D: return QStringLiteral("5");
|
||||
case 0x005E: return QStringLiteral("6");
|
||||
case 0x005F: return QStringLiteral("7");
|
||||
case 0x0060: return QStringLiteral("8");
|
||||
case 0x0061: return QStringLiteral("9");
|
||||
case 0x0062: return QStringLiteral("0");
|
||||
case 0x0063: return QStringLiteral(".");
|
||||
default:
|
||||
return Mid_GetKeyboardUsageText(Usage);
|
||||
}
|
||||
}
|
||||
|
||||
QString Lgc_FunctionButton_GetFeatureTypeText(Lgc_FunctionFeature_Type Type)
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case Lgc_FunctionFeature_Type::KeyCombination:
|
||||
return QStringLiteral("快捷键");
|
||||
|
||||
case Lgc_FunctionFeature_Type::KeySequence:
|
||||
return QStringLiteral("快捷键序列");
|
||||
|
||||
case Lgc_FunctionFeature_Type::Website:
|
||||
return QStringLiteral("打开网址");
|
||||
|
||||
default:
|
||||
return QStringLiteral("未知类型");
|
||||
}
|
||||
}
|
||||
|
||||
QVector<quint16> Lgc_FunctionButton_GetConfigurableUsages()
|
||||
{
|
||||
return {
|
||||
0x0053, 0x0054, 0x0055, 0x0056,
|
||||
0x005F, 0x0060, 0x0061, 0x0057,
|
||||
0x005C, 0x005D, 0x005E,
|
||||
0x0059, 0x005A, 0x005B, 0x0058,
|
||||
0x0062, 0x0063
|
||||
};
|
||||
}
|
||||
|
||||
QVector<int> Lgc_FunctionButton_GetFeatureIdList(const Lgc_FunctionButton_Config& Config)
|
||||
{
|
||||
QVector<int> FeatureIdList = Config.FeatureMap.keys().toVector();
|
||||
std::sort(FeatureIdList.begin(), FeatureIdList.end());
|
||||
return FeatureIdList;
|
||||
}
|
||||
|
||||
Lgc_FunctionFeature_Definition Lgc_FunctionButton_GetFeature(
|
||||
const Lgc_FunctionButton_Config& Config,
|
||||
int FeatureId)
|
||||
{
|
||||
return Config.FeatureMap.value(FeatureId);
|
||||
}
|
||||
|
||||
QString Lgc_FunctionButton_GetFeatureName(const Lgc_FunctionFeature_Definition& Feature)
|
||||
{
|
||||
if (!Feature.Name.trimmed().isEmpty())
|
||||
{
|
||||
return Feature.Name.trimmed();
|
||||
}
|
||||
return Feature.Id > 0 ? QStringLiteral("功能%1").arg(Feature.Id) : QStringLiteral("未命名功能");
|
||||
}
|
||||
|
||||
int Lgc_FunctionButton_AddFeature(Lgc_FunctionButton_Config* p_Config)
|
||||
{
|
||||
if (p_Config == nullptr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int FeatureId = 1;
|
||||
while (p_Config->FeatureMap.contains(FeatureId))
|
||||
{
|
||||
++FeatureId;
|
||||
}
|
||||
p_Config->NextFeatureId = FeatureId + 1;
|
||||
|
||||
Lgc_FunctionFeature_Definition Feature;
|
||||
Feature.Id = FeatureId;
|
||||
Feature.Name = QStringLiteral("功能%1").arg(FeatureId);
|
||||
Feature.Type = Lgc_FunctionFeature_Type::KeyCombination;
|
||||
p_Config->FeatureMap.insert(FeatureId, Feature);
|
||||
return FeatureId;
|
||||
}
|
||||
|
||||
void Lgc_FunctionButton_RemoveFeature(Lgc_FunctionButton_Config* p_Config, int FeatureId)
|
||||
{
|
||||
if ((p_Config == nullptr) || (FeatureId <= 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
p_Config->FeatureMap.remove(FeatureId);
|
||||
if (p_Config->FeatureMap.isEmpty())
|
||||
{
|
||||
p_Config->NextFeatureId = 1;
|
||||
}
|
||||
else if (FeatureId < p_Config->NextFeatureId)
|
||||
{
|
||||
p_Config->NextFeatureId = FeatureId;
|
||||
}
|
||||
|
||||
auto It = p_Config->UsageFeatureIdMap.begin();
|
||||
while (It != p_Config->UsageFeatureIdMap.end())
|
||||
{
|
||||
if (It.value() == FeatureId)
|
||||
{
|
||||
It = p_Config->UsageFeatureIdMap.erase(It);
|
||||
}
|
||||
else
|
||||
{
|
||||
++It;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Lgc_FunctionButton_SetFeature(
|
||||
Lgc_FunctionButton_Config* p_Config,
|
||||
const Lgc_FunctionFeature_Definition& Feature)
|
||||
{
|
||||
if ((p_Config == nullptr) || (Feature.Id <= 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
p_Config->FeatureMap.insert(Feature.Id, Feature);
|
||||
if (p_Config->NextFeatureId <= Feature.Id)
|
||||
{
|
||||
p_Config->NextFeatureId = Feature.Id + 1;
|
||||
}
|
||||
}
|
||||
|
||||
int Lgc_FunctionButton_GetUsageFeatureId(
|
||||
const Lgc_FunctionButton_Config& Config,
|
||||
quint16 Usage)
|
||||
{
|
||||
const int FeatureId = Config.UsageFeatureIdMap.value(Usage, 0);
|
||||
return Config.FeatureMap.contains(FeatureId) ? FeatureId : 0;
|
||||
}
|
||||
|
||||
void Lgc_FunctionButton_SetUsageFeatureId(
|
||||
Lgc_FunctionButton_Config* p_Config,
|
||||
quint16 Usage,
|
||||
int FeatureId)
|
||||
{
|
||||
if (p_Config == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ((FeatureId <= 0) || !p_Config->FeatureMap.contains(FeatureId))
|
||||
{
|
||||
p_Config->UsageFeatureIdMap.remove(Usage);
|
||||
return;
|
||||
}
|
||||
|
||||
p_Config->UsageFeatureIdMap.insert(Usage, FeatureId);
|
||||
}
|
||||
|
||||
bool Lgc_FunctionButton_HasUsageFeature(
|
||||
const Lgc_FunctionButton_Config& Config,
|
||||
quint16 Usage)
|
||||
{
|
||||
return Lgc_FunctionButton_GetUsageFeatureId(Config, Usage) > 0;
|
||||
}
|
||||
|
||||
bool Lgc_FunctionButton_SendUsageToWindows(quint16 Usage, bool IsPressed)
|
||||
{
|
||||
const Lgc_FunctionButton_Struct_WindowsKey Key = Lgc_FunctionButton_GetWindowsKey(Usage);
|
||||
return Lgc_FunctionButton_SendWindowsKey(Key, IsPressed);
|
||||
}
|
||||
|
||||
bool Lgc_FunctionButton_RunBinding(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
quint16 Usage,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
if ((p_State == nullptr) || (p_TextStatus == nullptr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
*p_TextStatus = QString();
|
||||
const int FeatureId =
|
||||
Lgc_FunctionButton_GetUsageFeatureId(p_State->FunctionButtonConfig, Usage);
|
||||
if (FeatureId <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const Lgc_FunctionFeature_Definition Feature =
|
||||
Lgc_FunctionButton_GetFeature(p_State->FunctionButtonConfig, FeatureId);
|
||||
if (Feature.Id <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (Feature.Type)
|
||||
{
|
||||
case Lgc_FunctionFeature_Type::KeyCombination:
|
||||
Lgc_FunctionButton_RunKeyCombination(Feature, Usage, p_TextStatus);
|
||||
return true;
|
||||
|
||||
case Lgc_FunctionFeature_Type::KeySequence:
|
||||
Lgc_FunctionButton_RunKeySequence(Feature, Usage, p_TextStatus);
|
||||
return true;
|
||||
|
||||
case Lgc_FunctionFeature_Type::Website:
|
||||
Lgc_FunctionButton_RunOpenWebsite(Feature, p_TextStatus);
|
||||
return true;
|
||||
|
||||
default:
|
||||
*p_TextStatus = QStringLiteral("%1 绑定了未知功能。")
|
||||
.arg(Lgc_FunctionButton_GetFeatureName(Feature));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
62
LOGIC/Lgc_Func_Button.h
Normal file
62
LOGIC/Lgc_Func_Button.h
Normal file
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include "MID/Mid_Def.h"
|
||||
#include <QtCore/QHash>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QVector>
|
||||
|
||||
struct Lgc_Core_Struct_State;
|
||||
|
||||
enum class Lgc_FunctionFeature_Type : quint8
|
||||
{
|
||||
KeyCombination = 0,
|
||||
KeySequence = 1,
|
||||
Website = 2
|
||||
};
|
||||
|
||||
struct Lgc_FunctionFeature_Definition
|
||||
{
|
||||
int Id = 0;
|
||||
QString Name;
|
||||
QString Description;
|
||||
Lgc_FunctionFeature_Type Type = Lgc_FunctionFeature_Type::KeyCombination;
|
||||
QString SequenceText;
|
||||
QString WebsiteUrl;
|
||||
};
|
||||
|
||||
struct Lgc_FunctionButton_Config
|
||||
{
|
||||
QHash<int, Lgc_FunctionFeature_Definition> FeatureMap;
|
||||
QHash<quint16, int> UsageFeatureIdMap;
|
||||
int NextFeatureId = 1;
|
||||
};
|
||||
|
||||
QString Lgc_FunctionButton_GetUsageShortText(quint16 Usage);
|
||||
QString Lgc_FunctionButton_GetFeatureTypeText(Lgc_FunctionFeature_Type Type);
|
||||
QVector<quint16> Lgc_FunctionButton_GetConfigurableUsages();
|
||||
QVector<int> Lgc_FunctionButton_GetFeatureIdList(const Lgc_FunctionButton_Config& Config);
|
||||
Lgc_FunctionFeature_Definition Lgc_FunctionButton_GetFeature(
|
||||
const Lgc_FunctionButton_Config& Config,
|
||||
int FeatureId);
|
||||
QString Lgc_FunctionButton_GetFeatureName(const Lgc_FunctionFeature_Definition& Feature);
|
||||
int Lgc_FunctionButton_AddFeature(Lgc_FunctionButton_Config* p_Config);
|
||||
void Lgc_FunctionButton_RemoveFeature(Lgc_FunctionButton_Config* p_Config, int FeatureId);
|
||||
void Lgc_FunctionButton_SetFeature(
|
||||
Lgc_FunctionButton_Config* p_Config,
|
||||
const Lgc_FunctionFeature_Definition& Feature);
|
||||
int Lgc_FunctionButton_GetUsageFeatureId(
|
||||
const Lgc_FunctionButton_Config& Config,
|
||||
quint16 Usage);
|
||||
void Lgc_FunctionButton_SetUsageFeatureId(
|
||||
Lgc_FunctionButton_Config* p_Config,
|
||||
quint16 Usage,
|
||||
int FeatureId);
|
||||
bool Lgc_FunctionButton_HasUsageFeature(
|
||||
const Lgc_FunctionButton_Config& Config,
|
||||
quint16 Usage);
|
||||
|
||||
bool Lgc_FunctionButton_SendUsageToWindows(quint16 Usage, bool IsPressed);
|
||||
bool Lgc_FunctionButton_RunBinding(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
quint16 Usage,
|
||||
QString* p_TextStatus);
|
||||
573
LOGIC/Lgc_Func_Button_Parse.cpp
Normal file
573
LOGIC/Lgc_Func_Button_Parse.cpp
Normal file
@@ -0,0 +1,573 @@
|
||||
#include "LOGIC/Lgc_Func_Button_Private.h"
|
||||
|
||||
#include <QtCore/QRegularExpression>
|
||||
#include <QtCore/QStringList>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bool Lgc_FunctionButton_IsSequenceSeparator(QChar Character)
|
||||
{
|
||||
return Character.isSpace() ||
|
||||
(Character == QLatin1Char(',')) ||
|
||||
(Character == QLatin1Char(';')) ||
|
||||
(Character == QLatin1Char('|')) ||
|
||||
(Character == QChar(0xFF0C)) ||
|
||||
(Character == QChar(0xFF1B)) ||
|
||||
(Character == QChar(0x3001));
|
||||
}
|
||||
|
||||
quint16 Lgc_FunctionButton_GetUsageFromDigit(QChar Character)
|
||||
{
|
||||
switch (Character.unicode())
|
||||
{
|
||||
case '0': return 0x0062;
|
||||
case '1': return 0x0059;
|
||||
case '2': return 0x005A;
|
||||
case '3': return 0x005B;
|
||||
case '4': return 0x005C;
|
||||
case '5': return 0x005D;
|
||||
case '6': return 0x005E;
|
||||
case '7': return 0x005F;
|
||||
case '8': return 0x0060;
|
||||
case '9': return 0x0061;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
quint16 Lgc_FunctionButton_GetUsageFromSymbol(QChar Character)
|
||||
{
|
||||
switch (Character.unicode())
|
||||
{
|
||||
case '+': return 0x0057;
|
||||
case '/': return 0x0054;
|
||||
case '*': return 0x0055;
|
||||
case '-': return 0x0056;
|
||||
case '.': return 0x0063;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Lgc_FunctionButton_ParseLegacySequenceText(
|
||||
const QString& Text,
|
||||
quint16 SourceUsage,
|
||||
QVector<quint16>* p_UsageList,
|
||||
QString* p_ErrorText)
|
||||
{
|
||||
p_UsageList->clear();
|
||||
if (p_ErrorText != nullptr) p_ErrorText->clear();
|
||||
|
||||
const QString UpperText = Text.toUpper();
|
||||
int Index = 0;
|
||||
const QVector<QPair<QString, quint16>> TokenList = {
|
||||
{ QStringLiteral("NUMLOCK"), 0x0053 },
|
||||
{ QStringLiteral("ENTER"), 0x0058 },
|
||||
{ QStringLiteral("DIVIDE"), 0x0054 },
|
||||
{ QStringLiteral("SLASH"), 0x0054 },
|
||||
{ QStringLiteral("MULTIPLY"), 0x0055 },
|
||||
{ QStringLiteral("ASTERISK"), 0x0055 },
|
||||
{ QStringLiteral("DECIMAL"), 0x0063 },
|
||||
{ QStringLiteral("MINUS"), 0x0056 },
|
||||
{ QStringLiteral("SOURCE"), SourceUsage },
|
||||
{ QStringLiteral("PLUS"), 0x0057 },
|
||||
{ QStringLiteral("STAR"), 0x0055 },
|
||||
{ QStringLiteral("SELF"), SourceUsage },
|
||||
{ QStringLiteral("DOT"), 0x0063 },
|
||||
{ QStringLiteral("NUM"), 0x0053 }
|
||||
};
|
||||
|
||||
while (Index < Text.size())
|
||||
{
|
||||
const QChar Character = Text.at(Index);
|
||||
if (Lgc_FunctionButton_IsSequenceSeparator(Character))
|
||||
{
|
||||
++Index;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((Index + 1) < Text.size())
|
||||
{
|
||||
const QString Token = Text.mid(Index, 2);
|
||||
if ((Token == QStringLiteral("本键")) || (Token == QStringLiteral("自身")))
|
||||
{
|
||||
p_UsageList->append(SourceUsage);
|
||||
Index += 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const quint16 DigitUsage = Lgc_FunctionButton_GetUsageFromDigit(Character);
|
||||
if (DigitUsage != 0)
|
||||
{
|
||||
p_UsageList->append(DigitUsage);
|
||||
++Index;
|
||||
continue;
|
||||
}
|
||||
|
||||
const quint16 SymbolUsage = Lgc_FunctionButton_GetUsageFromSymbol(Character);
|
||||
if (SymbolUsage != 0)
|
||||
{
|
||||
p_UsageList->append(SymbolUsage);
|
||||
++Index;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool IsMatched = false;
|
||||
for (const auto& Token : TokenList)
|
||||
{
|
||||
if (!UpperText.mid(Index, Token.first.size()).compare(Token.first, Qt::CaseInsensitive))
|
||||
{
|
||||
p_UsageList->append(Token.second);
|
||||
Index += Token.first.size();
|
||||
IsMatched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (IsMatched)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (p_ErrorText != nullptr)
|
||||
{
|
||||
*p_ErrorText = QStringLiteral("无法识别的按键序列片段:%1").arg(Text.mid(Index, 8));
|
||||
}
|
||||
p_UsageList->clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Lgc_FunctionButton_TryParseSequenceToken(
|
||||
const QString& Token,
|
||||
quint16 SourceUsage,
|
||||
Lgc_FunctionButton_Struct_SequenceKey* p_KeyItem)
|
||||
{
|
||||
const QString TrimmedToken = Token.trimmed();
|
||||
const QString UpperToken = TrimmedToken.toUpper();
|
||||
if (UpperToken.isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto SetKey = [p_KeyItem](WORD VirtualKey, DWORD ExtraFlags, bool IsModifier, const QString& Text)
|
||||
{
|
||||
p_KeyItem->Key = { VirtualKey, ExtraFlags, IsModifier };
|
||||
p_KeyItem->Text = Text;
|
||||
};
|
||||
|
||||
if ((UpperToken == QStringLiteral("SOURCE")) ||
|
||||
(UpperToken == QStringLiteral("SELF")) ||
|
||||
(TrimmedToken == QStringLiteral("本键")) ||
|
||||
(TrimmedToken == QStringLiteral("自身")))
|
||||
{
|
||||
const auto SourceKey = Lgc_FunctionButton_GetWindowsKey(SourceUsage);
|
||||
if (SourceKey.VirtualKey == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
p_KeyItem->Key = SourceKey;
|
||||
p_KeyItem->Text = Lgc_FunctionButton_GetUsageShortText(SourceUsage);
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((UpperToken.size() == 1) && UpperToken.at(0).isLetterOrNumber())
|
||||
{
|
||||
SetKey(static_cast<WORD>(UpperToken.at(0).unicode()), 0, false, UpperToken);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (UpperToken == QStringLiteral("CTRL") || UpperToken == QStringLiteral("CONTROL"))
|
||||
{
|
||||
SetKey(VK_CONTROL, 0, true, QStringLiteral("Ctrl"));
|
||||
return true;
|
||||
}
|
||||
if (UpperToken == QStringLiteral("SHIFT"))
|
||||
{
|
||||
SetKey(VK_SHIFT, 0, true, QStringLiteral("Shift"));
|
||||
return true;
|
||||
}
|
||||
if (UpperToken == QStringLiteral("ALT"))
|
||||
{
|
||||
SetKey(VK_MENU, 0, true, QStringLiteral("Alt"));
|
||||
return true;
|
||||
}
|
||||
if (UpperToken == QStringLiteral("WIN") || UpperToken == QStringLiteral("META"))
|
||||
{
|
||||
SetKey(VK_LWIN, 0, true, QStringLiteral("Win"));
|
||||
return true;
|
||||
}
|
||||
if (UpperToken == QStringLiteral("ENTER"))
|
||||
{
|
||||
SetKey(VK_RETURN, 0, false, QStringLiteral("Enter"));
|
||||
return true;
|
||||
}
|
||||
if (UpperToken == QStringLiteral("NUMENTER"))
|
||||
{
|
||||
SetKey(VK_RETURN, KEYEVENTF_EXTENDEDKEY, false, QStringLiteral("NumEnter"));
|
||||
return true;
|
||||
}
|
||||
if (UpperToken == QStringLiteral("SPACE"))
|
||||
{
|
||||
SetKey(VK_SPACE, 0, false, QStringLiteral("Space"));
|
||||
return true;
|
||||
}
|
||||
if (UpperToken == QStringLiteral("TAB"))
|
||||
{
|
||||
SetKey(VK_TAB, 0, false, QStringLiteral("Tab"));
|
||||
return true;
|
||||
}
|
||||
if (UpperToken == QStringLiteral("ESC") || UpperToken == QStringLiteral("ESCAPE"))
|
||||
{
|
||||
SetKey(VK_ESCAPE, 0, false, QStringLiteral("Esc"));
|
||||
return true;
|
||||
}
|
||||
if (UpperToken == QStringLiteral("BACKSPACE"))
|
||||
{
|
||||
SetKey(VK_BACK, 0, false, QStringLiteral("Backspace"));
|
||||
return true;
|
||||
}
|
||||
if (UpperToken == QStringLiteral("DELETE"))
|
||||
{
|
||||
SetKey(VK_DELETE, KEYEVENTF_EXTENDEDKEY, false, QStringLiteral("Delete"));
|
||||
return true;
|
||||
}
|
||||
if (UpperToken == QStringLiteral("INSERT"))
|
||||
{
|
||||
SetKey(VK_INSERT, KEYEVENTF_EXTENDEDKEY, false, QStringLiteral("Insert"));
|
||||
return true;
|
||||
}
|
||||
if (UpperToken == QStringLiteral("HOME"))
|
||||
{
|
||||
SetKey(VK_HOME, KEYEVENTF_EXTENDEDKEY, false, QStringLiteral("Home"));
|
||||
return true;
|
||||
}
|
||||
if (UpperToken == QStringLiteral("END"))
|
||||
{
|
||||
SetKey(VK_END, KEYEVENTF_EXTENDEDKEY, false, QStringLiteral("End"));
|
||||
return true;
|
||||
}
|
||||
if (UpperToken == QStringLiteral("PAGEUP"))
|
||||
{
|
||||
SetKey(VK_PRIOR, KEYEVENTF_EXTENDEDKEY, false, QStringLiteral("PageUp"));
|
||||
return true;
|
||||
}
|
||||
if (UpperToken == QStringLiteral("PAGEDOWN"))
|
||||
{
|
||||
SetKey(VK_NEXT, KEYEVENTF_EXTENDEDKEY, false, QStringLiteral("PageDown"));
|
||||
return true;
|
||||
}
|
||||
if (UpperToken == QStringLiteral("LEFT"))
|
||||
{
|
||||
SetKey(VK_LEFT, KEYEVENTF_EXTENDEDKEY, false, QStringLiteral("Left"));
|
||||
return true;
|
||||
}
|
||||
if (UpperToken == QStringLiteral("RIGHT"))
|
||||
{
|
||||
SetKey(VK_RIGHT, KEYEVENTF_EXTENDEDKEY, false, QStringLiteral("Right"));
|
||||
return true;
|
||||
}
|
||||
if (UpperToken == QStringLiteral("UP"))
|
||||
{
|
||||
SetKey(VK_UP, KEYEVENTF_EXTENDEDKEY, false, QStringLiteral("Up"));
|
||||
return true;
|
||||
}
|
||||
if (UpperToken == QStringLiteral("DOWN"))
|
||||
{
|
||||
SetKey(VK_DOWN, KEYEVENTF_EXTENDEDKEY, false, QStringLiteral("Down"));
|
||||
return true;
|
||||
}
|
||||
if (UpperToken == QStringLiteral("CAPSLOCK"))
|
||||
{
|
||||
SetKey(VK_CAPITAL, 0, false, QStringLiteral("CapsLock"));
|
||||
return true;
|
||||
}
|
||||
if (UpperToken == QStringLiteral("PRINTSCREEN"))
|
||||
{
|
||||
SetKey(VK_SNAPSHOT, KEYEVENTF_EXTENDEDKEY, false, QStringLiteral("PrintScreen"));
|
||||
return true;
|
||||
}
|
||||
if (UpperToken == QStringLiteral("SCROLLLOCK"))
|
||||
{
|
||||
SetKey(VK_SCROLL, 0, false, QStringLiteral("ScrollLock"));
|
||||
return true;
|
||||
}
|
||||
if (UpperToken == QStringLiteral("PAUSE"))
|
||||
{
|
||||
SetKey(VK_PAUSE, 0, false, QStringLiteral("Pause"));
|
||||
return true;
|
||||
}
|
||||
if (UpperToken == QStringLiteral("NUM0")) { SetKey(VK_NUMPAD0, 0, false, QStringLiteral("Num0")); return true; }
|
||||
if (UpperToken == QStringLiteral("NUM1")) { SetKey(VK_NUMPAD1, 0, false, QStringLiteral("Num1")); return true; }
|
||||
if (UpperToken == QStringLiteral("NUM2")) { SetKey(VK_NUMPAD2, 0, false, QStringLiteral("Num2")); return true; }
|
||||
if (UpperToken == QStringLiteral("NUM3")) { SetKey(VK_NUMPAD3, 0, false, QStringLiteral("Num3")); return true; }
|
||||
if (UpperToken == QStringLiteral("NUM4")) { SetKey(VK_NUMPAD4, 0, false, QStringLiteral("Num4")); return true; }
|
||||
if (UpperToken == QStringLiteral("NUM5")) { SetKey(VK_NUMPAD5, 0, false, QStringLiteral("Num5")); return true; }
|
||||
if (UpperToken == QStringLiteral("NUM6")) { SetKey(VK_NUMPAD6, 0, false, QStringLiteral("Num6")); return true; }
|
||||
if (UpperToken == QStringLiteral("NUM7")) { SetKey(VK_NUMPAD7, 0, false, QStringLiteral("Num7")); return true; }
|
||||
if (UpperToken == QStringLiteral("NUM8")) { SetKey(VK_NUMPAD8, 0, false, QStringLiteral("Num8")); return true; }
|
||||
if (UpperToken == QStringLiteral("NUM9")) { SetKey(VK_NUMPAD9, 0, false, QStringLiteral("Num9")); return true; }
|
||||
if (UpperToken == QStringLiteral("NUM/")) { SetKey(VK_DIVIDE, KEYEVENTF_EXTENDEDKEY, false, QStringLiteral("Num/")); return true; }
|
||||
if (UpperToken == QStringLiteral("NUM*")) { SetKey(VK_MULTIPLY, 0, false, QStringLiteral("Num*")); return true; }
|
||||
if (UpperToken == QStringLiteral("NUM-")) { SetKey(VK_SUBTRACT, 0, false, QStringLiteral("Num-")); return true; }
|
||||
if (UpperToken == QStringLiteral("NUM+")) { SetKey(VK_ADD, 0, false, QStringLiteral("Num+")); return true; }
|
||||
if (UpperToken == QStringLiteral("NUM.")) { SetKey(VK_DECIMAL, 0, false, QStringLiteral("Num.")); return true; }
|
||||
if (UpperToken == QStringLiteral("COMMA")) { SetKey(VK_OEM_COMMA, 0, false, QStringLiteral("Comma")); return true; }
|
||||
if (UpperToken == QStringLiteral("PERIOD")) { SetKey(VK_OEM_PERIOD, 0, false, QStringLiteral("Period")); return true; }
|
||||
if (UpperToken == QStringLiteral("SEMICOLON")) { SetKey(VK_OEM_1, 0, false, QStringLiteral("Semicolon")); return true; }
|
||||
if (UpperToken == QStringLiteral("SLASH")) { SetKey(VK_OEM_2, 0, false, QStringLiteral("Slash")); return true; }
|
||||
if (UpperToken == QStringLiteral("GRAVE")) { SetKey(VK_OEM_3, 0, false, QStringLiteral("Grave")); return true; }
|
||||
if (UpperToken == QStringLiteral("LEFTBRACKET")) { SetKey(VK_OEM_4, 0, false, QStringLiteral("LeftBracket")); return true; }
|
||||
if (UpperToken == QStringLiteral("BACKSLASH")) { SetKey(VK_OEM_5, 0, false, QStringLiteral("Backslash")); return true; }
|
||||
if (UpperToken == QStringLiteral("RIGHTBRACKET")) { SetKey(VK_OEM_6, 0, false, QStringLiteral("RightBracket")); return true; }
|
||||
if (UpperToken == QStringLiteral("QUOTE")) { SetKey(VK_OEM_7, 0, false, QStringLiteral("Quote")); return true; }
|
||||
if (UpperToken == QStringLiteral("MINUS")) { SetKey(VK_OEM_MINUS, 0, false, QStringLiteral("Minus")); return true; }
|
||||
if (UpperToken == QStringLiteral("EQUAL")) { SetKey(VK_OEM_PLUS, 0, false, QStringLiteral("Equal")); return true; }
|
||||
|
||||
const QRegularExpression FunctionKeyPattern(QStringLiteral("^F(\\d{1,2})$"));
|
||||
const QRegularExpressionMatch Match = FunctionKeyPattern.match(UpperToken);
|
||||
if (Match.hasMatch())
|
||||
{
|
||||
const int FunctionIndex = Match.captured(1).toInt();
|
||||
if ((FunctionIndex >= 1) && (FunctionIndex <= 24))
|
||||
{
|
||||
SetKey(
|
||||
static_cast<WORD>(VK_F1 + FunctionIndex - 1),
|
||||
0,
|
||||
false,
|
||||
QStringLiteral("F%1").arg(FunctionIndex));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Lgc_FunctionButton_ParseRecordedSequenceText(
|
||||
const QString& Text,
|
||||
quint16 SourceUsage,
|
||||
QVector<Lgc_FunctionButton_Struct_SequenceKey>* p_KeyList,
|
||||
QString* p_ErrorText)
|
||||
{
|
||||
p_KeyList->clear();
|
||||
if (p_ErrorText != nullptr) p_ErrorText->clear();
|
||||
|
||||
const QStringList TokenList = Text.split(
|
||||
QRegularExpression(QStringLiteral("[\\s,;|,;、]+")),
|
||||
QString::SkipEmptyParts);
|
||||
if (TokenList.isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const QString& Token : TokenList)
|
||||
{
|
||||
Lgc_FunctionButton_Struct_SequenceKey KeyItem;
|
||||
if (!Lgc_FunctionButton_TryParseSequenceToken(Token, SourceUsage, &KeyItem))
|
||||
{
|
||||
if (p_ErrorText != nullptr)
|
||||
{
|
||||
*p_ErrorText = QStringLiteral("无法识别的按键序列片段:%1").arg(Token);
|
||||
}
|
||||
p_KeyList->clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
p_KeyList->append(KeyItem);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Lgc_FunctionButton_IsSameWindowsKey(
|
||||
const Lgc_FunctionButton_Struct_WindowsKey& Left,
|
||||
const Lgc_FunctionButton_Struct_WindowsKey& Right)
|
||||
{
|
||||
return (Left.VirtualKey == Right.VirtualKey) &&
|
||||
(Left.ExtraFlags == Right.ExtraFlags);
|
||||
}
|
||||
|
||||
bool Lgc_FunctionButton_ParseKeyCombinationText(
|
||||
const QString& Text,
|
||||
quint16 SourceUsage,
|
||||
QVector<Lgc_FunctionButton_Struct_SequenceKey>* p_KeyList,
|
||||
QString* p_ErrorText)
|
||||
{
|
||||
p_KeyList->clear();
|
||||
if (p_ErrorText != nullptr) p_ErrorText->clear();
|
||||
|
||||
QStringList TokenList;
|
||||
if (Text.contains(QLatin1Char('+')))
|
||||
{
|
||||
TokenList = Text.split(
|
||||
QRegularExpression(QStringLiteral("\\s*\\+\\s*")),
|
||||
QString::SkipEmptyParts);
|
||||
}
|
||||
else
|
||||
{
|
||||
TokenList = Text.split(
|
||||
QRegularExpression(QStringLiteral("[\\s,;|,;、]+")),
|
||||
QString::SkipEmptyParts);
|
||||
}
|
||||
|
||||
for (const QString& Token : TokenList)
|
||||
{
|
||||
Lgc_FunctionButton_Struct_SequenceKey KeyItem;
|
||||
if (!Lgc_FunctionButton_TryParseSequenceToken(Token, SourceUsage, &KeyItem))
|
||||
{
|
||||
if (p_ErrorText != nullptr)
|
||||
{
|
||||
*p_ErrorText = QStringLiteral("无法识别的按键片段:%1").arg(Token);
|
||||
}
|
||||
p_KeyList->clear();
|
||||
return false;
|
||||
}
|
||||
p_KeyList->append(KeyItem);
|
||||
}
|
||||
|
||||
return !p_KeyList->isEmpty();
|
||||
}
|
||||
|
||||
QString Lgc_FunctionButton_FormatKeyCombination(
|
||||
const QVector<Lgc_FunctionButton_Struct_SequenceKey>& KeyList)
|
||||
{
|
||||
QStringList TextList;
|
||||
for (const auto& KeyItem : KeyList)
|
||||
{
|
||||
TextList.append(KeyItem.Text);
|
||||
}
|
||||
return TextList.join(QStringLiteral("+"));
|
||||
}
|
||||
|
||||
QVector<QVector<Lgc_FunctionButton_Struct_SequenceKey>>
|
||||
Lgc_FunctionButton_GroupSequenceKeysIntoCombinations(
|
||||
const QVector<Lgc_FunctionButton_Struct_SequenceKey>& KeyList)
|
||||
{
|
||||
QVector<QVector<Lgc_FunctionButton_Struct_SequenceKey>> CombinationList;
|
||||
QVector<Lgc_FunctionButton_Struct_SequenceKey> ModifierList;
|
||||
|
||||
for (const auto& KeyItem : KeyList)
|
||||
{
|
||||
if (KeyItem.Key.IsModifier)
|
||||
{
|
||||
bool IsDuplicate = false;
|
||||
for (const auto& OldModifier : ModifierList)
|
||||
{
|
||||
if (Lgc_FunctionButton_IsSameWindowsKey(OldModifier.Key, KeyItem.Key))
|
||||
{
|
||||
IsDuplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!IsDuplicate)
|
||||
{
|
||||
ModifierList.append(KeyItem);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
QVector<Lgc_FunctionButton_Struct_SequenceKey> Combination = ModifierList;
|
||||
Combination.append(KeyItem);
|
||||
CombinationList.append(Combination);
|
||||
}
|
||||
|
||||
if (CombinationList.isEmpty() && !ModifierList.isEmpty())
|
||||
{
|
||||
CombinationList.append(ModifierList);
|
||||
}
|
||||
|
||||
return CombinationList;
|
||||
}
|
||||
|
||||
bool Lgc_FunctionButton_ParseShortcutSequenceText(
|
||||
const QString& Text,
|
||||
quint16 SourceUsage,
|
||||
QVector<QVector<Lgc_FunctionButton_Struct_SequenceKey>>* p_CombinationList,
|
||||
QString* p_ErrorText)
|
||||
{
|
||||
p_CombinationList->clear();
|
||||
if (p_ErrorText != nullptr) p_ErrorText->clear();
|
||||
|
||||
const QStringList SegmentList = Text.split(
|
||||
QRegularExpression(QStringLiteral("\\s*(?:->|=>|→)\\s*")),
|
||||
QString::SkipEmptyParts);
|
||||
|
||||
if (SegmentList.size() > 1)
|
||||
{
|
||||
for (const QString& SegmentText : SegmentList)
|
||||
{
|
||||
QVector<Lgc_FunctionButton_Struct_SequenceKey> Combination;
|
||||
if (!Lgc_FunctionButton_ParseKeyCombinationText(
|
||||
SegmentText,
|
||||
SourceUsage,
|
||||
&Combination,
|
||||
p_ErrorText))
|
||||
{
|
||||
p_CombinationList->clear();
|
||||
return false;
|
||||
}
|
||||
p_CombinationList->append(Combination);
|
||||
}
|
||||
return !p_CombinationList->isEmpty();
|
||||
}
|
||||
|
||||
QVector<Lgc_FunctionButton_Struct_SequenceKey> FlatKeyList;
|
||||
if (!Lgc_FunctionButton_ParseRecordedSequenceText(
|
||||
Text,
|
||||
SourceUsage,
|
||||
&FlatKeyList,
|
||||
p_ErrorText))
|
||||
{
|
||||
QVector<Lgc_FunctionButton_Struct_SequenceKey> SingleCombination;
|
||||
if (!Lgc_FunctionButton_ParseKeyCombinationText(
|
||||
Text,
|
||||
SourceUsage,
|
||||
&SingleCombination,
|
||||
p_ErrorText))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
p_CombinationList->append(SingleCombination);
|
||||
return true;
|
||||
}
|
||||
|
||||
*p_CombinationList = Lgc_FunctionButton_GroupSequenceKeysIntoCombinations(FlatKeyList);
|
||||
return !p_CombinationList->isEmpty();
|
||||
}
|
||||
|
||||
QString Lgc_FunctionButton_FormatShortcutSequence(
|
||||
const QVector<QVector<Lgc_FunctionButton_Struct_SequenceKey>>& CombinationList)
|
||||
{
|
||||
QStringList TextList;
|
||||
for (const auto& Combination : CombinationList)
|
||||
{
|
||||
TextList.append(Lgc_FunctionButton_FormatKeyCombination(Combination));
|
||||
}
|
||||
return TextList.join(QStringLiteral(" -> "));
|
||||
}
|
||||
|
||||
QVector<Lgc_FunctionButton_Struct_SequenceKey> Lgc_FunctionButton_ConvertUsageListToSequenceKeys(
|
||||
const QVector<quint16>& UsageList)
|
||||
{
|
||||
QVector<Lgc_FunctionButton_Struct_SequenceKey> KeyList;
|
||||
for (quint16 Usage : UsageList)
|
||||
{
|
||||
const auto Key = Lgc_FunctionButton_GetWindowsKey(Usage);
|
||||
if (Key.VirtualKey == 0)
|
||||
{
|
||||
KeyList.clear();
|
||||
return KeyList;
|
||||
}
|
||||
|
||||
Lgc_FunctionButton_Struct_SequenceKey KeyItem;
|
||||
KeyItem.Key = Key;
|
||||
KeyItem.Text = Lgc_FunctionButton_GetUsageShortText(Usage);
|
||||
KeyList.append(KeyItem);
|
||||
}
|
||||
return KeyList;
|
||||
}
|
||||
76
LOGIC/Lgc_Func_Button_Private.h
Normal file
76
LOGIC/Lgc_Func_Button_Private.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include "LOGIC/Lgc_Func_Button.h"
|
||||
#include <QtCore/QVector>
|
||||
#include <Windows.h>
|
||||
|
||||
struct Lgc_FunctionButton_Struct_WindowsKey
|
||||
{
|
||||
WORD VirtualKey = 0;
|
||||
DWORD ExtraFlags = 0;
|
||||
bool IsModifier = false;
|
||||
};
|
||||
|
||||
struct Lgc_FunctionButton_Struct_SequenceKey
|
||||
{
|
||||
Lgc_FunctionButton_Struct_WindowsKey Key;
|
||||
QString Text;
|
||||
};
|
||||
|
||||
Lgc_FunctionButton_Struct_WindowsKey Lgc_FunctionButton_GetWindowsKey(quint16 Usage);
|
||||
bool Lgc_FunctionButton_SendWindowsKey(
|
||||
const Lgc_FunctionButton_Struct_WindowsKey& Key,
|
||||
bool IsPressed);
|
||||
bool Lgc_FunctionButton_IsSameWindowsKey(
|
||||
const Lgc_FunctionButton_Struct_WindowsKey& Left,
|
||||
const Lgc_FunctionButton_Struct_WindowsKey& Right);
|
||||
|
||||
bool Lgc_FunctionButton_ParseLegacySequenceText(
|
||||
const QString& Text,
|
||||
quint16 SourceUsage,
|
||||
QVector<quint16>* p_UsageList,
|
||||
QString* p_ErrorText);
|
||||
bool Lgc_FunctionButton_TryParseSequenceToken(
|
||||
const QString& Token,
|
||||
quint16 SourceUsage,
|
||||
Lgc_FunctionButton_Struct_SequenceKey* p_KeyItem);
|
||||
bool Lgc_FunctionButton_ParseRecordedSequenceText(
|
||||
const QString& Text,
|
||||
quint16 SourceUsage,
|
||||
QVector<Lgc_FunctionButton_Struct_SequenceKey>* p_KeyList,
|
||||
QString* p_ErrorText);
|
||||
bool Lgc_FunctionButton_ParseKeyCombinationText(
|
||||
const QString& Text,
|
||||
quint16 SourceUsage,
|
||||
QVector<Lgc_FunctionButton_Struct_SequenceKey>* p_KeyList,
|
||||
QString* p_ErrorText);
|
||||
QString Lgc_FunctionButton_FormatKeyCombination(
|
||||
const QVector<Lgc_FunctionButton_Struct_SequenceKey>& KeyList);
|
||||
QVector<QVector<Lgc_FunctionButton_Struct_SequenceKey>>
|
||||
Lgc_FunctionButton_GroupSequenceKeysIntoCombinations(
|
||||
const QVector<Lgc_FunctionButton_Struct_SequenceKey>& KeyList);
|
||||
bool Lgc_FunctionButton_ParseShortcutSequenceText(
|
||||
const QString& Text,
|
||||
quint16 SourceUsage,
|
||||
QVector<QVector<Lgc_FunctionButton_Struct_SequenceKey>>* p_CombinationList,
|
||||
QString* p_ErrorText);
|
||||
QString Lgc_FunctionButton_FormatShortcutSequence(
|
||||
const QVector<QVector<Lgc_FunctionButton_Struct_SequenceKey>>& CombinationList);
|
||||
QVector<Lgc_FunctionButton_Struct_SequenceKey> Lgc_FunctionButton_ConvertUsageListToSequenceKeys(
|
||||
const QVector<quint16>& UsageList);
|
||||
|
||||
bool Lgc_FunctionButton_SendKeyCombination(
|
||||
const QVector<Lgc_FunctionButton_Struct_SequenceKey>& KeyList);
|
||||
bool Lgc_FunctionButton_SendShortcutSequence(
|
||||
const QVector<QVector<Lgc_FunctionButton_Struct_SequenceKey>>& CombinationList);
|
||||
void Lgc_FunctionButton_RunKeyCombination(
|
||||
const Lgc_FunctionFeature_Definition& Feature,
|
||||
quint16 SourceUsage,
|
||||
QString* p_TextStatus);
|
||||
void Lgc_FunctionButton_RunKeySequence(
|
||||
const Lgc_FunctionFeature_Definition& Feature,
|
||||
quint16 SourceUsage,
|
||||
QString* p_TextStatus);
|
||||
void Lgc_FunctionButton_RunOpenWebsite(
|
||||
const Lgc_FunctionFeature_Definition& Feature,
|
||||
QString* p_TextStatus);
|
||||
302
LOGIC/Lgc_Func_Button_Run.cpp
Normal file
302
LOGIC/Lgc_Func_Button_Run.cpp
Normal file
@@ -0,0 +1,302 @@
|
||||
#include "LOGIC/Lgc_Func_Button_Private.h"
|
||||
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtGui/QDesktopServices>
|
||||
#include <Windows.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
constexpr DWORD LGC_FUNCTIONBUTTON_KEY_HOLD_DELAY_MS = 12;
|
||||
constexpr DWORD LGC_FUNCTIONBUTTON_SEQUENCE_STEP_DELAY_MS = 60;
|
||||
|
||||
} // namespace
|
||||
|
||||
Lgc_FunctionButton_Struct_WindowsKey Lgc_FunctionButton_GetWindowsKey(quint16 Usage)
|
||||
{
|
||||
switch (Usage)
|
||||
{
|
||||
case 0x0053: return { VK_NUMLOCK, 0 };
|
||||
case 0x0054: return { VK_DIVIDE, KEYEVENTF_EXTENDEDKEY };
|
||||
case 0x0055: return { VK_MULTIPLY, 0 };
|
||||
case 0x0056: return { VK_SUBTRACT, 0 };
|
||||
case 0x0057: return { VK_ADD, 0 };
|
||||
case 0x0058: return { VK_RETURN, KEYEVENTF_EXTENDEDKEY };
|
||||
case 0x0059: return { VK_NUMPAD1, 0 };
|
||||
case 0x005A: return { VK_NUMPAD2, 0 };
|
||||
case 0x005B: return { VK_NUMPAD3, 0 };
|
||||
case 0x005C: return { VK_NUMPAD4, 0 };
|
||||
case 0x005D: return { VK_NUMPAD5, 0 };
|
||||
case 0x005E: return { VK_NUMPAD6, 0 };
|
||||
case 0x005F: return { VK_NUMPAD7, 0 };
|
||||
case 0x0060: return { VK_NUMPAD8, 0 };
|
||||
case 0x0061: return { VK_NUMPAD9, 0 };
|
||||
case 0x0062: return { VK_NUMPAD0, 0 };
|
||||
case 0x0063: return { VK_DECIMAL, 0 };
|
||||
case 0x00E0: return { VK_CONTROL, 0, true };
|
||||
case 0x00E1: return { VK_SHIFT, 0, true };
|
||||
case 0x00E2: return { VK_MENU, 0, true };
|
||||
case 0x00E3: return { VK_LWIN, 0, true };
|
||||
case 0x00E4: return { VK_CONTROL, KEYEVENTF_EXTENDEDKEY, true };
|
||||
case 0x00E5: return { VK_SHIFT, 0, true };
|
||||
case 0x00E6: return { VK_MENU, KEYEVENTF_EXTENDEDKEY, true };
|
||||
case 0x00E7: return { VK_RWIN, 0, true };
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
bool Lgc_FunctionButton_SendWindowsKey(
|
||||
const Lgc_FunctionButton_Struct_WindowsKey& Key,
|
||||
bool IsPressed)
|
||||
{
|
||||
if (Key.VirtualKey == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
INPUT InputData = {};
|
||||
InputData.type = INPUT_KEYBOARD;
|
||||
InputData.ki.wVk = Key.VirtualKey;
|
||||
InputData.ki.dwFlags = Key.ExtraFlags;
|
||||
if (!IsPressed)
|
||||
{
|
||||
InputData.ki.dwFlags |= KEYEVENTF_KEYUP;
|
||||
}
|
||||
|
||||
return SendInput(1, &InputData, sizeof(INPUT)) == 1;
|
||||
}
|
||||
|
||||
bool Lgc_FunctionButton_SendKeyCombination(
|
||||
const QVector<Lgc_FunctionButton_Struct_SequenceKey>& KeyList)
|
||||
{
|
||||
QVector<Lgc_FunctionButton_Struct_WindowsKey> ModifierList;
|
||||
QVector<Lgc_FunctionButton_Struct_WindowsKey> NormalKeyList;
|
||||
|
||||
for (const auto& KeyItem : KeyList)
|
||||
{
|
||||
if (KeyItem.Key.VirtualKey == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (KeyItem.Key.IsModifier)
|
||||
{
|
||||
bool IsDuplicate = false;
|
||||
for (const auto& OldModifier : ModifierList)
|
||||
{
|
||||
if (Lgc_FunctionButton_IsSameWindowsKey(OldModifier, KeyItem.Key))
|
||||
{
|
||||
IsDuplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!IsDuplicate)
|
||||
{
|
||||
ModifierList.append(KeyItem.Key);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
NormalKeyList.append(KeyItem.Key);
|
||||
}
|
||||
|
||||
for (int ModifierIndex = 0; ModifierIndex < ModifierList.size(); ++ModifierIndex)
|
||||
{
|
||||
const auto& ModifierKey = ModifierList.at(ModifierIndex);
|
||||
if (!Lgc_FunctionButton_SendWindowsKey(ModifierKey, true))
|
||||
{
|
||||
for (int Index = ModifierIndex - 1; Index >= 0; --Index)
|
||||
{
|
||||
Lgc_FunctionButton_SendWindowsKey(ModifierList.at(Index), false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Sleep(LGC_FUNCTIONBUTTON_KEY_HOLD_DELAY_MS);
|
||||
}
|
||||
|
||||
if (NormalKeyList.isEmpty())
|
||||
{
|
||||
for (int Index = ModifierList.size() - 1; Index >= 0; --Index)
|
||||
{
|
||||
if (!Lgc_FunctionButton_SendWindowsKey(ModifierList.at(Index), false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Sleep(LGC_FUNCTIONBUTTON_KEY_HOLD_DELAY_MS);
|
||||
}
|
||||
return !ModifierList.isEmpty();
|
||||
}
|
||||
|
||||
for (const auto& NormalKey : NormalKeyList)
|
||||
{
|
||||
if (!Lgc_FunctionButton_SendWindowsKey(NormalKey, true))
|
||||
{
|
||||
for (int Index = ModifierList.size() - 1; Index >= 0; --Index)
|
||||
{
|
||||
Lgc_FunctionButton_SendWindowsKey(ModifierList.at(Index), false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Sleep(LGC_FUNCTIONBUTTON_KEY_HOLD_DELAY_MS);
|
||||
if (!Lgc_FunctionButton_SendWindowsKey(NormalKey, false))
|
||||
{
|
||||
for (int Index = ModifierList.size() - 1; Index >= 0; --Index)
|
||||
{
|
||||
Lgc_FunctionButton_SendWindowsKey(ModifierList.at(Index), false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Sleep(LGC_FUNCTIONBUTTON_KEY_HOLD_DELAY_MS);
|
||||
}
|
||||
|
||||
for (int Index = ModifierList.size() - 1; Index >= 0; --Index)
|
||||
{
|
||||
if (!Lgc_FunctionButton_SendWindowsKey(ModifierList.at(Index), false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Sleep(LGC_FUNCTIONBUTTON_KEY_HOLD_DELAY_MS);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Lgc_FunctionButton_SendShortcutSequence(
|
||||
const QVector<QVector<Lgc_FunctionButton_Struct_SequenceKey>>& CombinationList)
|
||||
{
|
||||
for (int Index = 0; Index < CombinationList.size(); ++Index)
|
||||
{
|
||||
const auto& Combination = CombinationList.at(Index);
|
||||
if (!Lgc_FunctionButton_SendKeyCombination(Combination))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (Index + 1 < CombinationList.size())
|
||||
{
|
||||
Sleep(LGC_FUNCTIONBUTTON_SEQUENCE_STEP_DELAY_MS);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Lgc_FunctionButton_RunKeyCombination(
|
||||
const Lgc_FunctionFeature_Definition& Feature,
|
||||
quint16 SourceUsage,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
const QString CombinationText = Feature.SequenceText.trimmed();
|
||||
if (CombinationText.isEmpty())
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("%1 的快捷键尚未配置。")
|
||||
.arg(Lgc_FunctionButton_GetFeatureName(Feature));
|
||||
return;
|
||||
}
|
||||
|
||||
QVector<Lgc_FunctionButton_Struct_SequenceKey> KeyList;
|
||||
QString ErrorText;
|
||||
if (!Lgc_FunctionButton_ParseKeyCombinationText(
|
||||
CombinationText,
|
||||
SourceUsage,
|
||||
&KeyList,
|
||||
&ErrorText))
|
||||
{
|
||||
QVector<quint16> UsageList;
|
||||
if (!Lgc_FunctionButton_ParseLegacySequenceText(
|
||||
CombinationText,
|
||||
SourceUsage,
|
||||
&UsageList,
|
||||
&ErrorText))
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("%1 的快捷键无效:%2")
|
||||
.arg(Lgc_FunctionButton_GetFeatureName(Feature), ErrorText);
|
||||
return;
|
||||
}
|
||||
|
||||
KeyList = Lgc_FunctionButton_ConvertUsageListToSequenceKeys(UsageList);
|
||||
if (KeyList.isEmpty())
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("%1 的快捷键无效:存在无法执行的按键。")
|
||||
.arg(Lgc_FunctionButton_GetFeatureName(Feature));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
*p_TextStatus = Lgc_FunctionButton_SendKeyCombination(KeyList)
|
||||
? QStringLiteral("%1 已输出快捷键:%2")
|
||||
.arg(Lgc_FunctionButton_GetFeatureName(Feature),
|
||||
Lgc_FunctionButton_FormatKeyCombination(KeyList))
|
||||
: QStringLiteral("%1 输出快捷键失败。")
|
||||
.arg(Lgc_FunctionButton_GetFeatureName(Feature));
|
||||
}
|
||||
|
||||
void Lgc_FunctionButton_RunKeySequence(
|
||||
const Lgc_FunctionFeature_Definition& Feature,
|
||||
quint16 SourceUsage,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
const QString SequenceText = Feature.SequenceText.trimmed();
|
||||
if (SequenceText.isEmpty())
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("%1 的快捷键序列尚未配置。")
|
||||
.arg(Lgc_FunctionButton_GetFeatureName(Feature));
|
||||
return;
|
||||
}
|
||||
|
||||
QVector<QVector<Lgc_FunctionButton_Struct_SequenceKey>> CombinationList;
|
||||
QString ErrorText;
|
||||
if (!Lgc_FunctionButton_ParseShortcutSequenceText(
|
||||
SequenceText,
|
||||
SourceUsage,
|
||||
&CombinationList,
|
||||
&ErrorText))
|
||||
{
|
||||
QVector<quint16> UsageList;
|
||||
if (!Lgc_FunctionButton_ParseLegacySequenceText(
|
||||
SequenceText,
|
||||
SourceUsage,
|
||||
&UsageList,
|
||||
&ErrorText))
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("%1 的快捷键序列无效:%2")
|
||||
.arg(Lgc_FunctionButton_GetFeatureName(Feature), ErrorText);
|
||||
return;
|
||||
}
|
||||
|
||||
const QVector<Lgc_FunctionButton_Struct_SequenceKey> KeyList =
|
||||
Lgc_FunctionButton_ConvertUsageListToSequenceKeys(UsageList);
|
||||
CombinationList = Lgc_FunctionButton_GroupSequenceKeysIntoCombinations(KeyList);
|
||||
if (CombinationList.isEmpty())
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("%1 的快捷键序列无效:存在无法执行的按键。")
|
||||
.arg(Lgc_FunctionButton_GetFeatureName(Feature));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
*p_TextStatus = Lgc_FunctionButton_SendShortcutSequence(CombinationList)
|
||||
? QStringLiteral("%1 已输出快捷键序列:%2")
|
||||
.arg(Lgc_FunctionButton_GetFeatureName(Feature),
|
||||
Lgc_FunctionButton_FormatShortcutSequence(CombinationList))
|
||||
: QStringLiteral("%1 输出快捷键序列失败。")
|
||||
.arg(Lgc_FunctionButton_GetFeatureName(Feature));
|
||||
}
|
||||
|
||||
void Lgc_FunctionButton_RunOpenWebsite(
|
||||
const Lgc_FunctionFeature_Definition& Feature,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
const QString UrlText = Feature.WebsiteUrl.trimmed();
|
||||
const QUrl Url = QUrl::fromUserInput(UrlText);
|
||||
if (UrlText.isEmpty() || !Url.isValid() || Url.isEmpty())
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("%1 的网址配置无效。")
|
||||
.arg(Lgc_FunctionButton_GetFeatureName(Feature));
|
||||
return;
|
||||
}
|
||||
|
||||
*p_TextStatus = QDesktopServices::openUrl(Url)
|
||||
? QStringLiteral("%1 已打开网址:%2")
|
||||
.arg(Lgc_FunctionButton_GetFeatureName(Feature), Url.toString())
|
||||
: QStringLiteral("%1 打开网址失败。")
|
||||
.arg(Lgc_FunctionButton_GetFeatureName(Feature));
|
||||
}
|
||||
49
MID/Mid_Ble.cpp
Normal file
49
MID/Mid_Ble.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "MID/Mid_Ble.h"
|
||||
|
||||
#include <QtCore/QStringList>
|
||||
|
||||
QString Mid_GetBleOpcodeText(quint8 Opcode)
|
||||
{
|
||||
return QStringLiteral("Opcode 0x%1")
|
||||
.arg(Opcode, 2, 16, QLatin1Char('0'))
|
||||
.toUpper();
|
||||
}
|
||||
|
||||
QString Mid_NormalizeBleAddress(const QString& Text)
|
||||
{
|
||||
QString HexText;
|
||||
HexText.reserve(Text.size());
|
||||
|
||||
for (const QChar Character : Text.trimmed())
|
||||
{
|
||||
if (Character.isDigit() ||
|
||||
((Character >= QLatin1Char('a')) && (Character <= QLatin1Char('f'))) ||
|
||||
((Character >= QLatin1Char('A')) && (Character <= QLatin1Char('F'))))
|
||||
{
|
||||
HexText.append(Character.toUpper());
|
||||
}
|
||||
}
|
||||
|
||||
return HexText.size() == 12 ? HexText : QString();
|
||||
}
|
||||
|
||||
QString Mid_FormatBleAddress(const QString& Address)
|
||||
{
|
||||
const QString Normalized = Mid_NormalizeBleAddress(Address);
|
||||
if (Normalized.isEmpty())
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
QStringList PartList;
|
||||
for (int Index = 0; Index < Normalized.size(); Index += 2)
|
||||
{
|
||||
PartList.append(Normalized.mid(Index, 2));
|
||||
}
|
||||
return PartList.join(QLatin1Char(':'));
|
||||
}
|
||||
|
||||
bool Mid_IsBleAddressValid(const QString& Address)
|
||||
{
|
||||
return !Mid_NormalizeBleAddress(Address).isEmpty();
|
||||
}
|
||||
9
MID/Mid_Ble.h
Normal file
9
MID/Mid_Ble.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QString>
|
||||
|
||||
// BLE raw packet opcode text helpers.
|
||||
QString Mid_GetBleOpcodeText(quint8 Opcode);
|
||||
QString Mid_NormalizeBleAddress(const QString& Text);
|
||||
QString Mid_FormatBleAddress(const QString& Address);
|
||||
bool Mid_IsBleAddressValid(const QString& Address);
|
||||
243
MID/Mid_Def.cpp
Normal file
243
MID/Mid_Def.cpp
Normal file
@@ -0,0 +1,243 @@
|
||||
#include "MID/Mid_Def.h"
|
||||
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QVector>
|
||||
#include <Windows.h>
|
||||
#include <SetupAPI.h>
|
||||
#include <hidsdi.h>
|
||||
#include <hidpi.h>
|
||||
|
||||
#pragma comment(lib, "hid.lib")
|
||||
#pragma comment(lib, "setupapi.lib")
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
QString Mid_GetDeviceInstanceId(HDEVINFO DeviceInfoSet, SP_DEVINFO_DATA* p_DeviceInfoData)
|
||||
{
|
||||
DWORD NeedLength = 0;
|
||||
SetupDiGetDeviceInstanceIdW(
|
||||
DeviceInfoSet,
|
||||
p_DeviceInfoData,
|
||||
nullptr,
|
||||
0,
|
||||
&NeedLength);
|
||||
if (NeedLength == 0)
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
QVector<wchar_t> Buffer(static_cast<int>(NeedLength) + 1, 0);
|
||||
return SetupDiGetDeviceInstanceIdW(
|
||||
DeviceInfoSet,
|
||||
p_DeviceInfoData,
|
||||
Buffer.data(),
|
||||
static_cast<DWORD>(Buffer.size()),
|
||||
nullptr)
|
||||
? QString::fromWCharArray(Buffer.constData()).trimmed()
|
||||
: QString();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Find the HID interface that matches VID/PID plus usage page/usage.
|
||||
bool Mid_FindHidInterface(
|
||||
const Mid_Struct_DeviceMatch& Match,
|
||||
QString* p_DevicePath,
|
||||
quint16* p_InputLength,
|
||||
quint16* p_OutputLength,
|
||||
QString* p_DeviceInstanceId)
|
||||
{
|
||||
GUID HidGuid;
|
||||
HidD_GetHidGuid(&HidGuid);
|
||||
|
||||
HDEVINFO DeviceInfoSet = SetupDiGetClassDevsW(&HidGuid, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
||||
if (DeviceInfoSet == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsFound = false;
|
||||
SP_DEVICE_INTERFACE_DATA InterfaceData = {};
|
||||
InterfaceData.cbSize = sizeof(InterfaceData);
|
||||
|
||||
for (DWORD Index = 0;
|
||||
SetupDiEnumDeviceInterfaces(DeviceInfoSet, nullptr, &HidGuid, Index, &InterfaceData);
|
||||
++Index)
|
||||
{
|
||||
DWORD NeedLength = 0;
|
||||
SP_DEVINFO_DATA DeviceInfoData = {};
|
||||
DeviceInfoData.cbSize = sizeof(DeviceInfoData);
|
||||
SetupDiGetDeviceInterfaceDetailW(
|
||||
DeviceInfoSet,
|
||||
&InterfaceData,
|
||||
nullptr,
|
||||
0,
|
||||
&NeedLength,
|
||||
&DeviceInfoData);
|
||||
if (NeedLength == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QByteArray Buffer(static_cast<int>(NeedLength), 0);
|
||||
auto* p_Detail = reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA_W*>(Buffer.data());
|
||||
p_Detail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
|
||||
|
||||
if (!SetupDiGetDeviceInterfaceDetailW(
|
||||
DeviceInfoSet,
|
||||
&InterfaceData,
|
||||
p_Detail,
|
||||
NeedLength,
|
||||
nullptr,
|
||||
&DeviceInfoData))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
HANDLE HandleQuery = CreateFileW(
|
||||
p_Detail->DevicePath,
|
||||
0,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
0,
|
||||
nullptr);
|
||||
if (HandleQuery == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
HIDD_ATTRIBUTES Attributes = {};
|
||||
Attributes.Size = sizeof(Attributes);
|
||||
PHIDP_PREPARSED_DATA p_Preparsed = nullptr;
|
||||
HIDP_CAPS Caps = {};
|
||||
const bool IsExactMatch =
|
||||
HidD_GetAttributes(HandleQuery, &Attributes) &&
|
||||
HidD_GetPreparsedData(HandleQuery, &p_Preparsed) &&
|
||||
(HidP_GetCaps(p_Preparsed, &Caps) == HIDP_STATUS_SUCCESS) &&
|
||||
(Caps.UsagePage == Match.UsagePage) &&
|
||||
(Caps.Usage == Match.Usage) &&
|
||||
(Attributes.VendorID == Match.VendorId) &&
|
||||
(Attributes.ProductID == Match.ProductId);
|
||||
|
||||
if (p_Preparsed != nullptr)
|
||||
{
|
||||
HidD_FreePreparsedData(p_Preparsed);
|
||||
}
|
||||
CloseHandle(HandleQuery);
|
||||
|
||||
if (IsExactMatch)
|
||||
{
|
||||
if (p_DevicePath != nullptr)
|
||||
{
|
||||
*p_DevicePath = QString::fromWCharArray(p_Detail->DevicePath);
|
||||
}
|
||||
if (p_InputLength != nullptr)
|
||||
{
|
||||
*p_InputLength = Caps.InputReportByteLength;
|
||||
}
|
||||
if (p_OutputLength != nullptr)
|
||||
{
|
||||
*p_OutputLength = Caps.OutputReportByteLength;
|
||||
}
|
||||
if (p_DeviceInstanceId != nullptr)
|
||||
{
|
||||
*p_DeviceInstanceId = Mid_GetDeviceInstanceId(DeviceInfoSet, &DeviceInfoData);
|
||||
}
|
||||
|
||||
IsFound = true;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SetupDiDestroyDeviceInfoList(DeviceInfoSet);
|
||||
return IsFound;
|
||||
}
|
||||
|
||||
bool Mid_IsBluetoothHidInstanceId(const QString& DeviceInstanceId)
|
||||
{
|
||||
const QString UpperId = DeviceInstanceId.trimmed().toUpper();
|
||||
return UpperId.contains(QStringLiteral("BTHLEDEVICE")) ||
|
||||
UpperId.contains(QStringLiteral("{00001812-0000-1000-8000-00805F9B34FB}"));
|
||||
}
|
||||
|
||||
QString Mid_GetHexText(const QByteArray& ByteArray)
|
||||
{
|
||||
QStringList TextList;
|
||||
for (int Index = 0; Index < ByteArray.size(); ++Index)
|
||||
{
|
||||
TextList.append(QStringLiteral("%1")
|
||||
.arg(static_cast<quint8>(ByteArray.at(Index)), 2, 16, QLatin1Char('0'))
|
||||
.toUpper());
|
||||
}
|
||||
return TextList.join(QLatin1Char(' '));
|
||||
}
|
||||
|
||||
QString Mid_GetModifierText(quint8 Modifier)
|
||||
{
|
||||
QStringList TextList;
|
||||
if (Modifier == 0)
|
||||
{
|
||||
return QStringLiteral("无");
|
||||
}
|
||||
|
||||
if ((Modifier & 0x01) != 0) TextList.append(QStringLiteral("Left Ctrl"));
|
||||
if ((Modifier & 0x02) != 0) TextList.append(QStringLiteral("Left Shift"));
|
||||
if ((Modifier & 0x04) != 0) TextList.append(QStringLiteral("Left Alt"));
|
||||
if ((Modifier & 0x08) != 0) TextList.append(QStringLiteral("Left GUI"));
|
||||
if ((Modifier & 0x10) != 0) TextList.append(QStringLiteral("Right Ctrl"));
|
||||
if ((Modifier & 0x20) != 0) TextList.append(QStringLiteral("Right Shift"));
|
||||
if ((Modifier & 0x40) != 0) TextList.append(QStringLiteral("Right Alt"));
|
||||
if ((Modifier & 0x80) != 0) TextList.append(QStringLiteral("Right GUI"));
|
||||
return TextList.join(QStringLiteral(", "));
|
||||
}
|
||||
|
||||
QString Mid_GetKeyboardUsageText(quint16 Usage)
|
||||
{
|
||||
switch (Usage)
|
||||
{
|
||||
case 0x0053: return QStringLiteral("Num Lock");
|
||||
case 0x0054: return QStringLiteral("小键盘 /");
|
||||
case 0x0055: return QStringLiteral("小键盘 *");
|
||||
case 0x0056: return QStringLiteral("小键盘 -");
|
||||
case 0x0057: return QStringLiteral("小键盘 +");
|
||||
case 0x0058: return QStringLiteral("小键盘 Enter");
|
||||
case 0x0059: return QStringLiteral("小键盘 1");
|
||||
case 0x005A: return QStringLiteral("小键盘 2");
|
||||
case 0x005B: return QStringLiteral("小键盘 3");
|
||||
case 0x005C: return QStringLiteral("小键盘 4");
|
||||
case 0x005D: return QStringLiteral("小键盘 5");
|
||||
case 0x005E: return QStringLiteral("小键盘 6");
|
||||
case 0x005F: return QStringLiteral("小键盘 7");
|
||||
case 0x0060: return QStringLiteral("小键盘 8");
|
||||
case 0x0061: return QStringLiteral("小键盘 9");
|
||||
case 0x0062: return QStringLiteral("小键盘 0");
|
||||
case 0x0063: return QStringLiteral("小键盘 .");
|
||||
case 0x00E0: return QStringLiteral("Left Ctrl");
|
||||
case 0x00E1: return QStringLiteral("Left Shift");
|
||||
case 0x00E2: return QStringLiteral("Left Alt");
|
||||
case 0x00E3: return QStringLiteral("Left GUI");
|
||||
case 0x00E4: return QStringLiteral("Right Ctrl");
|
||||
case 0x00E5: return QStringLiteral("Right Shift");
|
||||
case 0x00E6: return QStringLiteral("Right Alt");
|
||||
case 0x00E7: return QStringLiteral("Right GUI");
|
||||
default:
|
||||
return QStringLiteral("未知 HID Usage");
|
||||
}
|
||||
}
|
||||
|
||||
QString Mid_GetConsumerUsageText(quint16 Usage)
|
||||
{
|
||||
switch (Usage)
|
||||
{
|
||||
case 0x0000: return QStringLiteral("释放");
|
||||
case 0x00E2: return QStringLiteral("Mute");
|
||||
case 0x00E9: return QStringLiteral("Volume Up");
|
||||
case 0x00EA: return QStringLiteral("Volume Down");
|
||||
default:
|
||||
return QStringLiteral("未知 Consumer Usage");
|
||||
}
|
||||
}
|
||||
|
||||
81
MID/Mid_Def.h
Normal file
81
MID/Mid_Def.h
Normal file
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QString>
|
||||
|
||||
// Shared protocol constants and raw packet definitions.
|
||||
|
||||
enum Mid_Enum_ReportId : quint8
|
||||
{
|
||||
Mid_Enum_ReportId_None = 0x00,
|
||||
Mid_Enum_ReportId_Nkro = 0x01,
|
||||
Mid_Enum_ReportId_Consumer = 0x03,
|
||||
Mid_Enum_ReportId_Vendor = 0x04,
|
||||
Mid_Enum_ReportId_VendorCommand = 0x05
|
||||
};
|
||||
|
||||
enum Mid_Enum_RawPacketSource : quint8
|
||||
{
|
||||
Mid_Enum_RawPacketSource_None = 0,
|
||||
Mid_Enum_RawPacketSource_UsbNkroRaw,
|
||||
Mid_Enum_RawPacketSource_UsbConsumerHid,
|
||||
Mid_Enum_RawPacketSource_UsbVendorHid,
|
||||
Mid_Enum_RawPacketSource_BleGatt,
|
||||
Mid_Enum_RawPacketSource_BleHidKeyboard,
|
||||
Mid_Enum_RawPacketSource_BleHidConsumer,
|
||||
Mid_Enum_RawPacketSource_BleHidVendor,
|
||||
Mid_Enum_RawPacketSource_BleHidVendorCommand
|
||||
};
|
||||
|
||||
const quint16 MID_CONST_VENDOR_ID_DEFAULT = 0x1209;
|
||||
const quint16 MID_CONST_PRODUCT_ID_DEFAULT = 0x0001;
|
||||
|
||||
struct Mid_Struct_DeviceConfig
|
||||
{
|
||||
quint16 VendorId = MID_CONST_VENDOR_ID_DEFAULT;
|
||||
quint16 ProductId = MID_CONST_PRODUCT_ID_DEFAULT;
|
||||
};
|
||||
|
||||
struct Mid_Struct_DeviceMatch
|
||||
{
|
||||
quint16 VendorId = 0;
|
||||
quint16 ProductId = 0;
|
||||
quint16 UsagePage = 0;
|
||||
quint16 Usage = 0;
|
||||
};
|
||||
|
||||
struct Mid_Struct_RawPacket
|
||||
{
|
||||
bool IsValid = false;
|
||||
Mid_Enum_RawPacketSource Source = Mid_Enum_RawPacketSource_None;
|
||||
QByteArray ByteArray;
|
||||
QString PortName;
|
||||
};
|
||||
|
||||
const quint16 MID_CONST_USAGE_PAGE_NKRO = 0x0001;
|
||||
const quint16 MID_CONST_USAGE_NKRO = 0x0006;
|
||||
const quint16 MID_CONST_USAGE_PAGE_CONSUMER = 0x000C;
|
||||
const quint16 MID_CONST_USAGE_CONSUMER = 0x0001;
|
||||
const quint16 MID_CONST_USAGE_PAGE_VENDOR = 0xFF00;
|
||||
const quint16 MID_CONST_USAGE_VENDOR = 0x0002;
|
||||
const quint16 MID_CONST_USAGE_PAGE_VENDOR_COMMAND = 0xFF01;
|
||||
const quint16 MID_CONST_USAGE_VENDOR_COMMAND = 0x0005;
|
||||
const int MID_CONST_KEYBOARD_USAGE_MAX = 0x00E7;
|
||||
const int MID_CONST_USAGE_BITMAP_SIZE = 29;
|
||||
const int MID_CONST_PACKET_SIZE_NKRO = 31;
|
||||
const int MID_CONST_PACKET_SIZE_VENDOR = 31;
|
||||
const int MID_CONST_PACKET_SIZE_VENDOR_COMMAND = 10;
|
||||
const int MID_CONST_PACKET_SIZE_VENDOR_COMMAND_DATA = 8;
|
||||
const int MID_CONST_PACKET_SIZE_CONSUMER = 3;
|
||||
bool Mid_FindHidInterface(
|
||||
const Mid_Struct_DeviceMatch& Match,
|
||||
QString* p_DevicePath,
|
||||
quint16* p_InputLength,
|
||||
quint16* p_OutputLength,
|
||||
QString* p_DeviceInstanceId = nullptr);
|
||||
bool Mid_IsBluetoothHidInstanceId(const QString& DeviceInstanceId);
|
||||
QString Mid_GetHexText(const QByteArray& ByteArray);
|
||||
QString Mid_GetModifierText(quint8 Modifier);
|
||||
QString Mid_GetKeyboardUsageText(quint16 Usage);
|
||||
QString Mid_GetConsumerUsageText(quint16 Usage);
|
||||
|
||||
30
main.cpp
Normal file
30
main.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "APP/APP_UIWindow.h"
|
||||
#include "APP/APP_Theme.h"
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtWidgets/QStyleFactory>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// 在创建 QApplication 之前开启高 DPI 缩放支持,
|
||||
// 让界面在高分屏缩放下保持正常显示。
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
|
||||
// 高分屏下使用更清晰的图片和图标资源。
|
||||
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
|
||||
|
||||
// 创建 Qt 图形界面应用对象。
|
||||
QApplication app(argc, argv);
|
||||
|
||||
// 统一使用 Fusion 风格,减少系统主题差异。
|
||||
app.setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
// 应用全局调色板和默认字体。
|
||||
app.setPalette(APP::APP_Theme::App_Func_GetPalette());
|
||||
app.setFont(APP::APP_Theme::App_Func_GetBodyFont());
|
||||
|
||||
APP::App_UIWindow window;
|
||||
window.show();
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
Reference in New Issue
Block a user