This commit is contained in:
2023-02-02 23:01:14 +01:00
parent a62ee9f811
commit 8d9694b125
14 changed files with 1905 additions and 41 deletions

View File

@@ -1,12 +1,14 @@
#pragma once
#include "AudioNotificationListener.h"
#include "WindowsShell.h"
#include <endpointvolume.h>
#include <string>
#include <vector>
#include <unordered_map>
#include <chrono>
class AudioDevice {
public:
@@ -49,6 +51,10 @@ public:
bool showChecklistExtras = false;
std::vector<std::string> taskNames = {};
std::unordered_map<std::string, std::vector<time_t>> tasks = {};
std::vector<std::string> baseStationMacAdresses = {};
float timerDuration = 5.f * 60.f;
float timerRepeatDuration = 2.f * 60.f;
bool timerRepeating = false;
};
enum class HoverTargetType
@@ -57,6 +63,14 @@ enum class HoverTargetType
HOVER_TARGET_CHECKLIST_DAY = 1,
};
struct TimerData {
bool isTimerActive = false;
std::chrono::system_clock::time_point timerStartTimestamp;
bool timerHasNotified = false;
float lastNotifySeconds;
TimerToastHandler toastHandler = {};
};
class ApplicationData {
public:
ApplicationSettings settings = {};
@@ -65,6 +79,8 @@ public:
std::shared_ptr<AudioData> audioData = std::make_shared<AudioData>();
HoverTargetType hoverTargetType = HoverTargetType::HOVER_TARGET_NONE;
time_t hoverTargetDay = 0;
TimerData timerData{};
std::mutex timerMutex{};
//ApplicationData(const ApplicationData&) = delete;
//ApplicationData& operator=(const ApplicationData&) = delete;

View File

@@ -14,6 +14,7 @@
#include "Settings.h"
#include "AsuroTool.h"
#include "WindowsShell.h"
#include "Timer.h"
#include <array>
#include <functional>
@@ -22,8 +23,10 @@
#include <format>
#include <ctime>
#define MAX_FONT_PATH_LENGTH 2048
#define SETTINGS_POPUP_NAME "settings_popup"
#define TIMER_WARNING_COLOR ImVec4(.7f, 0.2f, 0.2f, .5f)
// Globals for use in callbacks
DrawData* gDrawData;
@@ -34,6 +37,8 @@ time_t selectedDay = 0;
int main()
{
isError(CoInitializeEx(NULL, COINIT_MULTITHREADED), "Failed to initialize");
std::wstring appDir;
getAppDir(appDir);
if (_wchdir(appDir.c_str()) != 0)
@@ -89,6 +94,10 @@ void init(DrawData& drawData, ApplicationData& appData)
iconFontPath.append("\\remixicon.ttf");
io.Fonts->AddFontFromFileTTF(iconFontPath.c_str(), 14.0f, &icons_config, icons_ranges);
// Start timer thread
std::thread timerThread(updateTimer, std::ref(appData));
timerThread.detach();
// Time
selectedDay = getDayStartOf(std::time(nullptr));
@@ -118,14 +127,23 @@ void draw(DrawData& drawData, ApplicationData& appData)
ImGui::SetNextWindowPos(ImVec2(0, customYCursor));
ImGui::SetNextWindowSize(ImVec2(viewportSize.x, 0));
customYCursor += checklistWindow(appData, std::format(" {} Checklist", ICON_CHECK_FILL).c_str()).y;
customYCursor += 5.;
// Timer
ImGui::SetNextWindowPos(ImVec2(0, customYCursor));
ImGui::SetNextWindowSize(ImVec2(viewportSize.x / 2.f, 0));
timerWindow(drawData, appData);
// Base Stations
ImGui::SetNextWindowPos(ImVec2(viewportSize.x / 2.f, customYCursor));
ImGui::SetNextWindowSize(ImVec2(viewportSize.x / 2.f, 0));
customYCursor += baseStationWindow(appData).y;
customYCursor += 5.;
// Playback Devices
ImGui::SetNextWindowPos(ImVec2(0, customYCursor));
ImGui::SetNextWindowSize(ImVec2(viewportSize.x, 0));
customYCursor += audioDeviceWindow(appData, appData.audioData->playbackDevices, std::format(" {} Playback", ICON_HEADPHONE_FILL).c_str()).y;
customYCursor += 5.;
// Recording devices
@@ -306,6 +324,162 @@ void drawDayLineButton(ApplicationData& appData, ImDrawList* drawList, float lin
}
}
void formatTime(std::chrono::system_clock::time_point time, char* buffer, size_t bufferSize)
{
const time_t tTime = std::chrono::system_clock::to_time_t(time);
tm timeInfo;
localtime_s(&timeInfo, &tTime);
std::strftime(buffer, bufferSize, "%H:%M", &timeInfo);
}
ImVec2 timerWindow(DrawData& drawData, ApplicationData& appData)
{
appData.timerMutex.lock();
TimerData& timerData = appData.timerData;
std::string title;
if (timerData.isTimerActive)
{
if (timerData.timerHasNotified)
{
title = std::format("{} Timer done!", ICON_TIMER_FILL);
}
else
{
float elapsedMinutes = calcElapsedSeconds(timerData.timerStartTimestamp) / 60.f;
title = std::format("{} Timer ({:.0f}/{:.0f} minutes)", ICON_TIMER_FILL, elapsedMinutes, appData.settings.timerDuration / 60.f);
}
}
else
{
title = std::format("{} Timer", ICON_TIMER_FILL);
}
// Draw window
if (ImGui::Begin(title.c_str(), 0, ImGuiWindowFlags_NoResize))
{
if (windowHeaderButton(ICON_SETTINGS_FILL))
{
ImGui::OpenPopup(SETTINGS_POPUP_NAME);
}
float timerDisplayMinutes = appData.settings.timerDuration / 60.f;
if (timerData.isTimerActive)
{
float elapsedSeconds = calcElapsedSeconds(timerData.timerStartTimestamp);
float timerPercentComplete = elapsedSeconds / appData.settings.timerDuration;
float timerDisplayPercent = timerPercentComplete;
timerDisplayMinutes = (appData.settings.timerDuration - elapsedSeconds) / 60.f;
if (timerDisplayMinutes < 0.f) timerDisplayMinutes = 0.f;
// Draw background rectangle
ImVec2 padding = ImGui::GetStyle().WindowPadding;
ImVec2 contentMin = ImGui::GetWindowContentRegionMin();
ImVec2 contentMax = ImGui::GetWindowContentRegionMax();
ImVec2 windowPos = ImGui::GetWindowPos();
ImVec2 rectMin = { contentMin.x + windowPos.x - padding.x, contentMin.y + windowPos.y - padding.y };
ImVec2 rectMax = { contentMax.x + windowPos.x + padding.x, contentMax.y + windowPos.y + padding.y };
if (timerDisplayMinutes > 0.f)
{
float contentWidth = rectMax.x - rectMin.x;
rectMax = { rectMin.x + contentWidth * (1.f - timerDisplayPercent), rectMax.y };
}
ImDrawList* drawList = ImGui::GetWindowDrawList();
ImRect tempRect = ImGui::GetCurrentWindow()->ClipRect;
ImGui::PopClipRect();
drawList->AddRectFilled(rectMin, rectMax, ImColor{ timerDisplayMinutes > 0.f ? ImGui::GetStyleColorVec4(ImGuiCol_TabUnfocusedActive) : TIMER_WARNING_COLOR });
ImGui::PushClipRect(tempRect.Min, tempRect.Max, false);
}
// Play/Reset
if (ImGui::Button(timerData.isTimerActive ? ICON_RESTART_LINE : ICON_PLAY_FILL))
{
if (!timerData.isTimerActive)
{
timerData.isTimerActive = true;
timerData.timerHasNotified = false;
timerData.timerStartTimestamp = std::chrono::system_clock::now();
}
else
{
timerData.isTimerActive = false;
}
}
// Timer controls
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5.f);
if (ImGui::Button(ICON_SUBTRACT_FILL))
{
appData.settings.timerDuration -= 60.f;
if (appData.settings.timerDuration < 0.) appData.settings.timerDuration = 0.f;
}
ImGui::SameLine();
ImGui::PushItemWidth(35.f);
if (ImGui::InputFloat("##timer", &timerDisplayMinutes, 0, 0, "%.0fm"))
{
appData.settings.timerDuration = timerDisplayMinutes * 60.f;
}
ImGui::PopItemWidth();
ImGui::SameLine();
if (ImGui::Button(ICON_ADD_FILL))
{
appData.settings.timerDuration += 60.f;
}
ImGui::SameLine();
char timeStr[32];
std::string timerText;
if (timerData.isTimerActive && timerData.timerHasNotified)
{
formatTime(timerData.timerStartTimestamp + std::chrono::seconds((int)appData.settings.timerDuration), timeStr, sizeof(timeStr));
timerText = std::format("Ended at {}", timeStr);
}
else
{
formatTime(std::chrono::system_clock::now() + std::chrono::seconds((int)appData.settings.timerDuration), timeStr, sizeof(timeStr));
timerText = std::format("Ends at {}", timeStr);
}
float availWidth = ImGui::GetContentRegionMax().x - ImGui::GetCursorPosX();
float textWidth = ImGui::CalcTextSize(timerText.c_str()).x;
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + availWidth - textWidth);
ImGui::Text(timerText.c_str());
}
if (ImGui::BeginPopup(SETTINGS_POPUP_NAME))
{
// Loop
//float loopContentWidth = ImGui::GetFrameHeight() + 5.f + ImGui::CalcTextSize("Loop").x + 5.f + 30.f + 10.f;
//ImGui::SetCursorPosX(ImGui::GetWindowWidth() - loopContentWidth);
ImGui::Checkbox("Loop", &appData.settings.timerRepeating);
ImGui::SameLine();
float loopTimerMinutes = appData.settings.timerRepeatDuration / 60.f;
ImGui::PushItemWidth(35.f);
if (ImGui::InputFloat("##timerloop", &loopTimerMinutes, 0, 0, "%.0fm"))
{
appData.settings.timerRepeatDuration = loopTimerMinutes * 60.f;
}
ImGui::PopItemWidth();
ImGui::EndPopup();
}
appData.timerMutex.unlock();
ImVec2 size = ImGui::GetWindowSize();
ImGui::End();
return size;
}
ImVec2 checklistWindow(ApplicationData& appData, const char* title)
{
if (ImGui::Begin(title, 0, ImGuiWindowFlags_NoResize))
@@ -351,16 +525,19 @@ ImVec2 checklistWindow(ApplicationData& appData, const char* title)
std::vector<time_t>& taskDates = appData.settings.tasks[taskName];
bool taskDone = std::any_of(taskDates.begin(), taskDates.end(), selectedDayMatcher);
bool highlightButton = std::all_of(taskDates.begin(), taskDates.end(), [&](time_t t) {
double timeDiffSeconds = difftime(today, getDayStartOf(t));
return timeDiffSeconds >= appData.checklistHighlightDurationDays * 24 * 60 * 60 + 1;
});
time_t mostRecentDoneDate = *std::max_element(taskDates.begin(), taskDates.end());
double timeDiffSeconds = difftime(today, getDayStartOf(mostRecentDoneDate));
int timeDiffDays = timeDiffSeconds / (60 * 60 * 24);
bool highlightButton = timeDiffDays >= appData.checklistHighlightDurationDays;
if (highlightButton)
{
ImGui::PushStyleColor(ImGuiCol_FrameBg, { .5f, .2f, .15f, 0.54f });
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, { .6f, .3f, .2f, 0.40f });
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, { .6f, .3f, .2f, 0.67f });
}
ImVec2 beforeCheckbox = ImGui::GetCursorScreenPos();
if (ImGui::Checkbox(taskName.c_str(), &taskDone))
{
if (taskDone)
@@ -372,11 +549,21 @@ ImVec2 checklistWindow(ApplicationData& appData, const char* title)
std::erase_if(taskDates, selectedDayMatcher);
}
}
ImGui::SameLine();
if (highlightButton)
{
ImGui::PopStyleColor(3);
ImVec2 afterCheckbox = ImGui::GetCursorScreenPos();
std::string dayText = std::format("{}", timeDiffDays);
float textWidth = ImGui::CalcTextSize(dayText.c_str()).x;
ImGui::SetCursorScreenPos({ beforeCheckbox.x + ImGui::GetFrameHeight() / 2.f - textWidth / 2.f, beforeCheckbox.y });
ImGui::PushStyleColor(ImGuiCol_Text, { 1.f, 1.f, 1.f, 1.f });
ImGui::Text(dayText.c_str());
ImGui::PopStyleColor();
ImGui::SetCursorScreenPos(afterCheckbox);
}
ImGui::SameLine();
}
if (ImGui::BeginPopup(SETTINGS_POPUP_NAME))
@@ -393,6 +580,77 @@ ImVec2 checklistWindow(ApplicationData& appData, const char* title)
return size;
}
void startBaseStationProc(const char* args)
{
// additional information
STARTUPINFOA si;
PROCESS_INFORMATION pi;
// set the size of the structures
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
// start the program up
DWORD creationFlags = 0;
#ifdef _DEBUG
creationFlags = CREATE_NEW_CONSOLE;
#endif
CreateProcessA("lighthouse-v2-manager.exe", const_cast<LPSTR>(args), NULL, NULL, FALSE, creationFlags, NULL, NULL, &si, &pi);
// Close process and thread handles.
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
ImVec2 baseStationWindow(ApplicationData& appData)
{
if (ImGui::Begin(std::format("{} Base Stations", ICON_ALARM_WARNING_FILL).c_str(), 0, ImGuiWindowFlags_NoResize))
{
if (windowHeaderButton(ICON_SETTINGS_FILL))
{
ImGui::OpenPopup(SETTINGS_POPUP_NAME);
}
if (ImGui::Button("Wake"))
{
std::string params{ " on" };
for (std::string& mac : appData.settings.baseStationMacAdresses)
{
params.append(" ");
params.append(mac.c_str());
}
startBaseStationProc(params.c_str());
}
ImGui::SameLine();
if (ImGui::Button("Shutdown"))
{
std::string params{ " off" };
for (std::string& mac : appData.settings.baseStationMacAdresses)
{
params.append(" ");
params.append(mac.c_str());
}
startBaseStationProc(params.c_str());
}
}
if (ImGui::BeginPopup(SETTINGS_POPUP_NAME))
{
if (ImGui::Button("Search"))
{
startBaseStationProc("discover");
// TODO: parse stuff (annoying)
}
ImGui::Text(std::format("Known: {}", appData.settings.baseStationMacAdresses.size()).c_str());
ImGui::EndPopup();
}
ImVec2 size = ImGui::GetWindowSize();
ImGui::End();
return size;
}
ImVec2 audioDeviceWindow(ApplicationData& appData, std::vector<AudioDevice>& deviceList, const char* title)
{
if (ImGui::Begin(title, 0, ImGuiWindowFlags_NoResize))

View File

@@ -15,5 +15,7 @@ void cleanup(DrawData& drawData, ApplicationData& appData);
ImVec2 menuBar(DrawData& drawData, ApplicationData& appData);
ImVec2 checklistWindow(ApplicationData& appData, const char* title);
ImVec2 timerWindow(DrawData& drawData, ApplicationData& appData);
ImVec2 baseStationWindow(ApplicationData& appData);
ImVec2 audioDeviceWindow(ApplicationData& appData, std::vector<AudioDevice>& deviceList, const char* title);
void drawCircle(float radius, ImU32 color);

View File

@@ -98,7 +98,7 @@
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>pathcch.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>pathcch.lib;runtimeobject.lib;%(AdditionalDependencies)</AdditionalDependencies>
<IgnoreSpecificDefaultLibraries>
</IgnoreSpecificDefaultLibraries>
</Link>
@@ -119,7 +119,7 @@
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>pathcch.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>pathcch.lib;runtimeobject.lib;%(AdditionalDependencies)</AdditionalDependencies>
<IgnoreSpecificDefaultLibraries>
</IgnoreSpecificDefaultLibraries>
</Link>
@@ -136,7 +136,7 @@
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>pathcch.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>pathcch.lib;runtimeobject.lib;%(AdditionalDependencies)</AdditionalDependencies>
<IgnoreSpecificDefaultLibraries>
</IgnoreSpecificDefaultLibraries>
</Link>
@@ -157,7 +157,7 @@
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>pathcch.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>pathcch.lib;runtimeobject.lib;%(AdditionalDependencies)</AdditionalDependencies>
<IgnoreSpecificDefaultLibraries>
</IgnoreSpecificDefaultLibraries>
</Link>
@@ -167,8 +167,10 @@
<ClCompile Include="AsuroTool.cpp" />
<ClCompile Include="AudioNotificationListener.cpp" />
<ClCompile Include="Settings.cpp" />
<ClCompile Include="Timer.cpp" />
<ClCompile Include="Util.cpp" />
<ClCompile Include="WindowsShell.cpp" />
<ClCompile Include="WinToast\wintoastlib.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="ApplicationData.h" />
@@ -179,8 +181,10 @@
<ClInclude Include="PolicyConfig.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="Settings.h" />
<ClInclude Include="Timer.h" />
<ClInclude Include="Util.h" />
<ClInclude Include="WindowsShell.h" />
<ClInclude Include="WinToast\wintoastlib.h" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ImguiBase\ImguiBase.vcxproj">
@@ -215,12 +219,17 @@
<ResourceCompile Include="AsuroTool.rc" />
</ItemGroup>
<ItemGroup>
<Image Include="d:\nextcloud\dragon collection\profile pics\icon2.ico" />
<Image Include="d:\nextcloud\dragon collection\profile pics\icon3.ico" />
<Image Include="D:\Nextcloud\Dragon Collection\Profile Pics\kaiju.ico" />
<Image Include="kaiju.ico" />
<Image Include="kaiju32.png" />
<Image Include="kaiju64.png" />
<CopyFileToFolders Include="kaiju.ico">
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</DeploymentContent>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent>
</CopyFileToFolders>
</ItemGroup>
<ItemGroup>
<CopyFileToFolders Include="basestation\lighthouse-v2-manager.exe">
<FileType>Document</FileType>
</CopyFileToFolders>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">

View File

@@ -19,6 +19,12 @@
<Filter Include="Header Files\Audio">
<UniqueIdentifier>{b65d213d-ddf6-4816-90d1-bf0811a51abf}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\lib">
<UniqueIdentifier>{7571e2e0-bbb5-4c09-a047-e1d3c2a0ed5e}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\lib">
<UniqueIdentifier>{ced4b7a2-267d-4b67-a8f1-41b3af68a5f0}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="AsuroTool.cpp">
@@ -42,6 +48,12 @@
<ClCompile Include="WindowsShell.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="WinToast\wintoastlib.cpp">
<Filter>Source Files\lib</Filter>
</ClCompile>
<ClCompile Include="Timer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="AsuroTool.h">
@@ -71,6 +83,12 @@
<ClInclude Include="WindowsShell.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="WinToast\wintoastlib.h">
<Filter>Header Files\lib</Filter>
</ClInclude>
<ClInclude Include="Timer.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<CopyFileToFolders Include="Montserrat-Regular.ttf">
@@ -79,30 +97,14 @@
<CopyFileToFolders Include="remixicon.ttf">
<Filter>Resource Files</Filter>
</CopyFileToFolders>
<CopyFileToFolders Include="basestation\lighthouse-v2-manager.exe" />
<CopyFileToFolders Include="kaiju.ico">
<Filter>Resource Files</Filter>
</CopyFileToFolders>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="AsuroTool.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<Image Include="d:\nextcloud\dragon collection\profile pics\icon2.ico">
<Filter>Resource Files</Filter>
</Image>
<Image Include="kaiju64.png">
<Filter>Resource Files</Filter>
</Image>
<Image Include="d:\nextcloud\dragon collection\profile pics\icon3.ico">
<Filter>Resource Files</Filter>
</Image>
<Image Include="D:\Nextcloud\Dragon Collection\Profile Pics\kaiju.ico">
<Filter>Resource Files</Filter>
</Image>
<Image Include="kaiju32.png">
<Filter>Resource Files</Filter>
</Image>
<Image Include="kaiju.ico">
<Filter>Resource Files</Filter>
</Image>
</ItemGroup>
</Project>

View File

@@ -16,11 +16,7 @@
void initAudio(ApplicationData& appData)
{
HRESULT audioResult;
audioResult = CoInitializeEx(NULL, COINIT_MULTITHREADED);
isError(audioResult, "Failed to initialize COM: ");
audioResult = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&appData.audioData->deviceEnumerator));
HRESULT audioResult = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&appData.audioData->deviceEnumerator));
isError(audioResult, "Failed to set up audio device enumerator: ");
appData.audioData->audioNotificationListener = new AudioNotificationListener(appData.audioData);

