first push

This commit is contained in:
2026-04-03 09:26:10 +08:00
parent 2937a44e07
commit 025b88e366
41 changed files with 6842 additions and 0 deletions

40
APP/APP_GlassCard.cpp Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

View 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

View 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
View 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
View 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
View 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
View 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
View 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, &reg, 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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);

View 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;
}

View 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);

View 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
View 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
View 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
View 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
View 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
View 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();
}