From 1dda4642c2116236c118dce331f1cec27b464b71 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Sat, 10 Aug 2019 21:14:23 +0200 Subject: [PATCH 01/50] #2: Added skeleton Qt5 GUI project to solution --- XBOFS.win.qt5/XBOFS.win.qt5.vcxproj | 139 ++++++++++++++++++++ XBOFS.win.qt5/XBOFS.win.qt5.vcxproj.filters | 46 +++++++ XBOFS.win.qt5/XBOFSWinQT5GUI.cpp | 7 + XBOFS.win.qt5/XBOFSWinQT5GUI.h | 15 +++ XBOFS.win.qt5/XBOFSWinQT5GUI.qrc | 4 + XBOFS.win.qt5/XBOFSWinQT5GUI.ui | 29 ++++ XBOFS.win.qt5/main.cpp | 10 ++ XBOFS.win.sln | 24 ++++ 8 files changed, 274 insertions(+) create mode 100644 XBOFS.win.qt5/XBOFS.win.qt5.vcxproj create mode 100644 XBOFS.win.qt5/XBOFS.win.qt5.vcxproj.filters create mode 100644 XBOFS.win.qt5/XBOFSWinQT5GUI.cpp create mode 100644 XBOFS.win.qt5/XBOFSWinQT5GUI.h create mode 100644 XBOFS.win.qt5/XBOFSWinQT5GUI.qrc create mode 100644 XBOFS.win.qt5/XBOFSWinQT5GUI.ui create mode 100644 XBOFS.win.qt5/main.cpp diff --git a/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj b/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj new file mode 100644 index 0000000..ac4cadf --- /dev/null +++ b/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj @@ -0,0 +1,139 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {B12702AD-ABFB-343A-A199-8E24837244A3} + Qt4VSv1.0 + 10.0.17763.0 + + + + Application + v141 + + + Application + WindowsApplicationForDrivers10.0 + + + + $(MSBuildProjectDirectory)\QtMsBuild + + + $(SolutionDir)$(Platform)\$(Configuration)\ + + + $(SolutionDir)$(Platform)\$(Configuration)\ + + + + + + + + + + + + + + + + + + + true + UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DLL;QT_CORE_LIB;QT_GUI_LIB;QT_WIDGETS_LIB;%(PreprocessorDefinitions) + .\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtWidgets;%(AdditionalIncludeDirectories) + Disabled + ProgramDatabase + MultiThreadedDebugDLL + true + + + Windows + $(OutDir)\$(ProjectName).exe + $(QTDIR)\lib;%(AdditionalLibraryDirectories) + true + qtmaind.lib;Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;%(AdditionalDependencies) + + + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + Moc'ing %(Identity)... + .\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtWidgets;%(AdditionalIncludeDirectories) + UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DLL;QT_CORE_LIB;QT_GUI_LIB;QT_WIDGETS_LIB;%(PreprocessorDefinitions) + + + Uic'ing %(Identity)... + .\GeneratedFiles\ui_%(Filename).h + + + Rcc'ing %(Identity)... + .\GeneratedFiles\qrc_%(Filename).cpp + + + + + true + UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DLL;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QT_GUI_LIB;QT_WIDGETS_LIB;%(PreprocessorDefinitions) + .\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtWidgets;%(AdditionalIncludeDirectories) + + MultiThreadedDLL + true + + + Windows + $(OutDir)\$(ProjectName).exe + $(QTDIR)\lib;%(AdditionalLibraryDirectories) + false + qtmain.lib;Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;%(AdditionalDependencies) + + + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + Moc'ing %(Identity)... + .\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtWidgets;%(AdditionalIncludeDirectories) + UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DLL;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QT_GUI_LIB;QT_WIDGETS_LIB;%(PreprocessorDefinitions) + + + Uic'ing %(Identity)... + .\GeneratedFiles\ui_%(Filename).h + + + Rcc'ing %(Identity)... + .\GeneratedFiles\qrc_%(Filename).cpp + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj.filters b/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj.filters new file mode 100644 index 0000000..323a1ad --- /dev/null +++ b/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj.filters @@ -0,0 +1,46 @@ + + + + + + Source Files + + + + + {D9D6E242-F8AF-46E4-B9FD-80ECBC20BA3E} + qrc;* + false + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + true + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + true + + + {99349809-55BA-4b9d-BF79-8FDBB0286EB3} + ui + true + + + + + Header Files + + + + + Form Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp new file mode 100644 index 0000000..8d1c410 --- /dev/null +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp @@ -0,0 +1,7 @@ +#include "XBOFSWinQT5GUI.h" + +XBOFSWinQT5GUI::XBOFSWinQT5GUI(QWidget *parent) + : QMainWindow(parent) +{ + ui.setupUi(this); +} diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.h b/XBOFS.win.qt5/XBOFSWinQT5GUI.h new file mode 100644 index 0000000..9105f16 --- /dev/null +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include "ui_XBOFSWinQT5GUI.h" + +class XBOFSWinQT5GUI : public QMainWindow +{ + Q_OBJECT + +public: + XBOFSWinQT5GUI(QWidget *parent = Q_NULLPTR); + +private: + Ui::XBOFSWinQT5GUIClass ui; +}; diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.qrc b/XBOFS.win.qt5/XBOFSWinQT5GUI.qrc new file mode 100644 index 0000000..a7f073c --- /dev/null +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.qrc @@ -0,0 +1,4 @@ + + + + diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.ui b/XBOFS.win.qt5/XBOFSWinQT5GUI.ui new file mode 100644 index 0000000..b5db87c --- /dev/null +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.ui @@ -0,0 +1,29 @@ + + XBOFSWinQT5GUIClass + + + XBOFSWinQT5GUIClass + + + + 0 + 0 + 600 + 400 + + + + XBOFSWinQT5GUI + + + + + + + + + + + + + diff --git a/XBOFS.win.qt5/main.cpp b/XBOFS.win.qt5/main.cpp new file mode 100644 index 0000000..8e1c532 --- /dev/null +++ b/XBOFS.win.qt5/main.cpp @@ -0,0 +1,10 @@ +#include "XBOFSWinQT5GUI.h" +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + XBOFSWinQT5GUI w; + w.show(); + return a.exec(); +} diff --git a/XBOFS.win.sln b/XBOFS.win.sln index f39444b..f492d8d 100644 --- a/XBOFS.win.sln +++ b/XBOFS.win.sln @@ -32,6 +32,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "presets", "presets", "{98CB presets\razer_atrox_zadig_preset.cfg = presets\razer_atrox_zadig_preset.cfg EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "XBOFS.win.qt5", "XBOFS.win.qt5\XBOFS.win.qt5.vcxproj", "{B12702AD-ABFB-343A-A199-8E24837244A3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug_DLL|x64 = Debug_DLL|x64 @@ -96,6 +98,28 @@ Global {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release|x64.Build.0 = Release_LIB|x64 {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release|x86.ActiveCfg = Release_DLL|Win32 {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release|x86.Build.0 = Release_DLL|Win32 + {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug_DLL|x64.ActiveCfg = Debug|x64 + {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug_DLL|x64.Build.0 = Debug|x64 + {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug_DLL|x86.ActiveCfg = Release|x64 + {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug_DLL|x86.Build.0 = Release|x64 + {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug_LIB|x64.ActiveCfg = Debug|x64 + {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug_LIB|x64.Build.0 = Debug|x64 + {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug_LIB|x86.ActiveCfg = Release|x64 + {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug_LIB|x86.Build.0 = Release|x64 + {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug|x64.ActiveCfg = Debug|x64 + {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug|x64.Build.0 = Debug|x64 + {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug|x86.ActiveCfg = Debug|x64 + {B12702AD-ABFB-343A-A199-8E24837244A3}.Release_DLL|x64.ActiveCfg = Release|x64 + {B12702AD-ABFB-343A-A199-8E24837244A3}.Release_DLL|x64.Build.0 = Release|x64 + {B12702AD-ABFB-343A-A199-8E24837244A3}.Release_DLL|x86.ActiveCfg = Release|x64 + {B12702AD-ABFB-343A-A199-8E24837244A3}.Release_DLL|x86.Build.0 = Release|x64 + {B12702AD-ABFB-343A-A199-8E24837244A3}.Release_LIB|x64.ActiveCfg = Release|x64 + {B12702AD-ABFB-343A-A199-8E24837244A3}.Release_LIB|x64.Build.0 = Release|x64 + {B12702AD-ABFB-343A-A199-8E24837244A3}.Release_LIB|x86.ActiveCfg = Release|x64 + {B12702AD-ABFB-343A-A199-8E24837244A3}.Release_LIB|x86.Build.0 = Release|x64 + {B12702AD-ABFB-343A-A199-8E24837244A3}.Release|x64.ActiveCfg = Release|x64 + {B12702AD-ABFB-343A-A199-8E24837244A3}.Release|x64.Build.0 = Release|x64 + {B12702AD-ABFB-343A-A199-8E24837244A3}.Release|x86.ActiveCfg = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From e10ce5440e23795952aa50a32b8c7c4d8acec401 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Tue, 13 Aug 2019 20:22:01 +0200 Subject: [PATCH 02/50] #2: Removed PDCurses and switch main project in solution --- .gitmodules | 3 - PDCurses | 1 - .../GeneratedFiles/qrc_XBOFSWinQT5GUI.cpp | 46 ++++++++ .../GeneratedFiles/ui_XBOFSWinQT5GUI.h | 66 +++++++++++ XBOFS.win.sln | 4 + XBOFS.win/PDCursesUIManager.cpp | 103 ------------------ XBOFS.win/PDCursesUIManager.h | 27 ----- XBOFS.win/XBOFS.win.vcxproj | 3 - XBOFS.win/XBOFS.win.vcxproj.filters | 9 -- XBOFS.win/main.cpp | 20 ---- pre-build.cmd | 5 +- 11 files changed, 117 insertions(+), 170 deletions(-) delete mode 160000 PDCurses create mode 100644 XBOFS.win.qt5/GeneratedFiles/qrc_XBOFSWinQT5GUI.cpp create mode 100644 XBOFS.win.qt5/GeneratedFiles/ui_XBOFSWinQT5GUI.h delete mode 100644 XBOFS.win/PDCursesUIManager.cpp delete mode 100644 XBOFS.win/PDCursesUIManager.h delete mode 100644 XBOFS.win/main.cpp diff --git a/.gitmodules b/.gitmodules index 3c4c637..f30108b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ [submodule "ViGEmClient"] path = ViGEmClient url = https://github.com/ViGEm/ViGEmClient -[submodule "PDCurses"] - path = PDCurses - url = https://github.com/wmcbrine/PDCurses diff --git a/PDCurses b/PDCurses deleted file mode 160000 index 2467ab2..0000000 --- a/PDCurses +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2467ab2b6c07163d0171b80ad6c252c29da28173 diff --git a/XBOFS.win.qt5/GeneratedFiles/qrc_XBOFSWinQT5GUI.cpp b/XBOFS.win.qt5/GeneratedFiles/qrc_XBOFSWinQT5GUI.cpp new file mode 100644 index 0000000..3d478ba --- /dev/null +++ b/XBOFS.win.qt5/GeneratedFiles/qrc_XBOFSWinQT5GUI.cpp @@ -0,0 +1,46 @@ +/**************************************************************************** +** Resource object code +** +** Created by: The Resource Compiler for Qt version 5.13.0 +** +** WARNING! All changes made in this file will be lost! +*****************************************************************************/ + +#ifdef QT_NAMESPACE +# define QT_RCC_PREPEND_NAMESPACE(name) ::QT_NAMESPACE::name +# define QT_RCC_MANGLE_NAMESPACE0(x) x +# define QT_RCC_MANGLE_NAMESPACE1(a, b) a##_##b +# define QT_RCC_MANGLE_NAMESPACE2(a, b) QT_RCC_MANGLE_NAMESPACE1(a,b) +# define QT_RCC_MANGLE_NAMESPACE(name) QT_RCC_MANGLE_NAMESPACE2( \ + QT_RCC_MANGLE_NAMESPACE0(name), QT_RCC_MANGLE_NAMESPACE0(QT_NAMESPACE)) +#else +# define QT_RCC_PREPEND_NAMESPACE(name) name +# define QT_RCC_MANGLE_NAMESPACE(name) name +#endif + +#ifdef QT_NAMESPACE +namespace QT_NAMESPACE { +#endif + +#ifdef QT_NAMESPACE +} +#endif + +int QT_RCC_MANGLE_NAMESPACE(qInitResources_XBOFSWinQT5GUI)(); +int QT_RCC_MANGLE_NAMESPACE(qInitResources_XBOFSWinQT5GUI)() +{ + return 1; +} + +int QT_RCC_MANGLE_NAMESPACE(qCleanupResources_XBOFSWinQT5GUI)(); +int QT_RCC_MANGLE_NAMESPACE(qCleanupResources_XBOFSWinQT5GUI)() +{ + return 1; +} + +namespace { + struct initializer { + initializer() { QT_RCC_MANGLE_NAMESPACE(qInitResources_XBOFSWinQT5GUI)(); } + ~initializer() { QT_RCC_MANGLE_NAMESPACE(qCleanupResources_XBOFSWinQT5GUI)(); } + } dummy; +} diff --git a/XBOFS.win.qt5/GeneratedFiles/ui_XBOFSWinQT5GUI.h b/XBOFS.win.qt5/GeneratedFiles/ui_XBOFSWinQT5GUI.h new file mode 100644 index 0000000..2341c1a --- /dev/null +++ b/XBOFS.win.qt5/GeneratedFiles/ui_XBOFSWinQT5GUI.h @@ -0,0 +1,66 @@ +/******************************************************************************** +** Form generated from reading UI file 'XBOFSWinQT5GUI.ui' +** +** Created by: Qt User Interface Compiler version 5.13.0 +** +** WARNING! All changes made in this file will be lost when recompiling UI file! +********************************************************************************/ + +#ifndef UI_XBOFSWINQT5GUI_H +#define UI_XBOFSWINQT5GUI_H + +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class Ui_XBOFSWinQT5GUIClass +{ +public: + QMenuBar *menuBar; + QToolBar *mainToolBar; + QWidget *centralWidget; + QStatusBar *statusBar; + + void setupUi(QMainWindow *XBOFSWinQT5GUIClass) + { + if (XBOFSWinQT5GUIClass->objectName().isEmpty()) + XBOFSWinQT5GUIClass->setObjectName(QString::fromUtf8("XBOFSWinQT5GUIClass")); + XBOFSWinQT5GUIClass->resize(600, 400); + menuBar = new QMenuBar(XBOFSWinQT5GUIClass); + menuBar->setObjectName(QString::fromUtf8("menuBar")); + XBOFSWinQT5GUIClass->setMenuBar(menuBar); + mainToolBar = new QToolBar(XBOFSWinQT5GUIClass); + mainToolBar->setObjectName(QString::fromUtf8("mainToolBar")); + XBOFSWinQT5GUIClass->addToolBar(mainToolBar); + centralWidget = new QWidget(XBOFSWinQT5GUIClass); + centralWidget->setObjectName(QString::fromUtf8("centralWidget")); + XBOFSWinQT5GUIClass->setCentralWidget(centralWidget); + statusBar = new QStatusBar(XBOFSWinQT5GUIClass); + statusBar->setObjectName(QString::fromUtf8("statusBar")); + XBOFSWinQT5GUIClass->setStatusBar(statusBar); + + retranslateUi(XBOFSWinQT5GUIClass); + + QMetaObject::connectSlotsByName(XBOFSWinQT5GUIClass); + } // setupUi + + void retranslateUi(QMainWindow *XBOFSWinQT5GUIClass) + { + XBOFSWinQT5GUIClass->setWindowTitle(QCoreApplication::translate("XBOFSWinQT5GUIClass", "XBOFSWinQT5GUI", nullptr)); + } // retranslateUi + +}; + +namespace Ui { + class XBOFSWinQT5GUIClass: public Ui_XBOFSWinQT5GUIClass {}; +} // namespace Ui + +QT_END_NAMESPACE + +#endif // UI_XBOFSWINQT5GUI_H diff --git a/XBOFS.win.sln b/XBOFS.win.sln index f492d8d..30a3aba 100644 --- a/XBOFS.win.sln +++ b/XBOFS.win.sln @@ -33,6 +33,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "presets", "presets", "{98CB EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "XBOFS.win.qt5", "XBOFS.win.qt5\XBOFS.win.qt5.vcxproj", "{B12702AD-ABFB-343A-A199-8E24837244A3}" + ProjectSection(ProjectDependencies) = postProject + {F6104731-5815-4BBA-A558-E859DD039413} = {F6104731-5815-4BBA-A558-E859DD039413} + {7DB06674-1F4F-464B-8E1C-172E9587F9DC} = {7DB06674-1F4F-464B-8E1C-172E9587F9DC} + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/XBOFS.win/PDCursesUIManager.cpp b/XBOFS.win/PDCursesUIManager.cpp deleted file mode 100644 index 58c4ef0..0000000 --- a/XBOFS.win/PDCursesUIManager.cpp +++ /dev/null @@ -1,103 +0,0 @@ -#include "PDCursesUIManager.h" - -#include - -PDCursesUIManager::PDCursesUIManager(DWORD parentThreadId) -: Thread("PDCursesUIManager", "PDCursesUIManager", parentThreadId, parentThreadId) -{ -} - -void PDCursesUIManager::wait() -{ - WaitForSingleObject(this->threadHandle, INFINITE); -} - -int PDCursesUIManager::checkMailbox() -{ - MSG message; - int messageCount = 0; - while (PeekMessage(&message, NULL, WM_USER, WM_APP, PM_REMOVE) != FALSE) { - messageCount++; - DWORD threadId = message.wParam; - THREAD_MESSAGES threadMessage = (THREAD_MESSAGES)message.message; - switch (threadMessage) { - case RAWUVEF_WIN_USB_DEVICE_MANAGER_STARTED: - case RAWUVEF_WIN_USB_DEVICE_MANAGER_SCANNING: - case RAWUVEF_WIN_USB_DEVICE_MANAGER_SLEEPING: - case RAWUVEF_WIN_USB_DEVICE_MANAGER_TERMINATING: - case RAWUVEF_WIN_USB_DEVICE_MANAGER_ERROR: - this->winUsbDeviceManagerStatus = threadMessage; - break; - case RAWUVEF_WIN_USB_DEVICE_STARTED: - case RAWUVEF_WIN_USB_DEVICE_VIGEM_CONNECT: - case RAWUVEF_WIN_USB_DEVICE_VIGEM_TARGET_ADD: - case RAWUVEF_WIN_USB_DEVICE_OPEN: - case RAWUVEF_WIN_USB_DEVICE_INIT: - case RAWUVEF_WIN_USB_DEVICE_READ_INPUT: - case RAWUVEF_WIN_USB_DEVICE_TERMINATING: - case RAWUVEF_WIN_USB_DEVICE_ERROR: - if (this->winUsbDeviceStatusMap.find(threadId) == this->winUsbDeviceStatusMap.end()) this->winUsbDeviceThreadIdList.push_back(threadId); - this->winUsbDeviceStatusMap.insert_or_assign(threadId, threadMessage); - break; - case RAWUVEF_STOPPED: - { - if (this->winUsbDeviceStatusMap.find(threadId) == this->winUsbDeviceStatusMap.end()) { - this->logger->warn("Thread %v not in WinUsbDeviceStatusMap", threadId); - break; - } - auto predicate = [threadId](DWORD threadIdB) { return threadId == threadIdB; }; - this->winUsbDeviceThreadIdList.remove_if(predicate); - this->winUsbDeviceStatusMap.erase(threadId); - break; - } - default: - this->logger->warn("Unsupported message: %d", threadMessage); - } - } - return messageCount; -} - -void PDCursesUIManager::render(bool exiting) -{ - erase(); - mvwprintw(this->window, 0, 0, "XBOFS.win v0.3a %s", exiting ? "" : "(Press Q to exit)"); - mvwprintw(this->window, 2, 0, "WinUSB Device Manager (Thread ID %d) status: %s", this->winUsbDeviceManager->getThreadId(), threadMessageToString(this->winUsbDeviceManagerStatus).c_str()); - auto counter = 0; - for (auto threadId : this->winUsbDeviceThreadIdList) { - mvwprintw(this->window, 3 + counter, 0, "WinUSB Device %d (Thread ID %d) status: %s", counter, threadId, threadMessageToString(this->winUsbDeviceStatusMap.at(threadId)).c_str()); - counter++; - } - if (exiting) mvwprintw(this->window, 4 + counter, 0, "Exiting. Waiting for all threads to exit..."); - refresh(); -} - -DWORD PDCursesUIManager::run() -{ - this->logger->info("Started thread for %v", this->identifier); - MSG threadMessage; - bool loop = true; - this->window = initscr(); - nodelay(this->window, TRUE); - noecho(); - this->logger->info("Starting WinUsbDeviceManager"); - this->winUsbDeviceManager = new WinUsbDeviceManager(this->threadId, this->threadId); - this->logger->info("Entering GUI processing loop"); - while (true) { - if (this->checkMailbox() > 0) this->render(false); - auto key = getch(); - if (key == 'Q' || key == 'q') break; - } - this->logger->info("Completed GUI processing loop"); - auto terminateWinUsbDeviceManager = [](LPVOID data) -> DWORD { - WinUsbDeviceManager* winUsbDeviceManager = (WinUsbDeviceManager*)data; - delete winUsbDeviceManager; - return 0; - }; - auto threadHandle = CreateThread(NULL, 0, terminateWinUsbDeviceManager, (LPVOID)this->winUsbDeviceManager, 0, NULL); - this->logger->info("Terminating WinUsbDeviceManager"); - while (WaitForSingleObject(threadHandle, 0) != WAIT_OBJECT_0) { - if (!this->checkMailbox()) break; - this->render(true); - } - return 0; -} diff --git a/XBOFS.win/PDCursesUIManager.h b/XBOFS.win/PDCursesUIManager.h deleted file mode 100644 index 3243513..0000000 --- a/XBOFS.win/PDCursesUIManager.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once -#include "pch.h" -#include "thread.h" -#include "WinUsbDeviceManager.h" - -#include - -class PDCursesUIManager : public Thread -{ -public: - PDCursesUIManager(DWORD parentThreadId); - ~PDCursesUIManager() {}; - - DWORD run(); - void wait(); -protected: - WINDOW *window; - WinUsbDeviceManager *winUsbDeviceManager; - THREAD_MESSAGES winUsbDeviceManagerStatus; - - std::list winUsbDeviceThreadIdList; - std::unordered_map winUsbDeviceStatusMap; - - void render(bool exiting); - int checkMailbox(); -}; - diff --git a/XBOFS.win/XBOFS.win.vcxproj b/XBOFS.win/XBOFS.win.vcxproj index 538c019..c1f5b45 100644 --- a/XBOFS.win/XBOFS.win.vcxproj +++ b/XBOFS.win/XBOFS.win.vcxproj @@ -41,8 +41,6 @@ - - @@ -52,7 +50,6 @@ - diff --git a/XBOFS.win/XBOFS.win.vcxproj.filters b/XBOFS.win/XBOFS.win.vcxproj.filters index c451e5a..0a978b2 100644 --- a/XBOFS.win/XBOFS.win.vcxproj.filters +++ b/XBOFS.win/XBOFS.win.vcxproj.filters @@ -34,9 +34,6 @@ Header Files - - Header Files - Header Files @@ -45,9 +42,6 @@ - - Source Files - Source Files @@ -57,9 +51,6 @@ Source Files - - Source Files - Source Files diff --git a/XBOFS.win/main.cpp b/XBOFS.win/main.cpp deleted file mode 100644 index deae802..0000000 --- a/XBOFS.win/main.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "pch.h" -#include "PDCursesUIManager.h" - -#include -#include -#include -#include - -INITIALIZE_EASYLOGGINGPP - -LONG __cdecl _tmain(LONG Argc, LPTSTR *Argv) { - el::Configurations conf("easyloggingpp.conf"); - el::Loggers::setDefaultConfigurations(conf, true); - el::Logger* logger = el::Loggers::getLogger("RazerAtroxWinUSBVigEmFeeder"); - DWORD threadId = GetCurrentThreadId(); - logger->info("Application start-up. Press enter to exit"); - std::unique_ptr manager(new PDCursesUIManager(threadId)); - manager->wait(); - logger->info("Application shut-down"); -} diff --git a/pre-build.cmd b/pre-build.cmd index 5dd2f37..5248aef 100644 --- a/pre-build.cmd +++ b/pre-build.cmd @@ -5,7 +5,4 @@ git submodule update ECHO Patching VigEmClient CD VigEmClient git apply ..\VigEmClient.patch 2>nul & exit 0 -CD .. -ECHO Building pdcurses -CD PDCurses\wincon -nmake -f Makefile.vc WIDE=Y UTF8=Y +CD .. \ No newline at end of file From 2b94f82580908d69b62c5a604cc29fbe93cccd2f Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Sun, 18 Aug 2019 21:58:11 +0200 Subject: [PATCH 03/50] #2: Updated UI for main window --- XBOFS.win.qt5/XBOFSWinQT5GUI.ui | 143 ++++++++++++++++++++++++++++---- 1 file changed, 125 insertions(+), 18 deletions(-) diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.ui b/XBOFS.win.qt5/XBOFSWinQT5GUI.ui index b5db87c..3231a76 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.ui +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.ui @@ -1,29 +1,136 @@ - + + XBOFSWinQT5GUIClass - - - XBOFSWinQT5GUIClass - - + + 0 0 - 600 - 400 + 655 + 427 - - XBOFSWinQT5GUI + + XBOFS.win - - - - + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + Main + + + + + + Status + + + + + + <html><head/><body><p><span style=" font-weight:600;">VigEmBus Status:</span></p></body></html> + + + + + + + + + + + + + + <html><head/><body><p><span style=" font-weight:600;">WinUSB Device Manager Status:</span></p></body></html> + + + + + + + + + + + + + + + + + Info + + + + + + true + + + + + + + + + + + + + + + + 0 + 0 + 655 + 21 + + + + + File + + + + + + + + TopToolBarArea + + + false + + + + + + Exit + + - - + - + - + From 4581c70c86e57d18852ee751f6cbadef971e818c Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Sun, 18 Aug 2019 21:58:28 +0200 Subject: [PATCH 04/50] #2: Added Widget for device tabs --- XBOFS.win.qt5/WinUsbDeviceWidget.ui | 77 +++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 XBOFS.win.qt5/WinUsbDeviceWidget.ui diff --git a/XBOFS.win.qt5/WinUsbDeviceWidget.ui b/XBOFS.win.qt5/WinUsbDeviceWidget.ui new file mode 100644 index 0000000..878a546 --- /dev/null +++ b/XBOFS.win.qt5/WinUsbDeviceWidget.ui @@ -0,0 +1,77 @@ + + + Form + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + Status + + + + + + <strong>WinUSB Device Status:</strong> + + + + + + + + + + + + + + <strong>VigEmClient Status:</strong> + + + Qt::AutoText + + + + + + + + + + + + + + + + + Info + + + + + + true + + + + + + + + + + + From 2670703b5482a25aa2dda6bc3b01299080e0ba7e Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Sun, 18 Aug 2019 21:59:41 +0200 Subject: [PATCH 05/50] #2: Updated configuration for XBOFS.win project --- XBOFS.win/XBOFS.win.vcxproj | 111 ++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 54 deletions(-) diff --git a/XBOFS.win/XBOFS.win.vcxproj b/XBOFS.win/XBOFS.win.vcxproj index c1f5b45..c69151d 100644 --- a/XBOFS.win/XBOFS.win.vcxproj +++ b/XBOFS.win/XBOFS.win.vcxproj @@ -1,37 +1,37 @@  - - Debug - Win32 + + Debug_LIB + ARM - - Release - Win32 + + Debug_LIB + ARM64 - - Debug - x64 + + Debug_LIB + Win32 - - Release + + Debug_LIB x64 - - Debug - ARM - - - Release + + Release_LIB ARM - - Debug + + Release_LIB ARM64 - - Release - ARM64 + + Release_LIB + Win32 + + + Release_LIB + x64 @@ -68,9 +68,10 @@ Debug Win32 XBOFS.win + $(LatestTargetPlatformVersion) - + Windows10 true WindowsApplicationForDrivers10.0 @@ -78,7 +79,7 @@ Universal Unicode - + Windows10 false WindowsApplicationForDrivers10.0 @@ -86,23 +87,23 @@ Universal Unicode - + Windows10 true WindowsApplicationForDrivers10.0 - Application - Universal + StaticLibrary + Desktop Unicode - + Windows10 false WindowsApplicationForDrivers10.0 - Application - Universal + StaticLibrary + Desktop Unicode - + Windows10 true WindowsApplicationForDrivers10.0 @@ -110,7 +111,7 @@ Universal Unicode - + Windows10 false WindowsApplicationForDrivers10.0 @@ -118,7 +119,7 @@ Universal Unicode - + Windows10 true WindowsApplicationForDrivers10.0 @@ -126,7 +127,7 @@ Universal Unicode - + Windows10 false WindowsApplicationForDrivers10.0 @@ -142,35 +143,37 @@ - + DbgengRemoteDebugger - + DbgengRemoteDebugger - + DbgengRemoteDebugger - $(SolutionDir)ViGEmClient\include;$(SolutionDir)PDCurses;$(IncludePath) + $(SolutionDir)ViGEmClient\include;$(IncludePath) $(SolutionDir)PDCurses\wincon;$(LibraryPath) + $(SolutionDir)lib\debug\$(PlatformShortName)\ - + DbgengRemoteDebugger - $(SolutionDir)ViGEmClient\include;$(SolutionDir)PDCurses;$(IncludePath) + $(SolutionDir)ViGEmClient\include;$(IncludePath) $(SolutionDir)PDCurses\wincon;$(LibraryPath) + $(SolutionDir)lib\release\$(PlatformShortName)\ - + DbgengRemoteDebugger - + DbgengRemoteDebugger - + DbgengRemoteDebugger - + DbgengRemoteDebugger - + _DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) MultiThreadedDebugDLL @@ -179,7 +182,7 @@ %(AdditionalDependencies);onecoreuap.lib;winusb.lib - + WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) @@ -187,14 +190,14 @@ %(AdditionalDependencies);onecoreuap.lib;winusb.lib - + _DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) MultiThreadedDebugDLL false - %(AdditionalDependencies);onecoreuap.lib;winusb.lib;pdcurses.lib + %(AdditionalDependencies);onecoreuap.lib;winusb.lib false @@ -202,14 +205,14 @@ Pre-build tasks - + WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;ELPP_UNICODE;ELPP_THREAD_SAFE;%(PreprocessorDefinitions) false stdcpp17 - %(AdditionalDependencies);onecoreuap.lib;winusb.lib;pdcurses.lib + %(AdditionalDependencies);onecoreuap.lib;winusb.lib false @@ -217,7 +220,7 @@ Pre-build tasks - + _DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) MultiThreadedDebugDLL @@ -226,7 +229,7 @@ %(AdditionalDependencies);onecoreuap.lib;winusb.lib - + WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) @@ -234,7 +237,7 @@ %(AdditionalDependencies);onecoreuap.lib;winusb.lib - + _DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) MultiThreadedDebugDLL @@ -243,7 +246,7 @@ %(AdditionalDependencies);onecoreuap.lib;winusb.lib - + WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) From 6562be388df72b3eaac13c15c80da94a693fdd86 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Sun, 18 Aug 2019 22:00:11 +0200 Subject: [PATCH 06/50] #2: Updated XBOFS.win.qt5 project --- XBOFS.win.qt5/XBOFS.win.qt5.vcxproj | 13 +++++++++++-- XBOFS.win.qt5/XBOFS.win.qt5.vcxproj.filters | 11 +++++++++++ XBOFS.win.qt5/XBOFSWinQT5GUI.cpp | 5 +++-- XBOFS.win.qt5/XBOFSWinQT5GUI.h | 4 ++++ 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj b/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj index ac4cadf..00a6280 100644 --- a/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj +++ b/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj @@ -30,9 +30,13 @@ $(SolutionDir)$(Platform)\$(Configuration)\ + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(SolutionDir)ViGEmClient\include;$(SolutionDir)XBOFS.win + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;$(SolutionDir)lib\release\x64 $(SolutionDir)$(Platform)\$(Configuration)\ + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(SolutionDir)ViGEmClient\include;$(SolutionDir)XBOFS.win + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;$(SolutionDir)lib\debug\x64 @@ -64,7 +68,7 @@ $(OutDir)\$(ProjectName).exe $(QTDIR)\lib;%(AdditionalLibraryDirectories) true - qtmaind.lib;Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;%(AdditionalDependencies) + qtmaind.lib;Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;ViGEmClient.lib;XBOFS.win.lib;%(AdditionalDependencies) .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp @@ -95,7 +99,7 @@ $(OutDir)\$(ProjectName).exe $(QTDIR)\lib;%(AdditionalLibraryDirectories) false - qtmain.lib;Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;%(AdditionalDependencies) + qtmain.lib;Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;ViGEmClient.lib;XBOFS.win.lib;%(AdditionalDependencies) .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp @@ -120,11 +124,16 @@ + + + + + diff --git a/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj.filters b/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj.filters index 323a1ad..c484225 100644 --- a/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj.filters +++ b/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj.filters @@ -37,10 +37,21 @@ Form Files + + Form Files + Resource Files + + + Header Files + + + Header Files + + \ No newline at end of file diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp index 8d1c410..71d0841 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp @@ -1,7 +1,8 @@ #include "XBOFSWinQT5GUI.h" XBOFSWinQT5GUI::XBOFSWinQT5GUI(QWidget *parent) - : QMainWindow(parent) +: QMainWindow(parent) { - ui.setupUi(this); + ui.setupUi(this); + } diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.h b/XBOFS.win.qt5/XBOFSWinQT5GUI.h index 9105f16..bcca989 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.h +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.h @@ -1,8 +1,12 @@ #pragma once +#include +#include + #include #include "ui_XBOFSWinQT5GUI.h" + class XBOFSWinQT5GUI : public QMainWindow { Q_OBJECT From 1ef59f7b3df70d223aa712d94f1e042be78b60a3 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Sun, 18 Aug 2019 22:00:37 +0200 Subject: [PATCH 07/50] #2: Updated solution --- XBOFS.win.sln | 68 +++++++++++---------------------------------------- 1 file changed, 14 insertions(+), 54 deletions(-) diff --git a/XBOFS.win.sln b/XBOFS.win.sln index 30a3aba..0f6378b 100644 --- a/XBOFS.win.sln +++ b/XBOFS.win.sln @@ -41,89 +41,49 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug_DLL|x64 = Debug_DLL|x64 - Debug_DLL|x86 = Debug_DLL|x86 Debug_LIB|x64 = Debug_LIB|x64 - Debug_LIB|x86 = Debug_LIB|x86 Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 Release_DLL|x64 = Release_DLL|x64 - Release_DLL|x86 = Release_DLL|x86 Release_LIB|x64 = Release_LIB|x64 - Release_LIB|x86 = Release_LIB|x86 Release|x64 = Release|x64 - Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F6104731-5815-4BBA-A558-E859DD039413}.Debug_DLL|x64.ActiveCfg = Debug|x64 - {F6104731-5815-4BBA-A558-E859DD039413}.Debug_DLL|x64.Build.0 = Debug|x64 - {F6104731-5815-4BBA-A558-E859DD039413}.Debug_DLL|x86.ActiveCfg = Debug|Win32 - {F6104731-5815-4BBA-A558-E859DD039413}.Debug_DLL|x86.Build.0 = Debug|Win32 - {F6104731-5815-4BBA-A558-E859DD039413}.Debug_LIB|x64.ActiveCfg = Debug|x64 - {F6104731-5815-4BBA-A558-E859DD039413}.Debug_LIB|x64.Build.0 = Debug|x64 - {F6104731-5815-4BBA-A558-E859DD039413}.Debug_LIB|x86.ActiveCfg = Debug|Win32 - {F6104731-5815-4BBA-A558-E859DD039413}.Debug_LIB|x86.Build.0 = Debug|Win32 - {F6104731-5815-4BBA-A558-E859DD039413}.Debug|x64.ActiveCfg = Debug|x64 - {F6104731-5815-4BBA-A558-E859DD039413}.Debug|x64.Build.0 = Debug|x64 - {F6104731-5815-4BBA-A558-E859DD039413}.Debug|x86.ActiveCfg = Debug|Win32 - {F6104731-5815-4BBA-A558-E859DD039413}.Debug|x86.Build.0 = Debug|Win32 - {F6104731-5815-4BBA-A558-E859DD039413}.Release_DLL|x64.ActiveCfg = Release|x64 - {F6104731-5815-4BBA-A558-E859DD039413}.Release_DLL|x64.Build.0 = Release|x64 - {F6104731-5815-4BBA-A558-E859DD039413}.Release_DLL|x86.ActiveCfg = Release|Win32 - {F6104731-5815-4BBA-A558-E859DD039413}.Release_DLL|x86.Build.0 = Release|Win32 - {F6104731-5815-4BBA-A558-E859DD039413}.Release_LIB|x64.ActiveCfg = Release|x64 - {F6104731-5815-4BBA-A558-E859DD039413}.Release_LIB|x64.Build.0 = Release|x64 - {F6104731-5815-4BBA-A558-E859DD039413}.Release_LIB|x86.ActiveCfg = Release|Win32 - {F6104731-5815-4BBA-A558-E859DD039413}.Release_LIB|x86.Build.0 = Release|Win32 - {F6104731-5815-4BBA-A558-E859DD039413}.Release|x64.ActiveCfg = Release|x64 - {F6104731-5815-4BBA-A558-E859DD039413}.Release|x64.Build.0 = Release|x64 - {F6104731-5815-4BBA-A558-E859DD039413}.Release|x86.ActiveCfg = Release|Win32 - {F6104731-5815-4BBA-A558-E859DD039413}.Release|x86.Build.0 = Release|Win32 + {F6104731-5815-4BBA-A558-E859DD039413}.Debug_DLL|x64.ActiveCfg = Debug_LIB|x64 + {F6104731-5815-4BBA-A558-E859DD039413}.Debug_DLL|x64.Build.0 = Debug_LIB|x64 + {F6104731-5815-4BBA-A558-E859DD039413}.Debug_LIB|x64.ActiveCfg = Debug_LIB|x64 + {F6104731-5815-4BBA-A558-E859DD039413}.Debug_LIB|x64.Build.0 = Debug_LIB|x64 + {F6104731-5815-4BBA-A558-E859DD039413}.Debug|x64.ActiveCfg = Debug_LIB|x64 + {F6104731-5815-4BBA-A558-E859DD039413}.Debug|x64.Build.0 = Debug_LIB|x64 + {F6104731-5815-4BBA-A558-E859DD039413}.Release_DLL|x64.ActiveCfg = Release_LIB|x64 + {F6104731-5815-4BBA-A558-E859DD039413}.Release_DLL|x64.Build.0 = Release_LIB|x64 + {F6104731-5815-4BBA-A558-E859DD039413}.Release_LIB|x64.ActiveCfg = Release_LIB|x64 + {F6104731-5815-4BBA-A558-E859DD039413}.Release_LIB|x64.Build.0 = Release_LIB|x64 + {F6104731-5815-4BBA-A558-E859DD039413}.Release|x64.ActiveCfg = Release_LIB|x64 + {F6104731-5815-4BBA-A558-E859DD039413}.Release|x64.Build.0 = Release_LIB|x64 {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug_DLL|x64.ActiveCfg = Debug_DLL|x64 {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug_DLL|x64.Build.0 = Debug_DLL|x64 - {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug_DLL|x86.ActiveCfg = Debug_DLL|Win32 - {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug_DLL|x86.Build.0 = Debug_DLL|Win32 {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug_LIB|x64.ActiveCfg = Debug_LIB|x64 {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug_LIB|x64.Build.0 = Debug_LIB|x64 - {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug_LIB|x86.ActiveCfg = Debug_LIB|Win32 - {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug_LIB|x86.Build.0 = Debug_LIB|Win32 - {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug|x64.ActiveCfg = Debug_DLL|x64 - {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug|x64.Build.0 = Debug_DLL|x64 - {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug|x86.ActiveCfg = Debug_LIB|Win32 - {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug|x86.Build.0 = Debug_LIB|Win32 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug|x64.ActiveCfg = Debug_LIB|x64 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug|x64.Build.0 = Debug_LIB|x64 {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release_DLL|x64.ActiveCfg = Release_DLL|x64 {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release_DLL|x64.Build.0 = Release_DLL|x64 - {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release_DLL|x86.ActiveCfg = Release_DLL|Win32 - {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release_DLL|x86.Build.0 = Release_DLL|Win32 {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release_LIB|x64.ActiveCfg = Release_LIB|x64 {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release_LIB|x64.Build.0 = Release_LIB|x64 - {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release_LIB|x86.ActiveCfg = Release_LIB|Win32 - {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release_LIB|x86.Build.0 = Release_LIB|Win32 {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release|x64.ActiveCfg = Release_LIB|x64 {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release|x64.Build.0 = Release_LIB|x64 - {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release|x86.ActiveCfg = Release_DLL|Win32 - {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release|x86.Build.0 = Release_DLL|Win32 {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug_DLL|x64.ActiveCfg = Debug|x64 {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug_DLL|x64.Build.0 = Debug|x64 - {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug_DLL|x86.ActiveCfg = Release|x64 - {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug_DLL|x86.Build.0 = Release|x64 {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug_LIB|x64.ActiveCfg = Debug|x64 {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug_LIB|x64.Build.0 = Debug|x64 - {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug_LIB|x86.ActiveCfg = Release|x64 - {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug_LIB|x86.Build.0 = Release|x64 {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug|x64.ActiveCfg = Debug|x64 {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug|x64.Build.0 = Debug|x64 - {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug|x86.ActiveCfg = Debug|x64 {B12702AD-ABFB-343A-A199-8E24837244A3}.Release_DLL|x64.ActiveCfg = Release|x64 {B12702AD-ABFB-343A-A199-8E24837244A3}.Release_DLL|x64.Build.0 = Release|x64 - {B12702AD-ABFB-343A-A199-8E24837244A3}.Release_DLL|x86.ActiveCfg = Release|x64 - {B12702AD-ABFB-343A-A199-8E24837244A3}.Release_DLL|x86.Build.0 = Release|x64 {B12702AD-ABFB-343A-A199-8E24837244A3}.Release_LIB|x64.ActiveCfg = Release|x64 {B12702AD-ABFB-343A-A199-8E24837244A3}.Release_LIB|x64.Build.0 = Release|x64 - {B12702AD-ABFB-343A-A199-8E24837244A3}.Release_LIB|x86.ActiveCfg = Release|x64 - {B12702AD-ABFB-343A-A199-8E24837244A3}.Release_LIB|x86.Build.0 = Release|x64 {B12702AD-ABFB-343A-A199-8E24837244A3}.Release|x64.ActiveCfg = Release|x64 {B12702AD-ABFB-343A-A199-8E24837244A3}.Release|x64.Build.0 = Release|x64 - {B12702AD-ABFB-343A-A199-8E24837244A3}.Release|x86.ActiveCfg = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 2bef47d8b56d8cb17ce5c7bbd85081df9dfa4955 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Wed, 21 Aug 2019 18:13:46 +0200 Subject: [PATCH 08/50] #2: Began replacing easyloggingpp with spdlog --- XBOFS.win/Thread.cpp | 12 +- XBOFS.win/Thread.h | 90 +- XBOFS.win/WinUsbDevice.cpp | 59 +- XBOFS.win/WinUsbDevice.h | 57 +- XBOFS.win/WinUsbDeviceManager.cpp | 22 +- XBOFS.win/WinUsbDeviceManager.h | 27 +- XBOFS.win/XBOFS.win.vcxproj | 8 +- XBOFS.win/XBOFS.win.vcxproj.filters | 7 - XBOFS.win/easylogging++.cc | 3112 ------------------ XBOFS.win/easylogging++.h | 4569 --------------------------- XBOFS.win/easyloggingpp.conf | 11 - XBOFS.win/pch.h | 5 +- XBOFS.win/utils.cpp | 23 +- XBOFS.win/utils.h | 8 +- 14 files changed, 173 insertions(+), 7837 deletions(-) delete mode 100644 XBOFS.win/easylogging++.cc delete mode 100644 XBOFS.win/easylogging++.h delete mode 100644 XBOFS.win/easyloggingpp.conf diff --git a/XBOFS.win/Thread.cpp b/XBOFS.win/Thread.cpp index 8ae9a6a..3de3439 100644 --- a/XBOFS.win/Thread.cpp +++ b/XBOFS.win/Thread.cpp @@ -1,5 +1,7 @@ #include "Thread.h" +using namespace XBOFSWin; + std::string threadMessageToString(THREAD_MESSAGES threadMessage) { switch (threadMessage) { @@ -20,21 +22,21 @@ std::string threadMessageToString(THREAD_MESSAGES threadMessage) return "Unknown Thread Message"; } -Thread::Thread(std::string identifier, std::string loggerName, DWORD parentThreadId, DWORD uiManagerThreadId) -: identifier(identifier), logger(el::Loggers::getLogger(loggerName)), parentThreadId(parentThreadId), uiManagerThreadId(uiManagerThreadId) +Thread::Thread(std::string identifier, std::shared_ptr logger, DWORD parentThreadId, DWORD uiManagerThreadId) +: identifier(identifier), logger(logger), parentThreadId(parentThreadId), uiManagerThreadId(uiManagerThreadId) { - this->logger->info("Starting thread for %v", this->identifier); + this->logger->info("Starting thread for {}", this->identifier); this->threadHandle = CreateThread(NULL, 0, startThread, (LPVOID)this, 0, &this->threadId); } Thread::~Thread() { - this->logger->info("Stopping thread for %v", this->identifier); + this->logger->info("Stopping thread for {}", this->identifier); PostThreadMessage(this->threadId, RAWUVEF_STOP, NULL, NULL); while (WaitForSingleObject(this->threadHandle, INFINITE) != WAIT_OBJECT_0); CloseHandle(this->threadHandle); this->notifyUIManager(RAWUVEF_STOPPED, NULL); - this->logger->info("Stopped thread for %v", this->identifier); + this->logger->info("Stopped thread for {}", this->identifier); } DWORD Thread::getThreadId() { diff --git a/XBOFS.win/Thread.h b/XBOFS.win/Thread.h index ebc6f64..aec0b9a 100644 --- a/XBOFS.win/Thread.h +++ b/XBOFS.win/Thread.h @@ -1,48 +1,50 @@ #pragma once #include "pch.h" -enum THREAD_MESSAGES { - RAWUVEF_STOP = WM_USER + 0, - RAWUVEF_STOPPED = WM_USER + 1, - RAWUVEF_WIN_USB_DEVICE_MANAGER_STARTED = WM_USER + 2, - RAWUVEF_WIN_USB_DEVICE_MANAGER_SCANNING = WM_USER + 3, - RAWUVEF_WIN_USB_DEVICE_MANAGER_SLEEPING = WM_USER + 4, - RAWUVEF_WIN_USB_DEVICE_MANAGER_TERMINATING = WM_USER + 5, - RAWUVEF_WIN_USB_DEVICE_MANAGER_ERROR = WM_USER + 6, - RAWUVEF_WIN_USB_DEVICE_STARTED = WM_USER + 7, - RAWUVEF_WIN_USB_DEVICE_VIGEM_CONNECT = WM_USER + 8, - RAWUVEF_WIN_USB_DEVICE_VIGEM_TARGET_ADD = WM_USER + 9, - RAWUVEF_WIN_USB_DEVICE_OPEN = WM_USER + 10, - RAWUVEF_WIN_USB_DEVICE_INIT = WM_USER + 11, - RAWUVEF_WIN_USB_DEVICE_READ_INPUT = WM_USER + 12, - RAWUVEF_WIN_USB_DEVICE_TERMINATING = WM_USER + 13, - RAWUVEF_WIN_USB_DEVICE_ERROR = WM_USER + 14 -}; - -std::string threadMessageToString(THREAD_MESSAGES threadMessage); - -class Thread -{ -public: - Thread() = delete; - Thread(std::string identifier, std::string loggerName, DWORD parentThreadId, DWORD uiManagerThreadId); - ~Thread(); - - virtual DWORD run() = 0; - DWORD getThreadId(); - DWORD getParentThreadId(); - DWORD getUiManagerThreadId(); - -protected: - const std::string identifier; - const DWORD parentThreadId; - const DWORD uiManagerThreadId; - - el::Logger* logger; - DWORD threadId = 0; - HANDLE threadHandle = NULL; - - static DWORD startThread(LPVOID data); - BOOL notifyUIManager(UINT messageValue, LPARAM lParam); -}; +namespace XBOFSWin { + enum THREAD_MESSAGES { + RAWUVEF_STOP = WM_USER + 0, + RAWUVEF_STOPPED = WM_USER + 1, + RAWUVEF_WIN_USB_DEVICE_MANAGER_STARTED = WM_USER + 2, + RAWUVEF_WIN_USB_DEVICE_MANAGER_SCANNING = WM_USER + 3, + RAWUVEF_WIN_USB_DEVICE_MANAGER_SLEEPING = WM_USER + 4, + RAWUVEF_WIN_USB_DEVICE_MANAGER_TERMINATING = WM_USER + 5, + RAWUVEF_WIN_USB_DEVICE_MANAGER_ERROR = WM_USER + 6, + RAWUVEF_WIN_USB_DEVICE_STARTED = WM_USER + 7, + RAWUVEF_WIN_USB_DEVICE_VIGEM_CONNECT = WM_USER + 8, + RAWUVEF_WIN_USB_DEVICE_VIGEM_TARGET_ADD = WM_USER + 9, + RAWUVEF_WIN_USB_DEVICE_OPEN = WM_USER + 10, + RAWUVEF_WIN_USB_DEVICE_INIT = WM_USER + 11, + RAWUVEF_WIN_USB_DEVICE_READ_INPUT = WM_USER + 12, + RAWUVEF_WIN_USB_DEVICE_TERMINATING = WM_USER + 13, + RAWUVEF_WIN_USB_DEVICE_ERROR = WM_USER + 14 + }; + + std::string threadMessageToString(THREAD_MESSAGES threadMessage); + + class Thread + { + public: + Thread() = delete; + Thread(std::string identifier, std::shared_ptr logger, DWORD parentThreadId, DWORD uiManagerThreadId); + ~Thread(); + + virtual DWORD run() = 0; + DWORD getThreadId(); + DWORD getParentThreadId(); + DWORD getUiManagerThreadId(); + + protected: + const std::string identifier; + const DWORD parentThreadId; + const DWORD uiManagerThreadId; + + std::shared_ptr logger; + DWORD threadId = 0; + HANDLE threadHandle = NULL; + + static DWORD startThread(LPVOID data); + BOOL notifyUIManager(UINT messageValue, LPARAM lParam); + }; +} diff --git a/XBOFS.win/WinUsbDevice.cpp b/XBOFS.win/WinUsbDevice.cpp index c2e1b82..80c2e35 100644 --- a/XBOFS.win/WinUsbDevice.cpp +++ b/XBOFS.win/WinUsbDevice.cpp @@ -1,10 +1,11 @@ #include "WinUsbDevice.h" +using namespace XBOFSWin; /* Constructs the WinUsbDevice instance and starts its event loop in a separate thread */ -WinUsbDevice::WinUsbDevice(tstring devicePath, std::string identifier, DWORD parentThreadId, DWORD uiManagerThreadId) -: Thread(identifier, "WinUsbDevice", parentThreadId, uiManagerThreadId), devicePath(devicePath) +WinUsbDevice::WinUsbDevice(tstring devicePath, std::string identifier, std::shared_ptr logger, DWORD parentThreadId, DWORD uiManagerThreadId) +: Thread(identifier, logger, parentThreadId, uiManagerThreadId), devicePath(devicePath) { } @@ -15,48 +16,48 @@ DWORD WinUsbDevice::run() { int failedWrites = 0; MSG threadMessage; this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_STARTED, NULL); - this->logger->info("Started thread for %v", this->identifier); - this->logger->info("Allocating VigEmClient for %v", this->identifier); + this->logger->info("Started thread for {}", this->identifier); + this->logger->info("Allocating VigEmClient for {}", this->identifier); this->vigEmClient = vigem_alloc(); - this->logger->info("Allocating VigEmTarget for %v", this->identifier); + this->logger->info("Allocating VigEmTarget for {}", this->identifier); this->vigEmTarget = vigem_target_x360_alloc(); - this->logger->info("Connecting VigEmClient for %v", this->identifier); + this->logger->info("Connecting VigEmClient for {}", this->identifier); this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_VIGEM_CONNECT, NULL); if (!VIGEM_SUCCESS(vigem_connect(this->vigEmClient))) { this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_ERROR, NULL); - this->logger->error("Unable to connect VigEmClient for %v", this->identifier); + this->logger->error("Unable to connect VigEmClient for {}", this->identifier); loop = false; } this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_VIGEM_TARGET_ADD, NULL); - this->logger->info("Adding VigEmTarget for %v", this->identifier); + this->logger->info("Adding VigEmTarget for {}", this->identifier); if (!VIGEM_SUCCESS(vigem_target_add(this->vigEmClient, this->vigEmTarget))) { this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_ERROR, NULL); - this->logger->error("Unable to add VigEmTarget for %v", this->identifier); + this->logger->error("Unable to add VigEmTarget for {}", this->identifier); loop = false; } // Loop reading input, processing it and dispatching it - this->logger->info("Starting Read-Process-Dispatch loop for %v", this->identifier); + this->logger->info("Starting Read-Process-Dispatch loop for {}", this->identifier); while (loop) { if (PeekMessage(&threadMessage, NULL, WM_USER, WM_APP, PM_REMOVE) == TRUE && threadMessage.message == RAWUVEF_STOP) loop = false; this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_OPEN, NULL); if (!this->openDevice()) { this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_ERROR, NULL); - this->logger->error("Unable to open WinUSB device for %v", this->identifier); + this->logger->error("Unable to open WinUSB device for {}", this->identifier); continue; } this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_INIT, NULL); if (!this->initRazorAtrox()) { this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_ERROR, NULL); - this->logger->error("Unable to init Razer Atrox for %v", this->identifier); + this->logger->error("Unable to init Razer Atrox for {}", this->identifier); continue; } this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_READ_INPUT, NULL); - this->logger->info("Reading input from Razer Atrox for %v", this->identifier); + this->logger->info("Reading input from Razer Atrox for {}", this->identifier); int currentFailedReads = 0; while (loop && currentFailedReads < 5) { if (PeekMessage(&threadMessage, NULL, WM_USER, WM_APP, PM_REMOVE) == TRUE && threadMessage.message == RAWUVEF_STOP) loop = false; if (!this->readInputFromRazerAtrox()) { - this->logger->warn("Failed to read input from Razer Atrox for %v", this->identifier); + this->logger->warn("Failed to read input from Razer Atrox for {}", this->identifier); currentFailedReads += 1; continue; } @@ -65,26 +66,26 @@ DWORD WinUsbDevice::run() { } if (currentFailedReads >= 5) { this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_ERROR, NULL); - this->logger->warn("Failed to read input from Razer Atrox 5 or more times for %v", this->identifier); + this->logger->warn("Failed to read input from Razer Atrox 5 or more times for {}", this->identifier); } failedReads += currentFailedReads; currentFailedReads = 0; } this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_TERMINATING, NULL); - this->logger->info("Completed Read-Process-Dispatch loop for %v", this->identifier); - this->logger->info("There were %v failed reads for %v", failedReads, this->identifier); - this->logger->info("There were %v failed writes for %v", failedWrites, this->identifier); - this->logger->info("Closing WinUSB device for %v", this->identifier); + this->logger->info("Completed Read-Process-Dispatch loop for {}", this->identifier); + this->logger->info("There were {} failed reads for {}", failedReads, this->identifier); + this->logger->info("There were {} failed writes for {}", failedWrites, this->identifier); + this->logger->info("Closing WinUSB device for {}", this->identifier); this->closeDevice(); - this->logger->info("Removing VigEmTarget for %v", this->identifier); + this->logger->info("Removing VigEmTarget for {}", this->identifier); vigem_target_remove(this->vigEmClient, this->vigEmTarget); - this->logger->info("Disconnecting VigEmClient for %v", this->identifier); + this->logger->info("Disconnecting VigEmClient for {}", this->identifier); vigem_disconnect(vigEmClient); - this->logger->info("Free VigEmTarget for %v", this->identifier); + this->logger->info("Free VigEmTarget for {}", this->identifier); vigem_target_free(this->vigEmTarget); - this->logger->info("Free VigEmClient for %v", this->identifier); + this->logger->info("Free VigEmClient for {}", this->identifier); vigem_free(this->vigEmClient); - this->logger->info("Completed thread for %v", this->identifier); + this->logger->info("Completed thread for {}", this->identifier); return 0; } @@ -95,7 +96,7 @@ bool WinUsbDevice::openDevice() { HRESULT hr = S_OK; BOOL bResult; this->deviceHandlesOpen = false; - this->logger->info("Opening WinUSB device for %v", this->identifier); + this->logger->info("Opening WinUSB device for {}", this->identifier); // Attempt to open device handle this->deviceHandle = CreateFile(this->devicePath.c_str(), GENERIC_WRITE | GENERIC_READ, @@ -106,7 +107,7 @@ bool WinUsbDevice::openDevice() { NULL); if (INVALID_HANDLE_VALUE == this->deviceHandle) { hr = HRESULT_FROM_WIN32(GetLastError()); - this->logger->error("Failed to open device handle for %v due to %v", this->identifier, hr); + this->logger->error("Failed to open device handle for {} due to {}", this->identifier, hr); return false; } // Initialize WinUsb handle @@ -114,7 +115,7 @@ bool WinUsbDevice::openDevice() { if (FALSE == bResult) { hr = HRESULT_FROM_WIN32(GetLastError()); CloseHandle(this->deviceHandle); - this->logger->error("Failed to initiallize WinUSB handle for %v due to %v", this->identifier, hr); + this->logger->error("Failed to initiallize WinUSB handle for {} due to {}", this->identifier, hr); return false; } this->deviceHandlesOpen = true; @@ -126,11 +127,11 @@ bool WinUsbDevice::openDevice() { this->winUsbHandle, USB_DEVICE_DESCRIPTOR_TYPE, 0, 0, (PBYTE)&winUsbDeviceDescriptor, sizeof(winUsbDeviceDescriptor), &bytesReceived ); if (winUsbGetDescriptorResult == FALSE || bytesReceived != sizeof(winUsbDeviceDescriptor)) { - this->logger->error("Failed to read USB descriptor for %v", this->identifier); + this->logger->error("Failed to read USB descriptor for {}", this->identifier); this->closeDevice(); return false; } - this->logger->info("Opened WinUSB device for %v", this->identifier); + this->logger->info("Opened WinUSB device for {}", this->identifier); return true; } diff --git a/XBOFS.win/WinUsbDevice.h b/XBOFS.win/WinUsbDevice.h index 72ca2fa..d631241 100644 --- a/XBOFS.win/WinUsbDevice.h +++ b/XBOFS.win/WinUsbDevice.h @@ -4,34 +4,35 @@ #include +namespace XBOFSWin { + /* + */ + class WinUsbDevice : public Thread + { + public: + WinUsbDevice(tstring devicePath, std::string identifier, std::shared_ptr logger, DWORD parentThreadId, DWORD uiManagerThreadId); + ~WinUsbDevice() {}; -/* -*/ -class WinUsbDevice : public Thread -{ -public: - WinUsbDevice(tstring devicePath, std::string identifier, DWORD parentThreadId, DWORD uiManagerThreadId); - ~WinUsbDevice() {}; + DWORD run(void); - DWORD run(void); - -protected: - const tstring devicePath; - - bool deviceHandlesOpen = false; - UCHAR RAZER_ATROX_INIT[5] = { 0x05, 0x20, 0x08, 0x01, 0x05 }; - RAZER_ATROX_DATA_PACKET dataPacket = {}; - RAZER_ATROX_BUTTON_STATE buttonState = {}; - PVIGEM_CLIENT vigEmClient = NULL; - PVIGEM_TARGET vigEmTarget = NULL; - WINUSB_INTERFACE_HANDLE winUsbHandle; - HANDLE deviceHandle; - - bool openDevice(); - bool closeDevice(); - bool initRazorAtrox(); - bool readInputFromRazerAtrox(); - RAZER_ATROX_PACKET_TYPES processInputFromRazerAtrox(); - bool dispatchInputToVigEmController(); -}; + protected: + const tstring devicePath; + + bool deviceHandlesOpen = false; + UCHAR RAZER_ATROX_INIT[5] = { 0x05, 0x20, 0x08, 0x01, 0x05 }; + RAZER_ATROX_DATA_PACKET dataPacket = {}; + RAZER_ATROX_BUTTON_STATE buttonState = {}; + PVIGEM_CLIENT vigEmClient = NULL; + PVIGEM_TARGET vigEmTarget = NULL; + WINUSB_INTERFACE_HANDLE winUsbHandle; + HANDLE deviceHandle; + + bool openDevice(); + bool closeDevice(); + bool initRazorAtrox(); + bool readInputFromRazerAtrox(); + RAZER_ATROX_PACKET_TYPES processInputFromRazerAtrox(); + bool dispatchInputToVigEmController(); + }; +} diff --git a/XBOFS.win/WinUsbDeviceManager.cpp b/XBOFS.win/WinUsbDeviceManager.cpp index d7376f0..63d0d7a 100644 --- a/XBOFS.win/WinUsbDeviceManager.cpp +++ b/XBOFS.win/WinUsbDeviceManager.cpp @@ -1,33 +1,35 @@ #include "WinUsbDeviceManager.h" #include "utils.h"; +using namespace XBOFSWin; /* Constructs the WinUsbDeviceManager and starts its event loop in a separate thread */ -WinUsbDeviceManager::WinUsbDeviceManager(DWORD parentThreadId, DWORD uiManagerThreadId) -: Thread("WinUsbDeviceManager", "WinUsbDeviceManager", parentThreadId, uiManagerThreadId) +WinUsbDeviceManager::WinUsbDeviceManager(std::shared_ptr logger, DWORD parentThreadId, DWORD uiManagerThreadId) +: Thread("WinUsbDeviceManager", logger, parentThreadId, uiManagerThreadId) {} DWORD WinUsbDeviceManager::run() { this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_MANAGER_STARTED, NULL); - this->logger->info("Started thread for %v", this->identifier); + this->logger->info("Started thread for {}", this->identifier); MSG threadMessage; bool loop = true; std::unordered_map devicePathWinUsbDeviceMap; - this->logger->info("Starting scan loop for %v", this->identifier); + this->logger->info("Starting scan loop for {}", this->identifier); while (loop) { this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_MANAGER_SCANNING, NULL); auto devicePaths = this->retrieveDevicePaths(); // Check the updated set for new devicePaths for (auto devicePath : devicePaths) { if (devicePathWinUsbDeviceMap.find(devicePath) != devicePathWinUsbDeviceMap.end()) continue; - this->logger->info("Adding WinUsbDevice at %v", devicePath); + this->logger->info("Adding WinUsbDevice at {}", devicePath); #ifdef UNICODE auto identifier = utf8_encode(devicePath); #else auto identifier = devicePath; #endif // UNICODE - auto winUsbDevice = new WinUsbDevice(devicePath, identifier, this->threadId, this->uiManagerThreadId); + auto logger = setup_logger("WinUsbDevice", "", this->logger->sinks()); + auto winUsbDevice = new WinUsbDevice(devicePath, identifier, logger, this->threadId, this->uiManagerThreadId); devicePathWinUsbDeviceMap.insert({ devicePath, winUsbDevice }); } // Check for WinUsbDevices to remove @@ -43,7 +45,7 @@ DWORD WinUsbDeviceManager::run() { Sleep(1000); } } - this->logger->info("Completed scan loop for %v", this->identifier); + this->logger->info("Completed scan loop for {}", this->identifier); this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_MANAGER_TERMINATING, NULL); for (auto tuple : devicePathWinUsbDeviceMap) delete tuple.second; return 0; @@ -76,7 +78,7 @@ std::set WinUsbDeviceManager::retrieveDevicePaths() { break; } - this->logger->debug("Device interface list size in bytes: %v", deviceInterfaceListSize * sizeof(TCHAR)); + this->logger->debug("Device interface list size in bytes: {}", deviceInterfaceListSize * sizeof(TCHAR)); deviceInterfaceList = (PTSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, @@ -113,10 +115,10 @@ std::set WinUsbDeviceManager::retrieveDevicePaths() { newDevicePaths.insert(devicePath); deviceInterfaceListMarker += devicePathSize + 1; position += devicePathSize + 1; - this->logger->debug("Device interface path detected: %v", devicePath); + this->logger->debug("Device interface path detected: {}", devicePath); } deviceInterfaceListMarker = NULL; - this->logger->debug("%v device interfaces detected", newDevicePaths.size()); + this->logger->debug("{} device interfaces detected", newDevicePaths.size()); } HeapFree(GetProcessHeap(), 0, deviceInterfaceList); return newDevicePaths; diff --git a/XBOFS.win/WinUsbDeviceManager.h b/XBOFS.win/WinUsbDeviceManager.h index fce9a44..66af3aa 100644 --- a/XBOFS.win/WinUsbDeviceManager.h +++ b/XBOFS.win/WinUsbDeviceManager.h @@ -8,16 +8,19 @@ /* */ -class WinUsbDeviceManager : public Thread -{ -public: - WinUsbDeviceManager(DWORD parentThreadId, DWORD uiManagerThreadId); - ~WinUsbDeviceManager() {}; - - DWORD run(); - -protected: - std::set retrieveDevicePaths(); - -}; +namespace XBOFSWin { + + class WinUsbDeviceManager : public Thread + { + public: + WinUsbDeviceManager(std::shared_ptr logger, DWORD parentThreadId, DWORD uiManagerThreadId); + ~WinUsbDeviceManager() {}; + + DWORD run(); + + protected: + std::set retrieveDevicePaths(); + + }; +} diff --git a/XBOFS.win/XBOFS.win.vcxproj b/XBOFS.win/XBOFS.win.vcxproj index c69151d..7a3aede 100644 --- a/XBOFS.win/XBOFS.win.vcxproj +++ b/XBOFS.win/XBOFS.win.vcxproj @@ -35,12 +35,10 @@ - - @@ -48,7 +46,6 @@ - @@ -193,7 +190,7 @@ _DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) - MultiThreadedDebugDLL + MultiThreadedDebug false @@ -207,9 +204,10 @@ - WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;ELPP_UNICODE;ELPP_THREAD_SAFE;%(PreprocessorDefinitions) + WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;BUILD_SHARED_LIBS;%(PreprocessorDefinitions) false stdcpp17 + MultiThreaded %(AdditionalDependencies);onecoreuap.lib;winusb.lib diff --git a/XBOFS.win/XBOFS.win.vcxproj.filters b/XBOFS.win/XBOFS.win.vcxproj.filters index 0a978b2..4210e55 100644 --- a/XBOFS.win/XBOFS.win.vcxproj.filters +++ b/XBOFS.win/XBOFS.win.vcxproj.filters @@ -28,9 +28,6 @@ Header Files - - Header Files - Header Files @@ -45,9 +42,6 @@ Source Files - - Source Files - Source Files @@ -63,6 +57,5 @@ - \ No newline at end of file diff --git a/XBOFS.win/easylogging++.cc b/XBOFS.win/easylogging++.cc deleted file mode 100644 index d763ee7..0000000 --- a/XBOFS.win/easylogging++.cc +++ /dev/null @@ -1,3112 +0,0 @@ -// -// Bismillah ar-Rahmaan ar-Raheem -// -// Easylogging++ v9.96.7 -// Cross-platform logging library for C++ applications -// -// Copyright (c) 2012-2018 Zuhd Web Services -// Copyright (c) 2012-2018 @abumusamq -// -// This library is released under the MIT Licence. -// https://github.com/zuhd-org/easyloggingpp/blob/master/LICENSE -// -// https://zuhd.org -// http://muflihun.com -// - -#include "easylogging++.h" - -#if defined(AUTO_INITIALIZE_EASYLOGGINGPP) -INITIALIZE_EASYLOGGINGPP -#endif - -namespace el { - -// el::base -namespace base { -// el::base::consts -namespace consts { - -// Level log values - These are values that are replaced in place of %level format specifier -// Extra spaces after format specifiers are only for readability purposes in log files -static const base::type::char_t* kInfoLevelLogValue = ELPP_LITERAL("INFO"); -static const base::type::char_t* kDebugLevelLogValue = ELPP_LITERAL("DEBUG"); -static const base::type::char_t* kWarningLevelLogValue = ELPP_LITERAL("WARNING"); -static const base::type::char_t* kErrorLevelLogValue = ELPP_LITERAL("ERROR"); -static const base::type::char_t* kFatalLevelLogValue = ELPP_LITERAL("FATAL"); -static const base::type::char_t* kVerboseLevelLogValue = - ELPP_LITERAL("VERBOSE"); // will become VERBOSE-x where x = verbose level -static const base::type::char_t* kTraceLevelLogValue = ELPP_LITERAL("TRACE"); -static const base::type::char_t* kInfoLevelShortLogValue = ELPP_LITERAL("I"); -static const base::type::char_t* kDebugLevelShortLogValue = ELPP_LITERAL("D"); -static const base::type::char_t* kWarningLevelShortLogValue = ELPP_LITERAL("W"); -static const base::type::char_t* kErrorLevelShortLogValue = ELPP_LITERAL("E"); -static const base::type::char_t* kFatalLevelShortLogValue = ELPP_LITERAL("F"); -static const base::type::char_t* kVerboseLevelShortLogValue = ELPP_LITERAL("V"); -static const base::type::char_t* kTraceLevelShortLogValue = ELPP_LITERAL("T"); -// Format specifiers - These are used to define log format -static const base::type::char_t* kAppNameFormatSpecifier = ELPP_LITERAL("%app"); -static const base::type::char_t* kLoggerIdFormatSpecifier = ELPP_LITERAL("%logger"); -static const base::type::char_t* kThreadIdFormatSpecifier = ELPP_LITERAL("%thread"); -static const base::type::char_t* kSeverityLevelFormatSpecifier = ELPP_LITERAL("%level"); -static const base::type::char_t* kSeverityLevelShortFormatSpecifier = ELPP_LITERAL("%levshort"); -static const base::type::char_t* kDateTimeFormatSpecifier = ELPP_LITERAL("%datetime"); -static const base::type::char_t* kLogFileFormatSpecifier = ELPP_LITERAL("%file"); -static const base::type::char_t* kLogFileBaseFormatSpecifier = ELPP_LITERAL("%fbase"); -static const base::type::char_t* kLogLineFormatSpecifier = ELPP_LITERAL("%line"); -static const base::type::char_t* kLogLocationFormatSpecifier = ELPP_LITERAL("%loc"); -static const base::type::char_t* kLogFunctionFormatSpecifier = ELPP_LITERAL("%func"); -static const base::type::char_t* kCurrentUserFormatSpecifier = ELPP_LITERAL("%user"); -static const base::type::char_t* kCurrentHostFormatSpecifier = ELPP_LITERAL("%host"); -static const base::type::char_t* kMessageFormatSpecifier = ELPP_LITERAL("%msg"); -static const base::type::char_t* kVerboseLevelFormatSpecifier = ELPP_LITERAL("%vlevel"); -static const char* kDateTimeFormatSpecifierForFilename = "%datetime"; -// Date/time -static const char* kDays[7] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; -static const char* kDaysAbbrev[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; -static const char* kMonths[12] = { "January", "February", "March", "Apri", "May", "June", "July", "August", - "September", "October", "November", "December" - }; -static const char* kMonthsAbbrev[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; -static const char* kDefaultDateTimeFormat = "%Y-%M-%d %H:%m:%s,%g"; -static const char* kDefaultDateTimeFormatInFilename = "%Y-%M-%d_%H-%m"; -static const int kYearBase = 1900; -static const char* kAm = "AM"; -static const char* kPm = "PM"; -// Miscellaneous constants - -static const char* kNullPointer = "nullptr"; -#if ELPP_VARIADIC_TEMPLATES_SUPPORTED -#endif // ELPP_VARIADIC_TEMPLATES_SUPPORTED -static const base::type::VerboseLevel kMaxVerboseLevel = 9; -static const char* kUnknownUser = "user"; -static const char* kUnknownHost = "unknown-host"; - - -//---------------- DEFAULT LOG FILE ----------------------- - -#if defined(ELPP_NO_DEFAULT_LOG_FILE) -# if ELPP_OS_UNIX -static const char* kDefaultLogFile = "/dev/null"; -# elif ELPP_OS_WINDOWS -static const char* kDefaultLogFile = "nul"; -# endif // ELPP_OS_UNIX -#elif defined(ELPP_DEFAULT_LOG_FILE) -static const char* kDefaultLogFile = ELPP_DEFAULT_LOG_FILE; -#else -static const char* kDefaultLogFile = "myeasylog.log"; -#endif // defined(ELPP_NO_DEFAULT_LOG_FILE) - - -#if !defined(ELPP_DISABLE_LOG_FILE_FROM_ARG) -static const char* kDefaultLogFileParam = "--default-log-file"; -#endif // !defined(ELPP_DISABLE_LOG_FILE_FROM_ARG) -#if defined(ELPP_LOGGING_FLAGS_FROM_ARG) -static const char* kLoggingFlagsParam = "--logging-flags"; -#endif // defined(ELPP_LOGGING_FLAGS_FROM_ARG) -static const char* kValidLoggerIdSymbols = - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._"; -static const char* kConfigurationComment = "##"; -static const char* kConfigurationLevel = "*"; -static const char* kConfigurationLoggerId = "--"; -} -// el::base::utils -namespace utils { - -/// @brief Aborts application due with user-defined status -static void abort(int status, const std::string& reason) { - // Both status and reason params are there for debugging with tools like gdb etc - ELPP_UNUSED(status); - ELPP_UNUSED(reason); -#if defined(ELPP_COMPILER_MSVC) && defined(_M_IX86) && defined(_DEBUG) - // Ignore msvc critical error dialog - break instead (on debug mode) - _asm int 3 -#else - ::abort(); -#endif // defined(ELPP_COMPILER_MSVC) && defined(_M_IX86) && defined(_DEBUG) -} - -} // namespace utils -} // namespace base - -// el - -// LevelHelper - -const char* LevelHelper::convertToString(Level level) { - // Do not use switch over strongly typed enums because Intel C++ compilers dont support them yet. - if (level == Level::Global) return "GLOBAL"; - if (level == Level::Debug) return "DEBUG"; - if (level == Level::Info) return "INFO"; - if (level == Level::Warning) return "WARNING"; - if (level == Level::Error) return "ERROR"; - if (level == Level::Fatal) return "FATAL"; - if (level == Level::Verbose) return "VERBOSE"; - if (level == Level::Trace) return "TRACE"; - return "UNKNOWN"; -} - -struct StringToLevelItem { - const char* levelString; - Level level; -}; - -static struct StringToLevelItem stringToLevelMap[] = { - { "global", Level::Global }, - { "debug", Level::Debug }, - { "info", Level::Info }, - { "warning", Level::Warning }, - { "error", Level::Error }, - { "fatal", Level::Fatal }, - { "verbose", Level::Verbose }, - { "trace", Level::Trace } -}; - -Level LevelHelper::convertFromString(const char* levelStr) { - for (auto& item : stringToLevelMap) { - if (base::utils::Str::cStringCaseEq(levelStr, item.levelString)) { - return item.level; - } - } - return Level::Unknown; -} - -void LevelHelper::forEachLevel(base::type::EnumType* startIndex, const std::function& fn) { - base::type::EnumType lIndexMax = LevelHelper::kMaxValid; - do { - if (fn()) { - break; - } - *startIndex = static_cast(*startIndex << 1); - } while (*startIndex <= lIndexMax); -} - -// ConfigurationTypeHelper - -const char* ConfigurationTypeHelper::convertToString(ConfigurationType configurationType) { - // Do not use switch over strongly typed enums because Intel C++ compilers dont support them yet. - if (configurationType == ConfigurationType::Enabled) return "ENABLED"; - if (configurationType == ConfigurationType::Filename) return "FILENAME"; - if (configurationType == ConfigurationType::Format) return "FORMAT"; - if (configurationType == ConfigurationType::ToFile) return "TO_FILE"; - if (configurationType == ConfigurationType::ToStandardOutput) return "TO_STANDARD_OUTPUT"; - if (configurationType == ConfigurationType::SubsecondPrecision) return "SUBSECOND_PRECISION"; - if (configurationType == ConfigurationType::PerformanceTracking) return "PERFORMANCE_TRACKING"; - if (configurationType == ConfigurationType::MaxLogFileSize) return "MAX_LOG_FILE_SIZE"; - if (configurationType == ConfigurationType::LogFlushThreshold) return "LOG_FLUSH_THRESHOLD"; - return "UNKNOWN"; -} - -struct ConfigurationStringToTypeItem { - const char* configString; - ConfigurationType configType; -}; - -static struct ConfigurationStringToTypeItem configStringToTypeMap[] = { - { "enabled", ConfigurationType::Enabled }, - { "to_file", ConfigurationType::ToFile }, - { "to_standard_output", ConfigurationType::ToStandardOutput }, - { "format", ConfigurationType::Format }, - { "filename", ConfigurationType::Filename }, - { "subsecond_precision", ConfigurationType::SubsecondPrecision }, - { "milliseconds_width", ConfigurationType::MillisecondsWidth }, - { "performance_tracking", ConfigurationType::PerformanceTracking }, - { "max_log_file_size", ConfigurationType::MaxLogFileSize }, - { "log_flush_threshold", ConfigurationType::LogFlushThreshold }, -}; - -ConfigurationType ConfigurationTypeHelper::convertFromString(const char* configStr) { - for (auto& item : configStringToTypeMap) { - if (base::utils::Str::cStringCaseEq(configStr, item.configString)) { - return item.configType; - } - } - return ConfigurationType::Unknown; -} - -void ConfigurationTypeHelper::forEachConfigType(base::type::EnumType* startIndex, const std::function& fn) { - base::type::EnumType cIndexMax = ConfigurationTypeHelper::kMaxValid; - do { - if (fn()) { - break; - } - *startIndex = static_cast(*startIndex << 1); - } while (*startIndex <= cIndexMax); -} - -// Configuration - -Configuration::Configuration(const Configuration& c) : - m_level(c.m_level), - m_configurationType(c.m_configurationType), - m_value(c.m_value) { -} - -Configuration& Configuration::operator=(const Configuration& c) { - if (&c != this) { - m_level = c.m_level; - m_configurationType = c.m_configurationType; - m_value = c.m_value; - } - return *this; -} - -/// @brief Full constructor used to sets value of configuration -Configuration::Configuration(Level level, ConfigurationType configurationType, const std::string& value) : - m_level(level), - m_configurationType(configurationType), - m_value(value) { -} - -void Configuration::log(el::base::type::ostream_t& os) const { - os << LevelHelper::convertToString(m_level) - << ELPP_LITERAL(" ") << ConfigurationTypeHelper::convertToString(m_configurationType) - << ELPP_LITERAL(" = ") << m_value.c_str(); -} - -/// @brief Used to find configuration from configuration (pointers) repository. Avoid using it. -Configuration::Predicate::Predicate(Level level, ConfigurationType configurationType) : - m_level(level), - m_configurationType(configurationType) { -} - -bool Configuration::Predicate::operator()(const Configuration* conf) const { - return ((conf != nullptr) && (conf->level() == m_level) && (conf->configurationType() == m_configurationType)); -} - -// Configurations - -Configurations::Configurations(void) : - m_configurationFile(std::string()), - m_isFromFile(false) { -} - -Configurations::Configurations(const std::string& configurationFile, bool useDefaultsForRemaining, - Configurations* base) : - m_configurationFile(configurationFile), - m_isFromFile(false) { - parseFromFile(configurationFile, base); - if (useDefaultsForRemaining) { - setRemainingToDefault(); - } -} - -bool Configurations::parseFromFile(const std::string& configurationFile, Configurations* base) { - // We initial assertion with true because if we have assertion diabled, we want to pass this - // check and if assertion is enabled we will have values re-assigned any way. - bool assertionPassed = true; - ELPP_ASSERT((assertionPassed = base::utils::File::pathExists(configurationFile.c_str(), true)) == true, - "Configuration file [" << configurationFile << "] does not exist!"); - if (!assertionPassed) { - return false; - } - bool success = Parser::parseFromFile(configurationFile, this, base); - m_isFromFile = success; - return success; -} - -bool Configurations::parseFromText(const std::string& configurationsString, Configurations* base) { - bool success = Parser::parseFromText(configurationsString, this, base); - if (success) { - m_isFromFile = false; - } - return success; -} - -void Configurations::setFromBase(Configurations* base) { - if (base == nullptr || base == this) { - return; - } - base::threading::ScopedLock scopedLock(base->lock()); - for (Configuration*& conf : base->list()) { - set(conf); - } -} - -bool Configurations::hasConfiguration(ConfigurationType configurationType) { - base::type::EnumType lIndex = LevelHelper::kMinValid; - bool result = false; - LevelHelper::forEachLevel(&lIndex, [&](void) -> bool { - if (hasConfiguration(LevelHelper::castFromInt(lIndex), configurationType)) { - result = true; - } - return result; - }); - return result; -} - -bool Configurations::hasConfiguration(Level level, ConfigurationType configurationType) { - base::threading::ScopedLock scopedLock(lock()); -#if ELPP_COMPILER_INTEL - // We cant specify template types here, Intel C++ throws compilation error - // "error: type name is not allowed" - return RegistryWithPred::get(level, configurationType) != nullptr; -#else - return RegistryWithPred::get(level, configurationType) != nullptr; -#endif // ELPP_COMPILER_INTEL -} - -void Configurations::set(Level level, ConfigurationType configurationType, const std::string& value) { - base::threading::ScopedLock scopedLock(lock()); - unsafeSet(level, configurationType, value); // This is not unsafe anymore as we have locked mutex - if (level == Level::Global) { - unsafeSetGlobally(configurationType, value, false); // Again this is not unsafe either - } -} - -void Configurations::set(Configuration* conf) { - if (conf == nullptr) { - return; - } - set(conf->level(), conf->configurationType(), conf->value()); -} - -void Configurations::setToDefault(void) { - setGlobally(ConfigurationType::Enabled, std::string("true"), true); - setGlobally(ConfigurationType::Filename, std::string(base::consts::kDefaultLogFile), true); -#if defined(ELPP_NO_LOG_TO_FILE) - setGlobally(ConfigurationType::ToFile, std::string("false"), true); -#else - setGlobally(ConfigurationType::ToFile, std::string("true"), true); -#endif // defined(ELPP_NO_LOG_TO_FILE) - setGlobally(ConfigurationType::ToStandardOutput, std::string("true"), true); - setGlobally(ConfigurationType::SubsecondPrecision, std::string("3"), true); - setGlobally(ConfigurationType::PerformanceTracking, std::string("true"), true); - setGlobally(ConfigurationType::MaxLogFileSize, std::string("0"), true); - setGlobally(ConfigurationType::LogFlushThreshold, std::string("0"), true); - - setGlobally(ConfigurationType::Format, std::string("%datetime %level [%logger] %msg"), true); - set(Level::Debug, ConfigurationType::Format, - std::string("%datetime %level [%logger] [%user@%host] [%func] [%loc] %msg")); - // INFO and WARNING are set to default by Level::Global - set(Level::Error, ConfigurationType::Format, std::string("%datetime %level [%logger] %msg")); - set(Level::Fatal, ConfigurationType::Format, std::string("%datetime %level [%logger] %msg")); - set(Level::Verbose, ConfigurationType::Format, std::string("%datetime %level-%vlevel [%logger] %msg")); - set(Level::Trace, ConfigurationType::Format, std::string("%datetime %level [%logger] [%func] [%loc] %msg")); -} - -void Configurations::setRemainingToDefault(void) { - base::threading::ScopedLock scopedLock(lock()); -#if defined(ELPP_NO_LOG_TO_FILE) - unsafeSetIfNotExist(Level::Global, ConfigurationType::Enabled, std::string("false")); -#else - unsafeSetIfNotExist(Level::Global, ConfigurationType::Enabled, std::string("true")); -#endif // defined(ELPP_NO_LOG_TO_FILE) - unsafeSetIfNotExist(Level::Global, ConfigurationType::Filename, std::string(base::consts::kDefaultLogFile)); - unsafeSetIfNotExist(Level::Global, ConfigurationType::ToStandardOutput, std::string("true")); - unsafeSetIfNotExist(Level::Global, ConfigurationType::SubsecondPrecision, std::string("3")); - unsafeSetIfNotExist(Level::Global, ConfigurationType::PerformanceTracking, std::string("true")); - unsafeSetIfNotExist(Level::Global, ConfigurationType::MaxLogFileSize, std::string("0")); - unsafeSetIfNotExist(Level::Global, ConfigurationType::Format, std::string("%datetime %level [%logger] %msg")); - unsafeSetIfNotExist(Level::Debug, ConfigurationType::Format, - std::string("%datetime %level [%logger] [%user@%host] [%func] [%loc] %msg")); - // INFO and WARNING are set to default by Level::Global - unsafeSetIfNotExist(Level::Error, ConfigurationType::Format, std::string("%datetime %level [%logger] %msg")); - unsafeSetIfNotExist(Level::Fatal, ConfigurationType::Format, std::string("%datetime %level [%logger] %msg")); - unsafeSetIfNotExist(Level::Verbose, ConfigurationType::Format, std::string("%datetime %level-%vlevel [%logger] %msg")); - unsafeSetIfNotExist(Level::Trace, ConfigurationType::Format, - std::string("%datetime %level [%logger] [%func] [%loc] %msg")); -} - -bool Configurations::Parser::parseFromFile(const std::string& configurationFile, Configurations* sender, - Configurations* base) { - sender->setFromBase(base); - std::ifstream fileStream_(configurationFile.c_str(), std::ifstream::in); - ELPP_ASSERT(fileStream_.is_open(), "Unable to open configuration file [" << configurationFile << "] for parsing."); - bool parsedSuccessfully = false; - std::string line = std::string(); - Level currLevel = Level::Unknown; - std::string currConfigStr = std::string(); - std::string currLevelStr = std::string(); - while (fileStream_.good()) { - std::getline(fileStream_, line); - parsedSuccessfully = parseLine(&line, &currConfigStr, &currLevelStr, &currLevel, sender); - ELPP_ASSERT(parsedSuccessfully, "Unable to parse configuration line: " << line); - } - return parsedSuccessfully; -} - -bool Configurations::Parser::parseFromText(const std::string& configurationsString, Configurations* sender, - Configurations* base) { - sender->setFromBase(base); - bool parsedSuccessfully = false; - std::stringstream ss(configurationsString); - std::string line = std::string(); - Level currLevel = Level::Unknown; - std::string currConfigStr = std::string(); - std::string currLevelStr = std::string(); - while (std::getline(ss, line)) { - parsedSuccessfully = parseLine(&line, &currConfigStr, &currLevelStr, &currLevel, sender); - ELPP_ASSERT(parsedSuccessfully, "Unable to parse configuration line: " << line); - } - return parsedSuccessfully; -} - -void Configurations::Parser::ignoreComments(std::string* line) { - std::size_t foundAt = 0; - std::size_t quotesStart = line->find("\""); - std::size_t quotesEnd = std::string::npos; - if (quotesStart != std::string::npos) { - quotesEnd = line->find("\"", quotesStart + 1); - while (quotesEnd != std::string::npos && line->at(quotesEnd - 1) == '\\') { - // Do not erase slash yet - we will erase it in parseLine(..) while loop - quotesEnd = line->find("\"", quotesEnd + 2); - } - } - if ((foundAt = line->find(base::consts::kConfigurationComment)) != std::string::npos) { - if (foundAt < quotesEnd) { - foundAt = line->find(base::consts::kConfigurationComment, quotesEnd + 1); - } - *line = line->substr(0, foundAt); - } -} - -bool Configurations::Parser::isLevel(const std::string& line) { - return base::utils::Str::startsWith(line, std::string(base::consts::kConfigurationLevel)); -} - -bool Configurations::Parser::isComment(const std::string& line) { - return base::utils::Str::startsWith(line, std::string(base::consts::kConfigurationComment)); -} - -bool Configurations::Parser::isConfig(const std::string& line) { - std::size_t assignment = line.find('='); - return line != "" && - ((line[0] >= 'A' && line[0] <= 'Z') || (line[0] >= 'a' && line[0] <= 'z')) && - (assignment != std::string::npos) && - (line.size() > assignment); -} - -bool Configurations::Parser::parseLine(std::string* line, std::string* currConfigStr, std::string* currLevelStr, - Level* currLevel, - Configurations* conf) { - ConfigurationType currConfig = ConfigurationType::Unknown; - std::string currValue = std::string(); - *line = base::utils::Str::trim(*line); - if (isComment(*line)) return true; - ignoreComments(line); - *line = base::utils::Str::trim(*line); - if (line->empty()) { - // Comment ignored - return true; - } - if (isLevel(*line)) { - if (line->size() <= 2) { - return true; - } - *currLevelStr = line->substr(1, line->size() - 2); - *currLevelStr = base::utils::Str::toUpper(*currLevelStr); - *currLevelStr = base::utils::Str::trim(*currLevelStr); - *currLevel = LevelHelper::convertFromString(currLevelStr->c_str()); - return true; - } - if (isConfig(*line)) { - std::size_t assignment = line->find('='); - *currConfigStr = line->substr(0, assignment); - *currConfigStr = base::utils::Str::toUpper(*currConfigStr); - *currConfigStr = base::utils::Str::trim(*currConfigStr); - currConfig = ConfigurationTypeHelper::convertFromString(currConfigStr->c_str()); - currValue = line->substr(assignment + 1); - currValue = base::utils::Str::trim(currValue); - std::size_t quotesStart = currValue.find("\"", 0); - std::size_t quotesEnd = std::string::npos; - if (quotesStart != std::string::npos) { - quotesEnd = currValue.find("\"", quotesStart + 1); - while (quotesEnd != std::string::npos && currValue.at(quotesEnd - 1) == '\\') { - currValue = currValue.erase(quotesEnd - 1, 1); - quotesEnd = currValue.find("\"", quotesEnd + 2); - } - } - if (quotesStart != std::string::npos && quotesEnd != std::string::npos) { - // Quote provided - check and strip if valid - ELPP_ASSERT((quotesStart < quotesEnd), "Configuration error - No ending quote found in [" - << currConfigStr << "]"); - ELPP_ASSERT((quotesStart + 1 != quotesEnd), "Empty configuration value for [" << currConfigStr << "]"); - if ((quotesStart != quotesEnd) && (quotesStart + 1 != quotesEnd)) { - // Explicit check in case if assertion is disabled - currValue = currValue.substr(quotesStart + 1, quotesEnd - 1); - } - } - } - ELPP_ASSERT(*currLevel != Level::Unknown, "Unrecognized severity level [" << *currLevelStr << "]"); - ELPP_ASSERT(currConfig != ConfigurationType::Unknown, "Unrecognized configuration [" << *currConfigStr << "]"); - if (*currLevel == Level::Unknown || currConfig == ConfigurationType::Unknown) { - return false; // unrecognizable level or config - } - conf->set(*currLevel, currConfig, currValue); - return true; -} - -void Configurations::unsafeSetIfNotExist(Level level, ConfigurationType configurationType, const std::string& value) { - Configuration* conf = RegistryWithPred::get(level, configurationType); - if (conf == nullptr) { - unsafeSet(level, configurationType, value); - } -} - -void Configurations::unsafeSet(Level level, ConfigurationType configurationType, const std::string& value) { - Configuration* conf = RegistryWithPred::get(level, configurationType); - if (conf == nullptr) { - registerNew(new Configuration(level, configurationType, value)); - } else { - conf->setValue(value); - } - if (level == Level::Global) { - unsafeSetGlobally(configurationType, value, false); - } -} - -void Configurations::setGlobally(ConfigurationType configurationType, const std::string& value, - bool includeGlobalLevel) { - if (includeGlobalLevel) { - set(Level::Global, configurationType, value); - } - base::type::EnumType lIndex = LevelHelper::kMinValid; - LevelHelper::forEachLevel(&lIndex, [&](void) -> bool { - set(LevelHelper::castFromInt(lIndex), configurationType, value); - return false; // Do not break lambda function yet as we need to set all levels regardless - }); -} - -void Configurations::unsafeSetGlobally(ConfigurationType configurationType, const std::string& value, - bool includeGlobalLevel) { - if (includeGlobalLevel) { - unsafeSet(Level::Global, configurationType, value); - } - base::type::EnumType lIndex = LevelHelper::kMinValid; - LevelHelper::forEachLevel(&lIndex, [&](void) -> bool { - unsafeSet(LevelHelper::castFromInt(lIndex), configurationType, value); - return false; // Do not break lambda function yet as we need to set all levels regardless - }); -} - -// LogBuilder - -void LogBuilder::convertToColoredOutput(base::type::string_t* logLine, Level level) { - if (!m_termSupportsColor) return; - const base::type::char_t* resetColor = ELPP_LITERAL("\x1b[0m"); - if (level == Level::Error || level == Level::Fatal) - *logLine = ELPP_LITERAL("\x1b[31m") + *logLine + resetColor; - else if (level == Level::Warning) - *logLine = ELPP_LITERAL("\x1b[33m") + *logLine + resetColor; - else if (level == Level::Debug) - *logLine = ELPP_LITERAL("\x1b[32m") + *logLine + resetColor; - else if (level == Level::Info) - *logLine = ELPP_LITERAL("\x1b[36m") + *logLine + resetColor; - else if (level == Level::Trace) - *logLine = ELPP_LITERAL("\x1b[35m") + *logLine + resetColor; -} - -// Logger - -Logger::Logger(const std::string& id, base::LogStreamsReferenceMap* logStreamsReference) : - m_id(id), - m_typedConfigurations(nullptr), - m_parentApplicationName(std::string()), - m_isConfigured(false), - m_logStreamsReference(logStreamsReference) { - initUnflushedCount(); -} - -Logger::Logger(const std::string& id, const Configurations& configurations, - base::LogStreamsReferenceMap* logStreamsReference) : - m_id(id), - m_typedConfigurations(nullptr), - m_parentApplicationName(std::string()), - m_isConfigured(false), - m_logStreamsReference(logStreamsReference) { - initUnflushedCount(); - configure(configurations); -} - -Logger::Logger(const Logger& logger) { - base::utils::safeDelete(m_typedConfigurations); - m_id = logger.m_id; - m_typedConfigurations = logger.m_typedConfigurations; - m_parentApplicationName = logger.m_parentApplicationName; - m_isConfigured = logger.m_isConfigured; - m_configurations = logger.m_configurations; - m_unflushedCount = logger.m_unflushedCount; - m_logStreamsReference = logger.m_logStreamsReference; -} - -Logger& Logger::operator=(const Logger& logger) { - if (&logger != this) { - base::utils::safeDelete(m_typedConfigurations); - m_id = logger.m_id; - m_typedConfigurations = logger.m_typedConfigurations; - m_parentApplicationName = logger.m_parentApplicationName; - m_isConfigured = logger.m_isConfigured; - m_configurations = logger.m_configurations; - m_unflushedCount = logger.m_unflushedCount; - m_logStreamsReference = logger.m_logStreamsReference; - } - return *this; -} - -void Logger::configure(const Configurations& configurations) { - m_isConfigured = false; // we set it to false in case if we fail - initUnflushedCount(); - if (m_typedConfigurations != nullptr) { - Configurations* c = const_cast(m_typedConfigurations->configurations()); - if (c->hasConfiguration(Level::Global, ConfigurationType::Filename)) { - flush(); - } - } - base::threading::ScopedLock scopedLock(lock()); - if (m_configurations != configurations) { - m_configurations.setFromBase(const_cast(&configurations)); - } - base::utils::safeDelete(m_typedConfigurations); - m_typedConfigurations = new base::TypedConfigurations(&m_configurations, m_logStreamsReference); - resolveLoggerFormatSpec(); - m_isConfigured = true; -} - -void Logger::reconfigure(void) { - ELPP_INTERNAL_INFO(1, "Reconfiguring logger [" << m_id << "]"); - configure(m_configurations); -} - -bool Logger::isValidId(const std::string& id) { - for (std::string::const_iterator it = id.begin(); it != id.end(); ++it) { - if (!base::utils::Str::contains(base::consts::kValidLoggerIdSymbols, *it)) { - return false; - } - } - return true; -} - -void Logger::flush(void) { - ELPP_INTERNAL_INFO(3, "Flushing logger [" << m_id << "] all levels"); - base::threading::ScopedLock scopedLock(lock()); - base::type::EnumType lIndex = LevelHelper::kMinValid; - LevelHelper::forEachLevel(&lIndex, [&](void) -> bool { - flush(LevelHelper::castFromInt(lIndex), nullptr); - return false; - }); -} - -void Logger::flush(Level level, base::type::fstream_t* fs) { - if (fs == nullptr && m_typedConfigurations->toFile(level)) { - fs = m_typedConfigurations->fileStream(level); - } - if (fs != nullptr) { - fs->flush(); - std::unordered_map::iterator iter = m_unflushedCount.find(level); - if (iter != m_unflushedCount.end()) { - iter->second = 0; - } - Helpers::validateFileRolling(this, level); - } -} - -void Logger::initUnflushedCount(void) { - m_unflushedCount.clear(); - base::type::EnumType lIndex = LevelHelper::kMinValid; - LevelHelper::forEachLevel(&lIndex, [&](void) -> bool { - m_unflushedCount.insert(std::make_pair(LevelHelper::castFromInt(lIndex), 0)); - return false; - }); -} - -void Logger::resolveLoggerFormatSpec(void) const { - base::type::EnumType lIndex = LevelHelper::kMinValid; - LevelHelper::forEachLevel(&lIndex, [&](void) -> bool { - base::LogFormat* logFormat = - const_cast(&m_typedConfigurations->logFormat(LevelHelper::castFromInt(lIndex))); - base::utils::Str::replaceFirstWithEscape(logFormat->m_format, base::consts::kLoggerIdFormatSpecifier, m_id); - return false; - }); -} - -// el::base -namespace base { - -// el::base::utils -namespace utils { - -// File - -base::type::fstream_t* File::newFileStream(const std::string& filename) { - base::type::fstream_t *fs = new base::type::fstream_t(filename.c_str(), - base::type::fstream_t::out -#if !defined(ELPP_FRESH_LOG_FILE) - | base::type::fstream_t::app -#endif - ); -#if defined(ELPP_UNICODE) - std::locale elppUnicodeLocale(""); -# if ELPP_OS_WINDOWS - std::locale elppUnicodeLocaleWindows(elppUnicodeLocale, new std::codecvt_utf8_utf16); - elppUnicodeLocale = elppUnicodeLocaleWindows; -# endif // ELPP_OS_WINDOWS - fs->imbue(elppUnicodeLocale); -#endif // defined(ELPP_UNICODE) - if (fs->is_open()) { - fs->flush(); - } else { - base::utils::safeDelete(fs); - ELPP_INTERNAL_ERROR("Bad file [" << filename << "]", true); - } - return fs; -} - -std::size_t File::getSizeOfFile(base::type::fstream_t* fs) { - if (fs == nullptr) { - return 0; - } - // Since the file stream is appended to or truncated, the current - // offset is the file size. - std::size_t size = static_cast(fs->tellg()); - return size; -} - -bool File::pathExists(const char* path, bool considerFile) { - if (path == nullptr) { - return false; - } -#if ELPP_OS_UNIX - ELPP_UNUSED(considerFile); - struct stat st; - return (stat(path, &st) == 0); -#elif ELPP_OS_WINDOWS - DWORD fileType = GetFileAttributesA(path); - if (fileType == INVALID_FILE_ATTRIBUTES) { - return false; - } - return considerFile ? true : ((fileType & FILE_ATTRIBUTE_DIRECTORY) == 0 ? false : true); -#endif // ELPP_OS_UNIX -} - -bool File::createPath(const std::string& path) { - if (path.empty()) { - return false; - } - if (base::utils::File::pathExists(path.c_str())) { - return true; - } - int status = -1; - - char* currPath = const_cast(path.c_str()); - std::string builtPath = std::string(); -#if ELPP_OS_UNIX - if (path[0] == '/') { - builtPath = "/"; - } - currPath = STRTOK(currPath, base::consts::kFilePathSeperator, 0); -#elif ELPP_OS_WINDOWS - // Use secure functions API - char* nextTok_ = nullptr; - currPath = STRTOK(currPath, base::consts::kFilePathSeperator, &nextTok_); - ELPP_UNUSED(nextTok_); -#endif // ELPP_OS_UNIX - while (currPath != nullptr) { - builtPath.append(currPath); - builtPath.append(base::consts::kFilePathSeperator); -#if ELPP_OS_UNIX - status = mkdir(builtPath.c_str(), ELPP_LOG_PERMS); - currPath = STRTOK(nullptr, base::consts::kFilePathSeperator, 0); -#elif ELPP_OS_WINDOWS - status = _mkdir(builtPath.c_str()); - currPath = STRTOK(nullptr, base::consts::kFilePathSeperator, &nextTok_); -#endif // ELPP_OS_UNIX - } - if (status == -1) { - ELPP_INTERNAL_ERROR("Error while creating path [" << path << "]", true); - return false; - } - return true; -} - -std::string File::extractPathFromFilename(const std::string& fullPath, const char* separator) { - if ((fullPath == "") || (fullPath.find(separator) == std::string::npos)) { - return fullPath; - } - std::size_t lastSlashAt = fullPath.find_last_of(separator); - if (lastSlashAt == 0) { - return std::string(separator); - } - return fullPath.substr(0, lastSlashAt + 1); -} - -void File::buildStrippedFilename(const char* filename, char buff[], std::size_t limit) { - std::size_t sizeOfFilename = strlen(filename); - if (sizeOfFilename >= limit) { - filename += (sizeOfFilename - limit); - if (filename[0] != '.' && filename[1] != '.') { // prepend if not already - filename += 3; // 3 = '..' - STRCAT(buff, "..", limit); - } - } - STRCAT(buff, filename, limit); -} - -void File::buildBaseFilename(const std::string& fullPath, char buff[], std::size_t limit, const char* separator) { - const char *filename = fullPath.c_str(); - std::size_t lastSlashAt = fullPath.find_last_of(separator); - filename += lastSlashAt ? lastSlashAt+1 : 0; - std::size_t sizeOfFilename = strlen(filename); - if (sizeOfFilename >= limit) { - filename += (sizeOfFilename - limit); - if (filename[0] != '.' && filename[1] != '.') { // prepend if not already - filename += 3; // 3 = '..' - STRCAT(buff, "..", limit); - } - } - STRCAT(buff, filename, limit); -} - -// Str - -bool Str::wildCardMatch(const char* str, const char* pattern) { - while (*pattern) { - switch (*pattern) { - case '?': - if (!*str) - return false; - ++str; - ++pattern; - break; - case '*': - if (wildCardMatch(str, pattern + 1)) - return true; - if (*str && wildCardMatch(str + 1, pattern)) - return true; - return false; - default: - if (*str++ != *pattern++) - return false; - break; - } - } - return !*str && !*pattern; -} - -std::string& Str::ltrim(std::string& str) { - str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](char c) { - return !std::isspace(c); - } )); - return str; -} - -std::string& Str::rtrim(std::string& str) { - str.erase(std::find_if(str.rbegin(), str.rend(), [](char c) { - return !std::isspace(c); - }).base(), str.end()); - return str; -} - -std::string& Str::trim(std::string& str) { - return ltrim(rtrim(str)); -} - -bool Str::startsWith(const std::string& str, const std::string& start) { - return (str.length() >= start.length()) && (str.compare(0, start.length(), start) == 0); -} - -bool Str::endsWith(const std::string& str, const std::string& end) { - return (str.length() >= end.length()) && (str.compare(str.length() - end.length(), end.length(), end) == 0); -} - -std::string& Str::replaceAll(std::string& str, char replaceWhat, char replaceWith) { - std::replace(str.begin(), str.end(), replaceWhat, replaceWith); - return str; -} - -std::string& Str::replaceAll(std::string& str, const std::string& replaceWhat, - const std::string& replaceWith) { - if (replaceWhat == replaceWith) - return str; - std::size_t foundAt = std::string::npos; - while ((foundAt = str.find(replaceWhat, foundAt + 1)) != std::string::npos) { - str.replace(foundAt, replaceWhat.length(), replaceWith); - } - return str; -} - -void Str::replaceFirstWithEscape(base::type::string_t& str, const base::type::string_t& replaceWhat, - const base::type::string_t& replaceWith) { - std::size_t foundAt = base::type::string_t::npos; - while ((foundAt = str.find(replaceWhat, foundAt + 1)) != base::type::string_t::npos) { - if (foundAt > 0 && str[foundAt - 1] == base::consts::kFormatSpecifierChar) { - str.erase(foundAt - 1, 1); - ++foundAt; - } else { - str.replace(foundAt, replaceWhat.length(), replaceWith); - return; - } - } -} -#if defined(ELPP_UNICODE) -void Str::replaceFirstWithEscape(base::type::string_t& str, const base::type::string_t& replaceWhat, - const std::string& replaceWith) { - replaceFirstWithEscape(str, replaceWhat, base::type::string_t(replaceWith.begin(), replaceWith.end())); -} -#endif // defined(ELPP_UNICODE) - -std::string& Str::toUpper(std::string& str) { - std::transform(str.begin(), str.end(), str.begin(), - [](char c) { - return static_cast(::toupper(c)); - }); - return str; -} - -bool Str::cStringEq(const char* s1, const char* s2) { - if (s1 == nullptr && s2 == nullptr) return true; - if (s1 == nullptr || s2 == nullptr) return false; - return strcmp(s1, s2) == 0; -} - -bool Str::cStringCaseEq(const char* s1, const char* s2) { - if (s1 == nullptr && s2 == nullptr) return true; - if (s1 == nullptr || s2 == nullptr) return false; - - // With thanks to cygwin for this code - int d = 0; - - while (true) { - const int c1 = toupper(*s1++); - const int c2 = toupper(*s2++); - - if (((d = c1 - c2) != 0) || (c2 == '\0')) { - break; - } - } - - return d == 0; -} - -bool Str::contains(const char* str, char c) { - for (; *str; ++str) { - if (*str == c) - return true; - } - return false; -} - -char* Str::convertAndAddToBuff(std::size_t n, int len, char* buf, const char* bufLim, bool zeroPadded) { - char localBuff[10] = ""; - char* p = localBuff + sizeof(localBuff) - 2; - if (n > 0) { - for (; n > 0 && p > localBuff && len > 0; n /= 10, --len) - *--p = static_cast(n % 10 + '0'); - } else { - *--p = '0'; - --len; - } - if (zeroPadded) - while (p > localBuff && len-- > 0) *--p = static_cast('0'); - return addToBuff(p, buf, bufLim); -} - -char* Str::addToBuff(const char* str, char* buf, const char* bufLim) { - while ((buf < bufLim) && ((*buf = *str++) != '\0')) - ++buf; - return buf; -} - -char* Str::clearBuff(char buff[], std::size_t lim) { - STRCPY(buff, "", lim); - ELPP_UNUSED(lim); // For *nix we dont have anything using lim in above STRCPY macro - return buff; -} - -/// @brief Converst wchar* to char* -/// NOTE: Need to free return value after use! -char* Str::wcharPtrToCharPtr(const wchar_t* line) { - std::size_t len_ = wcslen(line) + 1; - char* buff_ = static_cast(malloc(len_ + 1)); -# if ELPP_OS_UNIX || (ELPP_OS_WINDOWS && !ELPP_CRT_DBG_WARNINGS) - std::wcstombs(buff_, line, len_); -# elif ELPP_OS_WINDOWS - std::size_t convCount_ = 0; - mbstate_t mbState_; - ::memset(static_cast(&mbState_), 0, sizeof(mbState_)); - wcsrtombs_s(&convCount_, buff_, len_, &line, len_, &mbState_); -# endif // ELPP_OS_UNIX || (ELPP_OS_WINDOWS && !ELPP_CRT_DBG_WARNINGS) - return buff_; -} - -// OS - -#if ELPP_OS_WINDOWS -/// @brief Gets environment variables for Windows based OS. -/// We are not using getenv(const char*) because of CRT deprecation -/// @param varname Variable name to get environment variable value for -/// @return If variable exist the value of it otherwise nullptr -const char* OS::getWindowsEnvironmentVariable(const char* varname) { - const DWORD bufferLen = 50; - static char buffer[bufferLen]; - if (GetEnvironmentVariableA(varname, buffer, bufferLen)) { - return buffer; - } - return nullptr; -} -#endif // ELPP_OS_WINDOWS -#if ELPP_OS_ANDROID -std::string OS::getProperty(const char* prop) { - char propVal[PROP_VALUE_MAX + 1]; - int ret = __system_property_get(prop, propVal); - return ret == 0 ? std::string() : std::string(propVal); -} - -std::string OS::getDeviceName(void) { - std::stringstream ss; - std::string manufacturer = getProperty("ro.product.manufacturer"); - std::string model = getProperty("ro.product.model"); - if (manufacturer.empty() || model.empty()) { - return std::string(); - } - ss << manufacturer << "-" << model; - return ss.str(); -} -#endif // ELPP_OS_ANDROID - -const std::string OS::getBashOutput(const char* command) { -#if (ELPP_OS_UNIX && !ELPP_OS_ANDROID && !ELPP_CYGWIN) - if (command == nullptr) { - return std::string(); - } - FILE* proc = nullptr; - if ((proc = popen(command, "r")) == nullptr) { - ELPP_INTERNAL_ERROR("\nUnable to run command [" << command << "]", true); - return std::string(); - } - char hBuff[4096]; - if (fgets(hBuff, sizeof(hBuff), proc) != nullptr) { - pclose(proc); - const std::size_t buffLen = strlen(hBuff); - if (buffLen > 0 && hBuff[buffLen - 1] == '\n') { - hBuff[buffLen - 1] = '\0'; - } - return std::string(hBuff); - } else { - pclose(proc); - } - return std::string(); -#else - ELPP_UNUSED(command); - return std::string(); -#endif // (ELPP_OS_UNIX && !ELPP_OS_ANDROID && !ELPP_CYGWIN) -} - -std::string OS::getEnvironmentVariable(const char* variableName, const char* defaultVal, - const char* alternativeBashCommand) { -#if ELPP_OS_UNIX - const char* val = getenv(variableName); -#elif ELPP_OS_WINDOWS - const char* val = getWindowsEnvironmentVariable(variableName); -#endif // ELPP_OS_UNIX - if ((val == nullptr) || ((strcmp(val, "") == 0))) { -#if ELPP_OS_UNIX && defined(ELPP_FORCE_ENV_VAR_FROM_BASH) - // Try harder on unix-based systems - std::string valBash = base::utils::OS::getBashOutput(alternativeBashCommand); - if (valBash.empty()) { - return std::string(defaultVal); - } else { - return valBash; - } -#elif ELPP_OS_WINDOWS || ELPP_OS_UNIX - ELPP_UNUSED(alternativeBashCommand); - return std::string(defaultVal); -#endif // ELPP_OS_UNIX && defined(ELPP_FORCE_ENV_VAR_FROM_BASH) - } - return std::string(val); -} - -std::string OS::currentUser(void) { -#if ELPP_OS_UNIX && !ELPP_OS_ANDROID - return getEnvironmentVariable("USER", base::consts::kUnknownUser, "whoami"); -#elif ELPP_OS_WINDOWS - return getEnvironmentVariable("USERNAME", base::consts::kUnknownUser); -#elif ELPP_OS_ANDROID - ELPP_UNUSED(base::consts::kUnknownUser); - return std::string("android"); -#else - return std::string(); -#endif // ELPP_OS_UNIX && !ELPP_OS_ANDROID -} - -std::string OS::currentHost(void) { -#if ELPP_OS_UNIX && !ELPP_OS_ANDROID - return getEnvironmentVariable("HOSTNAME", base::consts::kUnknownHost, "hostname"); -#elif ELPP_OS_WINDOWS - return getEnvironmentVariable("COMPUTERNAME", base::consts::kUnknownHost); -#elif ELPP_OS_ANDROID - ELPP_UNUSED(base::consts::kUnknownHost); - return getDeviceName(); -#else - return std::string(); -#endif // ELPP_OS_UNIX && !ELPP_OS_ANDROID -} - -bool OS::termSupportsColor(void) { - std::string term = getEnvironmentVariable("TERM", ""); - return term == "xterm" || term == "xterm-color" || term == "xterm-256color" - || term == "screen" || term == "linux" || term == "cygwin" - || term == "screen-256color"; -} - -// DateTime - -void DateTime::gettimeofday(struct timeval* tv) { -#if ELPP_OS_WINDOWS - if (tv != nullptr) { -# if ELPP_COMPILER_MSVC || defined(_MSC_EXTENSIONS) - const unsigned __int64 delta_ = 11644473600000000Ui64; -# else - const unsigned __int64 delta_ = 11644473600000000ULL; -# endif // ELPP_COMPILER_MSVC || defined(_MSC_EXTENSIONS) - const double secOffSet = 0.000001; - const unsigned long usecOffSet = 1000000; - FILETIME fileTime; - GetSystemTimeAsFileTime(&fileTime); - unsigned __int64 present = 0; - present |= fileTime.dwHighDateTime; - present = present << 32; - present |= fileTime.dwLowDateTime; - present /= 10; // mic-sec - // Subtract the difference - present -= delta_; - tv->tv_sec = static_cast(present * secOffSet); - tv->tv_usec = static_cast(present % usecOffSet); - } -#else - ::gettimeofday(tv, nullptr); -#endif // ELPP_OS_WINDOWS -} - -std::string DateTime::getDateTime(const char* format, const base::SubsecondPrecision* ssPrec) { - struct timeval currTime; - gettimeofday(&currTime); - return timevalToString(currTime, format, ssPrec); -} - -std::string DateTime::timevalToString(struct timeval tval, const char* format, - const el::base::SubsecondPrecision* ssPrec) { - struct ::tm timeInfo; - buildTimeInfo(&tval, &timeInfo); - const int kBuffSize = 30; - char buff_[kBuffSize] = ""; - parseFormat(buff_, kBuffSize, format, &timeInfo, static_cast(tval.tv_usec / ssPrec->m_offset), - ssPrec); - return std::string(buff_); -} - -base::type::string_t DateTime::formatTime(unsigned long long time, base::TimestampUnit timestampUnit) { - base::type::EnumType start = static_cast(timestampUnit); - const base::type::char_t* unit = base::consts::kTimeFormats[start].unit; - for (base::type::EnumType i = start; i < base::consts::kTimeFormatsCount - 1; ++i) { - if (time <= base::consts::kTimeFormats[i].value) { - break; - } - if (base::consts::kTimeFormats[i].value == 1000.0f && time / 1000.0f < 1.9f) { - break; - } - time /= static_cast(base::consts::kTimeFormats[i].value); - unit = base::consts::kTimeFormats[i + 1].unit; - } - base::type::stringstream_t ss; - ss << time << " " << unit; - return ss.str(); -} - -unsigned long long DateTime::getTimeDifference(const struct timeval& endTime, const struct timeval& startTime, - base::TimestampUnit timestampUnit) { - if (timestampUnit == base::TimestampUnit::Microsecond) { - return static_cast(static_cast(1000000 * endTime.tv_sec + endTime.tv_usec) - - static_cast(1000000 * startTime.tv_sec + startTime.tv_usec)); - } - // milliseconds - auto conv = [](const struct timeval& tim) { - return static_cast((tim.tv_sec * 1000) + (tim.tv_usec / 1000)); - }; - return static_cast(conv(endTime) - conv(startTime)); -} - -struct ::tm* DateTime::buildTimeInfo(struct timeval* currTime, struct ::tm* timeInfo) { -#if ELPP_OS_UNIX - time_t rawTime = currTime->tv_sec; - ::elpptime_r(&rawTime, timeInfo); - return timeInfo; -#else -# if ELPP_COMPILER_MSVC - ELPP_UNUSED(currTime); - time_t t; -# if defined(_USE_32BIT_TIME_T) - _time32(&t); -# else - _time64(&t); -# endif - elpptime_s(timeInfo, &t); - return timeInfo; -# else - // For any other compilers that don't have CRT warnings issue e.g, MinGW or TDM GCC- we use different method - time_t rawTime = currTime->tv_sec; - struct tm* tmInf = elpptime(&rawTime); - *timeInfo = *tmInf; - return timeInfo; -# endif // ELPP_COMPILER_MSVC -#endif // ELPP_OS_UNIX -} - -char* DateTime::parseFormat(char* buf, std::size_t bufSz, const char* format, const struct tm* tInfo, - std::size_t msec, const base::SubsecondPrecision* ssPrec) { - const char* bufLim = buf + bufSz; - for (; *format; ++format) { - if (*format == base::consts::kFormatSpecifierChar) { - switch (*++format) { - case base::consts::kFormatSpecifierChar: // Escape - break; - case '\0': // End - --format; - break; - case 'd': // Day - buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_mday, 2, buf, bufLim); - continue; - case 'a': // Day of week (short) - buf = base::utils::Str::addToBuff(base::consts::kDaysAbbrev[tInfo->tm_wday], buf, bufLim); - continue; - case 'A': // Day of week (long) - buf = base::utils::Str::addToBuff(base::consts::kDays[tInfo->tm_wday], buf, bufLim); - continue; - case 'M': // month - buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_mon + 1, 2, buf, bufLim); - continue; - case 'b': // month (short) - buf = base::utils::Str::addToBuff(base::consts::kMonthsAbbrev[tInfo->tm_mon], buf, bufLim); - continue; - case 'B': // month (long) - buf = base::utils::Str::addToBuff(base::consts::kMonths[tInfo->tm_mon], buf, bufLim); - continue; - case 'y': // year (two digits) - buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_year + base::consts::kYearBase, 2, buf, bufLim); - continue; - case 'Y': // year (four digits) - buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_year + base::consts::kYearBase, 4, buf, bufLim); - continue; - case 'h': // hour (12-hour) - buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_hour % 12, 2, buf, bufLim); - continue; - case 'H': // hour (24-hour) - buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_hour, 2, buf, bufLim); - continue; - case 'm': // minute - buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_min, 2, buf, bufLim); - continue; - case 's': // second - buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_sec, 2, buf, bufLim); - continue; - case 'z': // subsecond part - case 'g': - buf = base::utils::Str::convertAndAddToBuff(msec, ssPrec->m_width, buf, bufLim); - continue; - case 'F': // AM/PM - buf = base::utils::Str::addToBuff((tInfo->tm_hour >= 12) ? base::consts::kPm : base::consts::kAm, buf, bufLim); - continue; - default: - continue; - } - } - if (buf == bufLim) break; - *buf++ = *format; - } - return buf; -} - -// CommandLineArgs - -void CommandLineArgs::setArgs(int argc, char** argv) { - m_params.clear(); - m_paramsWithValue.clear(); - if (argc == 0 || argv == nullptr) { - return; - } - m_argc = argc; - m_argv = argv; - for (int i = 1; i < m_argc; ++i) { - const char* v = (strstr(m_argv[i], "=")); - if (v != nullptr && strlen(v) > 0) { - std::string key = std::string(m_argv[i]); - key = key.substr(0, key.find_first_of('=')); - if (hasParamWithValue(key.c_str())) { - ELPP_INTERNAL_INFO(1, "Skipping [" << key << "] arg since it already has value [" - << getParamValue(key.c_str()) << "]"); - } else { - m_paramsWithValue.insert(std::make_pair(key, std::string(v + 1))); - } - } - if (v == nullptr) { - if (hasParam(m_argv[i])) { - ELPP_INTERNAL_INFO(1, "Skipping [" << m_argv[i] << "] arg since it already exists"); - } else { - m_params.push_back(std::string(m_argv[i])); - } - } - } -} - -bool CommandLineArgs::hasParamWithValue(const char* paramKey) const { - return m_paramsWithValue.find(std::string(paramKey)) != m_paramsWithValue.end(); -} - -const char* CommandLineArgs::getParamValue(const char* paramKey) const { - std::unordered_map::const_iterator iter = m_paramsWithValue.find(std::string(paramKey)); - return iter != m_paramsWithValue.end() ? iter->second.c_str() : ""; -} - -bool CommandLineArgs::hasParam(const char* paramKey) const { - return std::find(m_params.begin(), m_params.end(), std::string(paramKey)) != m_params.end(); -} - -bool CommandLineArgs::empty(void) const { - return m_params.empty() && m_paramsWithValue.empty(); -} - -std::size_t CommandLineArgs::size(void) const { - return m_params.size() + m_paramsWithValue.size(); -} - -base::type::ostream_t& operator<<(base::type::ostream_t& os, const CommandLineArgs& c) { - for (int i = 1; i < c.m_argc; ++i) { - os << ELPP_LITERAL("[") << c.m_argv[i] << ELPP_LITERAL("]"); - if (i < c.m_argc - 1) { - os << ELPP_LITERAL(" "); - } - } - return os; -} - -} // namespace utils - -// el::base::threading -namespace threading { - -#if ELPP_THREADING_ENABLED -# if ELPP_USE_STD_THREADING -# if ELPP_ASYNC_LOGGING -static void msleep(int ms) { - // Only when async logging enabled - this is because async is strict on compiler -# if defined(ELPP_NO_SLEEP_FOR) - usleep(ms * 1000); -# else - std::this_thread::sleep_for(std::chrono::milliseconds(ms)); -# endif // defined(ELPP_NO_SLEEP_FOR) -} -# endif // ELPP_ASYNC_LOGGING -# endif // !ELPP_USE_STD_THREADING -#endif // ELPP_THREADING_ENABLED - -} // namespace threading - -// el::base - -// SubsecondPrecision - -void SubsecondPrecision::init(int width) { - if (width < 1 || width > 6) { - width = base::consts::kDefaultSubsecondPrecision; - } - m_width = width; - switch (m_width) { - case 3: - m_offset = 1000; - break; - case 4: - m_offset = 100; - break; - case 5: - m_offset = 10; - break; - case 6: - m_offset = 1; - break; - default: - m_offset = 1000; - break; - } -} - -// LogFormat - -LogFormat::LogFormat(void) : - m_level(Level::Unknown), - m_userFormat(base::type::string_t()), - m_format(base::type::string_t()), - m_dateTimeFormat(std::string()), - m_flags(0x0), - m_currentUser(base::utils::OS::currentUser()), - m_currentHost(base::utils::OS::currentHost()) { -} - -LogFormat::LogFormat(Level level, const base::type::string_t& format) - : m_level(level), m_userFormat(format), m_currentUser(base::utils::OS::currentUser()), - m_currentHost(base::utils::OS::currentHost()) { - parseFromFormat(m_userFormat); -} - -LogFormat::LogFormat(const LogFormat& logFormat): - m_level(logFormat.m_level), - m_userFormat(logFormat.m_userFormat), - m_format(logFormat.m_format), - m_dateTimeFormat(logFormat.m_dateTimeFormat), - m_flags(logFormat.m_flags), - m_currentUser(logFormat.m_currentUser), - m_currentHost(logFormat.m_currentHost) { -} - -LogFormat::LogFormat(LogFormat&& logFormat) { - m_level = std::move(logFormat.m_level); - m_userFormat = std::move(logFormat.m_userFormat); - m_format = std::move(logFormat.m_format); - m_dateTimeFormat = std::move(logFormat.m_dateTimeFormat); - m_flags = std::move(logFormat.m_flags); - m_currentUser = std::move(logFormat.m_currentUser); - m_currentHost = std::move(logFormat.m_currentHost); -} - -LogFormat& LogFormat::operator=(const LogFormat& logFormat) { - if (&logFormat != this) { - m_level = logFormat.m_level; - m_userFormat = logFormat.m_userFormat; - m_dateTimeFormat = logFormat.m_dateTimeFormat; - m_flags = logFormat.m_flags; - m_currentUser = logFormat.m_currentUser; - m_currentHost = logFormat.m_currentHost; - } - return *this; -} - -bool LogFormat::operator==(const LogFormat& other) { - return m_level == other.m_level && m_userFormat == other.m_userFormat && m_format == other.m_format && - m_dateTimeFormat == other.m_dateTimeFormat && m_flags == other.m_flags; -} - -/// @brief Updates format to be used while logging. -/// @param userFormat User provided format -void LogFormat::parseFromFormat(const base::type::string_t& userFormat) { - // We make copy because we will be changing the format - // i.e, removing user provided date format from original format - // and then storing it. - base::type::string_t formatCopy = userFormat; - m_flags = 0x0; - auto conditionalAddFlag = [&](const base::type::char_t* specifier, base::FormatFlags flag) { - std::size_t foundAt = base::type::string_t::npos; - while ((foundAt = formatCopy.find(specifier, foundAt + 1)) != base::type::string_t::npos) { - if (foundAt > 0 && formatCopy[foundAt - 1] == base::consts::kFormatSpecifierChar) { - if (hasFlag(flag)) { - // If we already have flag we remove the escape chars so that '%%' is turned to '%' - // even after specifier resolution - this is because we only replaceFirst specifier - formatCopy.erase(foundAt - 1, 1); - ++foundAt; - } - } else { - if (!hasFlag(flag)) addFlag(flag); - } - } - }; - conditionalAddFlag(base::consts::kAppNameFormatSpecifier, base::FormatFlags::AppName); - conditionalAddFlag(base::consts::kSeverityLevelFormatSpecifier, base::FormatFlags::Level); - conditionalAddFlag(base::consts::kSeverityLevelShortFormatSpecifier, base::FormatFlags::LevelShort); - conditionalAddFlag(base::consts::kLoggerIdFormatSpecifier, base::FormatFlags::LoggerId); - conditionalAddFlag(base::consts::kThreadIdFormatSpecifier, base::FormatFlags::ThreadId); - conditionalAddFlag(base::consts::kLogFileFormatSpecifier, base::FormatFlags::File); - conditionalAddFlag(base::consts::kLogFileBaseFormatSpecifier, base::FormatFlags::FileBase); - conditionalAddFlag(base::consts::kLogLineFormatSpecifier, base::FormatFlags::Line); - conditionalAddFlag(base::consts::kLogLocationFormatSpecifier, base::FormatFlags::Location); - conditionalAddFlag(base::consts::kLogFunctionFormatSpecifier, base::FormatFlags::Function); - conditionalAddFlag(base::consts::kCurrentUserFormatSpecifier, base::FormatFlags::User); - conditionalAddFlag(base::consts::kCurrentHostFormatSpecifier, base::FormatFlags::Host); - conditionalAddFlag(base::consts::kMessageFormatSpecifier, base::FormatFlags::LogMessage); - conditionalAddFlag(base::consts::kVerboseLevelFormatSpecifier, base::FormatFlags::VerboseLevel); - // For date/time we need to extract user's date format first - std::size_t dateIndex = std::string::npos; - if ((dateIndex = formatCopy.find(base::consts::kDateTimeFormatSpecifier)) != std::string::npos) { - while (dateIndex > 0 && formatCopy[dateIndex - 1] == base::consts::kFormatSpecifierChar) { - dateIndex = formatCopy.find(base::consts::kDateTimeFormatSpecifier, dateIndex + 1); - } - if (dateIndex != std::string::npos) { - addFlag(base::FormatFlags::DateTime); - updateDateFormat(dateIndex, formatCopy); - } - } - m_format = formatCopy; - updateFormatSpec(); -} - -void LogFormat::updateDateFormat(std::size_t index, base::type::string_t& currFormat) { - if (hasFlag(base::FormatFlags::DateTime)) { - index += ELPP_STRLEN(base::consts::kDateTimeFormatSpecifier); - } - const base::type::char_t* ptr = currFormat.c_str() + index; - if ((currFormat.size() > index) && (ptr[0] == '{')) { - // User has provided format for date/time - ++ptr; - int count = 1; // Start by 1 in order to remove starting brace - std::stringstream ss; - for (; *ptr; ++ptr, ++count) { - if (*ptr == '}') { - ++count; // In order to remove ending brace - break; - } - ss << static_cast(*ptr); - } - currFormat.erase(index, count); - m_dateTimeFormat = ss.str(); - } else { - // No format provided, use default - if (hasFlag(base::FormatFlags::DateTime)) { - m_dateTimeFormat = std::string(base::consts::kDefaultDateTimeFormat); - } - } -} - -void LogFormat::updateFormatSpec(void) { - // Do not use switch over strongly typed enums because Intel C++ compilers dont support them yet. - if (m_level == Level::Debug) { - base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier, - base::consts::kDebugLevelLogValue); - base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier, - base::consts::kDebugLevelShortLogValue); - } else if (m_level == Level::Info) { - base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier, - base::consts::kInfoLevelLogValue); - base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier, - base::consts::kInfoLevelShortLogValue); - } else if (m_level == Level::Warning) { - base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier, - base::consts::kWarningLevelLogValue); - base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier, - base::consts::kWarningLevelShortLogValue); - } else if (m_level == Level::Error) { - base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier, - base::consts::kErrorLevelLogValue); - base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier, - base::consts::kErrorLevelShortLogValue); - } else if (m_level == Level::Fatal) { - base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier, - base::consts::kFatalLevelLogValue); - base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier, - base::consts::kFatalLevelShortLogValue); - } else if (m_level == Level::Verbose) { - base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier, - base::consts::kVerboseLevelLogValue); - base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier, - base::consts::kVerboseLevelShortLogValue); - } else if (m_level == Level::Trace) { - base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier, - base::consts::kTraceLevelLogValue); - base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier, - base::consts::kTraceLevelShortLogValue); - } - if (hasFlag(base::FormatFlags::User)) { - base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kCurrentUserFormatSpecifier, - m_currentUser); - } - if (hasFlag(base::FormatFlags::Host)) { - base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kCurrentHostFormatSpecifier, - m_currentHost); - } - // Ignore Level::Global and Level::Unknown -} - -// TypedConfigurations - -TypedConfigurations::TypedConfigurations(Configurations* configurations, - base::LogStreamsReferenceMap* logStreamsReference) { - m_configurations = configurations; - m_logStreamsReference = logStreamsReference; - build(m_configurations); -} - -TypedConfigurations::TypedConfigurations(const TypedConfigurations& other) { - this->m_configurations = other.m_configurations; - this->m_logStreamsReference = other.m_logStreamsReference; - build(m_configurations); -} - -bool TypedConfigurations::enabled(Level level) { - return getConfigByVal(level, &m_enabledMap, "enabled"); -} - -bool TypedConfigurations::toFile(Level level) { - return getConfigByVal(level, &m_toFileMap, "toFile"); -} - -const std::string& TypedConfigurations::filename(Level level) { - return getConfigByRef(level, &m_filenameMap, "filename"); -} - -bool TypedConfigurations::toStandardOutput(Level level) { - return getConfigByVal(level, &m_toStandardOutputMap, "toStandardOutput"); -} - -const base::LogFormat& TypedConfigurations::logFormat(Level level) { - return getConfigByRef(level, &m_logFormatMap, "logFormat"); -} - -const base::SubsecondPrecision& TypedConfigurations::subsecondPrecision(Level level) { - return getConfigByRef(level, &m_subsecondPrecisionMap, "subsecondPrecision"); -} - -const base::MillisecondsWidth& TypedConfigurations::millisecondsWidth(Level level) { - return getConfigByRef(level, &m_subsecondPrecisionMap, "millisecondsWidth"); -} - -bool TypedConfigurations::performanceTracking(Level level) { - return getConfigByVal(level, &m_performanceTrackingMap, "performanceTracking"); -} - -base::type::fstream_t* TypedConfigurations::fileStream(Level level) { - return getConfigByRef(level, &m_fileStreamMap, "fileStream").get(); -} - -std::size_t TypedConfigurations::maxLogFileSize(Level level) { - return getConfigByVal(level, &m_maxLogFileSizeMap, "maxLogFileSize"); -} - -std::size_t TypedConfigurations::logFlushThreshold(Level level) { - return getConfigByVal(level, &m_logFlushThresholdMap, "logFlushThreshold"); -} - -void TypedConfigurations::build(Configurations* configurations) { - base::threading::ScopedLock scopedLock(lock()); - auto getBool = [] (std::string boolStr) -> bool { // Pass by value for trimming - base::utils::Str::trim(boolStr); - return (boolStr == "TRUE" || boolStr == "true" || boolStr == "1"); - }; - std::vector withFileSizeLimit; - for (Configurations::const_iterator it = configurations->begin(); it != configurations->end(); ++it) { - Configuration* conf = *it; - // We cannot use switch on strong enums because Intel C++ dont support them yet - if (conf->configurationType() == ConfigurationType::Enabled) { - setValue(conf->level(), getBool(conf->value()), &m_enabledMap); - } else if (conf->configurationType() == ConfigurationType::ToFile) { - setValue(conf->level(), getBool(conf->value()), &m_toFileMap); - } else if (conf->configurationType() == ConfigurationType::ToStandardOutput) { - setValue(conf->level(), getBool(conf->value()), &m_toStandardOutputMap); - } else if (conf->configurationType() == ConfigurationType::Filename) { - // We do not yet configure filename but we will configure in another - // loop. This is because if file cannot be created, we will force ToFile - // to be false. Because configuring logger is not necessarily performance - // sensative operation, we can live with another loop; (by the way this loop - // is not very heavy either) - } else if (conf->configurationType() == ConfigurationType::Format) { - setValue(conf->level(), base::LogFormat(conf->level(), - base::type::string_t(conf->value().begin(), conf->value().end())), &m_logFormatMap); - } else if (conf->configurationType() == ConfigurationType::SubsecondPrecision) { - setValue(Level::Global, - base::SubsecondPrecision(static_cast(getULong(conf->value()))), &m_subsecondPrecisionMap); - } else if (conf->configurationType() == ConfigurationType::PerformanceTracking) { - setValue(Level::Global, getBool(conf->value()), &m_performanceTrackingMap); - } else if (conf->configurationType() == ConfigurationType::MaxLogFileSize) { - auto v = getULong(conf->value()); - setValue(conf->level(), static_cast(v), &m_maxLogFileSizeMap); - if (v != 0) { - withFileSizeLimit.push_back(conf); - } - } else if (conf->configurationType() == ConfigurationType::LogFlushThreshold) { - setValue(conf->level(), static_cast(getULong(conf->value())), &m_logFlushThresholdMap); - } - } - // As mentioned earlier, we will now set filename configuration in separate loop to deal with non-existent files - for (Configurations::const_iterator it = configurations->begin(); it != configurations->end(); ++it) { - Configuration* conf = *it; - if (conf->configurationType() == ConfigurationType::Filename) { - insertFile(conf->level(), conf->value()); - } - } - for (std::vector::iterator conf = withFileSizeLimit.begin(); - conf != withFileSizeLimit.end(); ++conf) { - // This is not unsafe as mutex is locked in currect scope - unsafeValidateFileRolling((*conf)->level(), base::defaultPreRollOutCallback); - } -} - -unsigned long TypedConfigurations::getULong(std::string confVal) { - bool valid = true; - base::utils::Str::trim(confVal); - valid = !confVal.empty() && std::find_if(confVal.begin(), confVal.end(), - [](char c) { - return !base::utils::Str::isDigit(c); - }) == confVal.end(); - if (!valid) { - valid = false; - ELPP_ASSERT(valid, "Configuration value not a valid integer [" << confVal << "]"); - return 0; - } - return atol(confVal.c_str()); -} - -std::string TypedConfigurations::resolveFilename(const std::string& filename) { - std::string resultingFilename = filename; - std::size_t dateIndex = std::string::npos; - std::string dateTimeFormatSpecifierStr = std::string(base::consts::kDateTimeFormatSpecifierForFilename); - if ((dateIndex = resultingFilename.find(dateTimeFormatSpecifierStr.c_str())) != std::string::npos) { - while (dateIndex > 0 && resultingFilename[dateIndex - 1] == base::consts::kFormatSpecifierChar) { - dateIndex = resultingFilename.find(dateTimeFormatSpecifierStr.c_str(), dateIndex + 1); - } - if (dateIndex != std::string::npos) { - const char* ptr = resultingFilename.c_str() + dateIndex; - // Goto end of specifier - ptr += dateTimeFormatSpecifierStr.size(); - std::string fmt; - if ((resultingFilename.size() > dateIndex) && (ptr[0] == '{')) { - // User has provided format for date/time - ++ptr; - int count = 1; // Start by 1 in order to remove starting brace - std::stringstream ss; - for (; *ptr; ++ptr, ++count) { - if (*ptr == '}') { - ++count; // In order to remove ending brace - break; - } - ss << *ptr; - } - resultingFilename.erase(dateIndex + dateTimeFormatSpecifierStr.size(), count); - fmt = ss.str(); - } else { - fmt = std::string(base::consts::kDefaultDateTimeFormatInFilename); - } - base::SubsecondPrecision ssPrec(3); - std::string now = base::utils::DateTime::getDateTime(fmt.c_str(), &ssPrec); - base::utils::Str::replaceAll(now, '/', '-'); // Replace path element since we are dealing with filename - base::utils::Str::replaceAll(resultingFilename, dateTimeFormatSpecifierStr, now); - } - } - return resultingFilename; -} - -void TypedConfigurations::insertFile(Level level, const std::string& fullFilename) { - std::string resolvedFilename = resolveFilename(fullFilename); - if (resolvedFilename.empty()) { - std::cerr << "Could not load empty file for logging, please re-check your configurations for level [" - << LevelHelper::convertToString(level) << "]"; - } - std::string filePath = base::utils::File::extractPathFromFilename(resolvedFilename, base::consts::kFilePathSeperator); - if (filePath.size() < resolvedFilename.size()) { - base::utils::File::createPath(filePath); - } - auto create = [&](Level level) { - base::LogStreamsReferenceMap::iterator filestreamIter = m_logStreamsReference->find(resolvedFilename); - base::type::fstream_t* fs = nullptr; - if (filestreamIter == m_logStreamsReference->end()) { - // We need a completely new stream, nothing to share with - fs = base::utils::File::newFileStream(resolvedFilename); - m_filenameMap.insert(std::make_pair(level, resolvedFilename)); - m_fileStreamMap.insert(std::make_pair(level, base::FileStreamPtr(fs))); - m_logStreamsReference->insert(std::make_pair(resolvedFilename, base::FileStreamPtr(m_fileStreamMap.at(level)))); - } else { - // Woops! we have an existing one, share it! - m_filenameMap.insert(std::make_pair(level, filestreamIter->first)); - m_fileStreamMap.insert(std::make_pair(level, base::FileStreamPtr(filestreamIter->second))); - fs = filestreamIter->second.get(); - } - if (fs == nullptr) { - // We display bad file error from newFileStream() - ELPP_INTERNAL_ERROR("Setting [TO_FILE] of [" - << LevelHelper::convertToString(level) << "] to FALSE", false); - setValue(level, false, &m_toFileMap); - } - }; - // If we dont have file conf for any level, create it for Level::Global first - // otherwise create for specified level - create(m_filenameMap.empty() && m_fileStreamMap.empty() ? Level::Global : level); -} - -bool TypedConfigurations::unsafeValidateFileRolling(Level level, const PreRollOutCallback& preRollOutCallback) { - base::type::fstream_t* fs = unsafeGetConfigByRef(level, &m_fileStreamMap, "fileStream").get(); - if (fs == nullptr) { - return true; - } - std::size_t maxLogFileSize = unsafeGetConfigByVal(level, &m_maxLogFileSizeMap, "maxLogFileSize"); - std::size_t currFileSize = base::utils::File::getSizeOfFile(fs); - if (maxLogFileSize != 0 && currFileSize >= maxLogFileSize) { - std::string fname = unsafeGetConfigByRef(level, &m_filenameMap, "filename"); - ELPP_INTERNAL_INFO(1, "Truncating log file [" << fname << "] as a result of configurations for level [" - << LevelHelper::convertToString(level) << "]"); - fs->close(); - preRollOutCallback(fname.c_str(), currFileSize); - fs->open(fname, std::fstream::out | std::fstream::trunc); - return true; - } - return false; -} - -// RegisteredHitCounters - -bool RegisteredHitCounters::validateEveryN(const char* filename, base::type::LineNumber lineNumber, std::size_t n) { - base::threading::ScopedLock scopedLock(lock()); - base::HitCounter* counter = get(filename, lineNumber); - if (counter == nullptr) { - registerNew(counter = new base::HitCounter(filename, lineNumber)); - } - counter->validateHitCounts(n); - bool result = (n >= 1 && counter->hitCounts() != 0 && counter->hitCounts() % n == 0); - return result; -} - -/// @brief Validates counter for hits >= N, i.e, registers new if does not exist otherwise updates original one -/// @return True if validation resulted in triggering hit. Meaning logs should be written everytime true is returned -bool RegisteredHitCounters::validateAfterN(const char* filename, base::type::LineNumber lineNumber, std::size_t n) { - base::threading::ScopedLock scopedLock(lock()); - base::HitCounter* counter = get(filename, lineNumber); - if (counter == nullptr) { - registerNew(counter = new base::HitCounter(filename, lineNumber)); - } - // Do not use validateHitCounts here since we do not want to reset counter here - // Note the >= instead of > because we are incrementing - // after this check - if (counter->hitCounts() >= n) - return true; - counter->increment(); - return false; -} - -/// @brief Validates counter for hits are <= n, i.e, registers new if does not exist otherwise updates original one -/// @return True if validation resulted in triggering hit. Meaning logs should be written everytime true is returned -bool RegisteredHitCounters::validateNTimes(const char* filename, base::type::LineNumber lineNumber, std::size_t n) { - base::threading::ScopedLock scopedLock(lock()); - base::HitCounter* counter = get(filename, lineNumber); - if (counter == nullptr) { - registerNew(counter = new base::HitCounter(filename, lineNumber)); - } - counter->increment(); - // Do not use validateHitCounts here since we do not want to reset counter here - if (counter->hitCounts() <= n) - return true; - return false; -} - -// RegisteredLoggers - -RegisteredLoggers::RegisteredLoggers(const LogBuilderPtr& defaultLogBuilder) : - m_defaultLogBuilder(defaultLogBuilder) { - m_defaultConfigurations.setToDefault(); -} - -Logger* RegisteredLoggers::get(const std::string& id, bool forceCreation) { - base::threading::ScopedLock scopedLock(lock()); - Logger* logger_ = base::utils::Registry::get(id); - if (logger_ == nullptr && forceCreation) { - bool validId = Logger::isValidId(id); - if (!validId) { - ELPP_ASSERT(validId, "Invalid logger ID [" << id << "]. Not registering this logger."); - return nullptr; - } - logger_ = new Logger(id, m_defaultConfigurations, &m_logStreamsReference); - logger_->m_logBuilder = m_defaultLogBuilder; - registerNew(id, logger_); - LoggerRegistrationCallback* callback = nullptr; - for (const std::pair& h - : m_loggerRegistrationCallbacks) { - callback = h.second.get(); - if (callback != nullptr && callback->enabled()) { - callback->handle(logger_); - } - } - } - return logger_; -} - -bool RegisteredLoggers::remove(const std::string& id) { - if (id == base::consts::kDefaultLoggerId) { - return false; - } - // get has internal lock - Logger* logger = base::utils::Registry::get(id); - if (logger != nullptr) { - // unregister has internal lock - unregister(logger); - } - return true; -} - -void RegisteredLoggers::unsafeFlushAll(void) { - ELPP_INTERNAL_INFO(1, "Flushing all log files"); - for (base::LogStreamsReferenceMap::iterator it = m_logStreamsReference.begin(); - it != m_logStreamsReference.end(); ++it) { - if (it->second.get() == nullptr) continue; - it->second->flush(); - } -} - -// VRegistry - -VRegistry::VRegistry(base::type::VerboseLevel level, base::type::EnumType* pFlags) : m_level(level), m_pFlags(pFlags) { -} - -/// @brief Sets verbose level. Accepted range is 0-9 -void VRegistry::setLevel(base::type::VerboseLevel level) { - base::threading::ScopedLock scopedLock(lock()); - if (level > 9) - m_level = base::consts::kMaxVerboseLevel; - else - m_level = level; -} - -void VRegistry::setModules(const char* modules) { - base::threading::ScopedLock scopedLock(lock()); - auto addSuffix = [](std::stringstream& ss, const char* sfx, const char* prev) { - if (prev != nullptr && base::utils::Str::endsWith(ss.str(), std::string(prev))) { - std::string chr(ss.str().substr(0, ss.str().size() - strlen(prev))); - ss.str(std::string("")); - ss << chr; - } - if (base::utils::Str::endsWith(ss.str(), std::string(sfx))) { - std::string chr(ss.str().substr(0, ss.str().size() - strlen(sfx))); - ss.str(std::string("")); - ss << chr; - } - ss << sfx; - }; - auto insert = [&](std::stringstream& ss, base::type::VerboseLevel level) { - if (!base::utils::hasFlag(LoggingFlag::DisableVModulesExtensions, *m_pFlags)) { - addSuffix(ss, ".h", nullptr); - m_modules.insert(std::make_pair(ss.str(), level)); - addSuffix(ss, ".c", ".h"); - m_modules.insert(std::make_pair(ss.str(), level)); - addSuffix(ss, ".cpp", ".c"); - m_modules.insert(std::make_pair(ss.str(), level)); - addSuffix(ss, ".cc", ".cpp"); - m_modules.insert(std::make_pair(ss.str(), level)); - addSuffix(ss, ".cxx", ".cc"); - m_modules.insert(std::make_pair(ss.str(), level)); - addSuffix(ss, ".-inl.h", ".cxx"); - m_modules.insert(std::make_pair(ss.str(), level)); - addSuffix(ss, ".hxx", ".-inl.h"); - m_modules.insert(std::make_pair(ss.str(), level)); - addSuffix(ss, ".hpp", ".hxx"); - m_modules.insert(std::make_pair(ss.str(), level)); - addSuffix(ss, ".hh", ".hpp"); - } - m_modules.insert(std::make_pair(ss.str(), level)); - }; - bool isMod = true; - bool isLevel = false; - std::stringstream ss; - int level = -1; - for (; *modules; ++modules) { - switch (*modules) { - case '=': - isLevel = true; - isMod = false; - break; - case ',': - isLevel = false; - isMod = true; - if (!ss.str().empty() && level != -1) { - insert(ss, static_cast(level)); - ss.str(std::string("")); - level = -1; - } - break; - default: - if (isMod) { - ss << *modules; - } else if (isLevel) { - if (isdigit(*modules)) { - level = static_cast(*modules) - 48; - } - } - break; - } - } - if (!ss.str().empty() && level != -1) { - insert(ss, static_cast(level)); - } -} - -bool VRegistry::allowed(base::type::VerboseLevel vlevel, const char* file) { - base::threading::ScopedLock scopedLock(lock()); - if (m_modules.empty() || file == nullptr) { - return vlevel <= m_level; - } else { - char baseFilename[base::consts::kSourceFilenameMaxLength] = ""; - base::utils::File::buildBaseFilename(file, baseFilename); - std::unordered_map::iterator it = m_modules.begin(); - for (; it != m_modules.end(); ++it) { - if (base::utils::Str::wildCardMatch(baseFilename, it->first.c_str())) { - return vlevel <= it->second; - } - } - if (base::utils::hasFlag(LoggingFlag::AllowVerboseIfModuleNotSpecified, *m_pFlags)) { - return true; - } - return false; - } -} - -void VRegistry::setFromArgs(const base::utils::CommandLineArgs* commandLineArgs) { - if (commandLineArgs->hasParam("-v") || commandLineArgs->hasParam("--verbose") || - commandLineArgs->hasParam("-V") || commandLineArgs->hasParam("--VERBOSE")) { - setLevel(base::consts::kMaxVerboseLevel); - } else if (commandLineArgs->hasParamWithValue("--v")) { - setLevel(static_cast(atoi(commandLineArgs->getParamValue("--v")))); - } else if (commandLineArgs->hasParamWithValue("--V")) { - setLevel(static_cast(atoi(commandLineArgs->getParamValue("--V")))); - } else if ((commandLineArgs->hasParamWithValue("-vmodule")) && vModulesEnabled()) { - setModules(commandLineArgs->getParamValue("-vmodule")); - } else if (commandLineArgs->hasParamWithValue("-VMODULE") && vModulesEnabled()) { - setModules(commandLineArgs->getParamValue("-VMODULE")); - } -} - -#if !defined(ELPP_DEFAULT_LOGGING_FLAGS) -# define ELPP_DEFAULT_LOGGING_FLAGS 0x0 -#endif // !defined(ELPP_DEFAULT_LOGGING_FLAGS) -// Storage -#if ELPP_ASYNC_LOGGING -Storage::Storage(const LogBuilderPtr& defaultLogBuilder, base::IWorker* asyncDispatchWorker) : -#else -Storage::Storage(const LogBuilderPtr& defaultLogBuilder) : -#endif // ELPP_ASYNC_LOGGING - m_registeredHitCounters(new base::RegisteredHitCounters()), - m_registeredLoggers(new base::RegisteredLoggers(defaultLogBuilder)), - m_flags(ELPP_DEFAULT_LOGGING_FLAGS), - m_vRegistry(new base::VRegistry(0, &m_flags)), - -#if ELPP_ASYNC_LOGGING - m_asyncLogQueue(new base::AsyncLogQueue()), - m_asyncDispatchWorker(asyncDispatchWorker), -#endif // ELPP_ASYNC_LOGGING - - m_preRollOutCallback(base::defaultPreRollOutCallback) { - // Register default logger - m_registeredLoggers->get(std::string(base::consts::kDefaultLoggerId)); - // We register default logger anyway (worse case it's not going to register) just in case - m_registeredLoggers->get("default"); - -#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) - // Register performance logger and reconfigure format - Logger* performanceLogger = m_registeredLoggers->get(std::string(base::consts::kPerformanceLoggerId)); - m_registeredLoggers->get("performance"); - performanceLogger->configurations()->setGlobally(ConfigurationType::Format, std::string("%datetime %level %msg")); - performanceLogger->reconfigure(); -#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) - -#if defined(ELPP_SYSLOG) - // Register syslog logger and reconfigure format - Logger* sysLogLogger = m_registeredLoggers->get(std::string(base::consts::kSysLogLoggerId)); - sysLogLogger->configurations()->setGlobally(ConfigurationType::Format, std::string("%level: %msg")); - sysLogLogger->reconfigure(); -#endif // defined(ELPP_SYSLOG) - addFlag(LoggingFlag::AllowVerboseIfModuleNotSpecified); -#if ELPP_ASYNC_LOGGING - installLogDispatchCallback(std::string("AsyncLogDispatchCallback")); -#else - installLogDispatchCallback(std::string("DefaultLogDispatchCallback")); -#endif // ELPP_ASYNC_LOGGING -#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) - installPerformanceTrackingCallback - (std::string("DefaultPerformanceTrackingCallback")); -#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) - ELPP_INTERNAL_INFO(1, "Easylogging++ has been initialized"); -#if ELPP_ASYNC_LOGGING - m_asyncDispatchWorker->start(); -#endif // ELPP_ASYNC_LOGGING -} - -Storage::~Storage(void) { - ELPP_INTERNAL_INFO(4, "Destroying storage"); -#if ELPP_ASYNC_LOGGING - ELPP_INTERNAL_INFO(5, "Replacing log dispatch callback to synchronous"); - uninstallLogDispatchCallback(std::string("AsyncLogDispatchCallback")); - installLogDispatchCallback(std::string("DefaultLogDispatchCallback")); - ELPP_INTERNAL_INFO(5, "Destroying asyncDispatchWorker"); - base::utils::safeDelete(m_asyncDispatchWorker); - ELPP_INTERNAL_INFO(5, "Destroying asyncLogQueue"); - base::utils::safeDelete(m_asyncLogQueue); -#endif // ELPP_ASYNC_LOGGING - ELPP_INTERNAL_INFO(5, "Destroying registeredHitCounters"); - base::utils::safeDelete(m_registeredHitCounters); - ELPP_INTERNAL_INFO(5, "Destroying registeredLoggers"); - base::utils::safeDelete(m_registeredLoggers); - ELPP_INTERNAL_INFO(5, "Destroying vRegistry"); - base::utils::safeDelete(m_vRegistry); -} - -bool Storage::hasCustomFormatSpecifier(const char* formatSpecifier) { - base::threading::ScopedLock scopedLock(customFormatSpecifiersLock()); - return std::find(m_customFormatSpecifiers.begin(), m_customFormatSpecifiers.end(), - formatSpecifier) != m_customFormatSpecifiers.end(); -} - -void Storage::installCustomFormatSpecifier(const CustomFormatSpecifier& customFormatSpecifier) { - if (hasCustomFormatSpecifier(customFormatSpecifier.formatSpecifier())) { - return; - } - base::threading::ScopedLock scopedLock(customFormatSpecifiersLock()); - m_customFormatSpecifiers.push_back(customFormatSpecifier); -} - -bool Storage::uninstallCustomFormatSpecifier(const char* formatSpecifier) { - base::threading::ScopedLock scopedLock(customFormatSpecifiersLock()); - std::vector::iterator it = std::find(m_customFormatSpecifiers.begin(), - m_customFormatSpecifiers.end(), formatSpecifier); - if (it != m_customFormatSpecifiers.end() && strcmp(formatSpecifier, it->formatSpecifier()) == 0) { - m_customFormatSpecifiers.erase(it); - return true; - } - return false; -} - -void Storage::setApplicationArguments(int argc, char** argv) { - m_commandLineArgs.setArgs(argc, argv); - m_vRegistry->setFromArgs(commandLineArgs()); - // default log file -#if !defined(ELPP_DISABLE_LOG_FILE_FROM_ARG) - if (m_commandLineArgs.hasParamWithValue(base::consts::kDefaultLogFileParam)) { - Configurations c; - c.setGlobally(ConfigurationType::Filename, - std::string(m_commandLineArgs.getParamValue(base::consts::kDefaultLogFileParam))); - registeredLoggers()->setDefaultConfigurations(c); - for (base::RegisteredLoggers::iterator it = registeredLoggers()->begin(); - it != registeredLoggers()->end(); ++it) { - it->second->configure(c); - } - } -#endif // !defined(ELPP_DISABLE_LOG_FILE_FROM_ARG) -#if defined(ELPP_LOGGING_FLAGS_FROM_ARG) - if (m_commandLineArgs.hasParamWithValue(base::consts::kLoggingFlagsParam)) { - int userInput = atoi(m_commandLineArgs.getParamValue(base::consts::kLoggingFlagsParam)); - if (ELPP_DEFAULT_LOGGING_FLAGS == 0x0) { - m_flags = userInput; - } else { - base::utils::addFlag(userInput, &m_flags); - } - } -#endif // defined(ELPP_LOGGING_FLAGS_FROM_ARG) -} - -} // namespace base - -// LogDispatchCallback -void LogDispatchCallback::handle(const LogDispatchData* data) { -#if defined(ELPP_THREAD_SAFE) - base::threading::ScopedLock scopedLock(m_fileLocksMapLock); - std::string filename = data->logMessage()->logger()->typedConfigurations()->filename(data->logMessage()->level()); - auto lock = m_fileLocks.find(filename); - if (lock == m_fileLocks.end()) { - m_fileLocks.emplace(std::make_pair(filename, std::unique_ptr(new base::threading::Mutex))); - } -#endif -} - -base::threading::Mutex& LogDispatchCallback::fileHandle(const LogDispatchData* data) { - auto it = m_fileLocks.find(data->logMessage()->logger()->typedConfigurations()->filename(data->logMessage()->level())); - return *(it->second.get()); -} - -namespace base { -// DefaultLogDispatchCallback - -void DefaultLogDispatchCallback::handle(const LogDispatchData* data) { -#if defined(ELPP_THREAD_SAFE) - LogDispatchCallback::handle(data); - base::threading::ScopedLock scopedLock(fileHandle(data)); -#endif - m_data = data; - dispatch(m_data->logMessage()->logger()->logBuilder()->build(m_data->logMessage(), - m_data->dispatchAction() == base::DispatchAction::NormalLog)); -} - -void DefaultLogDispatchCallback::dispatch(base::type::string_t&& logLine) { - if (m_data->dispatchAction() == base::DispatchAction::NormalLog) { - if (m_data->logMessage()->logger()->m_typedConfigurations->toFile(m_data->logMessage()->level())) { - base::type::fstream_t* fs = m_data->logMessage()->logger()->m_typedConfigurations->fileStream( - m_data->logMessage()->level()); - if (fs != nullptr) { - fs->write(logLine.c_str(), logLine.size()); - if (fs->fail()) { - ELPP_INTERNAL_ERROR("Unable to write log to file [" - << m_data->logMessage()->logger()->m_typedConfigurations->filename(m_data->logMessage()->level()) << "].\n" - << "Few possible reasons (could be something else):\n" << " * Permission denied\n" - << " * Disk full\n" << " * Disk is not writable", true); - } else { - if (ELPP->hasFlag(LoggingFlag::ImmediateFlush) - || (m_data->logMessage()->logger()->isFlushNeeded(m_data->logMessage()->level()))) { - m_data->logMessage()->logger()->flush(m_data->logMessage()->level(), fs); - } - } - } else { - ELPP_INTERNAL_ERROR("Log file for [" << LevelHelper::convertToString(m_data->logMessage()->level()) << "] " - << "has not been configured but [TO_FILE] is configured to TRUE. [Logger ID: " - << m_data->logMessage()->logger()->id() << "]", false); - } - } - if (m_data->logMessage()->logger()->m_typedConfigurations->toStandardOutput(m_data->logMessage()->level())) { - if (ELPP->hasFlag(LoggingFlag::ColoredTerminalOutput)) - m_data->logMessage()->logger()->logBuilder()->convertToColoredOutput(&logLine, m_data->logMessage()->level()); - ELPP_COUT << ELPP_COUT_LINE(logLine); - } - } -#if defined(ELPP_SYSLOG) - else if (m_data->dispatchAction() == base::DispatchAction::SysLog) { - // Determine syslog priority - int sysLogPriority = 0; - if (m_data->logMessage()->level() == Level::Fatal) - sysLogPriority = LOG_EMERG; - else if (m_data->logMessage()->level() == Level::Error) - sysLogPriority = LOG_ERR; - else if (m_data->logMessage()->level() == Level::Warning) - sysLogPriority = LOG_WARNING; - else if (m_data->logMessage()->level() == Level::Info) - sysLogPriority = LOG_INFO; - else if (m_data->logMessage()->level() == Level::Debug) - sysLogPriority = LOG_DEBUG; - else - sysLogPriority = LOG_NOTICE; -# if defined(ELPP_UNICODE) - char* line = base::utils::Str::wcharPtrToCharPtr(logLine.c_str()); - syslog(sysLogPriority, "%s", line); - free(line); -# else - syslog(sysLogPriority, "%s", logLine.c_str()); -# endif - } -#endif // defined(ELPP_SYSLOG) -} - -#if ELPP_ASYNC_LOGGING - -// AsyncLogDispatchCallback - -void AsyncLogDispatchCallback::handle(const LogDispatchData* data) { - base::type::string_t logLine = data->logMessage()->logger()->logBuilder()->build(data->logMessage(), - data->dispatchAction() == base::DispatchAction::NormalLog); - if (data->dispatchAction() == base::DispatchAction::NormalLog - && data->logMessage()->logger()->typedConfigurations()->toStandardOutput(data->logMessage()->level())) { - if (ELPP->hasFlag(LoggingFlag::ColoredTerminalOutput)) - data->logMessage()->logger()->logBuilder()->convertToColoredOutput(&logLine, data->logMessage()->level()); - ELPP_COUT << ELPP_COUT_LINE(logLine); - } - // Save resources and only queue if we want to write to file otherwise just ignore handler - if (data->logMessage()->logger()->typedConfigurations()->toFile(data->logMessage()->level())) { - ELPP->asyncLogQueue()->push(AsyncLogItem(*(data->logMessage()), *data, logLine)); - } -} - -// AsyncDispatchWorker -AsyncDispatchWorker::AsyncDispatchWorker() { - setContinueRunning(false); -} - -AsyncDispatchWorker::~AsyncDispatchWorker() { - setContinueRunning(false); - ELPP_INTERNAL_INFO(6, "Stopping dispatch worker - Cleaning log queue"); - clean(); - ELPP_INTERNAL_INFO(6, "Log queue cleaned"); -} - -bool AsyncDispatchWorker::clean(void) { - std::mutex m; - std::unique_lock lk(m); - cv.wait(lk, [] { return !ELPP->asyncLogQueue()->empty(); }); - emptyQueue(); - lk.unlock(); - cv.notify_one(); - return ELPP->asyncLogQueue()->empty(); -} - -void AsyncDispatchWorker::emptyQueue(void) { - while (!ELPP->asyncLogQueue()->empty()) { - AsyncLogItem data = ELPP->asyncLogQueue()->next(); - handle(&data); - base::threading::msleep(100); - } -} - -void AsyncDispatchWorker::start(void) { - base::threading::msleep(5000); // 5s (why?) - setContinueRunning(true); - std::thread t1(&AsyncDispatchWorker::run, this); - t1.join(); -} - -void AsyncDispatchWorker::handle(AsyncLogItem* logItem) { - LogDispatchData* data = logItem->data(); - LogMessage* logMessage = logItem->logMessage(); - Logger* logger = logMessage->logger(); - base::TypedConfigurations* conf = logger->typedConfigurations(); - base::type::string_t logLine = logItem->logLine(); - if (data->dispatchAction() == base::DispatchAction::NormalLog) { - if (conf->toFile(logMessage->level())) { - base::type::fstream_t* fs = conf->fileStream(logMessage->level()); - if (fs != nullptr) { - fs->write(logLine.c_str(), logLine.size()); - if (fs->fail()) { - ELPP_INTERNAL_ERROR("Unable to write log to file [" - << conf->filename(logMessage->level()) << "].\n" - << "Few possible reasons (could be something else):\n" << " * Permission denied\n" - << " * Disk full\n" << " * Disk is not writable", true); - } else { - if (ELPP->hasFlag(LoggingFlag::ImmediateFlush) || (logger->isFlushNeeded(logMessage->level()))) { - logger->flush(logMessage->level(), fs); - } - } - } else { - ELPP_INTERNAL_ERROR("Log file for [" << LevelHelper::convertToString(logMessage->level()) << "] " - << "has not been configured but [TO_FILE] is configured to TRUE. [Logger ID: " << logger->id() << "]", false); - } - } - } -# if defined(ELPP_SYSLOG) - else if (data->dispatchAction() == base::DispatchAction::SysLog) { - // Determine syslog priority - int sysLogPriority = 0; - if (logMessage->level() == Level::Fatal) - sysLogPriority = LOG_EMERG; - else if (logMessage->level() == Level::Error) - sysLogPriority = LOG_ERR; - else if (logMessage->level() == Level::Warning) - sysLogPriority = LOG_WARNING; - else if (logMessage->level() == Level::Info) - sysLogPriority = LOG_INFO; - else if (logMessage->level() == Level::Debug) - sysLogPriority = LOG_DEBUG; - else - sysLogPriority = LOG_NOTICE; -# if defined(ELPP_UNICODE) - char* line = base::utils::Str::wcharPtrToCharPtr(logLine.c_str()); - syslog(sysLogPriority, "%s", line); - free(line); -# else - syslog(sysLogPriority, "%s", logLine.c_str()); -# endif - } -# endif // defined(ELPP_SYSLOG) -} - -void AsyncDispatchWorker::run(void) { - while (continueRunning()) { - emptyQueue(); - base::threading::msleep(10); // 10ms - } -} -#endif // ELPP_ASYNC_LOGGING - -// DefaultLogBuilder - -base::type::string_t DefaultLogBuilder::build(const LogMessage* logMessage, bool appendNewLine) const { - base::TypedConfigurations* tc = logMessage->logger()->typedConfigurations(); - const base::LogFormat* logFormat = &tc->logFormat(logMessage->level()); - base::type::string_t logLine = logFormat->format(); - char buff[base::consts::kSourceFilenameMaxLength + base::consts::kSourceLineMaxLength] = ""; - const char* bufLim = buff + sizeof(buff); - if (logFormat->hasFlag(base::FormatFlags::AppName)) { - // App name - base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kAppNameFormatSpecifier, - logMessage->logger()->parentApplicationName()); - } - if (logFormat->hasFlag(base::FormatFlags::ThreadId)) { - // Thread ID - base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kThreadIdFormatSpecifier, - ELPP->getThreadName(base::threading::getCurrentThreadId())); - } - if (logFormat->hasFlag(base::FormatFlags::DateTime)) { - // DateTime - base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kDateTimeFormatSpecifier, - base::utils::DateTime::getDateTime(logFormat->dateTimeFormat().c_str(), - &tc->subsecondPrecision(logMessage->level()))); - } - if (logFormat->hasFlag(base::FormatFlags::Function)) { - // Function - base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kLogFunctionFormatSpecifier, logMessage->func()); - } - if (logFormat->hasFlag(base::FormatFlags::File)) { - // File - base::utils::Str::clearBuff(buff, base::consts::kSourceFilenameMaxLength); - base::utils::File::buildStrippedFilename(logMessage->file().c_str(), buff); - base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kLogFileFormatSpecifier, std::string(buff)); - } - if (logFormat->hasFlag(base::FormatFlags::FileBase)) { - // FileBase - base::utils::Str::clearBuff(buff, base::consts::kSourceFilenameMaxLength); - base::utils::File::buildBaseFilename(logMessage->file(), buff); - base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kLogFileBaseFormatSpecifier, std::string(buff)); - } - if (logFormat->hasFlag(base::FormatFlags::Line)) { - // Line - char* buf = base::utils::Str::clearBuff(buff, base::consts::kSourceLineMaxLength); - buf = base::utils::Str::convertAndAddToBuff(logMessage->line(), base::consts::kSourceLineMaxLength, buf, bufLim, false); - base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kLogLineFormatSpecifier, std::string(buff)); - } - if (logFormat->hasFlag(base::FormatFlags::Location)) { - // Location - char* buf = base::utils::Str::clearBuff(buff, - base::consts::kSourceFilenameMaxLength + base::consts::kSourceLineMaxLength); - base::utils::File::buildStrippedFilename(logMessage->file().c_str(), buff); - buf = base::utils::Str::addToBuff(buff, buf, bufLim); - buf = base::utils::Str::addToBuff(":", buf, bufLim); - buf = base::utils::Str::convertAndAddToBuff(logMessage->line(), base::consts::kSourceLineMaxLength, buf, bufLim, - false); - base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kLogLocationFormatSpecifier, std::string(buff)); - } - if (logMessage->level() == Level::Verbose && logFormat->hasFlag(base::FormatFlags::VerboseLevel)) { - // Verbose level - char* buf = base::utils::Str::clearBuff(buff, 1); - buf = base::utils::Str::convertAndAddToBuff(logMessage->verboseLevel(), 1, buf, bufLim, false); - base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kVerboseLevelFormatSpecifier, std::string(buff)); - } - if (logFormat->hasFlag(base::FormatFlags::LogMessage)) { - // Log message - base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kMessageFormatSpecifier, logMessage->message()); - } -#if !defined(ELPP_DISABLE_CUSTOM_FORMAT_SPECIFIERS) - el::base::threading::ScopedLock lock_(ELPP->customFormatSpecifiersLock()); - ELPP_UNUSED(lock_); - for (std::vector::const_iterator it = ELPP->customFormatSpecifiers()->begin(); - it != ELPP->customFormatSpecifiers()->end(); ++it) { - std::string fs(it->formatSpecifier()); - base::type::string_t wcsFormatSpecifier(fs.begin(), fs.end()); - base::utils::Str::replaceFirstWithEscape(logLine, wcsFormatSpecifier, it->resolver()(logMessage)); - } -#endif // !defined(ELPP_DISABLE_CUSTOM_FORMAT_SPECIFIERS) - if (appendNewLine) logLine += ELPP_LITERAL("\n"); - return logLine; -} - -// LogDispatcher - -void LogDispatcher::dispatch(void) { - if (m_proceed && m_dispatchAction == base::DispatchAction::None) { - m_proceed = false; - } - if (!m_proceed) { - return; - } -#ifndef ELPP_NO_GLOBAL_LOCK - // see https://github.com/muflihun/easyloggingpp/issues/580 - // global lock is turned off by default unless - // ELPP_NO_GLOBAL_LOCK is defined - base::threading::ScopedLock scopedLock(ELPP->lock()); -#endif - base::TypedConfigurations* tc = m_logMessage->logger()->m_typedConfigurations; - if (ELPP->hasFlag(LoggingFlag::StrictLogFileSizeCheck)) { - tc->validateFileRolling(m_logMessage->level(), ELPP->preRollOutCallback()); - } - LogDispatchCallback* callback = nullptr; - LogDispatchData data; - for (const std::pair& h - : ELPP->m_logDispatchCallbacks) { - callback = h.second.get(); - if (callback != nullptr && callback->enabled()) { - data.setLogMessage(m_logMessage); - data.setDispatchAction(m_dispatchAction); - callback->handle(&data); - } - } -} - -// MessageBuilder - -void MessageBuilder::initialize(Logger* logger) { - m_logger = logger; - m_containerLogSeperator = ELPP->hasFlag(LoggingFlag::NewLineForContainer) ? - ELPP_LITERAL("\n ") : ELPP_LITERAL(", "); -} - -MessageBuilder& MessageBuilder::operator<<(const wchar_t* msg) { - if (msg == nullptr) { - m_logger->stream() << base::consts::kNullPointer; - return *this; - } -# if defined(ELPP_UNICODE) - m_logger->stream() << msg; -# else - char* buff_ = base::utils::Str::wcharPtrToCharPtr(msg); - m_logger->stream() << buff_; - free(buff_); -# endif - if (ELPP->hasFlag(LoggingFlag::AutoSpacing)) { - m_logger->stream() << " "; - } - return *this; -} - -// Writer - -Writer& Writer::construct(Logger* logger, bool needLock) { - m_logger = logger; - initializeLogger(logger->id(), false, needLock); - m_messageBuilder.initialize(m_logger); - return *this; -} - -Writer& Writer::construct(int count, const char* loggerIds, ...) { - if (ELPP->hasFlag(LoggingFlag::MultiLoggerSupport)) { - va_list loggersList; - va_start(loggersList, loggerIds); - const char* id = loggerIds; - m_loggerIds.reserve(count); - for (int i = 0; i < count; ++i) { - m_loggerIds.push_back(std::string(id)); - id = va_arg(loggersList, const char*); - } - va_end(loggersList); - initializeLogger(m_loggerIds.at(0)); - } else { - initializeLogger(std::string(loggerIds)); - } - m_messageBuilder.initialize(m_logger); - return *this; -} - -void Writer::initializeLogger(const std::string& loggerId, bool lookup, bool needLock) { - if (lookup) { - m_logger = ELPP->registeredLoggers()->get(loggerId, ELPP->hasFlag(LoggingFlag::CreateLoggerAutomatically)); - } - if (m_logger == nullptr) { - { - if (!ELPP->registeredLoggers()->has(std::string(base::consts::kDefaultLoggerId))) { - // Somehow default logger has been unregistered. Not good! Register again - ELPP->registeredLoggers()->get(std::string(base::consts::kDefaultLoggerId)); - } - } - Writer(Level::Debug, m_file, m_line, m_func).construct(1, base::consts::kDefaultLoggerId) - << "Logger [" << loggerId << "] is not registered yet!"; - m_proceed = false; - } else { - if (needLock) { - m_logger->acquireLock(); // This should not be unlocked by checking m_proceed because - // m_proceed can be changed by lines below - } - if (ELPP->hasFlag(LoggingFlag::HierarchicalLogging)) { - m_proceed = m_level == Level::Verbose ? m_logger->enabled(m_level) : - LevelHelper::castToInt(m_level) >= LevelHelper::castToInt(ELPP->m_loggingLevel); - } else { - m_proceed = m_logger->enabled(m_level); - } - } -} - -void Writer::processDispatch() { -#if ELPP_LOGGING_ENABLED - if (ELPP->hasFlag(LoggingFlag::MultiLoggerSupport)) { - bool firstDispatched = false; - base::type::string_t logMessage; - std::size_t i = 0; - do { - if (m_proceed) { - if (firstDispatched) { - m_logger->stream() << logMessage; - } else { - firstDispatched = true; - if (m_loggerIds.size() > 1) { - logMessage = m_logger->stream().str(); - } - } - triggerDispatch(); - } else if (m_logger != nullptr) { - m_logger->stream().str(ELPP_LITERAL("")); - m_logger->releaseLock(); - } - if (i + 1 < m_loggerIds.size()) { - initializeLogger(m_loggerIds.at(i + 1)); - } - } while (++i < m_loggerIds.size()); - } else { - if (m_proceed) { - triggerDispatch(); - } else if (m_logger != nullptr) { - m_logger->stream().str(ELPP_LITERAL("")); - m_logger->releaseLock(); - } - } -#else - if (m_logger != nullptr) { - m_logger->stream().str(ELPP_LITERAL("")); - m_logger->releaseLock(); - } -#endif // ELPP_LOGGING_ENABLED -} - -void Writer::triggerDispatch(void) { - if (m_proceed) { - if (m_msg == nullptr) { - LogMessage msg(m_level, m_file, m_line, m_func, m_verboseLevel, - m_logger); - base::LogDispatcher(m_proceed, &msg, m_dispatchAction).dispatch(); - } else { - base::LogDispatcher(m_proceed, m_msg, m_dispatchAction).dispatch(); - } - } - if (m_logger != nullptr) { - m_logger->stream().str(ELPP_LITERAL("")); - m_logger->releaseLock(); - } - if (m_proceed && m_level == Level::Fatal - && !ELPP->hasFlag(LoggingFlag::DisableApplicationAbortOnFatalLog)) { - base::Writer(Level::Warning, m_file, m_line, m_func).construct(1, base::consts::kDefaultLoggerId) - << "Aborting application. Reason: Fatal log at [" << m_file << ":" << m_line << "]"; - std::stringstream reasonStream; - reasonStream << "Fatal log at [" << m_file << ":" << m_line << "]" - << " If you wish to disable 'abort on fatal log' please use " - << "el::Loggers::addFlag(el::LoggingFlag::DisableApplicationAbortOnFatalLog)"; - base::utils::abort(1, reasonStream.str()); - } - m_proceed = false; -} - -// PErrorWriter - -PErrorWriter::~PErrorWriter(void) { - if (m_proceed) { -#if ELPP_COMPILER_MSVC - char buff[256]; - strerror_s(buff, 256, errno); - m_logger->stream() << ": " << buff << " [" << errno << "]"; -#else - m_logger->stream() << ": " << strerror(errno) << " [" << errno << "]"; -#endif - } -} - -// PerformanceTracker - -#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) - -PerformanceTracker::PerformanceTracker(const std::string& blockName, - base::TimestampUnit timestampUnit, - const std::string& loggerId, - bool scopedLog, Level level) : - m_blockName(blockName), m_timestampUnit(timestampUnit), m_loggerId(loggerId), m_scopedLog(scopedLog), - m_level(level), m_hasChecked(false), m_lastCheckpointId(std::string()), m_enabled(false) { -#if !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED - // We store it locally so that if user happen to change configuration by the end of scope - // or before calling checkpoint, we still depend on state of configuraton at time of construction - el::Logger* loggerPtr = ELPP->registeredLoggers()->get(loggerId, false); - m_enabled = loggerPtr != nullptr && loggerPtr->m_typedConfigurations->performanceTracking(m_level); - if (m_enabled) { - base::utils::DateTime::gettimeofday(&m_startTime); - } -#endif // !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED -} - -PerformanceTracker::~PerformanceTracker(void) { -#if !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED - if (m_enabled) { - base::threading::ScopedLock scopedLock(lock()); - if (m_scopedLog) { - base::utils::DateTime::gettimeofday(&m_endTime); - base::type::string_t formattedTime = getFormattedTimeTaken(); - PerformanceTrackingData data(PerformanceTrackingData::DataType::Complete); - data.init(this); - data.m_formattedTimeTaken = formattedTime; - PerformanceTrackingCallback* callback = nullptr; - for (const std::pair& h - : ELPP->m_performanceTrackingCallbacks) { - callback = h.second.get(); - if (callback != nullptr && callback->enabled()) { - callback->handle(&data); - } - } - } - } -#endif // !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) -} - -void PerformanceTracker::checkpoint(const std::string& id, const char* file, base::type::LineNumber line, - const char* func) { -#if !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED - if (m_enabled) { - base::threading::ScopedLock scopedLock(lock()); - base::utils::DateTime::gettimeofday(&m_endTime); - base::type::string_t formattedTime = m_hasChecked ? getFormattedTimeTaken(m_lastCheckpointTime) : ELPP_LITERAL(""); - PerformanceTrackingData data(PerformanceTrackingData::DataType::Checkpoint); - data.init(this); - data.m_checkpointId = id; - data.m_file = file; - data.m_line = line; - data.m_func = func; - data.m_formattedTimeTaken = formattedTime; - PerformanceTrackingCallback* callback = nullptr; - for (const std::pair& h - : ELPP->m_performanceTrackingCallbacks) { - callback = h.second.get(); - if (callback != nullptr && callback->enabled()) { - callback->handle(&data); - } - } - base::utils::DateTime::gettimeofday(&m_lastCheckpointTime); - m_hasChecked = true; - m_lastCheckpointId = id; - } -#endif // !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED - ELPP_UNUSED(id); - ELPP_UNUSED(file); - ELPP_UNUSED(line); - ELPP_UNUSED(func); -} - -const base::type::string_t PerformanceTracker::getFormattedTimeTaken(struct timeval startTime) const { - if (ELPP->hasFlag(LoggingFlag::FixedTimeFormat)) { - base::type::stringstream_t ss; - ss << base::utils::DateTime::getTimeDifference(m_endTime, - startTime, m_timestampUnit) << " " << base::consts::kTimeFormats[static_cast - (m_timestampUnit)].unit; - return ss.str(); - } - return base::utils::DateTime::formatTime(base::utils::DateTime::getTimeDifference(m_endTime, - startTime, m_timestampUnit), m_timestampUnit); -} - -#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) - -namespace debug { -#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG) - -// StackTrace - -StackTrace::StackTraceEntry::StackTraceEntry(std::size_t index, const std::string& loc, const std::string& demang, - const std::string& hex, - const std::string& addr) : - m_index(index), - m_location(loc), - m_demangled(demang), - m_hex(hex), - m_addr(addr) { -} - -std::ostream& operator<<(std::ostream& ss, const StackTrace::StackTraceEntry& si) { - ss << "[" << si.m_index << "] " << si.m_location << (si.m_hex.empty() ? "" : "+") << si.m_hex << " " << si.m_addr << - (si.m_demangled.empty() ? "" : ":") << si.m_demangled; - return ss; -} - -std::ostream& operator<<(std::ostream& os, const StackTrace& st) { - std::vector::const_iterator it = st.m_stack.begin(); - while (it != st.m_stack.end()) { - os << " " << *it++ << "\n"; - } - return os; -} - -void StackTrace::generateNew(void) { -#if ELPP_STACKTRACE - m_stack.clear(); - void* stack[kMaxStack]; - unsigned int size = backtrace(stack, kMaxStack); - char** strings = backtrace_symbols(stack, size); - if (size > kStackStart) { // Skip StackTrace c'tor and generateNew - for (std::size_t i = kStackStart; i < size; ++i) { - std::string mangName; - std::string location; - std::string hex; - std::string addr; - - // entry: 2 crash.cpp.bin 0x0000000101552be5 _ZN2el4base5debug10StackTraceC1Ev + 21 - const std::string line(strings[i]); - auto p = line.find("_"); - if (p != std::string::npos) { - mangName = line.substr(p); - mangName = mangName.substr(0, mangName.find(" +")); - } - p = line.find("0x"); - if (p != std::string::npos) { - addr = line.substr(p); - addr = addr.substr(0, addr.find("_")); - } - // Perform demangling if parsed properly - if (!mangName.empty()) { - int status = 0; - char* demangName = abi::__cxa_demangle(mangName.data(), 0, 0, &status); - // if demangling is successful, output the demangled function name - if (status == 0) { - // Success (see http://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.3/a01696.html) - StackTraceEntry entry(i - 1, location, demangName, hex, addr); - m_stack.push_back(entry); - } else { - // Not successful - we will use mangled name - StackTraceEntry entry(i - 1, location, mangName, hex, addr); - m_stack.push_back(entry); - } - free(demangName); - } else { - StackTraceEntry entry(i - 1, line); - m_stack.push_back(entry); - } - } - } - free(strings); -#else - ELPP_INTERNAL_INFO(1, "Stacktrace generation not supported for selected compiler"); -#endif // ELPP_STACKTRACE -} - -// Static helper functions - -static std::string crashReason(int sig) { - std::stringstream ss; - bool foundReason = false; - for (int i = 0; i < base::consts::kCrashSignalsCount; ++i) { - if (base::consts::kCrashSignals[i].numb == sig) { - ss << "Application has crashed due to [" << base::consts::kCrashSignals[i].name << "] signal"; - if (ELPP->hasFlag(el::LoggingFlag::LogDetailedCrashReason)) { - ss << std::endl << - " " << base::consts::kCrashSignals[i].brief << std::endl << - " " << base::consts::kCrashSignals[i].detail; - } - foundReason = true; - } - } - if (!foundReason) { - ss << "Application has crashed due to unknown signal [" << sig << "]"; - } - return ss.str(); -} -/// @brief Logs reason of crash from sig -static void logCrashReason(int sig, bool stackTraceIfAvailable, Level level, const char* logger) { - if (sig == SIGINT && ELPP->hasFlag(el::LoggingFlag::IgnoreSigInt)) { - return; - } - std::stringstream ss; - ss << "CRASH HANDLED; "; - ss << crashReason(sig); -#if ELPP_STACKTRACE - if (stackTraceIfAvailable) { - ss << std::endl << " ======= Backtrace: =========" << std::endl << base::debug::StackTrace(); - } -#else - ELPP_UNUSED(stackTraceIfAvailable); -#endif // ELPP_STACKTRACE - ELPP_WRITE_LOG(el::base::Writer, level, base::DispatchAction::NormalLog, logger) << ss.str(); -} - -static inline void crashAbort(int sig) { - base::utils::abort(sig, std::string()); -} - -/// @brief Default application crash handler -/// -/// @detail This function writes log using 'default' logger, prints stack trace for GCC based compilers and aborts program. -static inline void defaultCrashHandler(int sig) { - base::debug::logCrashReason(sig, true, Level::Fatal, base::consts::kDefaultLoggerId); - base::debug::crashAbort(sig); -} - -// CrashHandler - -CrashHandler::CrashHandler(bool useDefault) { - if (useDefault) { - setHandler(defaultCrashHandler); - } -} - -void CrashHandler::setHandler(const Handler& cHandler) { - m_handler = cHandler; -#if defined(ELPP_HANDLE_SIGABRT) - int i = 0; // SIGABRT is at base::consts::kCrashSignals[0] -#else - int i = 1; -#endif // defined(ELPP_HANDLE_SIGABRT) - for (; i < base::consts::kCrashSignalsCount; ++i) { - m_handler = signal(base::consts::kCrashSignals[i].numb, cHandler); - } -} - -#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG) -} // namespace debug -} // namespace base - -// el - -// Helpers - -#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG) - -void Helpers::crashAbort(int sig, const char* sourceFile, unsigned int long line) { - std::stringstream ss; - ss << base::debug::crashReason(sig).c_str(); - ss << " - [Called el::Helpers::crashAbort(" << sig << ")]"; - if (sourceFile != nullptr && strlen(sourceFile) > 0) { - ss << " - Source: " << sourceFile; - if (line > 0) - ss << ":" << line; - else - ss << " (line number not specified)"; - } - base::utils::abort(sig, ss.str()); -} - -void Helpers::logCrashReason(int sig, bool stackTraceIfAvailable, Level level, const char* logger) { - el::base::debug::logCrashReason(sig, stackTraceIfAvailable, level, logger); -} - -#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG) - -// Loggers - -Logger* Loggers::getLogger(const std::string& identity, bool registerIfNotAvailable) { - return ELPP->registeredLoggers()->get(identity, registerIfNotAvailable); -} - -void Loggers::setDefaultLogBuilder(el::LogBuilderPtr& logBuilderPtr) { - ELPP->registeredLoggers()->setDefaultLogBuilder(logBuilderPtr); -} - -bool Loggers::unregisterLogger(const std::string& identity) { - return ELPP->registeredLoggers()->remove(identity); -} - -bool Loggers::hasLogger(const std::string& identity) { - return ELPP->registeredLoggers()->has(identity); -} - -Logger* Loggers::reconfigureLogger(Logger* logger, const Configurations& configurations) { - if (!logger) return nullptr; - logger->configure(configurations); - return logger; -} - -Logger* Loggers::reconfigureLogger(const std::string& identity, const Configurations& configurations) { - return Loggers::reconfigureLogger(Loggers::getLogger(identity), configurations); -} - -Logger* Loggers::reconfigureLogger(const std::string& identity, ConfigurationType configurationType, - const std::string& value) { - Logger* logger = Loggers::getLogger(identity); - if (logger == nullptr) { - return nullptr; - } - logger->configurations()->set(Level::Global, configurationType, value); - logger->reconfigure(); - return logger; -} - -void Loggers::reconfigureAllLoggers(const Configurations& configurations) { - for (base::RegisteredLoggers::iterator it = ELPP->registeredLoggers()->begin(); - it != ELPP->registeredLoggers()->end(); ++it) { - Loggers::reconfigureLogger(it->second, configurations); - } -} - -void Loggers::reconfigureAllLoggers(Level level, ConfigurationType configurationType, - const std::string& value) { - for (base::RegisteredLoggers::iterator it = ELPP->registeredLoggers()->begin(); - it != ELPP->registeredLoggers()->end(); ++it) { - Logger* logger = it->second; - logger->configurations()->set(level, configurationType, value); - logger->reconfigure(); - } -} - -void Loggers::setDefaultConfigurations(const Configurations& configurations, bool reconfigureExistingLoggers) { - ELPP->registeredLoggers()->setDefaultConfigurations(configurations); - if (reconfigureExistingLoggers) { - Loggers::reconfigureAllLoggers(configurations); - } -} - -const Configurations* Loggers::defaultConfigurations(void) { - return ELPP->registeredLoggers()->defaultConfigurations(); -} - -const base::LogStreamsReferenceMap* Loggers::logStreamsReference(void) { - return ELPP->registeredLoggers()->logStreamsReference(); -} - -base::TypedConfigurations Loggers::defaultTypedConfigurations(void) { - return base::TypedConfigurations( - ELPP->registeredLoggers()->defaultConfigurations(), - ELPP->registeredLoggers()->logStreamsReference()); -} - -std::vector* Loggers::populateAllLoggerIds(std::vector* targetList) { - targetList->clear(); - for (base::RegisteredLoggers::iterator it = ELPP->registeredLoggers()->list().begin(); - it != ELPP->registeredLoggers()->list().end(); ++it) { - targetList->push_back(it->first); - } - return targetList; -} - -void Loggers::configureFromGlobal(const char* globalConfigurationFilePath) { - std::ifstream gcfStream(globalConfigurationFilePath, std::ifstream::in); - ELPP_ASSERT(gcfStream.is_open(), "Unable to open global configuration file [" << globalConfigurationFilePath - << "] for parsing."); - std::string line = std::string(); - std::stringstream ss; - Logger* logger = nullptr; - auto configure = [&](void) { - ELPP_INTERNAL_INFO(8, "Configuring logger: '" << logger->id() << "' with configurations \n" << ss.str() - << "\n--------------"); - Configurations c; - c.parseFromText(ss.str()); - logger->configure(c); - }; - while (gcfStream.good()) { - std::getline(gcfStream, line); - ELPP_INTERNAL_INFO(1, "Parsing line: " << line); - base::utils::Str::trim(line); - if (Configurations::Parser::isComment(line)) continue; - Configurations::Parser::ignoreComments(&line); - base::utils::Str::trim(line); - if (line.size() > 2 && base::utils::Str::startsWith(line, std::string(base::consts::kConfigurationLoggerId))) { - if (!ss.str().empty() && logger != nullptr) { - configure(); - } - ss.str(std::string("")); - line = line.substr(2); - base::utils::Str::trim(line); - if (line.size() > 1) { - ELPP_INTERNAL_INFO(1, "Getting logger: '" << line << "'"); - logger = getLogger(line); - } - } else { - ss << line << "\n"; - } - } - if (!ss.str().empty() && logger != nullptr) { - configure(); - } -} - -bool Loggers::configureFromArg(const char* argKey) { -#if defined(ELPP_DISABLE_CONFIGURATION_FROM_PROGRAM_ARGS) - ELPP_UNUSED(argKey); -#else - if (!Helpers::commandLineArgs()->hasParamWithValue(argKey)) { - return false; - } - configureFromGlobal(Helpers::commandLineArgs()->getParamValue(argKey)); -#endif // defined(ELPP_DISABLE_CONFIGURATION_FROM_PROGRAM_ARGS) - return true; -} - -void Loggers::flushAll(void) { - ELPP->registeredLoggers()->flushAll(); -} - -void Loggers::setVerboseLevel(base::type::VerboseLevel level) { - ELPP->vRegistry()->setLevel(level); -} - -base::type::VerboseLevel Loggers::verboseLevel(void) { - return ELPP->vRegistry()->level(); -} - -void Loggers::setVModules(const char* modules) { - if (ELPP->vRegistry()->vModulesEnabled()) { - ELPP->vRegistry()->setModules(modules); - } -} - -void Loggers::clearVModules(void) { - ELPP->vRegistry()->clearModules(); -} - -// VersionInfo - -const std::string VersionInfo::version(void) { - return std::string("9.96.7"); -} -/// @brief Release date of current version -const std::string VersionInfo::releaseDate(void) { - return std::string("24-11-2018 0728hrs"); -} - -} // namespace el diff --git a/XBOFS.win/easylogging++.h b/XBOFS.win/easylogging++.h deleted file mode 100644 index 688d537..0000000 --- a/XBOFS.win/easylogging++.h +++ /dev/null @@ -1,4569 +0,0 @@ -// -// Bismillah ar-Rahmaan ar-Raheem -// -// Easylogging++ v9.96.7 -// Single-header only, cross-platform logging library for C++ applications -// -// Copyright (c) 2012-2018 Zuhd Web Services -// Copyright (c) 2012-2018 @abumusamq -// -// This library is released under the MIT Licence. -// https://github.com/zuhd-org/easyloggingpp/blob/master/LICENSE -// -// https://zuhd.org -// http://muflihun.com -// - -#ifndef EASYLOGGINGPP_H -#define EASYLOGGINGPP_H -// Compilers and C++0x/C++11 Evaluation -#if __cplusplus >= 201103L -# define ELPP_CXX11 1 -#endif // __cplusplus >= 201103L -#if (defined(__GNUC__)) -# define ELPP_COMPILER_GCC 1 -#else -# define ELPP_COMPILER_GCC 0 -#endif -#if ELPP_COMPILER_GCC -# define ELPP_GCC_VERSION (__GNUC__ * 10000 \ -+ __GNUC_MINOR__ * 100 \ -+ __GNUC_PATCHLEVEL__) -# if defined(__GXX_EXPERIMENTAL_CXX0X__) -# define ELPP_CXX0X 1 -# endif -#endif -// Visual C++ -#if defined(_MSC_VER) -# define ELPP_COMPILER_MSVC 1 -#else -# define ELPP_COMPILER_MSVC 0 -#endif -#define ELPP_CRT_DBG_WARNINGS ELPP_COMPILER_MSVC -#if ELPP_COMPILER_MSVC -# if (_MSC_VER == 1600) -# define ELPP_CXX0X 1 -# elif(_MSC_VER >= 1700) -# define ELPP_CXX11 1 -# endif -#endif -// Clang++ -#if (defined(__clang__) && (__clang__ == 1)) -# define ELPP_COMPILER_CLANG 1 -#else -# define ELPP_COMPILER_CLANG 0 -#endif -#if ELPP_COMPILER_CLANG -# if __has_include() -# include // Make __GLIBCXX__ defined when using libstdc++ -# if !defined(__GLIBCXX__) || __GLIBCXX__ >= 20150426 -# define ELPP_CLANG_SUPPORTS_THREAD -# endif // !defined(__GLIBCXX__) || __GLIBCXX__ >= 20150426 -# endif // __has_include() -#endif -#if (defined(__MINGW32__) || defined(__MINGW64__)) -# define ELPP_MINGW 1 -#else -# define ELPP_MINGW 0 -#endif -#if (defined(__CYGWIN__) && (__CYGWIN__ == 1)) -# define ELPP_CYGWIN 1 -#else -# define ELPP_CYGWIN 0 -#endif -#if (defined(__INTEL_COMPILER)) -# define ELPP_COMPILER_INTEL 1 -#else -# define ELPP_COMPILER_INTEL 0 -#endif -// Operating System Evaluation -// Windows -#if (defined(_WIN32) || defined(_WIN64)) -# define ELPP_OS_WINDOWS 1 -#else -# define ELPP_OS_WINDOWS 0 -#endif -// Linux -#if (defined(__linux) || defined(__linux__)) -# define ELPP_OS_LINUX 1 -#else -# define ELPP_OS_LINUX 0 -#endif -#if (defined(__APPLE__)) -# define ELPP_OS_MAC 1 -#else -# define ELPP_OS_MAC 0 -#endif -#if (defined(__FreeBSD__) || defined(__FreeBSD_kernel__)) -# define ELPP_OS_FREEBSD 1 -#else -# define ELPP_OS_FREEBSD 0 -#endif -#if (defined(__sun)) -# define ELPP_OS_SOLARIS 1 -#else -# define ELPP_OS_SOLARIS 0 -#endif -#if (defined(_AIX)) -# define ELPP_OS_AIX 1 -#else -# define ELPP_OS_AIX 0 -#endif -#if (defined(__NetBSD__)) -# define ELPP_OS_NETBSD 1 -#else -# define ELPP_OS_NETBSD 0 -#endif -#if defined(__EMSCRIPTEN__) -# define ELPP_OS_EMSCRIPTEN 1 -#else -# define ELPP_OS_EMSCRIPTEN 0 -#endif -// Unix -#if ((ELPP_OS_LINUX || ELPP_OS_MAC || ELPP_OS_FREEBSD || ELPP_OS_NETBSD || ELPP_OS_SOLARIS || ELPP_OS_AIX || ELPP_OS_EMSCRIPTEN) && (!ELPP_OS_WINDOWS)) -# define ELPP_OS_UNIX 1 -#else -# define ELPP_OS_UNIX 0 -#endif -#if (defined(__ANDROID__)) -# define ELPP_OS_ANDROID 1 -#else -# define ELPP_OS_ANDROID 0 -#endif -// Evaluating Cygwin as *nix OS -#if !ELPP_OS_UNIX && !ELPP_OS_WINDOWS && ELPP_CYGWIN -# undef ELPP_OS_UNIX -# undef ELPP_OS_LINUX -# define ELPP_OS_UNIX 1 -# define ELPP_OS_LINUX 1 -#endif // !ELPP_OS_UNIX && !ELPP_OS_WINDOWS && ELPP_CYGWIN -#if !defined(ELPP_INTERNAL_DEBUGGING_OUT_INFO) -# define ELPP_INTERNAL_DEBUGGING_OUT_INFO std::cout -#endif // !defined(ELPP_INTERNAL_DEBUGGING_OUT) -#if !defined(ELPP_INTERNAL_DEBUGGING_OUT_ERROR) -# define ELPP_INTERNAL_DEBUGGING_OUT_ERROR std::cerr -#endif // !defined(ELPP_INTERNAL_DEBUGGING_OUT) -#if !defined(ELPP_INTERNAL_DEBUGGING_ENDL) -# define ELPP_INTERNAL_DEBUGGING_ENDL std::endl -#endif // !defined(ELPP_INTERNAL_DEBUGGING_OUT) -#if !defined(ELPP_INTERNAL_DEBUGGING_MSG) -# define ELPP_INTERNAL_DEBUGGING_MSG(msg) msg -#endif // !defined(ELPP_INTERNAL_DEBUGGING_OUT) -// Internal Assertions and errors -#if !defined(ELPP_DISABLE_ASSERT) -# if (defined(ELPP_DEBUG_ASSERT_FAILURE)) -# define ELPP_ASSERT(expr, msg) if (!(expr)) { \ -std::stringstream internalInfoStream; internalInfoStream << msg; \ -ELPP_INTERNAL_DEBUGGING_OUT_ERROR \ -<< "EASYLOGGING++ ASSERTION FAILED (LINE: " << __LINE__ << ") [" #expr << "] WITH MESSAGE \"" \ -<< ELPP_INTERNAL_DEBUGGING_MSG(internalInfoStream.str()) << "\"" << ELPP_INTERNAL_DEBUGGING_ENDL; base::utils::abort(1, \ -"ELPP Assertion failure, please define ELPP_DEBUG_ASSERT_FAILURE"); } -# else -# define ELPP_ASSERT(expr, msg) if (!(expr)) { \ -std::stringstream internalInfoStream; internalInfoStream << msg; \ -ELPP_INTERNAL_DEBUGGING_OUT_ERROR\ -<< "ASSERTION FAILURE FROM EASYLOGGING++ (LINE: " \ -<< __LINE__ << ") [" #expr << "] WITH MESSAGE \"" << ELPP_INTERNAL_DEBUGGING_MSG(internalInfoStream.str()) << "\"" \ -<< ELPP_INTERNAL_DEBUGGING_ENDL; } -# endif // (defined(ELPP_DEBUG_ASSERT_FAILURE)) -#else -# define ELPP_ASSERT(x, y) -#endif //(!defined(ELPP_DISABLE_ASSERT) -#if ELPP_COMPILER_MSVC -# define ELPP_INTERNAL_DEBUGGING_WRITE_PERROR \ -{ char buff[256]; strerror_s(buff, 256, errno); \ -ELPP_INTERNAL_DEBUGGING_OUT_ERROR << ": " << buff << " [" << errno << "]";} (void)0 -#else -# define ELPP_INTERNAL_DEBUGGING_WRITE_PERROR \ -ELPP_INTERNAL_DEBUGGING_OUT_ERROR << ": " << strerror(errno) << " [" << errno << "]"; (void)0 -#endif // ELPP_COMPILER_MSVC -#if defined(ELPP_DEBUG_ERRORS) -# if !defined(ELPP_INTERNAL_ERROR) -# define ELPP_INTERNAL_ERROR(msg, pe) { \ -std::stringstream internalInfoStream; internalInfoStream << " " << msg; \ -ELPP_INTERNAL_DEBUGGING_OUT_ERROR \ -<< "ERROR FROM EASYLOGGING++ (LINE: " << __LINE__ << ") " \ -<< ELPP_INTERNAL_DEBUGGING_MSG(internalInfoStream.str()) << ELPP_INTERNAL_DEBUGGING_ENDL; \ -if (pe) { ELPP_INTERNAL_DEBUGGING_OUT_ERROR << " "; ELPP_INTERNAL_DEBUGGING_WRITE_PERROR; }} (void)0 -# endif -#else -# undef ELPP_INTERNAL_INFO -# define ELPP_INTERNAL_ERROR(msg, pe) -#endif // defined(ELPP_DEBUG_ERRORS) -#if (defined(ELPP_DEBUG_INFO)) -# if !(defined(ELPP_INTERNAL_INFO_LEVEL)) -# define ELPP_INTERNAL_INFO_LEVEL 9 -# endif // !(defined(ELPP_INTERNAL_INFO_LEVEL)) -# if !defined(ELPP_INTERNAL_INFO) -# define ELPP_INTERNAL_INFO(lvl, msg) { if (lvl <= ELPP_INTERNAL_INFO_LEVEL) { \ -std::stringstream internalInfoStream; internalInfoStream << " " << msg; \ -ELPP_INTERNAL_DEBUGGING_OUT_INFO << ELPP_INTERNAL_DEBUGGING_MSG(internalInfoStream.str()) \ -<< ELPP_INTERNAL_DEBUGGING_ENDL; }} -# endif -#else -# undef ELPP_INTERNAL_INFO -# define ELPP_INTERNAL_INFO(lvl, msg) -#endif // (defined(ELPP_DEBUG_INFO)) -#if (defined(ELPP_FEATURE_ALL)) || (defined(ELPP_FEATURE_CRASH_LOG)) -# if (ELPP_COMPILER_GCC && !ELPP_MINGW && !ELPP_OS_ANDROID && !ELPP_OS_EMSCRIPTEN) -# define ELPP_STACKTRACE 1 -# else -# if ELPP_COMPILER_MSVC -# pragma message("Stack trace not available for this compiler") -# else -# warning "Stack trace not available for this compiler"; -# endif // ELPP_COMPILER_MSVC -# define ELPP_STACKTRACE 0 -# endif // ELPP_COMPILER_GCC -#else -# define ELPP_STACKTRACE 0 -#endif // (defined(ELPP_FEATURE_ALL)) || (defined(ELPP_FEATURE_CRASH_LOG)) -// Miscellaneous macros -#define ELPP_UNUSED(x) (void)x -#if ELPP_OS_UNIX -// Log file permissions for unix-based systems -# define ELPP_LOG_PERMS S_IRUSR | S_IWUSR | S_IXUSR | S_IWGRP | S_IRGRP | S_IXGRP | S_IWOTH | S_IXOTH -#endif // ELPP_OS_UNIX -#if defined(ELPP_AS_DLL) && ELPP_COMPILER_MSVC -# if defined(ELPP_EXPORT_SYMBOLS) -# define ELPP_EXPORT __declspec(dllexport) -# else -# define ELPP_EXPORT __declspec(dllimport) -# endif // defined(ELPP_EXPORT_SYMBOLS) -#else -# define ELPP_EXPORT -#endif // defined(ELPP_AS_DLL) && ELPP_COMPILER_MSVC -// Some special functions that are VC++ specific -#undef STRTOK -#undef STRERROR -#undef STRCAT -#undef STRCPY -#if ELPP_CRT_DBG_WARNINGS -# define STRTOK(a, b, c) strtok_s(a, b, c) -# define STRERROR(a, b, c) strerror_s(a, b, c) -# define STRCAT(a, b, len) strcat_s(a, len, b) -# define STRCPY(a, b, len) strcpy_s(a, len, b) -#else -# define STRTOK(a, b, c) strtok(a, b) -# define STRERROR(a, b, c) strerror(c) -# define STRCAT(a, b, len) strcat(a, b) -# define STRCPY(a, b, len) strcpy(a, b) -#endif -// Compiler specific support evaluations -#if (ELPP_MINGW && !defined(ELPP_FORCE_USE_STD_THREAD)) -# define ELPP_USE_STD_THREADING 0 -#else -# if ((ELPP_COMPILER_CLANG && defined(ELPP_CLANG_SUPPORTS_THREAD)) || \ - (!ELPP_COMPILER_CLANG && defined(ELPP_CXX11)) || \ - defined(ELPP_FORCE_USE_STD_THREAD)) -# define ELPP_USE_STD_THREADING 1 -# else -# define ELPP_USE_STD_THREADING 0 -# endif -#endif -#undef ELPP_FINAL -#if ELPP_COMPILER_INTEL || (ELPP_GCC_VERSION < 40702) -# define ELPP_FINAL -#else -# define ELPP_FINAL final -#endif // ELPP_COMPILER_INTEL || (ELPP_GCC_VERSION < 40702) -#if defined(ELPP_EXPERIMENTAL_ASYNC) -# define ELPP_ASYNC_LOGGING 1 -#else -# define ELPP_ASYNC_LOGGING 0 -#endif // defined(ELPP_EXPERIMENTAL_ASYNC) -#if defined(ELPP_THREAD_SAFE) || ELPP_ASYNC_LOGGING -# define ELPP_THREADING_ENABLED 1 -#else -# define ELPP_THREADING_ENABLED 0 -#endif // defined(ELPP_THREAD_SAFE) || ELPP_ASYNC_LOGGING -// Function macro ELPP_FUNC -#undef ELPP_FUNC -#if ELPP_COMPILER_MSVC // Visual C++ -# define ELPP_FUNC __FUNCSIG__ -#elif ELPP_COMPILER_GCC // GCC -# define ELPP_FUNC __PRETTY_FUNCTION__ -#elif ELPP_COMPILER_INTEL // Intel C++ -# define ELPP_FUNC __PRETTY_FUNCTION__ -#elif ELPP_COMPILER_CLANG // Clang++ -# define ELPP_FUNC __PRETTY_FUNCTION__ -#else -# if defined(__func__) -# define ELPP_FUNC __func__ -# else -# define ELPP_FUNC "" -# endif // defined(__func__) -#endif // defined(_MSC_VER) -#undef ELPP_VARIADIC_TEMPLATES_SUPPORTED -// Keep following line commented until features are fixed -#define ELPP_VARIADIC_TEMPLATES_SUPPORTED \ -(ELPP_COMPILER_GCC || ELPP_COMPILER_CLANG || ELPP_COMPILER_INTEL || (ELPP_COMPILER_MSVC && _MSC_VER >= 1800)) -// Logging Enable/Disable macros -#if defined(ELPP_DISABLE_LOGS) -#define ELPP_LOGGING_ENABLED 0 -#else -#define ELPP_LOGGING_ENABLED 1 -#endif -#if (!defined(ELPP_DISABLE_DEBUG_LOGS) && (ELPP_LOGGING_ENABLED)) -# define ELPP_DEBUG_LOG 1 -#else -# define ELPP_DEBUG_LOG 0 -#endif // (!defined(ELPP_DISABLE_DEBUG_LOGS) && (ELPP_LOGGING_ENABLED)) -#if (!defined(ELPP_DISABLE_INFO_LOGS) && (ELPP_LOGGING_ENABLED)) -# define ELPP_INFO_LOG 1 -#else -# define ELPP_INFO_LOG 0 -#endif // (!defined(ELPP_DISABLE_INFO_LOGS) && (ELPP_LOGGING_ENABLED)) -#if (!defined(ELPP_DISABLE_WARNING_LOGS) && (ELPP_LOGGING_ENABLED)) -# define ELPP_WARNING_LOG 1 -#else -# define ELPP_WARNING_LOG 0 -#endif // (!defined(ELPP_DISABLE_WARNING_LOGS) && (ELPP_LOGGING_ENABLED)) -#if (!defined(ELPP_DISABLE_ERROR_LOGS) && (ELPP_LOGGING_ENABLED)) -# define ELPP_ERROR_LOG 1 -#else -# define ELPP_ERROR_LOG 0 -#endif // (!defined(ELPP_DISABLE_ERROR_LOGS) && (ELPP_LOGGING_ENABLED)) -#if (!defined(ELPP_DISABLE_FATAL_LOGS) && (ELPP_LOGGING_ENABLED)) -# define ELPP_FATAL_LOG 1 -#else -# define ELPP_FATAL_LOG 0 -#endif // (!defined(ELPP_DISABLE_FATAL_LOGS) && (ELPP_LOGGING_ENABLED)) -#if (!defined(ELPP_DISABLE_TRACE_LOGS) && (ELPP_LOGGING_ENABLED)) -# define ELPP_TRACE_LOG 1 -#else -# define ELPP_TRACE_LOG 0 -#endif // (!defined(ELPP_DISABLE_TRACE_LOGS) && (ELPP_LOGGING_ENABLED)) -#if (!defined(ELPP_DISABLE_VERBOSE_LOGS) && (ELPP_LOGGING_ENABLED)) -# define ELPP_VERBOSE_LOG 1 -#else -# define ELPP_VERBOSE_LOG 0 -#endif // (!defined(ELPP_DISABLE_VERBOSE_LOGS) && (ELPP_LOGGING_ENABLED)) -#if (!(ELPP_CXX0X || ELPP_CXX11)) -# error "C++0x (or higher) support not detected! (Is `-std=c++11' missing?)" -#endif // (!(ELPP_CXX0X || ELPP_CXX11)) -// Headers -#if defined(ELPP_SYSLOG) -# include -#endif // defined(ELPP_SYSLOG) -#include -#include -#include -#include -#include -#include -#include -#include -#if defined(ELPP_UNICODE) -# include -# if ELPP_OS_WINDOWS -# include -# endif // ELPP_OS_WINDOWS -#endif // defined(ELPP_UNICODE) -#if ELPP_STACKTRACE -# include -# include -#endif // ELPP_STACKTRACE -#if ELPP_OS_ANDROID -# include -#endif // ELPP_OS_ANDROID -#if ELPP_OS_UNIX -# include -# include -#elif ELPP_OS_WINDOWS -# include -# include -# if defined(WIN32_LEAN_AND_MEAN) -# if defined(ELPP_WINSOCK2) -# include -# else -# include -# endif // defined(ELPP_WINSOCK2) -# endif // defined(WIN32_LEAN_AND_MEAN) -#endif // ELPP_OS_UNIX -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if ELPP_THREADING_ENABLED -# if ELPP_USE_STD_THREADING -# include -# include -# else -# if ELPP_OS_UNIX -# include -# endif // ELPP_OS_UNIX -# endif // ELPP_USE_STD_THREADING -#endif // ELPP_THREADING_ENABLED -#if ELPP_ASYNC_LOGGING -# if defined(ELPP_NO_SLEEP_FOR) -# include -# endif // defined(ELPP_NO_SLEEP_FOR) -# include -# include -# include -#endif // ELPP_ASYNC_LOGGING -#if defined(ELPP_STL_LOGGING) -// For logging STL based templates -# include -# include -# include -# include -# include -# include -# if defined(ELPP_LOG_STD_ARRAY) -# include -# endif // defined(ELPP_LOG_STD_ARRAY) -# if defined(ELPP_LOG_UNORDERED_SET) -# include -# endif // defined(ELPP_UNORDERED_SET) -#endif // defined(ELPP_STL_LOGGING) -#if defined(ELPP_QT_LOGGING) -// For logging Qt based classes & templates -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -#endif // defined(ELPP_QT_LOGGING) -#if defined(ELPP_BOOST_LOGGING) -// For logging boost based classes & templates -# include -# include -# include -# include -# include -# include -# include -# include -#endif // defined(ELPP_BOOST_LOGGING) -#if defined(ELPP_WXWIDGETS_LOGGING) -// For logging wxWidgets based classes & templates -# include -#endif // defined(ELPP_WXWIDGETS_LOGGING) -#if defined(ELPP_UTC_DATETIME) -# define elpptime_r gmtime_r -# define elpptime_s gmtime_s -# define elpptime gmtime -#else -# define elpptime_r localtime_r -# define elpptime_s localtime_s -# define elpptime localtime -#endif // defined(ELPP_UTC_DATETIME) -// Forward declarations -namespace el { -class Logger; -class LogMessage; -class PerformanceTrackingData; -class Loggers; -class Helpers; -template class Callback; -class LogDispatchCallback; -class PerformanceTrackingCallback; -class LoggerRegistrationCallback; -class LogDispatchData; -namespace base { -class Storage; -class RegisteredLoggers; -class PerformanceTracker; -class MessageBuilder; -class Writer; -class PErrorWriter; -class LogDispatcher; -class DefaultLogBuilder; -class DefaultLogDispatchCallback; -#if ELPP_ASYNC_LOGGING -class AsyncLogDispatchCallback; -class AsyncDispatchWorker; -#endif // ELPP_ASYNC_LOGGING -class DefaultPerformanceTrackingCallback; -} // namespace base -} // namespace el -/// @brief Easylogging++ entry namespace -namespace el { -/// @brief Namespace containing base/internal functionality used by Easylogging++ -namespace base { -/// @brief Data types used by Easylogging++ -namespace type { -#undef ELPP_LITERAL -#undef ELPP_STRLEN -#undef ELPP_COUT -#if defined(ELPP_UNICODE) -# define ELPP_LITERAL(txt) L##txt -# define ELPP_STRLEN wcslen -# if defined ELPP_CUSTOM_COUT -# define ELPP_COUT ELPP_CUSTOM_COUT -# else -# define ELPP_COUT std::wcout -# endif // defined ELPP_CUSTOM_COUT -typedef wchar_t char_t; -typedef std::wstring string_t; -typedef std::wstringstream stringstream_t; -typedef std::wfstream fstream_t; -typedef std::wostream ostream_t; -#else -# define ELPP_LITERAL(txt) txt -# define ELPP_STRLEN strlen -# if defined ELPP_CUSTOM_COUT -# define ELPP_COUT ELPP_CUSTOM_COUT -# else -# define ELPP_COUT std::cout -# endif // defined ELPP_CUSTOM_COUT -typedef char char_t; -typedef std::string string_t; -typedef std::stringstream stringstream_t; -typedef std::fstream fstream_t; -typedef std::ostream ostream_t; -#endif // defined(ELPP_UNICODE) -#if defined(ELPP_CUSTOM_COUT_LINE) -# define ELPP_COUT_LINE(logLine) ELPP_CUSTOM_COUT_LINE(logLine) -#else -# define ELPP_COUT_LINE(logLine) logLine << std::flush -#endif // defined(ELPP_CUSTOM_COUT_LINE) -typedef unsigned int EnumType; -typedef unsigned short VerboseLevel; -typedef unsigned long int LineNumber; -typedef std::shared_ptr StoragePointer; -typedef std::shared_ptr LogDispatchCallbackPtr; -typedef std::shared_ptr PerformanceTrackingCallbackPtr; -typedef std::shared_ptr LoggerRegistrationCallbackPtr; -typedef std::unique_ptr PerformanceTrackerPtr; -} // namespace type -/// @brief Internal helper class that prevent copy constructor for class -/// -/// @detail When using this class simply inherit it privately -class NoCopy { - protected: - NoCopy(void) {} - private: - NoCopy(const NoCopy&); - NoCopy& operator=(const NoCopy&); -}; -/// @brief Internal helper class that makes all default constructors private. -/// -/// @detail This prevents initializing class making it static unless an explicit constructor is declared. -/// When using this class simply inherit it privately -class StaticClass { - private: - StaticClass(void); - StaticClass(const StaticClass&); - StaticClass& operator=(const StaticClass&); -}; -} // namespace base -/// @brief Represents enumeration for severity level used to determine level of logging -/// -/// @detail With Easylogging++, developers may disable or enable any level regardless of -/// what the severity is. Or they can choose to log using hierarchical logging flag -enum class Level : base::type::EnumType { - /// @brief Generic level that represents all the levels. Useful when setting global configuration for all levels - Global = 1, - /// @brief Information that can be useful to back-trace certain events - mostly useful than debug logs. - Trace = 2, - /// @brief Informational events most useful for developers to debug application - Debug = 4, - /// @brief Severe error information that will presumably abort application - Fatal = 8, - /// @brief Information representing errors in application but application will keep running - Error = 16, - /// @brief Useful when application has potentially harmful situtaions - Warning = 32, - /// @brief Information that can be highly useful and vary with verbose logging level. - Verbose = 64, - /// @brief Mainly useful to represent current progress of application - Info = 128, - /// @brief Represents unknown level - Unknown = 1010 -}; -} // namespace el -namespace std { -template<> struct hash { - public: - std::size_t operator()(const el::Level& l) const { - return hash {}(static_cast(l)); - } -}; -} -namespace el { -/// @brief Static class that contains helper functions for el::Level -class LevelHelper : base::StaticClass { - public: - /// @brief Represents minimum valid level. Useful when iterating through enum. - static const base::type::EnumType kMinValid = static_cast(Level::Trace); - /// @brief Represents maximum valid level. This is used internally and you should not need it. - static const base::type::EnumType kMaxValid = static_cast(Level::Info); - /// @brief Casts level to int, useful for iterating through enum. - static base::type::EnumType castToInt(Level level) { - return static_cast(level); - } - /// @brief Casts int(ushort) to level, useful for iterating through enum. - static Level castFromInt(base::type::EnumType l) { - return static_cast(l); - } - /// @brief Converts level to associated const char* - /// @return Upper case string based level. - static const char* convertToString(Level level); - /// @brief Converts from levelStr to Level - /// @param levelStr Upper case string based level. - /// Lower case is also valid but providing upper case is recommended. - static Level convertFromString(const char* levelStr); - /// @brief Applies specified function to each level starting from startIndex - /// @param startIndex initial value to start the iteration from. This is passed as pointer and - /// is left-shifted so this can be used inside function (fn) to represent current level. - /// @param fn function to apply with each level. This bool represent whether or not to stop iterating through levels. - static void forEachLevel(base::type::EnumType* startIndex, const std::function& fn); -}; -/// @brief Represents enumeration of ConfigurationType used to configure or access certain aspect -/// of logging -enum class ConfigurationType : base::type::EnumType { - /// @brief Determines whether or not corresponding level and logger of logging is enabled - /// You may disable all logs by using el::Level::Global - Enabled = 1, - /// @brief Whether or not to write corresponding log to log file - ToFile = 2, - /// @brief Whether or not to write corresponding level and logger log to standard output. - /// By standard output meaning termnal, command prompt etc - ToStandardOutput = 4, - /// @brief Determines format of logging corresponding level and logger. - Format = 8, - /// @brief Determines log file (full path) to write logs to for correponding level and logger - Filename = 16, - /// @brief Specifies precision of the subsecond part. It should be within range (1-6). - SubsecondPrecision = 32, - /// @brief Alias of SubsecondPrecision (for backward compatibility) - MillisecondsWidth = SubsecondPrecision, - /// @brief Determines whether or not performance tracking is enabled. - /// - /// @detail This does not depend on logger or level. Performance tracking always uses 'performance' logger - PerformanceTracking = 64, - /// @brief Specifies log file max size. - /// - /// @detail If file size of corresponding log file (for corresponding level) is >= specified size, log file will - /// be truncated and re-initiated. - MaxLogFileSize = 128, - /// @brief Specifies number of log entries to hold until we flush pending log data - LogFlushThreshold = 256, - /// @brief Represents unknown configuration - Unknown = 1010 -}; -/// @brief Static class that contains helper functions for el::ConfigurationType -class ConfigurationTypeHelper : base::StaticClass { - public: - /// @brief Represents minimum valid configuration type. Useful when iterating through enum. - static const base::type::EnumType kMinValid = static_cast(ConfigurationType::Enabled); - /// @brief Represents maximum valid configuration type. This is used internally and you should not need it. - static const base::type::EnumType kMaxValid = static_cast(ConfigurationType::MaxLogFileSize); - /// @brief Casts configuration type to int, useful for iterating through enum. - static base::type::EnumType castToInt(ConfigurationType configurationType) { - return static_cast(configurationType); - } - /// @brief Casts int(ushort) to configurationt type, useful for iterating through enum. - static ConfigurationType castFromInt(base::type::EnumType c) { - return static_cast(c); - } - /// @brief Converts configuration type to associated const char* - /// @returns Upper case string based configuration type. - static const char* convertToString(ConfigurationType configurationType); - /// @brief Converts from configStr to ConfigurationType - /// @param configStr Upper case string based configuration type. - /// Lower case is also valid but providing upper case is recommended. - static ConfigurationType convertFromString(const char* configStr); - /// @brief Applies specified function to each configuration type starting from startIndex - /// @param startIndex initial value to start the iteration from. This is passed by pointer and is left-shifted - /// so this can be used inside function (fn) to represent current configuration type. - /// @param fn function to apply with each configuration type. - /// This bool represent whether or not to stop iterating through configurations. - static inline void forEachConfigType(base::type::EnumType* startIndex, const std::function& fn); -}; -/// @brief Flags used while writing logs. This flags are set by user -enum class LoggingFlag : base::type::EnumType { - /// @brief Makes sure we have new line for each container log entry - NewLineForContainer = 1, - /// @brief Makes sure if -vmodule is used and does not specifies a module, then verbose - /// logging is allowed via that module. - AllowVerboseIfModuleNotSpecified = 2, - /// @brief When handling crashes by default, detailed crash reason will be logged as well - LogDetailedCrashReason = 4, - /// @brief Allows to disable application abortion when logged using FATAL level - DisableApplicationAbortOnFatalLog = 8, - /// @brief Flushes log with every log-entry (performance sensative) - Disabled by default - ImmediateFlush = 16, - /// @brief Enables strict file rolling - StrictLogFileSizeCheck = 32, - /// @brief Make terminal output colorful for supported terminals - ColoredTerminalOutput = 64, - /// @brief Supports use of multiple logging in same macro, e.g, CLOG(INFO, "default", "network") - MultiLoggerSupport = 128, - /// @brief Disables comparing performance tracker's checkpoints - DisablePerformanceTrackingCheckpointComparison = 256, - /// @brief Disable VModules - DisableVModules = 512, - /// @brief Disable VModules extensions - DisableVModulesExtensions = 1024, - /// @brief Enables hierarchical logging - HierarchicalLogging = 2048, - /// @brief Creates logger automatically when not available - CreateLoggerAutomatically = 4096, - /// @brief Adds spaces b/w logs that separated by left-shift operator - AutoSpacing = 8192, - /// @brief Preserves time format and does not convert it to sec, hour etc (performance tracking only) - FixedTimeFormat = 16384, - // @brief Ignore SIGINT or crash - IgnoreSigInt = 32768, -}; -namespace base { -/// @brief Namespace containing constants used internally. -namespace consts { -static const char kFormatSpecifierCharValue = 'v'; -static const char kFormatSpecifierChar = '%'; -static const unsigned int kMaxLogPerCounter = 100000; -static const unsigned int kMaxLogPerContainer = 100; -static const unsigned int kDefaultSubsecondPrecision = 3; - -#ifdef ELPP_DEFAULT_LOGGER -static const char* kDefaultLoggerId = ELPP_DEFAULT_LOGGER; -#else -static const char* kDefaultLoggerId = "default"; -#endif - -#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) -#ifdef ELPP_DEFAULT_PERFORMANCE_LOGGER -static const char* kPerformanceLoggerId = ELPP_DEFAULT_PERFORMANCE_LOGGER; -#else -static const char* kPerformanceLoggerId = "performance"; -#endif // ELPP_DEFAULT_PERFORMANCE_LOGGER -#endif - -#if defined(ELPP_SYSLOG) -static const char* kSysLogLoggerId = "syslog"; -#endif // defined(ELPP_SYSLOG) - -#if ELPP_OS_WINDOWS -static const char* kFilePathSeperator = "\\"; -#else -static const char* kFilePathSeperator = "/"; -#endif // ELPP_OS_WINDOWS - -static const std::size_t kSourceFilenameMaxLength = 100; -static const std::size_t kSourceLineMaxLength = 10; -static const Level kPerformanceTrackerDefaultLevel = Level::Info; -const struct { - double value; - const base::type::char_t* unit; -} kTimeFormats[] = { - { 1000.0f, ELPP_LITERAL("us") }, - { 1000.0f, ELPP_LITERAL("ms") }, - { 60.0f, ELPP_LITERAL("seconds") }, - { 60.0f, ELPP_LITERAL("minutes") }, - { 24.0f, ELPP_LITERAL("hours") }, - { 7.0f, ELPP_LITERAL("days") } -}; -static const int kTimeFormatsCount = sizeof(kTimeFormats) / sizeof(kTimeFormats[0]); -const struct { - int numb; - const char* name; - const char* brief; - const char* detail; -} kCrashSignals[] = { - // NOTE: Do not re-order, if you do please check CrashHandler(bool) constructor and CrashHandler::setHandler(..) - { - SIGABRT, "SIGABRT", "Abnormal termination", - "Program was abnormally terminated." - }, - { - SIGFPE, "SIGFPE", "Erroneous arithmetic operation", - "Arithemetic operation issue such as division by zero or operation resulting in overflow." - }, - { - SIGILL, "SIGILL", "Illegal instruction", - "Generally due to a corruption in the code or to an attempt to execute data." - }, - { - SIGSEGV, "SIGSEGV", "Invalid access to memory", - "Program is trying to read an invalid (unallocated, deleted or corrupted) or inaccessible memory." - }, - { - SIGINT, "SIGINT", "Interactive attention signal", - "Interruption generated (generally) by user or operating system." - }, -}; -static const int kCrashSignalsCount = sizeof(kCrashSignals) / sizeof(kCrashSignals[0]); -} // namespace consts -} // namespace base -typedef std::function PreRollOutCallback; -namespace base { -static inline void defaultPreRollOutCallback(const char*, std::size_t) {} -/// @brief Enum to represent timestamp unit -enum class TimestampUnit : base::type::EnumType { - Microsecond = 0, Millisecond = 1, Second = 2, Minute = 3, Hour = 4, Day = 5 -}; -/// @brief Format flags used to determine specifiers that are active for performance improvements. -enum class FormatFlags : base::type::EnumType { - DateTime = 1 << 1, - LoggerId = 1 << 2, - File = 1 << 3, - Line = 1 << 4, - Location = 1 << 5, - Function = 1 << 6, - User = 1 << 7, - Host = 1 << 8, - LogMessage = 1 << 9, - VerboseLevel = 1 << 10, - AppName = 1 << 11, - ThreadId = 1 << 12, - Level = 1 << 13, - FileBase = 1 << 14, - LevelShort = 1 << 15 -}; -/// @brief A subsecond precision class containing actual width and offset of the subsecond part -class SubsecondPrecision { - public: - SubsecondPrecision(void) { - init(base::consts::kDefaultSubsecondPrecision); - } - explicit SubsecondPrecision(int width) { - init(width); - } - bool operator==(const SubsecondPrecision& ssPrec) { - return m_width == ssPrec.m_width && m_offset == ssPrec.m_offset; - } - int m_width; - unsigned int m_offset; - private: - void init(int width); -}; -/// @brief Type alias of SubsecondPrecision -typedef SubsecondPrecision MillisecondsWidth; -/// @brief Namespace containing utility functions/static classes used internally -namespace utils { -/// @brief Deletes memory safely and points to null -template -static -typename std::enable_if::value, void>::type -safeDelete(T*& pointer) { - if (pointer == nullptr) - return; - delete pointer; - pointer = nullptr; -} -/// @brief Bitwise operations for C++11 strong enum class. This casts e into Flag_T and returns value after bitwise operation -/// Use these function as
flag = bitwise::Or(MyEnum::val1, flag);
-namespace bitwise { -template -static inline base::type::EnumType And(Enum e, base::type::EnumType flag) { - return static_cast(flag) & static_cast(e); -} -template -static inline base::type::EnumType Not(Enum e, base::type::EnumType flag) { - return static_cast(flag) & ~(static_cast(e)); -} -template -static inline base::type::EnumType Or(Enum e, base::type::EnumType flag) { - return static_cast(flag) | static_cast(e); -} -} // namespace bitwise -template -static inline void addFlag(Enum e, base::type::EnumType* flag) { - *flag = base::utils::bitwise::Or(e, *flag); -} -template -static inline void removeFlag(Enum e, base::type::EnumType* flag) { - *flag = base::utils::bitwise::Not(e, *flag); -} -template -static inline bool hasFlag(Enum e, base::type::EnumType flag) { - return base::utils::bitwise::And(e, flag) > 0x0; -} -} // namespace utils -namespace threading { -#if ELPP_THREADING_ENABLED -# if !ELPP_USE_STD_THREADING -namespace internal { -/// @brief A mutex wrapper for compiler that dont yet support std::recursive_mutex -class Mutex : base::NoCopy { - public: - Mutex(void) { -# if ELPP_OS_UNIX - pthread_mutexattr_t attr; - pthread_mutexattr_init(&attr); - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); - pthread_mutex_init(&m_underlyingMutex, &attr); - pthread_mutexattr_destroy(&attr); -# elif ELPP_OS_WINDOWS - InitializeCriticalSection(&m_underlyingMutex); -# endif // ELPP_OS_UNIX - } - - virtual ~Mutex(void) { -# if ELPP_OS_UNIX - pthread_mutex_destroy(&m_underlyingMutex); -# elif ELPP_OS_WINDOWS - DeleteCriticalSection(&m_underlyingMutex); -# endif // ELPP_OS_UNIX - } - - inline void lock(void) { -# if ELPP_OS_UNIX - pthread_mutex_lock(&m_underlyingMutex); -# elif ELPP_OS_WINDOWS - EnterCriticalSection(&m_underlyingMutex); -# endif // ELPP_OS_UNIX - } - - inline bool try_lock(void) { -# if ELPP_OS_UNIX - return (pthread_mutex_trylock(&m_underlyingMutex) == 0); -# elif ELPP_OS_WINDOWS - return TryEnterCriticalSection(&m_underlyingMutex); -# endif // ELPP_OS_UNIX - } - - inline void unlock(void) { -# if ELPP_OS_UNIX - pthread_mutex_unlock(&m_underlyingMutex); -# elif ELPP_OS_WINDOWS - LeaveCriticalSection(&m_underlyingMutex); -# endif // ELPP_OS_UNIX - } - - private: -# if ELPP_OS_UNIX - pthread_mutex_t m_underlyingMutex; -# elif ELPP_OS_WINDOWS - CRITICAL_SECTION m_underlyingMutex; -# endif // ELPP_OS_UNIX -}; -/// @brief Scoped lock for compiler that dont yet support std::lock_guard -template -class ScopedLock : base::NoCopy { - public: - explicit ScopedLock(M& mutex) { - m_mutex = &mutex; - m_mutex->lock(); - } - - virtual ~ScopedLock(void) { - m_mutex->unlock(); - } - private: - M* m_mutex; - ScopedLock(void); -}; -} // namespace internal -typedef base::threading::internal::Mutex Mutex; -typedef base::threading::internal::ScopedLock ScopedLock; -# else -typedef std::recursive_mutex Mutex; -typedef std::lock_guard ScopedLock; -# endif // !ELPP_USE_STD_THREADING -#else -namespace internal { -/// @brief Mutex wrapper used when multi-threading is disabled. -class NoMutex : base::NoCopy { - public: - NoMutex(void) {} - inline void lock(void) {} - inline bool try_lock(void) { - return true; - } - inline void unlock(void) {} -}; -/// @brief Lock guard wrapper used when multi-threading is disabled. -template -class NoScopedLock : base::NoCopy { - public: - explicit NoScopedLock(Mutex&) { - } - virtual ~NoScopedLock(void) { - } - private: - NoScopedLock(void); -}; -} // namespace internal -typedef base::threading::internal::NoMutex Mutex; -typedef base::threading::internal::NoScopedLock ScopedLock; -#endif // ELPP_THREADING_ENABLED -/// @brief Base of thread safe class, this class is inheritable-only -class ThreadSafe { - public: - virtual inline void acquireLock(void) ELPP_FINAL { m_mutex.lock(); } - virtual inline void releaseLock(void) ELPP_FINAL { m_mutex.unlock(); } - virtual inline base::threading::Mutex& lock(void) ELPP_FINAL { return m_mutex; } - protected: - ThreadSafe(void) {} - virtual ~ThreadSafe(void) {} - private: - base::threading::Mutex m_mutex; -}; - -#if ELPP_THREADING_ENABLED -# if !ELPP_USE_STD_THREADING -/// @brief Gets ID of currently running threading in windows systems. On unix, nothing is returned. -static std::string getCurrentThreadId(void) { - std::stringstream ss; -# if (ELPP_OS_WINDOWS) - ss << GetCurrentThreadId(); -# endif // (ELPP_OS_WINDOWS) - return ss.str(); -} -# else -/// @brief Gets ID of currently running threading using std::this_thread::get_id() -static std::string getCurrentThreadId(void) { - std::stringstream ss; - ss << std::this_thread::get_id(); - return ss.str(); -} -# endif // !ELPP_USE_STD_THREADING -#else -static inline std::string getCurrentThreadId(void) { - return std::string(); -} -#endif // ELPP_THREADING_ENABLED -} // namespace threading -namespace utils { -class File : base::StaticClass { - public: - /// @brief Creates new out file stream for specified filename. - /// @return Pointer to newly created fstream or nullptr - static base::type::fstream_t* newFileStream(const std::string& filename); - - /// @brief Gets size of file provided in stream - static std::size_t getSizeOfFile(base::type::fstream_t* fs); - - /// @brief Determines whether or not provided path exist in current file system - static bool pathExists(const char* path, bool considerFile = false); - - /// @brief Creates specified path on file system - /// @param path Path to create. - static bool createPath(const std::string& path); - /// @brief Extracts path of filename with leading slash - static std::string extractPathFromFilename(const std::string& fullPath, - const char* seperator = base::consts::kFilePathSeperator); - /// @brief builds stripped filename and puts it in buff - static void buildStrippedFilename(const char* filename, char buff[], - std::size_t limit = base::consts::kSourceFilenameMaxLength); - /// @brief builds base filename and puts it in buff - static void buildBaseFilename(const std::string& fullPath, char buff[], - std::size_t limit = base::consts::kSourceFilenameMaxLength, - const char* seperator = base::consts::kFilePathSeperator); -}; -/// @brief String utilities helper class used internally. You should not use it. -class Str : base::StaticClass { - public: - /// @brief Checks if character is digit. Dont use libc implementation of it to prevent locale issues. - static inline bool isDigit(char c) { - return c >= '0' && c <= '9'; - } - - /// @brief Matches wildcards, '*' and '?' only supported. - static bool wildCardMatch(const char* str, const char* pattern); - - static std::string& ltrim(std::string& str); - static std::string& rtrim(std::string& str); - static std::string& trim(std::string& str); - - /// @brief Determines whether or not str starts with specified string - /// @param str String to check - /// @param start String to check against - /// @return Returns true if starts with specified string, false otherwise - static bool startsWith(const std::string& str, const std::string& start); - - /// @brief Determines whether or not str ends with specified string - /// @param str String to check - /// @param end String to check against - /// @return Returns true if ends with specified string, false otherwise - static bool endsWith(const std::string& str, const std::string& end); - - /// @brief Replaces all instances of replaceWhat with 'replaceWith'. Original variable is changed for performance. - /// @param [in,out] str String to replace from - /// @param replaceWhat Character to replace - /// @param replaceWith Character to replace with - /// @return Modified version of str - static std::string& replaceAll(std::string& str, char replaceWhat, char replaceWith); - - /// @brief Replaces all instances of 'replaceWhat' with 'replaceWith'. (String version) Replaces in place - /// @param str String to replace from - /// @param replaceWhat Character to replace - /// @param replaceWith Character to replace with - /// @return Modified (original) str - static std::string& replaceAll(std::string& str, const std::string& replaceWhat, - const std::string& replaceWith); - - static void replaceFirstWithEscape(base::type::string_t& str, const base::type::string_t& replaceWhat, - const base::type::string_t& replaceWith); -#if defined(ELPP_UNICODE) - static void replaceFirstWithEscape(base::type::string_t& str, const base::type::string_t& replaceWhat, - const std::string& replaceWith); -#endif // defined(ELPP_UNICODE) - /// @brief Converts string to uppercase - /// @param str String to convert - /// @return Uppercase string - static std::string& toUpper(std::string& str); - - /// @brief Compares cstring equality - uses strcmp - static bool cStringEq(const char* s1, const char* s2); - - /// @brief Compares cstring equality (case-insensitive) - uses toupper(char) - /// Dont use strcasecmp because of CRT (VC++) - static bool cStringCaseEq(const char* s1, const char* s2); - - /// @brief Returns true if c exist in str - static bool contains(const char* str, char c); - - static char* convertAndAddToBuff(std::size_t n, int len, char* buf, const char* bufLim, bool zeroPadded = true); - static char* addToBuff(const char* str, char* buf, const char* bufLim); - static char* clearBuff(char buff[], std::size_t lim); - - /// @brief Converst wchar* to char* - /// NOTE: Need to free return value after use! - static char* wcharPtrToCharPtr(const wchar_t* line); -}; -/// @brief Operating System helper static class used internally. You should not use it. -class OS : base::StaticClass { - public: -#if ELPP_OS_WINDOWS - /// @brief Gets environment variables for Windows based OS. - /// We are not using getenv(const char*) because of CRT deprecation - /// @param varname Variable name to get environment variable value for - /// @return If variable exist the value of it otherwise nullptr - static const char* getWindowsEnvironmentVariable(const char* varname); -#endif // ELPP_OS_WINDOWS -#if ELPP_OS_ANDROID - /// @brief Reads android property value - static std::string getProperty(const char* prop); - - /// @brief Reads android device name - static std::string getDeviceName(void); -#endif // ELPP_OS_ANDROID - - /// @brief Runs command on terminal and returns the output. - /// - /// @detail This is applicable only on unix based systems, for all other OS, an empty string is returned. - /// @param command Bash command - /// @return Result of bash output or empty string if no result found. - static const std::string getBashOutput(const char* command); - - /// @brief Gets environment variable. This is cross-platform and CRT safe (for VC++) - /// @param variableName Environment variable name - /// @param defaultVal If no environment variable or value found the value to return by default - /// @param alternativeBashCommand If environment variable not found what would be alternative bash command - /// in order to look for value user is looking for. E.g, for 'user' alternative command will 'whoami' - static std::string getEnvironmentVariable(const char* variableName, const char* defaultVal, - const char* alternativeBashCommand = nullptr); - /// @brief Gets current username. - static std::string currentUser(void); - - /// @brief Gets current host name or computer name. - /// - /// @detail For android systems this is device name with its manufacturer and model seperated by hyphen - static std::string currentHost(void); - /// @brief Whether or not terminal supports colors - static bool termSupportsColor(void); -}; -/// @brief Contains utilities for cross-platform date/time. This class make use of el::base::utils::Str -class DateTime : base::StaticClass { - public: - /// @brief Cross platform gettimeofday for Windows and unix platform. This can be used to determine current microsecond. - /// - /// @detail For unix system it uses gettimeofday(timeval*, timezone*) and for Windows, a seperate implementation is provided - /// @param [in,out] tv Pointer that gets updated - static void gettimeofday(struct timeval* tv); - - /// @brief Gets current date and time with a subsecond part. - /// @param format User provided date/time format - /// @param ssPrec A pointer to base::SubsecondPrecision from configuration (non-null) - /// @returns string based date time in specified format. - static std::string getDateTime(const char* format, const base::SubsecondPrecision* ssPrec); - - /// @brief Converts timeval (struct from ctime) to string using specified format and subsecond precision - static std::string timevalToString(struct timeval tval, const char* format, - const el::base::SubsecondPrecision* ssPrec); - - /// @brief Formats time to get unit accordingly, units like second if > 1000 or minutes if > 60000 etc - static base::type::string_t formatTime(unsigned long long time, base::TimestampUnit timestampUnit); - - /// @brief Gets time difference in milli/micro second depending on timestampUnit - static unsigned long long getTimeDifference(const struct timeval& endTime, const struct timeval& startTime, - base::TimestampUnit timestampUnit); - - - static struct ::tm* buildTimeInfo(struct timeval* currTime, struct ::tm* timeInfo); - private: - static char* parseFormat(char* buf, std::size_t bufSz, const char* format, const struct tm* tInfo, - std::size_t msec, const base::SubsecondPrecision* ssPrec); -}; -/// @brief Command line arguments for application if specified using el::Helpers::setArgs(..) or START_EASYLOGGINGPP(..) -class CommandLineArgs { - public: - CommandLineArgs(void) { - setArgs(0, static_cast(nullptr)); - } - CommandLineArgs(int argc, const char** argv) { - setArgs(argc, argv); - } - CommandLineArgs(int argc, char** argv) { - setArgs(argc, argv); - } - virtual ~CommandLineArgs(void) {} - /// @brief Sets arguments and parses them - inline void setArgs(int argc, const char** argv) { - setArgs(argc, const_cast(argv)); - } - /// @brief Sets arguments and parses them - void setArgs(int argc, char** argv); - /// @brief Returns true if arguments contain paramKey with a value (seperated by '=') - bool hasParamWithValue(const char* paramKey) const; - /// @brief Returns value of arguments - /// @see hasParamWithValue(const char*) - const char* getParamValue(const char* paramKey) const; - /// @brief Return true if arguments has a param (not having a value) i,e without '=' - bool hasParam(const char* paramKey) const; - /// @brief Returns true if no params available. This exclude argv[0] - bool empty(void) const; - /// @brief Returns total number of arguments. This exclude argv[0] - std::size_t size(void) const; - friend base::type::ostream_t& operator<<(base::type::ostream_t& os, const CommandLineArgs& c); - - private: - int m_argc; - char** m_argv; - std::unordered_map m_paramsWithValue; - std::vector m_params; -}; -/// @brief Abstract registry (aka repository) that provides basic interface for pointer repository specified by T_Ptr type. -/// -/// @detail Most of the functions are virtual final methods but anything implementing this abstract class should implement -/// unregisterAll() and deepCopy(const AbstractRegistry&) and write registerNew() method according to container -/// and few more methods; get() to find element, unregister() to unregister single entry. -/// Please note that this is thread-unsafe and should also implement thread-safety mechanisms in implementation. -template -class AbstractRegistry : public base::threading::ThreadSafe { - public: - typedef typename Container::iterator iterator; - typedef typename Container::const_iterator const_iterator; - - /// @brief Default constructor - AbstractRegistry(void) {} - - /// @brief Move constructor that is useful for base classes - AbstractRegistry(AbstractRegistry&& sr) { - if (this == &sr) { - return; - } - unregisterAll(); - m_list = std::move(sr.m_list); - } - - bool operator==(const AbstractRegistry& other) { - if (size() != other.size()) { - return false; - } - for (std::size_t i = 0; i < m_list.size(); ++i) { - if (m_list.at(i) != other.m_list.at(i)) { - return false; - } - } - return true; - } - - bool operator!=(const AbstractRegistry& other) { - if (size() != other.size()) { - return true; - } - for (std::size_t i = 0; i < m_list.size(); ++i) { - if (m_list.at(i) != other.m_list.at(i)) { - return true; - } - } - return false; - } - - /// @brief Assignment move operator - AbstractRegistry& operator=(AbstractRegistry&& sr) { - if (this == &sr) { - return *this; - } - unregisterAll(); - m_list = std::move(sr.m_list); - return *this; - } - - virtual ~AbstractRegistry(void) { - } - - /// @return Iterator pointer from start of repository - virtual inline iterator begin(void) ELPP_FINAL { - return m_list.begin(); - } - - /// @return Iterator pointer from end of repository - virtual inline iterator end(void) ELPP_FINAL { - return m_list.end(); - } - - - /// @return Constant iterator pointer from start of repository - virtual inline const_iterator cbegin(void) const ELPP_FINAL { - return m_list.cbegin(); - } - - /// @return End of repository - virtual inline const_iterator cend(void) const ELPP_FINAL { - return m_list.cend(); - } - - /// @return Whether or not repository is empty - virtual inline bool empty(void) const ELPP_FINAL { - return m_list.empty(); - } - - /// @return Size of repository - virtual inline std::size_t size(void) const ELPP_FINAL { - return m_list.size(); - } - - /// @brief Returns underlying container by reference - virtual inline Container& list(void) ELPP_FINAL { - return m_list; - } - - /// @brief Returns underlying container by constant reference. - virtual inline const Container& list(void) const ELPP_FINAL { - return m_list; - } - - /// @brief Unregisters all the pointers from current repository. - virtual void unregisterAll(void) = 0; - - protected: - virtual void deepCopy(const AbstractRegistry&) = 0; - void reinitDeepCopy(const AbstractRegistry& sr) { - unregisterAll(); - deepCopy(sr); - } - - private: - Container m_list; -}; - -/// @brief A pointer registry mechanism to manage memory and provide search functionalities. (non-predicate version) -/// -/// @detail NOTE: This is thread-unsafe implementation (although it contains lock function, it does not use these functions) -/// of AbstractRegistry. Any implementation of this class should be -/// explicitly (by using lock functions) -template -class Registry : public AbstractRegistry> { - public: - typedef typename Registry::iterator iterator; - typedef typename Registry::const_iterator const_iterator; - - Registry(void) {} - - /// @brief Copy constructor that is useful for base classes. Try to avoid this constructor, use move constructor. - Registry(const Registry& sr) : AbstractRegistry>() { - if (this == &sr) { - return; - } - this->reinitDeepCopy(sr); - } - - /// @brief Assignment operator that unregisters all the existing registeries and deeply copies each of repo element - /// @see unregisterAll() - /// @see deepCopy(const AbstractRegistry&) - Registry& operator=(const Registry& sr) { - if (this == &sr) { - return *this; - } - this->reinitDeepCopy(sr); - return *this; - } - - virtual ~Registry(void) { - unregisterAll(); - } - - protected: - virtual void unregisterAll(void) ELPP_FINAL { - if (!this->empty()) { - for (auto&& curr : this->list()) { - base::utils::safeDelete(curr.second); - } - this->list().clear(); - } - } - -/// @brief Registers new registry to repository. - virtual void registerNew(const T_Key& uniqKey, T_Ptr* ptr) ELPP_FINAL { - unregister(uniqKey); - this->list().insert(std::make_pair(uniqKey, ptr)); - } - -/// @brief Unregisters single entry mapped to specified unique key - void unregister(const T_Key& uniqKey) { - T_Ptr* existing = get(uniqKey); - if (existing != nullptr) { - this->list().erase(uniqKey); - base::utils::safeDelete(existing); - } - } - -/// @brief Gets pointer from repository. If none found, nullptr is returned. - T_Ptr* get(const T_Key& uniqKey) { - iterator it = this->list().find(uniqKey); - return it == this->list().end() - ? nullptr - : it->second; - } - - private: - virtual void deepCopy(const AbstractRegistry>& sr) ELPP_FINAL { - for (const_iterator it = sr.cbegin(); it != sr.cend(); ++it) { - registerNew(it->first, new T_Ptr(*it->second)); - } - } -}; - -/// @brief A pointer registry mechanism to manage memory and provide search functionalities. (predicate version) -/// -/// @detail NOTE: This is thread-unsafe implementation of AbstractRegistry. Any implementation of this class -/// should be made thread-safe explicitly -template -class RegistryWithPred : public AbstractRegistry> { - public: - typedef typename RegistryWithPred::iterator iterator; - typedef typename RegistryWithPred::const_iterator const_iterator; - - RegistryWithPred(void) { - } - - virtual ~RegistryWithPred(void) { - unregisterAll(); - } - - /// @brief Copy constructor that is useful for base classes. Try to avoid this constructor, use move constructor. - RegistryWithPred(const RegistryWithPred& sr) : AbstractRegistry>() { - if (this == &sr) { - return; - } - this->reinitDeepCopy(sr); - } - - /// @brief Assignment operator that unregisters all the existing registeries and deeply copies each of repo element - /// @see unregisterAll() - /// @see deepCopy(const AbstractRegistry&) - RegistryWithPred& operator=(const RegistryWithPred& sr) { - if (this == &sr) { - return *this; - } - this->reinitDeepCopy(sr); - return *this; - } - - friend base::type::ostream_t& operator<<(base::type::ostream_t& os, const RegistryWithPred& sr) { - for (const_iterator it = sr.list().begin(); it != sr.list().end(); ++it) { - os << ELPP_LITERAL(" ") << **it << ELPP_LITERAL("\n"); - } - return os; - } - - protected: - virtual void unregisterAll(void) ELPP_FINAL { - if (!this->empty()) { - for (auto&& curr : this->list()) { - base::utils::safeDelete(curr); - } - this->list().clear(); - } - } - - virtual void unregister(T_Ptr*& ptr) ELPP_FINAL { - if (ptr) { - iterator iter = this->begin(); - for (; iter != this->end(); ++iter) { - if (ptr == *iter) { - break; - } - } - if (iter != this->end() && *iter != nullptr) { - this->list().erase(iter); - base::utils::safeDelete(*iter); - } - } - } - - virtual inline void registerNew(T_Ptr* ptr) ELPP_FINAL { - this->list().push_back(ptr); - } - -/// @brief Gets pointer from repository with speicifed arguments. Arguments are passed to predicate -/// in order to validate pointer. - template - T_Ptr* get(const T& arg1, const T2 arg2) { - iterator iter = std::find_if(this->list().begin(), this->list().end(), Pred(arg1, arg2)); - if (iter != this->list().end() && *iter != nullptr) { - return *iter; - } - return nullptr; - } - - private: - virtual void deepCopy(const AbstractRegistry>& sr) { - for (const_iterator it = sr.list().begin(); it != sr.list().end(); ++it) { - registerNew(new T_Ptr(**it)); - } - } -}; -class Utils { - public: - template - static bool installCallback(const std::string& id, std::unordered_map* mapT) { - if (mapT->find(id) == mapT->end()) { - mapT->insert(std::make_pair(id, TPtr(new T()))); - return true; - } - return false; - } - - template - static void uninstallCallback(const std::string& id, std::unordered_map* mapT) { - if (mapT->find(id) != mapT->end()) { - mapT->erase(id); - } - } - - template - static T* callback(const std::string& id, std::unordered_map* mapT) { - typename std::unordered_map::iterator iter = mapT->find(id); - if (iter != mapT->end()) { - return static_cast(iter->second.get()); - } - return nullptr; - } -}; -} // namespace utils -} // namespace base -/// @brief Base of Easylogging++ friendly class -/// -/// @detail After inheriting this class publicly, implement pure-virtual function `void log(std::ostream&) const` -class Loggable { - public: - virtual ~Loggable(void) {} - virtual void log(el::base::type::ostream_t&) const = 0; - private: - friend inline el::base::type::ostream_t& operator<<(el::base::type::ostream_t& os, const Loggable& loggable) { - loggable.log(os); - return os; - } -}; -namespace base { -/// @brief Represents log format containing flags and date format. This is used internally to start initial log -class LogFormat : public Loggable { - public: - LogFormat(void); - LogFormat(Level level, const base::type::string_t& format); - LogFormat(const LogFormat& logFormat); - LogFormat(LogFormat&& logFormat); - LogFormat& operator=(const LogFormat& logFormat); - virtual ~LogFormat(void) {} - bool operator==(const LogFormat& other); - - /// @brief Updates format to be used while logging. - /// @param userFormat User provided format - void parseFromFormat(const base::type::string_t& userFormat); - - inline Level level(void) const { - return m_level; - } - - inline const base::type::string_t& userFormat(void) const { - return m_userFormat; - } - - inline const base::type::string_t& format(void) const { - return m_format; - } - - inline const std::string& dateTimeFormat(void) const { - return m_dateTimeFormat; - } - - inline base::type::EnumType flags(void) const { - return m_flags; - } - - inline bool hasFlag(base::FormatFlags flag) const { - return base::utils::hasFlag(flag, m_flags); - } - - virtual void log(el::base::type::ostream_t& os) const { - os << m_format; - } - - protected: - /// @brief Updates date time format if available in currFormat. - /// @param index Index where %datetime, %date or %time was found - /// @param [in,out] currFormat current format that is being used to format - virtual void updateDateFormat(std::size_t index, base::type::string_t& currFormat) ELPP_FINAL; - - /// @brief Updates %level from format. This is so that we dont have to do it at log-writing-time. It uses m_format and m_level - virtual void updateFormatSpec(void) ELPP_FINAL; - - inline void addFlag(base::FormatFlags flag) { - base::utils::addFlag(flag, &m_flags); - } - - private: - Level m_level; - base::type::string_t m_userFormat; - base::type::string_t m_format; - std::string m_dateTimeFormat; - base::type::EnumType m_flags; - std::string m_currentUser; - std::string m_currentHost; - friend class el::Logger; // To resolve loggerId format specifier easily -}; -} // namespace base -/// @brief Resolving function for format specifier -typedef std::function FormatSpecifierValueResolver; -/// @brief User-provided custom format specifier -/// @see el::Helpers::installCustomFormatSpecifier -/// @see FormatSpecifierValueResolver -class CustomFormatSpecifier { - public: - CustomFormatSpecifier(const char* formatSpecifier, const FormatSpecifierValueResolver& resolver) : - m_formatSpecifier(formatSpecifier), m_resolver(resolver) {} - inline const char* formatSpecifier(void) const { - return m_formatSpecifier; - } - inline const FormatSpecifierValueResolver& resolver(void) const { - return m_resolver; - } - inline bool operator==(const char* formatSpecifier) { - return strcmp(m_formatSpecifier, formatSpecifier) == 0; - } - - private: - const char* m_formatSpecifier; - FormatSpecifierValueResolver m_resolver; -}; -/// @brief Represents single configuration that has representing level, configuration type and a string based value. -/// -/// @detail String based value means any value either its boolean, integer or string itself, it will be embedded inside quotes -/// and will be parsed later. -/// -/// Consider some examples below: -/// * el::Configuration confEnabledInfo(el::Level::Info, el::ConfigurationType::Enabled, "true"); -/// * el::Configuration confMaxLogFileSizeInfo(el::Level::Info, el::ConfigurationType::MaxLogFileSize, "2048"); -/// * el::Configuration confFilenameInfo(el::Level::Info, el::ConfigurationType::Filename, "/var/log/my.log"); -class Configuration : public Loggable { - public: - Configuration(const Configuration& c); - Configuration& operator=(const Configuration& c); - - virtual ~Configuration(void) { - } - - /// @brief Full constructor used to sets value of configuration - Configuration(Level level, ConfigurationType configurationType, const std::string& value); - - /// @brief Gets level of current configuration - inline Level level(void) const { - return m_level; - } - - /// @brief Gets configuration type of current configuration - inline ConfigurationType configurationType(void) const { - return m_configurationType; - } - - /// @brief Gets string based configuration value - inline const std::string& value(void) const { - return m_value; - } - - /// @brief Set string based configuration value - /// @param value Value to set. Values have to be std::string; For boolean values use "true", "false", for any integral values - /// use them in quotes. They will be parsed when configuring - inline void setValue(const std::string& value) { - m_value = value; - } - - virtual void log(el::base::type::ostream_t& os) const; - - /// @brief Used to find configuration from configuration (pointers) repository. Avoid using it. - class Predicate { - public: - Predicate(Level level, ConfigurationType configurationType); - - bool operator()(const Configuration* conf) const; - - private: - Level m_level; - ConfigurationType m_configurationType; - }; - - private: - Level m_level; - ConfigurationType m_configurationType; - std::string m_value; -}; - -/// @brief Thread-safe Configuration repository -/// -/// @detail This repository represents configurations for all the levels and configuration type mapped to a value. -class Configurations : public base::utils::RegistryWithPred { - public: - /// @brief Default constructor with empty repository - Configurations(void); - - /// @brief Constructor used to set configurations using configuration file. - /// @param configurationFile Full path to configuration file - /// @param useDefaultsForRemaining Lets you set the remaining configurations to default. - /// @param base If provided, this configuration will be based off existing repository that this argument is pointing to. - /// @see parseFromFile(const std::string&, Configurations* base) - /// @see setRemainingToDefault() - Configurations(const std::string& configurationFile, bool useDefaultsForRemaining = true, - Configurations* base = nullptr); - - virtual ~Configurations(void) { - } - - /// @brief Parses configuration from file. - /// @param configurationFile Full path to configuration file - /// @param base Configurations to base new configuration repository off. This value is used when you want to use - /// existing Configurations to base all the values and then set rest of configuration via configuration file. - /// @return True if successfully parsed, false otherwise. You may define 'ELPP_DEBUG_ASSERT_FAILURE' to make sure you - /// do not proceed without successful parse. - bool parseFromFile(const std::string& configurationFile, Configurations* base = nullptr); - - /// @brief Parse configurations from configuration string. - /// - /// @detail This configuration string has same syntax as configuration file contents. Make sure all the necessary - /// new line characters are provided. - /// @param base Configurations to base new configuration repository off. This value is used when you want to use - /// existing Configurations to base all the values and then set rest of configuration via configuration text. - /// @return True if successfully parsed, false otherwise. You may define 'ELPP_DEBUG_ASSERT_FAILURE' to make sure you - /// do not proceed without successful parse. - bool parseFromText(const std::string& configurationsString, Configurations* base = nullptr); - - /// @brief Sets configuration based-off an existing configurations. - /// @param base Pointer to existing configurations. - void setFromBase(Configurations* base); - - /// @brief Determines whether or not specified configuration type exists in the repository. - /// - /// @detail Returns as soon as first level is found. - /// @param configurationType Type of configuration to check existence for. - bool hasConfiguration(ConfigurationType configurationType); - - /// @brief Determines whether or not specified configuration type exists for specified level - /// @param level Level to check - /// @param configurationType Type of configuration to check existence for. - bool hasConfiguration(Level level, ConfigurationType configurationType); - - /// @brief Sets value of configuration for specified level. - /// - /// @detail Any existing configuration for specified level will be replaced. Also note that configuration types - /// ConfigurationType::SubsecondPrecision and ConfigurationType::PerformanceTracking will be ignored if not set for - /// Level::Global because these configurations are not dependant on level. - /// @param level Level to set configuration for (el::Level). - /// @param configurationType Type of configuration (el::ConfigurationType) - /// @param value A string based value. Regardless of what the data type of configuration is, it will always be string - /// from users' point of view. This is then parsed later to be used internally. - /// @see Configuration::setValue(const std::string& value) - /// @see el::Level - /// @see el::ConfigurationType - void set(Level level, ConfigurationType configurationType, const std::string& value); - - /// @brief Sets single configuration based on other single configuration. - /// @see set(Level level, ConfigurationType configurationType, const std::string& value) - void set(Configuration* conf); - - inline Configuration* get(Level level, ConfigurationType configurationType) { - base::threading::ScopedLock scopedLock(lock()); - return RegistryWithPred::get(level, configurationType); - } - - /// @brief Sets configuration for all levels. - /// @param configurationType Type of configuration - /// @param value String based value - /// @see Configurations::set(Level level, ConfigurationType configurationType, const std::string& value) - inline void setGlobally(ConfigurationType configurationType, const std::string& value) { - setGlobally(configurationType, value, false); - } - - /// @brief Clears repository so that all the configurations are unset - inline void clear(void) { - base::threading::ScopedLock scopedLock(lock()); - unregisterAll(); - } - - /// @brief Gets configuration file used in parsing this configurations. - /// - /// @detail If this repository was set manually or by text this returns empty string. - inline const std::string& configurationFile(void) const { - return m_configurationFile; - } - - /// @brief Sets configurations to "factory based" configurations. - void setToDefault(void); - - /// @brief Lets you set the remaining configurations to default. - /// - /// @detail By remaining, it means that the level/type a configuration does not exist for. - /// This function is useful when you want to minimize chances of failures, e.g, if you have a configuration file that sets - /// configuration for all the configurations except for Enabled or not, we use this so that ENABLED is set to default i.e, - /// true. If you dont do this explicitly (either by calling this function or by using second param in Constructor - /// and try to access a value, an error is thrown - void setRemainingToDefault(void); - - /// @brief Parser used internally to parse configurations from file or text. - /// - /// @detail This class makes use of base::utils::Str. - /// You should not need this unless you are working on some tool for Easylogging++ - class Parser : base::StaticClass { - public: - /// @brief Parses configuration from file. - /// @param configurationFile Full path to configuration file - /// @param sender Sender configurations pointer. Usually 'this' is used from calling class - /// @param base Configurations to base new configuration repository off. This value is used when you want to use - /// existing Configurations to base all the values and then set rest of configuration via configuration file. - /// @return True if successfully parsed, false otherwise. You may define '_STOP_ON_FIRSTELPP_ASSERTION' to make sure you - /// do not proceed without successful parse. - static bool parseFromFile(const std::string& configurationFile, Configurations* sender, - Configurations* base = nullptr); - - /// @brief Parse configurations from configuration string. - /// - /// @detail This configuration string has same syntax as configuration file contents. Make sure all the necessary - /// new line characters are provided. You may define '_STOP_ON_FIRSTELPP_ASSERTION' to make sure you - /// do not proceed without successful parse (This is recommended) - /// @param configurationsString the configuration in plain text format - /// @param sender Sender configurations pointer. Usually 'this' is used from calling class - /// @param base Configurations to base new configuration repository off. This value is used when you want to use - /// existing Configurations to base all the values and then set rest of configuration via configuration text. - /// @return True if successfully parsed, false otherwise. - static bool parseFromText(const std::string& configurationsString, Configurations* sender, - Configurations* base = nullptr); - - private: - friend class el::Loggers; - static void ignoreComments(std::string* line); - static bool isLevel(const std::string& line); - static bool isComment(const std::string& line); - static inline bool isConfig(const std::string& line); - static bool parseLine(std::string* line, std::string* currConfigStr, std::string* currLevelStr, Level* currLevel, - Configurations* conf); - }; - - private: - std::string m_configurationFile; - bool m_isFromFile; - friend class el::Loggers; - - /// @brief Unsafely sets configuration if does not already exist - void unsafeSetIfNotExist(Level level, ConfigurationType configurationType, const std::string& value); - - /// @brief Thread unsafe set - void unsafeSet(Level level, ConfigurationType configurationType, const std::string& value); - - /// @brief Sets configurations for all levels including Level::Global if includeGlobalLevel is true - /// @see Configurations::setGlobally(ConfigurationType configurationType, const std::string& value) - void setGlobally(ConfigurationType configurationType, const std::string& value, bool includeGlobalLevel); - - /// @brief Sets configurations (Unsafely) for all levels including Level::Global if includeGlobalLevel is true - /// @see Configurations::setGlobally(ConfigurationType configurationType, const std::string& value) - void unsafeSetGlobally(ConfigurationType configurationType, const std::string& value, bool includeGlobalLevel); -}; - -namespace base { -typedef std::shared_ptr FileStreamPtr; -typedef std::unordered_map LogStreamsReferenceMap; -/// @brief Configurations with data types. -/// -/// @detail el::Configurations have string based values. This is whats used internally in order to read correct configurations. -/// This is to perform faster while writing logs using correct configurations. -/// -/// This is thread safe and final class containing non-virtual destructor (means nothing should inherit this class) -class TypedConfigurations : public base::threading::ThreadSafe { - public: - /// @brief Constructor to initialize (construct) the object off el::Configurations - /// @param configurations Configurations pointer/reference to base this typed configurations off. - /// @param logStreamsReference Use ELPP->registeredLoggers()->logStreamsReference() - TypedConfigurations(Configurations* configurations, base::LogStreamsReferenceMap* logStreamsReference); - - TypedConfigurations(const TypedConfigurations& other); - - virtual ~TypedConfigurations(void) { - } - - const Configurations* configurations(void) const { - return m_configurations; - } - - bool enabled(Level level); - bool toFile(Level level); - const std::string& filename(Level level); - bool toStandardOutput(Level level); - const base::LogFormat& logFormat(Level level); - const base::SubsecondPrecision& subsecondPrecision(Level level = Level::Global); - const base::MillisecondsWidth& millisecondsWidth(Level level = Level::Global); - bool performanceTracking(Level level = Level::Global); - base::type::fstream_t* fileStream(Level level); - std::size_t maxLogFileSize(Level level); - std::size_t logFlushThreshold(Level level); - - private: - Configurations* m_configurations; - std::unordered_map m_enabledMap; - std::unordered_map m_toFileMap; - std::unordered_map m_filenameMap; - std::unordered_map m_toStandardOutputMap; - std::unordered_map m_logFormatMap; - std::unordered_map m_subsecondPrecisionMap; - std::unordered_map m_performanceTrackingMap; - std::unordered_map m_fileStreamMap; - std::unordered_map m_maxLogFileSizeMap; - std::unordered_map m_logFlushThresholdMap; - base::LogStreamsReferenceMap* m_logStreamsReference; - - friend class el::Helpers; - friend class el::base::MessageBuilder; - friend class el::base::Writer; - friend class el::base::DefaultLogDispatchCallback; - friend class el::base::LogDispatcher; - - template - inline Conf_T getConfigByVal(Level level, const std::unordered_map* confMap, const char* confName) { - base::threading::ScopedLock scopedLock(lock()); - return unsafeGetConfigByVal(level, confMap, confName); // This is not unsafe anymore - mutex locked in scope - } - - template - inline Conf_T& getConfigByRef(Level level, std::unordered_map* confMap, const char* confName) { - base::threading::ScopedLock scopedLock(lock()); - return unsafeGetConfigByRef(level, confMap, confName); // This is not unsafe anymore - mutex locked in scope - } - - template - Conf_T unsafeGetConfigByVal(Level level, const std::unordered_map* confMap, const char* confName) { - ELPP_UNUSED(confName); - typename std::unordered_map::const_iterator it = confMap->find(level); - if (it == confMap->end()) { - try { - return confMap->at(Level::Global); - } catch (...) { - ELPP_INTERNAL_ERROR("Unable to get configuration [" << confName << "] for level [" - << LevelHelper::convertToString(level) << "]" - << std::endl << "Please ensure you have properly configured logger.", false); - return Conf_T(); - } - } - return it->second; - } - - template - Conf_T& unsafeGetConfigByRef(Level level, std::unordered_map* confMap, const char* confName) { - ELPP_UNUSED(confName); - typename std::unordered_map::iterator it = confMap->find(level); - if (it == confMap->end()) { - try { - return confMap->at(Level::Global); - } catch (...) { - ELPP_INTERNAL_ERROR("Unable to get configuration [" << confName << "] for level [" - << LevelHelper::convertToString(level) << "]" - << std::endl << "Please ensure you have properly configured logger.", false); - } - } - return it->second; - } - - template - void setValue(Level level, const Conf_T& value, std::unordered_map* confMap, - bool includeGlobalLevel = true) { - // If map is empty and we are allowed to add into generic level (Level::Global), do it! - if (confMap->empty() && includeGlobalLevel) { - confMap->insert(std::make_pair(Level::Global, value)); - return; - } - // If same value exist in generic level already, dont add it to explicit level - typename std::unordered_map::iterator it = confMap->find(Level::Global); - if (it != confMap->end() && it->second == value) { - return; - } - // Now make sure we dont double up values if we really need to add it to explicit level - it = confMap->find(level); - if (it == confMap->end()) { - // Value not found for level, add new - confMap->insert(std::make_pair(level, value)); - } else { - // Value found, just update value - confMap->at(level) = value; - } - } - - void build(Configurations* configurations); - unsigned long getULong(std::string confVal); - std::string resolveFilename(const std::string& filename); - void insertFile(Level level, const std::string& fullFilename); - bool unsafeValidateFileRolling(Level level, const PreRollOutCallback& preRollOutCallback); - - inline bool validateFileRolling(Level level, const PreRollOutCallback& preRollOutCallback) { - base::threading::ScopedLock scopedLock(lock()); - return unsafeValidateFileRolling(level, preRollOutCallback); - } -}; -/// @brief Class that keeps record of current line hit for occasional logging -class HitCounter { - public: - HitCounter(void) : - m_filename(""), - m_lineNumber(0), - m_hitCounts(0) { - } - - HitCounter(const char* filename, base::type::LineNumber lineNumber) : - m_filename(filename), - m_lineNumber(lineNumber), - m_hitCounts(0) { - } - - HitCounter(const HitCounter& hitCounter) : - m_filename(hitCounter.m_filename), - m_lineNumber(hitCounter.m_lineNumber), - m_hitCounts(hitCounter.m_hitCounts) { - } - - HitCounter& operator=(const HitCounter& hitCounter) { - if (&hitCounter != this) { - m_filename = hitCounter.m_filename; - m_lineNumber = hitCounter.m_lineNumber; - m_hitCounts = hitCounter.m_hitCounts; - } - return *this; - } - - virtual ~HitCounter(void) { - } - - /// @brief Resets location of current hit counter - inline void resetLocation(const char* filename, base::type::LineNumber lineNumber) { - m_filename = filename; - m_lineNumber = lineNumber; - } - - /// @brief Validates hit counts and resets it if necessary - inline void validateHitCounts(std::size_t n) { - if (m_hitCounts >= base::consts::kMaxLogPerCounter) { - m_hitCounts = (n >= 1 ? base::consts::kMaxLogPerCounter % n : 0); - } - ++m_hitCounts; - } - - inline const char* filename(void) const { - return m_filename; - } - - inline base::type::LineNumber lineNumber(void) const { - return m_lineNumber; - } - - inline std::size_t hitCounts(void) const { - return m_hitCounts; - } - - inline void increment(void) { - ++m_hitCounts; - } - - class Predicate { - public: - Predicate(const char* filename, base::type::LineNumber lineNumber) - : m_filename(filename), - m_lineNumber(lineNumber) { - } - inline bool operator()(const HitCounter* counter) { - return ((counter != nullptr) && - (strcmp(counter->m_filename, m_filename) == 0) && - (counter->m_lineNumber == m_lineNumber)); - } - - private: - const char* m_filename; - base::type::LineNumber m_lineNumber; - }; - - private: - const char* m_filename; - base::type::LineNumber m_lineNumber; - std::size_t m_hitCounts; -}; -/// @brief Repository for hit counters used across the application -class RegisteredHitCounters : public base::utils::RegistryWithPred { - public: - /// @brief Validates counter for every N, i.e, registers new if does not exist otherwise updates original one - /// @return True if validation resulted in triggering hit. Meaning logs should be written everytime true is returned - bool validateEveryN(const char* filename, base::type::LineNumber lineNumber, std::size_t n); - - /// @brief Validates counter for hits >= N, i.e, registers new if does not exist otherwise updates original one - /// @return True if validation resulted in triggering hit. Meaning logs should be written everytime true is returned - bool validateAfterN(const char* filename, base::type::LineNumber lineNumber, std::size_t n); - - /// @brief Validates counter for hits are <= n, i.e, registers new if does not exist otherwise updates original one - /// @return True if validation resulted in triggering hit. Meaning logs should be written everytime true is returned - bool validateNTimes(const char* filename, base::type::LineNumber lineNumber, std::size_t n); - - /// @brief Gets hit counter registered at specified position - inline const base::HitCounter* getCounter(const char* filename, base::type::LineNumber lineNumber) { - base::threading::ScopedLock scopedLock(lock()); - return get(filename, lineNumber); - } -}; -/// @brief Action to be taken for dispatching -enum class DispatchAction : base::type::EnumType { - None = 1, NormalLog = 2, SysLog = 4 -}; -} // namespace base -template -class Callback : protected base::threading::ThreadSafe { - public: - Callback(void) : m_enabled(true) {} - inline bool enabled(void) const { - return m_enabled; - } - inline void setEnabled(bool enabled) { - base::threading::ScopedLock scopedLock(lock()); - m_enabled = enabled; - } - protected: - virtual void handle(const T* handlePtr) = 0; - private: - bool m_enabled; -}; -class LogDispatchData { - public: - LogDispatchData() : m_logMessage(nullptr), m_dispatchAction(base::DispatchAction::None) {} - inline const LogMessage* logMessage(void) const { - return m_logMessage; - } - inline base::DispatchAction dispatchAction(void) const { - return m_dispatchAction; - } - inline void setLogMessage(LogMessage* logMessage) { - m_logMessage = logMessage; - } - inline void setDispatchAction(base::DispatchAction dispatchAction) { - m_dispatchAction = dispatchAction; - } - private: - LogMessage* m_logMessage; - base::DispatchAction m_dispatchAction; - friend class base::LogDispatcher; - -}; -class LogDispatchCallback : public Callback { - protected: - virtual void handle(const LogDispatchData* data); - base::threading::Mutex& fileHandle(const LogDispatchData* data); - private: - friend class base::LogDispatcher; - std::unordered_map> m_fileLocks; - base::threading::Mutex m_fileLocksMapLock; -}; -class PerformanceTrackingCallback : public Callback { - private: - friend class base::PerformanceTracker; -}; -class LoggerRegistrationCallback : public Callback { - private: - friend class base::RegisteredLoggers; -}; -class LogBuilder : base::NoCopy { - public: - LogBuilder() : m_termSupportsColor(base::utils::OS::termSupportsColor()) {} - virtual ~LogBuilder(void) { - ELPP_INTERNAL_INFO(3, "Destroying log builder...") - } - virtual base::type::string_t build(const LogMessage* logMessage, bool appendNewLine) const = 0; - void convertToColoredOutput(base::type::string_t* logLine, Level level); - private: - bool m_termSupportsColor; - friend class el::base::DefaultLogDispatchCallback; -}; -typedef std::shared_ptr LogBuilderPtr; -/// @brief Represents a logger holding ID and configurations we need to write logs -/// -/// @detail This class does not write logs itself instead its used by writer to read configuations from. -class Logger : public base::threading::ThreadSafe, public Loggable { - public: - Logger(const std::string& id, base::LogStreamsReferenceMap* logStreamsReference); - Logger(const std::string& id, const Configurations& configurations, base::LogStreamsReferenceMap* logStreamsReference); - Logger(const Logger& logger); - Logger& operator=(const Logger& logger); - - virtual ~Logger(void) { - base::utils::safeDelete(m_typedConfigurations); - } - - virtual inline void log(el::base::type::ostream_t& os) const { - os << m_id.c_str(); - } - - /// @brief Configures the logger using specified configurations. - void configure(const Configurations& configurations); - - /// @brief Reconfigures logger using existing configurations - void reconfigure(void); - - inline const std::string& id(void) const { - return m_id; - } - - inline const std::string& parentApplicationName(void) const { - return m_parentApplicationName; - } - - inline void setParentApplicationName(const std::string& parentApplicationName) { - m_parentApplicationName = parentApplicationName; - } - - inline Configurations* configurations(void) { - return &m_configurations; - } - - inline base::TypedConfigurations* typedConfigurations(void) { - return m_typedConfigurations; - } - - static bool isValidId(const std::string& id); - - /// @brief Flushes logger to sync all log files for all levels - void flush(void); - - void flush(Level level, base::type::fstream_t* fs); - - inline bool isFlushNeeded(Level level) { - return ++m_unflushedCount.find(level)->second >= m_typedConfigurations->logFlushThreshold(level); - } - - inline LogBuilder* logBuilder(void) const { - return m_logBuilder.get(); - } - - inline void setLogBuilder(const LogBuilderPtr& logBuilder) { - m_logBuilder = logBuilder; - } - - inline bool enabled(Level level) const { - return m_typedConfigurations->enabled(level); - } - -#if ELPP_VARIADIC_TEMPLATES_SUPPORTED -# define LOGGER_LEVEL_WRITERS_SIGNATURES(FUNCTION_NAME)\ -template \ -inline void FUNCTION_NAME(const char*, const T&, const Args&...);\ -template \ -inline void FUNCTION_NAME(const T&); - - template - inline void verbose(int, const char*, const T&, const Args&...); - - template - inline void verbose(int, const T&); - - LOGGER_LEVEL_WRITERS_SIGNATURES(info) - LOGGER_LEVEL_WRITERS_SIGNATURES(debug) - LOGGER_LEVEL_WRITERS_SIGNATURES(warn) - LOGGER_LEVEL_WRITERS_SIGNATURES(error) - LOGGER_LEVEL_WRITERS_SIGNATURES(fatal) - LOGGER_LEVEL_WRITERS_SIGNATURES(trace) -# undef LOGGER_LEVEL_WRITERS_SIGNATURES -#endif // ELPP_VARIADIC_TEMPLATES_SUPPORTED - private: - std::string m_id; - base::TypedConfigurations* m_typedConfigurations; - base::type::stringstream_t m_stream; - std::string m_parentApplicationName; - bool m_isConfigured; - Configurations m_configurations; - std::unordered_map m_unflushedCount; - base::LogStreamsReferenceMap* m_logStreamsReference; - LogBuilderPtr m_logBuilder; - - friend class el::LogMessage; - friend class el::Loggers; - friend class el::Helpers; - friend class el::base::RegisteredLoggers; - friend class el::base::DefaultLogDispatchCallback; - friend class el::base::MessageBuilder; - friend class el::base::Writer; - friend class el::base::PErrorWriter; - friend class el::base::Storage; - friend class el::base::PerformanceTracker; - friend class el::base::LogDispatcher; - - Logger(void); - -#if ELPP_VARIADIC_TEMPLATES_SUPPORTED - template - void log_(Level, int, const char*, const T&, const Args&...); - - template - inline void log_(Level, int, const T&); - - template - void log(Level, const char*, const T&, const Args&...); - - template - inline void log(Level, const T&); -#endif // ELPP_VARIADIC_TEMPLATES_SUPPORTED - - void initUnflushedCount(void); - - inline base::type::stringstream_t& stream(void) { - return m_stream; - } - - void resolveLoggerFormatSpec(void) const; -}; -namespace base { -/// @brief Loggers repository -class RegisteredLoggers : public base::utils::Registry { - public: - explicit RegisteredLoggers(const LogBuilderPtr& defaultLogBuilder); - - virtual ~RegisteredLoggers(void) { - unsafeFlushAll(); - } - - inline void setDefaultConfigurations(const Configurations& configurations) { - base::threading::ScopedLock scopedLock(lock()); - m_defaultConfigurations.setFromBase(const_cast(&configurations)); - } - - inline Configurations* defaultConfigurations(void) { - return &m_defaultConfigurations; - } - - Logger* get(const std::string& id, bool forceCreation = true); - - template - inline bool installLoggerRegistrationCallback(const std::string& id) { - return base::utils::Utils::installCallback(id, - &m_loggerRegistrationCallbacks); - } - - template - inline void uninstallLoggerRegistrationCallback(const std::string& id) { - base::utils::Utils::uninstallCallback(id, &m_loggerRegistrationCallbacks); - } - - template - inline T* loggerRegistrationCallback(const std::string& id) { - return base::utils::Utils::callback(id, &m_loggerRegistrationCallbacks); - } - - bool remove(const std::string& id); - - inline bool has(const std::string& id) { - return get(id, false) != nullptr; - } - - inline void unregister(Logger*& logger) { - base::threading::ScopedLock scopedLock(lock()); - base::utils::Registry::unregister(logger->id()); - } - - inline base::LogStreamsReferenceMap* logStreamsReference(void) { - return &m_logStreamsReference; - } - - inline void flushAll(void) { - base::threading::ScopedLock scopedLock(lock()); - unsafeFlushAll(); - } - - inline void setDefaultLogBuilder(LogBuilderPtr& logBuilderPtr) { - base::threading::ScopedLock scopedLock(lock()); - m_defaultLogBuilder = logBuilderPtr; - } - - private: - LogBuilderPtr m_defaultLogBuilder; - Configurations m_defaultConfigurations; - base::LogStreamsReferenceMap m_logStreamsReference; - std::unordered_map m_loggerRegistrationCallbacks; - friend class el::base::Storage; - - void unsafeFlushAll(void); -}; -/// @brief Represents registries for verbose logging -class VRegistry : base::NoCopy, public base::threading::ThreadSafe { - public: - explicit VRegistry(base::type::VerboseLevel level, base::type::EnumType* pFlags); - - /// @brief Sets verbose level. Accepted range is 0-9 - void setLevel(base::type::VerboseLevel level); - - inline base::type::VerboseLevel level(void) const { - return m_level; - } - - inline void clearModules(void) { - base::threading::ScopedLock scopedLock(lock()); - m_modules.clear(); - } - - void setModules(const char* modules); - - bool allowed(base::type::VerboseLevel vlevel, const char* file); - - inline const std::unordered_map& modules(void) const { - return m_modules; - } - - void setFromArgs(const base::utils::CommandLineArgs* commandLineArgs); - - /// @brief Whether or not vModules enabled - inline bool vModulesEnabled(void) { - return !base::utils::hasFlag(LoggingFlag::DisableVModules, *m_pFlags); - } - - private: - base::type::VerboseLevel m_level; - base::type::EnumType* m_pFlags; - std::unordered_map m_modules; -}; -} // namespace base -class LogMessage { - public: - LogMessage(Level level, const std::string& file, base::type::LineNumber line, const std::string& func, - base::type::VerboseLevel verboseLevel, Logger* logger) : - m_level(level), m_file(file), m_line(line), m_func(func), - m_verboseLevel(verboseLevel), m_logger(logger), m_message(logger->stream().str()) { - } - inline Level level(void) const { - return m_level; - } - inline const std::string& file(void) const { - return m_file; - } - inline base::type::LineNumber line(void) const { - return m_line; - } - inline const std::string& func(void) const { - return m_func; - } - inline base::type::VerboseLevel verboseLevel(void) const { - return m_verboseLevel; - } - inline Logger* logger(void) const { - return m_logger; - } - inline const base::type::string_t& message(void) const { - return m_message; - } - private: - Level m_level; - std::string m_file; - base::type::LineNumber m_line; - std::string m_func; - base::type::VerboseLevel m_verboseLevel; - Logger* m_logger; - base::type::string_t m_message; -}; -namespace base { -#if ELPP_ASYNC_LOGGING -class AsyncLogItem { - public: - explicit AsyncLogItem(const LogMessage& logMessage, const LogDispatchData& data, const base::type::string_t& logLine) - : m_logMessage(logMessage), m_dispatchData(data), m_logLine(logLine) {} - virtual ~AsyncLogItem() {} - inline LogMessage* logMessage(void) { - return &m_logMessage; - } - inline LogDispatchData* data(void) { - return &m_dispatchData; - } - inline base::type::string_t logLine(void) { - return m_logLine; - } - private: - LogMessage m_logMessage; - LogDispatchData m_dispatchData; - base::type::string_t m_logLine; -}; -class AsyncLogQueue : public base::threading::ThreadSafe { - public: - virtual ~AsyncLogQueue() { - ELPP_INTERNAL_INFO(6, "~AsyncLogQueue"); - } - - inline AsyncLogItem next(void) { - base::threading::ScopedLock scopedLock(lock()); - AsyncLogItem result = m_queue.front(); - m_queue.pop(); - return result; - } - - inline void push(const AsyncLogItem& item) { - base::threading::ScopedLock scopedLock(lock()); - m_queue.push(item); - } - inline void pop(void) { - base::threading::ScopedLock scopedLock(lock()); - m_queue.pop(); - } - inline AsyncLogItem front(void) { - base::threading::ScopedLock scopedLock(lock()); - return m_queue.front(); - } - inline bool empty(void) { - base::threading::ScopedLock scopedLock(lock()); - return m_queue.empty(); - } - private: - std::queue m_queue; -}; -class IWorker { - public: - virtual ~IWorker() {} - virtual void start() = 0; -}; -#endif // ELPP_ASYNC_LOGGING -/// @brief Easylogging++ management storage -class Storage : base::NoCopy, public base::threading::ThreadSafe { - public: -#if ELPP_ASYNC_LOGGING - Storage(const LogBuilderPtr& defaultLogBuilder, base::IWorker* asyncDispatchWorker); -#else - explicit Storage(const LogBuilderPtr& defaultLogBuilder); -#endif // ELPP_ASYNC_LOGGING - - virtual ~Storage(void); - - inline bool validateEveryNCounter(const char* filename, base::type::LineNumber lineNumber, std::size_t occasion) { - return hitCounters()->validateEveryN(filename, lineNumber, occasion); - } - - inline bool validateAfterNCounter(const char* filename, base::type::LineNumber lineNumber, std::size_t n) { - return hitCounters()->validateAfterN(filename, lineNumber, n); - } - - inline bool validateNTimesCounter(const char* filename, base::type::LineNumber lineNumber, std::size_t n) { - return hitCounters()->validateNTimes(filename, lineNumber, n); - } - - inline base::RegisteredHitCounters* hitCounters(void) const { - return m_registeredHitCounters; - } - - inline base::RegisteredLoggers* registeredLoggers(void) const { - return m_registeredLoggers; - } - - inline base::VRegistry* vRegistry(void) const { - return m_vRegistry; - } - -#if ELPP_ASYNC_LOGGING - inline base::AsyncLogQueue* asyncLogQueue(void) const { - return m_asyncLogQueue; - } -#endif // ELPP_ASYNC_LOGGING - - inline const base::utils::CommandLineArgs* commandLineArgs(void) const { - return &m_commandLineArgs; - } - - inline void addFlag(LoggingFlag flag) { - base::utils::addFlag(flag, &m_flags); - } - - inline void removeFlag(LoggingFlag flag) { - base::utils::removeFlag(flag, &m_flags); - } - - inline bool hasFlag(LoggingFlag flag) const { - return base::utils::hasFlag(flag, m_flags); - } - - inline base::type::EnumType flags(void) const { - return m_flags; - } - - inline void setFlags(base::type::EnumType flags) { - m_flags = flags; - } - - inline void setPreRollOutCallback(const PreRollOutCallback& callback) { - m_preRollOutCallback = callback; - } - - inline void unsetPreRollOutCallback(void) { - m_preRollOutCallback = base::defaultPreRollOutCallback; - } - - inline PreRollOutCallback& preRollOutCallback(void) { - return m_preRollOutCallback; - } - - bool hasCustomFormatSpecifier(const char* formatSpecifier); - void installCustomFormatSpecifier(const CustomFormatSpecifier& customFormatSpecifier); - bool uninstallCustomFormatSpecifier(const char* formatSpecifier); - - const std::vector* customFormatSpecifiers(void) const { - return &m_customFormatSpecifiers; - } - - base::threading::Mutex& customFormatSpecifiersLock() { - return m_customFormatSpecifiersLock; - } - - inline void setLoggingLevel(Level level) { - m_loggingLevel = level; - } - - template - inline bool installLogDispatchCallback(const std::string& id) { - return base::utils::Utils::installCallback(id, &m_logDispatchCallbacks); - } - - template - inline void uninstallLogDispatchCallback(const std::string& id) { - base::utils::Utils::uninstallCallback(id, &m_logDispatchCallbacks); - } - template - inline T* logDispatchCallback(const std::string& id) { - return base::utils::Utils::callback(id, &m_logDispatchCallbacks); - } - -#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) - template - inline bool installPerformanceTrackingCallback(const std::string& id) { - return base::utils::Utils::installCallback(id, - &m_performanceTrackingCallbacks); - } - - template - inline void uninstallPerformanceTrackingCallback(const std::string& id) { - base::utils::Utils::uninstallCallback(id, - &m_performanceTrackingCallbacks); - } - - template - inline T* performanceTrackingCallback(const std::string& id) { - return base::utils::Utils::callback(id, &m_performanceTrackingCallbacks); - } -#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) - - /// @brief Sets thread name for current thread. Requires std::thread - inline void setThreadName(const std::string& name) { - if (name.empty()) return; - base::threading::ScopedLock scopedLock(m_threadNamesLock); - m_threadNames[base::threading::getCurrentThreadId()] = name; - } - - inline std::string getThreadName(const std::string& threadId) { - base::threading::ScopedLock scopedLock(m_threadNamesLock); - std::unordered_map::const_iterator it = m_threadNames.find(threadId); - if (it == m_threadNames.end()) { - return threadId; - } - return it->second; - } - private: - base::RegisteredHitCounters* m_registeredHitCounters; - base::RegisteredLoggers* m_registeredLoggers; - base::type::EnumType m_flags; - base::VRegistry* m_vRegistry; -#if ELPP_ASYNC_LOGGING - base::AsyncLogQueue* m_asyncLogQueue; - base::IWorker* m_asyncDispatchWorker; -#endif // ELPP_ASYNC_LOGGING - base::utils::CommandLineArgs m_commandLineArgs; - PreRollOutCallback m_preRollOutCallback; - std::unordered_map m_logDispatchCallbacks; - std::unordered_map m_performanceTrackingCallbacks; - std::unordered_map m_threadNames; - std::vector m_customFormatSpecifiers; - base::threading::Mutex m_customFormatSpecifiersLock; - base::threading::Mutex m_threadNamesLock; - Level m_loggingLevel; - - friend class el::Helpers; - friend class el::base::DefaultLogDispatchCallback; - friend class el::LogBuilder; - friend class el::base::MessageBuilder; - friend class el::base::Writer; - friend class el::base::PerformanceTracker; - friend class el::base::LogDispatcher; - - void setApplicationArguments(int argc, char** argv); - - inline void setApplicationArguments(int argc, const char** argv) { - setApplicationArguments(argc, const_cast(argv)); - } -}; -extern ELPP_EXPORT base::type::StoragePointer elStorage; -#define ELPP el::base::elStorage -class DefaultLogDispatchCallback : public LogDispatchCallback { - protected: - void handle(const LogDispatchData* data); - private: - const LogDispatchData* m_data; - void dispatch(base::type::string_t&& logLine); -}; -#if ELPP_ASYNC_LOGGING -class AsyncLogDispatchCallback : public LogDispatchCallback { - protected: - void handle(const LogDispatchData* data); -}; -class AsyncDispatchWorker : public base::IWorker, public base::threading::ThreadSafe { - public: - AsyncDispatchWorker(); - virtual ~AsyncDispatchWorker(); - - bool clean(void); - void emptyQueue(void); - virtual void start(void); - void handle(AsyncLogItem* logItem); - void run(void); - - void setContinueRunning(bool value) { - base::threading::ScopedLock scopedLock(m_continueRunningLock); - m_continueRunning = value; - } - - bool continueRunning(void) const { - return m_continueRunning; - } - private: - std::condition_variable cv; - bool m_continueRunning; - base::threading::Mutex m_continueRunningLock; -}; -#endif // ELPP_ASYNC_LOGGING -} // namespace base -namespace base { -class DefaultLogBuilder : public LogBuilder { - public: - base::type::string_t build(const LogMessage* logMessage, bool appendNewLine) const; -}; -/// @brief Dispatches log messages -class LogDispatcher : base::NoCopy { - public: - LogDispatcher(bool proceed, LogMessage* logMessage, base::DispatchAction dispatchAction) : - m_proceed(proceed), - m_logMessage(logMessage), - m_dispatchAction(std::move(dispatchAction)) { - } - - void dispatch(void); - - private: - bool m_proceed; - LogMessage* m_logMessage; - base::DispatchAction m_dispatchAction; -}; -#if defined(ELPP_STL_LOGGING) -/// @brief Workarounds to write some STL logs -/// -/// @detail There is workaround needed to loop through some stl containers. In order to do that, we need iterable containers -/// of same type and provide iterator interface and pass it on to writeIterator(). -/// Remember, this is passed by value in constructor so that we dont change original containers. -/// This operation is as expensive as Big-O(std::min(class_.size(), base::consts::kMaxLogPerContainer)) -namespace workarounds { -/// @brief Abstract IterableContainer template that provides interface for iterable classes of type T -template -class IterableContainer { - public: - typedef typename Container::iterator iterator; - typedef typename Container::const_iterator const_iterator; - IterableContainer(void) {} - virtual ~IterableContainer(void) {} - iterator begin(void) { - return getContainer().begin(); - } - iterator end(void) { - return getContainer().end(); - } - private: - virtual Container& getContainer(void) = 0; -}; -/// @brief Implements IterableContainer and provides iterable std::priority_queue class -template, typename Comparator = std::less> -class IterablePriorityQueue : public IterableContainer, - public std::priority_queue { - public: - IterablePriorityQueue(std::priority_queue queue_) { - std::size_t count_ = 0; - while (++count_ < base::consts::kMaxLogPerContainer && !queue_.empty()) { - this->push(queue_.top()); - queue_.pop(); - } - } - private: - inline Container& getContainer(void) { - return this->c; - } -}; -/// @brief Implements IterableContainer and provides iterable std::queue class -template> -class IterableQueue : public IterableContainer, public std::queue { - public: - IterableQueue(std::queue queue_) { - std::size_t count_ = 0; - while (++count_ < base::consts::kMaxLogPerContainer && !queue_.empty()) { - this->push(queue_.front()); - queue_.pop(); - } - } - private: - inline Container& getContainer(void) { - return this->c; - } -}; -/// @brief Implements IterableContainer and provides iterable std::stack class -template> -class IterableStack : public IterableContainer, public std::stack { - public: - IterableStack(std::stack stack_) { - std::size_t count_ = 0; - while (++count_ < base::consts::kMaxLogPerContainer && !stack_.empty()) { - this->push(stack_.top()); - stack_.pop(); - } - } - private: - inline Container& getContainer(void) { - return this->c; - } -}; -} // namespace workarounds -#endif // defined(ELPP_STL_LOGGING) -// Log message builder -class MessageBuilder { - public: - MessageBuilder(void) : m_logger(nullptr), m_containerLogSeperator(ELPP_LITERAL("")) {} - void initialize(Logger* logger); - -# define ELPP_SIMPLE_LOG(LOG_TYPE)\ -MessageBuilder& operator<<(LOG_TYPE msg) {\ -m_logger->stream() << msg;\ -if (ELPP->hasFlag(LoggingFlag::AutoSpacing)) {\ -m_logger->stream() << " ";\ -}\ -return *this;\ -} - - inline MessageBuilder& operator<<(const std::string& msg) { - return operator<<(msg.c_str()); - } - ELPP_SIMPLE_LOG(char) - ELPP_SIMPLE_LOG(bool) - ELPP_SIMPLE_LOG(signed short) - ELPP_SIMPLE_LOG(unsigned short) - ELPP_SIMPLE_LOG(signed int) - ELPP_SIMPLE_LOG(unsigned int) - ELPP_SIMPLE_LOG(signed long) - ELPP_SIMPLE_LOG(unsigned long) - ELPP_SIMPLE_LOG(float) - ELPP_SIMPLE_LOG(double) - ELPP_SIMPLE_LOG(char*) - ELPP_SIMPLE_LOG(const char*) - ELPP_SIMPLE_LOG(const void*) - ELPP_SIMPLE_LOG(long double) - inline MessageBuilder& operator<<(const std::wstring& msg) { - return operator<<(msg.c_str()); - } - MessageBuilder& operator<<(const wchar_t* msg); - // ostream manipulators - inline MessageBuilder& operator<<(std::ostream& (*OStreamMani)(std::ostream&)) { - m_logger->stream() << OStreamMani; - return *this; - } -#define ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(temp) \ -template \ -inline MessageBuilder& operator<<(const temp& template_inst) { \ -return writeIterator(template_inst.begin(), template_inst.end(), template_inst.size()); \ -} -#define ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(temp) \ -template \ -inline MessageBuilder& operator<<(const temp& template_inst) { \ -return writeIterator(template_inst.begin(), template_inst.end(), template_inst.size()); \ -} -#define ELPP_ITERATOR_CONTAINER_LOG_THREE_ARG(temp) \ -template \ -inline MessageBuilder& operator<<(const temp& template_inst) { \ -return writeIterator(template_inst.begin(), template_inst.end(), template_inst.size()); \ -} -#define ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(temp) \ -template \ -inline MessageBuilder& operator<<(const temp& template_inst) { \ -return writeIterator(template_inst.begin(), template_inst.end(), template_inst.size()); \ -} -#define ELPP_ITERATOR_CONTAINER_LOG_FIVE_ARG(temp) \ -template \ -inline MessageBuilder& operator<<(const temp& template_inst) { \ -return writeIterator(template_inst.begin(), template_inst.end(), template_inst.size()); \ -} - -#if defined(ELPP_STL_LOGGING) - ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(std::vector) - ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(std::list) - ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(std::deque) - ELPP_ITERATOR_CONTAINER_LOG_THREE_ARG(std::set) - ELPP_ITERATOR_CONTAINER_LOG_THREE_ARG(std::multiset) - ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(std::map) - ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(std::multimap) - template - inline MessageBuilder& operator<<(const std::queue& queue_) { - base::workarounds::IterableQueue iterableQueue_ = - static_cast >(queue_); - return writeIterator(iterableQueue_.begin(), iterableQueue_.end(), iterableQueue_.size()); - } - template - inline MessageBuilder& operator<<(const std::stack& stack_) { - base::workarounds::IterableStack iterableStack_ = - static_cast >(stack_); - return writeIterator(iterableStack_.begin(), iterableStack_.end(), iterableStack_.size()); - } - template - inline MessageBuilder& operator<<(const std::priority_queue& priorityQueue_) { - base::workarounds::IterablePriorityQueue iterablePriorityQueue_ = - static_cast >(priorityQueue_); - return writeIterator(iterablePriorityQueue_.begin(), iterablePriorityQueue_.end(), iterablePriorityQueue_.size()); - } - template - MessageBuilder& operator<<(const std::pair& pair_) { - m_logger->stream() << ELPP_LITERAL("("); - operator << (static_cast(pair_.first)); - m_logger->stream() << ELPP_LITERAL(", "); - operator << (static_cast(pair_.second)); - m_logger->stream() << ELPP_LITERAL(")"); - return *this; - } - template - MessageBuilder& operator<<(const std::bitset& bitset_) { - m_logger->stream() << ELPP_LITERAL("["); - operator << (bitset_.to_string()); - m_logger->stream() << ELPP_LITERAL("]"); - return *this; - } -# if defined(ELPP_LOG_STD_ARRAY) - template - inline MessageBuilder& operator<<(const std::array& array) { - return writeIterator(array.begin(), array.end(), array.size()); - } -# endif // defined(ELPP_LOG_STD_ARRAY) -# if defined(ELPP_LOG_UNORDERED_MAP) - ELPP_ITERATOR_CONTAINER_LOG_FIVE_ARG(std::unordered_map) - ELPP_ITERATOR_CONTAINER_LOG_FIVE_ARG(std::unordered_multimap) -# endif // defined(ELPP_LOG_UNORDERED_MAP) -# if defined(ELPP_LOG_UNORDERED_SET) - ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(std::unordered_set) - ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(std::unordered_multiset) -# endif // defined(ELPP_LOG_UNORDERED_SET) -#endif // defined(ELPP_STL_LOGGING) -#if defined(ELPP_QT_LOGGING) - inline MessageBuilder& operator<<(const QString& msg) { -# if defined(ELPP_UNICODE) - m_logger->stream() << msg.toStdWString(); -# else - m_logger->stream() << msg.toStdString(); -# endif // defined(ELPP_UNICODE) - return *this; - } - inline MessageBuilder& operator<<(const QByteArray& msg) { - return operator << (QString(msg)); - } - inline MessageBuilder& operator<<(const QStringRef& msg) { - return operator<<(msg.toString()); - } - inline MessageBuilder& operator<<(qint64 msg) { -# if defined(ELPP_UNICODE) - m_logger->stream() << QString::number(msg).toStdWString(); -# else - m_logger->stream() << QString::number(msg).toStdString(); -# endif // defined(ELPP_UNICODE) - return *this; - } - inline MessageBuilder& operator<<(quint64 msg) { -# if defined(ELPP_UNICODE) - m_logger->stream() << QString::number(msg).toStdWString(); -# else - m_logger->stream() << QString::number(msg).toStdString(); -# endif // defined(ELPP_UNICODE) - return *this; - } - inline MessageBuilder& operator<<(QChar msg) { - m_logger->stream() << msg.toLatin1(); - return *this; - } - inline MessageBuilder& operator<<(const QLatin1String& msg) { - m_logger->stream() << msg.latin1(); - return *this; - } - ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(QList) - ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(QVector) - ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(QQueue) - ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(QSet) - ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(QLinkedList) - ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(QStack) - template - MessageBuilder& operator<<(const QPair& pair_) { - m_logger->stream() << ELPP_LITERAL("("); - operator << (static_cast(pair_.first)); - m_logger->stream() << ELPP_LITERAL(", "); - operator << (static_cast(pair_.second)); - m_logger->stream() << ELPP_LITERAL(")"); - return *this; - } - template - MessageBuilder& operator<<(const QMap& map_) { - m_logger->stream() << ELPP_LITERAL("["); - QList keys = map_.keys(); - typename QList::const_iterator begin = keys.begin(); - typename QList::const_iterator end = keys.end(); - int max_ = static_cast(base::consts::kMaxLogPerContainer); // to prevent warning - for (int index_ = 0; begin != end && index_ < max_; ++index_, ++begin) { - m_logger->stream() << ELPP_LITERAL("("); - operator << (static_cast(*begin)); - m_logger->stream() << ELPP_LITERAL(", "); - operator << (static_cast(map_.value(*begin))); - m_logger->stream() << ELPP_LITERAL(")"); - m_logger->stream() << ((index_ < keys.size() -1) ? m_containerLogSeperator : ELPP_LITERAL("")); - } - if (begin != end) { - m_logger->stream() << ELPP_LITERAL("..."); - } - m_logger->stream() << ELPP_LITERAL("]"); - return *this; - } - template - inline MessageBuilder& operator<<(const QMultiMap& map_) { - operator << (static_cast>(map_)); - return *this; - } - template - MessageBuilder& operator<<(const QHash& hash_) { - m_logger->stream() << ELPP_LITERAL("["); - QList keys = hash_.keys(); - typename QList::const_iterator begin = keys.begin(); - typename QList::const_iterator end = keys.end(); - int max_ = static_cast(base::consts::kMaxLogPerContainer); // prevent type warning - for (int index_ = 0; begin != end && index_ < max_; ++index_, ++begin) { - m_logger->stream() << ELPP_LITERAL("("); - operator << (static_cast(*begin)); - m_logger->stream() << ELPP_LITERAL(", "); - operator << (static_cast(hash_.value(*begin))); - m_logger->stream() << ELPP_LITERAL(")"); - m_logger->stream() << ((index_ < keys.size() -1) ? m_containerLogSeperator : ELPP_LITERAL("")); - } - if (begin != end) { - m_logger->stream() << ELPP_LITERAL("..."); - } - m_logger->stream() << ELPP_LITERAL("]"); - return *this; - } - template - inline MessageBuilder& operator<<(const QMultiHash& multiHash_) { - operator << (static_cast>(multiHash_)); - return *this; - } -#endif // defined(ELPP_QT_LOGGING) -#if defined(ELPP_BOOST_LOGGING) - ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(boost::container::vector) - ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(boost::container::stable_vector) - ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(boost::container::list) - ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(boost::container::deque) - ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(boost::container::map) - ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(boost::container::flat_map) - ELPP_ITERATOR_CONTAINER_LOG_THREE_ARG(boost::container::set) - ELPP_ITERATOR_CONTAINER_LOG_THREE_ARG(boost::container::flat_set) -#endif // defined(ELPP_BOOST_LOGGING) - - /// @brief Macro used internally that can be used externally to make containers easylogging++ friendly - /// - /// @detail This macro expands to write an ostream& operator<< for container. This container is expected to - /// have begin() and end() methods that return respective iterators - /// @param ContainerType Type of container e.g, MyList from WX_DECLARE_LIST(int, MyList); in wxwidgets - /// @param SizeMethod Method used to get size of container. - /// @param ElementInstance Instance of element to be fed out. Insance name is "elem". See WXELPP_ENABLED macro - /// for an example usage -#define MAKE_CONTAINERELPP_FRIENDLY(ContainerType, SizeMethod, ElementInstance) \ -el::base::type::ostream_t& operator<<(el::base::type::ostream_t& ss, const ContainerType& container) {\ -const el::base::type::char_t* sep = ELPP->hasFlag(el::LoggingFlag::NewLineForContainer) ? \ -ELPP_LITERAL("\n ") : ELPP_LITERAL(", ");\ -ContainerType::const_iterator elem = container.begin();\ -ContainerType::const_iterator endElem = container.end();\ -std::size_t size_ = container.SizeMethod; \ -ss << ELPP_LITERAL("[");\ -for (std::size_t i = 0; elem != endElem && i < el::base::consts::kMaxLogPerContainer; ++i, ++elem) { \ -ss << ElementInstance;\ -ss << ((i < size_ - 1) ? sep : ELPP_LITERAL(""));\ -}\ -if (elem != endElem) {\ -ss << ELPP_LITERAL("...");\ -}\ -ss << ELPP_LITERAL("]");\ -return ss;\ -} -#if defined(ELPP_WXWIDGETS_LOGGING) - ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(wxVector) -# define ELPP_WX_PTR_ENABLED(ContainerType) MAKE_CONTAINERELPP_FRIENDLY(ContainerType, size(), *(*elem)) -# define ELPP_WX_ENABLED(ContainerType) MAKE_CONTAINERELPP_FRIENDLY(ContainerType, size(), (*elem)) -# define ELPP_WX_HASH_MAP_ENABLED(ContainerType) MAKE_CONTAINERELPP_FRIENDLY(ContainerType, size(), \ -ELPP_LITERAL("(") << elem->first << ELPP_LITERAL(", ") << elem->second << ELPP_LITERAL(")") -#else -# define ELPP_WX_PTR_ENABLED(ContainerType) -# define ELPP_WX_ENABLED(ContainerType) -# define ELPP_WX_HASH_MAP_ENABLED(ContainerType) -#endif // defined(ELPP_WXWIDGETS_LOGGING) - // Other classes - template - ELPP_SIMPLE_LOG(const Class&) -#undef ELPP_SIMPLE_LOG -#undef ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG -#undef ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG -#undef ELPP_ITERATOR_CONTAINER_LOG_THREE_ARG -#undef ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG -#undef ELPP_ITERATOR_CONTAINER_LOG_FIVE_ARG - private: - Logger* m_logger; - const base::type::char_t* m_containerLogSeperator; - - template - MessageBuilder& writeIterator(Iterator begin_, Iterator end_, std::size_t size_) { - m_logger->stream() << ELPP_LITERAL("["); - for (std::size_t i = 0; begin_ != end_ && i < base::consts::kMaxLogPerContainer; ++i, ++begin_) { - operator << (*begin_); - m_logger->stream() << ((i < size_ - 1) ? m_containerLogSeperator : ELPP_LITERAL("")); - } - if (begin_ != end_) { - m_logger->stream() << ELPP_LITERAL("..."); - } - m_logger->stream() << ELPP_LITERAL("]"); - if (ELPP->hasFlag(LoggingFlag::AutoSpacing)) { - m_logger->stream() << " "; - } - return *this; - } -}; -/// @brief Writes nothing - Used when certain log is disabled -class NullWriter : base::NoCopy { - public: - NullWriter(void) {} - - // Null manipulator - inline NullWriter& operator<<(std::ostream& (*)(std::ostream&)) { - return *this; - } - - template - inline NullWriter& operator<<(const T&) { - return *this; - } - - inline operator bool() { - return true; - } -}; -/// @brief Main entry point of each logging -class Writer : base::NoCopy { - public: - Writer(Level level, const char* file, base::type::LineNumber line, - const char* func, base::DispatchAction dispatchAction = base::DispatchAction::NormalLog, - base::type::VerboseLevel verboseLevel = 0) : - m_msg(nullptr), m_level(level), m_file(file), m_line(line), m_func(func), m_verboseLevel(verboseLevel), - m_logger(nullptr), m_proceed(false), m_dispatchAction(dispatchAction) { - } - - Writer(LogMessage* msg, base::DispatchAction dispatchAction = base::DispatchAction::NormalLog) : - m_msg(msg), m_level(msg != nullptr ? msg->level() : Level::Unknown), - m_line(0), m_logger(nullptr), m_proceed(false), m_dispatchAction(dispatchAction) { - } - - virtual ~Writer(void) { - processDispatch(); - } - - template - inline Writer& operator<<(const T& log) { -#if ELPP_LOGGING_ENABLED - if (m_proceed) { - m_messageBuilder << log; - } -#endif // ELPP_LOGGING_ENABLED - return *this; - } - - inline Writer& operator<<(std::ostream& (*log)(std::ostream&)) { -#if ELPP_LOGGING_ENABLED - if (m_proceed) { - m_messageBuilder << log; - } -#endif // ELPP_LOGGING_ENABLED - return *this; - } - - inline operator bool() { - return true; - } - - Writer& construct(Logger* logger, bool needLock = true); - Writer& construct(int count, const char* loggerIds, ...); - protected: - LogMessage* m_msg; - Level m_level; - const char* m_file; - const base::type::LineNumber m_line; - const char* m_func; - base::type::VerboseLevel m_verboseLevel; - Logger* m_logger; - bool m_proceed; - base::MessageBuilder m_messageBuilder; - base::DispatchAction m_dispatchAction; - std::vector m_loggerIds; - friend class el::Helpers; - - void initializeLogger(const std::string& loggerId, bool lookup = true, bool needLock = true); - void processDispatch(); - void triggerDispatch(void); -}; -class PErrorWriter : public base::Writer { - public: - PErrorWriter(Level level, const char* file, base::type::LineNumber line, - const char* func, base::DispatchAction dispatchAction = base::DispatchAction::NormalLog, - base::type::VerboseLevel verboseLevel = 0) : - base::Writer(level, file, line, func, dispatchAction, verboseLevel) { - } - - virtual ~PErrorWriter(void); -}; -} // namespace base -// Logging from Logger class. Why this is here? Because we have Storage and Writer class available -#if ELPP_VARIADIC_TEMPLATES_SUPPORTED -template -void Logger::log_(Level level, int vlevel, const char* s, const T& value, const Args&... args) { - base::MessageBuilder b; - b.initialize(this); - while (*s) { - if (*s == base::consts::kFormatSpecifierChar) { - if (*(s + 1) == base::consts::kFormatSpecifierChar) { - ++s; - } else { - if (*(s + 1) == base::consts::kFormatSpecifierCharValue) { - ++s; - b << value; - log_(level, vlevel, ++s, args...); - return; - } - } - } - b << *s++; - } - ELPP_INTERNAL_ERROR("Too many arguments provided. Unable to handle. Please provide more format specifiers", false); -} -template -void Logger::log_(Level level, int vlevel, const T& log) { - if (level == Level::Verbose) { - if (ELPP->vRegistry()->allowed(vlevel, __FILE__)) { - base::Writer(Level::Verbose, "FILE", 0, "FUNCTION", - base::DispatchAction::NormalLog, vlevel).construct(this, false) << log; - } else { - stream().str(ELPP_LITERAL("")); - releaseLock(); - } - } else { - base::Writer(level, "FILE", 0, "FUNCTION").construct(this, false) << log; - } -} -template -inline void Logger::log(Level level, const char* s, const T& value, const Args&... args) { - acquireLock(); // released in Writer! - log_(level, 0, s, value, args...); -} -template -inline void Logger::log(Level level, const T& log) { - acquireLock(); // released in Writer! - log_(level, 0, log); -} -# if ELPP_VERBOSE_LOG -template -inline void Logger::verbose(int vlevel, const char* s, const T& value, const Args&... args) { - acquireLock(); // released in Writer! - log_(el::Level::Verbose, vlevel, s, value, args...); -} -template -inline void Logger::verbose(int vlevel, const T& log) { - acquireLock(); // released in Writer! - log_(el::Level::Verbose, vlevel, log); -} -# else -template -inline void Logger::verbose(int, const char*, const T&, const Args&...) { - return; -} -template -inline void Logger::verbose(int, const T&) { - return; -} -# endif // ELPP_VERBOSE_LOG -# define LOGGER_LEVEL_WRITERS(FUNCTION_NAME, LOG_LEVEL)\ -template \ -inline void Logger::FUNCTION_NAME(const char* s, const T& value, const Args&... args) {\ -log(LOG_LEVEL, s, value, args...);\ -}\ -template \ -inline void Logger::FUNCTION_NAME(const T& value) {\ -log(LOG_LEVEL, value);\ -} -# define LOGGER_LEVEL_WRITERS_DISABLED(FUNCTION_NAME, LOG_LEVEL)\ -template \ -inline void Logger::FUNCTION_NAME(const char*, const T&, const Args&...) {\ -return;\ -}\ -template \ -inline void Logger::FUNCTION_NAME(const T&) {\ -return;\ -} - -# if ELPP_INFO_LOG -LOGGER_LEVEL_WRITERS(info, Level::Info) -# else -LOGGER_LEVEL_WRITERS_DISABLED(info, Level::Info) -# endif // ELPP_INFO_LOG -# if ELPP_DEBUG_LOG -LOGGER_LEVEL_WRITERS(debug, Level::Debug) -# else -LOGGER_LEVEL_WRITERS_DISABLED(debug, Level::Debug) -# endif // ELPP_DEBUG_LOG -# if ELPP_WARNING_LOG -LOGGER_LEVEL_WRITERS(warn, Level::Warning) -# else -LOGGER_LEVEL_WRITERS_DISABLED(warn, Level::Warning) -# endif // ELPP_WARNING_LOG -# if ELPP_ERROR_LOG -LOGGER_LEVEL_WRITERS(error, Level::Error) -# else -LOGGER_LEVEL_WRITERS_DISABLED(error, Level::Error) -# endif // ELPP_ERROR_LOG -# if ELPP_FATAL_LOG -LOGGER_LEVEL_WRITERS(fatal, Level::Fatal) -# else -LOGGER_LEVEL_WRITERS_DISABLED(fatal, Level::Fatal) -# endif // ELPP_FATAL_LOG -# if ELPP_TRACE_LOG -LOGGER_LEVEL_WRITERS(trace, Level::Trace) -# else -LOGGER_LEVEL_WRITERS_DISABLED(trace, Level::Trace) -# endif // ELPP_TRACE_LOG -# undef LOGGER_LEVEL_WRITERS -# undef LOGGER_LEVEL_WRITERS_DISABLED -#endif // ELPP_VARIADIC_TEMPLATES_SUPPORTED -#if ELPP_COMPILER_MSVC -# define ELPP_VARIADIC_FUNC_MSVC(variadicFunction, variadicArgs) variadicFunction variadicArgs -# define ELPP_VARIADIC_FUNC_MSVC_RUN(variadicFunction, ...) ELPP_VARIADIC_FUNC_MSVC(variadicFunction, (__VA_ARGS__)) -# define el_getVALength(...) ELPP_VARIADIC_FUNC_MSVC_RUN(el_resolveVALength, 0, ## __VA_ARGS__,\ -10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) -#else -# if ELPP_COMPILER_CLANG -# define el_getVALength(...) el_resolveVALength(0, __VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) -# else -# define el_getVALength(...) el_resolveVALength(0, ## __VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) -# endif // ELPP_COMPILER_CLANG -#endif // ELPP_COMPILER_MSVC -#define el_resolveVALength(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N -#define ELPP_WRITE_LOG(writer, level, dispatchAction, ...) \ -writer(level, __FILE__, __LINE__, ELPP_FUNC, dispatchAction).construct(el_getVALength(__VA_ARGS__), __VA_ARGS__) -#define ELPP_WRITE_LOG_IF(writer, condition, level, dispatchAction, ...) if (condition) \ -writer(level, __FILE__, __LINE__, ELPP_FUNC, dispatchAction).construct(el_getVALength(__VA_ARGS__), __VA_ARGS__) -#define ELPP_WRITE_LOG_EVERY_N(writer, occasion, level, dispatchAction, ...) \ -ELPP->validateEveryNCounter(__FILE__, __LINE__, occasion) && \ -writer(level, __FILE__, __LINE__, ELPP_FUNC, dispatchAction).construct(el_getVALength(__VA_ARGS__), __VA_ARGS__) -#define ELPP_WRITE_LOG_AFTER_N(writer, n, level, dispatchAction, ...) \ -ELPP->validateAfterNCounter(__FILE__, __LINE__, n) && \ -writer(level, __FILE__, __LINE__, ELPP_FUNC, dispatchAction).construct(el_getVALength(__VA_ARGS__), __VA_ARGS__) -#define ELPP_WRITE_LOG_N_TIMES(writer, n, level, dispatchAction, ...) \ -ELPP->validateNTimesCounter(__FILE__, __LINE__, n) && \ -writer(level, __FILE__, __LINE__, ELPP_FUNC, dispatchAction).construct(el_getVALength(__VA_ARGS__), __VA_ARGS__) -#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) -class PerformanceTrackingData { - public: - enum class DataType : base::type::EnumType { - Checkpoint = 1, Complete = 2 - }; - // Do not use constructor, will run into multiple definition error, use init(PerformanceTracker*) - explicit PerformanceTrackingData(DataType dataType) : m_performanceTracker(nullptr), - m_dataType(dataType), m_firstCheckpoint(false), m_file(""), m_line(0), m_func("") {} - inline const std::string* blockName(void) const; - inline const struct timeval* startTime(void) const; - inline const struct timeval* endTime(void) const; - inline const struct timeval* lastCheckpointTime(void) const; - inline const base::PerformanceTracker* performanceTracker(void) const { - return m_performanceTracker; - } - inline PerformanceTrackingData::DataType dataType(void) const { - return m_dataType; - } - inline bool firstCheckpoint(void) const { - return m_firstCheckpoint; - } - inline std::string checkpointId(void) const { - return m_checkpointId; - } - inline const char* file(void) const { - return m_file; - } - inline base::type::LineNumber line(void) const { - return m_line; - } - inline const char* func(void) const { - return m_func; - } - inline const base::type::string_t* formattedTimeTaken() const { - return &m_formattedTimeTaken; - } - inline const std::string& loggerId(void) const; - private: - base::PerformanceTracker* m_performanceTracker; - base::type::string_t m_formattedTimeTaken; - PerformanceTrackingData::DataType m_dataType; - bool m_firstCheckpoint; - std::string m_checkpointId; - const char* m_file; - base::type::LineNumber m_line; - const char* m_func; - inline void init(base::PerformanceTracker* performanceTracker, bool firstCheckpoint = false) { - m_performanceTracker = performanceTracker; - m_firstCheckpoint = firstCheckpoint; - } - - friend class el::base::PerformanceTracker; -}; -namespace base { -/// @brief Represents performanceTracker block of code that conditionally adds performance status to log -/// either when goes outside the scope of when checkpoint() is called -class PerformanceTracker : public base::threading::ThreadSafe, public Loggable { - public: - PerformanceTracker(const std::string& blockName, - base::TimestampUnit timestampUnit = base::TimestampUnit::Millisecond, - const std::string& loggerId = std::string(el::base::consts::kPerformanceLoggerId), - bool scopedLog = true, Level level = base::consts::kPerformanceTrackerDefaultLevel); - /// @brief Copy constructor - PerformanceTracker(const PerformanceTracker& t) : - m_blockName(t.m_blockName), m_timestampUnit(t.m_timestampUnit), m_loggerId(t.m_loggerId), m_scopedLog(t.m_scopedLog), - m_level(t.m_level), m_hasChecked(t.m_hasChecked), m_lastCheckpointId(t.m_lastCheckpointId), m_enabled(t.m_enabled), - m_startTime(t.m_startTime), m_endTime(t.m_endTime), m_lastCheckpointTime(t.m_lastCheckpointTime) { - } - virtual ~PerformanceTracker(void); - /// @brief A checkpoint for current performanceTracker block. - void checkpoint(const std::string& id = std::string(), const char* file = __FILE__, - base::type::LineNumber line = __LINE__, - const char* func = ""); - inline Level level(void) const { - return m_level; - } - private: - std::string m_blockName; - base::TimestampUnit m_timestampUnit; - std::string m_loggerId; - bool m_scopedLog; - Level m_level; - bool m_hasChecked; - std::string m_lastCheckpointId; - bool m_enabled; - struct timeval m_startTime, m_endTime, m_lastCheckpointTime; - - PerformanceTracker(void); - - friend class el::PerformanceTrackingData; - friend class base::DefaultPerformanceTrackingCallback; - - const inline base::type::string_t getFormattedTimeTaken() const { - return getFormattedTimeTaken(m_startTime); - } - - const base::type::string_t getFormattedTimeTaken(struct timeval startTime) const; - - virtual inline void log(el::base::type::ostream_t& os) const { - os << getFormattedTimeTaken(); - } -}; -class DefaultPerformanceTrackingCallback : public PerformanceTrackingCallback { - protected: - void handle(const PerformanceTrackingData* data) { - m_data = data; - base::type::stringstream_t ss; - if (m_data->dataType() == PerformanceTrackingData::DataType::Complete) { - ss << ELPP_LITERAL("Executed [") << m_data->blockName()->c_str() << ELPP_LITERAL("] in [") << - *m_data->formattedTimeTaken() << ELPP_LITERAL("]"); - } else { - ss << ELPP_LITERAL("Performance checkpoint"); - if (!m_data->checkpointId().empty()) { - ss << ELPP_LITERAL(" [") << m_data->checkpointId().c_str() << ELPP_LITERAL("]"); - } - ss << ELPP_LITERAL(" for block [") << m_data->blockName()->c_str() << ELPP_LITERAL("] : [") << - *m_data->performanceTracker(); - if (!ELPP->hasFlag(LoggingFlag::DisablePerformanceTrackingCheckpointComparison) - && m_data->performanceTracker()->m_hasChecked) { - ss << ELPP_LITERAL(" ([") << *m_data->formattedTimeTaken() << ELPP_LITERAL("] from "); - if (m_data->performanceTracker()->m_lastCheckpointId.empty()) { - ss << ELPP_LITERAL("last checkpoint"); - } else { - ss << ELPP_LITERAL("checkpoint '") << m_data->performanceTracker()->m_lastCheckpointId.c_str() << ELPP_LITERAL("'"); - } - ss << ELPP_LITERAL(")]"); - } else { - ss << ELPP_LITERAL("]"); - } - } - el::base::Writer(m_data->performanceTracker()->level(), m_data->file(), m_data->line(), m_data->func()).construct(1, - m_data->loggerId().c_str()) << ss.str(); - } - private: - const PerformanceTrackingData* m_data; -}; -} // namespace base -inline const std::string* PerformanceTrackingData::blockName() const { - return const_cast(&m_performanceTracker->m_blockName); -} -inline const struct timeval* PerformanceTrackingData::startTime() const { - return const_cast(&m_performanceTracker->m_startTime); -} -inline const struct timeval* PerformanceTrackingData::endTime() const { - return const_cast(&m_performanceTracker->m_endTime); -} -inline const struct timeval* PerformanceTrackingData::lastCheckpointTime() const { - return const_cast(&m_performanceTracker->m_lastCheckpointTime); -} -inline const std::string& PerformanceTrackingData::loggerId(void) const { - return m_performanceTracker->m_loggerId; -} -#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) -namespace base { -/// @brief Contains some internal debugging tools like crash handler and stack tracer -namespace debug { -#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG) -class StackTrace : base::NoCopy { - public: - static const unsigned int kMaxStack = 64; - static const unsigned int kStackStart = 2; // We want to skip c'tor and StackTrace::generateNew() - class StackTraceEntry { - public: - StackTraceEntry(std::size_t index, const std::string& loc, const std::string& demang, const std::string& hex, - const std::string& addr); - StackTraceEntry(std::size_t index, const std::string& loc) : - m_index(index), - m_location(loc) { - } - std::size_t m_index; - std::string m_location; - std::string m_demangled; - std::string m_hex; - std::string m_addr; - friend std::ostream& operator<<(std::ostream& ss, const StackTraceEntry& si); - - private: - StackTraceEntry(void); - }; - - StackTrace(void) { - generateNew(); - } - - virtual ~StackTrace(void) { - } - - inline std::vector& getLatestStack(void) { - return m_stack; - } - - friend std::ostream& operator<<(std::ostream& os, const StackTrace& st); - - private: - std::vector m_stack; - - void generateNew(void); -}; -/// @brief Handles unexpected crashes -class CrashHandler : base::NoCopy { - public: - typedef void (*Handler)(int); - - explicit CrashHandler(bool useDefault); - explicit CrashHandler(const Handler& cHandler) { - setHandler(cHandler); - } - void setHandler(const Handler& cHandler); - - private: - Handler m_handler; -}; -#else -class CrashHandler { - public: - explicit CrashHandler(bool) {} -}; -#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG) -} // namespace debug -} // namespace base -extern base::debug::CrashHandler elCrashHandler; -#define MAKE_LOGGABLE(ClassType, ClassInstance, OutputStreamInstance) \ -el::base::type::ostream_t& operator<<(el::base::type::ostream_t& OutputStreamInstance, const ClassType& ClassInstance) -/// @brief Initializes syslog with process ID, options and facility. calls closelog() on d'tor -class SysLogInitializer { - public: - SysLogInitializer(const char* processIdent, int options = 0, int facility = 0) { -#if defined(ELPP_SYSLOG) - openlog(processIdent, options, facility); -#else - ELPP_UNUSED(processIdent); - ELPP_UNUSED(options); - ELPP_UNUSED(facility); -#endif // defined(ELPP_SYSLOG) - } - virtual ~SysLogInitializer(void) { -#if defined(ELPP_SYSLOG) - closelog(); -#endif // defined(ELPP_SYSLOG) - } -}; -#define ELPP_INITIALIZE_SYSLOG(id, opt, fac) el::SysLogInitializer elSyslogInit(id, opt, fac) -/// @brief Static helpers for developers -class Helpers : base::StaticClass { - public: - /// @brief Shares logging repository (base::Storage) - static inline void setStorage(base::type::StoragePointer storage) { - ELPP = storage; - } - /// @return Main storage repository - static inline base::type::StoragePointer storage() { - return ELPP; - } - /// @brief Sets application arguments and figures out whats active for logging and whats not. - static inline void setArgs(int argc, char** argv) { - ELPP->setApplicationArguments(argc, argv); - } - /// @copydoc setArgs(int argc, char** argv) - static inline void setArgs(int argc, const char** argv) { - ELPP->setApplicationArguments(argc, const_cast(argv)); - } - /// @brief Sets thread name for current thread. Requires std::thread - static inline void setThreadName(const std::string& name) { - ELPP->setThreadName(name); - } - static inline std::string getThreadName() { - return ELPP->getThreadName(base::threading::getCurrentThreadId()); - } -#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG) - /// @brief Overrides default crash handler and installs custom handler. - /// @param crashHandler A functor with no return type that takes single int argument. - /// Handler is a typedef with specification: void (*Handler)(int) - static inline void setCrashHandler(const el::base::debug::CrashHandler::Handler& crashHandler) { - el::elCrashHandler.setHandler(crashHandler); - } - /// @brief Abort due to crash with signal in parameter - /// @param sig Crash signal - static void crashAbort(int sig, const char* sourceFile = "", unsigned int long line = 0); - /// @brief Logs reason of crash as per sig - /// @param sig Crash signal - /// @param stackTraceIfAvailable Includes stack trace if available - /// @param level Logging level - /// @param logger Logger to use for logging - static void logCrashReason(int sig, bool stackTraceIfAvailable = false, - Level level = Level::Fatal, const char* logger = base::consts::kDefaultLoggerId); -#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG) - /// @brief Installs pre rollout callback, this callback is triggered when log file is about to be rolled out - /// (can be useful for backing up) - static inline void installPreRollOutCallback(const PreRollOutCallback& callback) { - ELPP->setPreRollOutCallback(callback); - } - /// @brief Uninstalls pre rollout callback - static inline void uninstallPreRollOutCallback(void) { - ELPP->unsetPreRollOutCallback(); - } - /// @brief Installs post log dispatch callback, this callback is triggered when log is dispatched - template - static inline bool installLogDispatchCallback(const std::string& id) { - return ELPP->installLogDispatchCallback(id); - } - /// @brief Uninstalls log dispatch callback - template - static inline void uninstallLogDispatchCallback(const std::string& id) { - ELPP->uninstallLogDispatchCallback(id); - } - template - static inline T* logDispatchCallback(const std::string& id) { - return ELPP->logDispatchCallback(id); - } -#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) - /// @brief Installs post performance tracking callback, this callback is triggered when performance tracking is finished - template - static inline bool installPerformanceTrackingCallback(const std::string& id) { - return ELPP->installPerformanceTrackingCallback(id); - } - /// @brief Uninstalls post performance tracking handler - template - static inline void uninstallPerformanceTrackingCallback(const std::string& id) { - ELPP->uninstallPerformanceTrackingCallback(id); - } - template - static inline T* performanceTrackingCallback(const std::string& id) { - return ELPP->performanceTrackingCallback(id); - } -#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) - /// @brief Converts template to std::string - useful for loggable classes to log containers within log(std::ostream&) const - template - static std::string convertTemplateToStdString(const T& templ) { - el::Logger* logger = - ELPP->registeredLoggers()->get(el::base::consts::kDefaultLoggerId); - if (logger == nullptr) { - return std::string(); - } - base::MessageBuilder b; - b.initialize(logger); - logger->acquireLock(); - b << templ; -#if defined(ELPP_UNICODE) - std::string s = std::string(logger->stream().str().begin(), logger->stream().str().end()); -#else - std::string s = logger->stream().str(); -#endif // defined(ELPP_UNICODE) - logger->stream().str(ELPP_LITERAL("")); - logger->releaseLock(); - return s; - } - /// @brief Returns command line arguments (pointer) provided to easylogging++ - static inline const el::base::utils::CommandLineArgs* commandLineArgs(void) { - return ELPP->commandLineArgs(); - } - /// @brief Reserve space for custom format specifiers for performance - /// @see std::vector::reserve - static inline void reserveCustomFormatSpecifiers(std::size_t size) { - ELPP->m_customFormatSpecifiers.reserve(size); - } - /// @brief Installs user defined format specifier and handler - static inline void installCustomFormatSpecifier(const CustomFormatSpecifier& customFormatSpecifier) { - ELPP->installCustomFormatSpecifier(customFormatSpecifier); - } - /// @brief Uninstalls user defined format specifier and handler - static inline bool uninstallCustomFormatSpecifier(const char* formatSpecifier) { - return ELPP->uninstallCustomFormatSpecifier(formatSpecifier); - } - /// @brief Returns true if custom format specifier is installed - static inline bool hasCustomFormatSpecifier(const char* formatSpecifier) { - return ELPP->hasCustomFormatSpecifier(formatSpecifier); - } - static inline void validateFileRolling(Logger* logger, Level level) { - if (ELPP == nullptr || logger == nullptr) return; - logger->m_typedConfigurations->validateFileRolling(level, ELPP->preRollOutCallback()); - } -}; -/// @brief Static helpers to deal with loggers and their configurations -class Loggers : base::StaticClass { - public: - /// @brief Gets existing or registers new logger - static Logger* getLogger(const std::string& identity, bool registerIfNotAvailable = true); - /// @brief Changes default log builder for future loggers - static void setDefaultLogBuilder(el::LogBuilderPtr& logBuilderPtr); - /// @brief Installs logger registration callback, this callback is triggered when new logger is registered - template - static inline bool installLoggerRegistrationCallback(const std::string& id) { - return ELPP->registeredLoggers()->installLoggerRegistrationCallback(id); - } - /// @brief Uninstalls log dispatch callback - template - static inline void uninstallLoggerRegistrationCallback(const std::string& id) { - ELPP->registeredLoggers()->uninstallLoggerRegistrationCallback(id); - } - template - static inline T* loggerRegistrationCallback(const std::string& id) { - return ELPP->registeredLoggers()->loggerRegistrationCallback(id); - } - /// @brief Unregisters logger - use it only when you know what you are doing, you may unregister - /// loggers initialized / used by third-party libs. - static bool unregisterLogger(const std::string& identity); - /// @brief Whether or not logger with id is registered - static bool hasLogger(const std::string& identity); - /// @brief Reconfigures specified logger with new configurations - static Logger* reconfigureLogger(Logger* logger, const Configurations& configurations); - /// @brief Reconfigures logger with new configurations after looking it up using identity - static Logger* reconfigureLogger(const std::string& identity, const Configurations& configurations); - /// @brief Reconfigures logger's single configuration - static Logger* reconfigureLogger(const std::string& identity, ConfigurationType configurationType, - const std::string& value); - /// @brief Reconfigures all the existing loggers with new configurations - static void reconfigureAllLoggers(const Configurations& configurations); - /// @brief Reconfigures single configuration for all the loggers - static inline void reconfigureAllLoggers(ConfigurationType configurationType, const std::string& value) { - reconfigureAllLoggers(Level::Global, configurationType, value); - } - /// @brief Reconfigures single configuration for all the loggers for specified level - static void reconfigureAllLoggers(Level level, ConfigurationType configurationType, - const std::string& value); - /// @brief Sets default configurations. This configuration is used for future (and conditionally for existing) loggers - static void setDefaultConfigurations(const Configurations& configurations, - bool reconfigureExistingLoggers = false); - /// @brief Returns current default - static const Configurations* defaultConfigurations(void); - /// @brief Returns log stream reference pointer if needed by user - static const base::LogStreamsReferenceMap* logStreamsReference(void); - /// @brief Default typed configuration based on existing defaultConf - static base::TypedConfigurations defaultTypedConfigurations(void); - /// @brief Populates all logger IDs in current repository. - /// @param [out] targetList List of fill up. - static std::vector* populateAllLoggerIds(std::vector* targetList); - /// @brief Sets configurations from global configuration file. - static void configureFromGlobal(const char* globalConfigurationFilePath); - /// @brief Configures loggers using command line arg. Ensure you have already set command line args, - /// @return False if invalid argument or argument with no value provided, true if attempted to configure logger. - /// If true is returned that does not mean it has been configured successfully, it only means that it - /// has attempeted to configure logger using configuration file provided in argument - static bool configureFromArg(const char* argKey); - /// @brief Flushes all loggers for all levels - Be careful if you dont know how many loggers are registered - static void flushAll(void); - /// @brief Adds logging flag used internally. - static inline void addFlag(LoggingFlag flag) { - ELPP->addFlag(flag); - } - /// @brief Removes logging flag used internally. - static inline void removeFlag(LoggingFlag flag) { - ELPP->removeFlag(flag); - } - /// @brief Determines whether or not certain flag is active - static inline bool hasFlag(LoggingFlag flag) { - return ELPP->hasFlag(flag); - } - /// @brief Adds flag and removes it when scope goes out - class ScopedAddFlag { - public: - ScopedAddFlag(LoggingFlag flag) : m_flag(flag) { - Loggers::addFlag(m_flag); - } - ~ScopedAddFlag(void) { - Loggers::removeFlag(m_flag); - } - private: - LoggingFlag m_flag; - }; - /// @brief Removes flag and add it when scope goes out - class ScopedRemoveFlag { - public: - ScopedRemoveFlag(LoggingFlag flag) : m_flag(flag) { - Loggers::removeFlag(m_flag); - } - ~ScopedRemoveFlag(void) { - Loggers::addFlag(m_flag); - } - private: - LoggingFlag m_flag; - }; - /// @brief Sets hierarchy for logging. Needs to enable logging flag (HierarchicalLogging) - static void setLoggingLevel(Level level) { - ELPP->setLoggingLevel(level); - } - /// @brief Sets verbose level on the fly - static void setVerboseLevel(base::type::VerboseLevel level); - /// @brief Gets current verbose level - static base::type::VerboseLevel verboseLevel(void); - /// @brief Sets vmodules as specified (on the fly) - static void setVModules(const char* modules); - /// @brief Clears vmodules - static void clearVModules(void); -}; -class VersionInfo : base::StaticClass { - public: - /// @brief Current version number - static const std::string version(void); - - /// @brief Release date of current version - static const std::string releaseDate(void); -}; -} // namespace el -#undef VLOG_IS_ON -/// @brief Determines whether verbose logging is on for specified level current file. -#define VLOG_IS_ON(verboseLevel) (ELPP->vRegistry()->allowed(verboseLevel, __FILE__)) -#undef TIMED_BLOCK -#undef TIMED_SCOPE -#undef TIMED_SCOPE_IF -#undef TIMED_FUNC -#undef TIMED_FUNC_IF -#undef ELPP_MIN_UNIT -#if defined(ELPP_PERFORMANCE_MICROSECONDS) -# define ELPP_MIN_UNIT el::base::TimestampUnit::Microsecond -#else -# define ELPP_MIN_UNIT el::base::TimestampUnit::Millisecond -#endif // (defined(ELPP_PERFORMANCE_MICROSECONDS)) -/// @brief Performance tracked scope. Performance gets written when goes out of scope using -/// 'performance' logger. -/// -/// @detail Please note in order to check the performance at a certain time you can use obj->checkpoint(); -/// @see el::base::PerformanceTracker -/// @see el::base::PerformanceTracker::checkpoint -// Note: Do not surround this definition with null macro because of obj instance -#define TIMED_SCOPE_IF(obj, blockname, condition) el::base::type::PerformanceTrackerPtr obj( condition ? \ - new el::base::PerformanceTracker(blockname, ELPP_MIN_UNIT) : nullptr ) -#define TIMED_SCOPE(obj, blockname) TIMED_SCOPE_IF(obj, blockname, true) -#define TIMED_BLOCK(obj, blockName) for (struct { int i; el::base::type::PerformanceTrackerPtr timer; } obj = { 0, \ - el::base::type::PerformanceTrackerPtr(new el::base::PerformanceTracker(blockName, ELPP_MIN_UNIT)) }; obj.i < 1; ++obj.i) -/// @brief Performance tracked function. Performance gets written when goes out of scope using -/// 'performance' logger. -/// -/// @detail Please note in order to check the performance at a certain time you can use obj->checkpoint(); -/// @see el::base::PerformanceTracker -/// @see el::base::PerformanceTracker::checkpoint -#define TIMED_FUNC_IF(obj,condition) TIMED_SCOPE_IF(obj, ELPP_FUNC, condition) -#define TIMED_FUNC(obj) TIMED_SCOPE(obj, ELPP_FUNC) -#undef PERFORMANCE_CHECKPOINT -#undef PERFORMANCE_CHECKPOINT_WITH_ID -#define PERFORMANCE_CHECKPOINT(obj) obj->checkpoint(std::string(), __FILE__, __LINE__, ELPP_FUNC) -#define PERFORMANCE_CHECKPOINT_WITH_ID(obj, id) obj->checkpoint(id, __FILE__, __LINE__, ELPP_FUNC) -#undef ELPP_COUNTER -#undef ELPP_COUNTER_POS -/// @brief Gets hit counter for file/line -#define ELPP_COUNTER (ELPP->hitCounters()->getCounter(__FILE__, __LINE__)) -/// @brief Gets hit counter position for file/line, -1 if not registered yet -#define ELPP_COUNTER_POS (ELPP_COUNTER == nullptr ? -1 : ELPP_COUNTER->hitCounts()) -// Undef levels to support LOG(LEVEL) -#undef INFO -#undef WARNING -#undef DEBUG -#undef ERROR -#undef FATAL -#undef TRACE -#undef VERBOSE -// Undef existing -#undef CINFO -#undef CWARNING -#undef CDEBUG -#undef CFATAL -#undef CERROR -#undef CTRACE -#undef CVERBOSE -#undef CINFO_IF -#undef CWARNING_IF -#undef CDEBUG_IF -#undef CERROR_IF -#undef CFATAL_IF -#undef CTRACE_IF -#undef CVERBOSE_IF -#undef CINFO_EVERY_N -#undef CWARNING_EVERY_N -#undef CDEBUG_EVERY_N -#undef CERROR_EVERY_N -#undef CFATAL_EVERY_N -#undef CTRACE_EVERY_N -#undef CVERBOSE_EVERY_N -#undef CINFO_AFTER_N -#undef CWARNING_AFTER_N -#undef CDEBUG_AFTER_N -#undef CERROR_AFTER_N -#undef CFATAL_AFTER_N -#undef CTRACE_AFTER_N -#undef CVERBOSE_AFTER_N -#undef CINFO_N_TIMES -#undef CWARNING_N_TIMES -#undef CDEBUG_N_TIMES -#undef CERROR_N_TIMES -#undef CFATAL_N_TIMES -#undef CTRACE_N_TIMES -#undef CVERBOSE_N_TIMES -// Normal logs -#if ELPP_INFO_LOG -# define CINFO(writer, dispatchAction, ...) ELPP_WRITE_LOG(writer, el::Level::Info, dispatchAction, __VA_ARGS__) -#else -# define CINFO(writer, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_INFO_LOG -#if ELPP_WARNING_LOG -# define CWARNING(writer, dispatchAction, ...) ELPP_WRITE_LOG(writer, el::Level::Warning, dispatchAction, __VA_ARGS__) -#else -# define CWARNING(writer, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_WARNING_LOG -#if ELPP_DEBUG_LOG -# define CDEBUG(writer, dispatchAction, ...) ELPP_WRITE_LOG(writer, el::Level::Debug, dispatchAction, __VA_ARGS__) -#else -# define CDEBUG(writer, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_DEBUG_LOG -#if ELPP_ERROR_LOG -# define CERROR(writer, dispatchAction, ...) ELPP_WRITE_LOG(writer, el::Level::Error, dispatchAction, __VA_ARGS__) -#else -# define CERROR(writer, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_ERROR_LOG -#if ELPP_FATAL_LOG -# define CFATAL(writer, dispatchAction, ...) ELPP_WRITE_LOG(writer, el::Level::Fatal, dispatchAction, __VA_ARGS__) -#else -# define CFATAL(writer, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_FATAL_LOG -#if ELPP_TRACE_LOG -# define CTRACE(writer, dispatchAction, ...) ELPP_WRITE_LOG(writer, el::Level::Trace, dispatchAction, __VA_ARGS__) -#else -# define CTRACE(writer, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_TRACE_LOG -#if ELPP_VERBOSE_LOG -# define CVERBOSE(writer, vlevel, dispatchAction, ...) if (VLOG_IS_ON(vlevel)) writer(\ -el::Level::Verbose, __FILE__, __LINE__, ELPP_FUNC, dispatchAction, vlevel).construct(el_getVALength(__VA_ARGS__), __VA_ARGS__) -#else -# define CVERBOSE(writer, vlevel, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_VERBOSE_LOG -// Conditional logs -#if ELPP_INFO_LOG -# define CINFO_IF(writer, condition_, dispatchAction, ...) \ -ELPP_WRITE_LOG_IF(writer, (condition_), el::Level::Info, dispatchAction, __VA_ARGS__) -#else -# define CINFO_IF(writer, condition_, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_INFO_LOG -#if ELPP_WARNING_LOG -# define CWARNING_IF(writer, condition_, dispatchAction, ...)\ -ELPP_WRITE_LOG_IF(writer, (condition_), el::Level::Warning, dispatchAction, __VA_ARGS__) -#else -# define CWARNING_IF(writer, condition_, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_WARNING_LOG -#if ELPP_DEBUG_LOG -# define CDEBUG_IF(writer, condition_, dispatchAction, ...)\ -ELPP_WRITE_LOG_IF(writer, (condition_), el::Level::Debug, dispatchAction, __VA_ARGS__) -#else -# define CDEBUG_IF(writer, condition_, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_DEBUG_LOG -#if ELPP_ERROR_LOG -# define CERROR_IF(writer, condition_, dispatchAction, ...)\ -ELPP_WRITE_LOG_IF(writer, (condition_), el::Level::Error, dispatchAction, __VA_ARGS__) -#else -# define CERROR_IF(writer, condition_, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_ERROR_LOG -#if ELPP_FATAL_LOG -# define CFATAL_IF(writer, condition_, dispatchAction, ...)\ -ELPP_WRITE_LOG_IF(writer, (condition_), el::Level::Fatal, dispatchAction, __VA_ARGS__) -#else -# define CFATAL_IF(writer, condition_, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_FATAL_LOG -#if ELPP_TRACE_LOG -# define CTRACE_IF(writer, condition_, dispatchAction, ...)\ -ELPP_WRITE_LOG_IF(writer, (condition_), el::Level::Trace, dispatchAction, __VA_ARGS__) -#else -# define CTRACE_IF(writer, condition_, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_TRACE_LOG -#if ELPP_VERBOSE_LOG -# define CVERBOSE_IF(writer, condition_, vlevel, dispatchAction, ...) if (VLOG_IS_ON(vlevel) && (condition_)) writer( \ -el::Level::Verbose, __FILE__, __LINE__, ELPP_FUNC, dispatchAction, vlevel).construct(el_getVALength(__VA_ARGS__), __VA_ARGS__) -#else -# define CVERBOSE_IF(writer, condition_, vlevel, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_VERBOSE_LOG -// Occasional logs -#if ELPP_INFO_LOG -# define CINFO_EVERY_N(writer, occasion, dispatchAction, ...)\ -ELPP_WRITE_LOG_EVERY_N(writer, occasion, el::Level::Info, dispatchAction, __VA_ARGS__) -#else -# define CINFO_EVERY_N(writer, occasion, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_INFO_LOG -#if ELPP_WARNING_LOG -# define CWARNING_EVERY_N(writer, occasion, dispatchAction, ...)\ -ELPP_WRITE_LOG_EVERY_N(writer, occasion, el::Level::Warning, dispatchAction, __VA_ARGS__) -#else -# define CWARNING_EVERY_N(writer, occasion, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_WARNING_LOG -#if ELPP_DEBUG_LOG -# define CDEBUG_EVERY_N(writer, occasion, dispatchAction, ...)\ -ELPP_WRITE_LOG_EVERY_N(writer, occasion, el::Level::Debug, dispatchAction, __VA_ARGS__) -#else -# define CDEBUG_EVERY_N(writer, occasion, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_DEBUG_LOG -#if ELPP_ERROR_LOG -# define CERROR_EVERY_N(writer, occasion, dispatchAction, ...)\ -ELPP_WRITE_LOG_EVERY_N(writer, occasion, el::Level::Error, dispatchAction, __VA_ARGS__) -#else -# define CERROR_EVERY_N(writer, occasion, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_ERROR_LOG -#if ELPP_FATAL_LOG -# define CFATAL_EVERY_N(writer, occasion, dispatchAction, ...)\ -ELPP_WRITE_LOG_EVERY_N(writer, occasion, el::Level::Fatal, dispatchAction, __VA_ARGS__) -#else -# define CFATAL_EVERY_N(writer, occasion, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_FATAL_LOG -#if ELPP_TRACE_LOG -# define CTRACE_EVERY_N(writer, occasion, dispatchAction, ...)\ -ELPP_WRITE_LOG_EVERY_N(writer, occasion, el::Level::Trace, dispatchAction, __VA_ARGS__) -#else -# define CTRACE_EVERY_N(writer, occasion, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_TRACE_LOG -#if ELPP_VERBOSE_LOG -# define CVERBOSE_EVERY_N(writer, occasion, vlevel, dispatchAction, ...)\ -CVERBOSE_IF(writer, ELPP->validateEveryNCounter(__FILE__, __LINE__, occasion), vlevel, dispatchAction, __VA_ARGS__) -#else -# define CVERBOSE_EVERY_N(writer, occasion, vlevel, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_VERBOSE_LOG -// After N logs -#if ELPP_INFO_LOG -# define CINFO_AFTER_N(writer, n, dispatchAction, ...)\ -ELPP_WRITE_LOG_AFTER_N(writer, n, el::Level::Info, dispatchAction, __VA_ARGS__) -#else -# define CINFO_AFTER_N(writer, n, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_INFO_LOG -#if ELPP_WARNING_LOG -# define CWARNING_AFTER_N(writer, n, dispatchAction, ...)\ -ELPP_WRITE_LOG_AFTER_N(writer, n, el::Level::Warning, dispatchAction, __VA_ARGS__) -#else -# define CWARNING_AFTER_N(writer, n, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_WARNING_LOG -#if ELPP_DEBUG_LOG -# define CDEBUG_AFTER_N(writer, n, dispatchAction, ...)\ -ELPP_WRITE_LOG_AFTER_N(writer, n, el::Level::Debug, dispatchAction, __VA_ARGS__) -#else -# define CDEBUG_AFTER_N(writer, n, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_DEBUG_LOG -#if ELPP_ERROR_LOG -# define CERROR_AFTER_N(writer, n, dispatchAction, ...)\ -ELPP_WRITE_LOG_AFTER_N(writer, n, el::Level::Error, dispatchAction, __VA_ARGS__) -#else -# define CERROR_AFTER_N(writer, n, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_ERROR_LOG -#if ELPP_FATAL_LOG -# define CFATAL_AFTER_N(writer, n, dispatchAction, ...)\ -ELPP_WRITE_LOG_AFTER_N(writer, n, el::Level::Fatal, dispatchAction, __VA_ARGS__) -#else -# define CFATAL_AFTER_N(writer, n, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_FATAL_LOG -#if ELPP_TRACE_LOG -# define CTRACE_AFTER_N(writer, n, dispatchAction, ...)\ -ELPP_WRITE_LOG_AFTER_N(writer, n, el::Level::Trace, dispatchAction, __VA_ARGS__) -#else -# define CTRACE_AFTER_N(writer, n, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_TRACE_LOG -#if ELPP_VERBOSE_LOG -# define CVERBOSE_AFTER_N(writer, n, vlevel, dispatchAction, ...)\ -CVERBOSE_IF(writer, ELPP->validateAfterNCounter(__FILE__, __LINE__, n), vlevel, dispatchAction, __VA_ARGS__) -#else -# define CVERBOSE_AFTER_N(writer, n, vlevel, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_VERBOSE_LOG -// N Times logs -#if ELPP_INFO_LOG -# define CINFO_N_TIMES(writer, n, dispatchAction, ...)\ -ELPP_WRITE_LOG_N_TIMES(writer, n, el::Level::Info, dispatchAction, __VA_ARGS__) -#else -# define CINFO_N_TIMES(writer, n, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_INFO_LOG -#if ELPP_WARNING_LOG -# define CWARNING_N_TIMES(writer, n, dispatchAction, ...)\ -ELPP_WRITE_LOG_N_TIMES(writer, n, el::Level::Warning, dispatchAction, __VA_ARGS__) -#else -# define CWARNING_N_TIMES(writer, n, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_WARNING_LOG -#if ELPP_DEBUG_LOG -# define CDEBUG_N_TIMES(writer, n, dispatchAction, ...)\ -ELPP_WRITE_LOG_N_TIMES(writer, n, el::Level::Debug, dispatchAction, __VA_ARGS__) -#else -# define CDEBUG_N_TIMES(writer, n, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_DEBUG_LOG -#if ELPP_ERROR_LOG -# define CERROR_N_TIMES(writer, n, dispatchAction, ...)\ -ELPP_WRITE_LOG_N_TIMES(writer, n, el::Level::Error, dispatchAction, __VA_ARGS__) -#else -# define CERROR_N_TIMES(writer, n, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_ERROR_LOG -#if ELPP_FATAL_LOG -# define CFATAL_N_TIMES(writer, n, dispatchAction, ...)\ -ELPP_WRITE_LOG_N_TIMES(writer, n, el::Level::Fatal, dispatchAction, __VA_ARGS__) -#else -# define CFATAL_N_TIMES(writer, n, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_FATAL_LOG -#if ELPP_TRACE_LOG -# define CTRACE_N_TIMES(writer, n, dispatchAction, ...)\ -ELPP_WRITE_LOG_N_TIMES(writer, n, el::Level::Trace, dispatchAction, __VA_ARGS__) -#else -# define CTRACE_N_TIMES(writer, n, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_TRACE_LOG -#if ELPP_VERBOSE_LOG -# define CVERBOSE_N_TIMES(writer, n, vlevel, dispatchAction, ...)\ -CVERBOSE_IF(writer, ELPP->validateNTimesCounter(__FILE__, __LINE__, n), vlevel, dispatchAction, __VA_ARGS__) -#else -# define CVERBOSE_N_TIMES(writer, n, vlevel, dispatchAction, ...) el::base::NullWriter() -#endif // ELPP_VERBOSE_LOG -// -// Custom Loggers - Requires (level, dispatchAction, loggerId/s) -// -// undef existing -#undef CLOG -#undef CLOG_VERBOSE -#undef CVLOG -#undef CLOG_IF -#undef CLOG_VERBOSE_IF -#undef CVLOG_IF -#undef CLOG_EVERY_N -#undef CVLOG_EVERY_N -#undef CLOG_AFTER_N -#undef CVLOG_AFTER_N -#undef CLOG_N_TIMES -#undef CVLOG_N_TIMES -// Normal logs -#define CLOG(LEVEL, ...)\ -C##LEVEL(el::base::Writer, el::base::DispatchAction::NormalLog, __VA_ARGS__) -#define CVLOG(vlevel, ...) CVERBOSE(el::base::Writer, vlevel, el::base::DispatchAction::NormalLog, __VA_ARGS__) -// Conditional logs -#define CLOG_IF(condition, LEVEL, ...)\ -C##LEVEL##_IF(el::base::Writer, condition, el::base::DispatchAction::NormalLog, __VA_ARGS__) -#define CVLOG_IF(condition, vlevel, ...)\ -CVERBOSE_IF(el::base::Writer, condition, vlevel, el::base::DispatchAction::NormalLog, __VA_ARGS__) -// Hit counts based logs -#define CLOG_EVERY_N(n, LEVEL, ...)\ -C##LEVEL##_EVERY_N(el::base::Writer, n, el::base::DispatchAction::NormalLog, __VA_ARGS__) -#define CVLOG_EVERY_N(n, vlevel, ...)\ -CVERBOSE_EVERY_N(el::base::Writer, n, vlevel, el::base::DispatchAction::NormalLog, __VA_ARGS__) -#define CLOG_AFTER_N(n, LEVEL, ...)\ -C##LEVEL##_AFTER_N(el::base::Writer, n, el::base::DispatchAction::NormalLog, __VA_ARGS__) -#define CVLOG_AFTER_N(n, vlevel, ...)\ -CVERBOSE_AFTER_N(el::base::Writer, n, vlevel, el::base::DispatchAction::NormalLog, __VA_ARGS__) -#define CLOG_N_TIMES(n, LEVEL, ...)\ -C##LEVEL##_N_TIMES(el::base::Writer, n, el::base::DispatchAction::NormalLog, __VA_ARGS__) -#define CVLOG_N_TIMES(n, vlevel, ...)\ -CVERBOSE_N_TIMES(el::base::Writer, n, vlevel, el::base::DispatchAction::NormalLog, __VA_ARGS__) -// -// Default Loggers macro using CLOG(), CLOG_VERBOSE() and CVLOG() macros -// -// undef existing -#undef LOG -#undef VLOG -#undef LOG_IF -#undef VLOG_IF -#undef LOG_EVERY_N -#undef VLOG_EVERY_N -#undef LOG_AFTER_N -#undef VLOG_AFTER_N -#undef LOG_N_TIMES -#undef VLOG_N_TIMES -#undef ELPP_CURR_FILE_LOGGER_ID -#if defined(ELPP_DEFAULT_LOGGER) -# define ELPP_CURR_FILE_LOGGER_ID ELPP_DEFAULT_LOGGER -#else -# define ELPP_CURR_FILE_LOGGER_ID el::base::consts::kDefaultLoggerId -#endif -#undef ELPP_TRACE -#define ELPP_TRACE CLOG(TRACE, ELPP_CURR_FILE_LOGGER_ID) -// Normal logs -#define LOG(LEVEL) CLOG(LEVEL, ELPP_CURR_FILE_LOGGER_ID) -#define VLOG(vlevel) CVLOG(vlevel, ELPP_CURR_FILE_LOGGER_ID) -// Conditional logs -#define LOG_IF(condition, LEVEL) CLOG_IF(condition, LEVEL, ELPP_CURR_FILE_LOGGER_ID) -#define VLOG_IF(condition, vlevel) CVLOG_IF(condition, vlevel, ELPP_CURR_FILE_LOGGER_ID) -// Hit counts based logs -#define LOG_EVERY_N(n, LEVEL) CLOG_EVERY_N(n, LEVEL, ELPP_CURR_FILE_LOGGER_ID) -#define VLOG_EVERY_N(n, vlevel) CVLOG_EVERY_N(n, vlevel, ELPP_CURR_FILE_LOGGER_ID) -#define LOG_AFTER_N(n, LEVEL) CLOG_AFTER_N(n, LEVEL, ELPP_CURR_FILE_LOGGER_ID) -#define VLOG_AFTER_N(n, vlevel) CVLOG_AFTER_N(n, vlevel, ELPP_CURR_FILE_LOGGER_ID) -#define LOG_N_TIMES(n, LEVEL) CLOG_N_TIMES(n, LEVEL, ELPP_CURR_FILE_LOGGER_ID) -#define VLOG_N_TIMES(n, vlevel) CVLOG_N_TIMES(n, vlevel, ELPP_CURR_FILE_LOGGER_ID) -// Generic PLOG() -#undef CPLOG -#undef CPLOG_IF -#undef PLOG -#undef PLOG_IF -#undef DCPLOG -#undef DCPLOG_IF -#undef DPLOG -#undef DPLOG_IF -#define CPLOG(LEVEL, ...)\ -C##LEVEL(el::base::PErrorWriter, el::base::DispatchAction::NormalLog, __VA_ARGS__) -#define CPLOG_IF(condition, LEVEL, ...)\ -C##LEVEL##_IF(el::base::PErrorWriter, condition, el::base::DispatchAction::NormalLog, __VA_ARGS__) -#define DCPLOG(LEVEL, ...)\ -if (ELPP_DEBUG_LOG) C##LEVEL(el::base::PErrorWriter, el::base::DispatchAction::NormalLog, __VA_ARGS__) -#define DCPLOG_IF(condition, LEVEL, ...)\ -C##LEVEL##_IF(el::base::PErrorWriter, (ELPP_DEBUG_LOG) && (condition), el::base::DispatchAction::NormalLog, __VA_ARGS__) -#define PLOG(LEVEL) CPLOG(LEVEL, ELPP_CURR_FILE_LOGGER_ID) -#define PLOG_IF(condition, LEVEL) CPLOG_IF(condition, LEVEL, ELPP_CURR_FILE_LOGGER_ID) -#define DPLOG(LEVEL) DCPLOG(LEVEL, ELPP_CURR_FILE_LOGGER_ID) -#define DPLOG_IF(condition, LEVEL) DCPLOG_IF(condition, LEVEL, ELPP_CURR_FILE_LOGGER_ID) -// Generic SYSLOG() -#undef CSYSLOG -#undef CSYSLOG_IF -#undef CSYSLOG_EVERY_N -#undef CSYSLOG_AFTER_N -#undef CSYSLOG_N_TIMES -#undef SYSLOG -#undef SYSLOG_IF -#undef SYSLOG_EVERY_N -#undef SYSLOG_AFTER_N -#undef SYSLOG_N_TIMES -#undef DCSYSLOG -#undef DCSYSLOG_IF -#undef DCSYSLOG_EVERY_N -#undef DCSYSLOG_AFTER_N -#undef DCSYSLOG_N_TIMES -#undef DSYSLOG -#undef DSYSLOG_IF -#undef DSYSLOG_EVERY_N -#undef DSYSLOG_AFTER_N -#undef DSYSLOG_N_TIMES -#if defined(ELPP_SYSLOG) -# define CSYSLOG(LEVEL, ...)\ -C##LEVEL(el::base::Writer, el::base::DispatchAction::SysLog, __VA_ARGS__) -# define CSYSLOG_IF(condition, LEVEL, ...)\ -C##LEVEL##_IF(el::base::Writer, condition, el::base::DispatchAction::SysLog, __VA_ARGS__) -# define CSYSLOG_EVERY_N(n, LEVEL, ...) C##LEVEL##_EVERY_N(el::base::Writer, n, el::base::DispatchAction::SysLog, __VA_ARGS__) -# define CSYSLOG_AFTER_N(n, LEVEL, ...) C##LEVEL##_AFTER_N(el::base::Writer, n, el::base::DispatchAction::SysLog, __VA_ARGS__) -# define CSYSLOG_N_TIMES(n, LEVEL, ...) C##LEVEL##_N_TIMES(el::base::Writer, n, el::base::DispatchAction::SysLog, __VA_ARGS__) -# define SYSLOG(LEVEL) CSYSLOG(LEVEL, el::base::consts::kSysLogLoggerId) -# define SYSLOG_IF(condition, LEVEL) CSYSLOG_IF(condition, LEVEL, el::base::consts::kSysLogLoggerId) -# define SYSLOG_EVERY_N(n, LEVEL) CSYSLOG_EVERY_N(n, LEVEL, el::base::consts::kSysLogLoggerId) -# define SYSLOG_AFTER_N(n, LEVEL) CSYSLOG_AFTER_N(n, LEVEL, el::base::consts::kSysLogLoggerId) -# define SYSLOG_N_TIMES(n, LEVEL) CSYSLOG_N_TIMES(n, LEVEL, el::base::consts::kSysLogLoggerId) -# define DCSYSLOG(LEVEL, ...) if (ELPP_DEBUG_LOG) C##LEVEL(el::base::Writer, el::base::DispatchAction::SysLog, __VA_ARGS__) -# define DCSYSLOG_IF(condition, LEVEL, ...)\ -C##LEVEL##_IF(el::base::Writer, (ELPP_DEBUG_LOG) && (condition), el::base::DispatchAction::SysLog, __VA_ARGS__) -# define DCSYSLOG_EVERY_N(n, LEVEL, ...)\ -if (ELPP_DEBUG_LOG) C##LEVEL##_EVERY_N(el::base::Writer, n, el::base::DispatchAction::SysLog, __VA_ARGS__) -# define DCSYSLOG_AFTER_N(n, LEVEL, ...)\ -if (ELPP_DEBUG_LOG) C##LEVEL##_AFTER_N(el::base::Writer, n, el::base::DispatchAction::SysLog, __VA_ARGS__) -# define DCSYSLOG_N_TIMES(n, LEVEL, ...)\ -if (ELPP_DEBUG_LOG) C##LEVEL##_EVERY_N(el::base::Writer, n, el::base::DispatchAction::SysLog, __VA_ARGS__) -# define DSYSLOG(LEVEL) DCSYSLOG(LEVEL, el::base::consts::kSysLogLoggerId) -# define DSYSLOG_IF(condition, LEVEL) DCSYSLOG_IF(condition, LEVEL, el::base::consts::kSysLogLoggerId) -# define DSYSLOG_EVERY_N(n, LEVEL) DCSYSLOG_EVERY_N(n, LEVEL, el::base::consts::kSysLogLoggerId) -# define DSYSLOG_AFTER_N(n, LEVEL) DCSYSLOG_AFTER_N(n, LEVEL, el::base::consts::kSysLogLoggerId) -# define DSYSLOG_N_TIMES(n, LEVEL) DCSYSLOG_N_TIMES(n, LEVEL, el::base::consts::kSysLogLoggerId) -#else -# define CSYSLOG(LEVEL, ...) el::base::NullWriter() -# define CSYSLOG_IF(condition, LEVEL, ...) el::base::NullWriter() -# define CSYSLOG_EVERY_N(n, LEVEL, ...) el::base::NullWriter() -# define CSYSLOG_AFTER_N(n, LEVEL, ...) el::base::NullWriter() -# define CSYSLOG_N_TIMES(n, LEVEL, ...) el::base::NullWriter() -# define SYSLOG(LEVEL) el::base::NullWriter() -# define SYSLOG_IF(condition, LEVEL) el::base::NullWriter() -# define SYSLOG_EVERY_N(n, LEVEL) el::base::NullWriter() -# define SYSLOG_AFTER_N(n, LEVEL) el::base::NullWriter() -# define SYSLOG_N_TIMES(n, LEVEL) el::base::NullWriter() -# define DCSYSLOG(LEVEL, ...) el::base::NullWriter() -# define DCSYSLOG_IF(condition, LEVEL, ...) el::base::NullWriter() -# define DCSYSLOG_EVERY_N(n, LEVEL, ...) el::base::NullWriter() -# define DCSYSLOG_AFTER_N(n, LEVEL, ...) el::base::NullWriter() -# define DCSYSLOG_N_TIMES(n, LEVEL, ...) el::base::NullWriter() -# define DSYSLOG(LEVEL) el::base::NullWriter() -# define DSYSLOG_IF(condition, LEVEL) el::base::NullWriter() -# define DSYSLOG_EVERY_N(n, LEVEL) el::base::NullWriter() -# define DSYSLOG_AFTER_N(n, LEVEL) el::base::NullWriter() -# define DSYSLOG_N_TIMES(n, LEVEL) el::base::NullWriter() -#endif // defined(ELPP_SYSLOG) -// -// Custom Debug Only Loggers - Requires (level, loggerId/s) -// -// undef existing -#undef DCLOG -#undef DCVLOG -#undef DCLOG_IF -#undef DCVLOG_IF -#undef DCLOG_EVERY_N -#undef DCVLOG_EVERY_N -#undef DCLOG_AFTER_N -#undef DCVLOG_AFTER_N -#undef DCLOG_N_TIMES -#undef DCVLOG_N_TIMES -// Normal logs -#define DCLOG(LEVEL, ...) if (ELPP_DEBUG_LOG) CLOG(LEVEL, __VA_ARGS__) -#define DCLOG_VERBOSE(vlevel, ...) if (ELPP_DEBUG_LOG) CLOG_VERBOSE(vlevel, __VA_ARGS__) -#define DCVLOG(vlevel, ...) if (ELPP_DEBUG_LOG) CVLOG(vlevel, __VA_ARGS__) -// Conditional logs -#define DCLOG_IF(condition, LEVEL, ...) if (ELPP_DEBUG_LOG) CLOG_IF(condition, LEVEL, __VA_ARGS__) -#define DCVLOG_IF(condition, vlevel, ...) if (ELPP_DEBUG_LOG) CVLOG_IF(condition, vlevel, __VA_ARGS__) -// Hit counts based logs -#define DCLOG_EVERY_N(n, LEVEL, ...) if (ELPP_DEBUG_LOG) CLOG_EVERY_N(n, LEVEL, __VA_ARGS__) -#define DCVLOG_EVERY_N(n, vlevel, ...) if (ELPP_DEBUG_LOG) CVLOG_EVERY_N(n, vlevel, __VA_ARGS__) -#define DCLOG_AFTER_N(n, LEVEL, ...) if (ELPP_DEBUG_LOG) CLOG_AFTER_N(n, LEVEL, __VA_ARGS__) -#define DCVLOG_AFTER_N(n, vlevel, ...) if (ELPP_DEBUG_LOG) CVLOG_AFTER_N(n, vlevel, __VA_ARGS__) -#define DCLOG_N_TIMES(n, LEVEL, ...) if (ELPP_DEBUG_LOG) CLOG_N_TIMES(n, LEVEL, __VA_ARGS__) -#define DCVLOG_N_TIMES(n, vlevel, ...) if (ELPP_DEBUG_LOG) CVLOG_N_TIMES(n, vlevel, __VA_ARGS__) -// -// Default Debug Only Loggers macro using CLOG(), CLOG_VERBOSE() and CVLOG() macros -// -#if !defined(ELPP_NO_DEBUG_MACROS) -// undef existing -#undef DLOG -#undef DVLOG -#undef DLOG_IF -#undef DVLOG_IF -#undef DLOG_EVERY_N -#undef DVLOG_EVERY_N -#undef DLOG_AFTER_N -#undef DVLOG_AFTER_N -#undef DLOG_N_TIMES -#undef DVLOG_N_TIMES -// Normal logs -#define DLOG(LEVEL) DCLOG(LEVEL, ELPP_CURR_FILE_LOGGER_ID) -#define DVLOG(vlevel) DCVLOG(vlevel, ELPP_CURR_FILE_LOGGER_ID) -// Conditional logs -#define DLOG_IF(condition, LEVEL) DCLOG_IF(condition, LEVEL, ELPP_CURR_FILE_LOGGER_ID) -#define DVLOG_IF(condition, vlevel) DCVLOG_IF(condition, vlevel, ELPP_CURR_FILE_LOGGER_ID) -// Hit counts based logs -#define DLOG_EVERY_N(n, LEVEL) DCLOG_EVERY_N(n, LEVEL, ELPP_CURR_FILE_LOGGER_ID) -#define DVLOG_EVERY_N(n, vlevel) DCVLOG_EVERY_N(n, vlevel, ELPP_CURR_FILE_LOGGER_ID) -#define DLOG_AFTER_N(n, LEVEL) DCLOG_AFTER_N(n, LEVEL, ELPP_CURR_FILE_LOGGER_ID) -#define DVLOG_AFTER_N(n, vlevel) DCVLOG_AFTER_N(n, vlevel, ELPP_CURR_FILE_LOGGER_ID) -#define DLOG_N_TIMES(n, LEVEL) DCLOG_N_TIMES(n, LEVEL, ELPP_CURR_FILE_LOGGER_ID) -#define DVLOG_N_TIMES(n, vlevel) DCVLOG_N_TIMES(n, vlevel, ELPP_CURR_FILE_LOGGER_ID) -#endif // defined(ELPP_NO_DEBUG_MACROS) -#if !defined(ELPP_NO_CHECK_MACROS) -// Check macros -#undef CCHECK -#undef CPCHECK -#undef CCHECK_EQ -#undef CCHECK_NE -#undef CCHECK_LT -#undef CCHECK_GT -#undef CCHECK_LE -#undef CCHECK_GE -#undef CCHECK_BOUNDS -#undef CCHECK_NOTNULL -#undef CCHECK_STRCASEEQ -#undef CCHECK_STRCASENE -#undef CHECK -#undef PCHECK -#undef CHECK_EQ -#undef CHECK_NE -#undef CHECK_LT -#undef CHECK_GT -#undef CHECK_LE -#undef CHECK_GE -#undef CHECK_BOUNDS -#undef CHECK_NOTNULL -#undef CHECK_STRCASEEQ -#undef CHECK_STRCASENE -#define CCHECK(condition, ...) CLOG_IF(!(condition), FATAL, __VA_ARGS__) << "Check failed: [" << #condition << "] " -#define CPCHECK(condition, ...) CPLOG_IF(!(condition), FATAL, __VA_ARGS__) << "Check failed: [" << #condition << "] " -#define CHECK(condition) CCHECK(condition, ELPP_CURR_FILE_LOGGER_ID) -#define PCHECK(condition) CPCHECK(condition, ELPP_CURR_FILE_LOGGER_ID) -#define CCHECK_EQ(a, b, ...) CCHECK(a == b, __VA_ARGS__) -#define CCHECK_NE(a, b, ...) CCHECK(a != b, __VA_ARGS__) -#define CCHECK_LT(a, b, ...) CCHECK(a < b, __VA_ARGS__) -#define CCHECK_GT(a, b, ...) CCHECK(a > b, __VA_ARGS__) -#define CCHECK_LE(a, b, ...) CCHECK(a <= b, __VA_ARGS__) -#define CCHECK_GE(a, b, ...) CCHECK(a >= b, __VA_ARGS__) -#define CCHECK_BOUNDS(val, min, max, ...) CCHECK(val >= min && val <= max, __VA_ARGS__) -#define CHECK_EQ(a, b) CCHECK_EQ(a, b, ELPP_CURR_FILE_LOGGER_ID) -#define CHECK_NE(a, b) CCHECK_NE(a, b, ELPP_CURR_FILE_LOGGER_ID) -#define CHECK_LT(a, b) CCHECK_LT(a, b, ELPP_CURR_FILE_LOGGER_ID) -#define CHECK_GT(a, b) CCHECK_GT(a, b, ELPP_CURR_FILE_LOGGER_ID) -#define CHECK_LE(a, b) CCHECK_LE(a, b, ELPP_CURR_FILE_LOGGER_ID) -#define CHECK_GE(a, b) CCHECK_GE(a, b, ELPP_CURR_FILE_LOGGER_ID) -#define CHECK_BOUNDS(val, min, max) CCHECK_BOUNDS(val, min, max, ELPP_CURR_FILE_LOGGER_ID) -#define CCHECK_NOTNULL(ptr, ...) CCHECK((ptr) != nullptr, __VA_ARGS__) -#define CCHECK_STREQ(str1, str2, ...) CLOG_IF(!el::base::utils::Str::cStringEq(str1, str2), FATAL, __VA_ARGS__) \ -<< "Check failed: [" << #str1 << " == " << #str2 << "] " -#define CCHECK_STRNE(str1, str2, ...) CLOG_IF(el::base::utils::Str::cStringEq(str1, str2), FATAL, __VA_ARGS__) \ -<< "Check failed: [" << #str1 << " != " << #str2 << "] " -#define CCHECK_STRCASEEQ(str1, str2, ...) CLOG_IF(!el::base::utils::Str::cStringCaseEq(str1, str2), FATAL, __VA_ARGS__) \ -<< "Check failed: [" << #str1 << " == " << #str2 << "] " -#define CCHECK_STRCASENE(str1, str2, ...) CLOG_IF(el::base::utils::Str::cStringCaseEq(str1, str2), FATAL, __VA_ARGS__) \ -<< "Check failed: [" << #str1 << " != " << #str2 << "] " -#define CHECK_NOTNULL(ptr) CCHECK_NOTNULL((ptr), ELPP_CURR_FILE_LOGGER_ID) -#define CHECK_STREQ(str1, str2) CCHECK_STREQ(str1, str2, ELPP_CURR_FILE_LOGGER_ID) -#define CHECK_STRNE(str1, str2) CCHECK_STRNE(str1, str2, ELPP_CURR_FILE_LOGGER_ID) -#define CHECK_STRCASEEQ(str1, str2) CCHECK_STRCASEEQ(str1, str2, ELPP_CURR_FILE_LOGGER_ID) -#define CHECK_STRCASENE(str1, str2) CCHECK_STRCASENE(str1, str2, ELPP_CURR_FILE_LOGGER_ID) -#undef DCCHECK -#undef DCCHECK_EQ -#undef DCCHECK_NE -#undef DCCHECK_LT -#undef DCCHECK_GT -#undef DCCHECK_LE -#undef DCCHECK_GE -#undef DCCHECK_BOUNDS -#undef DCCHECK_NOTNULL -#undef DCCHECK_STRCASEEQ -#undef DCCHECK_STRCASENE -#undef DCPCHECK -#undef DCHECK -#undef DCHECK_EQ -#undef DCHECK_NE -#undef DCHECK_LT -#undef DCHECK_GT -#undef DCHECK_LE -#undef DCHECK_GE -#undef DCHECK_BOUNDS_ -#undef DCHECK_NOTNULL -#undef DCHECK_STRCASEEQ -#undef DCHECK_STRCASENE -#undef DPCHECK -#define DCCHECK(condition, ...) if (ELPP_DEBUG_LOG) CCHECK(condition, __VA_ARGS__) -#define DCCHECK_EQ(a, b, ...) if (ELPP_DEBUG_LOG) CCHECK_EQ(a, b, __VA_ARGS__) -#define DCCHECK_NE(a, b, ...) if (ELPP_DEBUG_LOG) CCHECK_NE(a, b, __VA_ARGS__) -#define DCCHECK_LT(a, b, ...) if (ELPP_DEBUG_LOG) CCHECK_LT(a, b, __VA_ARGS__) -#define DCCHECK_GT(a, b, ...) if (ELPP_DEBUG_LOG) CCHECK_GT(a, b, __VA_ARGS__) -#define DCCHECK_LE(a, b, ...) if (ELPP_DEBUG_LOG) CCHECK_LE(a, b, __VA_ARGS__) -#define DCCHECK_GE(a, b, ...) if (ELPP_DEBUG_LOG) CCHECK_GE(a, b, __VA_ARGS__) -#define DCCHECK_BOUNDS(val, min, max, ...) if (ELPP_DEBUG_LOG) CCHECK_BOUNDS(val, min, max, __VA_ARGS__) -#define DCCHECK_NOTNULL(ptr, ...) if (ELPP_DEBUG_LOG) CCHECK_NOTNULL((ptr), __VA_ARGS__) -#define DCCHECK_STREQ(str1, str2, ...) if (ELPP_DEBUG_LOG) CCHECK_STREQ(str1, str2, __VA_ARGS__) -#define DCCHECK_STRNE(str1, str2, ...) if (ELPP_DEBUG_LOG) CCHECK_STRNE(str1, str2, __VA_ARGS__) -#define DCCHECK_STRCASEEQ(str1, str2, ...) if (ELPP_DEBUG_LOG) CCHECK_STRCASEEQ(str1, str2, __VA_ARGS__) -#define DCCHECK_STRCASENE(str1, str2, ...) if (ELPP_DEBUG_LOG) CCHECK_STRCASENE(str1, str2, __VA_ARGS__) -#define DCPCHECK(condition, ...) if (ELPP_DEBUG_LOG) CPCHECK(condition, __VA_ARGS__) -#define DCHECK(condition) DCCHECK(condition, ELPP_CURR_FILE_LOGGER_ID) -#define DCHECK_EQ(a, b) DCCHECK_EQ(a, b, ELPP_CURR_FILE_LOGGER_ID) -#define DCHECK_NE(a, b) DCCHECK_NE(a, b, ELPP_CURR_FILE_LOGGER_ID) -#define DCHECK_LT(a, b) DCCHECK_LT(a, b, ELPP_CURR_FILE_LOGGER_ID) -#define DCHECK_GT(a, b) DCCHECK_GT(a, b, ELPP_CURR_FILE_LOGGER_ID) -#define DCHECK_LE(a, b) DCCHECK_LE(a, b, ELPP_CURR_FILE_LOGGER_ID) -#define DCHECK_GE(a, b) DCCHECK_GE(a, b, ELPP_CURR_FILE_LOGGER_ID) -#define DCHECK_BOUNDS(val, min, max) DCCHECK_BOUNDS(val, min, max, ELPP_CURR_FILE_LOGGER_ID) -#define DCHECK_NOTNULL(ptr) DCCHECK_NOTNULL((ptr), ELPP_CURR_FILE_LOGGER_ID) -#define DCHECK_STREQ(str1, str2) DCCHECK_STREQ(str1, str2, ELPP_CURR_FILE_LOGGER_ID) -#define DCHECK_STRNE(str1, str2) DCCHECK_STRNE(str1, str2, ELPP_CURR_FILE_LOGGER_ID) -#define DCHECK_STRCASEEQ(str1, str2) DCCHECK_STRCASEEQ(str1, str2, ELPP_CURR_FILE_LOGGER_ID) -#define DCHECK_STRCASENE(str1, str2) DCCHECK_STRCASENE(str1, str2, ELPP_CURR_FILE_LOGGER_ID) -#define DPCHECK(condition) DCPCHECK(condition, ELPP_CURR_FILE_LOGGER_ID) -#endif // defined(ELPP_NO_CHECK_MACROS) -#if defined(ELPP_DISABLE_DEFAULT_CRASH_HANDLING) -# define ELPP_USE_DEF_CRASH_HANDLER false -#else -# define ELPP_USE_DEF_CRASH_HANDLER true -#endif // defined(ELPP_DISABLE_DEFAULT_CRASH_HANDLING) -#define ELPP_CRASH_HANDLER_INIT -#define ELPP_INIT_EASYLOGGINGPP(val) \ -namespace el { \ -namespace base { \ -el::base::type::StoragePointer elStorage(val); \ -} \ -el::base::debug::CrashHandler elCrashHandler(ELPP_USE_DEF_CRASH_HANDLER); \ -} - -#if ELPP_ASYNC_LOGGING -# define INITIALIZE_EASYLOGGINGPP ELPP_INIT_EASYLOGGINGPP(new el::base::Storage(el::LogBuilderPtr(new el::base::DefaultLogBuilder()),\ -new el::base::AsyncDispatchWorker())) -#else -# define INITIALIZE_EASYLOGGINGPP ELPP_INIT_EASYLOGGINGPP(new el::base::Storage(el::LogBuilderPtr(new el::base::DefaultLogBuilder()))) -#endif // ELPP_ASYNC_LOGGING -#define INITIALIZE_NULL_EASYLOGGINGPP \ -namespace el {\ -namespace base {\ -el::base::type::StoragePointer elStorage;\ -}\ -el::base::debug::CrashHandler elCrashHandler(ELPP_USE_DEF_CRASH_HANDLER);\ -} -#define SHARE_EASYLOGGINGPP(initializedStorage)\ -namespace el {\ -namespace base {\ -el::base::type::StoragePointer elStorage(initializedStorage);\ -}\ -el::base::debug::CrashHandler elCrashHandler(ELPP_USE_DEF_CRASH_HANDLER);\ -} - -#if defined(ELPP_UNICODE) -# define START_EASYLOGGINGPP(argc, argv) el::Helpers::setArgs(argc, argv); std::locale::global(std::locale("")) -#else -# define START_EASYLOGGINGPP(argc, argv) el::Helpers::setArgs(argc, argv) -#endif // defined(ELPP_UNICODE) -#endif // EASYLOGGINGPP_H diff --git a/XBOFS.win/easyloggingpp.conf b/XBOFS.win/easyloggingpp.conf deleted file mode 100644 index b2d01cb..0000000 --- a/XBOFS.win/easyloggingpp.conf +++ /dev/null @@ -1,11 +0,0 @@ -* GLOBAL: - FILENAME = "xbofs.win.log" - ENABLED = true - TO_FILE = true - TO_STANDARD_OUTPUT = false - SUBSECOND_PRECISION = 6 - PERFORMANCE_TRACKING = false - MAX_LOG_FILE_SIZE = 5242880 ## 5MB - Comment starts with two hashes (##) - LOG_FLUSH_THRESHOLD = 100 ## Flush after every 100 logs -* DEBUG: - FILENAME = "xbofs.win.debug.log" \ No newline at end of file diff --git a/XBOFS.win/pch.h b/XBOFS.win/pch.h index fb1b3da..d3216dd 100644 --- a/XBOFS.win/pch.h +++ b/XBOFS.win/pch.h @@ -8,9 +8,12 @@ #include #include #include +#include +#include -#include "easylogging++.h" #include "device.h" +#include "utils.h" + typedef std::basic_string tstring; diff --git a/XBOFS.win/utils.cpp b/XBOFS.win/utils.cpp index a8edcc3..b6eab2d 100644 --- a/XBOFS.win/utils.cpp +++ b/XBOFS.win/utils.cpp @@ -3,7 +3,7 @@ /* See https://stackoverflow.com/a/3999597/106057 */ -std::string utf8_encode(const std::wstring &wstr) +std::string XBOFSWin::utf8_encode(const std::wstring &wstr) { if (wstr.empty()) return std::string(); int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL); @@ -15,11 +15,30 @@ std::string utf8_encode(const std::wstring &wstr) /* See https://stackoverflow.com/a/3999597/106057 */ -std::wstring utf8_decode(const std::string &str) +std::wstring XBOFSWin::utf8_decode(const std::string &str) { if (str.empty()) return std::wstring(); int size_needed = MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), NULL, 0); std::wstring wstrTo(size_needed, 0); MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), &wstrTo[0], size_needed); return wstrTo; +} + +std::shared_ptr XBOFSWin::setup_logger(std::string loggerName, std::string logFile, std::vector sinks) +{ + auto logger = spdlog::get(loggerName); + if (!logger) + { + if (sinks.size() > 0) + { + logger = std::make_shared(loggerName, std::begin(sinks), std::end(sinks)); + spdlog::register_logger(logger); + } + else + { + logger = spdlog::basic_logger_mt(loggerName, logFile); + } + } + + return logger; } \ No newline at end of file diff --git a/XBOFS.win/utils.h b/XBOFS.win/utils.h index cb295f5..b65f4f5 100644 --- a/XBOFS.win/utils.h +++ b/XBOFS.win/utils.h @@ -1,5 +1,9 @@ #pragma once #include "pch.h" -std::string utf8_encode(const std::wstring &wstr); -std::wstring utf8_decode(const std::string &str); \ No newline at end of file +namespace XBOFSWin { + + std::string utf8_encode(const std::wstring &wstr); + std::wstring utf8_decode(const std::string &str); + std::shared_ptr setup_logger(std::string loggerName, std::string logFile, std::vector sinks); +} \ No newline at end of file From 9a3d7fc506b0f4ab5bcb7fbd178fea3c02112c82 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Thu, 22 Aug 2019 22:17:48 +0200 Subject: [PATCH 09/50] #2: Relocated XBOFS.win source and header files to clean up project folder --- XBOFS.win/XBOFS.win.vcxproj | 32 +++++++++---------- XBOFS.win/XBOFS.win.vcxproj.filters | 28 ++++++++-------- XBOFS.win/{ => include/XBOFS.win}/Thread.h | 2 +- .../{ => include/XBOFS.win}/WinUsbDevice.h | 5 ++- .../XBOFS.win}/WinUsbDeviceManager.h | 7 ++-- XBOFS.win/{ => include/XBOFS.win}/device.h | 0 XBOFS.win/{ => include/XBOFS.win}/pch.h | 0 XBOFS.win/{ => include/XBOFS.win}/utils.h | 2 +- XBOFS.win/{ => src}/Thread.cpp | 2 +- XBOFS.win/{ => src}/WinUsbDevice.cpp | 2 +- XBOFS.win/{ => src}/WinUsbDeviceManager.cpp | 14 ++++---- XBOFS.win/{ => src}/device.cpp | 2 +- XBOFS.win/{ => src}/utils.cpp | 2 +- 13 files changed, 48 insertions(+), 50 deletions(-) rename XBOFS.win/{ => include/XBOFS.win}/Thread.h (98%) rename XBOFS.win/{ => include/XBOFS.win}/WinUsbDevice.h (94%) rename XBOFS.win/{ => include/XBOFS.win}/WinUsbDeviceManager.h (80%) rename XBOFS.win/{ => include/XBOFS.win}/device.h (100%) rename XBOFS.win/{ => include/XBOFS.win}/pch.h (100%) rename XBOFS.win/{ => include/XBOFS.win}/utils.h (91%) rename XBOFS.win/{ => src}/Thread.cpp (98%) rename XBOFS.win/{ => src}/WinUsbDevice.cpp (99%) rename XBOFS.win/{ => src}/WinUsbDeviceManager.cpp (92%) rename XBOFS.win/{ => src}/device.cpp (99%) rename XBOFS.win/{ => src}/utils.cpp (97%) diff --git a/XBOFS.win/XBOFS.win.vcxproj b/XBOFS.win/XBOFS.win.vcxproj index 7a3aede..328e2a2 100644 --- a/XBOFS.win/XBOFS.win.vcxproj +++ b/XBOFS.win/XBOFS.win.vcxproj @@ -38,24 +38,24 @@ - - - - - + + {7db06674-1f4f-464b-8e1c-172e9587f9dc} + - - - - - - + + + + + + - - {7db06674-1f4f-464b-8e1c-172e9587f9dc} - + + + + + {F6104731-5815-4BBA-A558-E859DD039413} @@ -148,13 +148,13 @@ DbgengRemoteDebugger - $(SolutionDir)ViGEmClient\include;$(IncludePath) + $(SolutionDir)ViGEmClient\include;$(SolutionDir)XBOFS.win\include;$(IncludePath) $(SolutionDir)PDCurses\wincon;$(LibraryPath) $(SolutionDir)lib\debug\$(PlatformShortName)\ DbgengRemoteDebugger - $(SolutionDir)ViGEmClient\include;$(IncludePath) + $(SolutionDir)ViGEmClient\include;$(SolutionDir)XBOFS.win\include;$(IncludePath) $(SolutionDir)PDCurses\wincon;$(LibraryPath) $(SolutionDir)lib\release\$(PlatformShortName)\ diff --git a/XBOFS.win/XBOFS.win.vcxproj.filters b/XBOFS.win/XBOFS.win.vcxproj.filters index 4210e55..5a53305 100644 --- a/XBOFS.win/XBOFS.win.vcxproj.filters +++ b/XBOFS.win/XBOFS.win.vcxproj.filters @@ -19,43 +19,43 @@ - + + + + Header Files - + Header Files - + Header Files - + Header Files - + Header Files - + Header Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - - - \ No newline at end of file diff --git a/XBOFS.win/Thread.h b/XBOFS.win/include/XBOFS.win/Thread.h similarity index 98% rename from XBOFS.win/Thread.h rename to XBOFS.win/include/XBOFS.win/Thread.h index aec0b9a..05e5e33 100644 --- a/XBOFS.win/Thread.h +++ b/XBOFS.win/include/XBOFS.win/Thread.h @@ -1,5 +1,5 @@ #pragma once -#include "pch.h" +#include namespace XBOFSWin { diff --git a/XBOFS.win/WinUsbDevice.h b/XBOFS.win/include/XBOFS.win/WinUsbDevice.h similarity index 94% rename from XBOFS.win/WinUsbDevice.h rename to XBOFS.win/include/XBOFS.win/WinUsbDevice.h index d631241..38405e1 100644 --- a/XBOFS.win/WinUsbDevice.h +++ b/XBOFS.win/include/XBOFS.win/WinUsbDevice.h @@ -1,7 +1,6 @@ #pragma once -#include "pch.h" -#include "Thread.h" - +#include +#include #include namespace XBOFSWin { diff --git a/XBOFS.win/WinUsbDeviceManager.h b/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h similarity index 80% rename from XBOFS.win/WinUsbDeviceManager.h rename to XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h index 66af3aa..4b12060 100644 --- a/XBOFS.win/WinUsbDeviceManager.h +++ b/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h @@ -1,8 +1,7 @@ #pragma once -#include "pch.h" -#include "Thread.h" -#include "WinUsbDevice.h" - +#include +#include +#include #include /* diff --git a/XBOFS.win/device.h b/XBOFS.win/include/XBOFS.win/device.h similarity index 100% rename from XBOFS.win/device.h rename to XBOFS.win/include/XBOFS.win/device.h diff --git a/XBOFS.win/pch.h b/XBOFS.win/include/XBOFS.win/pch.h similarity index 100% rename from XBOFS.win/pch.h rename to XBOFS.win/include/XBOFS.win/pch.h diff --git a/XBOFS.win/utils.h b/XBOFS.win/include/XBOFS.win/utils.h similarity index 91% rename from XBOFS.win/utils.h rename to XBOFS.win/include/XBOFS.win/utils.h index b65f4f5..e88bce9 100644 --- a/XBOFS.win/utils.h +++ b/XBOFS.win/include/XBOFS.win/utils.h @@ -1,5 +1,5 @@ #pragma once -#include "pch.h" +#include namespace XBOFSWin { diff --git a/XBOFS.win/Thread.cpp b/XBOFS.win/src/Thread.cpp similarity index 98% rename from XBOFS.win/Thread.cpp rename to XBOFS.win/src/Thread.cpp index 3de3439..ce8fb99 100644 --- a/XBOFS.win/Thread.cpp +++ b/XBOFS.win/src/Thread.cpp @@ -1,4 +1,4 @@ -#include "Thread.h" +#include "XBOFS.win\Thread.h" using namespace XBOFSWin; diff --git a/XBOFS.win/WinUsbDevice.cpp b/XBOFS.win/src/WinUsbDevice.cpp similarity index 99% rename from XBOFS.win/WinUsbDevice.cpp rename to XBOFS.win/src/WinUsbDevice.cpp index 80c2e35..6049d5b 100644 --- a/XBOFS.win/WinUsbDevice.cpp +++ b/XBOFS.win/src/WinUsbDevice.cpp @@ -1,4 +1,4 @@ -#include "WinUsbDevice.h" +#include "XBOFS.win\WinUsbDevice.h" using namespace XBOFSWin; /* diff --git a/XBOFS.win/WinUsbDeviceManager.cpp b/XBOFS.win/src/WinUsbDeviceManager.cpp similarity index 92% rename from XBOFS.win/WinUsbDeviceManager.cpp rename to XBOFS.win/src/WinUsbDeviceManager.cpp index 63d0d7a..8fbc997 100644 --- a/XBOFS.win/WinUsbDeviceManager.cpp +++ b/XBOFS.win/src/WinUsbDeviceManager.cpp @@ -1,5 +1,5 @@ -#include "WinUsbDeviceManager.h" -#include "utils.h"; +#include "XBOFS.win\WinUsbDeviceManager.h" +#include "XBOFS.win\utils.h"; using namespace XBOFSWin; /* @@ -22,14 +22,14 @@ DWORD WinUsbDeviceManager::run() { // Check the updated set for new devicePaths for (auto devicePath : devicePaths) { if (devicePathWinUsbDeviceMap.find(devicePath) != devicePathWinUsbDeviceMap.end()) continue; - this->logger->info("Adding WinUsbDevice at {}", devicePath); + this->logger->info("Adding WinUsbDevice at {}", utf8_encode(devicePath)); #ifdef UNICODE auto identifier = utf8_encode(devicePath); #else auto identifier = devicePath; - #endif // UNICODE - auto logger = setup_logger("WinUsbDevice", "", this->logger->sinks()); - auto winUsbDevice = new WinUsbDevice(devicePath, identifier, logger, this->threadId, this->uiManagerThreadId); + #endif // UNICODE + auto winUsbDeviceLogger = setup_logger("WinUsbDevice", "", this->logger->sinks()); + auto winUsbDevice = new WinUsbDevice(devicePath, identifier, winUsbDeviceLogger, this->threadId, this->uiManagerThreadId); devicePathWinUsbDeviceMap.insert({ devicePath, winUsbDevice }); } // Check for WinUsbDevices to remove @@ -115,7 +115,7 @@ std::set WinUsbDeviceManager::retrieveDevicePaths() { newDevicePaths.insert(devicePath); deviceInterfaceListMarker += devicePathSize + 1; position += devicePathSize + 1; - this->logger->debug("Device interface path detected: {}", devicePath); + this->logger->debug("Device interface path detected: {}", utf8_encode(devicePath)); } deviceInterfaceListMarker = NULL; this->logger->debug("{} device interfaces detected", newDevicePaths.size()); diff --git a/XBOFS.win/device.cpp b/XBOFS.win/src/device.cpp similarity index 99% rename from XBOFS.win/device.cpp rename to XBOFS.win/src/device.cpp index 59e6a6d..7f91b18 100644 --- a/XBOFS.win/device.cpp +++ b/XBOFS.win/src/device.cpp @@ -1,4 +1,4 @@ -#include "pch.h" +#include "XBOFS.win\pch.h" #include diff --git a/XBOFS.win/utils.cpp b/XBOFS.win/src/utils.cpp similarity index 97% rename from XBOFS.win/utils.cpp rename to XBOFS.win/src/utils.cpp index b6eab2d..cc56ead 100644 --- a/XBOFS.win/utils.cpp +++ b/XBOFS.win/src/utils.cpp @@ -1,4 +1,4 @@ -#include "utils.h" +#include "XBOFS.win\utils.h" /* See https://stackoverflow.com/a/3999597/106057 From 81d7640975ee08538cbcf99aa3eb7b32aaee3f18 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Sat, 24 Aug 2019 16:27:18 +0200 Subject: [PATCH 10/50] #2: Added Qt generated files to .gitignore. Removed some generated files that were added to source control --- .gitignore | 53 ++++++++++++++- .../GeneratedFiles/ui_XBOFSWinQT5GUI.h | 66 ------------------- XBOFS.win.qt5/XBOFS.win.qt5.vcxproj | 20 +++--- XBOFS.win.qt5/XBOFS.win.qt5.vcxproj.filters | 8 --- XBOFS.win.qt5/XBOFSWinQT5GUI.cpp | 5 +- XBOFS.win.qt5/XBOFSWinQT5GUI.h | 7 +- 6 files changed, 71 insertions(+), 88 deletions(-) delete mode 100644 XBOFS.win.qt5/GeneratedFiles/ui_XBOFSWinQT5GUI.h diff --git a/.gitignore b/.gitignore index 3c4efe2..1314be0 100644 --- a/.gitignore +++ b/.gitignore @@ -258,4 +258,55 @@ paket-files/ # Python Tools for Visual Studio (PTVS) __pycache__/ -*.pyc \ No newline at end of file +*.pyc + +# Qt +# C++ objects and libs +*.slo +*.lo +*.o +*.a +*.la +*.lai +*.so +*.dll +*.dylib + +# Qt-es +object_script.*.Release +object_script.*.Debug +*_plugin_import.cpp +/.qmake.cache +/.qmake.stash +*.pro.user +*.pro.user.* +*.qbs.user +*.qbs.user.* +*.moc +moc_*.cpp +moc_*.h +qrc_*.cpp +ui_*.h +*.qmlc +*.jsc +Makefile* +*build-* + +# Qt unit tests +target_wrapper.* + +# QtCreator +*.autosave + +# QtCreator Qml +*.qmlproject.user +*.qmlproject.user.* + +# QtCreator CMake +CMakeLists.txt.user* + +# QtCreator 4.8< compilation database +compile_commands.json + +# QtCreator local machine specific files for imported projects +*creator.user* \ No newline at end of file diff --git a/XBOFS.win.qt5/GeneratedFiles/ui_XBOFSWinQT5GUI.h b/XBOFS.win.qt5/GeneratedFiles/ui_XBOFSWinQT5GUI.h deleted file mode 100644 index 2341c1a..0000000 --- a/XBOFS.win.qt5/GeneratedFiles/ui_XBOFSWinQT5GUI.h +++ /dev/null @@ -1,66 +0,0 @@ -/******************************************************************************** -** Form generated from reading UI file 'XBOFSWinQT5GUI.ui' -** -** Created by: Qt User Interface Compiler version 5.13.0 -** -** WARNING! All changes made in this file will be lost when recompiling UI file! -********************************************************************************/ - -#ifndef UI_XBOFSWINQT5GUI_H -#define UI_XBOFSWINQT5GUI_H - -#include -#include -#include -#include -#include -#include -#include - -QT_BEGIN_NAMESPACE - -class Ui_XBOFSWinQT5GUIClass -{ -public: - QMenuBar *menuBar; - QToolBar *mainToolBar; - QWidget *centralWidget; - QStatusBar *statusBar; - - void setupUi(QMainWindow *XBOFSWinQT5GUIClass) - { - if (XBOFSWinQT5GUIClass->objectName().isEmpty()) - XBOFSWinQT5GUIClass->setObjectName(QString::fromUtf8("XBOFSWinQT5GUIClass")); - XBOFSWinQT5GUIClass->resize(600, 400); - menuBar = new QMenuBar(XBOFSWinQT5GUIClass); - menuBar->setObjectName(QString::fromUtf8("menuBar")); - XBOFSWinQT5GUIClass->setMenuBar(menuBar); - mainToolBar = new QToolBar(XBOFSWinQT5GUIClass); - mainToolBar->setObjectName(QString::fromUtf8("mainToolBar")); - XBOFSWinQT5GUIClass->addToolBar(mainToolBar); - centralWidget = new QWidget(XBOFSWinQT5GUIClass); - centralWidget->setObjectName(QString::fromUtf8("centralWidget")); - XBOFSWinQT5GUIClass->setCentralWidget(centralWidget); - statusBar = new QStatusBar(XBOFSWinQT5GUIClass); - statusBar->setObjectName(QString::fromUtf8("statusBar")); - XBOFSWinQT5GUIClass->setStatusBar(statusBar); - - retranslateUi(XBOFSWinQT5GUIClass); - - QMetaObject::connectSlotsByName(XBOFSWinQT5GUIClass); - } // setupUi - - void retranslateUi(QMainWindow *XBOFSWinQT5GUIClass) - { - XBOFSWinQT5GUIClass->setWindowTitle(QCoreApplication::translate("XBOFSWinQT5GUIClass", "XBOFSWinQT5GUI", nullptr)); - } // retranslateUi - -}; - -namespace Ui { - class XBOFSWinQT5GUIClass: public Ui_XBOFSWinQT5GUIClass {}; -} // namespace Ui - -QT_END_NAMESPACE - -#endif // UI_XBOFSWINQT5GUI_H diff --git a/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj b/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj index 00a6280..734153a 100644 --- a/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj +++ b/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj @@ -30,12 +30,12 @@ $(SolutionDir)$(Platform)\$(Configuration)\ - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(SolutionDir)ViGEmClient\include;$(SolutionDir)XBOFS.win + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(SolutionDir)ViGEmClient\include;$(SolutionDir)XBOFS.win\include $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;$(SolutionDir)lib\release\x64 $(SolutionDir)$(Platform)\$(Configuration)\ - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(SolutionDir)ViGEmClient\include;$(SolutionDir)XBOFS.win + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(SolutionDir)ViGEmClient\include;$(SolutionDir)XBOFS.win\include $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;$(SolutionDir)lib\debug\x64 @@ -60,15 +60,17 @@ .\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtWidgets;%(AdditionalIncludeDirectories) Disabled ProgramDatabase - MultiThreadedDebugDLL + MultiThreadedDebug true + Cdecl Windows $(OutDir)\$(ProjectName).exe $(QTDIR)\lib;%(AdditionalLibraryDirectories) true - qtmaind.lib;Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;ViGEmClient.lib;XBOFS.win.lib;%(AdditionalDependencies) + qtmaind.lib;Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;ViGEmClient.lib;XBOFS.win.lib;onecoreuap.lib;winusb.lib;%(AdditionalDependencies) + msvcrt.lib;%(IgnoreSpecificDefaultLibraries) .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp @@ -91,15 +93,17 @@ UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DLL;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QT_GUI_LIB;QT_WIDGETS_LIB;%(PreprocessorDefinitions) .\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtWidgets;%(AdditionalIncludeDirectories) - MultiThreadedDLL + MultiThreaded true + false Windows $(OutDir)\$(ProjectName).exe $(QTDIR)\lib;%(AdditionalLibraryDirectories) false - qtmain.lib;Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;ViGEmClient.lib;XBOFS.win.lib;%(AdditionalDependencies) + qtmain.lib;Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;ViGEmClient.lib;XBOFS.win.lib;onecoreuap.lib;winusb.lib;%(AdditionalDependencies) + msvcrt.lib;%(IgnoreSpecificDefaultLibraries) .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp @@ -130,10 +134,6 @@ - - - - diff --git a/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj.filters b/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj.filters index c484225..91bc0ac 100644 --- a/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj.filters +++ b/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj.filters @@ -46,12 +46,4 @@ Resource Files - - - Header Files - - - Header Files - - \ No newline at end of file diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp index 71d0841..8707f4c 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp @@ -1,8 +1,11 @@ #include "XBOFSWinQT5GUI.h" +#include XBOFSWinQT5GUI::XBOFSWinQT5GUI(QWidget *parent) : QMainWindow(parent) { ui.setupUi(this); - + DWORD threadId = GetCurrentThreadId(); + this->logger = XBOFSWin::setup_logger("test", "test.log", std::vector()); + this->winUsbDeviceManager = new XBOFSWin::WinUsbDeviceManager(this->logger, threadId, threadId); } diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.h b/XBOFS.win.qt5/XBOFSWinQT5GUI.h index bcca989..9a680ac 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.h +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.h @@ -1,7 +1,8 @@ #pragma once -#include -#include +#include +#include +#include #include #include "ui_XBOFSWinQT5GUI.h" @@ -16,4 +17,6 @@ class XBOFSWinQT5GUI : public QMainWindow private: Ui::XBOFSWinQT5GUIClass ui; + XBOFSWin::WinUsbDeviceManager *winUsbDeviceManager; + std::shared_ptr logger; }; From bad39b0abf72354c5ae695301e8b8e662806c5b1 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Fri, 11 Oct 2019 22:49:00 +0200 Subject: [PATCH 11/50] #2: Relocated XBOFS.win project to XBOFS.win.old --- XBOFS.win.old/XBOFS.win.old.vcxproj | 259 ++++++++++++++++++ XBOFS.win.old/XBOFS.win.old.vcxproj.filters | 61 +++++ XBOFS.win.old/include/XBOFS.win/Thread.h | 50 ++++ .../include/XBOFS.win/WinUsbDevice.h | 37 +++ .../include/XBOFS.win/WinUsbDeviceManager.h | 25 ++ XBOFS.win.old/include/XBOFS.win/device.h | 68 +++++ XBOFS.win.old/include/XBOFS.win/pch.h | 19 ++ XBOFS.win.old/include/XBOFS.win/utils.h | 9 + XBOFS.win.old/src/Thread.cpp | 63 +++++ XBOFS.win.old/src/WinUsbDevice.cpp | 223 +++++++++++++++ XBOFS.win.old/src/WinUsbDeviceManager.cpp | 125 +++++++++ XBOFS.win.old/src/device.cpp | 235 ++++++++++++++++ XBOFS.win.old/src/utils.cpp | 44 +++ 13 files changed, 1218 insertions(+) create mode 100644 XBOFS.win.old/XBOFS.win.old.vcxproj create mode 100644 XBOFS.win.old/XBOFS.win.old.vcxproj.filters create mode 100644 XBOFS.win.old/include/XBOFS.win/Thread.h create mode 100644 XBOFS.win.old/include/XBOFS.win/WinUsbDevice.h create mode 100644 XBOFS.win.old/include/XBOFS.win/WinUsbDeviceManager.h create mode 100644 XBOFS.win.old/include/XBOFS.win/device.h create mode 100644 XBOFS.win.old/include/XBOFS.win/pch.h create mode 100644 XBOFS.win.old/include/XBOFS.win/utils.h create mode 100644 XBOFS.win.old/src/Thread.cpp create mode 100644 XBOFS.win.old/src/WinUsbDevice.cpp create mode 100644 XBOFS.win.old/src/WinUsbDeviceManager.cpp create mode 100644 XBOFS.win.old/src/device.cpp create mode 100644 XBOFS.win.old/src/utils.cpp diff --git a/XBOFS.win.old/XBOFS.win.old.vcxproj b/XBOFS.win.old/XBOFS.win.old.vcxproj new file mode 100644 index 0000000..9fe16da --- /dev/null +++ b/XBOFS.win.old/XBOFS.win.old.vcxproj @@ -0,0 +1,259 @@ + + + + + Debug_LIB + ARM + + + Debug_LIB + ARM64 + + + Debug_LIB + Win32 + + + Debug_LIB + x64 + + + Release_LIB + ARM + + + Release_LIB + ARM64 + + + Release_LIB + Win32 + + + Release_LIB + x64 + + + + + + + + {7db06674-1f4f-464b-8e1c-172e9587f9dc} + + + + + + + + + + + + + + + + + + + {F6104731-5815-4BBA-A558-E859DD039413} + {b287f7f7-abf3-440d-b608-cb36380f2981} + v4.5 + 12.0 + Debug + Win32 + XBOFS.win + $(LatestTargetPlatformVersion) + XBOFS.win.old + + + + Windows10 + true + WindowsApplicationForDrivers10.0 + Application + Universal + Unicode + + + Windows10 + false + WindowsApplicationForDrivers10.0 + Application + Universal + Unicode + + + Windows10 + true + WindowsApplicationForDrivers10.0 + StaticLibrary + Desktop + Unicode + + + Windows10 + false + WindowsApplicationForDrivers10.0 + StaticLibrary + Desktop + Unicode + + + Windows10 + true + WindowsApplicationForDrivers10.0 + Application + Universal + Unicode + + + Windows10 + false + WindowsApplicationForDrivers10.0 + Application + Universal + Unicode + + + Windows10 + true + WindowsApplicationForDrivers10.0 + Application + Universal + Unicode + + + Windows10 + false + WindowsApplicationForDrivers10.0 + Application + Universal + Unicode + + + + + + + + + + + DbgengRemoteDebugger + + + DbgengRemoteDebugger + + + DbgengRemoteDebugger + $(SolutionDir)ViGEmClient\include;$(SolutionDir)XBOFS.win\include;$(IncludePath) + $(SolutionDir)PDCurses\wincon;$(LibraryPath) + $(SolutionDir)lib\debug\$(PlatformShortName)\ + + + DbgengRemoteDebugger + $(SolutionDir)ViGEmClient\include;$(SolutionDir)XBOFS.win\include;$(IncludePath) + $(SolutionDir)PDCurses\wincon;$(LibraryPath) + $(SolutionDir)lib\release\$(PlatformShortName)\ + + + DbgengRemoteDebugger + + + DbgengRemoteDebugger + + + DbgengRemoteDebugger + + + DbgengRemoteDebugger + + + + _DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + + + %(AdditionalDependencies);onecoreuap.lib;winusb.lib + + + + + WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) + + + %(AdditionalDependencies);onecoreuap.lib;winusb.lib + + + + + _DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) + MultiThreadedDebug + false + + + %(AdditionalDependencies);onecoreuap.lib;winusb.lib + false + + + cd $(SolutionDir) & call pre-build.cmd + Pre-build tasks + + + + + WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;BUILD_SHARED_LIBS;%(PreprocessorDefinitions) + false + stdcpp17 + MultiThreaded + + + %(AdditionalDependencies);onecoreuap.lib;winusb.lib + false + + + cd $(SolutionDir) & call pre-build.cmd + Pre-build tasks + + + + + _DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + + + %(AdditionalDependencies);onecoreuap.lib;winusb.lib + + + + + WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) + + + %(AdditionalDependencies);onecoreuap.lib;winusb.lib + + + + + _DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + + + %(AdditionalDependencies);onecoreuap.lib;winusb.lib + + + + + WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) + + + %(AdditionalDependencies);onecoreuap.lib;winusb.lib + + + + + + \ No newline at end of file diff --git a/XBOFS.win.old/XBOFS.win.old.vcxproj.filters b/XBOFS.win.old/XBOFS.win.old.vcxproj.filters new file mode 100644 index 0000000..5a53305 --- /dev/null +++ b/XBOFS.win.old/XBOFS.win.old.vcxproj.filters @@ -0,0 +1,61 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {8E41214B-6785-4CFE-B992-037D68949A14} + inf;inv;inx;mof;mc; + + + + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/XBOFS.win.old/include/XBOFS.win/Thread.h b/XBOFS.win.old/include/XBOFS.win/Thread.h new file mode 100644 index 0000000..05e5e33 --- /dev/null +++ b/XBOFS.win.old/include/XBOFS.win/Thread.h @@ -0,0 +1,50 @@ +#pragma once +#include + +namespace XBOFSWin { + + enum THREAD_MESSAGES { + RAWUVEF_STOP = WM_USER + 0, + RAWUVEF_STOPPED = WM_USER + 1, + RAWUVEF_WIN_USB_DEVICE_MANAGER_STARTED = WM_USER + 2, + RAWUVEF_WIN_USB_DEVICE_MANAGER_SCANNING = WM_USER + 3, + RAWUVEF_WIN_USB_DEVICE_MANAGER_SLEEPING = WM_USER + 4, + RAWUVEF_WIN_USB_DEVICE_MANAGER_TERMINATING = WM_USER + 5, + RAWUVEF_WIN_USB_DEVICE_MANAGER_ERROR = WM_USER + 6, + RAWUVEF_WIN_USB_DEVICE_STARTED = WM_USER + 7, + RAWUVEF_WIN_USB_DEVICE_VIGEM_CONNECT = WM_USER + 8, + RAWUVEF_WIN_USB_DEVICE_VIGEM_TARGET_ADD = WM_USER + 9, + RAWUVEF_WIN_USB_DEVICE_OPEN = WM_USER + 10, + RAWUVEF_WIN_USB_DEVICE_INIT = WM_USER + 11, + RAWUVEF_WIN_USB_DEVICE_READ_INPUT = WM_USER + 12, + RAWUVEF_WIN_USB_DEVICE_TERMINATING = WM_USER + 13, + RAWUVEF_WIN_USB_DEVICE_ERROR = WM_USER + 14 + }; + + std::string threadMessageToString(THREAD_MESSAGES threadMessage); + + class Thread + { + public: + Thread() = delete; + Thread(std::string identifier, std::shared_ptr logger, DWORD parentThreadId, DWORD uiManagerThreadId); + ~Thread(); + + virtual DWORD run() = 0; + DWORD getThreadId(); + DWORD getParentThreadId(); + DWORD getUiManagerThreadId(); + + protected: + const std::string identifier; + const DWORD parentThreadId; + const DWORD uiManagerThreadId; + + std::shared_ptr logger; + DWORD threadId = 0; + HANDLE threadHandle = NULL; + + static DWORD startThread(LPVOID data); + BOOL notifyUIManager(UINT messageValue, LPARAM lParam); + }; +} diff --git a/XBOFS.win.old/include/XBOFS.win/WinUsbDevice.h b/XBOFS.win.old/include/XBOFS.win/WinUsbDevice.h new file mode 100644 index 0000000..38405e1 --- /dev/null +++ b/XBOFS.win.old/include/XBOFS.win/WinUsbDevice.h @@ -0,0 +1,37 @@ +#pragma once +#include +#include +#include + +namespace XBOFSWin { + /* + */ + class WinUsbDevice : public Thread + { + public: + WinUsbDevice(tstring devicePath, std::string identifier, std::shared_ptr logger, DWORD parentThreadId, DWORD uiManagerThreadId); + ~WinUsbDevice() {}; + + DWORD run(void); + + protected: + const tstring devicePath; + + bool deviceHandlesOpen = false; + UCHAR RAZER_ATROX_INIT[5] = { 0x05, 0x20, 0x08, 0x01, 0x05 }; + RAZER_ATROX_DATA_PACKET dataPacket = {}; + RAZER_ATROX_BUTTON_STATE buttonState = {}; + PVIGEM_CLIENT vigEmClient = NULL; + PVIGEM_TARGET vigEmTarget = NULL; + WINUSB_INTERFACE_HANDLE winUsbHandle; + HANDLE deviceHandle; + + bool openDevice(); + bool closeDevice(); + bool initRazorAtrox(); + bool readInputFromRazerAtrox(); + RAZER_ATROX_PACKET_TYPES processInputFromRazerAtrox(); + bool dispatchInputToVigEmController(); + }; +} + diff --git a/XBOFS.win.old/include/XBOFS.win/WinUsbDeviceManager.h b/XBOFS.win.old/include/XBOFS.win/WinUsbDeviceManager.h new file mode 100644 index 0000000..4b12060 --- /dev/null +++ b/XBOFS.win.old/include/XBOFS.win/WinUsbDeviceManager.h @@ -0,0 +1,25 @@ +#pragma once +#include +#include +#include +#include + +/* + +*/ +namespace XBOFSWin { + + class WinUsbDeviceManager : public Thread + { + public: + WinUsbDeviceManager(std::shared_ptr logger, DWORD parentThreadId, DWORD uiManagerThreadId); + ~WinUsbDeviceManager() {}; + + DWORD run(); + + protected: + std::set retrieveDevicePaths(); + + }; +} + diff --git a/XBOFS.win.old/include/XBOFS.win/device.h b/XBOFS.win.old/include/XBOFS.win/device.h new file mode 100644 index 0000000..508de82 --- /dev/null +++ b/XBOFS.win.old/include/XBOFS.win/device.h @@ -0,0 +1,68 @@ +#pragma once +// +// Define below GUIDs +// +#include + +// +// Device Interface GUID. +// Used by all WinUsb devices that this application talks to. +// Must match "DeviceInterfaceGUIDs" registry value specified in the INF file. +// +// 5ACF052A-3BE5-46AE-905E-356BA17671BD +DEFINE_GUID(GUID_DEVINTERFACE_XBOFS_WIN_CONTROLLER, + 0x5ACF052A, 0x3BE5, 0x46AE, 0x90, 0x5E, 0x35, 0x6B, 0xA1, 0x76, 0x71, 0xBD); + +typedef struct _DEVICE_DATA { + + BOOL HandlesOpen; + WINUSB_INTERFACE_HANDLE WinusbHandle; + HANDLE DeviceHandle; + TCHAR DevicePath[MAX_PATH]; + +} DEVICE_DATA, *PDEVICE_DATA; + +HRESULT +OpenDevice( + _Out_ PDEVICE_DATA DeviceData, + _Out_opt_ PBOOL FailureDeviceNotFound + ); + +VOID +CloseDevice( + _Inout_ PDEVICE_DATA DeviceData + ); + +struct RAZER_ATROX_DATA_PACKET +{ + UCHAR data[30]; + ULONG transferred; +}; + +struct RAZER_ATROX_BUTTON_STATE +{ + BOOL buttonX; + BOOL buttonY; + BOOL buttonA; + BOOL buttonB; + BOOL rightButton; + BOOL leftButton; + BOOL rightTrigger; + BOOL leftTrigger; + BOOL buttonMenu; + BOOL buttonView; + BOOL buttonGuide; + BOOL stickUp; + BOOL stickLeft; + BOOL stickDown; + BOOL stickRight; +}; + +enum RAZER_ATROX_PACKET_TYPES +{ + UNKNOWN, + DUMMY, + HEARTBEAT, + GUIDE, + BUTTON_INPUT +}; diff --git a/XBOFS.win.old/include/XBOFS.win/pch.h b/XBOFS.win.old/include/XBOFS.win/pch.h new file mode 100644 index 0000000..d3216dd --- /dev/null +++ b/XBOFS.win.old/include/XBOFS.win/pch.h @@ -0,0 +1,19 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "device.h" +#include "utils.h" + + +typedef std::basic_string tstring; + diff --git a/XBOFS.win.old/include/XBOFS.win/utils.h b/XBOFS.win.old/include/XBOFS.win/utils.h new file mode 100644 index 0000000..e88bce9 --- /dev/null +++ b/XBOFS.win.old/include/XBOFS.win/utils.h @@ -0,0 +1,9 @@ +#pragma once +#include + +namespace XBOFSWin { + + std::string utf8_encode(const std::wstring &wstr); + std::wstring utf8_decode(const std::string &str); + std::shared_ptr setup_logger(std::string loggerName, std::string logFile, std::vector sinks); +} \ No newline at end of file diff --git a/XBOFS.win.old/src/Thread.cpp b/XBOFS.win.old/src/Thread.cpp new file mode 100644 index 0000000..ce8fb99 --- /dev/null +++ b/XBOFS.win.old/src/Thread.cpp @@ -0,0 +1,63 @@ +#include "XBOFS.win\Thread.h" + +using namespace XBOFSWin; + +std::string threadMessageToString(THREAD_MESSAGES threadMessage) +{ + switch (threadMessage) { + case RAWUVEF_WIN_USB_DEVICE_MANAGER_STARTED: return "Started"; + case RAWUVEF_WIN_USB_DEVICE_MANAGER_SCANNING: + case RAWUVEF_WIN_USB_DEVICE_MANAGER_SLEEPING: return "Active"; + case RAWUVEF_WIN_USB_DEVICE_MANAGER_TERMINATING: return "Terminating..."; + case RAWUVEF_WIN_USB_DEVICE_MANAGER_ERROR: return "Error!"; + case RAWUVEF_WIN_USB_DEVICE_STARTED: return "Started"; + case RAWUVEF_WIN_USB_DEVICE_VIGEM_CONNECT: return "VigEmClient connect..."; + case RAWUVEF_WIN_USB_DEVICE_VIGEM_TARGET_ADD: return "VigEmClient target add..."; + case RAWUVEF_WIN_USB_DEVICE_OPEN: return "Device Open..."; + case RAWUVEF_WIN_USB_DEVICE_INIT: return "Device Init..."; + case RAWUVEF_WIN_USB_DEVICE_READ_INPUT: return "Reading input..."; + case RAWUVEF_WIN_USB_DEVICE_TERMINATING: return "Terminating..."; + case RAWUVEF_WIN_USB_DEVICE_ERROR: return "Error!"; + } + return "Unknown Thread Message"; +} + +Thread::Thread(std::string identifier, std::shared_ptr logger, DWORD parentThreadId, DWORD uiManagerThreadId) +: identifier(identifier), logger(logger), parentThreadId(parentThreadId), uiManagerThreadId(uiManagerThreadId) +{ + this->logger->info("Starting thread for {}", this->identifier); + this->threadHandle = CreateThread(NULL, 0, startThread, (LPVOID)this, 0, &this->threadId); +} + +Thread::~Thread() +{ + this->logger->info("Stopping thread for {}", this->identifier); + PostThreadMessage(this->threadId, RAWUVEF_STOP, NULL, NULL); + while (WaitForSingleObject(this->threadHandle, INFINITE) != WAIT_OBJECT_0); + CloseHandle(this->threadHandle); + this->notifyUIManager(RAWUVEF_STOPPED, NULL); + this->logger->info("Stopped thread for {}", this->identifier); +} + +DWORD Thread::getThreadId() { + return this->threadId; +} + +DWORD Thread::getParentThreadId() { + return this->parentThreadId; +} + +DWORD Thread::getUiManagerThreadId() { + return this->uiManagerThreadId; +} + +DWORD Thread::startThread(LPVOID data) { + Thread* thread = (Thread*)data; + MSG threadMessage; + PeekMessage(&threadMessage, NULL, WM_USER, WM_USER, PM_NOREMOVE); + return thread->run(); +} + +BOOL Thread::notifyUIManager(UINT messageValue, LPARAM lParam) { + return PostThreadMessage(this->uiManagerThreadId, messageValue, this->threadId, lParam); +} diff --git a/XBOFS.win.old/src/WinUsbDevice.cpp b/XBOFS.win.old/src/WinUsbDevice.cpp new file mode 100644 index 0000000..6049d5b --- /dev/null +++ b/XBOFS.win.old/src/WinUsbDevice.cpp @@ -0,0 +1,223 @@ +#include "XBOFS.win\WinUsbDevice.h" + +using namespace XBOFSWin; +/* +Constructs the WinUsbDevice instance and starts its event loop in a separate thread +*/ +WinUsbDevice::WinUsbDevice(tstring devicePath, std::string identifier, std::shared_ptr logger, DWORD parentThreadId, DWORD uiManagerThreadId) +: Thread(identifier, logger, parentThreadId, uiManagerThreadId), devicePath(devicePath) +{ + +} + +DWORD WinUsbDevice::run() { + bool loop = true; + int failedReads = 0; + int failedWrites = 0; + MSG threadMessage; + this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_STARTED, NULL); + this->logger->info("Started thread for {}", this->identifier); + this->logger->info("Allocating VigEmClient for {}", this->identifier); + this->vigEmClient = vigem_alloc(); + this->logger->info("Allocating VigEmTarget for {}", this->identifier); + this->vigEmTarget = vigem_target_x360_alloc(); + this->logger->info("Connecting VigEmClient for {}", this->identifier); + this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_VIGEM_CONNECT, NULL); + if (!VIGEM_SUCCESS(vigem_connect(this->vigEmClient))) { + this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_ERROR, NULL); + this->logger->error("Unable to connect VigEmClient for {}", this->identifier); + loop = false; + } + this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_VIGEM_TARGET_ADD, NULL); + this->logger->info("Adding VigEmTarget for {}", this->identifier); + if (!VIGEM_SUCCESS(vigem_target_add(this->vigEmClient, this->vigEmTarget))) { + this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_ERROR, NULL); + this->logger->error("Unable to add VigEmTarget for {}", this->identifier); + loop = false; + } + // Loop reading input, processing it and dispatching it + this->logger->info("Starting Read-Process-Dispatch loop for {}", this->identifier); + while (loop) { + if (PeekMessage(&threadMessage, NULL, WM_USER, WM_APP, PM_REMOVE) == TRUE && threadMessage.message == RAWUVEF_STOP) loop = false; + this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_OPEN, NULL); + if (!this->openDevice()) { + this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_ERROR, NULL); + this->logger->error("Unable to open WinUSB device for {}", this->identifier); + continue; + } + this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_INIT, NULL); + if (!this->initRazorAtrox()) { + this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_ERROR, NULL); + this->logger->error("Unable to init Razer Atrox for {}", this->identifier); + continue; + } + this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_READ_INPUT, NULL); + this->logger->info("Reading input from Razer Atrox for {}", this->identifier); + int currentFailedReads = 0; + while (loop && currentFailedReads < 5) { + if (PeekMessage(&threadMessage, NULL, WM_USER, WM_APP, PM_REMOVE) == TRUE && threadMessage.message == RAWUVEF_STOP) loop = false; + if (!this->readInputFromRazerAtrox()) { + this->logger->warn("Failed to read input from Razer Atrox for {}", this->identifier); + currentFailedReads += 1; + continue; + } + this->processInputFromRazerAtrox(); + if (!this->dispatchInputToVigEmController()) failedWrites += 1; + } + if (currentFailedReads >= 5) { + this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_ERROR, NULL); + this->logger->warn("Failed to read input from Razer Atrox 5 or more times for {}", this->identifier); + } + failedReads += currentFailedReads; + currentFailedReads = 0; + } + this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_TERMINATING, NULL); + this->logger->info("Completed Read-Process-Dispatch loop for {}", this->identifier); + this->logger->info("There were {} failed reads for {}", failedReads, this->identifier); + this->logger->info("There were {} failed writes for {}", failedWrites, this->identifier); + this->logger->info("Closing WinUSB device for {}", this->identifier); + this->closeDevice(); + this->logger->info("Removing VigEmTarget for {}", this->identifier); + vigem_target_remove(this->vigEmClient, this->vigEmTarget); + this->logger->info("Disconnecting VigEmClient for {}", this->identifier); + vigem_disconnect(vigEmClient); + this->logger->info("Free VigEmTarget for {}", this->identifier); + vigem_target_free(this->vigEmTarget); + this->logger->info("Free VigEmClient for {}", this->identifier); + vigem_free(this->vigEmClient); + this->logger->info("Completed thread for {}", this->identifier); + return 0; +} + +/* +Attempt to open and initialize the WinUsb device +*/ +bool WinUsbDevice::openDevice() { + HRESULT hr = S_OK; + BOOL bResult; + this->deviceHandlesOpen = false; + this->logger->info("Opening WinUSB device for {}", this->identifier); + // Attempt to open device handle + this->deviceHandle = CreateFile(this->devicePath.c_str(), + GENERIC_WRITE | GENERIC_READ, + FILE_SHARE_WRITE | FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, + NULL); + if (INVALID_HANDLE_VALUE == this->deviceHandle) { + hr = HRESULT_FROM_WIN32(GetLastError()); + this->logger->error("Failed to open device handle for {} due to {}", this->identifier, hr); + return false; + } + // Initialize WinUsb handle + bResult = WinUsb_Initialize(this->deviceHandle, &this->winUsbHandle); + if (FALSE == bResult) { + hr = HRESULT_FROM_WIN32(GetLastError()); + CloseHandle(this->deviceHandle); + this->logger->error("Failed to initiallize WinUSB handle for {} due to {}", this->identifier, hr); + return false; + } + this->deviceHandlesOpen = true; + // Make GetDescriptor call to device in order to ensure communications with it work + BOOL winUsbGetDescriptorResult; + USB_DEVICE_DESCRIPTOR winUsbDeviceDescriptor; + ULONG bytesReceived; + winUsbGetDescriptorResult = WinUsb_GetDescriptor( + this->winUsbHandle, USB_DEVICE_DESCRIPTOR_TYPE, 0, 0, (PBYTE)&winUsbDeviceDescriptor, sizeof(winUsbDeviceDescriptor), &bytesReceived + ); + if (winUsbGetDescriptorResult == FALSE || bytesReceived != sizeof(winUsbDeviceDescriptor)) { + this->logger->error("Failed to read USB descriptor for {}", this->identifier); + this->closeDevice(); + return false; + } + this->logger->info("Opened WinUSB device for {}", this->identifier); + return true; +} + +/* +Attempt to close the WinUsb device +*/ +bool WinUsbDevice::closeDevice() { + if (!this->deviceHandlesOpen) return false; + WinUsb_Free(this->winUsbHandle); + CloseHandle(this->deviceHandle); + this->deviceHandlesOpen = false; + return true; +} + +/* +Process data read from Razer Atrox +*/ +RAZER_ATROX_PACKET_TYPES WinUsbDevice::processInputFromRazerAtrox() { + if (this->dataPacket.transferred == 0) return UNKNOWN; + switch (this->dataPacket.data[0]) { + case 0x01: // Dummy packet? + return DUMMY; + case 0x03: // Heartbeat packet? + return HEARTBEAT; + case 0x07: // Guide button + buttonState.buttonGuide = dataPacket.data[4] & 0x01; + return GUIDE; + case 0x20: // Inputs + buttonState.buttonA = dataPacket.data[22] & 0x10; + buttonState.buttonB = dataPacket.data[22] & 0x20; + buttonState.buttonX = dataPacket.data[22] & 0x01; + buttonState.buttonY = dataPacket.data[22] & 0x02; + buttonState.rightButton = dataPacket.data[22] & 0x04; + buttonState.leftButton = dataPacket.data[22] & 0x08; + buttonState.rightTrigger = dataPacket.data[22] & 0x40; + buttonState.leftTrigger = dataPacket.data[22] & 0x80; + buttonState.buttonMenu = dataPacket.data[04] & 0x04; + buttonState.buttonView = dataPacket.data[04] & 0x08; + buttonState.stickUp = dataPacket.data[05] & 0x01; + buttonState.stickDown = dataPacket.data[05] & 0x02; + buttonState.stickLeft = dataPacket.data[05] & 0x04; + buttonState.stickRight = dataPacket.data[05] & 0x08; + return BUTTON_INPUT; + } + return UNKNOWN; +} + +/* +Blocking data read from end-point 0x81 +*/ +bool WinUsbDevice::readInputFromRazerAtrox() +{ + if (this->winUsbHandle == INVALID_HANDLE_VALUE) return false; + return WinUsb_ReadPipe(this->winUsbHandle, 0x81, this->dataPacket.data, 30, &this->dataPacket.transferred, NULL); +} + +/* +Sent the INIT packet to the Razor Atrox +*/ +bool WinUsbDevice::initRazorAtrox() { + this->logger->info("Init Razer Atrox"); + if (this->winUsbHandle == INVALID_HANDLE_VALUE) return false; + ULONG cbSent = 0; + return WinUsb_WritePipe(this->winUsbHandle, 0x01, (PUCHAR)this->RAZER_ATROX_INIT, 5, &cbSent, 0); +} + +/* +Dispatch data to the VigEm XB360 controller +*/ +bool WinUsbDevice::dispatchInputToVigEmController() { + XUSB_REPORT controllerData{}; + if (this->buttonState.buttonGuide) controllerData.wButtons |= XUSB_GAMEPAD_GUIDE; + if (this->buttonState.buttonMenu) controllerData.wButtons |= XUSB_GAMEPAD_START; + if (this->buttonState.buttonView) controllerData.wButtons |= XUSB_GAMEPAD_BACK; + if (this->buttonState.buttonA) controllerData.wButtons |= XUSB_GAMEPAD_A; + if (this->buttonState.buttonB) controllerData.wButtons |= XUSB_GAMEPAD_B; + if (this->buttonState.buttonX) controllerData.wButtons |= XUSB_GAMEPAD_X; + if (this->buttonState.buttonY) controllerData.wButtons |= XUSB_GAMEPAD_Y; + if (this->buttonState.leftButton) controllerData.wButtons |= XUSB_GAMEPAD_LEFT_SHOULDER; + if (this->buttonState.rightButton) controllerData.wButtons |= XUSB_GAMEPAD_RIGHT_SHOULDER; + if (this->buttonState.stickUp) controllerData.wButtons |= XUSB_GAMEPAD_DPAD_UP; + if (this->buttonState.stickDown) controllerData.wButtons |= XUSB_GAMEPAD_DPAD_DOWN; + if (this->buttonState.stickLeft) controllerData.wButtons |= XUSB_GAMEPAD_DPAD_LEFT; + if (this->buttonState.stickRight) controllerData.wButtons |= XUSB_GAMEPAD_DPAD_RIGHT; + if (this->buttonState.leftTrigger) controllerData.bLeftTrigger = 0xff; + if (this->buttonState.rightTrigger) controllerData.bRightTrigger = 0xff; + const auto controllerUpdateResult = vigem_target_x360_update(this->vigEmClient, this->vigEmTarget, controllerData); + return VIGEM_SUCCESS(controllerUpdateResult); +} diff --git a/XBOFS.win.old/src/WinUsbDeviceManager.cpp b/XBOFS.win.old/src/WinUsbDeviceManager.cpp new file mode 100644 index 0000000..8fbc997 --- /dev/null +++ b/XBOFS.win.old/src/WinUsbDeviceManager.cpp @@ -0,0 +1,125 @@ +#include "XBOFS.win\WinUsbDeviceManager.h" +#include "XBOFS.win\utils.h"; + +using namespace XBOFSWin; +/* +Constructs the WinUsbDeviceManager and starts its event loop in a separate thread +*/ +WinUsbDeviceManager::WinUsbDeviceManager(std::shared_ptr logger, DWORD parentThreadId, DWORD uiManagerThreadId) +: Thread("WinUsbDeviceManager", logger, parentThreadId, uiManagerThreadId) +{} + +DWORD WinUsbDeviceManager::run() { + this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_MANAGER_STARTED, NULL); + this->logger->info("Started thread for {}", this->identifier); + MSG threadMessage; + bool loop = true; + std::unordered_map devicePathWinUsbDeviceMap; + this->logger->info("Starting scan loop for {}", this->identifier); + while (loop) { + this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_MANAGER_SCANNING, NULL); + auto devicePaths = this->retrieveDevicePaths(); + // Check the updated set for new devicePaths + for (auto devicePath : devicePaths) { + if (devicePathWinUsbDeviceMap.find(devicePath) != devicePathWinUsbDeviceMap.end()) continue; + this->logger->info("Adding WinUsbDevice at {}", utf8_encode(devicePath)); + #ifdef UNICODE + auto identifier = utf8_encode(devicePath); + #else + auto identifier = devicePath; + #endif // UNICODE + auto winUsbDeviceLogger = setup_logger("WinUsbDevice", "", this->logger->sinks()); + auto winUsbDevice = new WinUsbDevice(devicePath, identifier, winUsbDeviceLogger, this->threadId, this->uiManagerThreadId); + devicePathWinUsbDeviceMap.insert({ devicePath, winUsbDevice }); + } + // Check for WinUsbDevices to remove + for (auto tuple : devicePathWinUsbDeviceMap) { + if (devicePaths.find(tuple.first) != devicePaths.end()) continue; + delete tuple.second; + devicePathWinUsbDeviceMap.erase(tuple.first); + } + // TODO: Processes messages in thread message queue + if (PeekMessage(&threadMessage, NULL, WM_USER, WM_APP, PM_REMOVE) == TRUE && threadMessage.message == RAWUVEF_STOP) loop = false; + else { + this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_MANAGER_SLEEPING, NULL); + Sleep(1000); + } + } + this->logger->info("Completed scan loop for {}", this->identifier); + this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_MANAGER_TERMINATING, NULL); + for (auto tuple : devicePathWinUsbDeviceMap) delete tuple.second; + return 0; +} + + +/* +Retrieve a vector of TCHAR* representing device paths that the device manager will work with +*/ +std::set WinUsbDeviceManager::retrieveDevicePaths() { + CONFIGRET configurationManagerResult = CR_SUCCESS; + HRESULT resultHandle = S_OK; + PTSTR deviceInterfaceList = NULL; + ULONG deviceInterfaceListSize = 0; + std::set newDevicePaths; + // + // Enumerate all devices exposing the interface. Do this in a loop + // in case a new interface is discovered while this code is executing, + // causing CM_Get_Device_Interface_List to return CR_BUFFER_SMALL. + // + this->logger->debug("Retrieving device interface paths"); + do { + configurationManagerResult = CM_Get_Device_Interface_List_Size(&deviceInterfaceListSize, + (LPGUID)&GUID_DEVINTERFACE_XBOFS_WIN_CONTROLLER, + NULL, + CM_GET_DEVICE_INTERFACE_LIST_PRESENT); + + if (configurationManagerResult != CR_SUCCESS) { + resultHandle = HRESULT_FROM_WIN32(CM_MapCrToWin32Err(configurationManagerResult, ERROR_INVALID_DATA)); + break; + } + + this->logger->debug("Device interface list size in bytes: {}", deviceInterfaceListSize * sizeof(TCHAR)); + + deviceInterfaceList = (PTSTR)HeapAlloc(GetProcessHeap(), + HEAP_ZERO_MEMORY, + deviceInterfaceListSize * sizeof(TCHAR)); + + if (deviceInterfaceList == NULL) { + resultHandle = E_OUTOFMEMORY; + break; + } + + configurationManagerResult = CM_Get_Device_Interface_List((LPGUID)&GUID_DEVINTERFACE_XBOFS_WIN_CONTROLLER, + NULL, + deviceInterfaceList, + deviceInterfaceListSize, + CM_GET_DEVICE_INTERFACE_LIST_PRESENT); + + if (configurationManagerResult != CR_SUCCESS) { + if (configurationManagerResult != CR_BUFFER_SMALL) { + resultHandle = HRESULT_FROM_WIN32(CM_MapCrToWin32Err(configurationManagerResult, ERROR_INVALID_DATA)); + } + } + } while (configurationManagerResult == CR_BUFFER_SMALL); + // Handle errors + if (resultHandle != S_OK || deviceInterfaceList == TEXT('\0')) { + // TODO: Log error + } + else { + auto deviceInterfaceListMarker = deviceInterfaceList; + auto position = 0; + while (position < deviceInterfaceListSize) { + auto devicePath = tstring(deviceInterfaceListMarker); + auto devicePathSize = devicePath.size(); + if (!devicePathSize) break; + newDevicePaths.insert(devicePath); + deviceInterfaceListMarker += devicePathSize + 1; + position += devicePathSize + 1; + this->logger->debug("Device interface path detected: {}", utf8_encode(devicePath)); + } + deviceInterfaceListMarker = NULL; + this->logger->debug("{} device interfaces detected", newDevicePaths.size()); + } + HeapFree(GetProcessHeap(), 0, deviceInterfaceList); + return newDevicePaths; +} \ No newline at end of file diff --git a/XBOFS.win.old/src/device.cpp b/XBOFS.win.old/src/device.cpp new file mode 100644 index 0000000..7f91b18 --- /dev/null +++ b/XBOFS.win.old/src/device.cpp @@ -0,0 +1,235 @@ +#include "XBOFS.win\pch.h" + +#include + +HRESULT +RetrieveDevicePath( + _Out_bytecap_(BufLen) LPTSTR DevicePath, + _In_ ULONG BufLen, + _Out_opt_ PBOOL FailureDeviceNotFound + ); + +HRESULT +OpenDevice( + _Out_ PDEVICE_DATA DeviceData, + _Out_opt_ PBOOL FailureDeviceNotFound + ) +/*++ + +Routine description: + + Open all needed handles to interact with the device. + + If the device has multiple USB interfaces, this function grants access to + only the first interface. + + If multiple devices have the same device interface GUID, there is no + guarantee of which one will be returned. + +Arguments: + + DeviceData - Struct filled in by this function. The caller should use the + WinusbHandle to interact with the device, and must pass the struct to + CloseDevice when finished. + + FailureDeviceNotFound - TRUE when failure is returned due to no devices + found with the correct device interface (device not connected, driver + not installed, or device is disabled in Device Manager); FALSE + otherwise. + +Return value: + + HRESULT + +--*/ +{ + HRESULT hr = S_OK; + BOOL bResult; + + DeviceData->HandlesOpen = FALSE; + + hr = RetrieveDevicePath(DeviceData->DevicePath, + sizeof(DeviceData->DevicePath), + FailureDeviceNotFound); + + if (FAILED(hr)) { + + return hr; + } + + DeviceData->DeviceHandle = CreateFile(DeviceData->DevicePath, + GENERIC_WRITE | GENERIC_READ, + FILE_SHARE_WRITE | FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, + NULL); + + if (INVALID_HANDLE_VALUE == DeviceData->DeviceHandle) { + + hr = HRESULT_FROM_WIN32(GetLastError()); + return hr; + } + + bResult = WinUsb_Initialize(DeviceData->DeviceHandle, + &DeviceData->WinusbHandle); + + if (FALSE == bResult) { + + hr = HRESULT_FROM_WIN32(GetLastError()); + CloseHandle(DeviceData->DeviceHandle); + return hr; + } + + DeviceData->HandlesOpen = TRUE; + return hr; +} + +VOID +CloseDevice( + _Inout_ PDEVICE_DATA DeviceData + ) +/*++ + +Routine description: + + Perform required cleanup when the device is no longer needed. + + If OpenDevice failed, do nothing. + +Arguments: + + DeviceData - Struct filled in by OpenDevice + +Return value: + + None + +--*/ +{ + if (FALSE == DeviceData->HandlesOpen) { + + // + // Called on an uninitialized DeviceData + // + return; + } + + WinUsb_Free(DeviceData->WinusbHandle); + CloseHandle(DeviceData->DeviceHandle); + DeviceData->HandlesOpen = FALSE; + + return; +} + +HRESULT +RetrieveDevicePath( + _Out_bytecap_(BufLen) LPTSTR DevicePath, + _In_ ULONG BufLen, + _Out_opt_ PBOOL FailureDeviceNotFound + ) +/*++ + +Routine description: + + Retrieve the device path that can be used to open the WinUSB-based device. + + If multiple devices have the same device interface GUID, there is no + guarantee of which one will be returned. + +Arguments: + + DevicePath - On successful return, the path of the device (use with CreateFile). + + BufLen - The size of DevicePath's buffer, in bytes + + FailureDeviceNotFound - TRUE when failure is returned due to no devices + found with the correct device interface (device not connected, driver + not installed, or device is disabled in Device Manager); FALSE + otherwise. + +Return value: + + HRESULT + +--*/ +{ + CONFIGRET cr = CR_SUCCESS; + HRESULT hr = S_OK; + PTSTR DeviceInterfaceList = NULL; + ULONG DeviceInterfaceListLength = 0; + + if (NULL != FailureDeviceNotFound) { + + *FailureDeviceNotFound = FALSE; + } + + // + // Enumerate all devices exposing the interface. Do this in a loop + // in case a new interface is discovered while this code is executing, + // causing CM_Get_Device_Interface_List to return CR_BUFFER_SMALL. + // + do { + cr = CM_Get_Device_Interface_List_Size(&DeviceInterfaceListLength, + (LPGUID)&GUID_DEVINTERFACE_XBOFS_WIN_CONTROLLER, + NULL, + CM_GET_DEVICE_INTERFACE_LIST_PRESENT); + + if (cr != CR_SUCCESS) { + hr = HRESULT_FROM_WIN32(CM_MapCrToWin32Err(cr, ERROR_INVALID_DATA)); + break; + } + + DeviceInterfaceList = (PTSTR)HeapAlloc(GetProcessHeap(), + HEAP_ZERO_MEMORY, + DeviceInterfaceListLength * sizeof(TCHAR)); + + if (DeviceInterfaceList == NULL) { + hr = E_OUTOFMEMORY; + break; + } + + cr = CM_Get_Device_Interface_List((LPGUID)&GUID_DEVINTERFACE_XBOFS_WIN_CONTROLLER, + NULL, + DeviceInterfaceList, + DeviceInterfaceListLength, + CM_GET_DEVICE_INTERFACE_LIST_PRESENT); + + if (cr != CR_SUCCESS) { + HeapFree(GetProcessHeap(), 0, DeviceInterfaceList); + + if (cr != CR_BUFFER_SMALL) { + hr = HRESULT_FROM_WIN32(CM_MapCrToWin32Err(cr, ERROR_INVALID_DATA)); + } + } + } while (cr == CR_BUFFER_SMALL); + + if (FAILED(hr)) { + return hr; + } + + // + // If the interface list is empty, no devices were found. + // + if (*DeviceInterfaceList == TEXT('\0')) { + if (NULL != FailureDeviceNotFound) { + *FailureDeviceNotFound = TRUE; + } + + hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND); + HeapFree(GetProcessHeap(), 0, DeviceInterfaceList); + return hr; + } + + // + // Give path of the first found device interface instance to the caller. CM_Get_Device_Interface_List ensured + // the instance is NULL-terminated. + // + hr = StringCbCopy(DevicePath, + BufLen, + DeviceInterfaceList); + + HeapFree(GetProcessHeap(), 0, DeviceInterfaceList); + + return hr; +} diff --git a/XBOFS.win.old/src/utils.cpp b/XBOFS.win.old/src/utils.cpp new file mode 100644 index 0000000..cc56ead --- /dev/null +++ b/XBOFS.win.old/src/utils.cpp @@ -0,0 +1,44 @@ +#include "XBOFS.win\utils.h" + +/* +See https://stackoverflow.com/a/3999597/106057 +*/ +std::string XBOFSWin::utf8_encode(const std::wstring &wstr) +{ + if (wstr.empty()) return std::string(); + int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL); + std::string strTo(size_needed, 0); + WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL); + return strTo; +} + +/* +See https://stackoverflow.com/a/3999597/106057 +*/ +std::wstring XBOFSWin::utf8_decode(const std::string &str) +{ + if (str.empty()) return std::wstring(); + int size_needed = MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), NULL, 0); + std::wstring wstrTo(size_needed, 0); + MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), &wstrTo[0], size_needed); + return wstrTo; +} + +std::shared_ptr XBOFSWin::setup_logger(std::string loggerName, std::string logFile, std::vector sinks) +{ + auto logger = spdlog::get(loggerName); + if (!logger) + { + if (sinks.size() > 0) + { + logger = std::make_shared(loggerName, std::begin(sinks), std::end(sinks)); + spdlog::register_logger(logger); + } + else + { + logger = spdlog::basic_logger_mt(loggerName, logFile); + } + } + + return logger; +} \ No newline at end of file From 5e9cb5acff72aabe9a81c5982eefa6cd938a3853 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Fri, 11 Oct 2019 22:49:58 +0200 Subject: [PATCH 12/50] #2: Added new XBOFS.win project based of the Qt class library template. Rewrote WinUsbDevice and WinUsbDeviceManager for usage with QThread, signals and slots --- XBOFS.win/XBOFS.win.vcxproj | 289 ++++++------------ XBOFS.win/XBOFS.win.vcxproj.filters | 80 +++-- XBOFS.win/include/XBOFS.win/WinUsbDevice.h | 28 +- .../include/XBOFS.win/WinUsbDeviceManager.h | 20 +- XBOFS.win/include/XBOFS.win/XBOFSwin.h | 9 + XBOFS.win/include/XBOFS.win/device.h | 1 + XBOFS.win/include/XBOFS.win/pch.h | 2 +- XBOFS.win/include/XBOFS.win/stdafx.h | 0 XBOFS.win/include/XBOFS.win/xbofswin_global.h | 13 + XBOFS.win/src/WinUsbDevice.cpp | 62 ++-- XBOFS.win/src/WinUsbDeviceManager.cpp | 47 +-- XBOFS.win/src/XBOFSwin.cpp | 6 + XBOFS.win/src/stdafx.cpp | 1 + 13 files changed, 268 insertions(+), 290 deletions(-) create mode 100644 XBOFS.win/include/XBOFS.win/XBOFSwin.h create mode 100644 XBOFS.win/include/XBOFS.win/stdafx.h create mode 100644 XBOFS.win/include/XBOFS.win/xbofswin_global.h create mode 100644 XBOFS.win/src/XBOFSwin.cpp create mode 100644 XBOFS.win/src/stdafx.cpp diff --git a/XBOFS.win/XBOFS.win.vcxproj b/XBOFS.win/XBOFS.win.vcxproj index 328e2a2..ed0e228 100644 --- a/XBOFS.win/XBOFS.win.vcxproj +++ b/XBOFS.win/XBOFS.win.vcxproj @@ -1,258 +1,153 @@  - + - - Debug_LIB - ARM - - - Debug_LIB - ARM64 - - - Debug_LIB - Win32 - Debug_LIB x64 - - Release_LIB - ARM - - - Release_LIB - ARM64 - - - Release_LIB - Win32 - Release_LIB x64 - - - - - - {7db06674-1f4f-464b-8e1c-172e9587f9dc} - - - - - - - - - - - - - - - - - - {F6104731-5815-4BBA-A558-E859DD039413} - {b287f7f7-abf3-440d-b608-cb36380f2981} - v4.5 - 12.0 - Debug - Win32 - XBOFS.win + {E92A7135-2110-45FC-BEBD-D8207CF56D48} + QtVS_v301 $(LatestTargetPlatformVersion) + v4.5 - - Windows10 - true - WindowsApplicationForDrivers10.0 - Application - Universal - Unicode - - - Windows10 - false - WindowsApplicationForDrivers10.0 - Application - Universal - Unicode - - Windows10 - true - WindowsApplicationForDrivers10.0 StaticLibrary - Desktop + WindowsApplicationForDrivers10.0 Unicode + Desktop - Windows10 - false - WindowsApplicationForDrivers10.0 StaticLibrary - Desktop - Unicode - - - Windows10 - true - WindowsApplicationForDrivers10.0 - Application - Universal - Unicode - - - Windows10 - false WindowsApplicationForDrivers10.0 - Application - Universal - Unicode - - - Windows10 - true - WindowsApplicationForDrivers10.0 - Application - Universal - Unicode - - - Windows10 - false - WindowsApplicationForDrivers10.0 - Application - Universal Unicode + Desktop - - - - - - - - - DbgengRemoteDebugger + + $(MSBuildProjectDirectory)\QtMsBuild - - DbgengRemoteDebugger + + $(SolutionDir)lib\release\$(PlatformShortName)\ + $(SolutionDir)ViGEmClient\include;$(SolutionDir)XBOFS.win\include;$(IncludePath) - DbgengRemoteDebugger - $(SolutionDir)ViGEmClient\include;$(SolutionDir)XBOFS.win\include;$(IncludePath) - $(SolutionDir)PDCurses\wincon;$(LibraryPath) $(SolutionDir)lib\debug\$(PlatformShortName)\ - - - DbgengRemoteDebugger $(SolutionDir)ViGEmClient\include;$(SolutionDir)XBOFS.win\include;$(IncludePath) - $(SolutionDir)PDCurses\wincon;$(LibraryPath) - $(SolutionDir)lib\release\$(PlatformShortName)\ - - - DbgengRemoteDebugger - - DbgengRemoteDebugger - - - DbgengRemoteDebugger + + + + + + + + + + + + + + + + + msvc2017_64 + core - - DbgengRemoteDebugger + + msvc2017_64 + core - - - _DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) - MultiThreadedDebugDLL - - - %(AdditionalDependencies);onecoreuap.lib;winusb.lib - - - - - WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) - - - %(AdditionalDependencies);onecoreuap.lib;winusb.lib - - + + + + + XBOFS.win/stdafx.h + - _DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) + true + Disabled + ProgramDatabase MultiThreadedDebug + true + NotUsing + stdafx.h + $(IntDir)$(TargetName).pch + XBOFSWIN_LIB;BUILD_STATIC;_DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) false + stdcpp17 - %(AdditionalDependencies);onecoreuap.lib;winusb.lib - false + Windows + $(OutDir)\$(ProjectName).lib + true cd $(SolutionDir) & call pre-build.cmd + + Pre-build tasks + + XBOFS.win/stdafx.h + - WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;BUILD_SHARED_LIBS;%(PreprocessorDefinitions) + true + + MultiThreaded + true + NotUsing + stdafx.h + $(IntDir)$(TargetName).pch + XBOFSWIN_LIB;BUILD_STATIC;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) false stdcpp17 - MultiThreaded - %(AdditionalDependencies);onecoreuap.lib;winusb.lib - false + Windows + $(OutDir)\$(ProjectName).lib + false cd $(SolutionDir) & call pre-build.cmd + + Pre-build tasks - - - _DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) - MultiThreadedDebugDLL - - - %(AdditionalDependencies);onecoreuap.lib;winusb.lib - - - - - WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) - - - %(AdditionalDependencies);onecoreuap.lib;winusb.lib - - - - - _DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) - MultiThreadedDebugDLL - - - %(AdditionalDependencies);onecoreuap.lib;winusb.lib - - - - - WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) - - - %(AdditionalDependencies);onecoreuap.lib;winusb.lib - - + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/XBOFS.win/XBOFS.win.vcxproj.filters b/XBOFS.win/XBOFS.win.vcxproj.filters index 5a53305..80b6672 100644 --- a/XBOFS.win/XBOFS.win.vcxproj.filters +++ b/XBOFS.win/XBOFS.win.vcxproj.filters @@ -1,25 +1,21 @@  - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {8E41214B-6785-4CFE-B992-037D68949A14} - inf;inv;inx;mof;mc; - - - - + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + @@ -28,34 +24,36 @@ Header Files - - Header Files - Header Files - - Header Files - - + Header Files - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - + + {D9D6E242-F8AF-46E4-B9FD-80ECBC20BA3E} + qrc;* + false + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + true + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + true + + + + + Header Files + + + Header Files + \ No newline at end of file diff --git a/XBOFS.win/include/XBOFS.win/WinUsbDevice.h b/XBOFS.win/include/XBOFS.win/WinUsbDevice.h index 38405e1..76581c3 100644 --- a/XBOFS.win/include/XBOFS.win/WinUsbDevice.h +++ b/XBOFS.win/include/XBOFS.win/WinUsbDevice.h @@ -2,19 +2,41 @@ #include #include #include +#include namespace XBOFSWin { /* */ - class WinUsbDevice : public Thread + class WinUsbDevice : public QObject { + Q_OBJECT + public: - WinUsbDevice(tstring devicePath, std::string identifier, std::shared_ptr logger, DWORD parentThreadId, DWORD uiManagerThreadId); + WinUsbDevice(tstring devicePath, std::string identifier, std::shared_ptr logger); ~WinUsbDevice() {}; - DWORD run(void); + //DWORD run(void); + + public slots: + void run(); + + signals: + void vigEmConnect(); + void vigEmConnected(); + void vigEmTargetAdd(); + void vigEmTargetAdded(); + void vigEmError(); + void winUsbDeviceOpen(); + void winUsbDeviceOpened(); + void winUsbDeviceInit(); + void winUsbDeviceInitComplete(); + void winUsbDeviceReadingInput(); + void winUsbDeviceTerminating(); + void winUsbDeviceError(); protected: + const std::string identifier; + const std::shared_ptr logger; const tstring devicePath; bool deviceHandlesOpen = false; diff --git a/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h b/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h index 4b12060..d2d7394 100644 --- a/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h +++ b/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h @@ -3,23 +3,35 @@ #include #include #include +#include +#include /* */ namespace XBOFSWin { - class WinUsbDeviceManager : public Thread + class WinUsbDeviceManager : public QObject { + Q_OBJECT public: - WinUsbDeviceManager(std::shared_ptr logger, DWORD parentThreadId, DWORD uiManagerThreadId); + WinUsbDeviceManager(std::string identifier, std::shared_ptr logger); ~WinUsbDeviceManager() {}; - DWORD run(); + public slots: + void run(); + + signals: + void winUsbDeviceManagerScanning(); + void winUsbDeviceManagerSleeping(); + void winUsbDeviceManagerTerminating(); protected: - std::set retrieveDevicePaths(); + const std::string identifier; + const std::shared_ptr logger; + std::set retrieveDevicePaths(); + std::unordered_map> devicePathWinUsbDeviceMap; }; } diff --git a/XBOFS.win/include/XBOFS.win/XBOFSwin.h b/XBOFS.win/include/XBOFS.win/XBOFSwin.h new file mode 100644 index 0000000..caa7357 --- /dev/null +++ b/XBOFS.win/include/XBOFS.win/XBOFSwin.h @@ -0,0 +1,9 @@ +#pragma once + +#include "xbofswin_global.h" + +class XBOFSWIN_EXPORT XBOFSwin +{ +public: + XBOFSwin(); +}; diff --git a/XBOFS.win/include/XBOFS.win/device.h b/XBOFS.win/include/XBOFS.win/device.h index 508de82..228cc10 100644 --- a/XBOFS.win/include/XBOFS.win/device.h +++ b/XBOFS.win/include/XBOFS.win/device.h @@ -3,6 +3,7 @@ // Define below GUIDs // #include +#include "stdafx.h" // // Device Interface GUID. diff --git a/XBOFS.win/include/XBOFS.win/pch.h b/XBOFS.win/include/XBOFS.win/pch.h index d3216dd..3890435 100644 --- a/XBOFS.win/include/XBOFS.win/pch.h +++ b/XBOFS.win/include/XBOFS.win/pch.h @@ -13,7 +13,7 @@ #include "device.h" #include "utils.h" - +#include "stdafx.h" typedef std::basic_string tstring; diff --git a/XBOFS.win/include/XBOFS.win/stdafx.h b/XBOFS.win/include/XBOFS.win/stdafx.h new file mode 100644 index 0000000..e69de29 diff --git a/XBOFS.win/include/XBOFS.win/xbofswin_global.h b/XBOFS.win/include/XBOFS.win/xbofswin_global.h new file mode 100644 index 0000000..fa43e83 --- /dev/null +++ b/XBOFS.win/include/XBOFS.win/xbofswin_global.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#ifndef BUILD_STATIC +# if defined(XBOFSWIN_LIB) +# define XBOFSWIN_EXPORT Q_DECL_EXPORT +# else +# define XBOFSWIN_EXPORT Q_DECL_IMPORT +# endif +#else +# define XBOFSWIN_EXPORT +#endif diff --git a/XBOFS.win/src/WinUsbDevice.cpp b/XBOFS.win/src/WinUsbDevice.cpp index 6049d5b..3c2ffdf 100644 --- a/XBOFS.win/src/WinUsbDevice.cpp +++ b/XBOFS.win/src/WinUsbDevice.cpp @@ -1,62 +1,72 @@ +#include +#include +#include + #include "XBOFS.win\WinUsbDevice.h" using namespace XBOFSWin; /* Constructs the WinUsbDevice instance and starts its event loop in a separate thread */ -WinUsbDevice::WinUsbDevice(tstring devicePath, std::string identifier, std::shared_ptr logger, DWORD parentThreadId, DWORD uiManagerThreadId) -: Thread(identifier, logger, parentThreadId, uiManagerThreadId), devicePath(devicePath) +WinUsbDevice::WinUsbDevice(tstring devicePath, std::string identifier, std::shared_ptr logger) +: QObject(), devicePath(devicePath), identifier(identifier), logger(logger) { } -DWORD WinUsbDevice::run() { +void WinUsbDevice::run() { bool loop = true; int failedReads = 0; int failedWrites = 0; - MSG threadMessage; - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_STARTED, NULL); - this->logger->info("Started thread for {}", this->identifier); + MSG threadMessage; + // Allocate objects for VigEm this->logger->info("Allocating VigEmClient for {}", this->identifier); this->vigEmClient = vigem_alloc(); this->logger->info("Allocating VigEmTarget for {}", this->identifier); this->vigEmTarget = vigem_target_x360_alloc(); - this->logger->info("Connecting VigEmClient for {}", this->identifier); - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_VIGEM_CONNECT, NULL); + // Connect to VigEm + this->logger->info("Connecting VigEmClient for {}", this->identifier); + emit vigEmConnect(); if (!VIGEM_SUCCESS(vigem_connect(this->vigEmClient))) { - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_ERROR, NULL); + emit vigEmError(); this->logger->error("Unable to connect VigEmClient for {}", this->identifier); loop = false; - } - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_VIGEM_TARGET_ADD, NULL); + } + emit vigEmConnected(); + // Add VigEm Target this->logger->info("Adding VigEmTarget for {}", this->identifier); + emit vigEmTargetAdd(); if (!VIGEM_SUCCESS(vigem_target_add(this->vigEmClient, this->vigEmTarget))) { - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_ERROR, NULL); + emit vigEmError(); this->logger->error("Unable to add VigEmTarget for {}", this->identifier); loop = false; } + emit vigEmTargetAdded(); // Loop reading input, processing it and dispatching it this->logger->info("Starting Read-Process-Dispatch loop for {}", this->identifier); - while (loop) { - if (PeekMessage(&threadMessage, NULL, WM_USER, WM_APP, PM_REMOVE) == TRUE && threadMessage.message == RAWUVEF_STOP) loop = false; - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_OPEN, NULL); + while (loop && !QThread::currentThread()->isInterruptionRequested()) { + // Open WinUsbDevice + emit winUsbDeviceOpen(); if (!this->openDevice()) { - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_ERROR, NULL); + emit winUsbDeviceError(); this->logger->error("Unable to open WinUSB device for {}", this->identifier); continue; } - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_INIT, NULL); + emit winUsbDeviceOpened(); + // Init WinUsbDevice + emit winUsbDeviceInit(); if (!this->initRazorAtrox()) { - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_ERROR, NULL); + emit winUsbDeviceError(); this->logger->error("Unable to init Razer Atrox for {}", this->identifier); continue; } - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_READ_INPUT, NULL); + emit winUsbDeviceInitComplete(); + // Read input + emit winUsbDeviceReadingInput(); this->logger->info("Reading input from Razer Atrox for {}", this->identifier); int currentFailedReads = 0; - while (loop && currentFailedReads < 5) { - if (PeekMessage(&threadMessage, NULL, WM_USER, WM_APP, PM_REMOVE) == TRUE && threadMessage.message == RAWUVEF_STOP) loop = false; - if (!this->readInputFromRazerAtrox()) { + while (loop && currentFailedReads < 5 && !QThread::currentThread()->isInterruptionRequested()) { + if (!this->readInputFromRazerAtrox()) { this->logger->warn("Failed to read input from Razer Atrox for {}", this->identifier); currentFailedReads += 1; continue; @@ -65,13 +75,14 @@ DWORD WinUsbDevice::run() { if (!this->dispatchInputToVigEmController()) failedWrites += 1; } if (currentFailedReads >= 5) { - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_ERROR, NULL); + emit winUsbDeviceError(); this->logger->warn("Failed to read input from Razer Atrox 5 or more times for {}", this->identifier); } failedReads += currentFailedReads; currentFailedReads = 0; + QThread::currentThread()->eventDispatcher()->processEvents(QEventLoop::AllEvents); } - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_TERMINATING, NULL); + emit winUsbDeviceTerminating(); this->logger->info("Completed Read-Process-Dispatch loop for {}", this->identifier); this->logger->info("There were {} failed reads for {}", failedReads, this->identifier); this->logger->info("There were {} failed writes for {}", failedWrites, this->identifier); @@ -85,8 +96,7 @@ DWORD WinUsbDevice::run() { vigem_target_free(this->vigEmTarget); this->logger->info("Free VigEmClient for {}", this->identifier); vigem_free(this->vigEmClient); - this->logger->info("Completed thread for {}", this->identifier); - return 0; + this->logger->info("Completed thread for {}", this->identifier); } /* diff --git a/XBOFS.win/src/WinUsbDeviceManager.cpp b/XBOFS.win/src/WinUsbDeviceManager.cpp index 8fbc997..57a406c 100644 --- a/XBOFS.win/src/WinUsbDeviceManager.cpp +++ b/XBOFS.win/src/WinUsbDeviceManager.cpp @@ -1,23 +1,24 @@ #include "XBOFS.win\WinUsbDeviceManager.h" #include "XBOFS.win\utils.h"; +#include +#include using namespace XBOFSWin; /* Constructs the WinUsbDeviceManager and starts its event loop in a separate thread */ -WinUsbDeviceManager::WinUsbDeviceManager(std::shared_ptr logger, DWORD parentThreadId, DWORD uiManagerThreadId) -: Thread("WinUsbDeviceManager", logger, parentThreadId, uiManagerThreadId) +WinUsbDeviceManager::WinUsbDeviceManager(std::string identifier, std::shared_ptr logger) +: QObject(), identifier(identifier), logger(logger) {} -DWORD WinUsbDeviceManager::run() { - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_MANAGER_STARTED, NULL); +void WinUsbDeviceManager::run() { this->logger->info("Started thread for {}", this->identifier); MSG threadMessage; bool loop = true; - std::unordered_map devicePathWinUsbDeviceMap; + this->logger->info("Starting scan loop for {}", this->identifier); - while (loop) { - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_MANAGER_SCANNING, NULL); + while (loop && !QThread::currentThread()->isInterruptionRequested()) { + emit winUsbDeviceManagerScanning(); auto devicePaths = this->retrieveDevicePaths(); // Check the updated set for new devicePaths for (auto devicePath : devicePaths) { @@ -27,28 +28,38 @@ DWORD WinUsbDeviceManager::run() { auto identifier = utf8_encode(devicePath); #else auto identifier = devicePath; - #endif // UNICODE + #endif // UNICODE + auto winUsbDeviceThread = new QThread(); auto winUsbDeviceLogger = setup_logger("WinUsbDevice", "", this->logger->sinks()); - auto winUsbDevice = new WinUsbDevice(devicePath, identifier, winUsbDeviceLogger, this->threadId, this->uiManagerThreadId); - devicePathWinUsbDeviceMap.insert({ devicePath, winUsbDevice }); + auto winUsbDevice = new WinUsbDevice(devicePath, identifier, winUsbDeviceLogger); + connect(winUsbDeviceThread, &QThread::finished, winUsbDevice, &QObject::deleteLater); + connect(winUsbDeviceThread, &QThread::started, winUsbDevice, &WinUsbDevice::run); + winUsbDevice->moveToThread(winUsbDeviceThread); + devicePathWinUsbDeviceMap.insert({ devicePath, std::make_pair(winUsbDeviceThread, winUsbDevice) }); + winUsbDeviceThread->start(); } // Check for WinUsbDevices to remove for (auto tuple : devicePathWinUsbDeviceMap) { if (devicePaths.find(tuple.first) != devicePaths.end()) continue; - delete tuple.second; + tuple.second.first->requestInterruption(); + tuple.second.first->terminate(); + tuple.second.first->wait(); devicePathWinUsbDeviceMap.erase(tuple.first); } - // TODO: Processes messages in thread message queue - if (PeekMessage(&threadMessage, NULL, WM_USER, WM_APP, PM_REMOVE) == TRUE && threadMessage.message == RAWUVEF_STOP) loop = false; + // Processes messages in thread message queue + QThread::currentThread()->eventDispatcher()->processEvents(QEventLoop::AllEvents); + if (QThread::currentThread()->isInterruptionRequested()) loop = false; else { - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_MANAGER_SLEEPING, NULL); - Sleep(1000); + emit winUsbDeviceManagerSleeping(); + QThread::msleep(1000); } } this->logger->info("Completed scan loop for {}", this->identifier); - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_MANAGER_TERMINATING, NULL); - for (auto tuple : devicePathWinUsbDeviceMap) delete tuple.second; - return 0; + emit winUsbDeviceManagerTerminating(); + for (auto tuple : devicePathWinUsbDeviceMap) { + delete tuple.second.second; + delete tuple.second.first; + } } diff --git a/XBOFS.win/src/XBOFSwin.cpp b/XBOFS.win/src/XBOFSwin.cpp new file mode 100644 index 0000000..326faae --- /dev/null +++ b/XBOFS.win/src/XBOFSwin.cpp @@ -0,0 +1,6 @@ +#include "stdafx.h" +#include "XBOFSwin.h" + +XBOFSwin::XBOFSwin() +{ +} diff --git a/XBOFS.win/src/stdafx.cpp b/XBOFS.win/src/stdafx.cpp new file mode 100644 index 0000000..952b483 --- /dev/null +++ b/XBOFS.win/src/stdafx.cpp @@ -0,0 +1 @@ +#include From d4c84d723d9a91bd5ddd12370c680366476fd9c5 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Fri, 11 Oct 2019 22:50:46 +0200 Subject: [PATCH 13/50] #2: Renamed base class for WinUsbDeviceWidget.ui --- XBOFS.win.qt5/WinUsbDeviceWidget.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/XBOFS.win.qt5/WinUsbDeviceWidget.ui b/XBOFS.win.qt5/WinUsbDeviceWidget.ui index 878a546..6be0494 100644 --- a/XBOFS.win.qt5/WinUsbDeviceWidget.ui +++ b/XBOFS.win.qt5/WinUsbDeviceWidget.ui @@ -1,7 +1,7 @@ - Form - + WinUsbDeviceWidget + 0 From 531fd21732d66c6caa101256890ca7f8868bb591 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Fri, 11 Oct 2019 22:51:37 +0200 Subject: [PATCH 14/50] #2: Updated solution --- XBOFS.win.sln | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/XBOFS.win.sln b/XBOFS.win.sln index 0f6378b..72cc10b 100644 --- a/XBOFS.win.sln +++ b/XBOFS.win.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.28307.572 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "XBOFS.win", "XBOFS.win\XBOFS.win.vcxproj", "{F6104731-5815-4BBA-A558-E859DD039413}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "XBOFS.win.old", "XBOFS.win.old\XBOFS.win.old.vcxproj", "{F6104731-5815-4BBA-A558-E859DD039413}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ViGEmClient", "ViGEmClient\src\ViGEmClient.vcxproj", "{7DB06674-1F4F-464B-8E1C-172E9587F9DC}" EndProject @@ -34,7 +34,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "presets", "presets", "{98CB EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "XBOFS.win.qt5", "XBOFS.win.qt5\XBOFS.win.qt5.vcxproj", "{B12702AD-ABFB-343A-A199-8E24837244A3}" ProjectSection(ProjectDependencies) = postProject - {F6104731-5815-4BBA-A558-E859DD039413} = {F6104731-5815-4BBA-A558-E859DD039413} + {E92A7135-2110-45FC-BEBD-D8207CF56D48} = {E92A7135-2110-45FC-BEBD-D8207CF56D48} + {7DB06674-1F4F-464B-8E1C-172E9587F9DC} = {7DB06674-1F4F-464B-8E1C-172E9587F9DC} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "XBOFS.win", "XBOFS.win\XBOFS.win.vcxproj", "{E92A7135-2110-45FC-BEBD-D8207CF56D48}" + ProjectSection(ProjectDependencies) = postProject {7DB06674-1F4F-464B-8E1C-172E9587F9DC} = {7DB06674-1F4F-464B-8E1C-172E9587F9DC} EndProjectSection EndProject @@ -84,6 +89,18 @@ Global {B12702AD-ABFB-343A-A199-8E24837244A3}.Release_LIB|x64.Build.0 = Release|x64 {B12702AD-ABFB-343A-A199-8E24837244A3}.Release|x64.ActiveCfg = Release|x64 {B12702AD-ABFB-343A-A199-8E24837244A3}.Release|x64.Build.0 = Release|x64 + {E92A7135-2110-45FC-BEBD-D8207CF56D48}.Debug_DLL|x64.ActiveCfg = Debug_LIB|x64 + {E92A7135-2110-45FC-BEBD-D8207CF56D48}.Debug_DLL|x64.Build.0 = Debug_LIB|x64 + {E92A7135-2110-45FC-BEBD-D8207CF56D48}.Debug_LIB|x64.ActiveCfg = Debug_LIB|x64 + {E92A7135-2110-45FC-BEBD-D8207CF56D48}.Debug_LIB|x64.Build.0 = Debug_LIB|x64 + {E92A7135-2110-45FC-BEBD-D8207CF56D48}.Debug|x64.ActiveCfg = Debug_LIB|x64 + {E92A7135-2110-45FC-BEBD-D8207CF56D48}.Debug|x64.Build.0 = Debug_LIB|x64 + {E92A7135-2110-45FC-BEBD-D8207CF56D48}.Release_DLL|x64.ActiveCfg = Release_LIB|x64 + {E92A7135-2110-45FC-BEBD-D8207CF56D48}.Release_DLL|x64.Build.0 = Release_LIB|x64 + {E92A7135-2110-45FC-BEBD-D8207CF56D48}.Release_LIB|x64.ActiveCfg = Release_LIB|x64 + {E92A7135-2110-45FC-BEBD-D8207CF56D48}.Release_LIB|x64.Build.0 = Release_LIB|x64 + {E92A7135-2110-45FC-BEBD-D8207CF56D48}.Release|x64.ActiveCfg = Release_LIB|x64 + {E92A7135-2110-45FC-BEBD-D8207CF56D48}.Release|x64.Build.0 = Release_LIB|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From ac098a06ccfa78be48f9195f773e3860a0c54969 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Sat, 12 Oct 2019 21:53:24 +0200 Subject: [PATCH 15/50] #2: Removed unnecessary files and fixed imports --- XBOFS.win/include/XBOFS.win/Thread.h | 50 --------------- XBOFS.win/include/XBOFS.win/WinUsbDevice.h | 1 - .../include/XBOFS.win/WinUsbDeviceManager.h | 1 - XBOFS.win/include/XBOFS.win/XBOFSwin.h | 9 --- XBOFS.win/include/XBOFS.win/xbofswin_global.h | 13 ---- XBOFS.win/src/Thread.cpp | 63 ------------------- XBOFS.win/src/WinUsbDeviceManager.cpp | 2 + XBOFS.win/src/XBOFSwin.cpp | 6 -- 8 files changed, 2 insertions(+), 143 deletions(-) delete mode 100644 XBOFS.win/include/XBOFS.win/Thread.h delete mode 100644 XBOFS.win/include/XBOFS.win/XBOFSwin.h delete mode 100644 XBOFS.win/include/XBOFS.win/xbofswin_global.h delete mode 100644 XBOFS.win/src/Thread.cpp delete mode 100644 XBOFS.win/src/XBOFSwin.cpp diff --git a/XBOFS.win/include/XBOFS.win/Thread.h b/XBOFS.win/include/XBOFS.win/Thread.h deleted file mode 100644 index 05e5e33..0000000 --- a/XBOFS.win/include/XBOFS.win/Thread.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once -#include - -namespace XBOFSWin { - - enum THREAD_MESSAGES { - RAWUVEF_STOP = WM_USER + 0, - RAWUVEF_STOPPED = WM_USER + 1, - RAWUVEF_WIN_USB_DEVICE_MANAGER_STARTED = WM_USER + 2, - RAWUVEF_WIN_USB_DEVICE_MANAGER_SCANNING = WM_USER + 3, - RAWUVEF_WIN_USB_DEVICE_MANAGER_SLEEPING = WM_USER + 4, - RAWUVEF_WIN_USB_DEVICE_MANAGER_TERMINATING = WM_USER + 5, - RAWUVEF_WIN_USB_DEVICE_MANAGER_ERROR = WM_USER + 6, - RAWUVEF_WIN_USB_DEVICE_STARTED = WM_USER + 7, - RAWUVEF_WIN_USB_DEVICE_VIGEM_CONNECT = WM_USER + 8, - RAWUVEF_WIN_USB_DEVICE_VIGEM_TARGET_ADD = WM_USER + 9, - RAWUVEF_WIN_USB_DEVICE_OPEN = WM_USER + 10, - RAWUVEF_WIN_USB_DEVICE_INIT = WM_USER + 11, - RAWUVEF_WIN_USB_DEVICE_READ_INPUT = WM_USER + 12, - RAWUVEF_WIN_USB_DEVICE_TERMINATING = WM_USER + 13, - RAWUVEF_WIN_USB_DEVICE_ERROR = WM_USER + 14 - }; - - std::string threadMessageToString(THREAD_MESSAGES threadMessage); - - class Thread - { - public: - Thread() = delete; - Thread(std::string identifier, std::shared_ptr logger, DWORD parentThreadId, DWORD uiManagerThreadId); - ~Thread(); - - virtual DWORD run() = 0; - DWORD getThreadId(); - DWORD getParentThreadId(); - DWORD getUiManagerThreadId(); - - protected: - const std::string identifier; - const DWORD parentThreadId; - const DWORD uiManagerThreadId; - - std::shared_ptr logger; - DWORD threadId = 0; - HANDLE threadHandle = NULL; - - static DWORD startThread(LPVOID data); - BOOL notifyUIManager(UINT messageValue, LPARAM lParam); - }; -} diff --git a/XBOFS.win/include/XBOFS.win/WinUsbDevice.h b/XBOFS.win/include/XBOFS.win/WinUsbDevice.h index 76581c3..8452367 100644 --- a/XBOFS.win/include/XBOFS.win/WinUsbDevice.h +++ b/XBOFS.win/include/XBOFS.win/WinUsbDevice.h @@ -1,6 +1,5 @@ #pragma once #include -#include #include #include diff --git a/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h b/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h index d2d7394..72ff878 100644 --- a/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h +++ b/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h @@ -1,6 +1,5 @@ #pragma once #include -#include #include #include #include diff --git a/XBOFS.win/include/XBOFS.win/XBOFSwin.h b/XBOFS.win/include/XBOFS.win/XBOFSwin.h deleted file mode 100644 index caa7357..0000000 --- a/XBOFS.win/include/XBOFS.win/XBOFSwin.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "xbofswin_global.h" - -class XBOFSWIN_EXPORT XBOFSwin -{ -public: - XBOFSwin(); -}; diff --git a/XBOFS.win/include/XBOFS.win/xbofswin_global.h b/XBOFS.win/include/XBOFS.win/xbofswin_global.h deleted file mode 100644 index fa43e83..0000000 --- a/XBOFS.win/include/XBOFS.win/xbofswin_global.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include - -#ifndef BUILD_STATIC -# if defined(XBOFSWIN_LIB) -# define XBOFSWIN_EXPORT Q_DECL_EXPORT -# else -# define XBOFSWIN_EXPORT Q_DECL_IMPORT -# endif -#else -# define XBOFSWIN_EXPORT -#endif diff --git a/XBOFS.win/src/Thread.cpp b/XBOFS.win/src/Thread.cpp deleted file mode 100644 index ce8fb99..0000000 --- a/XBOFS.win/src/Thread.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include "XBOFS.win\Thread.h" - -using namespace XBOFSWin; - -std::string threadMessageToString(THREAD_MESSAGES threadMessage) -{ - switch (threadMessage) { - case RAWUVEF_WIN_USB_DEVICE_MANAGER_STARTED: return "Started"; - case RAWUVEF_WIN_USB_DEVICE_MANAGER_SCANNING: - case RAWUVEF_WIN_USB_DEVICE_MANAGER_SLEEPING: return "Active"; - case RAWUVEF_WIN_USB_DEVICE_MANAGER_TERMINATING: return "Terminating..."; - case RAWUVEF_WIN_USB_DEVICE_MANAGER_ERROR: return "Error!"; - case RAWUVEF_WIN_USB_DEVICE_STARTED: return "Started"; - case RAWUVEF_WIN_USB_DEVICE_VIGEM_CONNECT: return "VigEmClient connect..."; - case RAWUVEF_WIN_USB_DEVICE_VIGEM_TARGET_ADD: return "VigEmClient target add..."; - case RAWUVEF_WIN_USB_DEVICE_OPEN: return "Device Open..."; - case RAWUVEF_WIN_USB_DEVICE_INIT: return "Device Init..."; - case RAWUVEF_WIN_USB_DEVICE_READ_INPUT: return "Reading input..."; - case RAWUVEF_WIN_USB_DEVICE_TERMINATING: return "Terminating..."; - case RAWUVEF_WIN_USB_DEVICE_ERROR: return "Error!"; - } - return "Unknown Thread Message"; -} - -Thread::Thread(std::string identifier, std::shared_ptr logger, DWORD parentThreadId, DWORD uiManagerThreadId) -: identifier(identifier), logger(logger), parentThreadId(parentThreadId), uiManagerThreadId(uiManagerThreadId) -{ - this->logger->info("Starting thread for {}", this->identifier); - this->threadHandle = CreateThread(NULL, 0, startThread, (LPVOID)this, 0, &this->threadId); -} - -Thread::~Thread() -{ - this->logger->info("Stopping thread for {}", this->identifier); - PostThreadMessage(this->threadId, RAWUVEF_STOP, NULL, NULL); - while (WaitForSingleObject(this->threadHandle, INFINITE) != WAIT_OBJECT_0); - CloseHandle(this->threadHandle); - this->notifyUIManager(RAWUVEF_STOPPED, NULL); - this->logger->info("Stopped thread for {}", this->identifier); -} - -DWORD Thread::getThreadId() { - return this->threadId; -} - -DWORD Thread::getParentThreadId() { - return this->parentThreadId; -} - -DWORD Thread::getUiManagerThreadId() { - return this->uiManagerThreadId; -} - -DWORD Thread::startThread(LPVOID data) { - Thread* thread = (Thread*)data; - MSG threadMessage; - PeekMessage(&threadMessage, NULL, WM_USER, WM_USER, PM_NOREMOVE); - return thread->run(); -} - -BOOL Thread::notifyUIManager(UINT messageValue, LPARAM lParam) { - return PostThreadMessage(this->uiManagerThreadId, messageValue, this->threadId, lParam); -} diff --git a/XBOFS.win/src/WinUsbDeviceManager.cpp b/XBOFS.win/src/WinUsbDeviceManager.cpp index 57a406c..a9f7189 100644 --- a/XBOFS.win/src/WinUsbDeviceManager.cpp +++ b/XBOFS.win/src/WinUsbDeviceManager.cpp @@ -39,6 +39,8 @@ void WinUsbDeviceManager::run() { winUsbDeviceThread->start(); } // Check for WinUsbDevices to remove + // TODO: Investigate this code, unplugging a device is crashing the application with a read access violated on tuple.right which would seem to indicate it's getting + // de-allocated in an unexpected fashion... for (auto tuple : devicePathWinUsbDeviceMap) { if (devicePaths.find(tuple.first) != devicePaths.end()) continue; tuple.second.first->requestInterruption(); diff --git a/XBOFS.win/src/XBOFSwin.cpp b/XBOFS.win/src/XBOFSwin.cpp deleted file mode 100644 index 326faae..0000000 --- a/XBOFS.win/src/XBOFSwin.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "stdafx.h" -#include "XBOFSwin.h" - -XBOFSwin::XBOFSwin() -{ -} From 240c90e2928ec7e6835187a2a38aae5ebb5b0365 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Sat, 12 Oct 2019 21:55:20 +0200 Subject: [PATCH 16/50] #2: Use QThread to run WinUsbDeviceManager --- XBOFS.win.qt5/XBOFS.win.qt5.vcxproj | 2 +- XBOFS.win.qt5/XBOFSWinQT5GUI.cpp | 14 ++++++++++++-- XBOFS.win.qt5/XBOFSWinQT5GUI.h | 5 ++--- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj b/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj index 734153a..686da26 100644 --- a/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj +++ b/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj @@ -62,7 +62,7 @@ ProgramDatabase MultiThreadedDebug true - Cdecl + StdCall Windows diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp index 8707f4c..12e0b8e 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp @@ -1,11 +1,21 @@ #include "XBOFSWinQT5GUI.h" #include +#include +#include +#include +#include XBOFSWinQT5GUI::XBOFSWinQT5GUI(QWidget *parent) : QMainWindow(parent) { ui.setupUi(this); DWORD threadId = GetCurrentThreadId(); - this->logger = XBOFSWin::setup_logger("test", "test.log", std::vector()); - this->winUsbDeviceManager = new XBOFSWin::WinUsbDeviceManager(this->logger, threadId, threadId); + auto sinks = std::vector(); + this->logger = XBOFSWin::setup_logger("test", "xbofs.win.log", sinks); + auto winUsbDeviceManagerThread = new QThread(); + auto winUsbDeviceManager = new XBOFSWin::WinUsbDeviceManager("WinUsbDeviceManager", this->logger); + connect(winUsbDeviceManagerThread, &QThread::finished, winUsbDeviceManager, &QObject::deleteLater); + connect(winUsbDeviceManagerThread, &QThread::started, winUsbDeviceManager, &XBOFSWin::WinUsbDeviceManager::run); + winUsbDeviceManager->moveToThread(winUsbDeviceManagerThread); + winUsbDeviceManagerThread->start(); } diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.h b/XBOFS.win.qt5/XBOFSWinQT5GUI.h index 9a680ac..ab120c0 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.h +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.h @@ -15,8 +15,7 @@ class XBOFSWinQT5GUI : public QMainWindow public: XBOFSWinQT5GUI(QWidget *parent = Q_NULLPTR); -private: - Ui::XBOFSWinQT5GUIClass ui; - XBOFSWin::WinUsbDeviceManager *winUsbDeviceManager; +protected: + Ui::XBOFSWinQT5GUIClass ui; std::shared_ptr logger; }; From 259cdafdae8e630b2ebf99b1a1fe77a7c0ce585c Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Sun, 13 Oct 2019 20:58:13 +0200 Subject: [PATCH 17/50] #2: Fixed illegal access error resulting from bad WinUsbDevice removal handling --- .../include/XBOFS.win/WinUsbDeviceManager.h | 3 +- XBOFS.win/src/WinUsbDeviceManager.cpp | 28 +++++++++++-------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h b/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h index 72ff878..c9dc515 100644 --- a/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h +++ b/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h @@ -29,8 +29,7 @@ namespace XBOFSWin { const std::string identifier; const std::shared_ptr logger; - std::set retrieveDevicePaths(); - std::unordered_map> devicePathWinUsbDeviceMap; + std::set retrieveDevicePaths(); }; } diff --git a/XBOFS.win/src/WinUsbDeviceManager.cpp b/XBOFS.win/src/WinUsbDeviceManager.cpp index a9f7189..d093ff1 100644 --- a/XBOFS.win/src/WinUsbDeviceManager.cpp +++ b/XBOFS.win/src/WinUsbDeviceManager.cpp @@ -12,10 +12,10 @@ WinUsbDeviceManager::WinUsbDeviceManager(std::string identifier, std::shared_ptr {} void WinUsbDeviceManager::run() { - this->logger->info("Started thread for {}", this->identifier); - MSG threadMessage; + this->logger->info("Started thread for {}", this->identifier); bool loop = true; - + std::unordered_map> devicePathWinUsbDeviceMap; + std::set previousDevicePaths; this->logger->info("Starting scan loop for {}", this->identifier); while (loop && !QThread::currentThread()->isInterruptionRequested()) { emit winUsbDeviceManagerScanning(); @@ -39,15 +39,18 @@ void WinUsbDeviceManager::run() { winUsbDeviceThread->start(); } // Check for WinUsbDevices to remove - // TODO: Investigate this code, unplugging a device is crashing the application with a read access violated on tuple.right which would seem to indicate it's getting - // de-allocated in an unexpected fashion... - for (auto tuple : devicePathWinUsbDeviceMap) { - if (devicePaths.find(tuple.first) != devicePaths.end()) continue; - tuple.second.first->requestInterruption(); - tuple.second.first->terminate(); - tuple.second.first->wait(); - devicePathWinUsbDeviceMap.erase(tuple.first); - } + for (auto iterator = devicePathWinUsbDeviceMap.begin(); iterator != devicePathWinUsbDeviceMap.end(); ) + { + auto devicePath = iterator->first; + auto winUsbDeviceThread = iterator->second.first; + if (devicePaths.find(devicePath) == devicePaths.end()) { + winUsbDeviceThread->requestInterruption(); + winUsbDeviceThread->terminate(); + winUsbDeviceThread->wait(); + iterator = devicePathWinUsbDeviceMap.erase(iterator); + } + else ++iterator; + } // Processes messages in thread message queue QThread::currentThread()->eventDispatcher()->processEvents(QEventLoop::AllEvents); if (QThread::currentThread()->isInterruptionRequested()) loop = false; @@ -55,6 +58,7 @@ void WinUsbDeviceManager::run() { emit winUsbDeviceManagerSleeping(); QThread::msleep(1000); } + previousDevicePaths = devicePaths; } this->logger->info("Completed scan loop for {}", this->identifier); emit winUsbDeviceManagerTerminating(); From 0815a60631d2ddff71fa42ac4bd15d97a7e91428 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Sun, 13 Oct 2019 22:39:57 +0200 Subject: [PATCH 18/50] #2: Improved logging --- XBOFS.win.qt5/XBOFSWinQT5GUI.cpp | 7 ++++++- XBOFS.win/src/WinUsbDeviceManager.cpp | 27 ++++++++++++++++++++++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp index 12e0b8e..0c593ab 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp @@ -1,6 +1,7 @@ #include "XBOFSWinQT5GUI.h" #include #include +#include #include #include #include @@ -11,7 +12,11 @@ XBOFSWinQT5GUI::XBOFSWinQT5GUI(QWidget *parent) ui.setupUi(this); DWORD threadId = GetCurrentThreadId(); auto sinks = std::vector(); - this->logger = XBOFSWin::setup_logger("test", "xbofs.win.log", sinks); + auto rotatingFileSink = std::make_shared("xbofs.win.qt5.log", 1024 * 1024 * 10, 10); + sinks.push_back(rotatingFileSink); + this->logger = XBOFSWin::setup_logger("WinUsbDeviceManager", "", sinks); + spdlog::flush_every(std::chrono::seconds(60)); + spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%t] [%n] [%l] %v"); auto winUsbDeviceManagerThread = new QThread(); auto winUsbDeviceManager = new XBOFSWin::WinUsbDeviceManager("WinUsbDeviceManager", this->logger); connect(winUsbDeviceManagerThread, &QThread::finished, winUsbDeviceManager, &QObject::deleteLater); diff --git a/XBOFS.win/src/WinUsbDeviceManager.cpp b/XBOFS.win/src/WinUsbDeviceManager.cpp index d093ff1..830dc54 100644 --- a/XBOFS.win/src/WinUsbDeviceManager.cpp +++ b/XBOFS.win/src/WinUsbDeviceManager.cpp @@ -42,11 +42,19 @@ void WinUsbDeviceManager::run() { for (auto iterator = devicePathWinUsbDeviceMap.begin(); iterator != devicePathWinUsbDeviceMap.end(); ) { auto devicePath = iterator->first; + #ifdef UNICODE + auto identifier = utf8_encode(devicePath); + #else + auto identifier = devicePath; + #endif // UNICODE auto winUsbDeviceThread = iterator->second.first; if (devicePaths.find(devicePath) == devicePaths.end()) { + this->logger->info("Requesting interruption of thread handling {}", identifier); winUsbDeviceThread->requestInterruption(); + this->logger->info("Signalling thread handling {} to terminate", identifier); winUsbDeviceThread->terminate(); - winUsbDeviceThread->wait(); + this->logger->info("Waiting for thread hanlding {} to terminate", identifier); + winUsbDeviceThread->wait(); iterator = devicePathWinUsbDeviceMap.erase(iterator); } else ++iterator; @@ -55,6 +63,7 @@ void WinUsbDeviceManager::run() { QThread::currentThread()->eventDispatcher()->processEvents(QEventLoop::AllEvents); if (QThread::currentThread()->isInterruptionRequested()) loop = false; else { + this->logger->info("Sleeping for 1000 milliseconds"); emit winUsbDeviceManagerSleeping(); QThread::msleep(1000); } @@ -62,9 +71,21 @@ void WinUsbDeviceManager::run() { } this->logger->info("Completed scan loop for {}", this->identifier); emit winUsbDeviceManagerTerminating(); + // TODO: Duplicate code, we should remove it for (auto tuple : devicePathWinUsbDeviceMap) { - delete tuple.second.second; - delete tuple.second.first; + auto devicePath = tuple.first; + #ifdef UNICODE + auto identifier = utf8_encode(devicePath); + #else + auto identifier = devicePath; + #endif // UNICODE + auto winUsbDeviceThread = tuple.second.first; + this->logger->info("Requesting interruption of thread handling {}", identifier); + winUsbDeviceThread->requestInterruption(); + this->logger->info("Signalling thread handling {} to terminate", identifier); + winUsbDeviceThread->terminate(); + this->logger->info("Waiting for thread hanlding {} to terminate", identifier); + winUsbDeviceThread->wait(); } } From 7117fc52a576d2a8ad7d7cd766e72ccd2ca5952a Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Mon, 14 Oct 2019 22:52:49 +0200 Subject: [PATCH 19/50] #2: Added default value for parent parameter for WinUsbDevice --- XBOFS.win/include/XBOFS.win/WinUsbDevice.h | 8 +++----- XBOFS.win/src/WinUsbDevice.cpp | 4 ++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/XBOFS.win/include/XBOFS.win/WinUsbDevice.h b/XBOFS.win/include/XBOFS.win/WinUsbDevice.h index 8452367..943be7e 100644 --- a/XBOFS.win/include/XBOFS.win/WinUsbDevice.h +++ b/XBOFS.win/include/XBOFS.win/WinUsbDevice.h @@ -10,11 +10,9 @@ namespace XBOFSWin { { Q_OBJECT - public: - WinUsbDevice(tstring devicePath, std::string identifier, std::shared_ptr logger); - ~WinUsbDevice() {}; - - //DWORD run(void); + public: + WinUsbDevice(tstring devicePath, std::string identifier, std::shared_ptr logger, QObject* parent=nullptr); + ~WinUsbDevice() {}; public slots: void run(); diff --git a/XBOFS.win/src/WinUsbDevice.cpp b/XBOFS.win/src/WinUsbDevice.cpp index 3c2ffdf..62ab59b 100644 --- a/XBOFS.win/src/WinUsbDevice.cpp +++ b/XBOFS.win/src/WinUsbDevice.cpp @@ -8,8 +8,8 @@ using namespace XBOFSWin; /* Constructs the WinUsbDevice instance and starts its event loop in a separate thread */ -WinUsbDevice::WinUsbDevice(tstring devicePath, std::string identifier, std::shared_ptr logger) -: QObject(), devicePath(devicePath), identifier(identifier), logger(logger) +WinUsbDevice::WinUsbDevice(tstring devicePath, std::string identifier, std::shared_ptr logger, QObject* parent) +: QObject(parent), devicePath(devicePath), identifier(identifier), logger(logger) { } From 50bcaf1b64e0f6a12d099ce2fe81c6ed8f6aaa87 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Mon, 14 Oct 2019 22:53:28 +0200 Subject: [PATCH 20/50] #2: Added additional signals and default parent parameter to WinUsbDeviceManager --- XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h | 6 ++++-- XBOFS.win/src/WinUsbDeviceManager.cpp | 7 +++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h b/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h index c9dc515..44be609 100644 --- a/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h +++ b/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h @@ -13,14 +13,16 @@ namespace XBOFSWin { class WinUsbDeviceManager : public QObject { Q_OBJECT - public: - WinUsbDeviceManager(std::string identifier, std::shared_ptr logger); + public: + WinUsbDeviceManager(std::string identifier, std::shared_ptr logger, QObject* parent=nullptr); ~WinUsbDeviceManager() {}; public slots: void run(); signals: + void winUsbDeviceAdded(const QString &identifier, const XBOFSWin::WinUsbDevice &winUsbDevice); + void winUsbDeviceRemoved(const QString &identifier, const XBOFSWin::WinUsbDevice &winUsbDevice); void winUsbDeviceManagerScanning(); void winUsbDeviceManagerSleeping(); void winUsbDeviceManagerTerminating(); diff --git a/XBOFS.win/src/WinUsbDeviceManager.cpp b/XBOFS.win/src/WinUsbDeviceManager.cpp index 830dc54..9b70f8d 100644 --- a/XBOFS.win/src/WinUsbDeviceManager.cpp +++ b/XBOFS.win/src/WinUsbDeviceManager.cpp @@ -7,8 +7,8 @@ using namespace XBOFSWin; /* Constructs the WinUsbDeviceManager and starts its event loop in a separate thread */ -WinUsbDeviceManager::WinUsbDeviceManager(std::string identifier, std::shared_ptr logger) -: QObject(), identifier(identifier), logger(logger) +WinUsbDeviceManager::WinUsbDeviceManager(std::string identifier, std::shared_ptr logger, QObject* parent) +: QObject(parent), identifier(identifier), logger(logger) {} void WinUsbDeviceManager::run() { @@ -37,6 +37,7 @@ void WinUsbDeviceManager::run() { winUsbDevice->moveToThread(winUsbDeviceThread); devicePathWinUsbDeviceMap.insert({ devicePath, std::make_pair(winUsbDeviceThread, winUsbDevice) }); winUsbDeviceThread->start(); + emit winUsbDeviceAdded(QString::fromStdString(identifier), *winUsbDevice); } // Check for WinUsbDevices to remove for (auto iterator = devicePathWinUsbDeviceMap.begin(); iterator != devicePathWinUsbDeviceMap.end(); ) @@ -48,6 +49,7 @@ void WinUsbDeviceManager::run() { auto identifier = devicePath; #endif // UNICODE auto winUsbDeviceThread = iterator->second.first; + auto winUsbDevice = iterator->second.second; if (devicePaths.find(devicePath) == devicePaths.end()) { this->logger->info("Requesting interruption of thread handling {}", identifier); winUsbDeviceThread->requestInterruption(); @@ -55,6 +57,7 @@ void WinUsbDeviceManager::run() { winUsbDeviceThread->terminate(); this->logger->info("Waiting for thread hanlding {} to terminate", identifier); winUsbDeviceThread->wait(); + emit winUsbDeviceRemoved(QString::fromStdString(identifier), *winUsbDevice); iterator = devicePathWinUsbDeviceMap.erase(iterator); } else ++iterator; From 96c37c63981116ba990f92c247fdb4fe04a7f7dc Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Mon, 14 Oct 2019 22:54:25 +0200 Subject: [PATCH 21/50] #2: Begin integrating WinUsbDeviceManager and main window using signals and slots. Added clean termination handling --- XBOFS.win.qt5/XBOFSWinQT5GUI.cpp | 41 +++++++++++++++++++++++++++----- XBOFS.win.qt5/XBOFSWinQT5GUI.h | 8 +++++++ 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp index 0c593ab..240ef7b 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp @@ -9,18 +9,47 @@ XBOFSWinQT5GUI::XBOFSWinQT5GUI(QWidget *parent) : QMainWindow(parent) { - ui.setupUi(this); - DWORD threadId = GetCurrentThreadId(); + ui.setupUi(this); + // Configure logging auto sinks = std::vector(); auto rotatingFileSink = std::make_shared("xbofs.win.qt5.log", 1024 * 1024 * 10, 10); sinks.push_back(rotatingFileSink); - this->logger = XBOFSWin::setup_logger("WinUsbDeviceManager", "", sinks); - spdlog::flush_every(std::chrono::seconds(60)); + logger = XBOFSWin::setup_logger("WinUsbDeviceManager", "", sinks); spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%t] [%n] [%l] %v"); - auto winUsbDeviceManagerThread = new QThread(); - auto winUsbDeviceManager = new XBOFSWin::WinUsbDeviceManager("WinUsbDeviceManager", this->logger); + logger->info("Logging initialised"); + // Start WinUsbDeviceManager + winUsbDeviceManagerThread = new QThread(); + winUsbDeviceManager = new XBOFSWin::WinUsbDeviceManager("WinUsbDeviceManager", logger); connect(winUsbDeviceManagerThread, &QThread::finished, winUsbDeviceManager, &QObject::deleteLater); connect(winUsbDeviceManagerThread, &QThread::started, winUsbDeviceManager, &XBOFSWin::WinUsbDeviceManager::run); + connect(winUsbDeviceManager, &XBOFSWin::WinUsbDeviceManager::winUsbDeviceManagerScanning, this, &XBOFSWinQT5GUI::winUsbDeviceManagerScanning); + connect(winUsbDeviceManager, &XBOFSWin::WinUsbDeviceManager::winUsbDeviceAdded, this, &XBOFSWinQT5GUI::winUsbDeviceAdded); + connect(winUsbDeviceManager, &XBOFSWin::WinUsbDeviceManager::winUsbDeviceRemoved, this, &XBOFSWinQT5GUI::winUsbDeviceRemoved); + connect(this, &XBOFSWinQT5GUI::destroyed, this, &XBOFSWinQT5GUI::terminateWinUsbDeviceManager); winUsbDeviceManager->moveToThread(winUsbDeviceManagerThread); winUsbDeviceManagerThread->start(); } + +void XBOFSWinQT5GUI::winUsbDeviceAdded(const QString &identifier, const XBOFSWin::WinUsbDevice &winUsbDevice) { + // TODO: Connect signals + // TODO: Update UI +} + +void XBOFSWinQT5GUI::winUsbDeviceRemoved(const QString &identifierr, const XBOFSWin::WinUsbDevice &winUsbDevice) { + // TODO: Update UI +} + +void XBOFSWinQT5GUI::winUsbDeviceManagerScanning() { + ui.winUsbDeviceManagerStatus->setText(QString::fromUtf8("Scanning for supported controllers...")); +} + +void XBOFSWinQT5GUI::terminateWinUsbDeviceManager() { + auto identifier = "WinUsbDeviceManager"; + logger->info("Requesting interruption of thread handling {}", identifier); + winUsbDeviceManagerThread->requestInterruption(); + logger->info("Signalling thread handling {} to terminate", identifier); + winUsbDeviceManagerThread->terminate(); + this->logger->info("Waiting for thread hanlding {} to terminate", identifier); + winUsbDeviceManagerThread->wait(); + logger->flush(); +} \ No newline at end of file diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.h b/XBOFS.win.qt5/XBOFSWinQT5GUI.h index ab120c0..fb4565a 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.h +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.h @@ -15,7 +15,15 @@ class XBOFSWinQT5GUI : public QMainWindow public: XBOFSWinQT5GUI(QWidget *parent = Q_NULLPTR); +public slots: + void winUsbDeviceAdded(const QString &identifier, const XBOFSWin::WinUsbDevice &winUsbDevice); + void winUsbDeviceRemoved(const QString &identifier, const XBOFSWin::WinUsbDevice &winUsbDevice); + void winUsbDeviceManagerScanning(); + void terminateWinUsbDeviceManager(); + protected: Ui::XBOFSWinQT5GUIClass ui; std::shared_ptr logger; + QThread *winUsbDeviceManagerThread; + XBOFSWin::WinUsbDeviceManager *winUsbDeviceManager; }; From c94df815078ee510d0bee79ef988958e530a70ed Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Tue, 15 Oct 2019 21:43:56 +0200 Subject: [PATCH 22/50] #2: Many changes: * Replace references to Razer Atrox with XBO Arcade Stick * Replace tstring with std::wstring * Enable unicode for spdlog * Removed excessive usage of this-> * Tweaked, cleaned and simplified logging * WinUsbDevice signals include devicePath now --- XBOFS.win/XBOFS.win.vcxproj | 4 +- XBOFS.win/include/XBOFS.win/WinUsbDevice.h | 45 ++-- .../include/XBOFS.win/WinUsbDeviceManager.h | 9 +- XBOFS.win/include/XBOFS.win/device.h | 6 +- XBOFS.win/include/XBOFS.win/pch.h | 2 - XBOFS.win/include/XBOFS.win/utils.h | 4 +- XBOFS.win/src/WinUsbDevice.cpp | 197 +++++++++--------- XBOFS.win/src/WinUsbDeviceManager.cpp | 88 ++++---- XBOFS.win/src/utils.cpp | 14 +- 9 files changed, 171 insertions(+), 198 deletions(-) diff --git a/XBOFS.win/XBOFS.win.vcxproj b/XBOFS.win/XBOFS.win.vcxproj index ed0e228..1828818 100644 --- a/XBOFS.win/XBOFS.win.vcxproj +++ b/XBOFS.win/XBOFS.win.vcxproj @@ -80,7 +80,7 @@ NotUsing stdafx.h $(IntDir)$(TargetName).pch - XBOFSWIN_LIB;BUILD_STATIC;_DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) + XBOFSWIN_LIB;BUILD_STATIC;_DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;SPDLOG_WCHAR_TO_UTF8_SUPPORT;%(PreprocessorDefinitions) false stdcpp17 @@ -108,7 +108,7 @@ NotUsing stdafx.h $(IntDir)$(TargetName).pch - XBOFSWIN_LIB;BUILD_STATIC;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) + XBOFSWIN_LIB;BUILD_STATIC;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;SPDLOG_WCHAR_TO_UTF8_SUPPORT;%(PreprocessorDefinitions) false stdcpp17 diff --git a/XBOFS.win/include/XBOFS.win/WinUsbDevice.h b/XBOFS.win/include/XBOFS.win/WinUsbDevice.h index 943be7e..cd07369 100644 --- a/XBOFS.win/include/XBOFS.win/WinUsbDevice.h +++ b/XBOFS.win/include/XBOFS.win/WinUsbDevice.h @@ -11,35 +11,34 @@ namespace XBOFSWin { Q_OBJECT public: - WinUsbDevice(tstring devicePath, std::string identifier, std::shared_ptr logger, QObject* parent=nullptr); + WinUsbDevice(std::wstring devicePath, std::shared_ptr logger, QObject* parent=nullptr); ~WinUsbDevice() {}; public slots: void run(); signals: - void vigEmConnect(); - void vigEmConnected(); - void vigEmTargetAdd(); - void vigEmTargetAdded(); - void vigEmError(); - void winUsbDeviceOpen(); - void winUsbDeviceOpened(); - void winUsbDeviceInit(); - void winUsbDeviceInitComplete(); - void winUsbDeviceReadingInput(); - void winUsbDeviceTerminating(); - void winUsbDeviceError(); - - protected: - const std::string identifier; + void vigEmConnect(const std::wstring &devicePath); + void vigEmConnected(const std::wstring &devicePath); + void vigEmTargetAdd(const std::wstring &devicePath); + void vigEmTargetAdded(const std::wstring &devicePath); + void vigEmError(const std::wstring &devicePath); + void winUsbDeviceOpen(const std::wstring &devicePath); + void winUsbDeviceOpened(const std::wstring &devicePath); + void winUsbDeviceInit(const std::wstring &devicePath); + void winUsbDeviceInitComplete(const std::wstring &devicePath); + void winUsbDeviceReadingInput(const std::wstring &devicePath); + void winUsbDeviceTerminating(const std::wstring &devicePath); + void winUsbDeviceError(const std::wstring &devicePath); + + protected: const std::shared_ptr logger; - const tstring devicePath; + const std::wstring devicePath; bool deviceHandlesOpen = false; - UCHAR RAZER_ATROX_INIT[5] = { 0x05, 0x20, 0x08, 0x01, 0x05 }; - RAZER_ATROX_DATA_PACKET dataPacket = {}; - RAZER_ATROX_BUTTON_STATE buttonState = {}; + UCHAR XBO_ARCADE_STICK_INIT[5] = { 0x05, 0x20, 0x08, 0x01, 0x05 }; + XBO_ARCADE_STICK_DATA_PACKET dataPacket = {}; + XBO_ARCADE_STICK_BUTTON_STATE buttonState = {}; PVIGEM_CLIENT vigEmClient = NULL; PVIGEM_TARGET vigEmTarget = NULL; WINUSB_INTERFACE_HANDLE winUsbHandle; @@ -47,9 +46,9 @@ namespace XBOFSWin { bool openDevice(); bool closeDevice(); - bool initRazorAtrox(); - bool readInputFromRazerAtrox(); - RAZER_ATROX_PACKET_TYPES processInputFromRazerAtrox(); + bool initXBOArcadeStick(); + bool readInputFromXBOArcadeStick(); + XBO_ARCADE_STICK_PACKET_TYPES processInputFromXBOArcadeStick(); bool dispatchInputToVigEmController(); }; } diff --git a/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h b/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h index 44be609..1eba3b5 100644 --- a/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h +++ b/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h @@ -14,24 +14,23 @@ namespace XBOFSWin { { Q_OBJECT public: - WinUsbDeviceManager(std::string identifier, std::shared_ptr logger, QObject* parent=nullptr); + WinUsbDeviceManager(std::shared_ptr logger, QObject* parent=nullptr); ~WinUsbDeviceManager() {}; public slots: void run(); signals: - void winUsbDeviceAdded(const QString &identifier, const XBOFSWin::WinUsbDevice &winUsbDevice); - void winUsbDeviceRemoved(const QString &identifier, const XBOFSWin::WinUsbDevice &winUsbDevice); + void winUsbDeviceAdded(const std::wstring &devicePath, const XBOFSWin::WinUsbDevice &winUsbDevice); + void winUsbDeviceRemoved(const std::wstring &devicePath, const XBOFSWin::WinUsbDevice &winUsbDevice); void winUsbDeviceManagerScanning(); void winUsbDeviceManagerSleeping(); void winUsbDeviceManagerTerminating(); protected: - const std::string identifier; const std::shared_ptr logger; - std::set retrieveDevicePaths(); + std::set retrieveDevicePaths(); }; } diff --git a/XBOFS.win/include/XBOFS.win/device.h b/XBOFS.win/include/XBOFS.win/device.h index 228cc10..14b9697 100644 --- a/XBOFS.win/include/XBOFS.win/device.h +++ b/XBOFS.win/include/XBOFS.win/device.h @@ -34,13 +34,13 @@ CloseDevice( _Inout_ PDEVICE_DATA DeviceData ); -struct RAZER_ATROX_DATA_PACKET +struct XBO_ARCADE_STICK_DATA_PACKET { UCHAR data[30]; ULONG transferred; }; -struct RAZER_ATROX_BUTTON_STATE +struct XBO_ARCADE_STICK_BUTTON_STATE { BOOL buttonX; BOOL buttonY; @@ -59,7 +59,7 @@ struct RAZER_ATROX_BUTTON_STATE BOOL stickRight; }; -enum RAZER_ATROX_PACKET_TYPES +enum XBO_ARCADE_STICK_PACKET_TYPES { UNKNOWN, DUMMY, diff --git a/XBOFS.win/include/XBOFS.win/pch.h b/XBOFS.win/include/XBOFS.win/pch.h index 3890435..711e3c6 100644 --- a/XBOFS.win/include/XBOFS.win/pch.h +++ b/XBOFS.win/include/XBOFS.win/pch.h @@ -15,5 +15,3 @@ #include "utils.h" #include "stdafx.h" -typedef std::basic_string tstring; - diff --git a/XBOFS.win/include/XBOFS.win/utils.h b/XBOFS.win/include/XBOFS.win/utils.h index e88bce9..5b7a610 100644 --- a/XBOFS.win/include/XBOFS.win/utils.h +++ b/XBOFS.win/include/XBOFS.win/utils.h @@ -4,6 +4,6 @@ namespace XBOFSWin { std::string utf8_encode(const std::wstring &wstr); - std::wstring utf8_decode(const std::string &str); - std::shared_ptr setup_logger(std::string loggerName, std::string logFile, std::vector sinks); + std::wstring utf8_decode(const std::string &str); + std::shared_ptr get_logger(std::string loggerName, std::vector sinks); } \ No newline at end of file diff --git a/XBOFS.win/src/WinUsbDevice.cpp b/XBOFS.win/src/WinUsbDevice.cpp index 62ab59b..193ae82 100644 --- a/XBOFS.win/src/WinUsbDevice.cpp +++ b/XBOFS.win/src/WinUsbDevice.cpp @@ -8,95 +8,91 @@ using namespace XBOFSWin; /* Constructs the WinUsbDevice instance and starts its event loop in a separate thread */ -WinUsbDevice::WinUsbDevice(tstring devicePath, std::string identifier, std::shared_ptr logger, QObject* parent) -: QObject(parent), devicePath(devicePath), identifier(identifier), logger(logger) +WinUsbDevice::WinUsbDevice(std::wstring devicePath, std::shared_ptr logger, QObject* parent) +: QObject(parent), devicePath(devicePath), logger(logger) { } void WinUsbDevice::run() { + logger->info("Entered run()"); bool loop = true; int failedReads = 0; - int failedWrites = 0; - MSG threadMessage; - // Allocate objects for VigEm - this->logger->info("Allocating VigEmClient for {}", this->identifier); - this->vigEmClient = vigem_alloc(); - this->logger->info("Allocating VigEmTarget for {}", this->identifier); - this->vigEmTarget = vigem_target_x360_alloc(); + int failedWrites = 0; + // Allocate objects for VigEm + vigEmClient = vigem_alloc(); + vigEmTarget = vigem_target_x360_alloc(); // Connect to VigEm - this->logger->info("Connecting VigEmClient for {}", this->identifier); - emit vigEmConnect(); - if (!VIGEM_SUCCESS(vigem_connect(this->vigEmClient))) { - emit vigEmError(); - this->logger->error("Unable to connect VigEmClient for {}", this->identifier); + logger->info("Connecting VigEmClient"); + emit vigEmConnect(devicePath); + if (!VIGEM_SUCCESS(vigem_connect(vigEmClient))) { + emit vigEmError(devicePath); + logger->error("Unable to connect VigEmClient"); loop = false; } - emit vigEmConnected(); + emit vigEmConnected(devicePath); // Add VigEm Target - this->logger->info("Adding VigEmTarget for {}", this->identifier); - emit vigEmTargetAdd(); - if (!VIGEM_SUCCESS(vigem_target_add(this->vigEmClient, this->vigEmTarget))) { - emit vigEmError(); - this->logger->error("Unable to add VigEmTarget for {}", this->identifier); + logger->info("Adding VigEmTarget"); + emit vigEmTargetAdd(devicePath); + if (!VIGEM_SUCCESS(vigem_target_add(vigEmClient, vigEmTarget))) { + emit vigEmError(devicePath); + logger->error("Unable to add VigEmTarget"); loop = false; } - emit vigEmTargetAdded(); + emit vigEmTargetAdded(devicePath); // Loop reading input, processing it and dispatching it - this->logger->info("Starting Read-Process-Dispatch loop for {}", this->identifier); + logger->info("Starting Read-Process-Dispatch loop"); while (loop && !QThread::currentThread()->isInterruptionRequested()) { // Open WinUsbDevice - emit winUsbDeviceOpen(); - if (!this->openDevice()) { - emit winUsbDeviceError(); - this->logger->error("Unable to open WinUSB device for {}", this->identifier); + emit winUsbDeviceOpen(devicePath); + if (!openDevice()) { + emit winUsbDeviceError(devicePath); + logger->error("Unable to open WinUSB device"); continue; } - emit winUsbDeviceOpened(); + emit winUsbDeviceOpened(devicePath); // Init WinUsbDevice - emit winUsbDeviceInit(); - if (!this->initRazorAtrox()) { - emit winUsbDeviceError(); - this->logger->error("Unable to init Razer Atrox for {}", this->identifier); + emit winUsbDeviceInit(devicePath); + if (!initXBOArcadeStick()) { + emit winUsbDeviceError(devicePath); + logger->error("Unable to init XBO Arcade Stick"); // TODO: Provide more details on stick vendor&product continue; } - emit winUsbDeviceInitComplete(); + emit winUsbDeviceInitComplete(devicePath); // Read input - emit winUsbDeviceReadingInput(); - this->logger->info("Reading input from Razer Atrox for {}", this->identifier); + emit winUsbDeviceReadingInput(devicePath); + logger->info("Reading input from XBO Arcade Stick"); // TODO: Provide more details on stick vendor&product int currentFailedReads = 0; while (loop && currentFailedReads < 5 && !QThread::currentThread()->isInterruptionRequested()) { - if (!this->readInputFromRazerAtrox()) { - this->logger->warn("Failed to read input from Razer Atrox for {}", this->identifier); + if (!readInputFromXBOArcadeStick()) { + logger->warn("Failed to read input from XBO Arcade Stick"); // TODO: Provide more details on stick vendor&product currentFailedReads += 1; continue; } - this->processInputFromRazerAtrox(); - if (!this->dispatchInputToVigEmController()) failedWrites += 1; + processInputFromXBOArcadeStick(); + if (!dispatchInputToVigEmController()) failedWrites += 1; } if (currentFailedReads >= 5) { - emit winUsbDeviceError(); - this->logger->warn("Failed to read input from Razer Atrox 5 or more times for {}", this->identifier); + emit winUsbDeviceError(devicePath); + logger->warn("Failed to read input from XBO Arcade Stick 5 or more times for {}"); } failedReads += currentFailedReads; currentFailedReads = 0; QThread::currentThread()->eventDispatcher()->processEvents(QEventLoop::AllEvents); } - emit winUsbDeviceTerminating(); - this->logger->info("Completed Read-Process-Dispatch loop for {}", this->identifier); - this->logger->info("There were {} failed reads for {}", failedReads, this->identifier); - this->logger->info("There were {} failed writes for {}", failedWrites, this->identifier); - this->logger->info("Closing WinUSB device for {}", this->identifier); - this->closeDevice(); - this->logger->info("Removing VigEmTarget for {}", this->identifier); - vigem_target_remove(this->vigEmClient, this->vigEmTarget); - this->logger->info("Disconnecting VigEmClient for {}", this->identifier); - vigem_disconnect(vigEmClient); - this->logger->info("Free VigEmTarget for {}", this->identifier); - vigem_target_free(this->vigEmTarget); - this->logger->info("Free VigEmClient for {}", this->identifier); - vigem_free(this->vigEmClient); - this->logger->info("Completed thread for {}", this->identifier); + emit winUsbDeviceTerminating(devicePath); + logger->info("Completed Read-Process-Dispatch loop"); + logger->info("There were {} failed reads", failedReads); + logger->info("There were {} failed writes", failedWrites); + logger->info("Closing WinUSB device"); + closeDevice(); + logger->info("Removing VigEmTarget"); + vigem_target_remove(vigEmClient, vigEmTarget); + logger->info("Disconnecting VigEmClient"); + vigem_disconnect(vigEmClient); + vigem_target_free(vigEmTarget); + vigem_free(vigEmClient); + logger->info("Completed run()"); } /* @@ -105,43 +101,44 @@ Attempt to open and initialize the WinUsb device bool WinUsbDevice::openDevice() { HRESULT hr = S_OK; BOOL bResult; - this->deviceHandlesOpen = false; - this->logger->info("Opening WinUSB device for {}", this->identifier); + deviceHandlesOpen = false; + logger->info("Opening WinUSB device"); // Attempt to open device handle - this->deviceHandle = CreateFile(this->devicePath.c_str(), + deviceHandle = CreateFile(devicePath.c_str(), GENERIC_WRITE | GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); - if (INVALID_HANDLE_VALUE == this->deviceHandle) { + if (INVALID_HANDLE_VALUE == deviceHandle) { hr = HRESULT_FROM_WIN32(GetLastError()); - this->logger->error("Failed to open device handle for {} due to {}", this->identifier, hr); + logger->error("Failed to open device handle due to {}", hr); return false; } // Initialize WinUsb handle - bResult = WinUsb_Initialize(this->deviceHandle, &this->winUsbHandle); + bResult = WinUsb_Initialize(deviceHandle, &winUsbHandle); if (FALSE == bResult) { hr = HRESULT_FROM_WIN32(GetLastError()); - CloseHandle(this->deviceHandle); - this->logger->error("Failed to initiallize WinUSB handle for {} due to {}", this->identifier, hr); + CloseHandle(deviceHandle); + logger->error("Failed to initiallize WinUSB handle due to {}", hr); return false; } - this->deviceHandlesOpen = true; + deviceHandlesOpen = true; // Make GetDescriptor call to device in order to ensure communications with it work BOOL winUsbGetDescriptorResult; USB_DEVICE_DESCRIPTOR winUsbDeviceDescriptor; ULONG bytesReceived; winUsbGetDescriptorResult = WinUsb_GetDescriptor( - this->winUsbHandle, USB_DEVICE_DESCRIPTOR_TYPE, 0, 0, (PBYTE)&winUsbDeviceDescriptor, sizeof(winUsbDeviceDescriptor), &bytesReceived + winUsbHandle, USB_DEVICE_DESCRIPTOR_TYPE, 0, 0, (PBYTE)&winUsbDeviceDescriptor, sizeof(winUsbDeviceDescriptor), &bytesReceived ); if (winUsbGetDescriptorResult == FALSE || bytesReceived != sizeof(winUsbDeviceDescriptor)) { - this->logger->error("Failed to read USB descriptor for {}", this->identifier); - this->closeDevice(); + logger->error("Failed to read USB descriptor"); + closeDevice(); return false; - } - this->logger->info("Opened WinUSB device for {}", this->identifier); + } + // TODO: Emit USB descriptor result + logger->info("Opened WinUSB device"); return true; } @@ -149,19 +146,19 @@ bool WinUsbDevice::openDevice() { Attempt to close the WinUsb device */ bool WinUsbDevice::closeDevice() { - if (!this->deviceHandlesOpen) return false; - WinUsb_Free(this->winUsbHandle); - CloseHandle(this->deviceHandle); - this->deviceHandlesOpen = false; + if (!deviceHandlesOpen) return false; + WinUsb_Free(winUsbHandle); + CloseHandle(deviceHandle); + deviceHandlesOpen = false; return true; } /* -Process data read from Razer Atrox +Process data read from XBO Arcade Stick */ -RAZER_ATROX_PACKET_TYPES WinUsbDevice::processInputFromRazerAtrox() { - if (this->dataPacket.transferred == 0) return UNKNOWN; - switch (this->dataPacket.data[0]) { +XBO_ARCADE_STICK_PACKET_TYPES WinUsbDevice::processInputFromXBOArcadeStick() { + if (dataPacket.transferred == 0) return UNKNOWN; + switch (dataPacket.data[0]) { case 0x01: // Dummy packet? return DUMMY; case 0x03: // Heartbeat packet? @@ -192,20 +189,20 @@ RAZER_ATROX_PACKET_TYPES WinUsbDevice::processInputFromRazerAtrox() { /* Blocking data read from end-point 0x81 */ -bool WinUsbDevice::readInputFromRazerAtrox() +bool WinUsbDevice::readInputFromXBOArcadeStick() { - if (this->winUsbHandle == INVALID_HANDLE_VALUE) return false; - return WinUsb_ReadPipe(this->winUsbHandle, 0x81, this->dataPacket.data, 30, &this->dataPacket.transferred, NULL); + if (winUsbHandle == INVALID_HANDLE_VALUE) return false; + return WinUsb_ReadPipe(winUsbHandle, 0x81, dataPacket.data, 30, &dataPacket.transferred, NULL); } /* Sent the INIT packet to the Razor Atrox */ -bool WinUsbDevice::initRazorAtrox() { - this->logger->info("Init Razer Atrox"); - if (this->winUsbHandle == INVALID_HANDLE_VALUE) return false; +bool WinUsbDevice::initXBOArcadeStick() { + logger->info("Init XBO Arcade Stick"); + if (winUsbHandle == INVALID_HANDLE_VALUE) return false; ULONG cbSent = 0; - return WinUsb_WritePipe(this->winUsbHandle, 0x01, (PUCHAR)this->RAZER_ATROX_INIT, 5, &cbSent, 0); + return WinUsb_WritePipe(winUsbHandle, 0x01, (PUCHAR)XBO_ARCADE_STICK_INIT, 5, &cbSent, 0); } /* @@ -213,21 +210,21 @@ Dispatch data to the VigEm XB360 controller */ bool WinUsbDevice::dispatchInputToVigEmController() { XUSB_REPORT controllerData{}; - if (this->buttonState.buttonGuide) controllerData.wButtons |= XUSB_GAMEPAD_GUIDE; - if (this->buttonState.buttonMenu) controllerData.wButtons |= XUSB_GAMEPAD_START; - if (this->buttonState.buttonView) controllerData.wButtons |= XUSB_GAMEPAD_BACK; - if (this->buttonState.buttonA) controllerData.wButtons |= XUSB_GAMEPAD_A; - if (this->buttonState.buttonB) controllerData.wButtons |= XUSB_GAMEPAD_B; - if (this->buttonState.buttonX) controllerData.wButtons |= XUSB_GAMEPAD_X; - if (this->buttonState.buttonY) controllerData.wButtons |= XUSB_GAMEPAD_Y; - if (this->buttonState.leftButton) controllerData.wButtons |= XUSB_GAMEPAD_LEFT_SHOULDER; - if (this->buttonState.rightButton) controllerData.wButtons |= XUSB_GAMEPAD_RIGHT_SHOULDER; - if (this->buttonState.stickUp) controllerData.wButtons |= XUSB_GAMEPAD_DPAD_UP; - if (this->buttonState.stickDown) controllerData.wButtons |= XUSB_GAMEPAD_DPAD_DOWN; - if (this->buttonState.stickLeft) controllerData.wButtons |= XUSB_GAMEPAD_DPAD_LEFT; - if (this->buttonState.stickRight) controllerData.wButtons |= XUSB_GAMEPAD_DPAD_RIGHT; - if (this->buttonState.leftTrigger) controllerData.bLeftTrigger = 0xff; - if (this->buttonState.rightTrigger) controllerData.bRightTrigger = 0xff; - const auto controllerUpdateResult = vigem_target_x360_update(this->vigEmClient, this->vigEmTarget, controllerData); + if (buttonState.buttonGuide) controllerData.wButtons |= XUSB_GAMEPAD_GUIDE; + if (buttonState.buttonMenu) controllerData.wButtons |= XUSB_GAMEPAD_START; + if (buttonState.buttonView) controllerData.wButtons |= XUSB_GAMEPAD_BACK; + if (buttonState.buttonA) controllerData.wButtons |= XUSB_GAMEPAD_A; + if (buttonState.buttonB) controllerData.wButtons |= XUSB_GAMEPAD_B; + if (buttonState.buttonX) controllerData.wButtons |= XUSB_GAMEPAD_X; + if (buttonState.buttonY) controllerData.wButtons |= XUSB_GAMEPAD_Y; + if (buttonState.leftButton) controllerData.wButtons |= XUSB_GAMEPAD_LEFT_SHOULDER; + if (buttonState.rightButton) controllerData.wButtons |= XUSB_GAMEPAD_RIGHT_SHOULDER; + if (buttonState.stickUp) controllerData.wButtons |= XUSB_GAMEPAD_DPAD_UP; + if (buttonState.stickDown) controllerData.wButtons |= XUSB_GAMEPAD_DPAD_DOWN; + if (buttonState.stickLeft) controllerData.wButtons |= XUSB_GAMEPAD_DPAD_LEFT; + if (buttonState.stickRight) controllerData.wButtons |= XUSB_GAMEPAD_DPAD_RIGHT; + if (buttonState.leftTrigger) controllerData.bLeftTrigger = 0xff; + if (buttonState.rightTrigger) controllerData.bRightTrigger = 0xff; + const auto controllerUpdateResult = vigem_target_x360_update(vigEmClient, vigEmTarget, controllerData); return VIGEM_SUCCESS(controllerUpdateResult); } diff --git a/XBOFS.win/src/WinUsbDeviceManager.cpp b/XBOFS.win/src/WinUsbDeviceManager.cpp index 9b70f8d..eccbb2b 100644 --- a/XBOFS.win/src/WinUsbDeviceManager.cpp +++ b/XBOFS.win/src/WinUsbDeviceManager.cpp @@ -2,112 +2,100 @@ #include "XBOFS.win\utils.h"; #include #include +#include using namespace XBOFSWin; /* Constructs the WinUsbDeviceManager and starts its event loop in a separate thread */ -WinUsbDeviceManager::WinUsbDeviceManager(std::string identifier, std::shared_ptr logger, QObject* parent) -: QObject(parent), identifier(identifier), logger(logger) +WinUsbDeviceManager::WinUsbDeviceManager(std::shared_ptr logger, QObject* parent) +: QObject(parent), logger(logger) {} void WinUsbDeviceManager::run() { - this->logger->info("Started thread for {}", this->identifier); - bool loop = true; - std::unordered_map> devicePathWinUsbDeviceMap; - std::set previousDevicePaths; - this->logger->info("Starting scan loop for {}", this->identifier); - while (loop && !QThread::currentThread()->isInterruptionRequested()) { + logger->info("Entered run()"); + std::unordered_map> devicePathWinUsbDeviceMap; + std::set previousDevicePaths; + logger->info("Starting scan loop"); + while (!QThread::currentThread()->isInterruptionRequested()) { emit winUsbDeviceManagerScanning(); auto devicePaths = this->retrieveDevicePaths(); // Check the updated set for new devicePaths for (auto devicePath : devicePaths) { - if (devicePathWinUsbDeviceMap.find(devicePath) != devicePathWinUsbDeviceMap.end()) continue; - this->logger->info("Adding WinUsbDevice at {}", utf8_encode(devicePath)); - #ifdef UNICODE - auto identifier = utf8_encode(devicePath); - #else - auto identifier = devicePath; - #endif // UNICODE - auto winUsbDeviceThread = new QThread(); - auto winUsbDeviceLogger = setup_logger("WinUsbDevice", "", this->logger->sinks()); - auto winUsbDevice = new WinUsbDevice(devicePath, identifier, winUsbDeviceLogger); + if (devicePathWinUsbDeviceMap.find(devicePath) != devicePathWinUsbDeviceMap.end()) continue; + this->logger->info(L"Adding WinUsbDevice at {}", devicePath); + auto winUsbDeviceThread = new QThread(); + auto winUsbDeviceLoggerName = utf8_encode(fmt::format(L"WinUsbDevice {}", devicePath)); + auto winUsbDeviceLogger = get_logger(winUsbDeviceLoggerName, logger->sinks()); + auto winUsbDevice = new WinUsbDevice(devicePath, winUsbDeviceLogger); connect(winUsbDeviceThread, &QThread::finished, winUsbDevice, &QObject::deleteLater); connect(winUsbDeviceThread, &QThread::started, winUsbDevice, &WinUsbDevice::run); winUsbDevice->moveToThread(winUsbDeviceThread); devicePathWinUsbDeviceMap.insert({ devicePath, std::make_pair(winUsbDeviceThread, winUsbDevice) }); + emit winUsbDeviceAdded(devicePath, *winUsbDevice); winUsbDeviceThread->start(); - emit winUsbDeviceAdded(QString::fromStdString(identifier), *winUsbDevice); + } // Check for WinUsbDevices to remove for (auto iterator = devicePathWinUsbDeviceMap.begin(); iterator != devicePathWinUsbDeviceMap.end(); ) { - auto devicePath = iterator->first; - #ifdef UNICODE - auto identifier = utf8_encode(devicePath); - #else - auto identifier = devicePath; - #endif // UNICODE + auto devicePath = iterator->first; auto winUsbDeviceThread = iterator->second.first; auto winUsbDevice = iterator->second.second; if (devicePaths.find(devicePath) == devicePaths.end()) { - this->logger->info("Requesting interruption of thread handling {}", identifier); + logger->info(L"Requesting interruption of thread handling {}", devicePath); winUsbDeviceThread->requestInterruption(); - this->logger->info("Signalling thread handling {} to terminate", identifier); + logger->info(L"Signalling thread handling {} to terminate", devicePath); winUsbDeviceThread->terminate(); - this->logger->info("Waiting for thread hanlding {} to terminate", identifier); + logger->info(L"Waiting for thread hanlding {} to terminate", devicePath); winUsbDeviceThread->wait(); - emit winUsbDeviceRemoved(QString::fromStdString(identifier), *winUsbDevice); + emit winUsbDeviceRemoved(devicePath, *winUsbDevice); iterator = devicePathWinUsbDeviceMap.erase(iterator); } else ++iterator; } // Processes messages in thread message queue QThread::currentThread()->eventDispatcher()->processEvents(QEventLoop::AllEvents); - if (QThread::currentThread()->isInterruptionRequested()) loop = false; - else { - this->logger->info("Sleeping for 1000 milliseconds"); + if (!QThread::currentThread()->isInterruptionRequested()) + { + logger->info("Sleeping for 1000 milliseconds"); emit winUsbDeviceManagerSleeping(); QThread::msleep(1000); } previousDevicePaths = devicePaths; } - this->logger->info("Completed scan loop for {}", this->identifier); + logger->info("Completed scan loop"); emit winUsbDeviceManagerTerminating(); // TODO: Duplicate code, we should remove it for (auto tuple : devicePathWinUsbDeviceMap) { - auto devicePath = tuple.first; - #ifdef UNICODE - auto identifier = utf8_encode(devicePath); - #else - auto identifier = devicePath; - #endif // UNICODE + auto devicePath = tuple.first; auto winUsbDeviceThread = tuple.second.first; - this->logger->info("Requesting interruption of thread handling {}", identifier); + logger->info(L"Requesting interruption of thread handling {}", devicePath); winUsbDeviceThread->requestInterruption(); - this->logger->info("Signalling thread handling {} to terminate", identifier); + logger->info(L"Signalling thread handling {} to terminate", devicePath); winUsbDeviceThread->terminate(); - this->logger->info("Waiting for thread hanlding {} to terminate", identifier); + logger->info(L"Waiting for thread hanlding {} to terminate", devicePath); winUsbDeviceThread->wait(); } + logger->info("Completed run()"); } /* Retrieve a vector of TCHAR* representing device paths that the device manager will work with */ -std::set WinUsbDeviceManager::retrieveDevicePaths() { +std::set WinUsbDeviceManager::retrieveDevicePaths() { CONFIGRET configurationManagerResult = CR_SUCCESS; HRESULT resultHandle = S_OK; PTSTR deviceInterfaceList = NULL; ULONG deviceInterfaceListSize = 0; - std::set newDevicePaths; + std::set newDevicePaths; // // Enumerate all devices exposing the interface. Do this in a loop // in case a new interface is discovered while this code is executing, // causing CM_Get_Device_Interface_List to return CR_BUFFER_SMALL. // - this->logger->debug("Retrieving device interface paths"); + logger->debug("Retrieving device interface paths"); do { configurationManagerResult = CM_Get_Device_Interface_List_Size(&deviceInterfaceListSize, (LPGUID)&GUID_DEVINTERFACE_XBOFS_WIN_CONTROLLER, @@ -119,7 +107,7 @@ std::set WinUsbDeviceManager::retrieveDevicePaths() { break; } - this->logger->debug("Device interface list size in bytes: {}", deviceInterfaceListSize * sizeof(TCHAR)); + logger->debug("Device interface list size in bytes: {}", deviceInterfaceListSize * sizeof(TCHAR)); deviceInterfaceList = (PTSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, @@ -144,22 +132,22 @@ std::set WinUsbDeviceManager::retrieveDevicePaths() { } while (configurationManagerResult == CR_BUFFER_SMALL); // Handle errors if (resultHandle != S_OK || deviceInterfaceList == TEXT('\0')) { - // TODO: Log error + // TODO: Log error? } else { auto deviceInterfaceListMarker = deviceInterfaceList; auto position = 0; while (position < deviceInterfaceListSize) { - auto devicePath = tstring(deviceInterfaceListMarker); + auto devicePath = std::wstring(deviceInterfaceListMarker); auto devicePathSize = devicePath.size(); if (!devicePathSize) break; newDevicePaths.insert(devicePath); deviceInterfaceListMarker += devicePathSize + 1; position += devicePathSize + 1; - this->logger->debug("Device interface path detected: {}", utf8_encode(devicePath)); + logger->debug(L"Device interface path detected: {}", devicePath); } deviceInterfaceListMarker = NULL; - this->logger->debug("{} device interfaces detected", newDevicePaths.size()); + logger->debug("{} device interfaces detected", newDevicePaths.size()); } HeapFree(GetProcessHeap(), 0, deviceInterfaceList); return newDevicePaths; diff --git a/XBOFS.win/src/utils.cpp b/XBOFS.win/src/utils.cpp index cc56ead..cb640de 100644 --- a/XBOFS.win/src/utils.cpp +++ b/XBOFS.win/src/utils.cpp @@ -24,21 +24,13 @@ std::wstring XBOFSWin::utf8_decode(const std::string &str) return wstrTo; } -std::shared_ptr XBOFSWin::setup_logger(std::string loggerName, std::string logFile, std::vector sinks) +std::shared_ptr XBOFSWin::get_logger(std::string loggerName, std::vector sinks) { auto logger = spdlog::get(loggerName); if (!logger) { - if (sinks.size() > 0) - { - logger = std::make_shared(loggerName, std::begin(sinks), std::end(sinks)); - spdlog::register_logger(logger); - } - else - { - logger = spdlog::basic_logger_mt(loggerName, logFile); - } + logger = std::make_shared(loggerName, std::begin(sinks), std::end(sinks)); + spdlog::register_logger(logger); } - return logger; } \ No newline at end of file From 70aee7b99952a2ec79d2df110666b4f1bb7e4636 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Tue, 15 Oct 2019 21:58:47 +0200 Subject: [PATCH 23/50] #2: Updated XBOFS.win.qt5 to reflect changes in XBOFS.win --- XBOFS.win.qt5/XBOFS.win.qt5.vcxproj | 8 ++++---- XBOFS.win.qt5/XBOFSWinQT5GUI.cpp | 19 +++++++++---------- XBOFS.win.qt5/XBOFSWinQT5GUI.h | 4 ++-- XBOFS.win.sln | 14 -------------- 4 files changed, 15 insertions(+), 30 deletions(-) diff --git a/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj b/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj index 686da26..847ad27 100644 --- a/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj +++ b/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj @@ -56,7 +56,7 @@ true - UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DLL;QT_CORE_LIB;QT_GUI_LIB;QT_WIDGETS_LIB;%(PreprocessorDefinitions) + UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DLL;QT_CORE_LIB;QT_GUI_LIB;QT_WIDGETS_LIB;SPDLOG_WCHAR_TO_UTF8_SUPPORT;%(PreprocessorDefinitions) .\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtWidgets;%(AdditionalIncludeDirectories) Disabled ProgramDatabase @@ -76,7 +76,7 @@ .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp Moc'ing %(Identity)... .\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtWidgets;%(AdditionalIncludeDirectories) - UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DLL;QT_CORE_LIB;QT_GUI_LIB;QT_WIDGETS_LIB;%(PreprocessorDefinitions) + UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DLL;QT_CORE_LIB;QT_GUI_LIB;QT_WIDGETS_LIB;SPDLOG_WCHAR_TO_UTF8_SUPPORT;%(PreprocessorDefinitions) Uic'ing %(Identity)... @@ -90,7 +90,7 @@ true - UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DLL;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QT_GUI_LIB;QT_WIDGETS_LIB;%(PreprocessorDefinitions) + UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DLL;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QT_GUI_LIB;QT_WIDGETS_LIB;SPDLOG_WCHAR_TO_UTF8_SUPPORT;%(PreprocessorDefinitions) .\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtWidgets;%(AdditionalIncludeDirectories) MultiThreaded @@ -109,7 +109,7 @@ .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp Moc'ing %(Identity)... .\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtWidgets;%(AdditionalIncludeDirectories) - UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DLL;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QT_GUI_LIB;QT_WIDGETS_LIB;%(PreprocessorDefinitions) + UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DLL;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QT_GUI_LIB;QT_WIDGETS_LIB;SPDLOG_WCHAR_TO_UTF8_SUPPORT;%(PreprocessorDefinitions) Uic'ing %(Identity)... diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp index 240ef7b..ae99d32 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp @@ -14,12 +14,12 @@ XBOFSWinQT5GUI::XBOFSWinQT5GUI(QWidget *parent) auto sinks = std::vector(); auto rotatingFileSink = std::make_shared("xbofs.win.qt5.log", 1024 * 1024 * 10, 10); sinks.push_back(rotatingFileSink); - logger = XBOFSWin::setup_logger("WinUsbDeviceManager", "", sinks); + logger = XBOFSWin::get_logger("XBOFSWin QT5 GUI", sinks); spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%t] [%n] [%l] %v"); logger->info("Logging initialised"); - // Start WinUsbDeviceManager + // Start WinUsbDeviceManager winUsbDeviceManagerThread = new QThread(); - winUsbDeviceManager = new XBOFSWin::WinUsbDeviceManager("WinUsbDeviceManager", logger); + winUsbDeviceManager = new XBOFSWin::WinUsbDeviceManager(XBOFSWin::get_logger("WinUsbDeviceManager", sinks)); connect(winUsbDeviceManagerThread, &QThread::finished, winUsbDeviceManager, &QObject::deleteLater); connect(winUsbDeviceManagerThread, &QThread::started, winUsbDeviceManager, &XBOFSWin::WinUsbDeviceManager::run); connect(winUsbDeviceManager, &XBOFSWin::WinUsbDeviceManager::winUsbDeviceManagerScanning, this, &XBOFSWinQT5GUI::winUsbDeviceManagerScanning); @@ -30,12 +30,12 @@ XBOFSWinQT5GUI::XBOFSWinQT5GUI(QWidget *parent) winUsbDeviceManagerThread->start(); } -void XBOFSWinQT5GUI::winUsbDeviceAdded(const QString &identifier, const XBOFSWin::WinUsbDevice &winUsbDevice) { +void XBOFSWinQT5GUI::winUsbDeviceAdded(const std::wstring &identifier, const XBOFSWin::WinUsbDevice &winUsbDevice) { // TODO: Connect signals // TODO: Update UI } -void XBOFSWinQT5GUI::winUsbDeviceRemoved(const QString &identifierr, const XBOFSWin::WinUsbDevice &winUsbDevice) { +void XBOFSWinQT5GUI::winUsbDeviceRemoved(const std::wstring &identifierr, const XBOFSWin::WinUsbDevice &winUsbDevice) { // TODO: Update UI } @@ -43,13 +43,12 @@ void XBOFSWinQT5GUI::winUsbDeviceManagerScanning() { ui.winUsbDeviceManagerStatus->setText(QString::fromUtf8("Scanning for supported controllers...")); } -void XBOFSWinQT5GUI::terminateWinUsbDeviceManager() { - auto identifier = "WinUsbDeviceManager"; - logger->info("Requesting interruption of thread handling {}", identifier); +void XBOFSWinQT5GUI::terminateWinUsbDeviceManager() { + logger->info("Requesting interruption of thread handling WinUsbDeviceManager"); winUsbDeviceManagerThread->requestInterruption(); - logger->info("Signalling thread handling {} to terminate", identifier); + logger->info("Signalling thread handling WinUsbDeviceManager to terminate"); winUsbDeviceManagerThread->terminate(); - this->logger->info("Waiting for thread hanlding {} to terminate", identifier); + logger->info("Waiting for thread hanlding WinUsbDeviceManager to terminate"); winUsbDeviceManagerThread->wait(); logger->flush(); } \ No newline at end of file diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.h b/XBOFS.win.qt5/XBOFSWinQT5GUI.h index fb4565a..b89934f 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.h +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.h @@ -16,8 +16,8 @@ class XBOFSWinQT5GUI : public QMainWindow XBOFSWinQT5GUI(QWidget *parent = Q_NULLPTR); public slots: - void winUsbDeviceAdded(const QString &identifier, const XBOFSWin::WinUsbDevice &winUsbDevice); - void winUsbDeviceRemoved(const QString &identifier, const XBOFSWin::WinUsbDevice &winUsbDevice); + void winUsbDeviceAdded(const std::wstring &devicePath, const XBOFSWin::WinUsbDevice &winUsbDevice); + void winUsbDeviceRemoved(const std::wstring &devicePath, const XBOFSWin::WinUsbDevice &winUsbDevice); void winUsbDeviceManagerScanning(); void terminateWinUsbDeviceManager(); diff --git a/XBOFS.win.sln b/XBOFS.win.sln index 72cc10b..ccb0455 100644 --- a/XBOFS.win.sln +++ b/XBOFS.win.sln @@ -3,8 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.28307.572 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "XBOFS.win.old", "XBOFS.win.old\XBOFS.win.old.vcxproj", "{F6104731-5815-4BBA-A558-E859DD039413}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ViGEmClient", "ViGEmClient\src\ViGEmClient.vcxproj", "{7DB06674-1F4F-464B-8E1C-172E9587F9DC}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1E0515A5-B0AA-4821-90DE-C764A2C35AF2}" @@ -53,18 +51,6 @@ Global Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F6104731-5815-4BBA-A558-E859DD039413}.Debug_DLL|x64.ActiveCfg = Debug_LIB|x64 - {F6104731-5815-4BBA-A558-E859DD039413}.Debug_DLL|x64.Build.0 = Debug_LIB|x64 - {F6104731-5815-4BBA-A558-E859DD039413}.Debug_LIB|x64.ActiveCfg = Debug_LIB|x64 - {F6104731-5815-4BBA-A558-E859DD039413}.Debug_LIB|x64.Build.0 = Debug_LIB|x64 - {F6104731-5815-4BBA-A558-E859DD039413}.Debug|x64.ActiveCfg = Debug_LIB|x64 - {F6104731-5815-4BBA-A558-E859DD039413}.Debug|x64.Build.0 = Debug_LIB|x64 - {F6104731-5815-4BBA-A558-E859DD039413}.Release_DLL|x64.ActiveCfg = Release_LIB|x64 - {F6104731-5815-4BBA-A558-E859DD039413}.Release_DLL|x64.Build.0 = Release_LIB|x64 - {F6104731-5815-4BBA-A558-E859DD039413}.Release_LIB|x64.ActiveCfg = Release_LIB|x64 - {F6104731-5815-4BBA-A558-E859DD039413}.Release_LIB|x64.Build.0 = Release_LIB|x64 - {F6104731-5815-4BBA-A558-E859DD039413}.Release|x64.ActiveCfg = Release_LIB|x64 - {F6104731-5815-4BBA-A558-E859DD039413}.Release|x64.Build.0 = Release_LIB|x64 {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug_DLL|x64.ActiveCfg = Debug_DLL|x64 {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug_DLL|x64.Build.0 = Debug_DLL|x64 {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug_LIB|x64.ActiveCfg = Debug_LIB|x64 From 65d59ebb27729753646bb1ea7e40ddbae8a5cde1 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Wed, 16 Oct 2019 15:56:05 +0200 Subject: [PATCH 24/50] #2: Removed XBOFS.win.old project --- XBOFS.win.old/XBOFS.win.old.vcxproj | 259 ------------------ XBOFS.win.old/XBOFS.win.old.vcxproj.filters | 61 ----- XBOFS.win.old/include/XBOFS.win/Thread.h | 50 ---- .../include/XBOFS.win/WinUsbDevice.h | 37 --- .../include/XBOFS.win/WinUsbDeviceManager.h | 25 -- XBOFS.win.old/include/XBOFS.win/device.h | 68 ----- XBOFS.win.old/include/XBOFS.win/pch.h | 19 -- XBOFS.win.old/include/XBOFS.win/utils.h | 9 - XBOFS.win.old/src/Thread.cpp | 63 ----- XBOFS.win.old/src/WinUsbDevice.cpp | 223 --------------- XBOFS.win.old/src/WinUsbDeviceManager.cpp | 125 --------- XBOFS.win.old/src/device.cpp | 235 ---------------- XBOFS.win.old/src/utils.cpp | 44 --- 13 files changed, 1218 deletions(-) delete mode 100644 XBOFS.win.old/XBOFS.win.old.vcxproj delete mode 100644 XBOFS.win.old/XBOFS.win.old.vcxproj.filters delete mode 100644 XBOFS.win.old/include/XBOFS.win/Thread.h delete mode 100644 XBOFS.win.old/include/XBOFS.win/WinUsbDevice.h delete mode 100644 XBOFS.win.old/include/XBOFS.win/WinUsbDeviceManager.h delete mode 100644 XBOFS.win.old/include/XBOFS.win/device.h delete mode 100644 XBOFS.win.old/include/XBOFS.win/pch.h delete mode 100644 XBOFS.win.old/include/XBOFS.win/utils.h delete mode 100644 XBOFS.win.old/src/Thread.cpp delete mode 100644 XBOFS.win.old/src/WinUsbDevice.cpp delete mode 100644 XBOFS.win.old/src/WinUsbDeviceManager.cpp delete mode 100644 XBOFS.win.old/src/device.cpp delete mode 100644 XBOFS.win.old/src/utils.cpp diff --git a/XBOFS.win.old/XBOFS.win.old.vcxproj b/XBOFS.win.old/XBOFS.win.old.vcxproj deleted file mode 100644 index 9fe16da..0000000 --- a/XBOFS.win.old/XBOFS.win.old.vcxproj +++ /dev/null @@ -1,259 +0,0 @@ - - - - - Debug_LIB - ARM - - - Debug_LIB - ARM64 - - - Debug_LIB - Win32 - - - Debug_LIB - x64 - - - Release_LIB - ARM - - - Release_LIB - ARM64 - - - Release_LIB - Win32 - - - Release_LIB - x64 - - - - - - - - {7db06674-1f4f-464b-8e1c-172e9587f9dc} - - - - - - - - - - - - - - - - - - - {F6104731-5815-4BBA-A558-E859DD039413} - {b287f7f7-abf3-440d-b608-cb36380f2981} - v4.5 - 12.0 - Debug - Win32 - XBOFS.win - $(LatestTargetPlatformVersion) - XBOFS.win.old - - - - Windows10 - true - WindowsApplicationForDrivers10.0 - Application - Universal - Unicode - - - Windows10 - false - WindowsApplicationForDrivers10.0 - Application - Universal - Unicode - - - Windows10 - true - WindowsApplicationForDrivers10.0 - StaticLibrary - Desktop - Unicode - - - Windows10 - false - WindowsApplicationForDrivers10.0 - StaticLibrary - Desktop - Unicode - - - Windows10 - true - WindowsApplicationForDrivers10.0 - Application - Universal - Unicode - - - Windows10 - false - WindowsApplicationForDrivers10.0 - Application - Universal - Unicode - - - Windows10 - true - WindowsApplicationForDrivers10.0 - Application - Universal - Unicode - - - Windows10 - false - WindowsApplicationForDrivers10.0 - Application - Universal - Unicode - - - - - - - - - - - DbgengRemoteDebugger - - - DbgengRemoteDebugger - - - DbgengRemoteDebugger - $(SolutionDir)ViGEmClient\include;$(SolutionDir)XBOFS.win\include;$(IncludePath) - $(SolutionDir)PDCurses\wincon;$(LibraryPath) - $(SolutionDir)lib\debug\$(PlatformShortName)\ - - - DbgengRemoteDebugger - $(SolutionDir)ViGEmClient\include;$(SolutionDir)XBOFS.win\include;$(IncludePath) - $(SolutionDir)PDCurses\wincon;$(LibraryPath) - $(SolutionDir)lib\release\$(PlatformShortName)\ - - - DbgengRemoteDebugger - - - DbgengRemoteDebugger - - - DbgengRemoteDebugger - - - DbgengRemoteDebugger - - - - _DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) - MultiThreadedDebugDLL - - - %(AdditionalDependencies);onecoreuap.lib;winusb.lib - - - - - WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) - - - %(AdditionalDependencies);onecoreuap.lib;winusb.lib - - - - - _DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) - MultiThreadedDebug - false - - - %(AdditionalDependencies);onecoreuap.lib;winusb.lib - false - - - cd $(SolutionDir) & call pre-build.cmd - Pre-build tasks - - - - - WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;BUILD_SHARED_LIBS;%(PreprocessorDefinitions) - false - stdcpp17 - MultiThreaded - - - %(AdditionalDependencies);onecoreuap.lib;winusb.lib - false - - - cd $(SolutionDir) & call pre-build.cmd - Pre-build tasks - - - - - _DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) - MultiThreadedDebugDLL - - - %(AdditionalDependencies);onecoreuap.lib;winusb.lib - - - - - WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) - - - %(AdditionalDependencies);onecoreuap.lib;winusb.lib - - - - - _DEBUG;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) - MultiThreadedDebugDLL - - - %(AdditionalDependencies);onecoreuap.lib;winusb.lib - - - - - WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;WINAPI_PARTITION_DESKTOP=1;WINAPI_PARTITION_SYSTEM=1;WINAPI_PARTITION_APP=1;WINAPI_PARTITION_PC_APP=1;%(PreprocessorDefinitions) - - - %(AdditionalDependencies);onecoreuap.lib;winusb.lib - - - - - - \ No newline at end of file diff --git a/XBOFS.win.old/XBOFS.win.old.vcxproj.filters b/XBOFS.win.old/XBOFS.win.old.vcxproj.filters deleted file mode 100644 index 5a53305..0000000 --- a/XBOFS.win.old/XBOFS.win.old.vcxproj.filters +++ /dev/null @@ -1,61 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {8E41214B-6785-4CFE-B992-037D68949A14} - inf;inv;inx;mof;mc; - - - - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - \ No newline at end of file diff --git a/XBOFS.win.old/include/XBOFS.win/Thread.h b/XBOFS.win.old/include/XBOFS.win/Thread.h deleted file mode 100644 index 05e5e33..0000000 --- a/XBOFS.win.old/include/XBOFS.win/Thread.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once -#include - -namespace XBOFSWin { - - enum THREAD_MESSAGES { - RAWUVEF_STOP = WM_USER + 0, - RAWUVEF_STOPPED = WM_USER + 1, - RAWUVEF_WIN_USB_DEVICE_MANAGER_STARTED = WM_USER + 2, - RAWUVEF_WIN_USB_DEVICE_MANAGER_SCANNING = WM_USER + 3, - RAWUVEF_WIN_USB_DEVICE_MANAGER_SLEEPING = WM_USER + 4, - RAWUVEF_WIN_USB_DEVICE_MANAGER_TERMINATING = WM_USER + 5, - RAWUVEF_WIN_USB_DEVICE_MANAGER_ERROR = WM_USER + 6, - RAWUVEF_WIN_USB_DEVICE_STARTED = WM_USER + 7, - RAWUVEF_WIN_USB_DEVICE_VIGEM_CONNECT = WM_USER + 8, - RAWUVEF_WIN_USB_DEVICE_VIGEM_TARGET_ADD = WM_USER + 9, - RAWUVEF_WIN_USB_DEVICE_OPEN = WM_USER + 10, - RAWUVEF_WIN_USB_DEVICE_INIT = WM_USER + 11, - RAWUVEF_WIN_USB_DEVICE_READ_INPUT = WM_USER + 12, - RAWUVEF_WIN_USB_DEVICE_TERMINATING = WM_USER + 13, - RAWUVEF_WIN_USB_DEVICE_ERROR = WM_USER + 14 - }; - - std::string threadMessageToString(THREAD_MESSAGES threadMessage); - - class Thread - { - public: - Thread() = delete; - Thread(std::string identifier, std::shared_ptr logger, DWORD parentThreadId, DWORD uiManagerThreadId); - ~Thread(); - - virtual DWORD run() = 0; - DWORD getThreadId(); - DWORD getParentThreadId(); - DWORD getUiManagerThreadId(); - - protected: - const std::string identifier; - const DWORD parentThreadId; - const DWORD uiManagerThreadId; - - std::shared_ptr logger; - DWORD threadId = 0; - HANDLE threadHandle = NULL; - - static DWORD startThread(LPVOID data); - BOOL notifyUIManager(UINT messageValue, LPARAM lParam); - }; -} diff --git a/XBOFS.win.old/include/XBOFS.win/WinUsbDevice.h b/XBOFS.win.old/include/XBOFS.win/WinUsbDevice.h deleted file mode 100644 index 38405e1..0000000 --- a/XBOFS.win.old/include/XBOFS.win/WinUsbDevice.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once -#include -#include -#include - -namespace XBOFSWin { - /* - */ - class WinUsbDevice : public Thread - { - public: - WinUsbDevice(tstring devicePath, std::string identifier, std::shared_ptr logger, DWORD parentThreadId, DWORD uiManagerThreadId); - ~WinUsbDevice() {}; - - DWORD run(void); - - protected: - const tstring devicePath; - - bool deviceHandlesOpen = false; - UCHAR RAZER_ATROX_INIT[5] = { 0x05, 0x20, 0x08, 0x01, 0x05 }; - RAZER_ATROX_DATA_PACKET dataPacket = {}; - RAZER_ATROX_BUTTON_STATE buttonState = {}; - PVIGEM_CLIENT vigEmClient = NULL; - PVIGEM_TARGET vigEmTarget = NULL; - WINUSB_INTERFACE_HANDLE winUsbHandle; - HANDLE deviceHandle; - - bool openDevice(); - bool closeDevice(); - bool initRazorAtrox(); - bool readInputFromRazerAtrox(); - RAZER_ATROX_PACKET_TYPES processInputFromRazerAtrox(); - bool dispatchInputToVigEmController(); - }; -} - diff --git a/XBOFS.win.old/include/XBOFS.win/WinUsbDeviceManager.h b/XBOFS.win.old/include/XBOFS.win/WinUsbDeviceManager.h deleted file mode 100644 index 4b12060..0000000 --- a/XBOFS.win.old/include/XBOFS.win/WinUsbDeviceManager.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once -#include -#include -#include -#include - -/* - -*/ -namespace XBOFSWin { - - class WinUsbDeviceManager : public Thread - { - public: - WinUsbDeviceManager(std::shared_ptr logger, DWORD parentThreadId, DWORD uiManagerThreadId); - ~WinUsbDeviceManager() {}; - - DWORD run(); - - protected: - std::set retrieveDevicePaths(); - - }; -} - diff --git a/XBOFS.win.old/include/XBOFS.win/device.h b/XBOFS.win.old/include/XBOFS.win/device.h deleted file mode 100644 index 508de82..0000000 --- a/XBOFS.win.old/include/XBOFS.win/device.h +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once -// -// Define below GUIDs -// -#include - -// -// Device Interface GUID. -// Used by all WinUsb devices that this application talks to. -// Must match "DeviceInterfaceGUIDs" registry value specified in the INF file. -// -// 5ACF052A-3BE5-46AE-905E-356BA17671BD -DEFINE_GUID(GUID_DEVINTERFACE_XBOFS_WIN_CONTROLLER, - 0x5ACF052A, 0x3BE5, 0x46AE, 0x90, 0x5E, 0x35, 0x6B, 0xA1, 0x76, 0x71, 0xBD); - -typedef struct _DEVICE_DATA { - - BOOL HandlesOpen; - WINUSB_INTERFACE_HANDLE WinusbHandle; - HANDLE DeviceHandle; - TCHAR DevicePath[MAX_PATH]; - -} DEVICE_DATA, *PDEVICE_DATA; - -HRESULT -OpenDevice( - _Out_ PDEVICE_DATA DeviceData, - _Out_opt_ PBOOL FailureDeviceNotFound - ); - -VOID -CloseDevice( - _Inout_ PDEVICE_DATA DeviceData - ); - -struct RAZER_ATROX_DATA_PACKET -{ - UCHAR data[30]; - ULONG transferred; -}; - -struct RAZER_ATROX_BUTTON_STATE -{ - BOOL buttonX; - BOOL buttonY; - BOOL buttonA; - BOOL buttonB; - BOOL rightButton; - BOOL leftButton; - BOOL rightTrigger; - BOOL leftTrigger; - BOOL buttonMenu; - BOOL buttonView; - BOOL buttonGuide; - BOOL stickUp; - BOOL stickLeft; - BOOL stickDown; - BOOL stickRight; -}; - -enum RAZER_ATROX_PACKET_TYPES -{ - UNKNOWN, - DUMMY, - HEARTBEAT, - GUIDE, - BUTTON_INPUT -}; diff --git a/XBOFS.win.old/include/XBOFS.win/pch.h b/XBOFS.win.old/include/XBOFS.win/pch.h deleted file mode 100644 index d3216dd..0000000 --- a/XBOFS.win.old/include/XBOFS.win/pch.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "device.h" -#include "utils.h" - - -typedef std::basic_string tstring; - diff --git a/XBOFS.win.old/include/XBOFS.win/utils.h b/XBOFS.win.old/include/XBOFS.win/utils.h deleted file mode 100644 index e88bce9..0000000 --- a/XBOFS.win.old/include/XBOFS.win/utils.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once -#include - -namespace XBOFSWin { - - std::string utf8_encode(const std::wstring &wstr); - std::wstring utf8_decode(const std::string &str); - std::shared_ptr setup_logger(std::string loggerName, std::string logFile, std::vector sinks); -} \ No newline at end of file diff --git a/XBOFS.win.old/src/Thread.cpp b/XBOFS.win.old/src/Thread.cpp deleted file mode 100644 index ce8fb99..0000000 --- a/XBOFS.win.old/src/Thread.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include "XBOFS.win\Thread.h" - -using namespace XBOFSWin; - -std::string threadMessageToString(THREAD_MESSAGES threadMessage) -{ - switch (threadMessage) { - case RAWUVEF_WIN_USB_DEVICE_MANAGER_STARTED: return "Started"; - case RAWUVEF_WIN_USB_DEVICE_MANAGER_SCANNING: - case RAWUVEF_WIN_USB_DEVICE_MANAGER_SLEEPING: return "Active"; - case RAWUVEF_WIN_USB_DEVICE_MANAGER_TERMINATING: return "Terminating..."; - case RAWUVEF_WIN_USB_DEVICE_MANAGER_ERROR: return "Error!"; - case RAWUVEF_WIN_USB_DEVICE_STARTED: return "Started"; - case RAWUVEF_WIN_USB_DEVICE_VIGEM_CONNECT: return "VigEmClient connect..."; - case RAWUVEF_WIN_USB_DEVICE_VIGEM_TARGET_ADD: return "VigEmClient target add..."; - case RAWUVEF_WIN_USB_DEVICE_OPEN: return "Device Open..."; - case RAWUVEF_WIN_USB_DEVICE_INIT: return "Device Init..."; - case RAWUVEF_WIN_USB_DEVICE_READ_INPUT: return "Reading input..."; - case RAWUVEF_WIN_USB_DEVICE_TERMINATING: return "Terminating..."; - case RAWUVEF_WIN_USB_DEVICE_ERROR: return "Error!"; - } - return "Unknown Thread Message"; -} - -Thread::Thread(std::string identifier, std::shared_ptr logger, DWORD parentThreadId, DWORD uiManagerThreadId) -: identifier(identifier), logger(logger), parentThreadId(parentThreadId), uiManagerThreadId(uiManagerThreadId) -{ - this->logger->info("Starting thread for {}", this->identifier); - this->threadHandle = CreateThread(NULL, 0, startThread, (LPVOID)this, 0, &this->threadId); -} - -Thread::~Thread() -{ - this->logger->info("Stopping thread for {}", this->identifier); - PostThreadMessage(this->threadId, RAWUVEF_STOP, NULL, NULL); - while (WaitForSingleObject(this->threadHandle, INFINITE) != WAIT_OBJECT_0); - CloseHandle(this->threadHandle); - this->notifyUIManager(RAWUVEF_STOPPED, NULL); - this->logger->info("Stopped thread for {}", this->identifier); -} - -DWORD Thread::getThreadId() { - return this->threadId; -} - -DWORD Thread::getParentThreadId() { - return this->parentThreadId; -} - -DWORD Thread::getUiManagerThreadId() { - return this->uiManagerThreadId; -} - -DWORD Thread::startThread(LPVOID data) { - Thread* thread = (Thread*)data; - MSG threadMessage; - PeekMessage(&threadMessage, NULL, WM_USER, WM_USER, PM_NOREMOVE); - return thread->run(); -} - -BOOL Thread::notifyUIManager(UINT messageValue, LPARAM lParam) { - return PostThreadMessage(this->uiManagerThreadId, messageValue, this->threadId, lParam); -} diff --git a/XBOFS.win.old/src/WinUsbDevice.cpp b/XBOFS.win.old/src/WinUsbDevice.cpp deleted file mode 100644 index 6049d5b..0000000 --- a/XBOFS.win.old/src/WinUsbDevice.cpp +++ /dev/null @@ -1,223 +0,0 @@ -#include "XBOFS.win\WinUsbDevice.h" - -using namespace XBOFSWin; -/* -Constructs the WinUsbDevice instance and starts its event loop in a separate thread -*/ -WinUsbDevice::WinUsbDevice(tstring devicePath, std::string identifier, std::shared_ptr logger, DWORD parentThreadId, DWORD uiManagerThreadId) -: Thread(identifier, logger, parentThreadId, uiManagerThreadId), devicePath(devicePath) -{ - -} - -DWORD WinUsbDevice::run() { - bool loop = true; - int failedReads = 0; - int failedWrites = 0; - MSG threadMessage; - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_STARTED, NULL); - this->logger->info("Started thread for {}", this->identifier); - this->logger->info("Allocating VigEmClient for {}", this->identifier); - this->vigEmClient = vigem_alloc(); - this->logger->info("Allocating VigEmTarget for {}", this->identifier); - this->vigEmTarget = vigem_target_x360_alloc(); - this->logger->info("Connecting VigEmClient for {}", this->identifier); - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_VIGEM_CONNECT, NULL); - if (!VIGEM_SUCCESS(vigem_connect(this->vigEmClient))) { - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_ERROR, NULL); - this->logger->error("Unable to connect VigEmClient for {}", this->identifier); - loop = false; - } - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_VIGEM_TARGET_ADD, NULL); - this->logger->info("Adding VigEmTarget for {}", this->identifier); - if (!VIGEM_SUCCESS(vigem_target_add(this->vigEmClient, this->vigEmTarget))) { - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_ERROR, NULL); - this->logger->error("Unable to add VigEmTarget for {}", this->identifier); - loop = false; - } - // Loop reading input, processing it and dispatching it - this->logger->info("Starting Read-Process-Dispatch loop for {}", this->identifier); - while (loop) { - if (PeekMessage(&threadMessage, NULL, WM_USER, WM_APP, PM_REMOVE) == TRUE && threadMessage.message == RAWUVEF_STOP) loop = false; - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_OPEN, NULL); - if (!this->openDevice()) { - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_ERROR, NULL); - this->logger->error("Unable to open WinUSB device for {}", this->identifier); - continue; - } - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_INIT, NULL); - if (!this->initRazorAtrox()) { - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_ERROR, NULL); - this->logger->error("Unable to init Razer Atrox for {}", this->identifier); - continue; - } - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_READ_INPUT, NULL); - this->logger->info("Reading input from Razer Atrox for {}", this->identifier); - int currentFailedReads = 0; - while (loop && currentFailedReads < 5) { - if (PeekMessage(&threadMessage, NULL, WM_USER, WM_APP, PM_REMOVE) == TRUE && threadMessage.message == RAWUVEF_STOP) loop = false; - if (!this->readInputFromRazerAtrox()) { - this->logger->warn("Failed to read input from Razer Atrox for {}", this->identifier); - currentFailedReads += 1; - continue; - } - this->processInputFromRazerAtrox(); - if (!this->dispatchInputToVigEmController()) failedWrites += 1; - } - if (currentFailedReads >= 5) { - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_ERROR, NULL); - this->logger->warn("Failed to read input from Razer Atrox 5 or more times for {}", this->identifier); - } - failedReads += currentFailedReads; - currentFailedReads = 0; - } - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_TERMINATING, NULL); - this->logger->info("Completed Read-Process-Dispatch loop for {}", this->identifier); - this->logger->info("There were {} failed reads for {}", failedReads, this->identifier); - this->logger->info("There were {} failed writes for {}", failedWrites, this->identifier); - this->logger->info("Closing WinUSB device for {}", this->identifier); - this->closeDevice(); - this->logger->info("Removing VigEmTarget for {}", this->identifier); - vigem_target_remove(this->vigEmClient, this->vigEmTarget); - this->logger->info("Disconnecting VigEmClient for {}", this->identifier); - vigem_disconnect(vigEmClient); - this->logger->info("Free VigEmTarget for {}", this->identifier); - vigem_target_free(this->vigEmTarget); - this->logger->info("Free VigEmClient for {}", this->identifier); - vigem_free(this->vigEmClient); - this->logger->info("Completed thread for {}", this->identifier); - return 0; -} - -/* -Attempt to open and initialize the WinUsb device -*/ -bool WinUsbDevice::openDevice() { - HRESULT hr = S_OK; - BOOL bResult; - this->deviceHandlesOpen = false; - this->logger->info("Opening WinUSB device for {}", this->identifier); - // Attempt to open device handle - this->deviceHandle = CreateFile(this->devicePath.c_str(), - GENERIC_WRITE | GENERIC_READ, - FILE_SHARE_WRITE | FILE_SHARE_READ, - NULL, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, - NULL); - if (INVALID_HANDLE_VALUE == this->deviceHandle) { - hr = HRESULT_FROM_WIN32(GetLastError()); - this->logger->error("Failed to open device handle for {} due to {}", this->identifier, hr); - return false; - } - // Initialize WinUsb handle - bResult = WinUsb_Initialize(this->deviceHandle, &this->winUsbHandle); - if (FALSE == bResult) { - hr = HRESULT_FROM_WIN32(GetLastError()); - CloseHandle(this->deviceHandle); - this->logger->error("Failed to initiallize WinUSB handle for {} due to {}", this->identifier, hr); - return false; - } - this->deviceHandlesOpen = true; - // Make GetDescriptor call to device in order to ensure communications with it work - BOOL winUsbGetDescriptorResult; - USB_DEVICE_DESCRIPTOR winUsbDeviceDescriptor; - ULONG bytesReceived; - winUsbGetDescriptorResult = WinUsb_GetDescriptor( - this->winUsbHandle, USB_DEVICE_DESCRIPTOR_TYPE, 0, 0, (PBYTE)&winUsbDeviceDescriptor, sizeof(winUsbDeviceDescriptor), &bytesReceived - ); - if (winUsbGetDescriptorResult == FALSE || bytesReceived != sizeof(winUsbDeviceDescriptor)) { - this->logger->error("Failed to read USB descriptor for {}", this->identifier); - this->closeDevice(); - return false; - } - this->logger->info("Opened WinUSB device for {}", this->identifier); - return true; -} - -/* -Attempt to close the WinUsb device -*/ -bool WinUsbDevice::closeDevice() { - if (!this->deviceHandlesOpen) return false; - WinUsb_Free(this->winUsbHandle); - CloseHandle(this->deviceHandle); - this->deviceHandlesOpen = false; - return true; -} - -/* -Process data read from Razer Atrox -*/ -RAZER_ATROX_PACKET_TYPES WinUsbDevice::processInputFromRazerAtrox() { - if (this->dataPacket.transferred == 0) return UNKNOWN; - switch (this->dataPacket.data[0]) { - case 0x01: // Dummy packet? - return DUMMY; - case 0x03: // Heartbeat packet? - return HEARTBEAT; - case 0x07: // Guide button - buttonState.buttonGuide = dataPacket.data[4] & 0x01; - return GUIDE; - case 0x20: // Inputs - buttonState.buttonA = dataPacket.data[22] & 0x10; - buttonState.buttonB = dataPacket.data[22] & 0x20; - buttonState.buttonX = dataPacket.data[22] & 0x01; - buttonState.buttonY = dataPacket.data[22] & 0x02; - buttonState.rightButton = dataPacket.data[22] & 0x04; - buttonState.leftButton = dataPacket.data[22] & 0x08; - buttonState.rightTrigger = dataPacket.data[22] & 0x40; - buttonState.leftTrigger = dataPacket.data[22] & 0x80; - buttonState.buttonMenu = dataPacket.data[04] & 0x04; - buttonState.buttonView = dataPacket.data[04] & 0x08; - buttonState.stickUp = dataPacket.data[05] & 0x01; - buttonState.stickDown = dataPacket.data[05] & 0x02; - buttonState.stickLeft = dataPacket.data[05] & 0x04; - buttonState.stickRight = dataPacket.data[05] & 0x08; - return BUTTON_INPUT; - } - return UNKNOWN; -} - -/* -Blocking data read from end-point 0x81 -*/ -bool WinUsbDevice::readInputFromRazerAtrox() -{ - if (this->winUsbHandle == INVALID_HANDLE_VALUE) return false; - return WinUsb_ReadPipe(this->winUsbHandle, 0x81, this->dataPacket.data, 30, &this->dataPacket.transferred, NULL); -} - -/* -Sent the INIT packet to the Razor Atrox -*/ -bool WinUsbDevice::initRazorAtrox() { - this->logger->info("Init Razer Atrox"); - if (this->winUsbHandle == INVALID_HANDLE_VALUE) return false; - ULONG cbSent = 0; - return WinUsb_WritePipe(this->winUsbHandle, 0x01, (PUCHAR)this->RAZER_ATROX_INIT, 5, &cbSent, 0); -} - -/* -Dispatch data to the VigEm XB360 controller -*/ -bool WinUsbDevice::dispatchInputToVigEmController() { - XUSB_REPORT controllerData{}; - if (this->buttonState.buttonGuide) controllerData.wButtons |= XUSB_GAMEPAD_GUIDE; - if (this->buttonState.buttonMenu) controllerData.wButtons |= XUSB_GAMEPAD_START; - if (this->buttonState.buttonView) controllerData.wButtons |= XUSB_GAMEPAD_BACK; - if (this->buttonState.buttonA) controllerData.wButtons |= XUSB_GAMEPAD_A; - if (this->buttonState.buttonB) controllerData.wButtons |= XUSB_GAMEPAD_B; - if (this->buttonState.buttonX) controllerData.wButtons |= XUSB_GAMEPAD_X; - if (this->buttonState.buttonY) controllerData.wButtons |= XUSB_GAMEPAD_Y; - if (this->buttonState.leftButton) controllerData.wButtons |= XUSB_GAMEPAD_LEFT_SHOULDER; - if (this->buttonState.rightButton) controllerData.wButtons |= XUSB_GAMEPAD_RIGHT_SHOULDER; - if (this->buttonState.stickUp) controllerData.wButtons |= XUSB_GAMEPAD_DPAD_UP; - if (this->buttonState.stickDown) controllerData.wButtons |= XUSB_GAMEPAD_DPAD_DOWN; - if (this->buttonState.stickLeft) controllerData.wButtons |= XUSB_GAMEPAD_DPAD_LEFT; - if (this->buttonState.stickRight) controllerData.wButtons |= XUSB_GAMEPAD_DPAD_RIGHT; - if (this->buttonState.leftTrigger) controllerData.bLeftTrigger = 0xff; - if (this->buttonState.rightTrigger) controllerData.bRightTrigger = 0xff; - const auto controllerUpdateResult = vigem_target_x360_update(this->vigEmClient, this->vigEmTarget, controllerData); - return VIGEM_SUCCESS(controllerUpdateResult); -} diff --git a/XBOFS.win.old/src/WinUsbDeviceManager.cpp b/XBOFS.win.old/src/WinUsbDeviceManager.cpp deleted file mode 100644 index 8fbc997..0000000 --- a/XBOFS.win.old/src/WinUsbDeviceManager.cpp +++ /dev/null @@ -1,125 +0,0 @@ -#include "XBOFS.win\WinUsbDeviceManager.h" -#include "XBOFS.win\utils.h"; - -using namespace XBOFSWin; -/* -Constructs the WinUsbDeviceManager and starts its event loop in a separate thread -*/ -WinUsbDeviceManager::WinUsbDeviceManager(std::shared_ptr logger, DWORD parentThreadId, DWORD uiManagerThreadId) -: Thread("WinUsbDeviceManager", logger, parentThreadId, uiManagerThreadId) -{} - -DWORD WinUsbDeviceManager::run() { - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_MANAGER_STARTED, NULL); - this->logger->info("Started thread for {}", this->identifier); - MSG threadMessage; - bool loop = true; - std::unordered_map devicePathWinUsbDeviceMap; - this->logger->info("Starting scan loop for {}", this->identifier); - while (loop) { - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_MANAGER_SCANNING, NULL); - auto devicePaths = this->retrieveDevicePaths(); - // Check the updated set for new devicePaths - for (auto devicePath : devicePaths) { - if (devicePathWinUsbDeviceMap.find(devicePath) != devicePathWinUsbDeviceMap.end()) continue; - this->logger->info("Adding WinUsbDevice at {}", utf8_encode(devicePath)); - #ifdef UNICODE - auto identifier = utf8_encode(devicePath); - #else - auto identifier = devicePath; - #endif // UNICODE - auto winUsbDeviceLogger = setup_logger("WinUsbDevice", "", this->logger->sinks()); - auto winUsbDevice = new WinUsbDevice(devicePath, identifier, winUsbDeviceLogger, this->threadId, this->uiManagerThreadId); - devicePathWinUsbDeviceMap.insert({ devicePath, winUsbDevice }); - } - // Check for WinUsbDevices to remove - for (auto tuple : devicePathWinUsbDeviceMap) { - if (devicePaths.find(tuple.first) != devicePaths.end()) continue; - delete tuple.second; - devicePathWinUsbDeviceMap.erase(tuple.first); - } - // TODO: Processes messages in thread message queue - if (PeekMessage(&threadMessage, NULL, WM_USER, WM_APP, PM_REMOVE) == TRUE && threadMessage.message == RAWUVEF_STOP) loop = false; - else { - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_MANAGER_SLEEPING, NULL); - Sleep(1000); - } - } - this->logger->info("Completed scan loop for {}", this->identifier); - this->notifyUIManager(RAWUVEF_WIN_USB_DEVICE_MANAGER_TERMINATING, NULL); - for (auto tuple : devicePathWinUsbDeviceMap) delete tuple.second; - return 0; -} - - -/* -Retrieve a vector of TCHAR* representing device paths that the device manager will work with -*/ -std::set WinUsbDeviceManager::retrieveDevicePaths() { - CONFIGRET configurationManagerResult = CR_SUCCESS; - HRESULT resultHandle = S_OK; - PTSTR deviceInterfaceList = NULL; - ULONG deviceInterfaceListSize = 0; - std::set newDevicePaths; - // - // Enumerate all devices exposing the interface. Do this in a loop - // in case a new interface is discovered while this code is executing, - // causing CM_Get_Device_Interface_List to return CR_BUFFER_SMALL. - // - this->logger->debug("Retrieving device interface paths"); - do { - configurationManagerResult = CM_Get_Device_Interface_List_Size(&deviceInterfaceListSize, - (LPGUID)&GUID_DEVINTERFACE_XBOFS_WIN_CONTROLLER, - NULL, - CM_GET_DEVICE_INTERFACE_LIST_PRESENT); - - if (configurationManagerResult != CR_SUCCESS) { - resultHandle = HRESULT_FROM_WIN32(CM_MapCrToWin32Err(configurationManagerResult, ERROR_INVALID_DATA)); - break; - } - - this->logger->debug("Device interface list size in bytes: {}", deviceInterfaceListSize * sizeof(TCHAR)); - - deviceInterfaceList = (PTSTR)HeapAlloc(GetProcessHeap(), - HEAP_ZERO_MEMORY, - deviceInterfaceListSize * sizeof(TCHAR)); - - if (deviceInterfaceList == NULL) { - resultHandle = E_OUTOFMEMORY; - break; - } - - configurationManagerResult = CM_Get_Device_Interface_List((LPGUID)&GUID_DEVINTERFACE_XBOFS_WIN_CONTROLLER, - NULL, - deviceInterfaceList, - deviceInterfaceListSize, - CM_GET_DEVICE_INTERFACE_LIST_PRESENT); - - if (configurationManagerResult != CR_SUCCESS) { - if (configurationManagerResult != CR_BUFFER_SMALL) { - resultHandle = HRESULT_FROM_WIN32(CM_MapCrToWin32Err(configurationManagerResult, ERROR_INVALID_DATA)); - } - } - } while (configurationManagerResult == CR_BUFFER_SMALL); - // Handle errors - if (resultHandle != S_OK || deviceInterfaceList == TEXT('\0')) { - // TODO: Log error - } - else { - auto deviceInterfaceListMarker = deviceInterfaceList; - auto position = 0; - while (position < deviceInterfaceListSize) { - auto devicePath = tstring(deviceInterfaceListMarker); - auto devicePathSize = devicePath.size(); - if (!devicePathSize) break; - newDevicePaths.insert(devicePath); - deviceInterfaceListMarker += devicePathSize + 1; - position += devicePathSize + 1; - this->logger->debug("Device interface path detected: {}", utf8_encode(devicePath)); - } - deviceInterfaceListMarker = NULL; - this->logger->debug("{} device interfaces detected", newDevicePaths.size()); - } - HeapFree(GetProcessHeap(), 0, deviceInterfaceList); - return newDevicePaths; -} \ No newline at end of file diff --git a/XBOFS.win.old/src/device.cpp b/XBOFS.win.old/src/device.cpp deleted file mode 100644 index 7f91b18..0000000 --- a/XBOFS.win.old/src/device.cpp +++ /dev/null @@ -1,235 +0,0 @@ -#include "XBOFS.win\pch.h" - -#include - -HRESULT -RetrieveDevicePath( - _Out_bytecap_(BufLen) LPTSTR DevicePath, - _In_ ULONG BufLen, - _Out_opt_ PBOOL FailureDeviceNotFound - ); - -HRESULT -OpenDevice( - _Out_ PDEVICE_DATA DeviceData, - _Out_opt_ PBOOL FailureDeviceNotFound - ) -/*++ - -Routine description: - - Open all needed handles to interact with the device. - - If the device has multiple USB interfaces, this function grants access to - only the first interface. - - If multiple devices have the same device interface GUID, there is no - guarantee of which one will be returned. - -Arguments: - - DeviceData - Struct filled in by this function. The caller should use the - WinusbHandle to interact with the device, and must pass the struct to - CloseDevice when finished. - - FailureDeviceNotFound - TRUE when failure is returned due to no devices - found with the correct device interface (device not connected, driver - not installed, or device is disabled in Device Manager); FALSE - otherwise. - -Return value: - - HRESULT - ---*/ -{ - HRESULT hr = S_OK; - BOOL bResult; - - DeviceData->HandlesOpen = FALSE; - - hr = RetrieveDevicePath(DeviceData->DevicePath, - sizeof(DeviceData->DevicePath), - FailureDeviceNotFound); - - if (FAILED(hr)) { - - return hr; - } - - DeviceData->DeviceHandle = CreateFile(DeviceData->DevicePath, - GENERIC_WRITE | GENERIC_READ, - FILE_SHARE_WRITE | FILE_SHARE_READ, - NULL, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, - NULL); - - if (INVALID_HANDLE_VALUE == DeviceData->DeviceHandle) { - - hr = HRESULT_FROM_WIN32(GetLastError()); - return hr; - } - - bResult = WinUsb_Initialize(DeviceData->DeviceHandle, - &DeviceData->WinusbHandle); - - if (FALSE == bResult) { - - hr = HRESULT_FROM_WIN32(GetLastError()); - CloseHandle(DeviceData->DeviceHandle); - return hr; - } - - DeviceData->HandlesOpen = TRUE; - return hr; -} - -VOID -CloseDevice( - _Inout_ PDEVICE_DATA DeviceData - ) -/*++ - -Routine description: - - Perform required cleanup when the device is no longer needed. - - If OpenDevice failed, do nothing. - -Arguments: - - DeviceData - Struct filled in by OpenDevice - -Return value: - - None - ---*/ -{ - if (FALSE == DeviceData->HandlesOpen) { - - // - // Called on an uninitialized DeviceData - // - return; - } - - WinUsb_Free(DeviceData->WinusbHandle); - CloseHandle(DeviceData->DeviceHandle); - DeviceData->HandlesOpen = FALSE; - - return; -} - -HRESULT -RetrieveDevicePath( - _Out_bytecap_(BufLen) LPTSTR DevicePath, - _In_ ULONG BufLen, - _Out_opt_ PBOOL FailureDeviceNotFound - ) -/*++ - -Routine description: - - Retrieve the device path that can be used to open the WinUSB-based device. - - If multiple devices have the same device interface GUID, there is no - guarantee of which one will be returned. - -Arguments: - - DevicePath - On successful return, the path of the device (use with CreateFile). - - BufLen - The size of DevicePath's buffer, in bytes - - FailureDeviceNotFound - TRUE when failure is returned due to no devices - found with the correct device interface (device not connected, driver - not installed, or device is disabled in Device Manager); FALSE - otherwise. - -Return value: - - HRESULT - ---*/ -{ - CONFIGRET cr = CR_SUCCESS; - HRESULT hr = S_OK; - PTSTR DeviceInterfaceList = NULL; - ULONG DeviceInterfaceListLength = 0; - - if (NULL != FailureDeviceNotFound) { - - *FailureDeviceNotFound = FALSE; - } - - // - // Enumerate all devices exposing the interface. Do this in a loop - // in case a new interface is discovered while this code is executing, - // causing CM_Get_Device_Interface_List to return CR_BUFFER_SMALL. - // - do { - cr = CM_Get_Device_Interface_List_Size(&DeviceInterfaceListLength, - (LPGUID)&GUID_DEVINTERFACE_XBOFS_WIN_CONTROLLER, - NULL, - CM_GET_DEVICE_INTERFACE_LIST_PRESENT); - - if (cr != CR_SUCCESS) { - hr = HRESULT_FROM_WIN32(CM_MapCrToWin32Err(cr, ERROR_INVALID_DATA)); - break; - } - - DeviceInterfaceList = (PTSTR)HeapAlloc(GetProcessHeap(), - HEAP_ZERO_MEMORY, - DeviceInterfaceListLength * sizeof(TCHAR)); - - if (DeviceInterfaceList == NULL) { - hr = E_OUTOFMEMORY; - break; - } - - cr = CM_Get_Device_Interface_List((LPGUID)&GUID_DEVINTERFACE_XBOFS_WIN_CONTROLLER, - NULL, - DeviceInterfaceList, - DeviceInterfaceListLength, - CM_GET_DEVICE_INTERFACE_LIST_PRESENT); - - if (cr != CR_SUCCESS) { - HeapFree(GetProcessHeap(), 0, DeviceInterfaceList); - - if (cr != CR_BUFFER_SMALL) { - hr = HRESULT_FROM_WIN32(CM_MapCrToWin32Err(cr, ERROR_INVALID_DATA)); - } - } - } while (cr == CR_BUFFER_SMALL); - - if (FAILED(hr)) { - return hr; - } - - // - // If the interface list is empty, no devices were found. - // - if (*DeviceInterfaceList == TEXT('\0')) { - if (NULL != FailureDeviceNotFound) { - *FailureDeviceNotFound = TRUE; - } - - hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND); - HeapFree(GetProcessHeap(), 0, DeviceInterfaceList); - return hr; - } - - // - // Give path of the first found device interface instance to the caller. CM_Get_Device_Interface_List ensured - // the instance is NULL-terminated. - // - hr = StringCbCopy(DevicePath, - BufLen, - DeviceInterfaceList); - - HeapFree(GetProcessHeap(), 0, DeviceInterfaceList); - - return hr; -} diff --git a/XBOFS.win.old/src/utils.cpp b/XBOFS.win.old/src/utils.cpp deleted file mode 100644 index cc56ead..0000000 --- a/XBOFS.win.old/src/utils.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "XBOFS.win\utils.h" - -/* -See https://stackoverflow.com/a/3999597/106057 -*/ -std::string XBOFSWin::utf8_encode(const std::wstring &wstr) -{ - if (wstr.empty()) return std::string(); - int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL); - std::string strTo(size_needed, 0); - WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL); - return strTo; -} - -/* -See https://stackoverflow.com/a/3999597/106057 -*/ -std::wstring XBOFSWin::utf8_decode(const std::string &str) -{ - if (str.empty()) return std::wstring(); - int size_needed = MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), NULL, 0); - std::wstring wstrTo(size_needed, 0); - MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), &wstrTo[0], size_needed); - return wstrTo; -} - -std::shared_ptr XBOFSWin::setup_logger(std::string loggerName, std::string logFile, std::vector sinks) -{ - auto logger = spdlog::get(loggerName); - if (!logger) - { - if (sinks.size() > 0) - { - logger = std::make_shared(loggerName, std::begin(sinks), std::end(sinks)); - spdlog::register_logger(logger); - } - else - { - logger = spdlog::basic_logger_mt(loggerName, logFile); - } - } - - return logger; -} \ No newline at end of file From efeb0267d3c03fecb4780722c93fec7409fa9142 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Wed, 16 Oct 2019 22:19:28 +0200 Subject: [PATCH 25/50] #2: Fixed winUsbDeviceAdded and winUsbDeviceRemoved signal definitions --- XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h | 6 +++--- XBOFS.win/src/WinUsbDeviceManager.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h b/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h index 1eba3b5..105778f 100644 --- a/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h +++ b/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h @@ -21,8 +21,8 @@ namespace XBOFSWin { void run(); signals: - void winUsbDeviceAdded(const std::wstring &devicePath, const XBOFSWin::WinUsbDevice &winUsbDevice); - void winUsbDeviceRemoved(const std::wstring &devicePath, const XBOFSWin::WinUsbDevice &winUsbDevice); + void winUsbDeviceAdded(const QString &devicePath, const XBOFSWin::WinUsbDevice *winUsbDevice); + void winUsbDeviceRemoved(const QString &devicePath, const XBOFSWin::WinUsbDevice *winUsbDevice); void winUsbDeviceManagerScanning(); void winUsbDeviceManagerSleeping(); void winUsbDeviceManagerTerminating(); @@ -30,7 +30,7 @@ namespace XBOFSWin { protected: const std::shared_ptr logger; - std::set retrieveDevicePaths(); + std::set retrieveDevicePaths(); }; } diff --git a/XBOFS.win/src/WinUsbDeviceManager.cpp b/XBOFS.win/src/WinUsbDeviceManager.cpp index eccbb2b..862e420 100644 --- a/XBOFS.win/src/WinUsbDeviceManager.cpp +++ b/XBOFS.win/src/WinUsbDeviceManager.cpp @@ -32,7 +32,7 @@ void WinUsbDeviceManager::run() { connect(winUsbDeviceThread, &QThread::started, winUsbDevice, &WinUsbDevice::run); winUsbDevice->moveToThread(winUsbDeviceThread); devicePathWinUsbDeviceMap.insert({ devicePath, std::make_pair(winUsbDeviceThread, winUsbDevice) }); - emit winUsbDeviceAdded(devicePath, *winUsbDevice); + emit winUsbDeviceAdded(QString::fromStdWString(devicePath), winUsbDevice); winUsbDeviceThread->start(); } @@ -49,7 +49,7 @@ void WinUsbDeviceManager::run() { winUsbDeviceThread->terminate(); logger->info(L"Waiting for thread hanlding {} to terminate", devicePath); winUsbDeviceThread->wait(); - emit winUsbDeviceRemoved(devicePath, *winUsbDevice); + emit winUsbDeviceRemoved(QString::fromStdWString(devicePath), winUsbDevice); iterator = devicePathWinUsbDeviceMap.erase(iterator); } else ++iterator; From 7656eb57449deaa30574586525f91396f7620479 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Wed, 16 Oct 2019 22:19:50 +0200 Subject: [PATCH 26/50] #2: Implemented addition and removal of per-controller tabs --- XBOFS.win.qt5/XBOFSWinQT5GUI.cpp | 17 ++++++++++++++--- XBOFS.win.qt5/XBOFSWinQT5GUI.h | 14 ++++++++++++-- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp index ae99d32..791eba2 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -30,13 +31,23 @@ XBOFSWinQT5GUI::XBOFSWinQT5GUI(QWidget *parent) winUsbDeviceManagerThread->start(); } -void XBOFSWinQT5GUI::winUsbDeviceAdded(const std::wstring &identifier, const XBOFSWin::WinUsbDevice &winUsbDevice) { +void XBOFSWinQT5GUI::winUsbDeviceAdded(const QString &devicePath, const XBOFSWin::WinUsbDevice *winUsbDevice) { // TODO: Connect signals - // TODO: Update UI + // TODO: Update UI + auto tabWidget = new QWidget(); + auto tabWidgetUi = new Ui::WinUsbDeviceWidget(); + tabWidget->setObjectName(devicePath); + tabWidgetUi->setupUi(tabWidget); + auto tabIndex = ui.tabWidget->addTab(tabWidget, QString()); + ui.tabWidget->setTabText(tabIndex, QString::fromStdWString(fmt::format(L"Stick {}", tabIndex))); + devicePathTabMap.insert({ devicePath, std::make_tuple(tabIndex, tabWidget, tabWidgetUi) }); } -void XBOFSWinQT5GUI::winUsbDeviceRemoved(const std::wstring &identifierr, const XBOFSWin::WinUsbDevice &winUsbDevice) { +void XBOFSWinQT5GUI::winUsbDeviceRemoved(const QString &devicePath, const XBOFSWin::WinUsbDevice *winUsbDevice) { // TODO: Update UI + auto tuple = devicePathTabMap.at(devicePath); + ui.tabWidget->removeTab(std::get<0>(tuple)); + devicePathTabMap.erase(devicePath); } void XBOFSWinQT5GUI::winUsbDeviceManagerScanning() { diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.h b/XBOFS.win.qt5/XBOFSWinQT5GUI.h index b89934f..47faf1a 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.h +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.h @@ -6,7 +6,15 @@ #include #include "ui_XBOFSWinQT5GUI.h" +#include "ui_WinUsbDeviceWidget.h" +namespace std { + template<> struct hash { + std::size_t operator()(const QString& s) const { + return qHash(s); + } + }; +} class XBOFSWinQT5GUI : public QMainWindow { @@ -16,8 +24,8 @@ class XBOFSWinQT5GUI : public QMainWindow XBOFSWinQT5GUI(QWidget *parent = Q_NULLPTR); public slots: - void winUsbDeviceAdded(const std::wstring &devicePath, const XBOFSWin::WinUsbDevice &winUsbDevice); - void winUsbDeviceRemoved(const std::wstring &devicePath, const XBOFSWin::WinUsbDevice &winUsbDevice); + void winUsbDeviceAdded(const QString &devicePath, const XBOFSWin::WinUsbDevice *winUsbDevice); + void winUsbDeviceRemoved(const QString &devicePath, const XBOFSWin::WinUsbDevice *winUsbDevice); void winUsbDeviceManagerScanning(); void terminateWinUsbDeviceManager(); @@ -26,4 +34,6 @@ public slots: std::shared_ptr logger; QThread *winUsbDeviceManagerThread; XBOFSWin::WinUsbDeviceManager *winUsbDeviceManager; + + std::unordered_map> devicePathTabMap; }; From b9f35b62a8a2108260652ef60a2b6f2e8abb3ea7 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Thu, 17 Oct 2019 16:40:19 +0200 Subject: [PATCH 27/50] #2: In WinUsbDevice main loop, process events before to connect WinUsbDevice --- XBOFS.win/src/WinUsbDevice.cpp | 6 +++--- XBOFS.win/src/WinUsbDeviceManager.cpp | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/XBOFS.win/src/WinUsbDevice.cpp b/XBOFS.win/src/WinUsbDevice.cpp index 193ae82..fa8bceb 100644 --- a/XBOFS.win/src/WinUsbDevice.cpp +++ b/XBOFS.win/src/WinUsbDevice.cpp @@ -42,7 +42,8 @@ void WinUsbDevice::run() { emit vigEmTargetAdded(devicePath); // Loop reading input, processing it and dispatching it logger->info("Starting Read-Process-Dispatch loop"); - while (loop && !QThread::currentThread()->isInterruptionRequested()) { + while (loop && !QThread::currentThread()->isInterruptionRequested()) { + QThread::currentThread()->eventDispatcher()->processEvents(QEventLoop::AllEvents); // Open WinUsbDevice emit winUsbDeviceOpen(devicePath); if (!openDevice()) { @@ -77,8 +78,7 @@ void WinUsbDevice::run() { logger->warn("Failed to read input from XBO Arcade Stick 5 or more times for {}"); } failedReads += currentFailedReads; - currentFailedReads = 0; - QThread::currentThread()->eventDispatcher()->processEvents(QEventLoop::AllEvents); + currentFailedReads = 0; } emit winUsbDeviceTerminating(devicePath); logger->info("Completed Read-Process-Dispatch loop"); diff --git a/XBOFS.win/src/WinUsbDeviceManager.cpp b/XBOFS.win/src/WinUsbDeviceManager.cpp index 862e420..31a51f4 100644 --- a/XBOFS.win/src/WinUsbDeviceManager.cpp +++ b/XBOFS.win/src/WinUsbDeviceManager.cpp @@ -69,7 +69,7 @@ void WinUsbDeviceManager::run() { // TODO: Duplicate code, we should remove it for (auto tuple : devicePathWinUsbDeviceMap) { auto devicePath = tuple.first; - auto winUsbDeviceThread = tuple.second.first; + auto winUsbDeviceThread = tuple.second.first; logger->info(L"Requesting interruption of thread handling {}", devicePath); winUsbDeviceThread->requestInterruption(); logger->info(L"Signalling thread handling {} to terminate", devicePath); @@ -85,11 +85,11 @@ void WinUsbDeviceManager::run() { Retrieve a vector of TCHAR* representing device paths that the device manager will work with */ std::set WinUsbDeviceManager::retrieveDevicePaths() { - CONFIGRET configurationManagerResult = CR_SUCCESS; - HRESULT resultHandle = S_OK; - PTSTR deviceInterfaceList = NULL; - ULONG deviceInterfaceListSize = 0; - std::set newDevicePaths; + CONFIGRET configurationManagerResult = CR_SUCCESS; + HRESULT resultHandle = S_OK; + PTSTR deviceInterfaceList = NULL; + ULONG deviceInterfaceListSize = 0; + std::set newDevicePaths; // // Enumerate all devices exposing the interface. Do this in a loop // in case a new interface is discovered while this code is executing, From 5d1b865450bd53f7cc5db2824c45be6619951be7 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Fri, 18 Oct 2019 23:01:53 +0200 Subject: [PATCH 28/50] #2: Register std::wstring as a with Qt metatype system to allow usage in signals and slots. Changed signature for winUsbDeviceRemoved signal. Delete winUsbDeviceThread once it's done --- XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h | 4 ++-- XBOFS.win/include/XBOFS.win/pch.h | 3 +++ XBOFS.win/include/XBOFS.win/utils.h | 1 + XBOFS.win/src/WinUsbDeviceManager.cpp | 11 ++++++----- XBOFS.win/src/utils.cpp | 7 ++++++- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h b/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h index 105778f..34716b7 100644 --- a/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h +++ b/XBOFS.win/include/XBOFS.win/WinUsbDeviceManager.h @@ -21,8 +21,8 @@ namespace XBOFSWin { void run(); signals: - void winUsbDeviceAdded(const QString &devicePath, const XBOFSWin::WinUsbDevice *winUsbDevice); - void winUsbDeviceRemoved(const QString &devicePath, const XBOFSWin::WinUsbDevice *winUsbDevice); + void winUsbDeviceAdded(const std::wstring &devicePath, const XBOFSWin::WinUsbDevice *winUsbDevice); + void winUsbDeviceRemoved(const std::wstring &devicePath); void winUsbDeviceManagerScanning(); void winUsbDeviceManagerSleeping(); void winUsbDeviceManagerTerminating(); diff --git a/XBOFS.win/include/XBOFS.win/pch.h b/XBOFS.win/include/XBOFS.win/pch.h index 711e3c6..2c7783d 100644 --- a/XBOFS.win/include/XBOFS.win/pch.h +++ b/XBOFS.win/include/XBOFS.win/pch.h @@ -10,8 +10,11 @@ #include #include #include +#include #include "device.h" #include "utils.h" #include "stdafx.h" + +Q_DECLARE_METATYPE(std::wstring) \ No newline at end of file diff --git a/XBOFS.win/include/XBOFS.win/utils.h b/XBOFS.win/include/XBOFS.win/utils.h index 5b7a610..c3ceb00 100644 --- a/XBOFS.win/include/XBOFS.win/utils.h +++ b/XBOFS.win/include/XBOFS.win/utils.h @@ -5,5 +5,6 @@ namespace XBOFSWin { std::string utf8_encode(const std::wstring &wstr); std::wstring utf8_decode(const std::string &str); + std::shared_ptr get_logger(std::wstring loggerName, std::vector sinks); std::shared_ptr get_logger(std::string loggerName, std::vector sinks); } \ No newline at end of file diff --git a/XBOFS.win/src/WinUsbDeviceManager.cpp b/XBOFS.win/src/WinUsbDeviceManager.cpp index 31a51f4..68c8d1a 100644 --- a/XBOFS.win/src/WinUsbDeviceManager.cpp +++ b/XBOFS.win/src/WinUsbDeviceManager.cpp @@ -25,16 +25,15 @@ void WinUsbDeviceManager::run() { if (devicePathWinUsbDeviceMap.find(devicePath) != devicePathWinUsbDeviceMap.end()) continue; this->logger->info(L"Adding WinUsbDevice at {}", devicePath); auto winUsbDeviceThread = new QThread(); - auto winUsbDeviceLoggerName = utf8_encode(fmt::format(L"WinUsbDevice {}", devicePath)); + auto winUsbDeviceLoggerName = fmt::format(L"WinUsbDevice {}", devicePath); auto winUsbDeviceLogger = get_logger(winUsbDeviceLoggerName, logger->sinks()); auto winUsbDevice = new WinUsbDevice(devicePath, winUsbDeviceLogger); connect(winUsbDeviceThread, &QThread::finished, winUsbDevice, &QObject::deleteLater); connect(winUsbDeviceThread, &QThread::started, winUsbDevice, &WinUsbDevice::run); winUsbDevice->moveToThread(winUsbDeviceThread); devicePathWinUsbDeviceMap.insert({ devicePath, std::make_pair(winUsbDeviceThread, winUsbDevice) }); - emit winUsbDeviceAdded(QString::fromStdWString(devicePath), winUsbDevice); - winUsbDeviceThread->start(); - + emit winUsbDeviceAdded(devicePath, winUsbDevice); + winUsbDeviceThread->start(); } // Check for WinUsbDevices to remove for (auto iterator = devicePathWinUsbDeviceMap.begin(); iterator != devicePathWinUsbDeviceMap.end(); ) @@ -49,7 +48,8 @@ void WinUsbDeviceManager::run() { winUsbDeviceThread->terminate(); logger->info(L"Waiting for thread hanlding {} to terminate", devicePath); winUsbDeviceThread->wait(); - emit winUsbDeviceRemoved(QString::fromStdWString(devicePath), winUsbDevice); + emit winUsbDeviceRemoved(devicePath); + delete winUsbDeviceThread; iterator = devicePathWinUsbDeviceMap.erase(iterator); } else ++iterator; @@ -76,6 +76,7 @@ void WinUsbDeviceManager::run() { winUsbDeviceThread->terminate(); logger->info(L"Waiting for thread hanlding {} to terminate", devicePath); winUsbDeviceThread->wait(); + delete winUsbDeviceThread; } logger->info("Completed run()"); } diff --git a/XBOFS.win/src/utils.cpp b/XBOFS.win/src/utils.cpp index cb640de..aeea68b 100644 --- a/XBOFS.win/src/utils.cpp +++ b/XBOFS.win/src/utils.cpp @@ -24,11 +24,16 @@ std::wstring XBOFSWin::utf8_decode(const std::string &str) return wstrTo; } +std::shared_ptr XBOFSWin::get_logger(std::wstring loggerName, std::vector sinks) +{ + return get_logger(utf8_encode(loggerName), sinks); +} + std::shared_ptr XBOFSWin::get_logger(std::string loggerName, std::vector sinks) { auto logger = spdlog::get(loggerName); if (!logger) - { + { logger = std::make_shared(loggerName, std::begin(sinks), std::end(sinks)); spdlog::register_logger(logger); } From 8c3a8b10509205bc67cd01761571bdebea4b6bf4 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Fri, 18 Oct 2019 23:04:36 +0200 Subject: [PATCH 29/50] #2: Updated signals and slots. Changed storage for WinUsbDeviceWidget tabs. Enhanced winUsbDeviceRemoved slot to perform clean-up and retitle remaining tabs --- XBOFS.win.qt5/XBOFS.win.qt5.vcxproj | 2 ++ XBOFS.win.qt5/XBOFSWinQT5GUI.cpp | 30 +++++++++++++++++++++-------- XBOFS.win.qt5/XBOFSWinQT5GUI.h | 6 +++--- XBOFS.win.qt5/main.cpp | 6 +++++- 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj b/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj index 847ad27..064daf6 100644 --- a/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj +++ b/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj @@ -63,6 +63,7 @@ MultiThreadedDebug true StdCall + stdcpp17 Windows @@ -96,6 +97,7 @@ MultiThreaded true false + stdcpp17 Windows diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp index 791eba2..ef56ac9 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp @@ -31,23 +31,37 @@ XBOFSWinQT5GUI::XBOFSWinQT5GUI(QWidget *parent) winUsbDeviceManagerThread->start(); } -void XBOFSWinQT5GUI::winUsbDeviceAdded(const QString &devicePath, const XBOFSWin::WinUsbDevice *winUsbDevice) { +void XBOFSWinQT5GUI::winUsbDeviceAdded(const std::wstring &devicePath, const XBOFSWin::WinUsbDevice *winUsbDevice) { // TODO: Connect signals // TODO: Update UI + auto devicePathQString = QString::fromStdWString(devicePath); auto tabWidget = new QWidget(); auto tabWidgetUi = new Ui::WinUsbDeviceWidget(); - tabWidget->setObjectName(devicePath); + tabWidget->setObjectName(devicePathQString); tabWidgetUi->setupUi(tabWidget); auto tabIndex = ui.tabWidget->addTab(tabWidget, QString()); ui.tabWidget->setTabText(tabIndex, QString::fromStdWString(fmt::format(L"Stick {}", tabIndex))); - devicePathTabMap.insert({ devicePath, std::make_tuple(tabIndex, tabWidget, tabWidgetUi) }); + tabs.push_back(std::make_tuple(devicePath, tabWidget, tabWidgetUi)); } -void XBOFSWinQT5GUI::winUsbDeviceRemoved(const QString &devicePath, const XBOFSWin::WinUsbDevice *winUsbDevice) { - // TODO: Update UI - auto tuple = devicePathTabMap.at(devicePath); - ui.tabWidget->removeTab(std::get<0>(tuple)); - devicePathTabMap.erase(devicePath); +void XBOFSWinQT5GUI::winUsbDeviceRemoved(const std::wstring &devicePath) { + auto iterator = tabs.begin(); + int index; + for (index = 1; iterator != tabs.end(); index++) { + auto currentDevicePath = std::get<0>(*iterator); + if (currentDevicePath == devicePath) break; + ++iterator; + } + if (iterator == tabs.end()) return; + auto tabWidget = std::get<1>(*iterator); + auto tabWidgetUi = std::get<2>(*iterator); + tabs.erase(iterator); + ui.tabWidget->removeTab(index); + delete tabWidget; + delete tabWidgetUi; + for (auto idx = 1; idx < ui.tabWidget->count(); idx++) { + ui.tabWidget->setTabText(idx, QString::fromStdWString(fmt::format(L"Stick {}", idx))); + } } void XBOFSWinQT5GUI::winUsbDeviceManagerScanning() { diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.h b/XBOFS.win.qt5/XBOFSWinQT5GUI.h index 47faf1a..2147523 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.h +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.h @@ -24,8 +24,8 @@ class XBOFSWinQT5GUI : public QMainWindow XBOFSWinQT5GUI(QWidget *parent = Q_NULLPTR); public slots: - void winUsbDeviceAdded(const QString &devicePath, const XBOFSWin::WinUsbDevice *winUsbDevice); - void winUsbDeviceRemoved(const QString &devicePath, const XBOFSWin::WinUsbDevice *winUsbDevice); + void winUsbDeviceAdded(const std::wstring &devicePath, const XBOFSWin::WinUsbDevice *winUsbDevice); + void winUsbDeviceRemoved(const std::wstring &devicePath); void winUsbDeviceManagerScanning(); void terminateWinUsbDeviceManager(); @@ -35,5 +35,5 @@ public slots: QThread *winUsbDeviceManagerThread; XBOFSWin::WinUsbDeviceManager *winUsbDeviceManager; - std::unordered_map> devicePathTabMap; + std::vector> tabs; }; diff --git a/XBOFS.win.qt5/main.cpp b/XBOFS.win.qt5/main.cpp index 8e1c532..7678330 100644 --- a/XBOFS.win.qt5/main.cpp +++ b/XBOFS.win.qt5/main.cpp @@ -1,9 +1,13 @@ -#include "XBOFSWinQT5GUI.h" +#include #include +#include + +#include "XBOFSWinQT5GUI.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); + qRegisterMetaType(); XBOFSWinQT5GUI w; w.show(); return a.exec(); From 7831f8fdf032a96040d89f25d6ea15aa0b945796 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Sat, 19 Oct 2019 22:53:34 +0200 Subject: [PATCH 30/50] #2: Fixed typo --- XBOFS.win/src/WinUsbDeviceManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XBOFS.win/src/WinUsbDeviceManager.cpp b/XBOFS.win/src/WinUsbDeviceManager.cpp index 68c8d1a..72e2532 100644 --- a/XBOFS.win/src/WinUsbDeviceManager.cpp +++ b/XBOFS.win/src/WinUsbDeviceManager.cpp @@ -46,7 +46,7 @@ void WinUsbDeviceManager::run() { winUsbDeviceThread->requestInterruption(); logger->info(L"Signalling thread handling {} to terminate", devicePath); winUsbDeviceThread->terminate(); - logger->info(L"Waiting for thread hanlding {} to terminate", devicePath); + logger->info(L"Waiting for thread handling {} to terminate", devicePath); winUsbDeviceThread->wait(); emit winUsbDeviceRemoved(devicePath); delete winUsbDeviceThread; From 2738f14998a0ae0280b593f74d1eaef150eb8a42 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Sat, 19 Oct 2019 22:53:58 +0200 Subject: [PATCH 31/50] #2: Began implementing handling of signals emited by WinUsbDevice instances --- XBOFS.win.qt5/XBOFSWinQT5GUI.cpp | 127 ++++++++++++++++++++++++++++--- XBOFS.win.qt5/XBOFSWinQT5GUI.h | 24 +++++- 2 files changed, 135 insertions(+), 16 deletions(-) diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp index ef56ac9..eecc28b 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp @@ -23,17 +23,58 @@ XBOFSWinQT5GUI::XBOFSWinQT5GUI(QWidget *parent) winUsbDeviceManager = new XBOFSWin::WinUsbDeviceManager(XBOFSWin::get_logger("WinUsbDeviceManager", sinks)); connect(winUsbDeviceManagerThread, &QThread::finished, winUsbDeviceManager, &QObject::deleteLater); connect(winUsbDeviceManagerThread, &QThread::started, winUsbDeviceManager, &XBOFSWin::WinUsbDeviceManager::run); - connect(winUsbDeviceManager, &XBOFSWin::WinUsbDeviceManager::winUsbDeviceManagerScanning, this, &XBOFSWinQT5GUI::winUsbDeviceManagerScanning); - connect(winUsbDeviceManager, &XBOFSWin::WinUsbDeviceManager::winUsbDeviceAdded, this, &XBOFSWinQT5GUI::winUsbDeviceAdded); - connect(winUsbDeviceManager, &XBOFSWin::WinUsbDeviceManager::winUsbDeviceRemoved, this, &XBOFSWinQT5GUI::winUsbDeviceRemoved); - connect(this, &XBOFSWinQT5GUI::destroyed, this, &XBOFSWinQT5GUI::terminateWinUsbDeviceManager); + connect(winUsbDeviceManager, &XBOFSWin::WinUsbDeviceManager::winUsbDeviceManagerScanning, this, &XBOFSWinQT5GUI::handleWinUsbDeviceManagerScanning); + connect(winUsbDeviceManager, &XBOFSWin::WinUsbDeviceManager::winUsbDeviceAdded, this, &XBOFSWinQT5GUI::handleWinUsbDeviceAdded); + connect(winUsbDeviceManager, &XBOFSWin::WinUsbDeviceManager::winUsbDeviceRemoved, this, &XBOFSWinQT5GUI::handleWinUsbDeviceRemoved); + connect(this, &XBOFSWinQT5GUI::destroyed, this, &XBOFSWinQT5GUI::handleTerminateWinUsbDeviceManager); winUsbDeviceManager->moveToThread(winUsbDeviceManagerThread); winUsbDeviceManagerThread->start(); } -void XBOFSWinQT5GUI::winUsbDeviceAdded(const std::wstring &devicePath, const XBOFSWin::WinUsbDevice *winUsbDevice) { - // TODO: Connect signals - // TODO: Update UI +//XBOFSWinQT5GUI::~XBOFSWinQT5GUI() { +// logger->info(L"Requesting interruption of thread handling WinUsbDeviceManager"); +// winUsbDeviceManagerThread->requestInterruption(); +// logger->info(L"Signalling thread handling WinUsbDeviceManager to terminate"); +// winUsbDeviceManagerThread->terminate(); +// logger->info(L"Waiting for thread handling WinUsbDeviceManager to terminate"); +// winUsbDeviceManagerThread->wait(); +// delete winUsbDeviceManagerThread; +// for (auto iterator = tabs.begin(); iterator != tabs.end(); ) { +// auto tabWidget = std::get<1>(*iterator); +// auto tabWidgetUi = std::get<2>(*iterator); +// delete tabWidget; +// delete tabWidgetUi; +// iterator = tabs.erase(iterator); +// } +//} + +std::optional>::iterator>> XBOFSWinQT5GUI::getIteratorForDevicePath(const std::wstring &devicePath) { + auto iterator = tabs.begin(); + int index; + for (index = 1; iterator != tabs.end(); index++) { + auto currentDevicePath = std::get<0>(*iterator); + if (currentDevicePath == devicePath) break; + ++iterator; + } + if (iterator == tabs.end()) return std::nullopt; + return std::optional>::iterator>>{std::make_pair(index, iterator)}; +} + +void XBOFSWinQT5GUI::handleWinUsbDeviceAdded(const std::wstring &devicePath, const XBOFSWin::WinUsbDevice *winUsbDevice) { + // Connect signals + connect(winUsbDevice, &XBOFSWin::WinUsbDevice::vigEmConnect, this, &XBOFSWinQT5GUI::handleVigEmConnect); + connect(winUsbDevice, &XBOFSWin::WinUsbDevice::vigEmConnected, this, &XBOFSWinQT5GUI::handleVigEmConnected); + connect(winUsbDevice, &XBOFSWin::WinUsbDevice::vigEmTargetAdd, this, &XBOFSWinQT5GUI::handleVigEmTargetAdd); + connect(winUsbDevice, &XBOFSWin::WinUsbDevice::vigEmTargetAdded, this, &XBOFSWinQT5GUI::handleVigEmTargetAdded); + connect(winUsbDevice, &XBOFSWin::WinUsbDevice::vigEmError, this, &XBOFSWinQT5GUI::handleVigEmError); + connect(winUsbDevice, &XBOFSWin::WinUsbDevice::winUsbDeviceOpen, this, &XBOFSWinQT5GUI::handleWinUsbDeviceOpen); + connect(winUsbDevice, &XBOFSWin::WinUsbDevice::winUsbDeviceOpened, this, &XBOFSWinQT5GUI::handleWinUsbDeviceOpened); + connect(winUsbDevice, &XBOFSWin::WinUsbDevice::winUsbDeviceInit, this, &XBOFSWinQT5GUI::handleWinUsbDeviceInit); + connect(winUsbDevice, &XBOFSWin::WinUsbDevice::winUsbDeviceInitComplete, this, &XBOFSWinQT5GUI::handleWinUsbDeviceInitComplete); + connect(winUsbDevice, &XBOFSWin::WinUsbDevice::winUsbDeviceReadingInput, this, &XBOFSWinQT5GUI::handleWinUsbDeviceReadingInput); + connect(winUsbDevice, &XBOFSWin::WinUsbDevice::winUsbDeviceTerminating, this, &XBOFSWinQT5GUI::handleWinUsbDeviceTerminating); + connect(winUsbDevice, &XBOFSWin::WinUsbDevice::winUsbDeviceError, this, &XBOFSWinQT5GUI::handleWinUsbDeviceError); + // Update UI auto devicePathQString = QString::fromStdWString(devicePath); auto tabWidget = new QWidget(); auto tabWidgetUi = new Ui::WinUsbDeviceWidget(); @@ -44,15 +85,20 @@ void XBOFSWinQT5GUI::winUsbDeviceAdded(const std::wstring &devicePath, const XBO tabs.push_back(std::make_tuple(devicePath, tabWidget, tabWidgetUi)); } -void XBOFSWinQT5GUI::winUsbDeviceRemoved(const std::wstring &devicePath) { - auto iterator = tabs.begin(); +void XBOFSWinQT5GUI::handleWinUsbDeviceRemoved(const std::wstring &devicePath) { + /*auto iterator = tabs.begin(); int index; for (index = 1; iterator != tabs.end(); index++) { auto currentDevicePath = std::get<0>(*iterator); if (currentDevicePath == devicePath) break; ++iterator; } - if (iterator == tabs.end()) return; + if (iterator == tabs.end()) return;*/ + auto optionalIterator = getIteratorForDevicePath(devicePath); + if (!optionalIterator) return; + auto tuple = *optionalIterator; + auto index = tuple.first; + auto iterator = tuple.second; auto tabWidget = std::get<1>(*iterator); auto tabWidgetUi = std::get<2>(*iterator); tabs.erase(iterator); @@ -64,11 +110,68 @@ void XBOFSWinQT5GUI::winUsbDeviceRemoved(const std::wstring &devicePath) { } } -void XBOFSWinQT5GUI::winUsbDeviceManagerScanning() { +void XBOFSWinQT5GUI::handleVigEmConnect(const std::wstring &devicePath) { + auto optionalIterator = getIteratorForDevicePath(devicePath); + if (!optionalIterator) return; + auto iterator = (*optionalIterator).second; + auto tabWidgetUi = std::get<2>(*iterator); + tabWidgetUi->vigEmClientStatus->setText(QString::fromUtf8("Connecting to VigEmBus...")); +} + +void XBOFSWinQT5GUI::handleVigEmConnected(const std::wstring &devicePath) { + auto optionalIterator = getIteratorForDevicePath(devicePath); + if (!optionalIterator) return; + auto iterator = (*optionalIterator).second; + auto tabWidgetUi = std::get<2>(*iterator); + tabWidgetUi->vigEmClientStatus->setText(QString::fromUtf8("Connected to VigEmBus")); +} + +void XBOFSWinQT5GUI::handleVigEmTargetAdd(const std::wstring &devicePath) { +} + +void XBOFSWinQT5GUI::handleVigEmTargetAdded(const std::wstring &devicePath) { +} + +void XBOFSWinQT5GUI::handleVigEmError(const std::wstring &devicePath) { +} + +void XBOFSWinQT5GUI::handleWinUsbDeviceOpen(const std::wstring &devicePath) { +} + +void XBOFSWinQT5GUI::handleWinUsbDeviceOpened(const std::wstring &devicePath) { +} + +void XBOFSWinQT5GUI::handleWinUsbDeviceInit(const std::wstring &devicePath) { +} + +void XBOFSWinQT5GUI::handleWinUsbDeviceInitComplete(const std::wstring &devicePath) { +} + +void XBOFSWinQT5GUI::handleWinUsbDeviceReadingInput(const std::wstring &devicePath) { + auto optionalIterator = getIteratorForDevicePath(devicePath); + if (!optionalIterator) return; + auto iterator = (*optionalIterator).second; + auto tabWidgetUi = std::get<2>(*iterator); + tabWidgetUi->winUsbDeviceStatus->setText(QString::fromUtf8("Reading input...")); +} + +void XBOFSWinQT5GUI::handleWinUsbDeviceTerminating(const std::wstring &devicePath) { +} + +void XBOFSWinQT5GUI::handleWinUsbDeviceError(const std::wstring &devicePath) { +} + +void XBOFSWinQT5GUI::handleWinUsbDeviceManagerScanning() { ui.winUsbDeviceManagerStatus->setText(QString::fromUtf8("Scanning for supported controllers...")); + /*ui.winUsbDeviceManagerStatus->setText(QString::fromUtf8(R"""( +

+ Scanning for supported controllers... +

+ )""")); + */ } -void XBOFSWinQT5GUI::terminateWinUsbDeviceManager() { +void XBOFSWinQT5GUI::handleTerminateWinUsbDeviceManager() { logger->info("Requesting interruption of thread handling WinUsbDeviceManager"); winUsbDeviceManagerThread->requestInterruption(); logger->info("Signalling thread handling WinUsbDeviceManager to terminate"); diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.h b/XBOFS.win.qt5/XBOFSWinQT5GUI.h index 2147523..fa40aac 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.h +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -22,12 +23,25 @@ class XBOFSWinQT5GUI : public QMainWindow public: XBOFSWinQT5GUI(QWidget *parent = Q_NULLPTR); + //~XBOFSWinQT5GUI(); public slots: - void winUsbDeviceAdded(const std::wstring &devicePath, const XBOFSWin::WinUsbDevice *winUsbDevice); - void winUsbDeviceRemoved(const std::wstring &devicePath); - void winUsbDeviceManagerScanning(); - void terminateWinUsbDeviceManager(); + void handleWinUsbDeviceAdded(const std::wstring &devicePath, const XBOFSWin::WinUsbDevice *winUsbDevice); + void handleWinUsbDeviceRemoved(const std::wstring &devicePath); + void handleWinUsbDeviceManagerScanning(); + void handleTerminateWinUsbDeviceManager(); + void handleVigEmConnect(const std::wstring &devicePath); + void handleVigEmConnected(const std::wstring &devicePath); + void handleVigEmTargetAdd(const std::wstring &devicePath); + void handleVigEmTargetAdded(const std::wstring &devicePath); + void handleVigEmError(const std::wstring &devicePath); + void handleWinUsbDeviceOpen(const std::wstring &devicePath); + void handleWinUsbDeviceOpened(const std::wstring &devicePath); + void handleWinUsbDeviceInit(const std::wstring &devicePath); + void handleWinUsbDeviceInitComplete(const std::wstring &devicePath); + void handleWinUsbDeviceReadingInput(const std::wstring &devicePath); + void handleWinUsbDeviceTerminating(const std::wstring &devicePath); + void handleWinUsbDeviceError(const std::wstring &devicePath); protected: Ui::XBOFSWinQT5GUIClass ui; @@ -36,4 +50,6 @@ public slots: XBOFSWin::WinUsbDeviceManager *winUsbDeviceManager; std::vector> tabs; + + std::optional>::iterator>> getIteratorForDevicePath(const std::wstring &devicePath); }; From cd0db5a117f6f6385810ed128d19ab483b049479 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Sat, 19 Oct 2019 23:04:44 +0200 Subject: [PATCH 32/50] #2: Additional UI outputs --- XBOFS.win.qt5/WinUsbDeviceWidget.ui | 16 +++++++++++++++- XBOFS.win.qt5/XBOFSWinQT5GUI.cpp | 16 ++++++++++++++-- XBOFS.win.qt5/XBOFSWinQT5GUI.ui | 20 ++++++++++---------- XBOFS.win/src/WinUsbDevice.cpp | 2 +- 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/XBOFS.win.qt5/WinUsbDeviceWidget.ui b/XBOFS.win.qt5/WinUsbDeviceWidget.ui index 6be0494..1cac4d7 100644 --- a/XBOFS.win.qt5/WinUsbDeviceWidget.ui +++ b/XBOFS.win.qt5/WinUsbDeviceWidget.ui @@ -37,7 +37,7 @@ - <strong>VigEmClient Status:</strong> + <strong>VigEm Client Status:</strong> Qt::AutoText @@ -51,6 +51,20 @@ + + + + <html><head/><body><p><span style=" font-weight:600;">VigEm Target Status:</span></p></body></html> + + + + + + + + + + diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp index eecc28b..1bcd533 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp @@ -115,7 +115,7 @@ void XBOFSWinQT5GUI::handleVigEmConnect(const std::wstring &devicePath) { if (!optionalIterator) return; auto iterator = (*optionalIterator).second; auto tabWidgetUi = std::get<2>(*iterator); - tabWidgetUi->vigEmClientStatus->setText(QString::fromUtf8("Connecting to VigEmBus...")); + tabWidgetUi->vigEmClientStatus->setText(QString::fromUtf8("Connecting...")); } void XBOFSWinQT5GUI::handleVigEmConnected(const std::wstring &devicePath) { @@ -123,16 +123,27 @@ void XBOFSWinQT5GUI::handleVigEmConnected(const std::wstring &devicePath) { if (!optionalIterator) return; auto iterator = (*optionalIterator).second; auto tabWidgetUi = std::get<2>(*iterator); - tabWidgetUi->vigEmClientStatus->setText(QString::fromUtf8("Connected to VigEmBus")); + tabWidgetUi->vigEmClientStatus->setText(QString::fromUtf8("Connected")); } void XBOFSWinQT5GUI::handleVigEmTargetAdd(const std::wstring &devicePath) { + auto optionalIterator = getIteratorForDevicePath(devicePath); + if (!optionalIterator) return; + auto iterator = (*optionalIterator).second; + auto tabWidgetUi = std::get<2>(*iterator); + tabWidgetUi->vigEmTargetStatus->setText(QString::fromUtf8("Adding...")); } void XBOFSWinQT5GUI::handleVigEmTargetAdded(const std::wstring &devicePath) { + auto optionalIterator = getIteratorForDevicePath(devicePath); + if (!optionalIterator) return; + auto iterator = (*optionalIterator).second; + auto tabWidgetUi = std::get<2>(*iterator); + tabWidgetUi->vigEmTargetStatus->setText(QString::fromUtf8("Added")); // TODO: Display more details on target } void XBOFSWinQT5GUI::handleVigEmError(const std::wstring &devicePath) { + // TODO: Note error output in text area } void XBOFSWinQT5GUI::handleWinUsbDeviceOpen(const std::wstring &devicePath) { @@ -159,6 +170,7 @@ void XBOFSWinQT5GUI::handleWinUsbDeviceTerminating(const std::wstring &devicePat } void XBOFSWinQT5GUI::handleWinUsbDeviceError(const std::wstring &devicePath) { + // TODO: Note error output in text area } void XBOFSWinQT5GUI::handleWinUsbDeviceManagerScanning() { diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.ui b/XBOFS.win.qt5/XBOFSWinQT5GUI.ui index 3231a76..1103392 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.ui +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.ui @@ -43,29 +43,29 @@ Status - - + + - <html><head/><body><p><span style=" font-weight:600;">VigEmBus Status:</span></p></body></html> + <html><head/><body><p><span style=" font-weight:600;">WinUSB Device Manager Status:</span></p></body></html> - - + + - - + + - <html><head/><body><p><span style=" font-weight:600;">WinUSB Device Manager Status:</span></p></body></html> + <html><head/><body><p><span style=" font-weight:600;">VigEmBus Status:</span></p></body></html> - - + + diff --git a/XBOFS.win/src/WinUsbDevice.cpp b/XBOFS.win/src/WinUsbDevice.cpp index fa8bceb..0e197b2 100644 --- a/XBOFS.win/src/WinUsbDevice.cpp +++ b/XBOFS.win/src/WinUsbDevice.cpp @@ -137,7 +137,7 @@ bool WinUsbDevice::openDevice() { closeDevice(); return false; } - // TODO: Emit USB descriptor result + // TODO: Emit USB descriptor result logger->info("Opened WinUSB device"); return true; } From e49fc8e1f2f381f3482582feaf4c62cf1471f5c8 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Sun, 20 Oct 2019 22:07:22 +0200 Subject: [PATCH 33/50] #2: Read manufacturer, product and serial number USB string descriptors and emit signal with device info --- XBOFS.win/include/XBOFS.win/WinUsbDevice.h | 13 ++++++++++- XBOFS.win/src/WinUsbDevice.cpp | 27 +++++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/XBOFS.win/include/XBOFS.win/WinUsbDevice.h b/XBOFS.win/include/XBOFS.win/WinUsbDevice.h index cd07369..4701212 100644 --- a/XBOFS.win/include/XBOFS.win/WinUsbDevice.h +++ b/XBOFS.win/include/XBOFS.win/WinUsbDevice.h @@ -4,6 +4,12 @@ #include namespace XBOFSWin { + struct USB_STRING_DESCRIPTOR { + UCHAR bLength; + UCHAR bDescriptorType; + WCHAR bString[255]; + }; + /* */ class WinUsbDevice : public QObject @@ -24,6 +30,8 @@ namespace XBOFSWin { void vigEmTargetAdded(const std::wstring &devicePath); void vigEmError(const std::wstring &devicePath); void winUsbDeviceOpen(const std::wstring &devicePath); + void winUsbDeviceInfo(const std::wstring &devicePath, quint16 vendorId, quint16 productId, + const std::wstring &manufacturer, const std::wstring &product, const std::wstring &serialNumber); void winUsbDeviceOpened(const std::wstring &devicePath); void winUsbDeviceInit(const std::wstring &devicePath); void winUsbDeviceInitComplete(const std::wstring &devicePath); @@ -34,6 +42,9 @@ namespace XBOFSWin { protected: const std::shared_ptr logger; const std::wstring devicePath; + std::wstring manufacturer = L"Unknown"; + std::wstring product = L"Unknown"; + std::wstring serialNumber = L"Unknown"; bool deviceHandlesOpen = false; UCHAR XBO_ARCADE_STICK_INIT[5] = { 0x05, 0x20, 0x08, 0x01, 0x05 }; @@ -42,7 +53,7 @@ namespace XBOFSWin { PVIGEM_CLIENT vigEmClient = NULL; PVIGEM_TARGET vigEmTarget = NULL; WINUSB_INTERFACE_HANDLE winUsbHandle; - HANDLE deviceHandle; + HANDLE deviceHandle; bool openDevice(); bool closeDevice(); diff --git a/XBOFS.win/src/WinUsbDevice.cpp b/XBOFS.win/src/WinUsbDevice.cpp index 0e197b2..f631880 100644 --- a/XBOFS.win/src/WinUsbDevice.cpp +++ b/XBOFS.win/src/WinUsbDevice.cpp @@ -136,8 +136,33 @@ bool WinUsbDevice::openDevice() { logger->error("Failed to read USB descriptor"); closeDevice(); return false; - } + } + // Get Manufacturer string + USB_STRING_DESCRIPTOR manufacturerDescriptor; + winUsbGetDescriptorResult = WinUsb_GetDescriptor( + winUsbHandle, USB_STRING_DESCRIPTOR_TYPE, winUsbDeviceDescriptor.iManufacturer, 0x0409, (PBYTE)&manufacturerDescriptor, sizeof(manufacturerDescriptor), &bytesReceived + ); + if (winUsbGetDescriptorResult == TRUE) { + manufacturer = std::wstring(manufacturerDescriptor.bString); + } + // Get Product string + USB_STRING_DESCRIPTOR productDescriptor; + winUsbGetDescriptorResult = WinUsb_GetDescriptor( + winUsbHandle, USB_STRING_DESCRIPTOR_TYPE, winUsbDeviceDescriptor.iProduct, 0x0409, (PBYTE)&productDescriptor, sizeof(productDescriptor), &bytesReceived + ); + if (winUsbGetDescriptorResult == TRUE) { + product = std::wstring(productDescriptor.bString); + } + // Get Serial number string + USB_STRING_DESCRIPTOR serialNumberDescriptor; + winUsbGetDescriptorResult = WinUsb_GetDescriptor( + winUsbHandle, USB_STRING_DESCRIPTOR_TYPE, winUsbDeviceDescriptor.iSerialNumber, 0x0409, (PBYTE)&serialNumberDescriptor, sizeof(serialNumberDescriptor), &bytesReceived + ); + if (winUsbGetDescriptorResult == TRUE) { + serialNumber = std::wstring(serialNumberDescriptor.bString); + } // TODO: Emit USB descriptor result + emit winUsbDeviceInfo(devicePath, (quint16)winUsbDeviceDescriptor.idVendor, (quint16)winUsbDeviceDescriptor.idProduct, manufacturer, product, serialNumber); logger->info("Opened WinUSB device"); return true; } From 0d79e9937390b386215b6fbf26e797a05cb3a6b8 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Sun, 20 Oct 2019 22:07:37 +0200 Subject: [PATCH 34/50] #2: Display additional device info --- XBOFS.win.qt5/WinUsbDeviceWidget.ui | 83 ++++++++++++++++++++++++++++- XBOFS.win.qt5/XBOFSWinQT5GUI.cpp | 14 +++++ XBOFS.win.qt5/XBOFSWinQT5GUI.h | 2 + 3 files changed, 97 insertions(+), 2 deletions(-) diff --git a/XBOFS.win.qt5/WinUsbDeviceWidget.ui b/XBOFS.win.qt5/WinUsbDeviceWidget.ui index 1cac4d7..e98d87f 100644 --- a/XBOFS.win.qt5/WinUsbDeviceWidget.ui +++ b/XBOFS.win.qt5/WinUsbDeviceWidget.ui @@ -6,8 +6,8 @@ 0 0 - 400 - 300 + 456 + 378 @@ -68,6 +68,85 @@ + + + + Device + + + + + + <html><head/><body><p><span style=" font-weight:600;">Vendor ID:</span></p></body></html> + + + + + + + <html><head/><body><p><span style=" font-weight:600;">Product ID:</span></p></body></html> + + + + + + + + + + + + + + <html><head/><body><p><span style=" font-weight:600;">Manufacturer:</span></p></body></html> + + + + + + + + + + + + + + <html><head/><body><p><span style=" font-weight:600;">Product:</span></p></body></html> + + + + + + + + + + + + + + <html><head/><body><p><span style=" font-weight:600;">Serial No:</span></p></body></html> + + + + + + + + + + + + + + + + + + + + diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp index 1bcd533..773c66a 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp @@ -69,6 +69,7 @@ void XBOFSWinQT5GUI::handleWinUsbDeviceAdded(const std::wstring &devicePath, con connect(winUsbDevice, &XBOFSWin::WinUsbDevice::vigEmError, this, &XBOFSWinQT5GUI::handleVigEmError); connect(winUsbDevice, &XBOFSWin::WinUsbDevice::winUsbDeviceOpen, this, &XBOFSWinQT5GUI::handleWinUsbDeviceOpen); connect(winUsbDevice, &XBOFSWin::WinUsbDevice::winUsbDeviceOpened, this, &XBOFSWinQT5GUI::handleWinUsbDeviceOpened); + connect(winUsbDevice, &XBOFSWin::WinUsbDevice::winUsbDeviceInfo, this, &XBOFSWinQT5GUI::handleWinUsbDeviceInfo); connect(winUsbDevice, &XBOFSWin::WinUsbDevice::winUsbDeviceInit, this, &XBOFSWinQT5GUI::handleWinUsbDeviceInit); connect(winUsbDevice, &XBOFSWin::WinUsbDevice::winUsbDeviceInitComplete, this, &XBOFSWinQT5GUI::handleWinUsbDeviceInitComplete); connect(winUsbDevice, &XBOFSWin::WinUsbDevice::winUsbDeviceReadingInput, this, &XBOFSWinQT5GUI::handleWinUsbDeviceReadingInput); @@ -149,6 +150,19 @@ void XBOFSWinQT5GUI::handleVigEmError(const std::wstring &devicePath) { void XBOFSWinQT5GUI::handleWinUsbDeviceOpen(const std::wstring &devicePath) { } +void XBOFSWinQT5GUI::handleWinUsbDeviceInfo(const std::wstring &devicePath, quint16 vendorId, quint16 productId, + const std::wstring &manufacturer, const std::wstring &product, const std::wstring &serialNumber) { + auto optionalIterator = getIteratorForDevicePath(devicePath); + if (!optionalIterator) return; + auto iterator = (*optionalIterator).second; + auto tabWidgetUi = std::get<2>(*iterator); + tabWidgetUi->vendorIdLabel->setText(QString::fromStdString(fmt::format("0x{:04X}", vendorId))); + tabWidgetUi->productIdLabel->setText(QString::fromStdString(fmt::format("0x{:04X}", productId))); + tabWidgetUi->manufacturerLabel->setText(QString::fromStdWString(manufacturer)); + tabWidgetUi->productLabel->setText(QString::fromStdWString(product)); + tabWidgetUi->serialNumberLabel->setText(QString::fromStdWString(serialNumber)); +} + void XBOFSWinQT5GUI::handleWinUsbDeviceOpened(const std::wstring &devicePath) { } diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.h b/XBOFS.win.qt5/XBOFSWinQT5GUI.h index fa40aac..9ed7c1d 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.h +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.h @@ -36,6 +36,8 @@ public slots: void handleVigEmTargetAdded(const std::wstring &devicePath); void handleVigEmError(const std::wstring &devicePath); void handleWinUsbDeviceOpen(const std::wstring &devicePath); + void handleWinUsbDeviceInfo(const std::wstring &devicePath, quint16 vendorId, quint16 productId, + const std::wstring &manufacturer, const std::wstring &product, const std::wstring &serialNumber); void handleWinUsbDeviceOpened(const std::wstring &devicePath); void handleWinUsbDeviceInit(const std::wstring &devicePath); void handleWinUsbDeviceInitComplete(const std::wstring &devicePath); From 816bc68fd5405de9204c107215f2fb46e9e48d15 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Mon, 21 Oct 2019 16:39:03 +0200 Subject: [PATCH 35/50] #2: Fixed retrieval of USB string descriptors --- XBOFS.win/include/XBOFS.win/WinUsbDevice.h | 9 +++-- XBOFS.win/src/WinUsbDevice.cpp | 47 ++++++++++++---------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/XBOFS.win/include/XBOFS.win/WinUsbDevice.h b/XBOFS.win/include/XBOFS.win/WinUsbDevice.h index 4701212..5ec0363 100644 --- a/XBOFS.win/include/XBOFS.win/WinUsbDevice.h +++ b/XBOFS.win/include/XBOFS.win/WinUsbDevice.h @@ -2,16 +2,17 @@ #include #include #include +#include namespace XBOFSWin { - struct USB_STRING_DESCRIPTOR { + struct STATIC_USB_STRING_DESCRIPTOR { UCHAR bLength; UCHAR bDescriptorType; - WCHAR bString[255]; + WCHAR bString[126]; }; - /* - */ + std::optional getWinUsbStringDescriptor(const WINUSB_INTERFACE_HANDLE &winUsbHandle, UCHAR index, USHORT languageId); + class WinUsbDevice : public QObject { Q_OBJECT diff --git a/XBOFS.win/src/WinUsbDevice.cpp b/XBOFS.win/src/WinUsbDevice.cpp index f631880..149c0b4 100644 --- a/XBOFS.win/src/WinUsbDevice.cpp +++ b/XBOFS.win/src/WinUsbDevice.cpp @@ -5,6 +5,24 @@ #include "XBOFS.win\WinUsbDevice.h" using namespace XBOFSWin; + +/* +Retrives a USB String Descriptor value +*/ +std::optional XBOFSWin::getWinUsbStringDescriptor(const WINUSB_INTERFACE_HANDLE &winUsbHandle, UCHAR index, USHORT languageId) { + STATIC_USB_STRING_DESCRIPTOR stringDescriptor; + stringDescriptor.bLength = 255; + ULONG bytesReceived; + BOOL winUsbGetDescriptorResult = WinUsb_GetDescriptor( + winUsbHandle, USB_STRING_DESCRIPTOR_TYPE, index, languageId, (PBYTE)&stringDescriptor, stringDescriptor.bLength, &bytesReceived + ); + if (winUsbGetDescriptorResult == FALSE) return std::nullopt; + UCHAR bStringBufferContentSize = stringDescriptor.bLength - sizeof(USB_STRING_DESCRIPTOR); + WCHAR dest[126]; + wcsncpy_s(dest, 126, stringDescriptor.bString, bStringBufferContentSize); + return std::optional{std::wstring(dest)}; +} + /* Constructs the WinUsbDevice instance and starts its event loop in a separate thread */ @@ -138,30 +156,15 @@ bool WinUsbDevice::openDevice() { return false; } // Get Manufacturer string - USB_STRING_DESCRIPTOR manufacturerDescriptor; - winUsbGetDescriptorResult = WinUsb_GetDescriptor( - winUsbHandle, USB_STRING_DESCRIPTOR_TYPE, winUsbDeviceDescriptor.iManufacturer, 0x0409, (PBYTE)&manufacturerDescriptor, sizeof(manufacturerDescriptor), &bytesReceived - ); - if (winUsbGetDescriptorResult == TRUE) { - manufacturer = std::wstring(manufacturerDescriptor.bString); - } + auto optionalManufacturer = getWinUsbStringDescriptor(winUsbHandle, winUsbDeviceDescriptor.iManufacturer, 0x0409); + if (optionalManufacturer) manufacturer = *optionalManufacturer; // Get Product string - USB_STRING_DESCRIPTOR productDescriptor; - winUsbGetDescriptorResult = WinUsb_GetDescriptor( - winUsbHandle, USB_STRING_DESCRIPTOR_TYPE, winUsbDeviceDescriptor.iProduct, 0x0409, (PBYTE)&productDescriptor, sizeof(productDescriptor), &bytesReceived - ); - if (winUsbGetDescriptorResult == TRUE) { - product = std::wstring(productDescriptor.bString); - } + auto optionalProduct = getWinUsbStringDescriptor(winUsbHandle, winUsbDeviceDescriptor.iProduct, 0x0409); + if (optionalProduct) product = *optionalProduct; // Get Serial number string - USB_STRING_DESCRIPTOR serialNumberDescriptor; - winUsbGetDescriptorResult = WinUsb_GetDescriptor( - winUsbHandle, USB_STRING_DESCRIPTOR_TYPE, winUsbDeviceDescriptor.iSerialNumber, 0x0409, (PBYTE)&serialNumberDescriptor, sizeof(serialNumberDescriptor), &bytesReceived - ); - if (winUsbGetDescriptorResult == TRUE) { - serialNumber = std::wstring(serialNumberDescriptor.bString); - } - // TODO: Emit USB descriptor result + auto optionalSerialNumber = getWinUsbStringDescriptor(winUsbHandle, winUsbDeviceDescriptor.iSerialNumber, 0x0409); + if (optionalSerialNumber) serialNumber = *optionalSerialNumber; + // Emit USB descriptor result emit winUsbDeviceInfo(devicePath, (quint16)winUsbDeviceDescriptor.idVendor, (quint16)winUsbDeviceDescriptor.idProduct, manufacturer, product, serialNumber); logger->info("Opened WinUSB device"); return true; From 4481586c6771da61da13c29d7a3316bf85a4eef4 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Tue, 22 Oct 2019 22:49:15 +0200 Subject: [PATCH 36/50] #2: Added application icon --- XBOFS.win.qt5/Resources/XBOFS.win.ico | Bin 0 -> 120113 bytes XBOFS.win.qt5/Resources/XBOFS.win.svg | 108 ++++++++++++++++++++ XBOFS.win.qt5/WinUsbDeviceWidget.ui | 60 ++++++++++- XBOFS.win.qt5/XBOFS.win.qt5.rc | Bin 0 -> 3296 bytes XBOFS.win.qt5/XBOFS.win.qt5.vcxproj | 42 ++++++++ XBOFS.win.qt5/XBOFS.win.qt5.vcxproj.filters | 18 ++++ XBOFS.win.qt5/XBOFSWinQT5GUI.cpp | 35 +------ XBOFS.win.qt5/XBOFSWinQT5GUI.h | 2 +- XBOFS.win.qt5/XBOFSWinQT5GUI.ui | 4 + XBOFS.win.qt5/resource.h | 17 +++ 10 files changed, 249 insertions(+), 37 deletions(-) create mode 100644 XBOFS.win.qt5/Resources/XBOFS.win.ico create mode 100644 XBOFS.win.qt5/Resources/XBOFS.win.svg create mode 100644 XBOFS.win.qt5/XBOFS.win.qt5.rc create mode 100644 XBOFS.win.qt5/resource.h diff --git a/XBOFS.win.qt5/Resources/XBOFS.win.ico b/XBOFS.win.qt5/Resources/XBOFS.win.ico new file mode 100644 index 0000000000000000000000000000000000000000..6862ca0db3db2b41a714859ecee5aa384f29681a GIT binary patch literal 120113 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

io>Y=sR?gjXej>rXeqAl+31k$ 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 z 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 + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + diff --git a/XBOFS.win.qt5/WinUsbDeviceWidget.ui b/XBOFS.win.qt5/WinUsbDeviceWidget.ui index e98d87f..7d38eb5 100644 --- a/XBOFS.win.qt5/WinUsbDeviceWidget.ui +++ b/XBOFS.win.qt5/WinUsbDeviceWidget.ui @@ -6,8 +6,8 @@ 0 0 - 456 - 378 + 523 + 452 @@ -68,10 +68,64 @@ + + + + Virtual Device + + + + + + <html><head/><body><p><span style=" font-weight:600;">Vendor ID:</span></p></body></html> + + + + + + + QFrame::NoFrame + + + + + + + + + + <html><head/><body><p><span style=" font-weight:600;">Product ID:</span></p></body></html> + + + + + + + + + + + + + + <html><head/><body><p><span style=" font-weight:600;">Player Number:</span></p></body></html> + + + + + + + + + + + + + - Device + Physical Device diff --git a/XBOFS.win.qt5/XBOFS.win.qt5.rc b/XBOFS.win.qt5/XBOFS.win.qt5.rc new file mode 100644 index 0000000000000000000000000000000000000000..f40c29f67156622eb7813137cdf771eef4c815fc GIT binary patch literal 3296 zcmdUxZI2Q$5Xa}WiQl2Z8!sC0;S(HiheQs|0T)e3$OS}6cnS*|6FsrtH*07env7uet1=I%PjK?BC!W z`Jso^@A$W{x{jUl#|88ke7iUD`#bpJ{Tt-&`@eV|iyDRK*y1Zi(geFti;d{{xNoug zsGZ|>7v$fJn@WfQXOFq_1+$okT}B}*6FbX^f+UkuQ}3@w_i}5@j^}nsRE3=Cl0SX2 ztIo+oqN`2QzTar>pk$9du8H9xQXS;a{HblI9jJZ!rjOG|34BfVZjr06>?wP9;j2Lj zGP@i@Rzp^Y2#_s@BS86agC(yywejC!L4KCUm{70pIX=thbbh^t?M;sq;&y4vN8W)w zOVL;N7d$2@;mRpz*v3Equh}UNEi;}hBZq@l))O+MWr6n`f0jeEZ#ErAhzINF9*J=gF zt*;JLG3Yg4r@R{^o%gV+4ZT1P&J|5aH}Z%!_1ZW;&*~Lkx;Z?mDiS1v-U)MQqIXpK zNJhKPXsS-WrmJ)M@HR^07MRrn?Nmo7qmH%6cZ?^yRss|6S4j!6J^*V~rCfq%l{6CZNOt7K-@kbgvawLCag4jz3ri;67l+>a{roGIMOkfPzQz7;D?j1k@yajyF{?$f V#?tUG-Y&>w<@0`>w}0>2=_f|YiQNDI literal 0 HcmV?d00001 diff --git a/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj b/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj index 064daf6..db698cc 100644 --- a/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj +++ b/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj @@ -87,6 +87,13 @@ Rcc'ing %(Identity)... .\GeneratedFiles\qrc_%(Filename).cpp + + + + + + $(OutDir)Resources + @@ -121,6 +128,13 @@ Rcc'ing %(Identity)... .\GeneratedFiles\qrc_%(Filename).cpp + + + + + + $(OutDir)Resources + @@ -136,6 +150,34 @@ + + + false + true + false + true + + + + + + + + true + false + false + true + + + + + false + true + Document + false + true + + diff --git a/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj.filters b/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj.filters index 91bc0ac..89c800e 100644 --- a/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj.filters +++ b/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj.filters @@ -46,4 +46,22 @@ Resource Files + + + Resource Files + + + + + Header Files + + + + + + + + Resource Files + + \ No newline at end of file diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp index 773c66a..c0e46cd 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp @@ -9,7 +9,7 @@ XBOFSWinQT5GUI::XBOFSWinQT5GUI(QWidget *parent) : QMainWindow(parent) -{ +{ ui.setupUi(this); // Configure logging auto sinks = std::vector(); @@ -31,23 +31,6 @@ XBOFSWinQT5GUI::XBOFSWinQT5GUI(QWidget *parent) winUsbDeviceManagerThread->start(); } -//XBOFSWinQT5GUI::~XBOFSWinQT5GUI() { -// logger->info(L"Requesting interruption of thread handling WinUsbDeviceManager"); -// winUsbDeviceManagerThread->requestInterruption(); -// logger->info(L"Signalling thread handling WinUsbDeviceManager to terminate"); -// winUsbDeviceManagerThread->terminate(); -// logger->info(L"Waiting for thread handling WinUsbDeviceManager to terminate"); -// winUsbDeviceManagerThread->wait(); -// delete winUsbDeviceManagerThread; -// for (auto iterator = tabs.begin(); iterator != tabs.end(); ) { -// auto tabWidget = std::get<1>(*iterator); -// auto tabWidgetUi = std::get<2>(*iterator); -// delete tabWidget; -// delete tabWidgetUi; -// iterator = tabs.erase(iterator); -// } -//} - std::optional>::iterator>> XBOFSWinQT5GUI::getIteratorForDevicePath(const std::wstring &devicePath) { auto iterator = tabs.begin(); int index; @@ -86,15 +69,7 @@ void XBOFSWinQT5GUI::handleWinUsbDeviceAdded(const std::wstring &devicePath, con tabs.push_back(std::make_tuple(devicePath, tabWidget, tabWidgetUi)); } -void XBOFSWinQT5GUI::handleWinUsbDeviceRemoved(const std::wstring &devicePath) { - /*auto iterator = tabs.begin(); - int index; - for (index = 1; iterator != tabs.end(); index++) { - auto currentDevicePath = std::get<0>(*iterator); - if (currentDevicePath == devicePath) break; - ++iterator; - } - if (iterator == tabs.end()) return;*/ +void XBOFSWinQT5GUI::handleWinUsbDeviceRemoved(const std::wstring &devicePath) { auto optionalIterator = getIteratorForDevicePath(devicePath); if (!optionalIterator) return; auto tuple = *optionalIterator; @@ -189,12 +164,6 @@ void XBOFSWinQT5GUI::handleWinUsbDeviceError(const std::wstring &devicePath) { void XBOFSWinQT5GUI::handleWinUsbDeviceManagerScanning() { ui.winUsbDeviceManagerStatus->setText(QString::fromUtf8("Scanning for supported controllers...")); - /*ui.winUsbDeviceManagerStatus->setText(QString::fromUtf8(R"""( -

- Scanning for supported controllers... -

- )""")); - */ } void XBOFSWinQT5GUI::handleTerminateWinUsbDeviceManager() { diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.h b/XBOFS.win.qt5/XBOFSWinQT5GUI.h index 9ed7c1d..d8cd915 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.h +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.h @@ -45,7 +45,7 @@ public slots: void handleWinUsbDeviceTerminating(const std::wstring &devicePath); void handleWinUsbDeviceError(const std::wstring &devicePath); -protected: +protected: Ui::XBOFSWinQT5GUIClass ui; std::shared_ptr logger; QThread *winUsbDeviceManagerThread; diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.ui b/XBOFS.win.qt5/XBOFSWinQT5GUI.ui index 1103392..beb1ef7 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.ui +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.ui @@ -13,6 +13,10 @@ XBOFS.win + + + Resources/XBOFS.win.svgResources/XBOFS.win.svg + diff --git a/XBOFS.win.qt5/resource.h b/XBOFS.win.qt5/resource.h new file mode 100644 index 0000000..5c4125d --- /dev/null +++ b/XBOFS.win.qt5/resource.h @@ -0,0 +1,17 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by XBOFS.win.qt5.rc +// +#define IDI_ICON2 102 +#define IDI_ICON1 102 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 103 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif From 3c80fdd1351da4e028cd3333c088eb3c431e3a1b Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Tue, 22 Oct 2019 22:49:42 +0200 Subject: [PATCH 37/50] #2: Attribute application icon as per https://support.flaticon.com/hc/en-us/articles/207248209-How-I-must-insert-the-attribution- --- docs/index.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index f719d24..118cbd7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -156,9 +156,12 @@ It's very likely. Take a look at the [device contributor guide](/device_contribu * [Device Monitoring Studio](https://www.hhdsoftware.com/device-monitoring-studio) * [Microsoft Visual Studio Community 2017](https://visualstudio.microsoft.com) * [ZaDig](https://zadig.akeo.ie) + * [InkScape](https://inkscape.org) * **Testing** * Multiple Atrox support * [Manick74](https://www.reddit.com/user/Manick74) * [TheDecn](https://www.reddit.com/user/TheDecn) * Madcatz TE2 XBO data collection - * [Fodenn](https://www.reddit.com/user/Fodenn) \ No newline at end of file + * [Fodenn](https://www.reddit.com/user/Fodenn) +* **Graphics** + * Icon made by Freepik from [www.flaticon.com](https://www.flaticon.com) and modified by [OOPMan](https://github.com/OOPMan) \ No newline at end of file From f06b8d1512f02007df51e2523c94a3cc1ef29d19 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Wed, 23 Oct 2019 23:23:09 +0200 Subject: [PATCH 38/50] #2: WIP tray icon --- XBOFS.win.qt5/XBOFSWinQT5GUI.cpp | 48 ++++++++++++++++++++++++++++++++ XBOFS.win.qt5/XBOFSWinQT5GUI.h | 12 +++++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp index c0e46cd..4ea89db 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include #include #include @@ -11,6 +13,7 @@ XBOFSWinQT5GUI::XBOFSWinQT5GUI(QWidget *parent) : QMainWindow(parent) { ui.setupUi(this); + connect(ui.actionExit, &QAction::triggered, this, &XBOFSWinQT5GUI::handleSystemTrayMenuExit); // Configure logging auto sinks = std::vector(); auto rotatingFileSink = std::make_shared("xbofs.win.qt5.log", 1024 * 1024 * 10, 10); @@ -29,6 +32,16 @@ XBOFSWinQT5GUI::XBOFSWinQT5GUI(QWidget *parent) connect(this, &XBOFSWinQT5GUI::destroyed, this, &XBOFSWinQT5GUI::handleTerminateWinUsbDeviceManager); winUsbDeviceManager->moveToThread(winUsbDeviceManagerThread); winUsbDeviceManagerThread->start(); + // System tray icon + if (QSystemTrayIcon::isSystemTrayAvailable()) { + systemTrayIcon = new QSystemTrayIcon(windowIcon(), this); + auto systemTrayIconMenu = new QMenu(this); + auto restoreAction = systemTrayIconMenu->addAction("Restore"); + connect(restoreAction, &QAction::triggered, this, &XBOFSWinQT5GUI::handleSystemTrayMenuRestore); + auto exitAction = systemTrayIconMenu->addAction("Exit"); + connect(exitAction, &QAction::triggered, this, &XBOFSWinQT5GUI::handleSystemTrayMenuExit); + systemTrayIcon->setContextMenu(systemTrayIconMenu); + } } std::optional>::iterator>> XBOFSWinQT5GUI::getIteratorForDevicePath(const std::wstring &devicePath) { @@ -174,4 +187,39 @@ void XBOFSWinQT5GUI::handleTerminateWinUsbDeviceManager() { logger->info("Waiting for thread hanlding WinUsbDeviceManager to terminate"); winUsbDeviceManagerThread->wait(); logger->flush(); +} + +void XBOFSWinQT5GUI::handleSystemTrayMenuRestore(const bool &checked) { + setWindowFlags(previousFlags); + showNormal(); +} + + +void XBOFSWinQT5GUI::handleSystemTrayMenuExit(const bool &checked) { + QApplication::quit(); + handleTerminateWinUsbDeviceManager(); +} + +void XBOFSWinQT5GUI::closeEvent(QCloseEvent *event) { + QMessageBox::information(this, tr("Systray"), + tr("The program will keep running in the " + "system tray. To terminate the program, " + "choose Quit in the context menu " + "of the system tray entry.")); + hide(); + event->ignore(); +} + +void XBOFSWinQT5GUI::hideEvent(QHideEvent *event) { + if (QSystemTrayIcon::isSystemTrayAvailable()) { + systemTrayIcon->show(); + previousFlags = windowFlags(); + setWindowFlags(Qt::ToolTip); + } +} + +void XBOFSWinQT5GUI::showEvent(QShowEvent *event) { + if (QSystemTrayIcon::isSystemTrayAvailable()) { + systemTrayIcon->hide(); + } } \ No newline at end of file diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.h b/XBOFS.win.qt5/XBOFSWinQT5GUI.h index d8cd915..20ed6ff 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.h +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.h @@ -6,6 +6,7 @@ #include #include +#include #include "ui_XBOFSWinQT5GUI.h" #include "ui_WinUsbDeviceWidget.h" @@ -45,8 +46,13 @@ public slots: void handleWinUsbDeviceTerminating(const std::wstring &devicePath); void handleWinUsbDeviceError(const std::wstring &devicePath); + void handleSystemTrayMenuRestore(const bool &checked); + void handleSystemTrayMenuExit(const bool &checked); + protected: - Ui::XBOFSWinQT5GUIClass ui; + Ui::XBOFSWinQT5GUIClass ui; + QSystemTrayIcon *systemTrayIcon; + Qt::WindowFlags previousFlags; std::shared_ptr logger; QThread *winUsbDeviceManagerThread; XBOFSWin::WinUsbDeviceManager *winUsbDeviceManager; @@ -54,4 +60,8 @@ public slots: std::vector> tabs; std::optional>::iterator>> getIteratorForDevicePath(const std::wstring &devicePath); + + void closeEvent(QCloseEvent *event); + void hideEvent(QHideEvent *event); + void showEvent(QShowEvent *event); }; From ab989a7175aea11e88667bb85107c0c2ec989204 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Thu, 24 Oct 2019 13:46:36 +0200 Subject: [PATCH 39/50] #2: Replace usage of QThread::terminate with QThread::quit in WinUsbDeviceManager --- XBOFS.win/src/WinUsbDeviceManager.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/XBOFS.win/src/WinUsbDeviceManager.cpp b/XBOFS.win/src/WinUsbDeviceManager.cpp index 72e2532..2d1b6f2 100644 --- a/XBOFS.win/src/WinUsbDeviceManager.cpp +++ b/XBOFS.win/src/WinUsbDeviceManager.cpp @@ -44,9 +44,9 @@ void WinUsbDeviceManager::run() { if (devicePaths.find(devicePath) == devicePaths.end()) { logger->info(L"Requesting interruption of thread handling {}", devicePath); winUsbDeviceThread->requestInterruption(); - logger->info(L"Signalling thread handling {} to terminate", devicePath); - winUsbDeviceThread->terminate(); - logger->info(L"Waiting for thread handling {} to terminate", devicePath); + logger->info(L"Signalling thread handling {} to quit", devicePath); + winUsbDeviceThread->quit(); + logger->info(L"Waiting for thread handling {} to quit", devicePath); winUsbDeviceThread->wait(); emit winUsbDeviceRemoved(devicePath); delete winUsbDeviceThread; @@ -72,9 +72,9 @@ void WinUsbDeviceManager::run() { auto winUsbDeviceThread = tuple.second.first; logger->info(L"Requesting interruption of thread handling {}", devicePath); winUsbDeviceThread->requestInterruption(); - logger->info(L"Signalling thread handling {} to terminate", devicePath); - winUsbDeviceThread->terminate(); - logger->info(L"Waiting for thread hanlding {} to terminate", devicePath); + logger->info(L"Signalling thread handling {} to quit", devicePath); + winUsbDeviceThread->quit(); + logger->info(L"Waiting for thread hanlding {} to quit", devicePath); winUsbDeviceThread->wait(); delete winUsbDeviceThread; } From 9d060d7706cb135c3bdabb69cd72661a61d6a2d1 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Fri, 25 Oct 2019 22:04:16 +0200 Subject: [PATCH 40/50] #2: Implemented settings, fixed minimize to tray and application exit --- XBOFS.win.qt5/XBOFSWinQT5GUI.cpp | 85 ++++++++++++++++++++++---------- XBOFS.win.qt5/XBOFSWinQT5GUI.h | 28 +++++++++-- XBOFS.win.qt5/XBOFSWinQT5GUI.ui | 39 ++++++++++++++- XBOFS.win.qt5/main.cpp | 19 +++++-- 4 files changed, 137 insertions(+), 34 deletions(-) diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp index 4ea89db..0826924 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp @@ -5,25 +5,34 @@ #include #include #include +#include #include #include #include -XBOFSWinQT5GUI::XBOFSWinQT5GUI(QWidget *parent) -: QMainWindow(parent) +XBOFSWinQT5GUI::XBOFSWinQT5GUI(std::shared_ptr logger, QWidget *parent) +: QMainWindow(parent), logger(logger) { + // Settings + settings = new QSettings("OOPMan", "XBOFS.win", this); + autostart = settings->value(SETTINGS_AUTOSTART, false).toBool(); + startMinimized = settings->value(SETTINGS_START_MINIMIZED, false).toBool(); + minimizeOnClose = settings->value(SETTINGS_MINIMIZE_ON_CLOSE, false).toBool(); + minimizeToTray = settings->value(SETTINGS_MINIMIZE_TO_TRAY, true).toBool(); + // UI ui.setupUi(this); + ui.autostartCheckBox->setChecked(autostart); + ui.startMinimizedCheckbox->setChecked(startMinimized); + ui.minimizeOnCloseCheckBox->setChecked(minimizeOnClose); + ui.minimizeToTrayCheckbox->setChecked(minimizeToTray); connect(ui.actionExit, &QAction::triggered, this, &XBOFSWinQT5GUI::handleSystemTrayMenuExit); - // Configure logging - auto sinks = std::vector(); - auto rotatingFileSink = std::make_shared("xbofs.win.qt5.log", 1024 * 1024 * 10, 10); - sinks.push_back(rotatingFileSink); - logger = XBOFSWin::get_logger("XBOFSWin QT5 GUI", sinks); - spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%t] [%n] [%l] %v"); - logger->info("Logging initialised"); + connect(ui.autostartCheckBox, &QCheckBox::stateChanged, this, &XBOFSWinQT5GUI::handleAutostartCheckboxStateChanged); + connect(ui.startMinimizedCheckbox, &QCheckBox::stateChanged, this, &XBOFSWinQT5GUI::handleStartMinimizedCheckboxStateChanged); + connect(ui.minimizeOnCloseCheckBox, &QCheckBox::stateChanged, this, &XBOFSWinQT5GUI::handleMinimizeOnCloseCheckboxStateChanged); + connect(ui.minimizeToTrayCheckbox, &QCheckBox::stateChanged, this, &XBOFSWinQT5GUI::handleMinimizeToTrayCheckboStateChanged); // Start WinUsbDeviceManager winUsbDeviceManagerThread = new QThread(); - winUsbDeviceManager = new XBOFSWin::WinUsbDeviceManager(XBOFSWin::get_logger("WinUsbDeviceManager", sinks)); + winUsbDeviceManager = new XBOFSWin::WinUsbDeviceManager(XBOFSWin::get_logger("WinUsbDeviceManager", logger->sinks())); connect(winUsbDeviceManagerThread, &QThread::finished, winUsbDeviceManager, &QObject::deleteLater); connect(winUsbDeviceManagerThread, &QThread::started, winUsbDeviceManager, &XBOFSWin::WinUsbDeviceManager::run); connect(winUsbDeviceManager, &XBOFSWin::WinUsbDeviceManager::winUsbDeviceManagerScanning, this, &XBOFSWinQT5GUI::handleWinUsbDeviceManagerScanning); @@ -41,7 +50,11 @@ XBOFSWinQT5GUI::XBOFSWinQT5GUI(QWidget *parent) auto exitAction = systemTrayIconMenu->addAction("Exit"); connect(exitAction, &QAction::triggered, this, &XBOFSWinQT5GUI::handleSystemTrayMenuExit); systemTrayIcon->setContextMenu(systemTrayIconMenu); + systemTrayIconEnabled = true; } + // Show or hide + show(); + if (startMinimized) hide(); } std::optional>::iterator>> XBOFSWinQT5GUI::getIteratorForDevicePath(const std::wstring &devicePath) { @@ -182,10 +195,11 @@ void XBOFSWinQT5GUI::handleWinUsbDeviceManagerScanning() { void XBOFSWinQT5GUI::handleTerminateWinUsbDeviceManager() { logger->info("Requesting interruption of thread handling WinUsbDeviceManager"); winUsbDeviceManagerThread->requestInterruption(); - logger->info("Signalling thread handling WinUsbDeviceManager to terminate"); - winUsbDeviceManagerThread->terminate(); - logger->info("Waiting for thread hanlding WinUsbDeviceManager to terminate"); + logger->info("Signalling thread handling WinUsbDeviceManager to quit"); + winUsbDeviceManagerThread->quit(); + logger->info("Waiting for thread hanlding WinUsbDeviceManager to quit"); winUsbDeviceManagerThread->wait(); + delete winUsbDeviceManagerThread; logger->flush(); } @@ -195,23 +209,21 @@ void XBOFSWinQT5GUI::handleSystemTrayMenuRestore(const bool &checked) { } -void XBOFSWinQT5GUI::handleSystemTrayMenuExit(const bool &checked) { +void XBOFSWinQT5GUI::handleSystemTrayMenuExit(const bool &checked) { + systemTrayIconEnabled = false; QApplication::quit(); - handleTerminateWinUsbDeviceManager(); } void XBOFSWinQT5GUI::closeEvent(QCloseEvent *event) { - QMessageBox::information(this, tr("Systray"), - tr("The program will keep running in the " - "system tray. To terminate the program, " - "choose Quit in the context menu " - "of the system tray entry.")); - hide(); - event->ignore(); + if (systemTrayIconEnabled && minimizeOnClose) { + hide(); + event->ignore(); + } + else handleSystemTrayMenuExit(true); } -void XBOFSWinQT5GUI::hideEvent(QHideEvent *event) { - if (QSystemTrayIcon::isSystemTrayAvailable()) { +void XBOFSWinQT5GUI::hideEvent(QHideEvent *event) { + if (systemTrayIconEnabled && minimizeToTray) { systemTrayIcon->show(); previousFlags = windowFlags(); setWindowFlags(Qt::ToolTip); @@ -219,7 +231,30 @@ void XBOFSWinQT5GUI::hideEvent(QHideEvent *event) { } void XBOFSWinQT5GUI::showEvent(QShowEvent *event) { - if (QSystemTrayIcon::isSystemTrayAvailable()) { + if (systemTrayIconEnabled) { systemTrayIcon->hide(); } +} + +void XBOFSWinQT5GUI::handleAutostartCheckboxStateChanged(const quint16 state) { + autostart = state == Qt::Checked; + settings->setValue(SETTINGS_AUTOSTART, autostart); + auto autostartSettings = QSettings("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat); + if (autostart) autostartSettings.setValue("XBOFS.win", "\"" + QApplication::applicationFilePath().replace("/", "\\") + "\""); + else autostartSettings.remove("XBOFS.win"); +} + +void XBOFSWinQT5GUI::handleStartMinimizedCheckboxStateChanged(const quint16 state) { + startMinimized = state == Qt::Checked; + settings->setValue(SETTINGS_START_MINIMIZED, startMinimized); +} + +void XBOFSWinQT5GUI::handleMinimizeOnCloseCheckboxStateChanged(const quint16 state) { + minimizeOnClose = state == Qt::Checked; + settings->setValue(SETTINGS_MINIMIZE_ON_CLOSE, minimizeOnClose); +} + +void XBOFSWinQT5GUI::handleMinimizeToTrayCheckboStateChanged(const quint16 state) { + minimizeToTray = state == Qt::Checked; + settings->setValue(SETTINGS_MINIMIZE_TO_TRAY, minimizeToTray); } \ No newline at end of file diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.h b/XBOFS.win.qt5/XBOFSWinQT5GUI.h index 20ed6ff..3dc5898 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.h +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.h @@ -7,6 +7,8 @@ #include #include +#include +#include #include "ui_XBOFSWinQT5GUI.h" #include "ui_WinUsbDeviceWidget.h" @@ -18,13 +20,18 @@ namespace std { }; } + +const QString SETTINGS_AUTOSTART("autostart"); +const QString SETTINGS_START_MINIMIZED("startMinimized"); +const QString SETTINGS_MINIMIZE_TO_TRAY("minimizeToTray"); +const QString SETTINGS_MINIMIZE_ON_CLOSE("minimizeOnClose"); + class XBOFSWinQT5GUI : public QMainWindow { Q_OBJECT public: - XBOFSWinQT5GUI(QWidget *parent = Q_NULLPTR); - //~XBOFSWinQT5GUI(); + XBOFSWinQT5GUI(std::shared_ptr logger, QWidget *parent = Q_NULLPTR); public slots: void handleWinUsbDeviceAdded(const std::wstring &devicePath, const XBOFSWin::WinUsbDevice *winUsbDevice); @@ -49,11 +56,24 @@ public slots: void handleSystemTrayMenuRestore(const bool &checked); void handleSystemTrayMenuExit(const bool &checked); -protected: + void handleAutostartCheckboxStateChanged(const quint16 state); + void handleStartMinimizedCheckboxStateChanged(const quint16 state); + void handleMinimizeToTrayCheckboStateChanged(const quint16 state); + void handleMinimizeOnCloseCheckboxStateChanged(const quint16 state); + +protected: Ui::XBOFSWinQT5GUIClass ui; + QSettings* settings; + bool autostart = false; + bool startMinimized = false; + bool minimizeToTray = false; + bool minimizeOnClose = false; + QSystemTrayIcon *systemTrayIcon; + bool systemTrayIconEnabled = false; Qt::WindowFlags previousFlags; - std::shared_ptr logger; + + const std::shared_ptr logger; QThread *winUsbDeviceManagerThread; XBOFSWin::WinUsbDeviceManager *winUsbDeviceManager; diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.ui b/XBOFS.win.qt5/XBOFSWinQT5GUI.ui index beb1ef7..23b7732 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.ui +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.ui @@ -50,7 +50,7 @@ - <html><head/><body><p><span style=" font-weight:600;">WinUSB Device Manager Status:</span></p></body></html> + <html><head/><body><p><span style=" font-weight:600;">WinUSB Status:</span></p></body></html> @@ -78,6 +78,43 @@
+ + + + Settings + + + + + + Start automatically on login + + + + + + + Start minimized + + + + + + + Minimize to system tray + + + + + + + Minimize on close + + + + + + diff --git a/XBOFS.win.qt5/main.cpp b/XBOFS.win.qt5/main.cpp index 7678330..7bb6d6d 100644 --- a/XBOFS.win.qt5/main.cpp +++ b/XBOFS.win.qt5/main.cpp @@ -1,14 +1,25 @@ #include #include #include +#include #include "XBOFSWinQT5GUI.h" int main(int argc, char *argv[]) { - QApplication a(argc, argv); + QApplication application(argc, argv); qRegisterMetaType(); - XBOFSWinQT5GUI w; - w.show(); - return a.exec(); + // Configure logging + spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%t] [%n] [%l] %v"); + spdlog::flush_every(std::chrono::seconds(1)); + auto sinks = std::vector(); + auto rotatingFileSink = std::make_shared("xbofs.win.qt5.log", 1024 * 1024 * 10, 10); + sinks.push_back(rotatingFileSink); + auto logger = XBOFSWin::get_logger("XBOFS.Win", sinks); + logger->info("Logging initialised"); + // Display main window + XBOFSWinQT5GUI mainWindow(XBOFSWin::get_logger("XBOFSWinQT5GUI", logger->sinks())); + auto result = application.exec(); + logger->info("Application exit"); + return result; } From 24898202e08c0a0d9d83576f646df05a3a960994 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Sat, 26 Oct 2019 22:22:04 +0200 Subject: [PATCH 41/50] #2: Added vigEmTargetInfo signal --- XBOFS.win/include/XBOFS.win/WinUsbDevice.h | 5 +++-- XBOFS.win/src/WinUsbDevice.cpp | 21 ++++++++++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/XBOFS.win/include/XBOFS.win/WinUsbDevice.h b/XBOFS.win/include/XBOFS.win/WinUsbDevice.h index 5ec0363..9599fda 100644 --- a/XBOFS.win/include/XBOFS.win/WinUsbDevice.h +++ b/XBOFS.win/include/XBOFS.win/WinUsbDevice.h @@ -28,10 +28,11 @@ namespace XBOFSWin { void vigEmConnect(const std::wstring &devicePath); void vigEmConnected(const std::wstring &devicePath); void vigEmTargetAdd(const std::wstring &devicePath); - void vigEmTargetAdded(const std::wstring &devicePath); + void vigEmTargetAdded(const std::wstring &devicePath); + void vigEmTargetInfo(const std::wstring &devicePath, quint16 vendorId, quint16 productId, const ulong index); void vigEmError(const std::wstring &devicePath); void winUsbDeviceOpen(const std::wstring &devicePath); - void winUsbDeviceInfo(const std::wstring &devicePath, quint16 vendorId, quint16 productId, + void winUsbDeviceInfo(const std::wstring &devicePath, quint16 vendorId, quint16 productId, const std::wstring &manufacturer, const std::wstring &product, const std::wstring &serialNumber); void winUsbDeviceOpened(const std::wstring &devicePath); void winUsbDeviceInit(const std::wstring &devicePath); diff --git a/XBOFS.win/src/WinUsbDevice.cpp b/XBOFS.win/src/WinUsbDevice.cpp index 149c0b4..395f43c 100644 --- a/XBOFS.win/src/WinUsbDevice.cpp +++ b/XBOFS.win/src/WinUsbDevice.cpp @@ -35,6 +35,7 @@ WinUsbDevice::WinUsbDevice(std::wstring devicePath, std::shared_ptrinfo("Entered run()"); bool loop = true; + int failedOpens = 0; int failedReads = 0; int failedWrites = 0; // Allocate objects for VigEm @@ -57,7 +58,17 @@ void WinUsbDevice::run() { logger->error("Unable to add VigEmTarget"); loop = false; } - emit vigEmTargetAdded(devicePath); + emit vigEmTargetAdded(devicePath); + // Get extra info from VigEmTarget + ULONG vigEmTargetIndex; + if (VIGEM_SUCCESS(vigem_target_x360_get_user_index(vigEmClient, vigEmTarget, &vigEmTargetIndex))) { + emit vigEmTargetInfo( + devicePath, + vigem_target_get_vid(vigEmTarget), + vigem_target_get_pid(vigEmTarget), + vigEmTargetIndex + ); + } // Loop reading input, processing it and dispatching it logger->info("Starting Read-Process-Dispatch loop"); while (loop && !QThread::currentThread()->isInterruptionRequested()) { @@ -66,9 +77,13 @@ void WinUsbDevice::run() { emit winUsbDeviceOpen(devicePath); if (!openDevice()) { emit winUsbDeviceError(devicePath); - logger->error("Unable to open WinUSB device"); + if (failedOpens == 1000) { + logger->error("Unable to open WinUSB device"); + failedOpens = 0; + } + else failedOpens++; continue; - } + } emit winUsbDeviceOpened(devicePath); // Init WinUsbDevice emit winUsbDeviceInit(devicePath); From 81319d9e0fa14c3a653e016931b854ef6a8bac2b Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Sat, 26 Oct 2019 22:22:34 +0200 Subject: [PATCH 42/50] #2: Hook vigEmTargetInfo signal --- XBOFS.win.qt5/XBOFSWinQT5GUI.cpp | 11 +++++++++++ XBOFS.win.qt5/XBOFSWinQT5GUI.h | 1 + 2 files changed, 12 insertions(+) diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp index 0826924..633b730 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp @@ -75,6 +75,7 @@ void XBOFSWinQT5GUI::handleWinUsbDeviceAdded(const std::wstring &devicePath, con connect(winUsbDevice, &XBOFSWin::WinUsbDevice::vigEmConnected, this, &XBOFSWinQT5GUI::handleVigEmConnected); connect(winUsbDevice, &XBOFSWin::WinUsbDevice::vigEmTargetAdd, this, &XBOFSWinQT5GUI::handleVigEmTargetAdd); connect(winUsbDevice, &XBOFSWin::WinUsbDevice::vigEmTargetAdded, this, &XBOFSWinQT5GUI::handleVigEmTargetAdded); + connect(winUsbDevice, &XBOFSWin::WinUsbDevice::vigEmTargetInfo, this, &XBOFSWinQT5GUI::handleVigEmTargetInfo); connect(winUsbDevice, &XBOFSWin::WinUsbDevice::vigEmError, this, &XBOFSWinQT5GUI::handleVigEmError); connect(winUsbDevice, &XBOFSWin::WinUsbDevice::winUsbDeviceOpen, this, &XBOFSWinQT5GUI::handleWinUsbDeviceOpen); connect(winUsbDevice, &XBOFSWin::WinUsbDevice::winUsbDeviceOpened, this, &XBOFSWinQT5GUI::handleWinUsbDeviceOpened); @@ -144,6 +145,16 @@ void XBOFSWinQT5GUI::handleVigEmTargetAdded(const std::wstring &devicePath) { tabWidgetUi->vigEmTargetStatus->setText(QString::fromUtf8("Added")); // TODO: Display more details on target } +void XBOFSWinQT5GUI::handleVigEmTargetInfo(const std::wstring &devicePath, quint16 vendorId, quint16 productId, const ulong index) { + auto optionalIterator = getIteratorForDevicePath(devicePath); + if (!optionalIterator) return; + auto iterator = (*optionalIterator).second; + auto tabWidgetUi = std::get<2>(*iterator); + tabWidgetUi->virtualDeviceVendorID->setText(QString::fromStdString(fmt::format("0x{:04X}", vendorId))); + tabWidgetUi->virtualDeviceProductID->setText(QString::fromStdString(fmt::format("0x{:04X}", productId))); + tabWidgetUi->virtualDeviceUserIndex->setText(QString::fromStdString(fmt::format("{}", index))); +} + void XBOFSWinQT5GUI::handleVigEmError(const std::wstring &devicePath) { // TODO: Note error output in text area } diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.h b/XBOFS.win.qt5/XBOFSWinQT5GUI.h index 3dc5898..52c650b 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.h +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.h @@ -42,6 +42,7 @@ public slots: void handleVigEmConnected(const std::wstring &devicePath); void handleVigEmTargetAdd(const std::wstring &devicePath); void handleVigEmTargetAdded(const std::wstring &devicePath); + void handleVigEmTargetInfo(const std::wstring &devicePath, quint16 vendorId, quint16 productId, const ulong index); void handleVigEmError(const std::wstring &devicePath); void handleWinUsbDeviceOpen(const std::wstring &devicePath); void handleWinUsbDeviceInfo(const std::wstring &devicePath, quint16 vendorId, quint16 productId, From c04061ae4f39ea06263be63ff8bb7e556ff4f80a Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Sat, 26 Oct 2019 22:24:38 +0200 Subject: [PATCH 43/50] #2: Removed info textbox from device tab widget --- XBOFS.win.qt5/WinUsbDeviceWidget.ui | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/XBOFS.win.qt5/WinUsbDeviceWidget.ui b/XBOFS.win.qt5/WinUsbDeviceWidget.ui index 7d38eb5..08d5daf 100644 --- a/XBOFS.win.qt5/WinUsbDeviceWidget.ui +++ b/XBOFS.win.qt5/WinUsbDeviceWidget.ui @@ -201,22 +201,6 @@ - - - - Info - - - - - - true - - - - - - From ecb7ee6050b0a12172edd5fe9de49a29f477cd01 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Sun, 27 Oct 2019 21:49:21 +0200 Subject: [PATCH 44/50] #2: Added functions to check for prescense of VigEmBus and XBOFS.win drivers --- XBOFS.win/include/XBOFS.win/utils.h | 4 ++++ XBOFS.win/src/utils.cpp | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/XBOFS.win/include/XBOFS.win/utils.h b/XBOFS.win/include/XBOFS.win/utils.h index c3ceb00..ab23212 100644 --- a/XBOFS.win/include/XBOFS.win/utils.h +++ b/XBOFS.win/include/XBOFS.win/utils.h @@ -7,4 +7,8 @@ namespace XBOFSWin { std::wstring utf8_decode(const std::string &str); std::shared_ptr get_logger(std::wstring loggerName, std::vector sinks); std::shared_ptr get_logger(std::string loggerName, std::vector sinks); + + bool deviceInterfaceAvailable(LPGUID deviceInterfaceGUID, bool present=true); + bool vigEmBusAvailable(); + bool XBOFSWinDeviceInstalled(); } \ No newline at end of file diff --git a/XBOFS.win/src/utils.cpp b/XBOFS.win/src/utils.cpp index aeea68b..dde0dbe 100644 --- a/XBOFS.win/src/utils.cpp +++ b/XBOFS.win/src/utils.cpp @@ -1,4 +1,6 @@ #include "XBOFS.win\utils.h" +#include +#include /* See https://stackoverflow.com/a/3999597/106057 @@ -38,4 +40,30 @@ std::shared_ptr XBOFSWin::get_logger(std::string loggerName, std spdlog::register_logger(logger); } return logger; +} + +bool XBOFSWin::deviceInterfaceAvailable(LPGUID deviceInterfaceGUID, bool present) +{ + CONFIGRET configurationManagerResult = CR_SUCCESS; + ULONG deviceInterfaceListSize = 0; + HRESULT resultHandle = S_OK; + configurationManagerResult = CM_Get_Device_Interface_List_Size( + &deviceInterfaceListSize, + deviceInterfaceGUID, + NULL, + present ? CM_GET_DEVICE_INTERFACE_LIST_PRESENT : CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES + ); + + if (configurationManagerResult != CR_SUCCESS) return false; + return deviceInterfaceListSize > 0; +} + +bool XBOFSWin::vigEmBusAvailable() +{ + return deviceInterfaceAvailable((LPGUID)&GUID_DEVINTERFACE_BUSENUM_VIGEM); +} + +bool XBOFSWin::XBOFSWinDeviceInstalled() +{ + return deviceInterfaceAvailable((LPGUID)&GUID_DEVINTERFACE_XBOFS_WIN_CONTROLLER, false); } \ No newline at end of file From d11abb103c799f5602611edf26261b87a148c808 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Sun, 27 Oct 2019 21:50:15 +0200 Subject: [PATCH 45/50] #2: Enhanced GUI to display additional info --- XBOFS.win.qt5/XBOFSWinQT5GUI.cpp | 31 +++++++++++++++++++++++- XBOFS.win.qt5/XBOFSWinQT5GUI.ui | 41 ++++++++++++++++++++++++++++---- 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp index 633b730..1e8e224 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp @@ -6,10 +6,27 @@ #include #include #include +#include #include #include #include +const auto installedMessage = QString::fromWCharArray(LR"""( +Installed +)"""); + +const auto vigEmBusNotInstalledMessage = QString::fromWCharArray(LR"""( +Not Installed! Click here to visit the VigEmBus release page and download the latest installer +)"""); + +const auto xbofsWinDriverNotInstalledMessage = QString::fromWCharArray(LR"""( +Not Installed! Click here to visit the XBOFS.win ZaDig WinUSB driver installation guide +)"""); + +void qtDesktopServicesOpenLink(const QString & link) { + QDesktopServices::openUrl(link); +} + XBOFSWinQT5GUI::XBOFSWinQT5GUI(std::shared_ptr logger, QWidget *parent) : QMainWindow(parent), logger(logger) { @@ -52,6 +69,18 @@ XBOFSWinQT5GUI::XBOFSWinQT5GUI(std::shared_ptr logger, QWidget * systemTrayIcon->setContextMenu(systemTrayIconMenu); systemTrayIconEnabled = true; } + // Check for VigEmBus driver + if (XBOFSWin::vigEmBusAvailable()) ui.vigEmBusStatus->setText(installedMessage); + else { + ui.vigEmBusStatus->setText(vigEmBusNotInstalledMessage); + connect(ui.vigEmBusStatus, &QLabel::linkActivated, &qtDesktopServicesOpenLink); + } + // Check for XBOFS.win driver + if (XBOFSWin::XBOFSWinDeviceInstalled()) ui.xbofsWinDriverStatus->setText(installedMessage); + else { + ui.xbofsWinDriverStatus->setText(xbofsWinDriverNotInstalledMessage); + connect(ui.xbofsWinDriverStatus, &QLabel::linkActivated, &qtDesktopServicesOpenLink); + } // Show or hide show(); if (startMinimized) hide(); @@ -200,7 +229,7 @@ void XBOFSWinQT5GUI::handleWinUsbDeviceError(const std::wstring &devicePath) { } void XBOFSWinQT5GUI::handleWinUsbDeviceManagerScanning() { - ui.winUsbDeviceManagerStatus->setText(QString::fromUtf8("Scanning for supported controllers...")); + ui.xbofsWinDeviceManagerStatus->setText(QString::fromWCharArray(LR"""(Scanning for supported controllers...)""")); } void XBOFSWinQT5GUI::handleTerminateWinUsbDeviceManager() { diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.ui b/XBOFS.win.qt5/XBOFSWinQT5GUI.ui index 23b7732..b5517e7 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.ui +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.ui @@ -50,29 +50,52 @@ - <html><head/><body><p><span style=" font-weight:600;">WinUSB Status:</span></p></body></html> + <html><head/><body><p><span style=" font-weight:600;">XBOFS.win Driver:</span></p></body></html> - + + + Qt::RichText + - + <html><head/><body><p><span style=" font-weight:600;">VigEmBus Status:</span></p></body></html> - + + + Qt::RichText + + + + + + + <html><head/><body><p><span style=" font-weight:600;">XBOFS.win Device Manager:</span></p></body></html> + + + + + + + + + + Qt::RichText + @@ -126,6 +149,16 @@ true + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">`</p></body></html> + + + Qt::TextBrowserInteraction + From be98aa896149de3cc396db5326baadf221b7b0e6 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Thu, 31 Oct 2019 15:08:34 +0200 Subject: [PATCH 46/50] #2: Implement version check --- XBOFS.win.qt5/XBOFSWinQT5GUI.cpp | 53 +++++++++++++- XBOFS.win.qt5/XBOFSWinQT5GUI.h | 12 +++- XBOFS.win.qt5/XBOFSWinQT5GUI.ui | 116 ++++++++++++++++++++++--------- 3 files changed, 145 insertions(+), 36 deletions(-) diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp index 1e8e224..0962bd3 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include #include #include @@ -16,11 +18,27 @@ const auto installedMessage = QString::fromWCharArray(LR"""( )"""); const auto vigEmBusNotInstalledMessage = QString::fromWCharArray(LR"""( -Not Installed! Click here to visit the VigEmBus release page and download the latest installer +Not Installed! Click here to visit the VigEmBus release page and download the latest installer )"""); const auto xbofsWinDriverNotInstalledMessage = QString::fromWCharArray(LR"""( -Not Installed! Click here to visit the XBOFS.win ZaDig WinUSB driver installation guide +Not Installed! Click here to visit the XBOFS.win ZaDig WinUSB driver installation guide +)"""); + +const auto currentVersionMessage = QString::fromWCharArray(LR"""( +%2 +)"""); + +const auto newVersionMessage = QString::fromWCharArray(LR"""( +%2 (A newer version is available! Click here to visit the download page) +)"""); + +const auto prereleaseVersionMessage = QString::fromWCharArray(LR"""( +%2 (Warning: You are running a pre-release version of the software! Stability is not guaranteed!) +)"""); + +const auto versionCheckTLSErrorMessage = QString::fromWCharArray(LR"""( +%2 (Warning: Version check failed due to missing OpenSSL installation. Click here to download OpenSSL installer) )"""); void qtDesktopServicesOpenLink(const QString & link) { @@ -36,17 +54,25 @@ XBOFSWinQT5GUI::XBOFSWinQT5GUI(std::shared_ptr logger, QWidget * startMinimized = settings->value(SETTINGS_START_MINIMIZED, false).toBool(); minimizeOnClose = settings->value(SETTINGS_MINIMIZE_ON_CLOSE, false).toBool(); minimizeToTray = settings->value(SETTINGS_MINIMIZE_TO_TRAY, true).toBool(); + checkForUpdates = settings->value(SETTINGS_CHECK_FOR_UPDATES, true).toBool(); // UI ui.setupUi(this); + ui.versionLabel->setText(currentVersionMessage.arg(VERSION, VERSION)); ui.autostartCheckBox->setChecked(autostart); ui.startMinimizedCheckbox->setChecked(startMinimized); ui.minimizeOnCloseCheckBox->setChecked(minimizeOnClose); ui.minimizeToTrayCheckbox->setChecked(minimizeToTray); + ui.updateCheckCheckbox->setChecked(checkForUpdates); connect(ui.actionExit, &QAction::triggered, this, &XBOFSWinQT5GUI::handleSystemTrayMenuExit); connect(ui.autostartCheckBox, &QCheckBox::stateChanged, this, &XBOFSWinQT5GUI::handleAutostartCheckboxStateChanged); connect(ui.startMinimizedCheckbox, &QCheckBox::stateChanged, this, &XBOFSWinQT5GUI::handleStartMinimizedCheckboxStateChanged); connect(ui.minimizeOnCloseCheckBox, &QCheckBox::stateChanged, this, &XBOFSWinQT5GUI::handleMinimizeOnCloseCheckboxStateChanged); connect(ui.minimizeToTrayCheckbox, &QCheckBox::stateChanged, this, &XBOFSWinQT5GUI::handleMinimizeToTrayCheckboStateChanged); + connect(ui.updateCheckCheckbox, &QCheckBox::stateChanged, this, &XBOFSWinQT5GUI::handleUpdateCheckCheckboxStateChanged); + connect(ui.versionLabel, &QLabel::linkActivated, &qtDesktopServicesOpenLink); + connect(ui.homepageLabel, &QLabel::linkActivated, &qtDesktopServicesOpenLink); + connect(ui.authorLabel, &QLabel::linkActivated, &qtDesktopServicesOpenLink); + connect(ui.specialThanksLabel, &QLabel::linkActivated, &qtDesktopServicesOpenLink); // Start WinUsbDeviceManager winUsbDeviceManagerThread = new QThread(); winUsbDeviceManager = new XBOFSWin::WinUsbDeviceManager(XBOFSWin::get_logger("WinUsbDeviceManager", logger->sinks())); @@ -81,6 +107,12 @@ XBOFSWinQT5GUI::XBOFSWinQT5GUI(std::shared_ptr logger, QWidget * ui.xbofsWinDriverStatus->setText(xbofsWinDriverNotInstalledMessage); connect(ui.xbofsWinDriverStatus, &QLabel::linkActivated, &qtDesktopServicesOpenLink); } + // Update Check + if (checkForUpdates) { + networkManager = new QNetworkAccessManager(this); + connect(networkManager, &QNetworkAccessManager::finished, this, &XBOFSWinQT5GUI::handleUpdateCheckResponse); + networkManager->head(QNetworkRequest(QUrl("https://github.com/OOPMan/XBOFS.win/releases/latest"))); + } // Show or hide show(); if (startMinimized) hide(); @@ -297,4 +329,21 @@ void XBOFSWinQT5GUI::handleMinimizeOnCloseCheckboxStateChanged(const quint16 sta void XBOFSWinQT5GUI::handleMinimizeToTrayCheckboStateChanged(const quint16 state) { minimizeToTray = state == Qt::Checked; settings->setValue(SETTINGS_MINIMIZE_TO_TRAY, minimizeToTray); +} + +void XBOFSWinQT5GUI::handleUpdateCheckCheckboxStateChanged(const quint16 state) { + checkForUpdates = state == Qt::Checked; + settings->setValue(SETTINGS_CHECK_FOR_UPDATES, checkForUpdates); +} + +void XBOFSWinQT5GUI::handleUpdateCheckResponse(QNetworkReply *response) { + response->deleteLater(); + auto errorCode = response->error(); + if (errorCode == QNetworkReply::UnknownNetworkError && response->errorString().contains("TLS")) + ui.versionLabel->setText(versionCheckTLSErrorMessage.arg(VERSION, VERSION)); + if (errorCode != QNetworkReply::NoError) return; + auto location = response->header(QNetworkRequest::LocationHeader).toString(); + auto versionTag = location.split('/').back(); + if (VERSION < versionTag) ui.versionLabel->setText(newVersionMessage.arg(VERSION, VERSION, versionTag)); + else if (VERSION > versionTag) ui.versionLabel->setText(prereleaseVersionMessage.arg(VERSION, VERSION)); } \ No newline at end of file diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.h b/XBOFS.win.qt5/XBOFSWinQT5GUI.h index 52c650b..22e67a8 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.h +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.h @@ -6,6 +6,8 @@ #include #include +#include +#include #include #include #include @@ -20,11 +22,12 @@ namespace std { }; } - +const QString VERSION("v0.4"); const QString SETTINGS_AUTOSTART("autostart"); const QString SETTINGS_START_MINIMIZED("startMinimized"); const QString SETTINGS_MINIMIZE_TO_TRAY("minimizeToTray"); const QString SETTINGS_MINIMIZE_ON_CLOSE("minimizeOnClose"); +const QString SETTINGS_CHECK_FOR_UPDATES("checkForUpdates"); class XBOFSWinQT5GUI : public QMainWindow { @@ -61,14 +64,19 @@ public slots: void handleStartMinimizedCheckboxStateChanged(const quint16 state); void handleMinimizeToTrayCheckboStateChanged(const quint16 state); void handleMinimizeOnCloseCheckboxStateChanged(const quint16 state); + void handleUpdateCheckCheckboxStateChanged(const quint16 state); + + void handleUpdateCheckResponse(QNetworkReply *response); protected: Ui::XBOFSWinQT5GUIClass ui; - QSettings* settings; + QSettings *settings; + QNetworkAccessManager *networkManager; bool autostart = false; bool startMinimized = false; bool minimizeToTray = false; bool minimizeOnClose = false; + bool checkForUpdates = false; QSystemTrayIcon *systemTrayIcon; bool systemTrayIconEnabled = false; diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.ui b/XBOFS.win.qt5/XBOFSWinQT5GUI.ui index b5517e7..9f98a19 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.ui +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.ui @@ -41,6 +41,77 @@ Main + + + + Info + + + + + + <html><head/><body><p><span style=" font-weight:600;">Version:</span></p></body></html> + + + + + + + + + + Qt::RichText + + + + + + + <html><head/><body><p><span style=" font-weight:600;">Homepage:</span></p></body></html> + + + + + + + <html><head/><body><p><a href="https://xbofs.win"><span style=" text-decoration: underline; color:#0000ff;">https://xbofs.win</span></a></p></body></html> + + + + + + + <html><head/><body><p><span style=" font-weight:600;">Author:</span></p></body></html> + + + + + + + <html><head/><body><p><a href="https://github.com/OOPMan/"><span style=" text-decoration: underline; color:#0000ff;">OOPMan</span></a></p></body></html> + + + + + + + <html><head/><body><p><span style=" font-weight:600;">Special Thanks:</span></p></body></html> + + + + + + + <html><head/><body><p><a href="https://github.com/nefarius/"><span style=" text-decoration: underline; color:#0000ff;">Nefarius</span></a></p></body></html> + + + Qt::RichText + + + + + + @@ -107,10 +178,10 @@ Settings - - + + - Start automatically on login + Minimize to system tray @@ -121,43 +192,24 @@ - - + + - Minimize to system tray + Minimize on close - - + + - Minimize on close + Start automatically on login - - - - - - - Info - - - - - - true - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">`</p></body></html> - - - Qt::TextBrowserInteraction + + + + Check for updates on Start From 1567a8d42655796229ec42a067c4d4aa9036e994 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Thu, 31 Oct 2019 15:08:52 +0200 Subject: [PATCH 47/50] #2: Added windeployqt to post-build step --- XBOFS.win.qt5/XBOFS.win.qt5.vcxproj | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj b/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj index db698cc..95d0e2b 100644 --- a/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj +++ b/XBOFS.win.qt5/XBOFS.win.qt5.vcxproj @@ -56,8 +56,8 @@ true - UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DLL;QT_CORE_LIB;QT_GUI_LIB;QT_WIDGETS_LIB;SPDLOG_WCHAR_TO_UTF8_SUPPORT;%(PreprocessorDefinitions) - .\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtWidgets;%(AdditionalIncludeDirectories) + UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DLL;QT_CORE_LIB;QT_GUI_LIB;QT_WIDGETS_LIB;SPDLOG_WCHAR_TO_UTF8_SUPPORT;QT_NETWORK_LIB;XBOFSWIN_QT5_DEBUG;%(PreprocessorDefinitions) + .\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtNetwork;%(AdditionalIncludeDirectories) Disabled ProgramDatabase MultiThreadedDebug @@ -70,14 +70,14 @@ $(OutDir)\$(ProjectName).exe $(QTDIR)\lib;%(AdditionalLibraryDirectories) true - qtmaind.lib;Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;ViGEmClient.lib;XBOFS.win.lib;onecoreuap.lib;winusb.lib;%(AdditionalDependencies) + qtmaind.lib;Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;ViGEmClient.lib;XBOFS.win.lib;onecoreuap.lib;winusb.lib;Qt5Networkd.lib;%(AdditionalDependencies) msvcrt.lib;%(IgnoreSpecificDefaultLibraries) .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp Moc'ing %(Identity)... - .\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtWidgets;%(AdditionalIncludeDirectories) - UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DLL;QT_CORE_LIB;QT_GUI_LIB;QT_WIDGETS_LIB;SPDLOG_WCHAR_TO_UTF8_SUPPORT;%(PreprocessorDefinitions) + .\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtNetwork;%(AdditionalIncludeDirectories) + UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DLL;QT_CORE_LIB;QT_GUI_LIB;QT_WIDGETS_LIB;SPDLOG_WCHAR_TO_UTF8_SUPPORT;QT_NETWORK_LIB;XBOFSWIN_QT5_DEBUG;%(PreprocessorDefinitions) Uic'ing %(Identity)... @@ -88,8 +88,7 @@ .\GeneratedFiles\qrc_%(Filename).cpp - - + set VCINSTALLDIR=$(VCInstallDir)&& cd $(OutputPath) && windeployqt --debug $(TargetFileName) $(OutDir)Resources @@ -98,8 +97,8 @@ true - UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DLL;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QT_GUI_LIB;QT_WIDGETS_LIB;SPDLOG_WCHAR_TO_UTF8_SUPPORT;%(PreprocessorDefinitions) - .\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtWidgets;%(AdditionalIncludeDirectories) + UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DLL;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QT_GUI_LIB;QT_WIDGETS_LIB;SPDLOG_WCHAR_TO_UTF8_SUPPORT;QT_NETWORK_LIB;XBOFSWIN_QT5_RELEASE;%(PreprocessorDefinitions) + .\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtNetwork;%(AdditionalIncludeDirectories) MultiThreaded true @@ -111,14 +110,14 @@ $(OutDir)\$(ProjectName).exe $(QTDIR)\lib;%(AdditionalLibraryDirectories) false - qtmain.lib;Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;ViGEmClient.lib;XBOFS.win.lib;onecoreuap.lib;winusb.lib;%(AdditionalDependencies) + qtmain.lib;Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;ViGEmClient.lib;XBOFS.win.lib;onecoreuap.lib;winusb.lib;Qt5Network.lib;%(AdditionalDependencies) msvcrt.lib;%(IgnoreSpecificDefaultLibraries) .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp Moc'ing %(Identity)... - .\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtWidgets;%(AdditionalIncludeDirectories) - UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DLL;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QT_GUI_LIB;QT_WIDGETS_LIB;SPDLOG_WCHAR_TO_UTF8_SUPPORT;%(PreprocessorDefinitions) + .\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtNetwork;%(AdditionalIncludeDirectories) + UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DLL;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QT_GUI_LIB;QT_WIDGETS_LIB;SPDLOG_WCHAR_TO_UTF8_SUPPORT;QT_NETWORK_LIB;XBOFSWIN_QT5_RELEASE;%(PreprocessorDefinitions) Uic'ing %(Identity)... @@ -129,8 +128,7 @@ .\GeneratedFiles\qrc_%(Filename).cpp - - + set VCINSTALLDIR=$(VCInstallDir)&& cd $(OutputPath) && windeployqt --release $(TargetFileName) $(OutDir)Resources From 8033c1f2740f2e514d21522b5f94629031519e06 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Thu, 31 Oct 2019 15:54:38 +0200 Subject: [PATCH 48/50] #2: Updated documentation --- docs/images/screenshots/01.jpg | Bin 28782 -> 53617 bytes docs/images/screenshots/02.jpg | Bin 33167 -> 47440 bytes docs/index.md | 15 +++++++++++---- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/images/screenshots/01.jpg b/docs/images/screenshots/01.jpg index 80b8823f653b8954a0a967359e9e7d12e36020da..d0d4d557861b819320098c0845178624b11e2410 100644 GIT binary patch literal 53617 zcmeFZcU03~(=QqX1Ox=>9hIh3L5kF4gh_e!zze_>ohRlarB=U!tJ6NJ(>vhKBkQH8t&J2721d zS1wai)3eZDVPs-vW~QNIWn*DtV_;%t`qK#sDew*%ITblM6%#ErEz|$-=e!+sg_30O zLOdzSbH{@(4Na|whEJXv8Jn1zSy|h>w0&h~@8<5|>E-R?8~iRLG%P$KG9mFp zQgX`2)U@oJ+`RmP!lL4;>YCa*M14bJXIFPmZ{N56fzh$?iAm(tGzz`6yt2Bs{%d0s zyZ`&(@aPzKa{7la5)kP>+4_sK|BkOK0ACl#$Vka3{_sU|!3$`lSIEe(NnT{Qr%&@UXt8(%084JipQ zd8AiBV9;6h;&BI*#1h7jQS1G(2)?XWsGV)n?;`Uf&S|tce~!Ak@=>x7f8q~^O6S0H z&}HlCrgk1y$6)()^}0Je!7GJCGFu`OyzBd?ES!e>_Bm)#+G8KNV~4HoHtE;y z$M$s^$u`#ZrUi^FX^Fn{AxA;{>%6U^E2fu{sgMk}MJQG^L?8oCxw1s8NAn&UMWZ5j zktqqUPjg{(IMD0q?I48dr-CfQb&#SSbqxA{qK7L_S>T=drLhrTCzJwnVSi3cPh*V>T zJM0gjs5F@Iq=R=0S$x=CG-M?7jlJMHH~+WDcUg_`26`c_G--F16FU`XBG}29Yrbh3 zDA3Dd4dOzH3~YTNHTvhEH#j&ht!qN%Diq!chL@l;oS!?qK03-fr2M#@k^dy!>qndD z^Gakg#iz^GRoL=8pMvn6biHw>U1wsCP2o(A*rx%$-#ivgQ|~`|w8K*qLD8So+mM6! zAzzLKOey{l@V&R9+0#jeXxZAqdSYJG3TJpP09{ZHDCgkZv0ac zUL)QriT92Fn(R9VS(g_(>Qc))K5LABp1CO7vr;77^zmZv9d@PVxBj%&v)*Hm()DT@ zYR11jXt*-Sl>q6#`U>?VZa11<`;LC*yOki4f?-9}ir*aw8tU?~jVyp$c+s1pbJ6w= zhYaEzbVW0O5+<(ec)T!LnvW#nZ95Nw^)nVS%pZz6b@wA}dg{eZe#Lr9gsCuwT+l$ra7c4AyW{SjgMQj4Av^X+ zppQmpaeQ4V42byz3&FXMM=Kx7TO&tRHf!7kEsuMOdU=D+L39v#j8LeqIvrkX=hWAK z0E#mJF4pawgWfTA-RYGi#5n^qVr-*kEZr2pjm8%)!_kjmD(9dttX)vt4M2pJZU6%- zfjJ|-)1!qiX8vPf2O{P3W|cpIlenV66nF|Jlb*$_%e|%#GtzYGzbR|XZajcf!l-B> zH-O7IMFUkEw=U2Xo`{rZXDOvTK6*oZ;`-*#ury~{Thd_gu5Q2clR!#`>o(G`Ynbc3 zBXQ*3?tJtQ&p|f#f;3cPGS?b6Z>*=hIeJsup#Ke!$Se$dNVWZ&1~3udf)}uu_X&_OgRQK7L;YdVOb1V*4b* zb?)9`8=b0$IGTTC7nNdz~T+`wu0*{OdXv75+qWS9cJL_cibNfWz2@52rFlHRNxGtmjSjSDY|(7 zV{!qR75JkJc>gGaKm3$WM#A=Yxk@T~cmXRA(GS>w{&UdRf2a=ae+<&}*5V)4@lOdK zjg()Vl{dDEf(F4aBky2XLn+2I{*ZAKjMjoFuE0A<(a}6@Si!pWa}XEMIY9WI&f%T$ zI1gYL=>AfdfT6pjjb0#7s<0iLgWmt+4=u`=PF82l|MU6iKfS@=e~DRKS9uoZEe^_i zbnzczS^J-2ITb(yQ$dKnz(|Bn^^VgD^8VDvZvcHz8h!cFd&t#b)E_~AI@J#iYNKNfTm=6$H+wikUb=U$ zgWFpu80|i+ms1IzoXE^90%TUXY+G@w28SkSYTFFmoK#E!h>Tfi7mdZ!hQr@h_ zSw;Or2XAx5k$6Ajg>L)7iPz&>De(#~$#4{%E5Yw|V;noUf{#T2hU~mp`Te-8V5~U5 ztJj*T(8*-af^%jvl~q=yVOMbO7b)Jt#3=}YWo@`fB(`>SBJ*9Dt^1MTpr5VddT-(P zD1q<#l_V1>iWZH*Cat1lGBFgLjXw&Um*z~@tE9=OxHKyDZkxt5K%n<0UYX ze;j!h0n8p=5zqtF|0*eenT>y$d&|z0eTfDx2#gWuJw5!k`Bp%q@*knoY$I#s7T)0@ z!PHyUbI8D(b0|Eob=>5^H267$Xu-70JKycYK)z6L9n19y;RAB3+fc zl-t4In_2?4{Bl(yMZbk_)Bahb|6GWkmo)&2FUy{T zzHuxR6wGe?bE%wIP2o+?wBa6bGhw(mk{Be%d%Ot8J~1BURSKxaUpfaR@L`U*!D#NW zbI?JW!b#kc#&$JUt~ZlEY1ch2?DQz*`4aVxl}5@F(?{4`q?>NUHLBkv>#&heU--VX z%N*>r|1@6A>RME%#2jVy5t=kCT3~01(K8x9O{L3WF=+TZUM`r#Qkh$~zFj7H;?qgK z81a{^UJt3F$?vn;Jv~vUe0+O+CGwQFU4?N1(41jiD(v;Gh$zTd4Ma!5Z^m8ffnY3I z%p3_yb0Fb%>@;^tRd&9IfT7l9$;UWd;os=SJ}VE~-1+h>2f687`azP9ROom%%>WM} z*X$CS-*4SMt=VAszav;(H?;@|fu#k3S=pIuv1)OHpaq+a*+P@Y<2Va#3ueqdFQ+2cHTK^<1&NtVqy|I zj*jKmy`A_PtIpI{pGQ~xsJ}C8(N)0H^g(~LYsN5PLWx?uC@<>bM4rj>1aMVDIBV!N z`p5@j@%4?VDpo(+z7}Soze79(V)q6iB_9PNV3}7n*MEPvT3dHZkzeKvqu@GvV%Ulx zwv~ZPD%~pwXK+IOXRN1Ad)4-1n^~K!$EjZaEq-=-A(IClB$~2Lz2e>ngWvJ9;J>BT>(PK8V7>Zvc;$Y^`MVf z?yG|cRdNqu&7l$YjspPCfNo^}v-KlucyXF4!_F>mj31HJ4rN#1VF#$v>9Y(kaTO<$ zJKtUwge8xebHqe1Seu5<4B9;Ea|!8`m6EM*=L~zwb`||_TmLROlm1=aC`j%(=z3(< z_Rn*W_c>@mF!I!JZ9AX_f91sjIsSs4A)26+g})eEXN8roM~WY8`3s*entiL(@yx%% zaQk}w*(in5tzWfK7%)*2@fOe~J>Y_K(1x^!*CR}mM5M=%!J>u6+N}D~yv0fsXJH^y zatze&B$zJMt#7o|70!5)rCujfTHD<`p4m}WP}ijWYBy8fle>6(fGO#P+=Z|^%LRfp zTLW-31rm+-DbC8Al0UT-dB|GvaNMDWV|HMXu>mYoFugBwuar#eT zfbLPIBm6!GA+uY$1Usf|HA z`MIld1;@eJqK<@d=w@A2^(?A;5GYws`h(7@*P$9f66qS}pwG2&M6v=La(HmbwzWv})j|)Fyya{}3Jnaz6*nkRs1P*UmvlPvHOgvM~T7|F!2F zB7-9@F=!HuW5`;*%MCtiKLpe_oRGx(*UL}8!#j0y0QhFscMiH>h=29(r2eGP`->or zzg~Ca#Uug`9r(SWs=x@EIEuV!b37wPDFLM_8j~|)lN)`TcP=(J z)}dt5SxF4t@BXXS?!aGM=i%z>(gf_%yE*e)0?)%`Z(pZNB_)3$b@7eMXkIM$CC7)$ zda=q1+GpOQc4bf0ZDP2-s@rUmGQFWnSe=iYhqVi%x9PiAJ^d$AQt)zvPsV@jyT_N==Xm?<9K;W9 zZ%#Z1G59&@5!{%6$t-Ix*|u=M*H>c}AUB*Svu>&M&YoFwQs?X^U41t9#$R}qiDVdV zy$SyP14%pw$pHZjjU!zELMrXR_kP2%%s)r&avlHc6{dfG+Jg4)4*nhkAlCY~9Q-{7 ze`4b|Y!ea$nH*!$hwxd+0^B88=KSKuX8fbEDQd?W3s6sKX(2$e|crELiKwP-XVyP zbq-ny$MGt|tHc0DnZzQ$=p4iYGz;`OXr#oFA7MMvXKgT9yM`F4SR2)e%_-_}=1-nX zocEC&x?Xh6OijbuFxG^RUcO_)QV*Sl>gobxB7Vscr3}$?&W}gSJW4VYwGgcni(}in z+9!;9Zrs_Wn)kVfuV-JQp{6J0eU2s1*!}Xwo~6-oq9yr@OjDn;n}^d_4^xPyk@v*U z#^ClYh^i+iqBmuhgM~}Lq6tp z1~s({O5G!=D~?00?IVafZ~_9JVlY=)MgZ&y#fy-(9UU?#i;`8r=I>cM6_%e8V-M=8 zpcglha;RE#=uK+KtU8S=yc7oMqe+lBCh_qD!xjCP_%jF6cu zbx-;_u#5ewtIkMs>b3;`P1e$hX+Pq^IDLHZ5vJ*mWYu9=nM<*?>`*Ejq$IDQ(pl* zvE_Vr+O}+bBbOY!6GeAI1U6LKN{G_%*Cz1cLj-{U#g=?{{P(@T$K`LC`2W{>Al09I zV60A4D#fmQkEb?iN#f8GdWby-)y$XuItT4j0^!hdDj*~rS@bWr-hz4Ibzgw1WKi7g z`s~CVMz|jkxE--X?<2v+DkN?g!wzm>7uXQk+53EHyH*c(sCT+YM~pFB4j?%P^)q91 zFH#)owDDp{ERz4-iijd?$Hk&P=v&Gy)1eAFdC0Ls-5HE#)e~jn&D5X!mbCkBbsL-x zO@zy~H4lFEkjy}XTra+HdyI;$7{(aGgI?#ciZ!>f+Tg0EZ&WCa27tX84sb7r4Y7hL zX!sRe&?stuu#00i&%wDk>C;PT*(C$*#H7yM6?&?kS31}N^iHF_ghVXWvsI>_OJh2~kgMH3I4>n@Z2c!d@iAGPVKlf;SaO+H1*7F2pYQOpjQ)Vr+jJX%yB(1T|*gK;%J% zMf?+AzO?Cf4V@)X%~aP9bqS7@k}ouFN+UQ!D$73F1+`#A8?JmWisVPpkG zpB~9(jFPWJR1Giv?2MiR?L=!z>2$xq*ZOz^q4(_SKeaDh^pbl((|uE6O2bFZB@Nn} z71lWG+nweGNEDnW#!388wsul%h2<9WtiY#|EU`;LUa}YEi}3PThxU2Ns_N#SO19`7 z*U_V$QZXm#IRonO^#+sgF&CudX|LWj|GaWTh^?Tt-Q$CCmiO=Fml2q?GlS}h(nGph z$W)h)Ee#c?Z1JeB*d+C1bEgjvF<*?PNHWFWXmMm=>Y5pO8j5};dNo(0E zHRjbfetkn&&h3 z55_V}&JmgP7$}g4Ych#+fhleeUj)n{-tlTEWqV=l#G zUZK>X*1g-ium!IIsG=d23a5w{mg+HNE3JHD*A-}|21Q7|$sj1A zY7{GI3L>se)||i;juu{9SSl8xMn2Cp4KW0N&6l71WcBGM_iG1zb8`Sls&J#qqsP3D zyRL}WDkV3XCYm7h;Zj74zzblvCQP8{MkS6i2me%lT z>uAjm_{qfcNXWEU1%EB!n`@~0i%8Exes<7$kXm^M;*j8}EF8J&NEG$H{=0`GCA180 zg+9S+^XdVjHX_K8@=UR$6gd*?mE1n9+^fu*B4;84n6qs7gg0d5+zr-7Idm%#g>qu8ViG;67xJfgfOhvw$4ycB4UuI{`3 zcB)^U^u2v8vJXmP=lkfiwPI<9RCR=9SMiRFg@##m?BvEgS%y*rSuSkk8>JDdo3_I< z=|yrA1%!Ktd0-Z1gwd&pENhmOi*~1kX}ho8khYV#!?JHo$yMA5@#8Knd#gb2%Tc|g z7oN$4aVp8ROFIWe95~V$ObYBv&nRm@j+}SckQ3ex;SP+i+PwAo(^u{uY6zr|o0fSc zlZx=UW-lDhp#)jK+-SD6Oc$Por{Er^umuiA6Q z@QE^_!=7?urq`3cZUVUo_T;B8-e!pzI{?XYr4&;c z5@ST#Hc1~i1iG*66uIxrLL5n>2y?Dc`^wTtOf6CuSI@I4nWoffGP9V?EgZVRX<}Lx z_P&pOA)e3ijST)a_IG=L2sWS%?Eq%juv?;KHLwu(S8zCUQA%V>zPH-TFRJl<@Ee7@ z0S&C7z&60LZItl1&RF?UWP2;NH{LnFjL@=Lm*YstPBT<4TOl!6oTX%^R*Gy&xc~G$ z!-+w0>69UB2obP8eT{<@15vH1*Vq2>`tU#%v z>!Rp_omQ~P5iWV&RYwq7iKmDOv9uqid>StadmouYSN4e(ec#>9-5!A}nhZyOl|y>7 zn41|fCR~62BehT(M{L}x9H_Jhh>b*Vj#%9Hy%UMkYUkD ztoBj;krw~G@81gUhz8x%CK)(!{^WSaLgJ^w)Kqr8A~-IK#)FP-u)++lj>-5D#x&$8 zSa7+){;2v@0`2+u1P&vOwM~cfn`O9?`lL+O?sM&Ovt7 zA$P_2NA~78-VSKr0y**2Z0|k$t$0af5U+Kn1G|MmhOA;4aSQj_M#>Hy{8={l`;2N6 zL?EfTPpCys0u7%DOfkL>@1tNIvx5U%mU}0_FK^6C{* z73++aFf+2oOe70|SXLIDz!gAdbaIYzP~x3rF!W`_S>8PwM+H?=xtq98I(BYJOmsqASGqE@7K6- zh1;r6+=Slr97-?H@`4we=SZ2>GS|tACVxYsvLUgl>l~D0)Ib*~%8!#8NPHR)4zpR}+Y4&Ro||^yxcAhDpGPgqlG=hrRU*dj}03zUSWsnJ3?KJ+^vauma{_z%?cg^ zIs)BQGR3#zgFeJcvR|q8PQ#t`+B=S z_&xJIQO#kH^-+ZsqRD|1qFBjI*sXT(6y))YhjY31eyzEb*VDwT$ksN;=komLT8qLt z=yBa>sgtv_)AQ=;x|saTC3L3-lRcH~OC{mARQwoFQ3nkPSLAQ3i9FIi7b@0>IxX;+ z5UG$p_UBZl5f{JXJ;0!|@B`%mQ9eb@(J76}6qX%~k2yQ;tya~CqAMPl$$l<>nXI$I zM;X@t``hh~vs|k*#9}(P3bH#bx{lP?#HKcc&q7^sk?ERammD<$mjYsNX#&w8-PEk~ zNN?yeBu1^y%z4$O)t+Rz*?(NU@k7$U{Ok^GAH@8XqG#bk9`ieMbuf?2@Z{)U~c zfQo=@eG`nG(I0W9ZFeK3O$ z{HcRhK(|wFQ%fi$DPn$lEny8!_BHfIwye1ncF!y{$1c~z915LNwXt#J)K(tq4!X0X z#{Tl9&?OK%|3Pk)VRahOfo*p&;fQV2&M{d{ir>kEuWJ38NzPk63J0q%6&Cl+oBJJ+ zyC=Q}$7$13`_5wpcvj~|VxxEwuRM&MkPcH@tl+l9#X&e;l#Xm>sM>{8n(#P&<3^9JkCm*# zYM-XQ8JMPAwT?dsDbKU>mzkACKAzRKlSp*oGPwB4TDYRJ9W3JL{+j1a`@!{s1hq(6 zuV>*oqTkE|Qx%G%*3iFHhdcFBojkjQ78SUzsBH$i8KVA#pQ5=Z#5 zEH7g!cKFemX9+dp-d)3J8kG+5$xh9Sb9xyHo~!xz&F1B?Gc~U5KDPILT1A*lnPNAG zdGxmwl*O#qt=l)dK))U)(CKH2Sk|S~%YFtG$t%MXgLuh;}!g>Q!z{WY7lD2w(Gi%T3pIXlMhcNVFa1aPG+B z_Rd*Wk6IpPox&OG#&$Z$ad@T{4hKBG=TM~9Vx2t6)!_GQ3Rd#AtpBRh4Qk5|X>F7I*R%@C77vWdrM)K;2hk(KoPjd8J%jF#s2&2@5e%cvgP)ihp$zcH?9sy-=7vt?hDZb(n#w2Hbna72hG^VlCswWVvWwBy4 zQLewL!uC-nUmyAKd1{P)c+xi)8K=Flrxl>((=uO{^!?=1U5J4m@#}w;ySm}nL6aKJ^}N*qI6u3CR^mIcJUp7OY~0>wA>CyHeucH3*}0; z#GI6r%j+@WpYFM-Ugr5LbL=;n_zZIwHdFlDYcb$NasJUXA85chIgghvK~t*)WI zb1iQ*-Y~GA*J*1zJ-nQ}uf1E_Cb0Tt-#M!7r86&XtOt?OIJV(SNEj?3Qz(e4H6-6D z8_F1JW+~8%6+l!|R&~Zm3#>dVJwq^Xv@C4*VL2Z+j>!zE`zafp{?MK=IS|19D9W#4 zG1PcfSYTRJ8@aaeemXGfqeZH@b5h!^p~DGgWrpoC0xTUW=OC*%JqL#(e>L*QNg`EP z=d>_x_c1`p*p8^XzgySOX}uiCkiYU!S=msbA(zgZ!L|M?mtNRi+Ozf)aCNcNTBe(p zFi146^5ay5v)CE)@FGTJAc__2oAd0ytz+$0lRa6eu3J#0Z5q8=)vv8m<&}JUnjhI} zJ2bow&nP?bSWug$vg3h@z{=CkGE4Fa!g&k*C0J1%aqX$84G(?JwPrctM}m(`l` zkL{!>M$sxzWYDmbSJo$V1V+4|+>oHRXRy=CCtI$F66(bN>RJj(c+lZ)7)=o7xXpP*^?*19_uDGOk z#@c_1BBsZ9;YMlhhXlQB#9&Iw{!PCoueMEC(jxD)L^lsL_Hv{LH76azhCNDV?t9U) zzttmck#rG=36+J)4HOC^V#Xgndh%d}kLHHVFDO$hy>~TG^v6=FbPxR{Z-b>;^|@ow z7G1H<`k2gseAJ1jIPQXtB*>Iz29f7d!M%ah#YnGP?6XIbPNDeBJA9o1dicQ z@vwnY=k4KTA3U$yXM0+7hKzI^nWgt__4J30pUQIuUxha|#e9ZVC$ap3)z}g3@)U8L zO%>q_R7%E6bj;q_#~K!w9h9M}V@q`@j~kP3zCKh|S#pRnzVWp_K`=q{Sa!vFzyHz+ z+tKDttp*Xf1*{|Dxb9%9E{(4298@L-C?LQr_6`Ru@l$f9af= z%($=pTL}qz?Q6VN>EhjQzJYr069m-6RE!)}jx94%bd$$Z$_abFE#Sj#hO5q@@T&4M zG??1Hm3PwJL2*BmtpPKUT%+VyGRGNiO0IB>lR6ra`hbdHsP~&Nr}4R%stjjt%-obis`k7yilSwkWMa8pogZb(G%bC;Y`ZRW)-`hlkp418I z!poX`&sW^(ru&wlp)ePlWf6#`HBBx|BJeZwK=y^K4p2d52H77Ya%XXFLM!8Z)l9KtB~LmEsF-3o(6vFA!HG+csHTvvr0|2 zRJF&X;oDn|a+)LV1S3aN(PT;SofI#Y5%cgUt^E#>AAB8@o4Hqy6>e(J8^&olA4B#z z9Qm6d7%%)esKopv(TtGdG2P6H+0z|M@9OtMgK1}`WLnx2w^NMYTiPMD*=KkjT%I(z z33D>Mj!4FdKmeSvz+VraZT%vSlQaZB(ZK4pSmBnqgCLYrGvGNc!B8)(~B&}d!fT& zij}3c^e7)KrwdiHGgHG(%tIrhDU6Wfljl)8FN}?TFUIxQ#13nYdeG6nIx>?jE3-ef z4d-i4gG?qLiR=k^lt9>)CAGXiXGuNopne{(>VI2JV`?=39(Tetw>q?O0>Nqwf-l2g zf|)nKOf_vR4n+lO4i0{Ho|DEr!sE>vcf4O8vMKd6v_?Q&9KL-Ne)seeZhg-huSG~E z^5Hd+wLZ19+Ir2grZOJEk6W?2IihY53p4YXnJz-|RhHydgTYJHeJ!(ml8)ayTAQ=e zy}XEx@RW^85xp)<+X>-x*W0U`H^`S*Qs!{l76z}(Nz-h;bLyWlTXl~s!45Gxt7M?? zVBSnYA-l08X=S?|bkdwv#nV#S7AZYsIXP4&<270C=&^$Bt(G`~p`yJwD|4!-)o(cE zMt`ePw{ShvpDH|Ei-gP*oN?3Oe*bx=LzZO+cLW--24q#Lu4Ygi`CwxsT=KmW=xBcN z^)%|{Ie35FT%h+Tb6qfqRdlGAP2F>}*!~WG?ZY^zbx+v{d zsqml`oUzPY@pd2f?6%yb_PicC3puIgF1&b;?ABGj-PzV@HN!+-pe9Jhc?(}7z+W~U zkcC+%$4GQFri2Et{5nGQR7`m|gw~HcZ6Al8Nh9PK%%$bIV4()hF8f%E9VP>(-Cqn< zUjZjO^iMUQApoyet^h?q=8Qmf6SHAAbaT6HYZ(Z3wLt#sTc7|fxHSZb)$|msWTDlx z2$ep+;Cq2YY2$oYmK69g@q+k5(EKHCmpn)u{*AF!F}$xBj`W5f*dw_ytVGSWptf!1 ze{P<8{I|{X->gH{}c>cUo$e`OkqNAsI)ExILP)y?n@9HttuExe-#lbYQ-ZMs_qeBNn z@?|3v<+icCkXNb?2QvLoRfVpU{YFMWpyvZF5FMeyY)9nBouH$*c};lu6V7_(uK*a3 z+GiBSpD-XVE?7mMnv^wY6UlbG@?7;+->*DZ)Ls`a4KAh6k^>u?mrp3@4&c=Ocu=yS z^7Kq$_NrEB&$GCOySxgYtfGyCB~p@F(}4=LNVzkl+|VAPfT{8F0mN(d7ZWo-#r|4L>z-F_8%+bWdbPC;ujO()&vDlY*b_;mfP0-#axf`*XodZ#;5Qu z2;>8>)ZV~(<#+?3SAysP7(zLp7u0o?x2hl23|<<~yfzA)3PAtMY@u6B|6oXXuk&i${c!tml6~mnFcRI^@oQzG zCd$n680nzm`(ZXSqwF}7#DBRS0GYR;wdu>RGdZdzVa9Z*UFQYOXlmAgwoOARG<6pSLDVzA_%J zO`g2bpV0fjLm>Bxg4Fggb;Wc}hZmA1adr+ju;idcGdZfg+~ShPdgGfZRr3kNp=!k~ zl<$j-6_I_bbK!s)+ec(QJw68sB1g29zO7RhjkqR6>NL6|v`RKA92e9!tv8-d2sxaz z9upf+_5y65A|bt&tYa`m-UvAQrlUiz90v>uo?b$JtF3KH_KPxPXS0b_w0dm~hQD#r zzO>wIfhFzEWG9Ye5h3Ll^Gdcjc6M42p=wnRoeyU2sQef!XC0a$?GgBfYS&u#QEL7f zzwtX@NQJt-xvnXBuh(F1<|4f=Q4vYqQxBRknYVET{ZIg$!y`M4Wuh&2Ud%#tY1wr) zO)1Z*--C73=+?!oMh@P1btKQ(h6z}OGF%f5q)O1|7rc|w zzjEc%x&tqR(mgCA8X8;EpycCHKIp&bcpo?)z{s<1@_k!K#l(AB@Ut-33pKxwk>;!^ zEUQT$R*)lb%&;X3kl9ob3&yk|+fHQT{yRCc3>w~l^+Sf4U0x$$XsiJ4Auu{uUS{jh z`N4mbPq)-!57!FJQ{c($Z#r7sG6Yl6S0X`f4`vXDY1q+3IbTk6BqSqmQdZu+y$RGT z(_mx1x0)aIED2w+e(<__2lsnvA>7$R?DBd$Djm9EDLC?Cvc`tRUQWYapuR!q`{RM? z+~mjjcBbD_^;YpHc$cy_gc#I%2T%$5)Lq~)C9kCP@+Qx1|Ixci7tQ8s=(YH zaZ5|`Q(a}r+@@=&a7m!pc-0;A8z-vd6hiF$nOqB!gOwkAZq=Nat)9_C+=TXo{0+?< zcVj;Z3;DEdjh)u@?l9>?ue$%04E4XbyoKoienJM6HY3S^LS(fkSk}`fiBlYq;^b`f zU*5UP8`tIt-;*HVd6(7Hj`RsY{cqy z1diGs;xJ%TAHZnOLC>P#1dy@azW|D~W0g+cOX9|Z&^p^4$Wqo@--583tP8));1M#; zm2=)vmUXRKY%{OwW*&?R_C3<<2{-Vfi?7OW&8y(V+HR*{7M57MTfBOSk|Wo!u{p(d z@9p$a4wI9|Mvd;0d-AnLQSMIPx4)l73_rRoBJcKUneb`{hawuw#)&sCOcn!nZD-wz zt`}O@_ZG%7fAzNMmRU51+&f2@oj`9EnFQC-?SbntFT{is1vU9^J?+EVM(*>5gX_HF z@)`*_?iqAltQ?M6L3nw`qF#Qe zG&HK2`v_c31YA+$DNDF4bIP%*pVM>`fR{Zc!tM-tO$_4n+Osf`-oZT_6?lGyqe?=H zXA1nqLjs8b@{NoFndwqkax0 z)ZwLT%_gvE!Fchf3G*iSp|8(_WM?I*w}>KR@VaJ+Qaj*;f}=D9YI`mCOoeC^Ilu18 zyr1WxAsmxY+QIf#3+2n32^>xE4$O%E#)rKam5m#IR8yBW+Vv3G>?JZ|rY5S+%G>yJ z+2>>D5YJg?QfKw^s$IyItG&0`hZXE4S)DmK=9?mZ#o{`qMfUS)9&qXbryt5~=(7C< z9q=G@3Eblx0jFpdAv7(?D+$3_a;*H0Abuj8A9d6Hm_H!nbIXx5;@LCK!nX?%@|W(O zgj>E`Kgz;B4OS(j7UJ}l1XF17VjfF$w9}806K1Hjo6?&!?>2tqEKC@IUe_QqSI@uY zBi@MP#cG(0*^JfB02yy)Fi$xa4v)Go%Nw#Al|$6`huGupt0&)MWMVK+{di$^br$j2 zAr0OGrMI`_X=%WKgCC@E&saiA17w>}yOaCvs=+y*%KJZIWACoBQ*6k;Vfw=MBSSdh zxO{x0$15ah57D?i+A_1|89%gFlj!KR02c%6C2YjooEfN#!(7T{P0i+Tybpu*>PMX& zD;ymy_u1F#1J~`!cdrExO5KIErFz{okaGrE)Dp^-WQu<|mfERsV_9nRW;TjS>{-)% zr~7F`DwCIlyx=t>`QF-{txRoW%hgf3iE7wj zvab;9##l)j!&e6CB(eL}SHFN9QB2X}X5bERxGy*iW|#vQNXgHZ8cRAVItr(oTYZT& zJ=U4Ko*sM;K=OXmzCpJbV=uoj5lwr%z8bSfE#W|0`k}&Zx@9HJe%}dOTYgdug?F9K z^(R{HAckO zoLO^ct(o5+dxg#3BrBWueV^xjo=KxZ?1pk6nR*r@8a9 z`A=<>?~lg7jwZEQqIPQ|EAGWx;zh6FDm2j~yuq;8b<%j_>NLGC(b#*ri|K*kvm~dm zYlpOlS8B5GV_Psp{G+;HfZYxh==R$S2kjz+s;Wq&A~$Qc zJ00>q0koOA5UU+$wT!_9nsBjl0RztvR^lqCM%eENeaYNFRBR^Iiai;gR5stqn&OJ{ z2Y+>-e>|bJ4y;2~c#KlbEaRcRS);Zf_+YZ9X=PeVm?%~pvA5sQ7)f)na+zZ+b{*py z-10epE`i>cF}3F+N?+#Ia)ldLbkX?KbT>~vI==8(ehNFA$98+j>vi}ySlkxI*&4=R z6~s!BXlL|?^i?jv@U|)#bdHL(|58RDMIV0sLuK_u?Gs0A&$*q=QI)nSHn|x)`zUhw1DYp(>&!))S z{ZXy;f<^IWsdg1>3x=JlejH0xpEJ3z2AiNsR}aYH#fNAE?DbB*A4(73wX^6TuT=K> zO~Jl(#_hu<03{j=!i(B@Zu!|3yUVCrqFIKwCs^j8m5gV3c5_le&+Zq=>>a+(Jcz}- zvTo`5M=^E@#+pGXvxQrp9gn<3B#1Y*)hc72D>8_=gsNIK z>}72kWMIDO`q_3}&C^6(B}VUHX(4nZO#1o+CaC_#+#&6M=-7jt=X35s9GX2g z?1W?I#Zx~6|KOvDk3+f@w_o$#O3^5GjOBT0ob+9kVTGfwFd$@bTHYPQl!KTdnxg&X z;YMZ|@;MDnl4VDhwE^P68*`YrNr?miTz$~Y$%!VcH!-I4$=(5f7La>>q^hH z^VfW(c84^Q9qn`X8s`dOE`Nzm8TEzOU#3+dl7G zw+>BF&?XXuEpWOIubZXVl+KuJn*s`PUD>Wy6zjl3uKGqa8aD~^$!J7xR?uif&PIoM z5bJutOo>9xkTTX1DpzDNsl#vj*%wmEsLEm35^C}ctZhi#3Paks{9ZeXvDK=4c9fHt zo*0ctYUA0BW8%r@d%)T|6qfK9$C>^dKwlUYAwKe6k`J$dwC9ZM_?!7vIql5MkBbkl zI=*0hW070Y?wP{lCA@2 zO->qJ)euj0ua34&>lj*i=sQ+as7R1W0 zyl-(8z9>6kR_B|+!>^Pyd+WOvhsfrFX|cfK+kT$f|kWHsNW#vvw7%*>3#U_O~}M zLfo_jlBU;4YCf^JMgTY)X{32oI7wgjmrGKa<^AxhGuP@Jp1oFRV%V5rt|X)8P6;CC z838ppFA8HUW&45)y7yAihxC@&`{|6IefgAs{T$t9|NXPAj=RUH;h;rDGj3zI`u)um z4|DfEA~l>i@&pEiTD__iyQJ@=A^)p+{!{IO(hcjL07R z_mdN^V~R;UnAIzuf9QgM#jnluAI=`0{^tk(^HKkEaQ)k~{6BCy@oG~;WndejuvOtw zzu05jjN>2}{yIY1g>!U9@?lElr(4z)bnP$5|9a^YYa70YW&sE%cF=z{K`5= zXi!H4-56@e$G?B=mj%&M&c;7tH{`UaC`fd(&(d{fi7AX(;MrrnU&=#(){zN|3wwi)O&QH=J8q?5YJ3~YOfdJFrAW+@O zf&L|K0g&Q^pzDF9t~C&te=*jLv}0q9EoE@EyGBVn`7NjIL?na82rLRC&~T2Z{)0$H z>JYWkwBE}&49ktLUr@5pTYn(uqb{R+PwnH!lL<;YSvwG+Ig>V99ULhc2fqZe?Bz(v zhj|@D;~Mo=H5*7{7uxBq9BvB~2tG^~+vSqKtoPzd=G(T!Ij;74ZMyYbzR=m`*ENWF zM(wjxwq516U(nXN-wn1*PiO8jm3gFIbliy{?5xDb*KG}=E6~T+_s30kU`s#>LF0q# zuYuS!#45;T4+$OFxE!vBM7fW-PFwNdFFLYIrBsY^QW@9nU3nRX89~&*SR_?~td9e* zN+u&HzVJaW_*wOJC7q)2^;9=)*V?)|%lPiX_HSL!E#$YaMB)s166Se7R+6Bbfvkr_ zplyedrm;75!^ZZH{Ta(Zzzet*2w=yF_J8QKRA&#CU@eJ%zcmtweShd2#$o;O=;K2W zA)g}HN(+Kuwt*Bo;;)(f&x`+j2>%=j|NBjczboSo4W84 zyLmPlO^PA=&uPNi0_5KX-L2CqoIe(#z#c}f56+()cR#n%He)x@v2*Dn*Oi1R=a}rm zW1q2|;eu@J(c0l^fG!6PL2(QcOKdB?ozZrkbQC&0VR7GGL%T#53ia@bA^q-Ew|@88 z*W5-XuHZGx*~?%<%EKVV#7>452+qbW%#8QkcgpE8lHYJ#$g^+PXUr!eI&SO)O;eg| z+;Ju1(;55dLu8|`_Yalm&;E^@MN?OfO}s;2G`XQk6Q!MGwBDkr3Q?= zp}a;(w-3+FtrUuFLpFAT_4WpG8rjY4QgKGc;D;kJ_<2kMMAe=i?^|qReRuQ8H3dUq zsr%ox1BAO-=~rUthVv>lzzecM^RwZA^dC8Lm!hpRDC^kJShtf4l45`K3;U4s<%xmY zS|eT5>&KrPP7@t0QVj`Pm9Hkhjdzxc!%JO#1Dt-1zUBv{`E7OijLvEJzvH=m`O?py zD2V*VQ58cG@`zTEsGs+3E6C6ljN_B#n8$#7zT5dNCO-I{GVxR)>EL{%BsQ00O3Cxh zEAXSlYkyJ8{_!aP8Kmpr0aQPJI$tLd%LlKb-s^ERt?i!Qyt(pbV(y4vLbN=4D*C}S zv=VC1t|*87DcJZPK!W-HDK#kl+o6)&plZ{|%hxyO-xD`Zd)nAJ~J>Do{G)%A_F zC9Nj%FRw$gZJ+uC^++>)Tq%&nen03~lI*%iMMBQT8w^8qXC|-ZeOWzfP@B%Sx%Ew0 zX7I<|H>c_Vhooi=rw5v)`^v*9;1bi6lt&58y6lf7|3$t0f7WX;P}~10+)4W`K?{{C zeuo*EfQ@z1h62$4&hP1{!a%8Y_rX4#c9_?2plu~xJZaFMBC+kei3Q{Z8RoYF{?OS^ z!hpH9`ww%if{_FP_SPo|yR;uZ@T05X#u4p>5$%lz?IRU%&Q^~#m-K=*mTT^M8#Eq% zNf~pI3~Dtil8+*pg4U!1J)l+X=83*g<#e`^q?hQs=nc84pK}RZDkLWk^?ORz?b88i z!TfgpCK*%#jI^dnABdOM3+K>qB6@bth*y!l5w2>OcR?lqh3?J>Qe}fjx%aYl`-R%T z9BUx z!P8_c;kwOe@$WsDfFI_E3Dn8ic^(?AB{TG5c>cT6>8#~*bFR-5Ciy~=wwX3rxIQDR zzZ432lzABj6d;iMO{KqH+jJF=pjWX?@X<1TuaFny9+D!t*O8`*BGkn5X9edL0_AnC zcod&Yim{LdxnjE-Y^)dxXe(wsoilGG9nMontu)#L)KErkz z%PeVq7t=vwAfH&kbCFJyqX-=-a>HZrP zIzXwTsvkg*$7Zo)D$~x_5#G;SAcJ7@)<&}Wrj;ox*2C@y&Xez+eNVYDr^9_^$y@AW z(5I97GkZ^7&&bJ_8g0ep!>kQwe^ukf#0Jm@-HPZ!T$BFFFtEL09xd@E6dR_treN31 z1(`=)HaL8lb_4s6CQ%(ZU?}@ryB88*>rbXbV*LH=^?0~LqzK6`oz@=3}Voa^?JupW9p<~{@X9b&k zpjm4hH_vezy5WNCOqZkVN|y=2fjUuT6EYC!cAs+%5g@?h_u>_rgk2}M(8U{LN6ev< zlZ6e*CYmX(ilVPV3s0SG`Dt;+^dl-*hoVW99HgSlEits)7lT*-Lw(Y1$ElqD8FDjm8L=bug6I55pY;Ng0C zm1TM=<%I*YH2$SF`-SMJs#I5h>r!xOa6dnht2OZ5N?l}YE7;Q!{iw^~McK*I4XDs=OVRMVpYj;P;#;0}?Xt%y zh{)R2>UzMO2T>J(zYttre&capx@lBus&QHU8bldvtliLmVTi_0{wzD6a6|N4`i)%4 z7b$*WcT{KI{h=EnToZp66#F*!3Kam3iiVOy&{q6ot-Zn!))wNV z$+J2K#@J}K-t!#Myv~lMU-6WJS8C=)x*V$=QWs5aSzp9U{x0Sq^XWwMlhHj2}QnaZW+WV)ffz-;#b_>{o{hO|#4cF!vfT;tV zp|)v1%>Exsx=Z?|(etGs0m>_?K2ZpOSH;zs`Z;$Tkuvkiskf~`T!_NqA^mxg?na(dEKY`rpaQ4$OiDo;SfHukwvym7SI8ll---$JO?f_^eAo>al7#8ipX~{ zSU}9V6QIk^F>n-*d&Yw!6=)|F(0>rGr|}lUGE6l0>a?%ZuK4wL&{)Y`L?!b|Vn%d! zGwmEPBLYF+g*(23TeR$9k|O$`5E#cgSzocfj7TrhsaWMX073|?UgHp4JA2tdjw zxQ#3FS>C#!FL0-s38$PrkZadv3{{Pwb2y2@&TEJAb)M>opW9}Ib0_3pQL_=6JarHP zq6WR()mrH~XxbK#z6VWiZ-fA_DAwIY0EiL4jo5kbUX-4JS?E9Xl=wsEj;KcewJdHW z62Z902x6ky?|p1C@;1)oqN^I$J8z_b+hC`IRIP!qnqfSi$*?|3EQWALAg>NH0b5{) zu~MB%{rGSl(MW)Zhsce0_7&TX)e`B!@$r#!O;7&h1xLorxW4EUc}gGlCgA*08I1!T z20RtP5ieGyy^LHwrTFzZq~$+3Z+rH%MO@q~x2u$flFfW}nqkoxWP4=1T`+E)PENHXAnsunbb10$=FD8nrB}bK4tt@aG6hpP-tv{O<5V#n$<8!~J z*iSTdxwE=rJzaaU32iYWqpr)2s_4i#v#lxhC zacq~^H@f*<$7Qjn5mhioq3K=$++QP`C{Yzp{I%P2=??y&G&|1#4eaVLj=hM0W2t z9`eQp8*L8gp?wmCk85ts1ByV{rMGQe>WXoC^LbqynG1=NlywbE5tI7SY(lnm&y2~6V|mSV%^f>@Zat97$p)c(12n&gX+ygCPIMcwFm*31EwfPafwWOERIaCQ`oI@eecz+b12WSO(%#wIY20_A z4%QGmJ1}J5v{?Z8EK9s((O3`05IzX_nBduy-6zK=8U~KLE$Qb}=XdZ^nax!yLH{Dj*kD)3N z0?0CC4Zp`af^1R`S5G~N+B|KvE%Fn)v;gakZ}i`=oQ(R*jF1--9rbRzNeCFgZCCO< z%4}Rl2W=x`q3?qRvv;yDLAB-DJFZDTaKigH^$wv^)(%_`v=SD~BkCxC^q}Y1wyoEp zQHyrXY+{9mcOLvg>pTGESO@r=R0(0M>w%#+m<4|6XNIvtt<&>cd`Wkes+06?ao1o7 z4-@%Z2;`_onBPF;yFm6M1Ik-WJ5Uo zq&k*IaoFag(~a$jJs@4HbWM_O^_OfLJ9}rz$)okgVRo%blq*Gpdj0n%%A!Gi4#w>F zXxsE#ZilUo?(g!vkNS#lFE4ZIGn~Kovpl}q-HhK{v+(E0KB_4w9nc#%UZl8APD*=D z<6*5pSUfVRcdtLdbj?T~*V4p-WNV0ff}+)c7*t^XYLigY*Sdkd!%h4`_T%7!U*Zj~ zh`LF)XE~`%)z6j!;2a@?f9Qm0LaQ|1*fzlvzhzK~7yKqWTkC8l6M9^b&az3yh`et$g0${{XaXh5BG-Bmb-t?()Qd#(`2Dp8Bl`t}!wUn zoiuZ{HrK2w37daE!$?7vkB5<+Ur44zV>G>SWsN44`nVy5wDG{Y%;k!jd(kSyW>e5+ zbmEEk^9kAACQ`H>vE}_hC7RY|2%r*m-u1@=rY6lBZ-- z-^N&KDCPH-%OS*5pC6SjKz6OKe0=uIo>*Tj0M4owGu6+~Ly+Y+&?P<5`BJ}^VmE?A zLLQLt2g;j$IyAQF5EGV(bq^B<(W5F!K=uG~vnbi{bc{Ocms^|g#}o&;l8qnb+1k;9 zfR;?o9g<{q4`{Pq$<&9*AY_QFhVn zE4yxy6;;EjKXmfHUK@7pm(|b&1`&`YblCl>$eAZQ1u^PgPrbU-4NlQJeONfo->`9u z08@Pq(L*MkAWei5GMC_2Yk2)p_oxrle70wC&&sQI#i~DndYdI$^|RzqZ+shiv=ZBr zs;}rTtzfPg%>a_7mt^=DiGOTEy*uWG&~Q6Ya6RDlGzoN1Pihzbr4f;t_ABD&N_R7# z`if@RZ^z$z6NvVPtl;vbGrkv@Ai0^_on^~<&Wqa_{P2P;JTpC;!4UZg<{xz zj?j}TYolrLj-3%lrL%If$-fN@#YoBVZK?fs#y98JpO|L(q?nWqZevm?G{??!RtPEg zij-LQseX+2F}7?2yjIyyKGmL892eidIWRft`m>TAZ87!BaxTUuLpF|iF?hyL!{K8r z@*VFC9Kr2A+A(tu$;s$qTwj?YU#)*J*_~5|k6+2@tlr~y?Sda!s0A(~4-#Z2u9q zOEbH&`s+CS`KMCKm@h22+P2;WS<&bZPUHsHCC19{K@=f!fZi5tFSg8I9H)$adEr~} z&CG9}9SnlCR-iWI1vj-DV@zsZJ{PC0tQ+4&>g9rN+KU!s|Dfl2eWo>u;Q?xNDoBCQ zI5JX@;$r0=IwESYN$ccF&X~xUKyTB%IklFBa-Iuj>$j^L)q_LjN;E=m`^ubkioIA0 z(Z|-BGY)C?BRusI#MgJJK?mq!5qZ&^k9 z$m)LmZv8)L2Vy-OOoiVr(Yf;{_j(!40R^WROIB9bJH2ANqf?f; zOXVLr2=%Y{_?;1UbwHAXmvKd_1tU6gQ0AMjTM#S*am?Wkr%v(vl*aQ`K5{pQW*{NQ z@~FYxsZziDcdg$SBeKjP+dYf*nvmci6b`E4TCs-C(0WMhPK>1U7cKJC>h1p(J^E)?x z%p|rQEg@r{eH|99Of*rHt?L!YPxh2ZYy6q)WPgr{rtKED%VW=v&2z;yNv^7GN4Dv= z*s;g92iVL`-cXym-yt4%6`1hOSWZuI->`&{JzGD0JO@Q$O}kn@PUBPsXycNr;e36+ z;?W>CeVBRGu04D+MWEHqzlNy#YWi&F%}Zq|))M)_6)6uD@~=Ih{Qb+%!CwZ*33=-o_rZ$8~jZisry6DLu+az#D! zAMkNBPCncV9;qc$OSFrf2kPG5``GMw#RJ{oM`JW`PpIU~(@hJ1l5k;ITL3piHwiyh;2S&TGvLsm|r}N z(pQ&-8$UhG&KLLfeaYn%Rc9e>t1_I)xbLGTV&OrI7ffh!?WjCgJFzppEBKSj@I{04 zjuS7}(WX^Mb#&xotiYPARZ})-I|4A?(59aDHGf@al3}aOXS8ohnRTcjYg3&4^l=iQ zRM}E;s45|$62}L5bkuXdZLHD+{A^R=dyIF4jy_7KVoC4WEA}yyLfQz1X`hnAGOIW0 zEfBI|IW#{4u^$N1{xWNjo>DVjlw*%}a4)~zob7xk;5<`_2ze6>K{GAq zy_UqFmrlfsWnWkSx70mYt6sz+>YA;OXCYu-Yfzx7pEjUQ~Ow?yBioO z%sqg2&=7CcanK-9~pHbBXqA-0)Qer2R4f>y-*Hg~_XV3qc}2F*`4e&K$B=D)pV z#h15cF6*tf_kODqCvX(#zRH}MI%OK9dR*DSu#m{;r}l^LaVmi4slsXULSD!M1zE!; z8~xMYY;3uU0=+Er;~lxvv@EarxLJL%cJZort@I13oi?440C^8}f ze}LsyvYYN3Q_S3A_{DgQFH-H<%DoMeDABtkmVgr@XXkg8l7;Rx654adsKT#5$a!tV z8sOih%j2160>WSHa^5QVDrnI>rT(RRvfg!Lo5nhYN!uLytqcjE39c*iK4!*8DS>1@ZQr8w``O7cc#zy@zwDH&Tf1*ZewG;>FO@?}xn)qFMf=cPeEvwg54Zmb=du>p8_UHR^4-LYl zzCJp`eJXXZ_9iiV2f04Ey>aaJUE;BV@lLb4rs{~5?NU)glY=8UJ;g6&@T@4B){$m9 zgLCwoKGTyzL0_ycd2^E6G3W>7C9vYQ{-*Va_G%7035ZZMYPin5+C0R5V++-106!~D z-a&t^FdLhTyNG3UMTw~{3Gk|ebtCS4t);&_b>}T9_srQtO>%t^RUU2_$s<$r+w1tw zq+=Fey!IW6TJ71@7Dr2rf0|Fn`MF*E<8uy|d=uaVpXrxzSD*9>Bh|px=+YtM^hWBj z)zn%LAF>MvVFGmtWz^yPM(&&NOs3SyEx|shvODs7<}%!yDo;GW`kL&k&*^E)I1x8= ze^@G2&lJr5w(;{s(iL|1T%* z|5Wa?b0`fE*0}cbK6j!x%uXE?lk5S$xi=Z;-9JChpha8l$^H#r(?B5s2%J=3GN8!v zj8XfT4YjIPObsYL_Ko^Or-!)e-c}>`FM2Wme=_&}qo4JkWcb0NVqs zgdn*SYwz!Tkg zj*E9n<4&8tVJLL^H|D@6)|LKazCrDu15bV6TUpY`$Lb;nu_R|;*$*UpCcwv4VE~wG zphAvqR75m9f}S_N!sU?!pVonFF6;-UiY08$7J7R`gvjttB`}^vJuvX} zaAA!!`-R)S^w;2W^fF}vn_-@8E6+Kf>$s!9hT0pJnp(zU-UG9R^D|?h(O6jR%=Abd zR{r5}g_m_O3*=r;vz^@N>4@;U1T@r@8R-1C^Y7kjnJ=ol>$@vkL1)4H=sK9Qb_Ggo+6PQ}G^B@*1Vbw1mC$eHaOZS2*hp4^F_uga;?i53LR zk4E)Y%%zGTZ%-sx@bakDV6fH@#Z(56mw`BaxIaql;b`zQhAFd?eEj!C-uht5ss zxCc%>4U~7H^C^#nwtyPZoD7C)Yy>W#%1XZo+`ttiaZ!v7P0u`_g0+#lJ6Lcg7{{1(5$X)n^V;Du1 zsvuHK6{*n%t`7X|DlRYw{2ZV$q*-rhU!dA9X!A|125d)q*-GCY5gxLSeMmTN-`sKk zT3zSt%67y-8N2_9eok_6IjR3?UPDy76}oNvq?Kz{A+WM(NR(L^B3uK!+Y8Z=?g%HB zE#w^Pc}mWmTjhDrTP0hioAJJS)B%h75nP7Pw5nIpiwb~ zs&b1=6rU0=`q1w45ZR8@KmiEH+xcyQRXeE$u&opY?}>=0D0*^tgBI_(?9b-(o;ABW zL@mDIZF0_0(MQ|N>rKtNPGsMaiajkZj3-OA?< zJw0Z(N8F~Co_-m|XrgSe6-JyFJhp<^6vF zXmvf+Cb2zAp=Qc*clLkqlw;2Ll@4`kT^P{Nx99&ozD{~L+bqx$k?fFm>dUipC%(4c z5OuE255yL41cuya&XTJ5{H47}hrdd63n}yJEfgx+TRSc(W@^ZoqSyv8O=+Vuw)%83 zM)E&!NdH3hdUo=5wi|<5?RZD1+W8i)S*q|a4Otbjn$#*oPt8roQvy>bp6ce2o*U)* zePT7vYjDP?780ES=uzt|yI($y4RZ!4(LQ(ewaS=)&0nfd?Ax(-RfW=q)IHHW%^jX$ zcF_xoLaT6=>JQ$`uW`5=#W@fNezUx|v931rt?&)VoPN65WJQ~B3I|WZ$&?cljR`R^ zO8@{-y){=R`(D_ChL2P0K4`9VKtb&~=aB!adrAFGH%+a?4Mm?y&RdHTn>(LA=s%)h z6x{({N+Qnox?RHv{-s>d;a)bT(ylnYXX*a)1l!unXAZ^UmR@VUbiVwyU{gfTA%)!$ zPY?g;T{yU~BcS`Va`^eM^0<7S;o!ME%QR*MR?pWn{K4X%w1tY-zI z7Nr$+Jb1ffJx(=WHsd*w|4M)__vxFo#s&t=L4L@(n+L0Xe23rPYGL7!sEyq6n z7S~N58!4p7R!i?Jbqi&1GNz@H7?J*Y0eI>c+tp^*e0#DNdyjbLg*dfTf;5P}QG2!$I)J&TH^OY8T}xo+rPuNE8UICW z`9u}u?(5F;E={6n8ZN#EM+tAgtl6;;&1y?0bi8T;#=^YIjQFM%^%eLv%stzg z+f2SH7}9suu}@#}7HrlvGUxTtx=lnkgLq8J-0Ip7S`k7WcCup)B7pN>#yk9`a>?|I z7{g1C?j{fy9*ZXZERya1ogRJnlT?JvF&y(ESYL-Yn&W|kG4ygpctv|lBRKnQdCfMi zim5p!p(m;dHI?7f8H)uU*>iLDEi=tN8?e%|sq$vqUPWSuJ}cS`n1+Bt(FK}Hq6%k}IDu&o zkPo#Va3IK&5>m@tLx3h?cuL=>fXKt9?@gh67?h1(R%*7PT+nP~S?`V4T{#h%PY{~s zBuAnKoQ0|Y4J6EHvw_p?_=%ivU1fXLjC~8L$L<*4?AeMD)eTo;1w?ksc2&#W3hfcIa3zS^!WG5bf)T~?l_U!r>Ih#O1oHF_gh01V3p*5&xFbiJJQj=B#~O(Z*hh@i+-Hp1bgQOoZ1TIsr0TcYw+< zcdTf_=@-y3p#j3lN<1FiHh zAjoHE{jHpJ>NHqMXp+aGdh4_Mou5XhPrrG_eQHma!y`4uY}Wjs zL}IlO-;h*Lo88nm8_{&&jiKHEitnfXwN>-~VhH}sRfHx8H|3D%9Bh=e{Mj>VyJ?pdj0OJ{p89QBvj$oj91~Pjn_~rFUlyf z_#XL`>q7Sd=X%Itd{XzPazxUCRIqrAv5o8ap%RE}zHqF!(M*#Yp)vx>vmK-WPP9EV z7-_u*^(;1#Z80iVnbV6Amu72t#P{fyZ*aatmAME3O?o`MKun9MLCzWVK%)>PSkTle z?Q+;{bbNs|vmQ^*=H928&A1&arVE>`?zlVlA@g|^^68`vTF@+f|Fhx&1}Pn)A49!C zw##KE=MddV+9xO0s{A7HP=toDGe=(RPpeUSu&-3hs|{t(n@rg($IY<{r`oAX6w!4- z-oZViY@ySW;~PGzS{%vFk+_SZ=D+(k%Is}-73jAbqX&ft@X4!9}DcV z#Tqf1fk$b<)NS(QAG!=&f4s&eiuC#<=q+TQ5om^6A=?-=oo}W0KlJ|;SY~Y?tGo*3 zyo!d)o{e0y<%s%lVm^#%m>{tk=BKmt_)XerS+kr{ZGG$ zO+!kB9(mNbfdPs7QkJ^>IC0mNELJ>y80|8I2R?_or^Pjlah_#&(`NC4Gy8SUv(Km{ z96UjNAKqF>HK@l_p<@ldMmmKW^}IhPI`m^e`3t2*X%@EbR zm|RNqE*R1d+0at-T*hLf zN^qx;w`d(K8-`pOCjg7l{1<|GT!VGV8i3GWAe($z=Y5d@W%+=4#%AKdXzM?yHqj%@ z*_Ir%+5W*nBa{JSs>_y|S}&}y1Pu9tPkAwtbpIX4$=t+0beCg=j^^M;XG?Pd?x#6W zA_ECgjMr!Wy(_Dy2&vEV;K^&cft(r%4x}j3b3T|eDDsCF?FX<-18GFW-_i&;?H6VT zs?DOkc?zg0=A}2D`UkWO^2%1f~oA;nGqdM8%rhCaf3`IV0vZb)Li+ z-Biv|<8gpWF+@7bpNcPkjlAG~c8O`27!`{GF_2q&^ah*6{V;8HIq7Bj`lYR!kx8SR zzo*@NYC0IBMZU$48VYCKiHZMydg_$|+=2-0VCHLvUDObn!{3>t;G4v)!@&ORE)aW# zmwYdt&%duXi1}`I`W<~-+@WlHXT;tgx)ZB*ECip3g>&u&M6Kvx&A$A3V=IYTuMvbq zZC!0`)`Ypw&*_QjH!X9|=%nk(wFFgpz^pNh@?guzn7Nv-SJALsj+c=FkPKd~g!=Jc zJwJe|n49?d?4#f-)N&#OdZ0bpI@tGzZZwbln3PG-kCj*PwjLRLsoc{OdsRi#Zhdre z#w;8gPdt+y*s;7WTXD&COK)+uinTM5&USLrO||J7;dnM`>vER&*yqvm$u|Gj?U&^K z(5Z`VrtKEKm~(qIw>sLOu8jj{b%4x2KTIW&NrG+o$Ly^aO62+zkJaD8Caw-nD@6 z*MNVc)pHikXJ=b_-QY4Ffg-n5m1At3iKlA{K6`M>QnV*DU$pVfRhW{!P_I=AUG^Qrrn6PW#In&v9s zvc32e1x7FAQ)hC`0THqeAx^6N+-yW$RGWH^+2lb>jgR)EFs%c3Rrq@p&tvw87FFsS zjOsc2eq~n>ua?k8R!w}N>UG^q?>9b9$)Yt0CLG53gHVb?z1raGE}K74<1=eh>( zqCHvNS7LJ-K|VRyH`HpCTUb2%Ls)HO2F+y7#Vnlxu77X1%(TPxy!-6bt-dpUB=S`v zgB4L@(9JA_W2m|j+-7ASqoTec@Bjy<@NA}>rpTN?n7WI2y1cp9drmi3cL(bt>ti>R zQ3QvtR?y654B(X^HLwW80kBpw1w!Bb<@VeXKuXI4=UyG~f`H8+Sw1;m+%7bG1YkpP z+|ywOuWz-4b)C!%CaO12zDQ*$@yGSD4BgKfwO&O^41YFoM8Erz?(}+G;jIB}gid z84fdl8-NKxqn(t~h7p%65?GmCvzGQpXeYOll3_M~xfqrxH5yQ*Mo zP#-PBNO#N|>%0``Dsd2^zJb;!gM6V@QKTdQ|A5{$zN0DmNH4<3|jXa;tQUxhGSRiEO(hZX;<{JLNO)mVPji+E@c zQo!VO3lzRMP+UX zD)L=*aPE4Bc!xS4y+APoz4@g%_#}8fDp5A5SG(hg$uma}a`okw3fNTLMy&abq;68` zvT93M**S*=nbXeHb7Tb=p~ygM*lU12g#GuPaF+i|e-ik$*h_X$(qT{a{*9Zbn)F-Y zp-mEm*)~5E#KOh6Xw4PbSp>4`7e%|P@R2J9awQ_ngx}h!GKy@qMjh>CmAdAt5k~Wl?w6d&tI29?DC*Ns?YWx`hRbGl z{NSTUpUPS*-;ZV1+c_E>JepizZ_$n1I_qQ}=+DEcYsk)@&+he%w1_q?XQ?r?=nkZ}o^D#{%k55;Jt)F_0ii`$ON+KO^s2nM%Inq_`+^{vFNagMSs z8+&+s5skxpf|Y{*OM6!y4(0my=cG=jB&Q@uXF&(Et3-uQgIT^Q95+}Cq8OUF`zwT#am`gmM426zx zG$vm(+L80;l~TJI?tYBCpYQYp>3nRxv3IpGQskg~)a30Ysqi&*Gg-*!;l@?bI6HmvNbT-!>Xq*}0nPu<>0X zI;l;gKBdA@0YErLMBMx>B>G8~^yN8X=&Fix{zcvfcmG5x8Ruc1mt-m^x>Tg86@#h6e)4dTo3a!d2ZuurAvXre#s#}WPB$n;I zXyPe(>JQI)zB&N?#Kgcno{WR?s@~TD0psb})M)`!0bug>;{&-tYNQK3WNvLm=P1YK z9fl0hRyy+Y^nOAhcDf}j$zGCESCfTvmIMn9Dhwd;$P`{DY{s2(?>u7Bkm)vfx{#-T z(`Qx9q#@nco%He)?Ab^)L6J}!*dD3GKT&Kse2^=YK}5nF}es zzF+M^9N~0ib?;AvE&SuHrPw_*FXjj2+S8=f;O#(x9&<{`YtPsxN6@UHFq(2~TvUs; zx9$_A-{H)W%VraE*>okT{-ENHF5AuaLExQnVq`c-RVR#1kJduwe8Ps|NX(j4{4)7A{M(XfPFT_7Hbhv)1#`?&dQX1q)gY zF?~yD=T))SRq$|v2$zoLQtkoN4nM+%=8Q+hd1CinY4LVlyVcr|jnx`jiB0f>56VQm z8`r9%S636W5&0#`)sU@U)4b?tZ#%nde7t`k)|bXf3=UwLvv8{QYLHmaP0%;av)W(Q zMsRzlBl7m@`e0qnyr@+JMH{!zB(m32AQmEog9%?AqH;+VQOG$x;y0F2a>mq0JkhBk z5x{7Rmz~FKSb8DFhmElT+ec;VX-OttcB;eET}J6Hymf;-n{n2avl8bFWX)e$cN%Zj zfoZipW5=`55n2ImoRvX{@)_Y~Mml4F#^#M6k?SotNSKGt+NCJCrZ~+Ps|T~2p4_;y zDI;5$&%o#u2b{^h1Ci!1C7%KX)s^E*Y~FDLdmtIEpJ?owG!WF?mc69xAvHSMQ*_|B zuB2rLQY+dhTr2=VDs{?s2bHdT%e0Z?^c&j~9(K4Dhae?vbO!A5w(fI%V->bRBtgCa zKXRW?SEkvh>a{jV!%O^lQ4wySUDejh-cMAzB^UeQk;w`#g)6Kboqe82>B7{#Movrb z4MWZVbYR??tDDs!=5nl{NWrEdRQAIDO`8BZYy??;9!DtI%9%8*iK^A}V|# z@u53fTncAftu1F;X>w|A-a2a;r|`&&qOinlvRm`Pm>$s(yENNy#Vdm0gIdHJcl!15 z6jmfb_QrBv6a&(xardXHjj4l}OOA(7e4jIuCNc-46e)*rLH zkgv~;%;aKEH}a88kPH5jfM{ELDe{uZnBnO9>s{sjbBu<)F=(rwPhDS1JZ!JObl0bg zT4DY-(N`;Z$Kbzk8CH!#h2bWgy@~ezn(27MuyHi8x4x((Vjy{72P~$`bIsmFSElH* ztDDq6S@u<5k$veOi1;|sRqyUCIsPf1_lIvAPBIzSJlD6r*?5C7GR?>apSgc|B6m~r zyiiu~Vgzee?MP26)up?eiKB=)doPN@SH6DG-D7egi|cRkq&S(mJ&(AwkmzduX(`FQ zGi}GalABfs-@g;w(@}9pi8-*HQ}5~ScQ4z~lKe(?rJ`5te#JX`xl zso8cv=j_0?TL~i1n|JKFg}r4wj;@x`X%M^RT$B&3K_zAA`a5Y!7zy>>nD_aLN&M1x z-ulo3$?^&x{bEYQ#HExebo>as09j$W`r}7Y%6WFm-ueL5vPcMQ2IfrK=F`?zizuvF z;WcQf5cN>&$%QDH%p5{f=Wfzk{z0ahc)^?O!H+a+8On!1)zQSqbL;&j0E}18*%jMu zwpzJ1byvSB(Gw}a3jBIM?HqnO>Whxzl4jD`g%L~})og_Lub6@|O2)ANX-}bC%b4;# zDa+j+$d2C&COdpeGuMpWxSm$nN9;o7`&V$ssY;(zBeQ=Q+j_?aJ3f_C>mm0%YO;!E zarjZ`?ly_@ zw}=tC*)PAdZ{vO2h<92(uU^%JVjPz#T-nh%J3K!&8{r<0X{BkOB~GH&^Kp&&n5#(C zn*6ll+_-f0K4eTM-sjb)>X@hHjIKk|9S0ja8aikw_FclJLF&xL3?cFKxawaaB>)}a zJkZTv&K#a@91|BF{^25<%sJ2#^Xe%EiJN5B59G0^a=>yf<82=b^6bN{n@UZY+9sy? zPYXeXw5*Nw!EGhPBfHxs?(a0sd9Vp5KP2**y$8xwp^n~4`~7WGW@~U6 z)iU}GME*gS3#)^(hkE4VUUf7eY!gzO#M(jC-fMNX7gIfF6tDAWsMjSCp^o*+wb?^zaJqxGMd0`^jS|k)_s6~6 zMB?Z@*PunkhAZ*?pa3 zrAt~9l-0uH4hHXjAd*k<4NMxPQSA+iwW%-RqtulhJ?=^KR?lK?az0we_3HY@7PHUr zUm#2T4nMvRyJzzUe|kz=Qu!of1iWPX4QZD2&Ug04+6(*)>vg0AvCiz=c$O;TV(dyT zbh2fj=@XCvezVG$#)>IkMp2BhQYa(8$H57`&FQREnA~rmmx5!_l-H$~8X+Rg?z&|9 zOw^!7iw<1hC%amkWjqj8f;?mb3%DI%ChfRlB3W;CWY~RYcVXAa`~$i|NWQgU3>P&+ zh&Kk9MZHTs2wRYeWm{~READsIySb3{6hwastLkB0B5hR>eR_7aqlM8)t2uX47~Q4Y z8+9$&C8iY3;%WQ8EWGes#uRDbAsV3MkY9RbM`>Zv%?M<#sl0K~)rp7tBC2C8mIMCu z45k1-ADN+S3Q08N)5A3eLkXL@hX}G9)iP2}S;oao-OTP`l3OUAH@qi#=jO?i#bej2 zj=qW_uy^6)j>q%VJ|-(Ar1r-~O zMvqlK@G2*wOPVZu8-yZ)UqQB<5V-(T!{;&KZ=*nVN3wSw@|ucxK}U*@(m)audSd_a zj+KBsmHbRfgd>iEEfoj@USaOa3wPRx?xy;NY4pA#ZCfmb4nC%L{&&R^|J&F9PVoiTA;0PP z+(Rm!E#5)zppif^0dqhD`qE92X94W)Q1q&(CkDKemyOUCf!8%e&Mg1jf=lpc2d?Jq z7fCN0{4}?c7SWdO1~mw#r2s&Q;SWdX9ho~aW`^lQaFE~_q`v^GZ2IA-wPweX|N8m6 zDrv-JwmL={RJj-|GTuwf)V-e;KX4Bc!1l8zA{rkXa$o*#pLyn zeH__v<_Cn=x=2Cr+488$^D;}lQ_LuV;xp6j{5g;cuHBz1_r8HDWDL0CJe;WmodGv2RJVsB@bYeP&~1~iqZF^-r(f`zmYRVBV^D9|rD9Y-JS^iK8KNX|il z)WH5qnGpSVjdADhu8K30Ys(_MgUd8vWT5_CwV;NoJ%&Ary}+50;~XC315Tr8yGpkG3?xKk{)f#(`h|-@7LhaYpWl$9FNM(&NH%# z>b@~Ap^r!U@Qm>W(Z_q&I11l+(Yf6?BpoX)Jt1lRlWc_Kowf#(7E++E0Qn#RJh}GAd!C?>Wb}imBY0!QbsLO_BM+y0-%`R zPLNt%x|g?Yc`I;Cqi>@#pXwl?#*szBaN$0Tem&0suV;T}OSRxBh<0&`tH?Vo3+mlk ztdt$cvJjJ07Iw^mgRbaQDxC*#dJTv(ztlo5r;)VfX=MZ%bV-=h{VK|0QJ_R06 zet055zb+!xwY-Crvku+Ad{Hj-PeIDsXKuj6lZ3KY`&&GJqPVv9h(!Q;p-CmcM?=@be?@tkMb5*crcGp( zr2^C*)mVdQTb^f^$VZ25h2*1Fc4;maY3aEQ3=PD@b3$jYG^pS{zW}STOImYv6F26@XKy;1m0eNErpb)eBCfq_|@ z;~P2rp0n+>V`#VR==$oV;2V6Sk9xr#J@Qe`fX+l3T?u>^=9ldU#@hGYz)jrO3oyM{ zp~Wb$RzHEk;=nObPDLKd{2{gW9?up3JJ4J8qTV3B^Mlx#H{7ajyaS+6ixM6%Y;pI; zw7>eSIWft(klsRcl^pb{_v_`wnbmMBV&`N5n))r5&r|z6!qt&qM+vD-m_IP=4rl|g zIwDvyb|!)mD6UZ9_R5iZtQjq(vWuC>QtyWHJgZJVTIwTCHYq)E-?CU(rn~5Y zi^TN>fLYR9%Ab-o8;j?J3qc_Fhx-rD*J&P@`bgwg!VAr+;PZcJg2HpcJBSDk{^bGP z=|B8jhWjPwA^-ykKTtd`?0|0&=hrX5=8l1kygrqnugcsz+Q3LgOe)env5|d3=kc!` zL1Te*$d<)YZTC7z>AvUzTf$qq6!1!KFud2|@DH$E zvMeF$!{Ujr{8(w;oo@m1X(RHhFkSL}KcOa&U`_g>E_$J1)snY5%HM9!nBJAz805b3 z*8By1f)jEqnga>LzRlH)3+B#){vGj7BEJILh3NZbnDwWuv3#{3Z6N`ye+8@S1UkUb zl4feTU>VX27g~a9RBpW(&xwB)$myHiCAEC?*PVU{)HEhJN1*>;Rc;V7PiG(0$o$ho zzuha4oD1wN^sm~-sDgjhABo+u0NcP>fXROw3T~O%Ej|L|-2&D%4&({U7W&hG7fq7H zi6wD!{xspmWqXlr&C(Vnkew*s5MMh3V;P{&F;h1; MZ?Z2RgfDdb2T=4=lmGw# literal 28782 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 diff --git a/docs/images/screenshots/02.jpg b/docs/images/screenshots/02.jpg index 9a5b644aa9f47565f656bfdd909a606bc6fab2d4..0e71424be4f80b8887625c3003d77d2ec51da3ba 100644 GIT binary patch literal 47440 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%0Du{NY9D;MFCPNuW9PG1G^UCxei!{ z6*%E&<57tTFP3jMsEA^x0u_fjx^pUS#n0;JMc7#rCsis86ng@`T4@8lU*kh5S8t(!nR)@#tAk@CH>@nVeH~LxAe5bYjLPvKZEcJ2KmZM_SF9 z&r_@X@$lmz<*%Q+kk@{I2&4S%{l8>e!JNh0+XNjP-Glo2#)0|4{?wVn6d}=Qi)+=x z2I@;v5o_E=7j39EUsC#gRRFRCVdZpWD^5@azHtc*?FzQEpf<43jia#M^0Po-v?3#e zDmi~NlvSdv zCnDY>MnjjNhT{+bzY+y;m`mY54c~S_UiLDmEyGnbqy*svA5;vwBwgJzNL*sBDvL(l zg5|K>%vEr)QVHHI_wD`?VWD;E=W?q+&WI(r-fY(Xib7qQjXB4Tq{xLyp-k;(d5=D& z+p6<_c^an1Ub69`M#c7o;_d=gP^3jcU{DU0-c$hheD;03Jh=Np@m_akjh|wHzTZ#( zycWS*O*4M7+NaBIi$G+^i&ra7kmWt}fe=DYBCnpNBCIeBUSuWMtx!vxz_KnjBBiR( zX?8+l^2fIL`!go$5<|WA)Ra;M%4*WgFME4Erx^9`-Lrcf647MtFb_l)e6_ezcvX|R zU@3O1SSlsa?6k2WvoXiQ<*cLJ$L}Gsp>AqASxMA9C<6oP0!5}hkG?T4rA)lbo4&%l ziB-RYC0Ri|_qQL94(XDk_~xW#pUSMJ|G>vs66d1+y;#7AmS-KeTas73JRuJbE+b(+jpK#l!4@E&)yq{-HBk z9_;qZ>7f6{R{OksS83GJlUuU)5@Dw=hlYdGPgS(CfM73!&_P4ZI>pX?XQwi0a& zZpKqyJW3vUoz|yDp2!E>WIt!3aYjA4wqdGZ{5$&SjoZV^?a@*juq!qhSNQqeKr?e` zlg4jXwi;d{RU7I|viyvOo>a{}FHjwLp!(r24@~b|KBtu*xAEEQ=5nJD;odMh*X z;{ez_;gCkx*oY>i0JGR>3Z@l6s6~kNu5LX zDjjF+Dqvbu={JbtcnUR~R1S`Iy#07QjG)AspEcS0O(nz3CeQv>h5ED36ZR5;0Q)IB zJIAR!uXEXi$JlQ*-pS~_aZP(d0tP$b&RcUdTa4G516sB#wy&svHS^L=;b585 z45jspvGu4}u+Qu&VH>-l^1|i3;gXG4YvfXUZ*OWE(q4pe-TV%oJLi|Rj6AW*tC4@O zh!w3_=^6Y;&7Dm)_P*w*wwgTXs^lJwaUPyqcw`D8p2;}cnL4U`7=Bbg3_(aQ_S!#+ z^atC)Eau9mrN%bpE@r|psx`yTY@WT`&#(&~n|sEEJmyoX6+nW=N3iB?E+4t{L zg(rlzr?B8Ri{WJl0poibh>m0LvCy1flW-N4fQ3-a5n&#nQ7!Tok0Os~Sy{o`*fVbf z-izgOdFY$zsh5sRpc`a$%in7cZ_RmnCaLGfJNHpCmL1~E^o+^;6- zSl?)fz8B}FjLr6XJh%@{0^^kl>e=Qi zV`rq)@yw=?_wE~#LzIDLL?^p^V0wiBmsTWm>s^`Qacg#(W_r2xHYj*iJC1>Q> z+EZzKILvGsxp7GUeed%-G$t*^ZK?kIA`E6GJy9?kZ)>c&xOCiRZ<7jLCr_Z>ZePx9 ze=zYLF6amNs+ZYdy=Cs|F=yc%6;*>7iC?K~qXY!eY7Fgq7ngBlAv zjjfdY+q9Pi+L&50?<>qV&oz#prkby5Sai^NTj0pm8=(0Qy}Tksd(SE<5(Fqj0D9*vY~APU(PN{bKJD27{>cNtJC-Y_vVitEU2#WXSZ5 zib3FQEjEX7DCKaeujjK0Y@KYd=(Xs^Znd;ml}UYZ^wb3wvgi4qe4i9tHNGW@SU=w1 zFHu7u6C3D4>y;elxIRqZ7~a=_KEMZ41lvjW)7r{NeG=MNEr+s!#vDz()9;o*msG&0 z!_Y51bx(6IT#7JQSHX=OxE>e1v&UVRvrD-ejcGH^u7T!jmY7OqC{Y@@@I#&hbte+h-=mK~Fyo*&C#@$D$FGO|-97N(&{KL6IX^rvg1I{L3dVJ{I z>yY}(JhdrE-k0n*Un$nM=E zz`UvcPj(IeH;OnI`vxY?rSy**J{y0tWOazvkiP`eZacp(-6huC%E4+F&U zEofbRP2l*`3imA4r!6;WxX)dk;$k1|p0j$3*ZHcXNBs32=4P^z>5hm%(2UF{N+8}mL-g-got{E)X;gn+`*U6X7cT^lsrJlM`(Hx1Ca)nsAV((<;~m6d4-nB?O#WsTO5Qr{ z^;XpepveH8wM_gW^_$KbTJHs0r5;K52SE!+MUD5H`4mYS@Pso z1YBSJFN(}61W5gaDBK?P;(tARYK!SB8-6~vv03pfQ*}}u`gRG!6W(^#QG+oc@NU?0 z*mxu!dJu?X{YgQ@gPQJYkW1um+WbX?V$Cn zQYH-5OnM9HCbY$pGq=RzXvFDiLrmz}NuWwuyk-|0+McQ@|AD>y;Xy@K;pl1GVr)gl zMrHa;R%NP$YUu?YOKCCmSAj0}04o|{J9Y2O#H0N8!%5x8r*v7l#mV5=+c=(7vgoCj zR#ad@J_J6k!DEa2P$c}kDyEh0jUv^DMXfmQ5;yL1pyzJ|f%KTrI2Rm#q4d7lIUl!*}B(EkP3HwbIM>DUtgeRaIJDn-Hu* zVtPHl=4n>N)p{8b7rHYL8`k9MkK-QZ2|l~1PH&H@R12tC6*Eqap}Z8u7&TAb>3F3* zNtOS-&s}&@8xUC=yqjIuFgKF|-bZ44Xg=2XQwY|c;R3tl``{JP6ITA&-!hW2Brs|s zA+LLC^oYntR3suHBZszvEedCdLy1A{4K8UhKN2h70U;5PdG@f&myb)2>SSJ^V()jb zI4dZ4JERG=RISMTM1jeuA$4;YF`%$aJPsv+LbK=Qp+N?9EH}UXE3mZce{v0Enx6MB zfz-}qjv@aUkb2-;m%x7pq`v>>AO0QQ{m-TT14{o-=KANpg#TyaR1o<)1LZTcy=iW} zlfoQ+Al4bS3_9R(zLEP6NEHB`LR&IAoz5zOeAb9PK#&aNkN&Mh^~d~C5yb(Zkz%~? zE+o)%LGUSK1qQ%UUH7yM7+{wgUL{6^<{NifdfHsK zWN5h&>(cd6`GK6@g|!QkpD>h!+tY$2IR1mjMP)BPcEH}osOGb_G2)cZoS*QG*NaH) zSj&$n4N0yP@#fk;HR5!b)0)`+K@=F5qwu;R5lZEkGcw|lB?#)C|KcHE!2 zf9;Bwbuh0}qSAPGg8pJ&h;?gD?C{INemRi7vl4BdtlnKVVh6KQk8!lCA4$2z{M7z# zJe#RS+K;(X%z+!0CxzYn>R$IM{kEPCOk+Q3Y*b@NxHakIQSU+F`c=`uT}N&{(H*af zRYsjtAIL)E0>yPY0EL@X75Ft}Y*$Z#y$0GKHE%GoRa|r<_WhZDi6Ob@)7^L6cg|^t zG3tut;K^t5BxO02spQq4G?c~J7{K^VktH@>=)jsNz8!uo1#$7Z3yYJX4Zhk}-miSp z83SG=^?cRh1D2}^NktHEOqp+qS{u9bGn{p`6dFIgdaz>R`s?;$LYgo6fD&r}J2kCf zReFfr|GrmB{WGHQzY#bNyA6f-7!K$>kOI3PtWv<DFRfIp8DGn1CEhSvIIecIy#raB zc$vq#m#QZ?-jau=@g;e+Ps}(Nialu_Q{WtNX0xZUJ~(F19G2foVDeXoIS;??6KiFG z#&=Ftb4=XpRjiHiNe$ROY^zw^7L9luIzp>*Q~~T6@UF-XvmqhpLiO2x{|Xx6F?YMd4ZCt# zqvwG(nyarKS$E~vEPkdgSx@XMkk7QF4VNT=qT57j@}w$bH}-zz@L#;;3f+xCcsQtk zs~D>TUgjBeP>8E)>7O`M8S)Z^&DP0-e}JjWw;9bN=}r1?qkcqn9ZB`F4}Q2$Y1m^K ze;Afu*%*u$2N3US_)gq*8z*PkK)q}>oUNrhYs$RS$L_Mes=dVU+mai87VZX)?-DA{ zDP5zm>N-*WiWQlyET#=&I&9gdoNoJiEmTcCoEfDwJa9=7?Uz~$@_qYxfpTRJL%EWu zQ1Z?8fK@&qBLCZGw02`Xwk2pArPx#{*OsbiZit}M>)5;O)$6^M2vdZ^qb~8 z4t^m3u7ZIui~#d^ah*K2Jw6N|-t+Qe)e0q;GCFssJ;lMbx|kl!>& z9BhqD9o`F))WW4b&EO4L`~Rc*BJ>|g`*uLf#wL4i`|?&4hq`q&d)2@va_lPQL`HU zTu~mO%`kYmQ&CMo>y1IcLmQj(sZg520#USyVkokctnn-gUVjb#n%zQ^k8R))QQr|&Xq$SlCL=LY<2PHNs9{Ju0)fg7y zuB?o7VuqEZQHu4n&H(U=^&496iRAI7LoPX3tssce5NvE%bHqfgN}5^w=Jl6>8c7-V zulnj=19gm*MZ4Ob?5vMJ8a~ZZY96okvc&^wZL0UPT%iQ|(~Q$7pwfLlK_Tg2o4apEu2m4(Jy}=s zW&Uk{Zt*o7c;mq=O#Q$jW|q@W0zs+ac0MsNt|7--l-rg8C1+=q$m2ZSzgTs9A$&Pz z`UOq_)_^&!OCXdZ1bOo-y7d|Bw*l*b-X8Ul9)x66pH?*{&LJCjz)l>tggnHN3sUi*z{O8oHJ9Cgjp8t9cH4 zqUB<5cqU7P!v4sE=W1cE#01r2#%=N;SCJMc{lT2VxQOC3JPJ&OhND{s@b;~)Rjgoi zA{72>GkSu6Cj`5Sf4yB=bcLhlYLSWoP_^;t<>);~n(gO&kO>#wZS~L!&K%lymaeN; z+t8Ju&l;!=SG@b-weu5YxI{)lMsIp8G6xJO$>O5K%YrD=hU5l!=c8SlVQ30E$7cjg z?P9Vi)rW3f+g+2HS`HG5Ehy5KKTY4N_AgUDRyfU@s8x@Edh>;{q$&B6n!R_42!O-ug`O6QrW4modqr{t9LwdrVi^} z9O8mtlRNR9LwAe0bD@ovxg&7E`R&Ye5+n>%x#`#k3lpT|vhU3-1&`}SJz z`>vJD^syCtx}h!u7ukOP+Qj(jxSK6J(!Cyi6O8zzDbL#K;_<=+V_k-l_vWk@K(%lo zrH|WYQ8cD%S;uL6iWr7#+hJmZ@Xea*$RibX{$xuY^8TAqZ!C%}HPJ~koC2Zyg+84y zNVEw29;ixGy0y;i8*k2J64ModM%;f&0VQQT13kAId1KB-r6X@`Mp=c?J~wgrv= z9YI-99{f01zDB=KagB5=S~7KCAP$e=!)EE=q3L6JZ8)WzSSqhziX;Dia>KA{Svysg zG8;B2M)X8PRI8`GZ~u{s-Z$EX5)pla5BZ+c#I|_)rA>}@_pG#WT9t}M!cJa0-hgos z9j0_4op~%P(A)z-1auu^rrJAaN|!(ffLeYx8^iBq3z*&6L$_dbesFhb0k2r5Emc?1 z@ug~Rr%UbLYQiDo;5Zdlr1%yXWsj*H@3*YD2eXQH=R5C(YP9Bf& zQuh_G8O_*p^Xus@i2(`S^|5uh_qNT*8lJ)Nx^H&In!d0j!2(Ly!8Jgn3)c9;(51BB z|Hkk}<~2;z0HiEvN?mAhAxs9)PA$eF`1pR)giR+To}(7t?{c`MI`~wmI8lQ=enwHqo6=>;YBw`2^LfAKQzkjYu$x==IL0M&O!}nLN=Ur!MW5L) zwvmhjsn?FOyD4HSl}2!@?3NcoU8Eill8Y|bDAR4tksmetA2F8nxY*eXo$!x!n+hv5 zuMVT?QyrT?<#W*r*dru!Y)pmc0B+1>aa=48!O^xGSbXC7*_YkROMY>!Y&s7|>z(Yx zTTDA#d){nu4q=-tsYShu1lgXPzEvbUhu!^9VxvB9lXv2aaQ@P?lF{MM?I-sMN_On) zI(d6T-8_Mu%UYdTMPWDk%K1`&(v%vwWMnBrW_guS`BM{5WG1rwuwW`0-wd5KSbBapuh^Gs7zQy&xp zH=6OCa%?xb?VY?jtF>c1eK<*Unkx!qQ0(kZ8XaLBNg?3>|1?={gKlaH)(_4t3R3}bGod_y#bfLPS;g&YO6yIs5HH&_#j;v;0%B)vsD0ov4&7g|G7wb^c300AaB_`UkcdHY`y0Br{@c7}Yn@H+X zWKwzKmzpd^x8|9d8+Hj@;Lm9Y>NCm~KuTdZAmXe@CmCnIZOI%P8UvKQy` zQ3)kf@W}&we3$s?AgaQ+@ErAtfW?GyQ9rBt}izA)_;8Dt6>8_{}{)IB0$9$cdK6qGi@2(Yq$KewNkl0gvQTF>0&)()d%^v9m~JX)rZTt+U*JV`5{%;?-F=JWE!Dia{$#nm3H9) zB3$^~w^O$UrE&WHErB3nMcq9PY2TuErMQ<(*78Y@RNq(xe)Uq#Lwd|zGO;_`-N$3$ zKF&nn{>f%q_h8dZN(>>k`;!gQ>jHohZ~t$B692vH{8K>1k+2+|8ht=$^1uXSYj{d23@ZktNFatGL_; zt@|4DIRZVx2ijl)v}Uv!!|Bg37_*Dnh6ePidT@}+7%D??&+EY*GhDQ9UwbOm@p7^&mVZ1=l=?R_f-1~KC0?XjiL+|P9&@; z)Ro{S(MK$uQ$~hu$7qgPk!JnE=j=T$n*X3H42g6^s5RZzVOu|_?;8`Qu7EOJ0|aPr zX|Tq1T2Q<88aXk`FQqGone;SK&pZFc3g5uF*aORhJ&mHVTzf+Wb?@$nyjOs5K&!`d zvPk12zC=IGMw=nsx}>&*OdpF6hdJKNARC{ch8Fh}jd@JehpFm}pMz`Pqu-<)#0V!9 z1hOB(Y)J zlK7&cCX$+@)3=iMKzA{| z(G7Y$Ce!Dc^$3nq)t3<}Sg!C|i7#Fe>|1d zB%ms=Cx}OJ{iY)*r6b7m(K!d-Wc=P7uiR0ts-7^a3oSq>ZS>^ADg3JM{>qK1 z;wO%IUBdc+HS79Co?~%o;Ktp`yn^<;Vc%3Em*(u{Jqb_Fj^fJrqMy0*CR+iYU5??R zv|~w%!s0-7(vse?4izzr(sc7UP@8_%|M3ZH?aQcAp~#P?FYep-+$J%HF2ztEERH5* zAtt46U6P7U`&2`5#a^ln^Z4*`8rP$AG*|nw7^&QCq_Wz^>c?t`Ld*fZiR;U*k9tJw zl#`Mi^GAm}lEkOSd<#^KB=g7Jq!p`LZ$58UlM-A>L3G-v1Prs6q@q8e3soJPK|(0K zHEu{h15}`!k1`q%^*`bX`?|R!QYGP@R7A1iknJ)WH~)|j+z5TECs6iGz;Z!;b=b-< zwJ~%1{+bvywqR_5!#igoX;rN#iCE}MRm6?BA$VdX^rSL$g|BoZor{821QWr_t3PyA zukFe$B=w4n=VlJ4E-4kiPtCRoSE7celC1`1?KozqweH$Y@4y^@G7-NXfzJ`4+ zRDys%fez>vd|$j`v1n;eep`O09I8CSjMSSQRp#4=gP8C)OX(vP#Hi-! z*&h5wAanjNTZ_Ne5`TtjW&L@h@b&$_FZ&OR|83-daj(B!>Q8#J|IGo!OO|F0%*I@r za3a}BzUg|(`B?kb<*@%uVJ`6cro7RHra7n@TrR^%?QV>rvjHs?=5w$&{tPSuJ0OPu z8Mo^Y{evCoX|WR4pSb<@zqtLcY#B~Qq^nND1!3s?QKif;H-J|X$~oXHr05g&hp0i=q8&um%B&}au-3VEIJQ6%` zVKLMeS~w-Oq0*KMU$NE|Ubt9&A;NJ`!D5phPR}Jq+=8gZSSYQ=X6cc1L2vYJ!t&HH8Snl&{2O(;kmB zdikNB1@c43FV+d4$ERE1o{J=`!&9zjop{EBebX+p%5Me_;9hg2M$qCZ@l&J-RW-*9 zKrUoWDJ)5^x-O0}D*pb}I5c7})J2gNTYO~&KL#I|=w7LQ77EKy$%Y^}00+-Wp<%qbe)AXtxPDD%Qa>LEq(T_v?T zxLs0T_36&YiI9EHAxEU%2+ ztER%=)&H7r;aQ2gqR|OLh99ncysu@5>X}_fhXE+08N{ls*GF{=_n&N*>*)37ZLkD$ zN7Iq!wGyDwxNASqKj^I_XW1-T4(+fU*1mYeDSSFZG~q&}#H|v&go@8H$>Ko~5-kKp zOW@l2Q`n<`p!48Xz@bK>G9dakYU5O0+>)#`3}=1$FzX|#eYwExDa)2zbpPbxJK^fa z)4Q;3=k_Syv>5wLg3TI;d|)cF*lq$7f;3q6uBQZs@_gN97JJH#)jOq-Ki}|2Lh(m~-$?=0G&pC}zYWUU^na@jDPM^l+VSJ1(&LzAwd=Wl-`f5s5N}f$%S#^QUva6v9Km=63abLtv;Vyc2xzDX&cqOeVs@Wf zqUUo;cK6Mpmct+g@D&$npsM$E*1bb{0)5)~xu);>`NMd&1kb4 ze0Rdav=ypAZ@0G{5Xgi0l6U_nz-N;sU^q5tkov*qLbNHhOUM2ruu{ESTOB%nc*Sy{ zE*N~KfY_pxf`;`2LH%IsE-?2|3hfQ^QH0fnHk&adb zXq!YfD6+17d`7F**+9sB(DOOcM-i>Lg~7C2mkNGR;?1nJP3&QIG6y3AD#X-XEu^cl zbut99^e)EO((-2Ba3c(M%xmylW-HH?cgE3dpG@{W13+k(o!2pdu3^R8vp_X%J5UJj zdUpScCoBNDIg)B2k~lu>@Wh53%>QtH(ZuG1F||Rx*i1~6q=fkaD8~q9Xc@*?*PuUD zGxmN^bp#3$fTAH+Mb&0EpASNZqLq5)^MvG9;pvhIiyLRJ>GD2%$R7#_n{5^EZjd5C zT)6I|i+<{16OiLWwA7}Q1RS7SxTu^8{l*SMT)Z>x+;CMEQT5zJ@!_gyS;(v+5x)gU zltFn$%9LR-dugtVRs|Qt+}3CoB{z| z&ux~5EAe1JzjzMC8^pDERh{3J6RT{#t?ry)p`>cnqnhk}Wu4>-dJcr&0_0BI2ILzY1)k_M zxQS^N5YFSvb+w(l7m~FhFe@GkE^j;O1A1JMb^KG6M0?y!b$|@IdUXvVUmLBQIwYMyR^0boT3aGq_X?c)C*TP9|;bxDIWuKl*@Aju` zf2t21zdT4rrokq*Ly!TF2Si?y?$`Ig^QuAs5SD3y$T@qUbFr|B*^vp-u!bSU;F)~t z68La4bSm1w3ceS7Y|%1HgSYNW$O;f` z@hKJ;Q(a%LnYytgexXk!Vr6pB1b2QcWaLm;?ZY#ljOm?1Iu#A~(2rRe)LW6`$)hCSmd<6u8{D}~;vqE5tj5t(Y7e1IMuXdI=T}TB7f<0SH?5AsZJ}13es_( zbqH^~D&l^iqGa-RVz! zoq)-kXy0){p&jXLqpeeVdZ}1YgZB?QglCa;QD2aL1Q2{T49QG6SaZtCE8nH?@XhXp zf&e-nE!CWvw#6Cb7t^!^Wt>RyrUu4Qd|b5Ah?y}>wVKg(YG(Wn>{Q}yx5nD5qQOxC zrzra-4*?2{0H2Ox5P{_B^%T0kKS>E8__c!X=)3cKJDQV?F9+$KWz#L@V| zvbwf<9#?H3D_>uh()AQP68c2<1YI_meEVcXg_kLVH)%96WJ5vWK}m7IHafCKl{g6~ zYVt9Rf1$$E`&F$nhnAEF-c}>2c=*H)p_}Tv@6vj){=$=h5UttdmYXV1y<#NGm@%rp z8MJWZnBZhdOrCQYO`P;;TMl#htGLvJ*CjVLveCC%8L@;{)3hM|n=2j2NP?B_fvr~Yi z-=`|47G-N+JKxbeR`-2p8tU!-+kr!(cGs}RnGprLUjZ?jt7;;h!gL|8O~5cyQ`l zVFIPQ8L^*ng-^T1_UboPQJKg{2)WaUU8cfi1YrMLc*Dm(wzyR}<`>{L9l_9X^Yp2 z(^?XB-6wJB%h}DdyAKvqy=yiY-V72+dS$(`uYGLz-w+1gTPuA6E_<##s5!gF+`9L;O%uPUC74MuaAd}JnUDfGC?FM;?+Mu3U+ zX5QCa8euZvbldJ*_pwo%JA?16FIGI>#z(M+f3n~{7vYM?aLP`F5EBSXp5MLE#9{z! z6@VCv*5rEsKI(GRo_m=Qx5=3R66$%#KN^((S1P^#-F^SrN-@ykGU9Sm;9_9M4!E4u z80QDjvwlxe{5cD;1Yn6B&^t=lFz~-qJVDC1KU&M`0<@5&gn^!TpmKB*Q%F=jGx#*`dqECaa5$L7CC7(lA;y@Y-eS@QB5rXc^^D=!(eNsNdam z*PxQeNk+ap?Qm$8nOMSp^p)$~piE<q4EI84C-v4!cgPt_ zXnqBmVGZ4W_-2f-kK>(GYoa6)e$WZI)P9j{Iu0qfRv7DSO1xWk=Ut1``6%&QD`!i5 zNQa$36=E@sb!QvE&6ki&y9rmkcn-6`zd(nwbm^()JIM7Hl^@p!y!L-zzCI_4>8qAd zw|X#LuZNy4i^Y+KQ$T6Osfw=fv7_2 ztVS~&sX$X!iY_W$Fk96w_u<$PlX%hhA2uQ3@hJ1YK$Rg&nt>iIB;)(M?WG>c$A%YP z;NB=iOa5k+S|I(T+W$b51RJG7N?JqZn#vdEkxaZ%#q0XonGLv#9~9;lusmo@Sd~eV zX#>8ff|r1$!rr9MgsYU+pg>6{VHG^B*qdT!m;}L>;mcBuBOBd^0S4V;ofJhrVLh4G zY~63|uc2l_+*zj(f~ae2F%5DPWVy+)l1SJOvejLRP#6{c;)3FCPv1i;H>w41nw^DD zbf&>ML9fqM&P=h)(wXfuGPTBm^KIZ5IrOCNj^K4$vkk|-fv3Eq zYT-Xl1K9c#4Bc;%Md||3-RN&s>9eP0kW~mhW(s-ERB-gbNo>1>_Ilz`b77B9%HWn& znpwI;{}zTHrG=wK5%`EwtEh`8IV=z6e&deO$RekQLfn^<_uo=^U&s;$AO^j6F3+tN zh~?}Mth=lN09+s5r1}_+^TIEwiIc;Yp_Zgu*)LVA zQrxt@H2j2U>aNnB_vVKTKqIp0aMAei>Jexg9vlP9#3kHw*c@M4t)D?BpNJ8wCey4o zO4qPW;S3Yl5A2}@;XS>z0TYiV&>S~~W6un$*QpJV`^B=%c@<=9nMQ=eRJ z&Px`Dx(g|LR3DW1v`%_u{n#OhIVn-qP2PH);u}9#RaO&BHk~u9!cMMaEB7($61A8&)zu(*r@nr3o@g?(XxX>M0Ndi3^6Cb(3xM<{w{yDeTE8VG3I#9m zdE6y+XEPw{9_|B2RQ&M&lq-8P%)Wj05WpxwfqmQnvw=K2lq6*^9w~biyx^VS87amxL z1OrmMmH%B|;?r?Me!WoK1t(TiGburGBH1p+% z3Hv#Y6na(*fuo0Y-bdSft@Q;U-c}*6W;>`SF#=JT+<1h%++KIs{)?J%@T$46DDB)2 z>?wv+CC#I7z8g8kFK+1C+;#bkm9Kbw&9?t2DcE#F z&q~VGG%jMRo~4KQO8Q!-(AS`9Ku7rGHG0rW#u2~i!+d{?ouEZFRa%CHpVKXtOqm$m zb142;X}4ShDE)!oWk<(1owf)HomxjEh%Y?l3`@i-p9+yj3(`FFYbcU_{gvKCxVv34 zd2sOB$FL_6CARk6_KzTDC>>@p>kP`0cn~`o!&$fTZV;AdAV$bSI^h!w&&EFds3WFX z_2SyGlMk1OFZCUKBB`1*!^s$K{SuOo^MuTWI$VElP`f#yqC92s`$c>*`CRft?;G#; z$#S<|&jCe9+X$dN0U}8Dkl(83-ID&-ez^o1Fyr5;S;|YEegTuL*uko ztF@wS_JWhQR>A7sY_lrmqjxEBc5Vwx+*`O~EyOBqdBMuRJ5 zqEl?UaxSE~bdVg6E$X;vf7v}60scNWAsQ$&fKvO(cDS=6U8ui!wjy3L$*Ml?^WmH{ zM`R@)+TryvQU2*eu37we#|~7-SIl{6IXn z<)9&fULsucgO!Z=MpC$E~wVg%Z*F>U~u-KBikO*;L9=9pKMBq z&!jbPpN4CNV41Ih_|@I>5f=+2CFV%O9+P*+_fal{Ojc3qBT$_0zGge`hLGn%Do*3H z9r6C>_%R)2)h9j|%4dJ%D$yC8ir76`E~BcDyQn;nTkPRn4jCREwa^riJ8>>86MDbI z>s8C)>L9K0tMMtOgG|f}6CHm%e@*5LtB-J=ragf^0y;r+ZMCHA>5~E>kq4Tb~yp13)?vBzsnLq0LyA zt8|G-XxF&EXWzDHoNv0Fq?AfFEbXPO0?>My`hW)Y&27$8k1c4;ZG|$^UEmCj0u6*% zzT=Z1owy&8v06{u&bS_JI{y9hGq!59Lp@tvYxs0xqiH($Jvjo4hob zVtoEs>PLkAKncQ=b{>z9`zHU5z2>m%g;BhZ%{Q%MvC1p#^tLW07VQ+g_JGCpU6Nsz z?c+=_4e(nLx;Htl*k4q9=ZItmH!{)gB76B9bL3&f7U>8;mt757C1aPtxRq7%MEbZy z9n6L>Ag(j(lyaLBDIZ<&=KQs;gMCr1E@bp)5nvIo8Bl_YQ!fl~6vn7EMRit>322#r zj<4Y8{djHPb^fk{;#S&8$oeTnB5`MhYit8;0|3iMrhOoh>gqmyjbLtEPmYr^Bm#8? zATzVNqF^8gOjo~j&hsbxmWz&-Hn_B}7u_*FxqQlfl^R4j7(WRvUsUJ#5O$+)3$D^w z-Boa~s#o`5j@PSYD<_V!9qQt?>AbyH2+Q&O`BSqC6U;Q!9=Jz_!SNUmcBjfjQ$?6? zp-<_KVq7?!&-f%6(-wHY?M%rm+r$f{Etw#7j)7WVP?W3Z$Cp)Q%}>W|GdzTfHkv}t zr^8HM-IV`Q#V#3JUeeUa5gD=lV5NjbmipBPnqzqdSH6m9A%zm^Jc)NfAs|6)ptw(= z)!JtInHb%Elx#P2QUSJcd1#!kgX;z=hki@mWkFl*=<%b}Y#2>>f`<_9}^1)(NqJ2V0LEs$sJUI8M1zS?v zF%jgFu3Z&9D_8oq2BXFx#xlNoUxV783wuLPL#`!#_8u2Xy_KrVk*oh64Oe~aYcjk+ zK0nvBu@5me`;6p&TF?zYQ#2DGH9{dU{cPK_DoaMY#<0@FzCM;Pk!TWtwd+D%r!2LM zU-^bQyKDAL-iM(v8F_GUd6TTsRHlhI__p+urS6>G^`jk6sx-~xln%JJXo964!AO;| zJ_ao*C8_UyoK?p5b_tSGT7ng}dUZXZE*tHLS-nRb8E(^^!XnkN3B+TCVMB}6#7{rj z4lNNtYb()G=^1vgiFr*zl45Mp_`EWh8%h8dJc%IAe`So6n7{9s`Buk_^(!81Z z+kNxfNi!)ktk!&(646wL$n{mILC?a$=|QJO4heT~a{6t$^NcIh3J%{zH;`<+zUkQq zY}PL^p?T?fF-xEdvzG)RA38)`d@ag-$uc1~29`}jXbvDbECr@ zT19s?zwI^63!_@okQ~vS^;Fkl`M$b}FrzApaE^2a_sCAr!3fIAF$xd&op-J0)8b?U z`XUxxS-kTCVb33YR&q40>E(*;wTgD%)tm8rednc&rJcKw_1@FEvCsCfJr1|Xb6p%y z(-bVwWX^?Rn+*h@#Xrd4M!y+Sl(Z;CVKBKwW6Jz-4^Hq#irpwP@#cZ&(QQ+m7LPTx zg^j{u&wMqm)ugPpsLRs!O$Jys`rXpZT}bD%K+160!eR~<9GFgVwn1mzL^xJHE|k9> z@XbEC^4(E6GH9)vby%4rH*Qe*b?opWc({k8TLMvk=UQ9NpMbqfa&mtxeqP|#G4we> zb;~RyU6!F*9sNkaAqVE=^d`^JndS4J4<%Tv6>gvk!va=k zXi|Y@3;tW~D^dagPZhkrvIKV{i@k>-0XRj9N%^o|k#%Hylf|@CI{f*uNWrT*LqS2!dso16 z_TdV6-I2U4CaR9Fsad}07np9>1>nPJ{&&C2|DTTkC%@af^m**YBgzcMz(d*bzf!0d z;+Y|i82aX4WmTSn%T2YH>1)ee!OReVQUY+87XHj(Do9+uPvc6SBAuBh9b=p-_Q5tO zX|-$gke}4nR&iB?h}?|3l`j_aZlCx^gZ(HM2#=35;v_8n1W{d>=AfpgaJ7h5vU)dS z?43PF)f*Wxr?u>>8zyW*_k2pqTT_fv=7Mnn%2J%V6geBKiVNCAhpXmeg-?4rKJ$P0 zjTNFwn-S3>S;`XdTo3V`aZ||+>w5dp=duSjRc*d`QPgGq^J5n8NhL# zSjZe)?&eJ6*6JDkc%L%!`i_VLTUh}lrS?!0s8l*@N&7brLLa6#dktO=D=%?Tsi~DFvZ!hWbvn zX*Tg8H>SoRHQ?lV4zV+)V+?O#Bxv3wzEQ!Qd#nI`{E& z%<9@b6Br)aYU6f_;|l`m2UDx@Zp{$vczc0#ZiE=j-9d0rF&!l5)tjmL$t7Zg1f+nd zS1Ea)F{c=329WxCHxv6BN@=k&S>?4KlS%YjkJk>T=sXX|3X{Istv?2nn4DV{nF4`m z!7T!Vsl`rSX2aMRNrr~2eab_So({Q|4%PXM{Hh(g|C#j7^(0AIXv^s4YB{OCC61oS zni);D9gwQ_LuH^_?u?tMS42*3Kfge+Uk#a}V8fnJ6e3iUEH5v95N!9_61rDgjFd}2w`+y+NvH=RV`OH zV8WLnH&K6jG5soT@JWhy|CUm<_7D_#D30rXMTotd-0E$^4`u_P!zi`)G=m9DSVc7A z?%H^Cp~div6&KZWu4~XM1cP?C;Ip>$CS>PrwRsOc?Yzkn)?8j2UEa6?b9n^PFch!^ zNyFb=27-s((Fv9q$~bId1WNrz^S7WlU<1x=OteN)KQ=fbVb_ms^+)NnuS{Me)WUB5BpFulBdeQ45!`w z`;|U;PokW<>dYYCW>hvlvfbC5Kkr5u>8cu0PNpb7F8X?$BR{yZl3!Dl9sH-0J zNt|l*Aek>THVeAMeRZq}J@W7Yw{>KRX2)fnXV1*%LPVdc%Q90Lkg5Dt9OR^r$0wFl zl2w1nD$?M<;&4ve!qf6$D@!YD4Ie4~VV_LgH9OZZrnr7l$Ub#+DEdh~H#AT(bm~Db z<5WHg4TUC^p>#H~h0mG8i{Xz`hR!Q~JaQGED62ozf3dzh}QthYKvG zXC{0MATmg6;9{8}SHFa9vXUb#)7kcX*r&j&gf)0hU1x`_c+hxYitt2U`hVDmm$} zVSBB^dHf+%q>XLffJ^B!CIZNp9b6@F50B6kry9S_;{~L`UWoUvJgQFF<4PGWs)RWi ziG!-mVjZ5|QjxTKs`+tpHHf85s}uDjhNm{B^AIbnOkx_T)i(XRb!DScGsSZl57Ad6 zd|GjBW2!k(yoMr2jvx;+$Dd$VwwK^Yy#I(ngR3c^7+Gkbhh(t0-w~J@I3cM`x)td+ zTNT`(f&{Ljw09bQ_N(VzxsI-~=o_E*iq))`##-B}8=V%2w`FF%TdTb)1o@PHxinqS zFdwG4(>EJ%^lOe&+~QJujr|jZv?;r*LqO^Ei-#Mg123Qy0OVIFC#O;b3xtvzgv;9) zBD6!dr2J;rySDU;`=!_Q#NXUXfM(bmoFhz~{PE+cAN~{gw0?%OpJNz9<_F@CxldN( z(gYl$+oy1e@HK0=Xj}3^nQ>{9wMx$NOqTt#Lne)H<8KE_`Li%{K&3}IK8FA)sdulj za!zChSVE#LJ(ckhNIX|g;A)5ViODC`=d@NTRz1&En^Sc$Mo!$zy`JMZkrwv5mv-0le3!XN73pJTi*Lcrr zkzKSsMT^=;!dSPOFaeV2iS6Txfh4Yft@w$V> zYci8EP5w)%0JLB+MLXCPo7*epn%a0OI*>91?RN7JY$2LP@+ayQBweJ)7C8k%k91eu5K(O{D!^PWrJJ((J2FX5M@;9_y7 z9UeBUCYo|(FI2S(J_$O7=BIROP&EdM2lv%; zOZv6Emy4Gw4`&QNejSxj()y!yIew!1fWCU(l-i+2bvcT)lIa6;TFQprOaDzis^SL; z-*O3^D6t@IYZb04q2}=SA#YO?2kU3@z6Pp!4%%lMSuO1@ZS?iw5$~KbBVGYTo@f6X z|1JSt=~@RffDgb{R>YlxNtrD#$QF>bs2DH)zy0MI7*iB;Q}e=vrg_Ym!4Qto{S3r; z+7o{K8&IbZE16xN6|^xLm`ynM;MhC)C>}>ol!vv_@^Fnh8%ot0-V)|?b%CCP)dg0D z5$L^}mwvMStEP3gT#ZjKg4trtj(} zD#x1DrvrW<&V_084|(K>84uo36C9)-(oJ5%?rZ?d+r6B?T&PXmA~JRwf3iJ41hf%< z?`fQJ3jC)NfkudsjX6%%eh}+?LC=l9fBT1l|F+BDKJoW>_}74AN+4AAA3M6HeY914 z-PEXwQFJvrYcLs5Oy=};?2RY~Wc#-2QdA*Mfjx|qHuVQldkHBAo&mWYrzIS=EdWfd zrr{<6)El+>R4u4*#n0m^R!-=0IOvH}hKaZQ?Y9^AiN;%Tv4-kTr@EpZ5m_0DXof=- z^SA8gGhNZwGyxe6&JFPWi`Y(o&SCl_&>D~8fL;*^dKt}}=R z$3|a6(291^{n7wTKWd;_R$IB_k{VFg#wPC;Y6g0tz|11`%S$P09ovLU8#%hS3ek?1 z(g)Qtd1YRJox3BvhtM(ezte@7*a$D>2R+o=Tt zuxaGB4n{Bt<;?QHtL+k@l+YUsKiN*UgIRL6-AZJB3WuK$z$2eQ08g`Y>A_Q7LSA85 zvj#VE|K{p!08SIdKFK*8JPD8Dk{1^HZmFNg+}lqHUgGsQg~4?jn4mG0=DAq+A?WWzEuc9a(H>@A}+tEP0>y2W!mY&aTqgd-QFkp-23UAI|LGGr%3p z2fz^-OyCHD#}wc@qknktmwLdb;BRS-;O%1oR%A*+kMk0jWLK280f5^7gz zrn(1>eBR!+q8qNFq>roy7rC&9*SMzM@sQEf6k38^Hc6i|Tv6|g^gQWN<+XFVhu$n5 z;>&6CbIZ@sH@32z8<#hm$;MRXwb)+}mi@`rW&=o-@#mr&VJJtysv=2$+%GZ!a121? z5o!ozyMXckARpTfj52Wj0R6SelS04}gbh9zez%B%>Ky?BlCkMVBSzo_OK?wi0vcBG+`cVjdI zEk;)m=Vc5|OtD6RBsgu%ce^QOcx1BW+2G#B=D-`Tb?aGTgVAr{ zt>!TWCFYu4*7=*x{l*XHq+)kfGy@+r(_K-{(9m4Wgy z2K-a3iC_1f1+JWHNe54slL6Bf0tYF- z_9T`dV#~Hml6PWyRru?656L^9GAh8kK_m5NL zeb>Qa9{{cdL}$EapkY0^j#9)GBY>W{{CoJu&;UmgHV3aIZ~VheZe@sb3hBcfnW*3W z=pz~)6D{~BPb-l6$p*ONx^5^XQJeX@!OaHdM!(L=$JPCAEjaH^>;-ZR<@qC1VVOD5 z<{yJ94h)!U2zV*zKiIX@nvT;wlHOJ>%qWij6qF3AR=47-`ZWHfC+5`!~n<_&q{ zll2y~6E^&ljq<*au8u`AQ88eqeFG^e@%M>h^LzxOrj}WuE~_c`-lvuA3@0rS@EJ-< z0gx0creFfW6S7mRhF>r5gO5cXENkGUqt=~;Un8u4S7Ln#4VQ_^V>yBtIlqRaU*Ir^ zUl1FGUQcrTgeOM${z@t4Sd}LNnK+RL3~!&Uu}S%{+f48vzSGM%aw9~Hd@}V zS+Rb3w~n~Aeg&lw0&)1`$t)unOG(sH+WYLO=4(^n_2U5Jbjqa7nN;c6A_0wPo#M!1 zH6#M}>D1r8&r}7+L`CP%y>ag9i6YFf92c0pJ2n@{rIZXmpMMz;8NiSkrS;W-){=p{ z{oQ#6(Z(20@1lSU!2AG=EA@^0_&!FAx`9kr!lRfV_qlgd3)b6VTYKKa?%U{!y~WF# zD_V};Jz{wE%)u#y5X*&bHfv47A8M&^;S{iAP5kosEZ{1Wv=Ja=@nTs5!`ixy ezFov`)pDOvGpzZ{oOL2SGlE>BIwJCO=>Gt&FUQUR literal 33167 zcmeFa2UL^I{w^A%iGcJjp!AN?o1k<=M0zLElu$)_jiMB(0s;aG(rXAvhtPYM8VS8i z4?RE#C*MA2pL^ElzGr{?-uvI{u6+#5de;gs^Ugf;%&*OF=DqrHwE!T0q^h9`z`?-* zJj4C~uBHJ>0NiWW{`kdy@UXx5*YWZ3@bGUC5)xb|xj{lge1n*n^d==a=}n58#Kh#Z zwos_PyM36_UjrR9xfi?AN|6)=8Y}56nOY|?h#Nv)FHHTqhb?z zbDdf-F0;Iqh+R|{Nn`zL5=r(t;GXY-_DyqhA9sy(Uyo@JN0KIe%9p4|nv_Ny33D~gi)JWIOh_Qz6#yT3 z1=tr0kh}uKdoO}7@J+7(KNF#8O~L_&KwO{6!22GKq!06a^Fcz&gIro2#b_Zq`Uixg z)$f>9M_=4`_}<>0p8ZWe?g|j%x+r{3a|A@wwO;``My>!i=G%Zr{6Cvf!eLi{_fX;e z=PAxN!8$;9luKB0Y2p9<@y8=)t%OpW^5&cDuaK^x<&v6X4H8sIXoV8m6 zX|X9WVb`0Qy8JElp2FT5S4WxU6*{sD$d17B44E>mML&~!#giGh<}zj_<**_lXwEBu zVpHT5AQ%Nkr7HXhDDkkOfCo)ifOkPx0Hey7xwgrvogK|hihEPCj|(OCz62S!+du`# zq;B=vEn7S~)TWr0$S9bdnZJza*Qrr8lm4i-CfDFye&_Di>nngL7Y>7zIHn1>#`3%y z%?~C5e}`sW0T!BAFFVru7M9Gw<5Nue$<22de)lB7l4D_`dv zQ8l$VgWGD5$(J|Ig)zj7SAZq^KM&s|4Dp%v6{6LXt^IzBb7Zfsf%jd~sFxzX-GFn( z-rD`V6p0LwJE&0c{VVn)X^xus2P2h*th4mDlU`=uC+fw+Rjn!WegDq}O)H_B(et?D z+$+t4$v{vX#u8YWh3iJ|)CIKHRULg4IwXC|{RwPeInKwg-#kW~mYou)+S8?;-Fj1% zOy5Plbq4ugOY2+^s=#~wP;Z^@ zOdgHX2|}R4Ea0}^D!+xoHDI*&EG?^!gzvu3=`l{E`JRwyt>JQ)fJp8>7@BcL5VH5r zA1aJto9*UGz^f09T|E6Xr{XQ2lc)Z!OonS*-YGz0shwM@yU_`R;TRW&83=ubr+N?( zj0)O@bim{2TSYrC&m~-}@6wc42DhZ+7dV;yd5nIUV#RO_&zM-nE1}X!T$d4!hQubl zJ8fPoR5LH{x<@e<2QKZ%cbz}v%A|g2rt8ti9`a@=XDm1IA;urJ1qg_Y~4xYsu#_LSj z80gxv6Usj9njD^$`aZU|Fd?v1U;PUCNRro%FDOJEkKE^WRPbH<@#oxss_&QS7q|4XT1=t;(i^4EaHk6u`g36y4cp@f9lqv5wH5(VQvJVZMT#Vu)Zcd)pgSQgq9 z`br(jcfX9^9&-DVvn+K#kd*G(^M9%y{_?z@6pr>Jj^=KyKR(V$H<*BgbRc^xCE&(W z9~Y1>A8e`op!vl}{gWI9ffwnrL} zTMUg9-)mX0O&E6Rdyha&?8A zjGa%jOceYl(^2eCPj^U)UYdeA`%r1j+JJ2CYN?teJ`T215dRjVb}=M!_hn8(o&OD? zmi1pu(0$ifNM^LZj zt%;oLaVOsG;4|(Zti4}m0L8pFJtSicz=`c*Vaw2KDg&*}vvqUGgh$aMKpeo29WTMY z!(6#Y*Bxo3|3hSGSe^rz22GZ?X=r%Y%)(?A6*I*9HId!GXO{7SLE;;H^`6^r%~+9> z3U~@XzXd2@DU#KF1&En+E#k`MF^m;d@c^0ofGuSVCO$Jd-FQ(gu{w7JkUH~v+hN_H zarDWPl=Nu27CeoK~w`DHJ!$#k@{&wORaXR6s-@ia*FK(s7UF8c&MJZbd(s z1;OK{w8V=|2l+E!Ne{Rbe<0=+9=`&x_;!(D{RdJTq!K2Q8>^Ynd;(z9&ad71emp~) z#*Dw-?itgyIjqrWAwTI_p?)U`_bydC+uaZ7%_plWh79X~b?Jfr1&tIJq1Ew(3m47K zCe2#sYNcC|tVw|II?hLS#&o^Ct+#Aa{!%CHG0o80o9W9fr;MK^Uu`m`4j0VyIz0Y9 z?dzs?i}F1UrN~OumnqKo{eA#`%}z$4%uZfw9J+y%2UutBv*1Q2NGLUl1WonE%FZLah85 zqOzDoMD4`r-Zf+g8-;qvH%U4@?g2l#6B;VCbfim8`Rtjar#s$=a6$*sEsSqez|8Ke zbvc`t3(M*5zgDgA%k%RCT`$3=U?b}d8%PaOJK;q5XmGK;I_ zg`ay{|007+DOYvsl4#cb^=YlrQBy_LWu{vT+$o34gwox(Q1-tvjH8%l;e&_km!4Mu z2@eHi@TSBo5G3D`cAxaVkl=z@1MMdPN@{+Fj^g+?cbRItR8=2OrW0m7jOqAk8gFWD zh7kN1?eDyvGg8Q@Vc>)8(|qK?tE&8NRSoUohinn+WS_qoOBT(2xb&4Uy4)FoYG zmU$-bwX|Lf{rWBIFGW7jFN`Mz)@sL}VTLN|%xa%$K7LY4{aS7oH68E%4lhO&F6W9C zk8LG87s$Y}7gPz0?hVXGDyt!Q5c&d&3F#{?$lg~}TwEH`XVbsp2gdQa&Gu5^dt_}y-T9<); z8MS}F8U^Uq9;QULVP<;;+1Te04>WZE+NJC`L%`#NZJqqGo(f$hM$=I7NLQ?!-%--3 zEz8h+Q@WC>QlFq<)EGI)QATz4_BO4P-4h(Z%8jnShUYwZbIH_s73JhIA8;>py}epS zaoFkP+!>x>!@T;b_PtFzPlUdJTnp}9(nLAlO``ix!t$;FHX9Nr{(|fWZhkw2T{0gDkuR!mzPuN5G{!$Y`B*&)weSc}*0e;{47o^i~l>W+W{EgCY zl>YZx{{L`>4DEBpT>*#)!#uTw2QL(+A$HY=Ub|C`H+H*J-99g~!S?=&_44)R@V30b z72xaK9|2?E<}<#^0LdJ1o5JF(u;yXV$O)4%1UB2?z5Sq}JRxdXsuwEzm$ZgG?q60g z*jpWC%}3WJ1tuqDdj+z*S^O36v3I}yXgJ)X(LEsYVCb&8)jIeW^L*r2YYE^@>7zS` zIB46_40eNpt>wzl_E{o?DqZrs1vFjcxOY)vWS+siTb=u9Ao#=4~8Wlug^i4X3=T=qVD zyJUS*f;V|XWy6IiV&;xM!g(#lY=Ge`CG%cUT))gK(M|^WyFTS;i!4;4I1i-Cf;@99 zsNcpFzcGDM0MBrPK16lN`1L1__*WW5=fa)Z)IJ^#+1eBGL*kk}!g~sCRK5xAyh+?= zA9vczrOa{Tud_$wR#-L*8Z>$|o*W#oY@7#~p8LA4ug@b;bec$uu+C_KS*0RJKlx7) zBb%m_^&HM_j!eBI^Y*hOn};-n{V(+;*}Vw7Vv#_#aWs3jWphL++y2e+tWCzjyablS zCK^rE+L>=N49PCX)vt1O>Uy5RXgkJ&qW&Q25Lht7zD^r!5kMe?^>%g9QcC8Ul zw<2>;YEnwp%#4NU17#40{qs~4$)PvlAG*5a^+{-)&YIgBqWM^imL_|^H z^O5RLL~dP%yzAJnd`5BjN;V9Yz2qGZv+~ZLaga3Zzf9i|tfZBoJ$b3de)oAoZ>X5! zw&L!q+%$(^bzCZ~eX|aubg>CJlR&PtqOH`F=k;m&BinoIUe-ibB;{fb^E|8i6g|5R zcYGSbiLWHYCSP@eD5V@ce1>uRagS#XuZRWuMx~L+>^aTw4I}f83`F7bl~2M8s4T z%>+XA#)VimHy5&?Dg5Qnop=DY2`vB}#5B0w?3h%%IT_8`vE{Vwbj14B-BIg>t8bBG z{pWgpQFB#}i(3-=?E(;b1ur$wlEtNB_M7O54S5-)ZcF|)Y*?pjjjrt5*Dj)w4d|d z=WI#r<^4FvK>o@rxm(n6{+bvme}^Sw`P(JhM9!o_H!(U%IyzIg_~HJitv}4>waATn z^+hyU2XZ7@4~?db9x@$%FpB>!w&DK(2ZO4$IDgDnuTTnD^LF__w zELxx%*|`QOwl{3Hl8n)eZgwD^p*Ll%Xp9T=6A zipo>*1y1D--LC;u9TS&DNoF%MZ$uhOT?N zOw$xUu!>#dSS7zzaSPoo#lo@AmA$m%uxv2#f{w0YhoqlTIie`jK!+S(#Q;aK;EyDY z8(%!mFn&S!An5TJu;V!68p^S)ULjNtSH5a79IfZR%xvgr@;qsGiMA|Q+9u5D8ZR}& z`wvYUxTV)fzi1pZGyK$81k*d#i@68p-Iu}mi>G1yuCLgVqL#%G0bHoXK;$ID(M6i! zgvSd(F^cXJkA|_`C-X;X(rdx475(pSm>9+V?2BshFEW4VT{t`JluR+*%P$A=yqo$o zCmy$lMyXn+dZKq7H&;491NsLsCeJr{&_Y*$$t%DYcy0qr=JFK)7sc4#8ib$Gj!3GK zXf6SD>5vX3-g-K#FrELdq4%WwAe^X@FO!7AI8mTQf4`=UnY%qzINU`ngKDtEL7t&G z_Q|B+i?N_V%AVCRPguHR%2N#{Aq#6MgyMl)U++GXMOvFgIzy79MrDJDR$;PsjelA| zk1anyaK$LUNc0!SdhkW_%ux`hwd`r*{{{5Z~C&U zM9^nrXMfMn6hs))ES*$8PyfxOiv^)H?x>vr@hnsq+<07^pP+Vhl(yod#;ej6#Egck zx7bt40u$hX+acFZiglCSBAD7CodoXe8O^v;QpIHb_*IAzJ)c2h;u}LlUH#%~^pw|c zFIDXY@i@Yh=?)7=kIr06Vlt#hD;;HTwU`NfeMpHfcxsYN()TD{f2kPD@oo$unmMGj zS$GiKifw$|oDXrECF}PfZdA6qmg;?%Ese1%3Ws`VSdzbNp;bl7`dsuY2<;e?udY>$ zR|VT8Vi4*2E&#s(&9qNYSL%>DkyiyOGBG0(sQk1`OA{|!eZ8S{`6g?h?m*uSY8)j3 z4qH$Lvz%4VmXQq1NXDY8*xjMp-E!XEOHxmRJ~~@A)z~=6!^_D1P@QbZ>{^AGLImR# zAk55^Rx9r^!`L(FU}#yrqPEIgx6|ZVJXefJ!#qgEA~T%+mcA@v)+u>V7Q?T7D6HLm zglS%2Y`Qrn5TJvEcX=fs9$jX4_V^|M=Yu`TIVSp7-!d#oz9s%PP}ZM*44&# zn7W%P5yjcez}dhY%)4(vf%A8lQ`ccSi_Y4uelzD!%AEwEnb)@q`5r};W{!+1b6S{~ z7>mSTzy0>XKoNfpvbhyZ=hLrLGvN`*q*?5r!WA(|Fd@iR-{A96>^*)(A6Lq2yCpGe z4S+42vQHdbsUYii=+lF=WGpw=DL;s@Vg3+_00DTTN2lt7#YfihYo3=eS5 z13Jja)pJZ5nnfL0Y@JAGF%sw>GRnTs_Ea2tEy{ye#_Ut-ZH%1D{)*VBkt`E<>hQF& zGbfTyoio}lp4O`JGwHt6dZ~!i?F8rUwWvOxtT*`7hM~FG4yu)pLWKp#5SwyGxL{`3A}SQ0r&u`}71HFJu%SYDu) z>dcBx^}t%ocu%g3KgHSz zmv1P7i9gtzB;9JVn_tBv{VvLVNf@A%+Y8;8pIX)n$*(p{m3&n+Z7fqv#l(N@hLj;5 z`GeejwRRwtkI9>ByFg~W#p+wq_QAx{CO>cCl5!(W!i_;42BGx zkFfeSpJ_TnY*Gpv$G5%XbKT4r*U#?tZMvuG0-_;mAACb-GE05HPmvQV67`AAQ~9X~ z;tq?=sR;Wd<1{|?Rpuu$Q46nqisku`!)Uge>gp>1qW(GbBvQ%+J1m@?;J-NoothjDP#Dn<}4Pz8O{L^)Q9VKdoE?TYkwb7M*18Mid^ z2W3?$*F@*kPzWbke@rARd$B5*TDPqI+AkI@WG|-S@*-y1ppMeQfymZYlZZ1OJX<}9 z=eWt$jEX~?0iMRD5t%>4- z$o}WafMLWVxB0aFwT(-s%H5;<-2%>O?I*wkOm$5)5Hsrc9J0LQs52Sx(7)JzGF`lK zywc2#I^^2)i8SLy7Q5K!+7@SdJ=!XW3SVi z=W`u_dyh>d`9y^-Ghg>`7-)wo;H{t%ZF0?3iZ0`zo*GAo*QR~HQP4mUMDM+0B@9{F zRJzvqBuQ3m<5wN?u&D&Sy6A)}z-u}q?aBEw@hbpuOm9SNp2>$weV?G-BhaSpTqwT~ zWsjJhua5L|`#Kopx|nv)bkJ3_=#w5Ey#X$?r${38z%|;EV=Ta6|Jv5mF|3hW$>!*2 zoCj0(SxonMLunK$iIe@VzPbmQX=H!>HJ)Wcn*!d^Fq6-4hpAAq|I*Z$@>uGDzb*1x zQ_-Pd$4eOT^O_gCXPNIsu3f7(idbsm@)1%9YZ4z9qu;~UodoY6fLssNe;#e#DPWb> zpGZvYF*9vqY`YY866AF;zc{^OTCa-^`iYSdZahnmLta=SBYc31JCo3*tw5Ibw$#D3 zLiaquFXJ`Ie5P~iN!QBhz3;s%wbUp6oVrD~+!QO>cG2(5wcMV1zjIqA`J^vb7jL-Z!fZHi?ta zNRDPuOG2ZgBMM7=?W!_Xpy$BqcVi=Sva>Ik?=AMZR1FK2BKhTh!O6M~}psH6DbK}EH6+2fi-tFpR`2PGr zLqqQ=*3bxZUSN&rUYu{Ycr>k*iqLX7G>B0Gxw~xR@#DSWBDpKcQ4*6aC&GI>D&iCv z24;d0F3wW}9SFOKovr*!TDh`c1bD{sEe)8q#U{SXCL_Xuks^Ues)ilaMfn>|jFFlR=m4JxF0DREt8y|E}I{rtYw7$R-qxI6@i>0u4r9szb zd=wR^)e0)lg)&If(|oy#-Q4ldhjE`=PwD)~^YsyV$JdVw^@q*vV)H~lx{-T2`|^e2 zZ7D9z6&}TouZq`Z_(~Eq?%c-girLazYCih>`li=05P*^&UQEweiqxid^rowU>lv%$ z4}NW@wqD|Rvr2jwW=%y$Top|D=yTCzYM*Iw19Y(J3XmJ5`gurC&#Ym(M)L}=p^$Q{ z*6kRr)VgIS(QvYiJRk0q@Ma7RDKeiI(`{$4)AVQZg$VATL!J&;MS!}mEA3cx55OMo z_qDArslBUWR_n05HR*9FSVb56bS%NNp*BjxMOSJKGdkBv?A+m&q{7+TwWOVdR=G?M z&|6@oHU}y4i1R2^g~mC%kxn#!I;fM^PP@@J>|(1^r`+&L8t+YN?}YqyRJUq(-H;NFRA0gI+7Y)55Hj@)6)ovNBuR6-d6y^ z-FpGRecTQ(ImfrOj{3pL2KN^fp-jc6hc76S1gmltWC6wt_oRjh@IB`70~#M~by_lO zOit_X6TIj8lGpGdb5|KTP`2CCnrLMoR;-S*Flo@;1|&ypO`bnqSF1^1`!Q!Ws|7XY z{5V3uV=9|1`C&!m+oJjBViH@%3Rk3d=a{2RdKzcqmyfCTA*H3zDl<(-Wh$Yck7j!L z+A!AW-m0NhywV3;l~KNp6O&te*tHgmQ<~ZF`eKFpL2?wUSq1Xs?LI-bP9ntRn3=}O zIm`Y>@+Y8a-_wPF91dO9G~Cs0U3@=btb8~7fl_M=qD=;-v)=4n4746L7}J%;6S3@r z4?A%@ZEn1iK9v}=s@M^n@98Vqp+JPS9r2fk$x%+Tfeja27)REs>nFZVW{I;k+zefA z3CECG)|NTOae1087T$~w8AwxMzIpp|gp;3~uHwN!MWxi|S$_P^1{Zlt{jwI9%DTD@ z`API{^T}RQXL0AjGz8-Zj%!{9NBbpqMSey3tHXu3kuBc^oIC|@^O}LU9^@(Z^uN8$ z^Yt;#GirP%0RISvE5Hs3kAT7yUeMNMLxU46D;gr^AIpqxp5M<(NQ`!2*!lV+N*_|# zYO9!iv-L)E$&oa3kuF1v~WL;pQL@xT;_(h(YX`}DX zxR0k@VGtPWt3r-+^Z2&(mob@cCs=>}lvV!qol586xIl(Y7l< zMFlEynX7T3*K>93D3D1X(_ahEshmw=1he5AD;*rj-%Uf|OiL*`Mk8H=x6vl9OJ?}B zsrQ4rMzs_>?35)bT1rkS?#hK7w8p7RU5@b*zT|(DJ(}Yuc%P~fW7M3l_5S7B6SBr8 zS4}X`cNmpm%yN65wz%lJ;SZoizxzsIPpp3uLO!L_&n0J$4td%N+}3`HPDeh5I z4|lc;z7(2@J7oTRd-{xto!h@IS*TB9fro5Ge9|I77{2XcMyk6F^71-u;|gYiJRjoE ziod1CZdRFPp!8vS>e`NMMNR6bPSqQ{AnX<#&PzvBbbo5V@F3Rsga7pN%3X5B_W>%Q z3|^jhB`hBIY7n2{b{Xc`p;$XmsRglpm06%e z+wKpm5nEyoZzdK4$~y;2XVJCKUh#cd-92k=+&jZ4Z_s#82se26*#hY>w`NfZM%6X% zri~qf=tsAc_Bz}(eeG+K;-){wEIzB&FcrT?<6P9H7X1Xz=|Nv?H}7&RLbM&j7pW_m z3Va7C+4aQ*F@ylHDU78(KdX>sySCCQ@E3=9&^; z)K$*vP98`th;rPCZ09fCIk?S3WK~OfZ_s3{iQFe&w?3VwvZ8|A6fxKDR_Ksa?X^ME z;mE;|3evql6h+UqGb72$+lGR0ju~u~Hbm!Xz8si*R4# zcmrS>8e#U8Pg{W47)Jt=`Vg`Ol{}i#}eD? znOyYN{qs>*fX62ptO5GtFi#}7?ZjJw61w|*Y=?177dd!5c-S}-d|<-^N^~nR=HOU+zo+ok2F;o1cUD^ zGrh)<>$gO)=Yg8=VH4YEO;7g{(7EcDk~z>RqiMSfTf@B&^-{cTOBE4l-$>pAYcl#$ zJ!6#SlJm7T#KAeDkDjYGX5?fusq`d4ok)E*&Q8#wgMa_7qJ+F#<+2+0!2D_HrAxEH zIc2r(0U8@;Q#X0)V1C9K+pGW4SuvTsDKT>bf6yTg}m+|bwClosCY{Er@iEI9x*EfC)$)A)a10!~{|&LaepgHMdaD8H=W>f?-ol7cUgw%c z;lt!jN|(WAU}sQ4=yIo@b8}a;x522Kf8n(8u$?<4D$+QlN;56VUon=7E}Eeu%2Yfq zTh+kMx=58U4h~wJ50P1q<(S+JkOVP-814F$#3u_25K|@B4Bf8BYuGB2OkZDE_98TV z>f!@lkc@_D6F7)1v@gp}pJPJ);icC%Yn$=nIup zQpKUQo|d?dR*8-s0ll|80^dSf?3SgAy^~=LkVcF63(V@uV~>FJ=J*D*Le9|qQg_F! z(h}Yr*|^?P<6xWG&D7ZZdGk$GExrSO+Ze~L%Q5crPVaeQxv(IwmDH+b?Qm0r*e+Kx zpK1kq1ZoJetW`NCUUyq_UV52U2Vu!T+Ho&g1eaHrn5y*|8BM1numT<%pipSCaaw=B zetyYKHHzc$E(B1CAdJD_aHOucHE%eM9_7gezkOZ7qK1=U&EaW$AW2d z0cS^w)Fo*~X?isQx9BeJI~UHw)Ijgi9SkE>Q=!&j$HYq$Lk5Ab`Q^WD;`afz%}Mza zTbK@Ch^it{qp3$AQZn768O>e9mH$J}vCNAWhoD1sY3CApeL2?4hII@l2rR5Ab^ zMRv#CQEeE(U!Pj%rz}O!1zmn#UIR%#@9iCWlHA_>rhYya>YVNPMM1r)VYKwv%q((8 zags{xWyylG3767a&W3E(1qLrBRc+EzdpHa0gpk-pp`cy*HQdJN70H*MUk903Jy+dA z3;6bI@@mND3Lc|pG2YE*r1oE`!7I(m5n?(jcp?8Y%=`Cn?|R1_(9@355(xW@ox9OJBfNz82U6@1$0s?w4MQ4!DMLbL z?C()&7mt{Um*~R9+!s$%?@MeMkFCVH1_x#t!r~UvSahaW?Ly~XaU`r+Oe~-0$I1zN zBWoTZtDs@x{(+C$b~TU3^Cq+6>C*Mza1C4=dD14>HE}4C8)I^Az#2V&V1f?B=K2z3 zVjUt8BRZXd@z0Yx^XvW?@ai|Q*!p-2i3`YOz(oZdg54DA7|BZK6A7&PzC^V-$6dY8 z)-K-a5!ojx{d!Tq%<7fuf`0TD>1E-ksP~J+5bnpwo#qhnNFdR)#c+YS8Q7)mbZn-V zu`9`TW{bb(DU>rY!-~&&oG!iQ4sW<(XhtteqQn7 zAn@t;`Sz{FW09>sM7FBXa=lUf&Q1!@QLWsl_i4ccW|4ryCCO;QSh>fZJlioDjx>x2uo*c1B32Ix?A#%44`N2n zR%b41ez7q|&K?hC=FNZ|!ktM*&r||Xc=#*&K00OJ^88r%v5V#)XQQX6pj7(EV_48?s>V-s_t9AAyP$^#2K+1!_pzPXt5Zlvxz@Fc@ZPN)On9^UmCogi`HPziZz z2~y@6!COX*`3Mb3+H$#XN-lb?IF{bhGY^T#Tc(DZ%J;n$Ltup1t>!l!{GJpp5-Ne@ z3rkc2sf%hvW)_zZdfmPSTJ4QvGL`9$YF4w$Yv@^0U4QQk1m2M>+3V4co!`-3fFn0`< z)AYS{_qq{=GTLJkPJBq|)xWAzCCw@{S0}w5BNGwH&pGY7wPr9oz3YdK+egOvNVe5u zQ?10EWiIr-i@#?}wLcnEaT3DidZ;bdE@U0|gjphbT5pa1_JO6uzVmvloJkQX7$ap} zKh6zG=l7hx<@+;2S_%LV(O8bxvewbsJWIaZp-FaICad%zR!738B(4B2HoZ+_5wL-?bPunM z3KyyAlYn(@*>e*2AszjkWg)w!j1b6~@a95gP2v@RY5oxGyEzu%bqLd15(HjQu_7@1 z75fSaC(Xx}N;8siUos^2Ds6%YBm&3M@4?9St=C(w0Mx7diB%#9onr0w&!D4OLc?>&^=F3n^TNWQsV|pf) zVPkA{<1mYLhw8_p#VO9vpgy@$>e3O09`E(F+M@7O4U9MXn6&ib3eWJ!&8cEWPBpzihlAM0yj1+gTAL$XOK3fET%DONwz1QP=NYvAf~2{l!KJo9Lq!T z{(&XR-|Cob9sqQrO0_@gC?;r$kLh6D7i?5ZbSC=}BBw#tkrqqAm7I;0isp_RrM+45 zanIKMYH(giOeblj0Hq%U#LsG!sog; zHiHXd&>l zlLk{)s1%UIWMYEpXtvXEPV8HLmXbj7iheOHMMvAy%4ydR>(X{=QC?UeUBj8$4(01I zqhJxHr34ZpMsMDw13fxOhmx*Nv8XNOTHb&k@n|hVIafciiiho+& zr)@g_JeKUk+B;|cAUux*P(;9#_sXoDC*Ruxy?2QD0Lg_x4k~~NFLE{%DCD@aw$j-y zBttxH790JwBQ|gSqSw2nd`j^P3~(|ZR%N+Jx(_px=6BDsu`&U-GLW&3h7wf9 z#vA275iSjs(;E0NBC=$U1jOSSo>uA|Pk{SrEqkYZPUEy${_*+8>BK!ahVV?db|ZIR zBHF*er|96Yopw!2UmX!(m`vt+Gm-x>>nk?oCY6U3(`%;tA?{*28XWQJOpVj`!G@Pv z(BnOV<`C@s&>Uo87S*mEyOX*HK5O)Qbi|L5z(n zESwFF&ZhGQ4I|<}s>a<&e%0wzDF_QmN6tD!+({w-ppP9*z;Id`WbbhP{7BPQE3wYc zbY8F-nl@YUlZ-NGf&vw<#mCf0XFguJbz*nmTkZlZ%HB70Qmmrn zmtU&iF|vPpcWTi8G&o4ohu?D4vv|?OiZ8H*+SNeF5Dlph(!)$0wFHzN9ZRZSSq@2;kh7 zxc7mGp>$7we?4RuL%U%~TZLSMx)vH+^aj;`M2zby9`=3Un0jr>`$MG`(ezxuykFtb zA%Z8;tt!K{#ptxI8KUiigmybJH1V2mBEH0)JWng}nSQb_J}~F5dBXEG)yo4>m?Bln zeH-@xD{pzH&@V>t7vV7p0O{KAfdGV*`rd!E|h_m$+E9YF{!$$YYOa*fG=IPkb))+@l_vc7H&U z6-3$d;^2YC^?Da&yN3+Ku^-*epVTehm+VaTPVh0@GC*Fm3V5AcoRlb!sE;ISF#2T0 z26dkyDINlNk4p_BSZZ8{_Z!gDM?d*7SWSDl>Eh#UIN~uYYaV9sU7=d2bVu%s{Q6ET z;Sa94{2@d(B|06|HG?B;h;p`!dT9E}k^FxsfE*!?B)9KV|44+rb0G#X<8u zX$zQVi42e56~NLP(*Tj6J4TtEAHrW|MeqRrT?P7M_>IDWvOEj;M?*e%L(CJk4lK{O z23`pdkh=nudq2UZ%-H|LJ;+%0Mkj$;!9Q9Hz#ESfJK>=H28^acZ1eWBafPG2M@v8d zZB71nK=Y4aDP|=Ic(_C#M24c3l9PmL->^62i*G}Cb?dl%vYUU&gy%7Xa}1a?;997R z3<~MuGizW?12Z+RRG=;}U8G7^fJe5aLJiu#80DrP{@si8|CP_9xnMwYW7NaihcD{(00%X=5};$)Y+@wN3k@{-`*7yGA| zQH)`jkGCdoBJhvurnw+R3JuYs*pbH(wi#rgl>_iPLI&*%{o(q?`vbI< zHLM7T=i{$Ui1n@IN5*guYW|rN+5qUNQ2)miqwN068P@oj&j{-s68hI#w8bY%b3q z+G5g12Ycihe%Rks`lsf<5R&*$ zQ|V~3G0ubq=xcfrE0{uldOjvF`vltKa2bW7!tM=IT3o-3toQyKJRh+uM*loy^B;!K zFOmk@E%O_jzwH(KRSN&c=I`eCZ*2bW<-l)jeq;0ZdGfDZfAi*l!<&WxGB_+Fcn%QT zGc-@OvGwKB&6%|-NEyCG*a|*lZ^By9Wu#a$r~3hG>i>a}i?RUh(YEoj3)&ZeAK(o+ z&oc#JnF6jynu$CSTHY&CV2)+1S2h-Xp|*#O>;Qk8IoZEe`QJs;Z?W^g5j(%l{(kRo-u&jx z-?{LwT>oG2rdbuOcR`FI>zw17IG!Ff>(H6qOir*I_+UOAKC$v@o0^^oSHLa)iR>g0 zEjzymza)TRcR0K;oJYq#CL506KXSz|cO&7Lx9QlIGqg0gV$`z#{6!AoIw#G!BzA-R zGRpA^@Nk$0ykJp-eR~l0jD|md_5E-6a^_>-z$h!vG&|kURI~I!d9?drcG|;)K@Ht- zj$FAx>{ocC1sY+u- zT?o=f!2aQD%MvZ?gye|^GOUgM2shiobhdY#(MotaKV-k+xx~(UaFgG9 zm7iAZHZJmHtK)rsQ^k@M(U=WvrpEB1rs7*1COeNXe_6vpG80Kdb3zynw%H@tP+;{? zz-=$wg5=Db5#^-p-VqI*<~>snbCG{Iq3ze4i|ND=HNidldBwE?R6LhR)gF17g-c%l z8l$SpT6fX$wRZy!&>3Ag!Zf|;qBfeyxYleZOcd~B5~ls4$U$ZTW@<|CZff{OQvcnv z8s({r>^#2t?X3%Tl)}=A--RP%Qk|2>>3i!75@epY>(I`KFHvPTlxFg`jWoQ5)a%*uiAG%Rr6P^#Nx`(G({E1PtUPbRi8SeM= zqqMTg{B{qcah5xm$pQR1(6;59nI)2F&U+oF4Qt_NcB!`mU@G173jW>>r_CsD)^_0e zO&{#RM%Q+l&sd5&X3#5H%?~}GDDLj|h8-KT$^?^rshB?dVIs}3LWl4auq}uR<-aiN zg*~IfwqerhP>2$=bg%FSD5*6OxT0)1JQDmH%quC1D-V7HYZKUH59ugKCR`x%i>#Vq*dIk-BFy8QE4=W z!M{vK^Dlv^ee$*H$Hl`JMz@7_Gwq*;i;6wI7CN9z?mh%S^J^|BY^N=!DOg!D?Z zdWX_l8%cV~Fil#^xItjz1op*KXjs?)1bgh~_p_T3=dm3b-{wQaz~m^}HfAMbd(UZE z*~p}L`$%@pJBCEzp#WyljIKrn-OFG!&Wn8})$dfs`*$i}-)igPRqJXFaaDT&ggssI zHtBp0&o=cms;jgvTL+6RGu|VeX-;VNw!IhnU1JT?uUlwa$msXh&;3pO7Wg z6tK3BRs=|1aG}(UYf`cT-TKLh_7b#L8j;5?eALJ{cG-eRi{vQAfCTQ`@!Dy zh%MJ@d{xg*-F#N+Y-wJ=1{o$6hQ}Y*mA6ltl6?2Z>Q@&(sEU5Qe{B6t{)6}VYOEig z{%am|U43>C%c42k!ZNur?2v7*%#%TQJGHWmZO) zX26UaEDnvI<(6Czwb{2el+$&6<(7TnpFXM2tXqBtScKRa{GD2(4%{=_bxXZ1_bYGa z8~s^lQzk_|H0n2P$PpBfN$+X?aXlDVGR@=IcP01jldCokLhh}e(H+L0@5MOOKkfgX z82nXzJCkbDN)F9!Yk*O>mU(9OlbUGPy2c87P(uEEL{4_={vE)SI@v46=xAx4_|vk$ zo&(j4F|Nnoq(4}(Pvl4Yk;fs|_uXF}8{;UF_K06|s`{sciBFzeyl%2wquf4a$)nv% zzfZ00@6}kp{Ez8x{U70xz~dL@i}g1Zl1+5Zd41D@T&U$j}{+nCp?9AK#^!48LH|CXQ&o{jo zpY(G7#`8_@bxmf*2VZZFJ(#L9J5TX|anv238-LF`Tv#D@f2)SdPl;N5p>L?08bMc$r3pRF__Pl*+%qyZC2E%WDM2SGe-zfHs7;Rg=#lhEPwd@odF@U8Kg+*}9@g0L&gA{d zpAWGX{a@-aDg$PFOO5pxF^ceh^fJ9+M5>bw{>ORy!=q!zA}2?G^ejEb9D6%?68oRu z*X{o^@JK%3|HV@Qj14AWwff^ILyfF!T?6oH#1H=O+h?^t?D9=Na%#7C&Gs;0@pDmh z%l;33_H9cY|K0f*{*U!`{U=+7x}uBmi|aqUvv1q~MFu7+kH!b83LI4h4X@FVfTbMb zLIPuf|A+VJ8GGN<5A)mJ?vZ{NEA6nUGIjH%H48U3d7dybDRf)0w1YwHP|pL#d50S8 H|K9`vDA+86 diff --git a/docs/index.md b/docs/index.md index 118cbd7..18b97ca 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,6 +6,7 @@ drivers and rewire buttons * All buttons on supported controllers work, no rewiring or fiddling required * Multiple supported controllers can be connected * Supported controllers work in Steam, Windows Store games, etc +* QT5 GUI # Supported controllers @@ -55,6 +56,10 @@ Steam only sees the virtual XB360 controller a **WinUSB** driver for your supported controller. If you own multiple different supported controllers then you must repeat step 5 for each different supported controller +### Optional +01. Install OpenSSL from [http://slproweb.com/products/Win32OpenSSL.html](http://slproweb.com/products/Win32OpenSSL.html) in order to + enable the update check functionality. Without this installed the application is unable to check for updates + ## Updating @@ -66,17 +71,18 @@ Steam only sees the virtual XB360 controller ## Usage 01. Open the folder that you extracted the **XBOFS.win.zip** bundle into -02. Run **XBOFS.win.exe** +02. Run **XBOFS.win.qt5.exe** 03. Connect your supported controller(s) 04. Wait for the interface to indicate your device(s) is **Reading input...**. You may see a pop-up notification about a new XB360 controller(s) being detected, this is normal -05. You can now minimize the **XBOFS.win.exe** window if you so choose +05. You can now minimize the **XBOFS.win.qt5.exe** window if you so choose. The application will minimise to the system tray + by default 06. You should now be able to play games using your supported controller(s). Any game that supports a standard XB360 controller should work, regardless of whether it is a Steam game, Windows store game or any other platform that supports the XB360 controller. Note that the input from the stick is mapped to the D-Pad on the virtual XB360 controller not the analog stick -07. When you are done playing, activate the **XBOXFS.win.exe** window and hit *Q*. The application will exit (it can - take up to 20 seconds to do so) and the virtual XB360 controller(s) will disappear +07. When you are done playing simply unplug your device(s) and pack them away. You can leave the application running minimized + as it does not consume a large amount of memory or CPU time. You can exit the application using the *File* menu or the tray icon ## Uninstallation @@ -89,6 +95,7 @@ Steam only sees the virtual XB360 controller 06. In the dialog box, mark the checkbox labelled *Delete the driver software for this device* 07. Click *Uninstall* 08. Replug your supported controller. It will now use whichever driver was previously configured for it +09. Delete the folder you extracted the **XBOFS.win.zip** bundle into ## Support From 850bfb4acdb2e7a7cff5d121e837f128892645a5 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Thu, 31 Oct 2019 21:46:15 +0200 Subject: [PATCH 49/50] #2: Fixed function used to detect VigEm and WinUSB drivers --- XBOFS.win/src/utils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XBOFS.win/src/utils.cpp b/XBOFS.win/src/utils.cpp index dde0dbe..e4c76c1 100644 --- a/XBOFS.win/src/utils.cpp +++ b/XBOFS.win/src/utils.cpp @@ -55,7 +55,7 @@ bool XBOFSWin::deviceInterfaceAvailable(LPGUID deviceInterfaceGUID, bool present ); if (configurationManagerResult != CR_SUCCESS) return false; - return deviceInterfaceListSize > 0; + return deviceInterfaceListSize > 1; } bool XBOFSWin::vigEmBusAvailable() From 60ca49d7f6d73387b83edeb23d9b8105b175b6c5 Mon Sep 17 00:00:00 2001 From: Adam Jorgensen Date: Thu, 31 Oct 2019 21:46:32 +0200 Subject: [PATCH 50/50] #2: Fixed autostart feature --- XBOFS.win.qt5/XBOFSWinQT5GUI.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp index 0962bd3..d291cb3 100644 --- a/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp +++ b/XBOFS.win.qt5/XBOFSWinQT5GUI.cpp @@ -7,6 +7,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -311,9 +314,11 @@ void XBOFSWinQT5GUI::showEvent(QShowEvent *event) { void XBOFSWinQT5GUI::handleAutostartCheckboxStateChanged(const quint16 state) { autostart = state == Qt::Checked; settings->setValue(SETTINGS_AUTOSTART, autostart); - auto autostartSettings = QSettings("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat); - if (autostart) autostartSettings.setValue("XBOFS.win", "\"" + QApplication::applicationFilePath().replace("/", "\\") + "\""); - else autostartSettings.remove("XBOFS.win"); + auto applicationFilePath = QApplication::applicationFilePath(); + auto applicationsLocation = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation); + auto linkPath = applicationsLocation + QDir::separator() + "Startup" + QDir::separator() + "XBOFS.win.qt5.lnk"; + if (autostart) QFile::link(applicationFilePath, linkPath); + else QFile::remove(linkPath); } void XBOFSWinQT5GUI::handleStartMinimizedCheckboxStateChanged(const quint16 state) {