View File

@@ -68,6 +68,31 @@ void settingsReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler, void* en
settings->tasks.insert({ task, std::vector{ dayTimestamp } });
}
}
std::string baseStationMac{};
baseStationMac.resize(MAX_MAC_ADDRESS_LENGTH);
if (sscanf_s(line, "baseStationMac=%s", &baseStationMac[0], MAX_MAC_ADDRESS_LENGTH))
{
settings->baseStationMacAdresses.push_back(baseStationMac);
}
float timerDuration;
if (sscanf_s(line, "timerDuration=%f", &timerDuration))
{
settings->timerDuration = timerDuration;
}
float timerRepeatDuration;
if (sscanf_s(line, "timerRepeatDuration=%f", &timerRepeatDuration))
{
settings->timerRepeatDuration = timerRepeatDuration;
}
int timerRepeating;
if (sscanf_s(line, "timerRepeating=%i", &timerRepeating))
{
settings->timerRepeating = (bool)timerRepeating;
}
}
void settingsWriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* outBuf)
@@ -88,6 +113,15 @@ void settingsWriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTex
outBuf->appendf("task=%lld %s\n", date, task.first.c_str());
}
}
for (std::string& baseStationMac : gAppData->settings.baseStationMacAdresses)
{
outBuf->appendf("baseStationMac=%s\n", baseStationMac.c_str());
}
outBuf->appendf("timerDuration=%f\n", gAppData->settings.timerDuration);
outBuf->appendf("timerRepeatDuration=%f\n", gAppData->settings.timerRepeatDuration);
outBuf->appendf("timerRepeating=%i\n", (int)gAppData->settings.timerRepeating);
}
void applySettings(DrawData& drawData, ApplicationData& appData)

View File

@@ -6,6 +6,7 @@
#define APPLICATION_SETTINGS_GROUP "ApplicationSettings"
#define MAX_TASK_NAME_LENGTH 1024
#define MAX_MAC_ADDRESS_LENGTH 128
void initSettings(DrawData& drawData, ApplicationData& appData);

65
AsuroTool/Timer.cpp Normal file
View File

@@ -0,0 +1,65 @@
#include "Timer.h"
float calcElapsedSeconds(std::chrono::system_clock::time_point startTime)
{
auto elapsedTime = std::chrono::system_clock::now() - startTime;
return std::chrono::duration_cast<std::chrono::seconds>(elapsedTime).count();
}
void updateTimer(ApplicationData& appData)
{
while (true)
{
appData.timerMutex.lock();
TimerData& timerData = appData.timerData;
if (timerData.isTimerActive)
{
// Main timer
float elapsedSeconds = calcElapsedSeconds(timerData.timerStartTimestamp);
float timerPercentComplete = elapsedSeconds / appData.settings.timerDuration;
if (timerPercentComplete >= 1.f)
{
if (!timerData.timerHasNotified)
{
// Alert
timerData.timerHasNotified = true;
std::wstring titleText = std::format(L"{} minutes over!", appData.settings.timerDuration / 60.f);
std::wstring messageText;
if (appData.settings.timerRepeating)
{
messageText = std::format(L"Repeats every {} minutes.", appData.settings.timerRepeatDuration / 60.f);
timerData.lastNotifySeconds = elapsedSeconds;
}
else
{
timerData.isTimerActive = false;
messageText = L"Timer does not repeat.";
}
showToastNotification(&timerData.toastHandler, titleText.c_str(), messageText.c_str());
}
else
{
// Loop timer
if (elapsedSeconds - timerData.lastNotifySeconds >= appData.settings.timerRepeatDuration)
{
timerData.lastNotifySeconds = elapsedSeconds;
float elapsedSecondsSinceEnd = elapsedSeconds - appData.settings.timerDuration;
std::wstring titleText = std::format(L"Timer ended {} minutes ago", elapsedSecondsSinceEnd / 60.f);
std::wstring messageText = std::format(L"Repeats every {} minutes.", appData.settings.timerRepeatDuration / 60.f);
showToastNotification(&timerData.toastHandler, titleText.c_str(), messageText.c_str());
}
}
}
}
appData.timerMutex.unlock();
Sleep(1000);
}
}

8
AsuroTool/Timer.h Normal file
View File

@@ -0,0 +1,8 @@
#pragma once
#include <chrono>
#include "ApplicationData.h"
float calcElapsedSeconds(std::chrono::system_clock::time_point startTime);
void updateTimer(ApplicationData& appData);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,234 @@
/* * Copyright (C) 2016-2019 Mohammed Boujemaoui <mohabouje@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef WINTOASTLIB_H
#define WINTOASTLIB_H
#include <Windows.h>
#include <sdkddkver.h>
#include <WinUser.h>
#include <ShObjIdl.h>
#include <wrl/implements.h>
#include <wrl/event.h>
#include <windows.ui.notifications.h>
#include <strsafe.h>
#include <Psapi.h>
#include <ShlObj.h>
#include <roapi.h>
#include <propvarutil.h>
#include <functiondiscoverykeys.h>
#include <iostream>
#include <winstring.h>
#include <string.h>
#include <vector>
#include <map>
using namespace Microsoft::WRL;
using namespace ABI::Windows::Data::Xml::Dom;
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::UI::Notifications;
using namespace Windows::Foundation;
namespace WinToastLib {
class IWinToastHandler {
public:
enum WinToastDismissalReason {
UserCanceled = ToastDismissalReason::ToastDismissalReason_UserCanceled,
ApplicationHidden = ToastDismissalReason::ToastDismissalReason_ApplicationHidden,
TimedOut = ToastDismissalReason::ToastDismissalReason_TimedOut
};
virtual ~IWinToastHandler() = default;
virtual void toastActivated() const = 0;
virtual void toastActivated(int actionIndex) const = 0;
virtual void toastDismissed(WinToastDismissalReason state) const = 0;
virtual void toastFailed() const = 0;
};
class WinToastTemplate {
public:
enum class Scenario { Default, Alarm, IncomingCall, Reminder };
enum Duration { System, Short, Long };
enum AudioOption { Default = 0, Silent, Loop };
enum TextField { FirstLine = 0, SecondLine, ThirdLine };
enum WinToastTemplateType {
ImageAndText01 = ToastTemplateType::ToastTemplateType_ToastImageAndText01,
ImageAndText02 = ToastTemplateType::ToastTemplateType_ToastImageAndText02,
ImageAndText03 = ToastTemplateType::ToastTemplateType_ToastImageAndText03,
ImageAndText04 = ToastTemplateType::ToastTemplateType_ToastImageAndText04,
Text01 = ToastTemplateType::ToastTemplateType_ToastText01,
Text02 = ToastTemplateType::ToastTemplateType_ToastText02,
Text03 = ToastTemplateType::ToastTemplateType_ToastText03,
Text04 = ToastTemplateType::ToastTemplateType_ToastText04,
};
enum AudioSystemFile {
DefaultSound,
IM,
Mail,
Reminder,
SMS,
Alarm,
Alarm2,
Alarm3,
Alarm4,
Alarm5,
Alarm6,
Alarm7,
Alarm8,
Alarm9,
Alarm10,
Call,
Call1,
Call2,
Call3,
Call4,
Call5,
Call6,
Call7,
Call8,
Call9,
Call10,
};
WinToastTemplate(_In_ WinToastTemplateType type = WinToastTemplateType::ImageAndText02);
~WinToastTemplate();
void setFirstLine(_In_ const std::wstring& text);
void setSecondLine(_In_ const std::wstring& text);
void setThirdLine(_In_ const std::wstring& text);
void setTextField(_In_ const std::wstring& txt, _In_ TextField pos);
void setAttributionText(_In_ const std::wstring& attributionText);
void setImagePath(_In_ const std::wstring& imgPath);
void setAudioPath(_In_ WinToastTemplate::AudioSystemFile audio);
void setAudioPath(_In_ const std::wstring& audioPath);
void setAudioOption(_In_ WinToastTemplate::AudioOption audioOption);
void setDuration(_In_ Duration duration);
void setExpiration(_In_ INT64 millisecondsFromNow);
void setScenario(_In_ Scenario scenario);
void addAction(_In_ const std::wstring& label);
std::size_t textFieldsCount() const;
std::size_t actionsCount() const;
bool hasImage() const;
const std::vector<std::wstring>& textFields() const;
const std::wstring& textField(_In_ TextField pos) const;
const std::wstring& actionLabel(_In_ std::size_t pos) const;
const std::wstring& imagePath() const;
const std::wstring& audioPath() const;
const std::wstring& attributionText() const;
const std::wstring& scenario() const;
INT64 expiration() const;
WinToastTemplateType type() const;
WinToastTemplate::AudioOption audioOption() const;
Duration duration() const;
private:
std::vector<std::wstring> _textFields{};
std::vector<std::wstring> _actions{};
std::wstring _imagePath{};
std::wstring _audioPath{};
std::wstring _attributionText{};
std::wstring _scenario{L"Default"};
INT64 _expiration{0};
AudioOption _audioOption{WinToastTemplate::AudioOption::Default};
WinToastTemplateType _type{WinToastTemplateType::Text01};
Duration _duration{Duration::System};
};
class WinToast {
public:
enum WinToastError {
NoError = 0,
NotInitialized,
SystemNotSupported,
ShellLinkNotCreated,
InvalidAppUserModelID,
InvalidParameters,
InvalidHandler,
NotDisplayed,
UnknownError
};
enum ShortcutResult {
SHORTCUT_UNCHANGED = 0,
SHORTCUT_WAS_CHANGED = 1,
SHORTCUT_WAS_CREATED = 2,
SHORTCUT_MISSING_PARAMETERS = -1,
SHORTCUT_INCOMPATIBLE_OS = -2,
SHORTCUT_COM_INIT_FAILURE = -3,
SHORTCUT_CREATE_FAILED = -4
};
enum ShortcutPolicy {
/* Don't check, create, or modify a shortcut. */
SHORTCUT_POLICY_IGNORE = 0,
/* Require a shortcut with matching AUMI, don't create or modify an existing one. */
SHORTCUT_POLICY_REQUIRE_NO_CREATE = 1,
/* Require a shortcut with matching AUMI, create if missing, modify if not matching.
* This is the default. */
SHORTCUT_POLICY_REQUIRE_CREATE = 2,
};
WinToast(void);
virtual ~WinToast();
static WinToast* instance();
static bool isCompatible();
static bool isSupportingModernFeatures();
static std::wstring configureAUMI(_In_ const std::wstring& companyName,
_In_ const std::wstring& productName,
_In_ const std::wstring& subProduct = std::wstring(),
_In_ const std::wstring& versionInformation = std::wstring());
static const std::wstring& strerror(_In_ WinToastError error);
virtual bool initialize(_Out_opt_ WinToastError* error = nullptr);
virtual bool isInitialized() const;
virtual bool hideToast(_In_ INT64 id);
virtual INT64 showToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHandler* handler, _Out_opt_ WinToastError* error = nullptr);
virtual void clear();
virtual enum ShortcutResult createShortcut();
const std::wstring& appName() const;
const std::wstring& appUserModelId() const;
void setAppUserModelId(_In_ const std::wstring& aumi);
void setAppName(_In_ const std::wstring& appName);
void setShortcutPolicy(_In_ ShortcutPolicy policy);
protected:
bool _isInitialized{false};
bool _hasCoInitialized{false};
ShortcutPolicy _shortcutPolicy{SHORTCUT_POLICY_REQUIRE_CREATE};
std::wstring _appName{};
std::wstring _aumi{};
std::map<INT64, ComPtr<IToastNotification>> _buffer{};
HRESULT validateShellLinkHelper(_Out_ bool& wasChanged);
HRESULT createShellLinkHelper();
HRESULT setImageFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& path);
HRESULT setAudioFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& path, _In_opt_ WinToastTemplate::AudioOption option = WinToastTemplate::AudioOption::Default);
HRESULT setTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& text, _In_ UINT32 pos);
HRESULT setAttributionTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& text);
HRESULT addActionHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& action, _In_ const std::wstring& arguments);
HRESULT addDurationHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& duration);
HRESULT addScenarioHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& scenario);
ComPtr<IToastNotifier> notifier(_In_ bool* succeded) const;
void setError(_Out_opt_ WinToastError* error, _In_ WinToastError value);
};
}
#endif // WINTOASTLIB_H

View File

@@ -3,6 +3,7 @@
#include "ApplicationData.h"
#include "Util.h"
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <shellapi.h>
#include <commctrl.h>
@@ -53,6 +54,17 @@ void initShell(DrawData& drawData)
std::cout << "Failed to hook tray icon events: " << std::hex << GetLastError() << std::endl;
}
// Setup Toasts
if (!WinToast::isCompatible()) {
OutputDebugString(L"Error, your system in not supported!\n");
}
WinToast::instance()->setAppName(L"AudioThingy");
const auto aumi = WinToast::configureAUMI(L"asuro", L"audiothingy");
WinToast::instance()->setAppUserModelId(aumi);
if (!WinToast::instance()->initialize()) {
OutputDebugString(L"Error, could not initialize the lib!\n");
}
// Set window minimize behavior
glfwSetWindowIconifyCallback(drawData.window, [](GLFWwindow* window, int isIconified) {
if (isIconified && gAppData->settings.docked)
@@ -70,6 +82,18 @@ void initShell(DrawData& drawData)
});
}
void showToastNotification(TimerToastHandler* handler, const wchar_t* title, const wchar_t* text)
{
WinToastTemplate templ = WinToastTemplate(WinToastTemplate::ImageAndText02);
templ.setImagePath(L"E:\\Code\\AsuroImgui\\x64\\Debug\\kaiju.ico");
templ.setTextField(title, WinToastTemplate::FirstLine);
templ.setTextField(text, WinToastTemplate::SecondLine);
templ.addAction(L"Stop");
if (!WinToast::instance()->showToast(templ, handler)) {
OutputDebugString(L"Error: Could not launch your toast notification!\n");
}
}
void cleanupShell(DrawData& drawData)
{
// Remove tray icon
@@ -100,3 +124,21 @@ LRESULT CALLBACK trayIconEventHandler(int code, WPARAM wParam, LPARAM lParam)
return CallNextHookEx(NULL, code, wParam, lParam);
}
void TimerToastHandler::toastActivated() const
{
glfwShowWindow(gDrawData->window);
glfwRestoreWindow(gDrawData->window);
}
void TimerToastHandler::toastActivated(int actionIndex) const
{
}
void TimerToastHandler::toastFailed() const
{
}
void TimerToastHandler::toastDismissed(WinToastDismissalReason state) const
{
}

View File

@@ -1,8 +1,22 @@
#pragma once
#include <ImguiBase.h>
#include "WinToast/wintoastlib.h"
using namespace WinToastLib;
struct TimerToastHandler : IWinToastHandler
{
void toastActivated() const override;
void toastActivated(int actionIndex) const override;
void toastFailed() const override;
void toastDismissed(WinToastDismissalReason state) const override;
};
void initShell(DrawData& drawData);
void showToastNotification(TimerToastHandler* handler, const wchar_t* title, const wchar_t* text);
void cleanupShell(DrawData& drawData);
LRESULT trayIconEventHandler(int code, WPARAM wParam, LPARAM lParam);