9 Commits

Author SHA1 Message Date
22f9a8a1a6 checklist stuff 2022-12-22 10:55:56 +01:00
7e76c4813d failed loudness eq experiment 2022-12-14 18:42:16 +01:00
8967846657 Separate windows shell interaction into it's own file 2022-08-10 14:17:50 +02:00
c58b6c5624 mute button 2022-07-26 20:34:40 +02:00
5f5e24fa9c 1.0.4 2022-07-26 18:08:13 +02:00
cdffa1b50b per channel peak meters 2022-07-26 18:07:57 +02:00
eec9e25e12 1.0.3 2022-07-26 15:22:51 +02:00
3b4853acda Fix device add/remove handlers 2022-07-26 15:22:23 +02:00
cea13986f0 1.0.2 2022-07-24 02:09:58 +02:00
21 changed files with 2807 additions and 195 deletions

View File

@@ -1,13 +1,44 @@
#include "ApplicationData.h"
#include "Util.h"
#include "AudioApi.h"
#include <functiondiscoverykeys.h>
AudioDevice::AudioDevice()
AudioDevice::AudioDevice(IMMDevice* device, LPCWSTR deviceId) : device(device)
{
id = std::wstring(deviceId);
IPropertyStore* propertyStore;
HRESULT err = device->OpenPropertyStore(STGM_READ, &propertyStore);
isError(err, "Failed to open prop store: ");
PROPVARIANT deviceNameProp;
const wchar_t* deviceName;
err = getDevicePropertyString(propertyStore, PKEY_Device_FriendlyName, &deviceNameProp, deviceName);
isError(err, "Failed to read name of device :");
name = utf8Encode(deviceName);
err = device->GetState(&state);
isError(err, "Failed to reat state of device: ");
err = device->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (LPVOID*)&volumeInterface);
isError(err, "Failed to get audio endpoint volume interface: ");
err = device->Activate(__uuidof(IAudioMeterInformation), CLSCTX_INPROC_SERVER, NULL, (LPVOID*)&meterInterface);
isError(err, "Failed to get audio meter interface: ");
getVolumeLimit(volumeInterface, &minVolumeDb, &maxVolumeDb);
if (propertyStore)
{
propertyStore->Release();
}
}
AudioDevice::AudioDevice(AudioDevice&& other) noexcept
: device(other.device), volumeInterface(other.volumeInterface), meterInterface(other.meterInterface),
id(other.id), name(other.name), state(other.state),
isDefaultConsole(other.isDefaultConsole), isDefaultMedia(other.isDefaultMedia), isDefaultCommunication(other.isDefaultCommunication)
isDefaultConsole(other.isDefaultConsole), isDefaultMedia(other.isDefaultMedia), isDefaultCommunication(other.isDefaultCommunication),
minVolumeDb(other.minVolumeDb), maxVolumeDb(other.maxVolumeDb)
{
other.device = nullptr;
other.volumeInterface = nullptr;
@@ -25,6 +56,8 @@ AudioDevice& AudioDevice::operator=(AudioDevice&& other) noexcept
this->isDefaultConsole = other.isDefaultConsole;
this->isDefaultMedia = other.isDefaultMedia;
this->isDefaultCommunication = other.isDefaultCommunication;
this->minVolumeDb = other.minVolumeDb;
this->maxVolumeDb = other.maxVolumeDb;
other.device = nullptr;
other.volumeInterface = nullptr;

View File

@@ -1,10 +1,12 @@
#pragma once
#include <mmdeviceapi.h>
#include "AudioNotificationListener.h"
#include <endpointvolume.h>
#include <string>
#include <vector>
#include "AudioNotificationListener.h"
#include <unordered_map>
class AudioDevice {
public:
@@ -14,11 +16,13 @@ public:
std::wstring id = {};
std::string name = {};
unsigned long state = {};
bool isDefaultConsole = {};
bool isDefaultMedia = {};
bool isDefaultCommunication = {};
bool isDefaultConsole = false;
bool isDefaultMedia = false;
bool isDefaultCommunication = false;
float minVolumeDb = 0.f;
float maxVolumeDb = 0.f;
AudioDevice();
AudioDevice(IMMDevice* device, LPCWSTR deviceId);
AudioDevice(AudioDevice&& other) noexcept;
AudioDevice& operator=(AudioDevice&& other) noexcept;
~AudioDevice();
@@ -42,13 +46,16 @@ public:
bool autostart = false;
bool docked = false;
bool showDisabledDevices = false;
std::vector<std::string> taskNames = {};
std::unordered_map<std::string, std::vector<time_t>> tasks = {};
};
class ApplicationData {
public:
ApplicationSettings settings = {};
size_t checklistLength = 0;
std::shared_ptr<AudioData> audioData = std::make_shared<AudioData>();
ApplicationData(const ApplicationData&) = delete;
ApplicationData& operator=(const ApplicationData&) = delete;
//ApplicationData(const ApplicationData&) = delete;
//ApplicationData& operator=(const ApplicationData&) = delete;
};

View File

@@ -7,25 +7,22 @@
// see https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/Win7Samples/winui/shell/appshellintegration/NotificationIcon/NotificationIcon.cpp
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#pragma comment(lib, "comctl32.lib")
#include <windows.h>
#include <functional>
#include <iostream>
#include <sstream>
#include <shellapi.h>
#include <commctrl.h>
#include <strsafe.h>
#pragma comment(lib, "rpcrt4.lib")
#include "Util.h"
#include "AudioApi.h"
#include "Settings.h"
#include "resource.h"
#include "AsuroTool.h"
#include "WindowsShell.h"
const size_t MAX_FONT_PATH_LENGTH = 2048;
const UINT TRAY_ID = 420;
const UINT WMAPP_NOTIFYCALLBACK = WM_APP + 1;
#include <array>
#include <functional>
#include <iostream>
#include <sstream>
#include <format>
#include <ctime>
#define MAX_FONT_PATH_LENGTH 2048
// Globals for use in callbacks
DrawData* gDrawData;
@@ -49,7 +46,7 @@ int main()
callbacks.drawFunc = std::bind(draw, std::placeholders::_1, std::ref(applicationData));
callbacks.cleanupFunc = std::bind(cleanup, std::placeholders::_1, std::ref(applicationData));
startImgui(callbacks, "Audio Thingy", 600, 400);
startImgui(callbacks, "Audio Thingy", 700, 400);
}
void init(DrawData& drawData, ApplicationData& appData)
@@ -75,63 +72,14 @@ void init(DrawData& drawData, ApplicationData& appData)
ImFontConfig icons_config;
icons_config.MergeMode = true;
icons_config.PixelSnapH = true;
icons_config.GlyphOffset = { 0., 2. };
std::string iconFontPath = std::string(appPathStr);
iconFontPath.append("\\remixicon.ttf");
io.Fonts->AddFontFromFileTTF(iconFontPath.c_str(), 14.0f, &icons_config, icons_ranges);
// Set window icon
HINSTANCE instance = GetModuleHandle(NULL);
LPWSTR iconId = MAKEINTRESOURCE(IDI_ICON1);
HANDLE iconLarge = LoadImageW(instance, iconId, IMAGE_ICON, 64, 64, 0);
HANDLE iconSmall = LoadImageW(instance, iconId, IMAGE_ICON, 32, 32, 0);
SendMessage(drawData.window_handle, WM_SETICON, ICON_BIG, (LPARAM)iconLarge);
SendMessage(drawData.window_handle, WM_SETICON, ICON_SMALL, (LPARAM)iconSmall);
SendMessage(drawData.window_handle, WM_SETICON, ICON_SMALL2, (LPARAM)iconSmall);
// Set tray icon
NOTIFYICONDATA nid = { sizeof(nid) };
nid.hWnd = drawData.window_handle;
nid.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE | NIF_SHOWTIP;
nid.uID = TRAY_ID;
nid.uCallbackMessage = WMAPP_NOTIFYCALLBACK;
HRESULT iconResult;
iconResult = LoadIconMetric(instance, iconId, LIM_SMALL, &nid.hIcon);
isError(iconResult, "Failed to load tray icon image: ");
iconResult = LoadString(instance, IDS_STRING_TOOLTIP, nid.szTip, ARRAYSIZE(nid.szTip));
isError(iconResult, "Failed to load tray icon text: ");
Shell_NotifyIcon(NIM_ADD, &nid);
nid.uVersion = NOTIFYICON_VERSION_4;
Shell_NotifyIcon(NIM_SETVERSION, &nid);
if (!SetWindowsHookEx(WH_CALLWNDPROC, trayIconEventHandler, instance, GetCurrentThreadId()))
{
std::cout << "Failed to hook tray icon events: " << std::hex << GetLastError() << std::endl;
}
// Set window minimize behavior
glfwSetWindowIconifyCallback(drawData.window, [](GLFWwindow* window, int isIconified) {
if (isIconified && gAppData->settings.docked)
{
glfwHideWindow(window);
}
isHidden = isIconified;
});
glfwSetWindowFocusCallback(drawData.window, [](GLFWwindow* window, int isFocused) {
if (!isFocused && gAppData->settings.docked && !justDocked)
{
glfwIconifyWindow(window);
}
});
// Load settings
initShell(drawData);
initSettings(drawData, appData);
// Set up audio device api
initAudio(appData);
}
@@ -139,7 +87,6 @@ void draw(DrawData& drawData, ApplicationData& appData)
{
justDocked = false;
// Actual Drawing
if (isHidden)
{
glfwWaitEvents();
@@ -152,17 +99,24 @@ void draw(DrawData& drawData, ApplicationData& appData)
// Menu Bar
customYCursor += menuBar(drawData, appData).y;
// Checklist
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.;
// Playback Devices
ImGui::SetNextWindowPos(ImVec2(0, customYCursor));
ImGui::SetNextWindowSize(ImVec2(viewportSize.x, 0));
customYCursor += audioDeviceWindow(appData, appData.audioData->playbackDevices, " \xEE\xB8\x84 Playback").y;
customYCursor += audioDeviceWindow(appData, appData.audioData->playbackDevices, std::format(" {} Playback", ICON_HEADPHONE_FILL).c_str()).y;
customYCursor += 5.;
// Recording devices
ImGui::SetNextWindowPos(ImVec2(0, customYCursor));
ImGui::SetNextWindowSize(ImVec2(viewportSize.x, 0));
customYCursor += audioDeviceWindow(appData, appData.audioData->recordingDevices, " \xEE\xBD\x8F Recording").y;
customYCursor += audioDeviceWindow(appData, appData.audioData->recordingDevices, std::format(" {} Recording", ICON_MIC_FILL).c_str()).y;
// Resize viewport
drawData.window_size.y = customYCursor;
@@ -177,11 +131,7 @@ void draw(DrawData& drawData, ApplicationData& appData)
void cleanup(DrawData& drawData, ApplicationData& appData)
{
// Remove tray icon
NOTIFYICONDATA nid = { sizeof(nid) };
nid.hWnd = drawData.window_handle;
nid.uID = TRAY_ID;
Shell_NotifyIcon(NIM_DELETE, &nid);
cleanupShell(drawData);
}
ImVec2 menuBar(DrawData& drawData, ApplicationData& appData)
@@ -222,13 +172,13 @@ ImVec2 menuBar(DrawData& drawData, ApplicationData& appData)
{
ImVec2 availableSpace = ImGui::GetContentRegionAvail();
ImVec2 cursorPos = ImGui::GetCursorPos();
ImGui::SetCursorPosX(cursorPos.x + availableSpace.x - 33.);
ImGui::SetCursorPosX(cursorPos.x + availableSpace.x - 47.);
if (ImGui::SmallButton("-"))
if (ImGui::SmallButton(ICON_SUBTRACT_FILL))
{
glfwIconifyWindow(drawData.window);
}
if (ImGui::SmallButton("x"))
if (ImGui::SmallButton(ICON_CLOSE_FILL))
{
glfwSetWindowShouldClose(drawData.window, GLFW_TRUE);
}
@@ -266,6 +216,91 @@ bool customButton(const char* id_start, const char* id_end, const char* title, b
return result;
}
void drawCheclistDayLines(ApplicationData& appData, ImDrawList* drawList, float lineHeight, time_t day)
{
auto& tasks = appData.settings.tasks;
size_t totalTasks = appData.settings.taskNames.size();
size_t count = std::count_if(tasks.begin(), tasks.end(), [&](std::pair<std::string, std::vector<time_t>> t) { return std::any_of(t.second.begin(), t.second.end(), [&](time_t tt) { return tt == day; }); });
for (int i = 0; i < count; i++)
{
ImVec2 cursorPos = ImGui::GetCursorScreenPos();
drawList->AddLine({ cursorPos.x, cursorPos.y }, { cursorPos.x, cursorPos.y + lineHeight }, ImColor(.4f, .9f, .3f), 3.f);
ImGui::SetCursorScreenPos({ cursorPos.x + 5.f, cursorPos.y });
}
for (int i = 0; i < max(0, totalTasks - count); i++)
{
ImVec2 cursorPos = ImGui::GetCursorScreenPos();
drawList->AddLine({ cursorPos.x, cursorPos.y }, { cursorPos.x, cursorPos.y + lineHeight }, ImColor(.1f, .3f, .05f), 3.f);
ImGui::SetCursorScreenPos({ cursorPos.x + 5.f, cursorPos.y });
}
}
time_t getDayStartOf(time_t time)
{
tm localTime;
localtime_s(&localTime, &time);
localTime.tm_hour = 0;
localTime.tm_min = 0;
localTime.tm_sec = 0;
return mktime(&localTime);
}
ImVec2 checklistWindow(ApplicationData& appData, const char* title)
{
if (ImGui::Begin(title, 0, ImGuiWindowFlags_NoResize))
{
time_t today = getDayStartOf(std::time(nullptr));
ImDrawList* drawList = ImGui::GetWindowDrawList();
float lineHeight = ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y * 2;
for (int pastDay = 3; pastDay >= 1; pastDay--)
{
time_t date = today - (60 * 60 * 24 * pastDay);
drawCheclistDayLines(appData, drawList, lineHeight, date);
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5.f);
}
ImVec2 cursorPos = ImGui::GetCursorScreenPos();
drawList->AddLine({ cursorPos.x, cursorPos.y - 2.f }, { cursorPos.x, cursorPos.y + lineHeight + 2.f }, IM_COL32_WHITE, 1.f);
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 10.f);
drawCheclistDayLines(appData, drawList, lineHeight, today);
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 10.f);
auto todayMatcher = [&](time_t t) { return getDayStartOf(t) == today; };
for (std::string& taskName : appData.settings.taskNames)
{
if (!appData.settings.tasks.contains(taskName))
{
appData.settings.tasks.insert({ taskName, std::vector<time_t>{} });
}
std::vector<time_t>& taskDates = appData.settings.tasks[taskName];
bool taskDoneToday = std::any_of(taskDates.begin(), taskDates.end(), todayMatcher);
if (ImGui::Checkbox(taskName.c_str(), &taskDoneToday))
{
if (taskDoneToday)
{
taskDates.push_back(today);
}
else
{
std::erase_if(taskDates, todayMatcher);
}
}
ImGui::SameLine();
}
}
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))
@@ -304,8 +339,23 @@ ImVec2 audioDeviceWindow(ApplicationData& appData, std::vector<AudioDevice>& dev
ImGui::TableNextColumn();
if (dev.state == DEVICE_STATE_ACTIVE)
{
// Log scale because it looks better (no actual reason for these exact numbers)
float meterValue = log10f(getMeterValue(dev.meterInterface) * 9. + 1.);
// Mute button
ImGui::PushID(std::string("bn_mute_").append(deviceIdUtf8).c_str());
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
bool isDeviceMuted = isMuted(dev.volumeInterface);
if (ImGui::Button(isDeviceMuted ? ICON_VOLUME_MUTE_FILL : ICON_VOLUME_UP_FILL))
{
setMuted(dev.volumeInterface, !isDeviceMuted);
}
ImGui::PopStyleColor();
ImGui::PopID();
ImGui::SameLine(0, 2);
// Meter
static std::array<float, 2> meterValues{};
UINT channelCount = getMeterValues(dev.meterInterface, meterValues);
auto drawList = ImGui::GetWindowDrawList();
ImVec2 windowPos = ImGui::GetWindowPos();
@@ -315,22 +365,35 @@ ImVec2 audioDeviceWindow(ApplicationData& appData, std::vector<AudioDevice>& dev
const float linePaddingX = 3.;
cursorPos.x += linePaddingX;
// BG line
drawList->AddLine(ImVec2(cursorPos.x, lineY), ImVec2(cursorPos.x + (space.x - 2. * linePaddingX), lineY), IM_COL32(120, 120, 120, 255), 2.);
drawList->AddLine(ImVec2(cursorPos.x, lineY), ImVec2(cursorPos.x + (space.x - 2. * linePaddingX) * meterValue, lineY), IM_COL32(200, 200, 255, 255), 3.);
// Channel lines
if (channelCount == 1)
{
drawList->AddLine(ImVec2(cursorPos.x, lineY), ImVec2(cursorPos.x + (space.x - 2. * linePaddingX) * meterValues[0], lineY), IM_COL32(200, 200, 255, 255), 3.);
}
if (channelCount == 2)
{
drawList->AddLine(ImVec2(cursorPos.x, lineY - 2), ImVec2(cursorPos.x + (space.x - 2. * linePaddingX) * meterValues[0], lineY - 2), IM_COL32(200, 200, 255, 255), 3.);
drawList->AddLine(ImVec2(cursorPos.x, lineY + 2), ImVec2(cursorPos.x + (space.x - 2. * linePaddingX) * meterValues[1], lineY + 2), IM_COL32(200, 200, 255, 255), 3.);
}
ImGui::SetNextItemWidth(space.x);
ImGui::PushID(&dev.id);
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0, 0, 0, 0));
if (isDeviceMuted) ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(.4, .4, .4, 1.));
float volume = getVolume(dev.volumeInterface);
float prevVolume = volume;
ImGui::SetNextItemWidth(space.x);
ImGui::PushID(dev.device);
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0, 0, 0, 0));
ImGui::SliderFloat("", &volume, 0., 1., "", ImGuiSliderFlags_NoRoundToFormat | ImGuiSliderFlags_AlwaysClamp);
ImGui::PopStyleColor();
ImGui::PopID();
if (prevVolume != volume)
if (ImGui::SliderFloat("", &volume, 0., 1., "", ImGuiSliderFlags_NoRoundToFormat | ImGuiSliderFlags_AlwaysClamp))
{
setVolume(dev.volumeInterface, volume);
}
if (isDeviceMuted) ImGui::PopStyleColor();
ImGui::PopStyleColor();
ImGui::PopID();
}
// Defaults
@@ -341,7 +404,7 @@ ImVec2 audioDeviceWindow(ApplicationData& appData, std::vector<AudioDevice>& dev
{
drawCircle(5, IM_COL32(50, 50, 222, 255));
}
if (customButton("bn_d_", deviceIdUtf8.c_str(), "\xEE\xBE\x82", !dev.isDefaultConsole))
if (customButton("bn_d_", deviceIdUtf8.c_str(), ICON_MUSIC_2_FILL, !dev.isDefaultConsole))
{
setDefaultAudioDevice(*appData.audioData, dev.id.c_str(), ERole::eConsole);
}
@@ -351,7 +414,7 @@ ImVec2 audioDeviceWindow(ApplicationData& appData, std::vector<AudioDevice>& dev
{
drawCircle(5, IM_COL32(222, 50, 50, 255));
}
if (customButton("bn_c_", deviceIdUtf8.c_str(), "\xEE\xBF\xA9", !dev.isDefaultCommunication))
if (customButton("bn_c_", deviceIdUtf8.c_str(), ICON_PHONE_FILL, !dev.isDefaultCommunication))
{
setDefaultAudioDevice(*appData.audioData, dev.id.c_str(), ERole::eCommunications);
}
@@ -379,25 +442,3 @@ void drawCircle(float radius, ImU32 color)
ImVec2 windowPos = ImGui::GetWindowPos();
drawList->AddCircleFilled(ImVec2(cursorPos.x + windowPos.x, cursorPos.y + windowPos.y + ImGui::GetTextLineHeight() / 2.), radius, color);
}
LRESULT CALLBACK trayIconEventHandler(int code, WPARAM wParam, LPARAM lParam)
{
if (code >= 0)
{
CWPSTRUCT* data = (CWPSTRUCT*)lParam;
if (data->message == WMAPP_NOTIFYCALLBACK)
{
auto id = HIWORD(data->lParam);
auto trayEvent = LOWORD(data->lParam);
if (id == TRAY_ID && trayEvent == WM_LBUTTONUP)
{
glfwShowWindow(gDrawData->window);
glfwRestoreWindow(gDrawData->window);
}
}
}
return CallNextHookEx(NULL, code, wParam, lParam);
}

View File

@@ -1,16 +1,15 @@
#pragma once
#include <vector>
#include "ImguiBase.h"
#include "ApplicationData.h"
#include <vector>
void init(DrawData& drawData, ApplicationData& customData);
void draw(DrawData& drawData, ApplicationData& customData);
void cleanup(DrawData& drawData, ApplicationData& appData);
ImVec2 menuBar(DrawData& drawData, ApplicationData& appData);
ImVec2 checklistWindow(ApplicationData& appData, const char* title);
ImVec2 audioDeviceWindow(ApplicationData& appData, std::vector<AudioDevice>& deviceList, const char* title);
void drawCircle(float radius, ImU32 color);
LRESULT trayIconEventHandler(int code, WPARAM wParam, LPARAM lParam);

View File

@@ -71,8 +71,8 @@ IDI_ICON1 ICON "kaiju.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,1,1
PRODUCTVERSION 1,0,1,1
FILEVERSION 1,0,4,1
PRODUCTVERSION 1,0,4,1
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -89,12 +89,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "Asuro"
VALUE "FileDescription", "Audio Thingy"
VALUE "FileVersion", "1.0.1.1"
VALUE "FileVersion", "1.0.4.1"
VALUE "InternalName", "AsuroTool.exe"
VALUE "LegalCopyright", "Copyright (C) 2022"
VALUE "OriginalFilename", "AsuroTool.exe"
VALUE "ProductName", "Audio Thingy"
VALUE "ProductVersion", "1.0.1.1"
VALUE "ProductVersion", "1.0.4.1"
END
END
BLOCK "VarFileInfo"
@@ -111,7 +111,7 @@ END
STRINGTABLE
BEGIN
IDS_STRING_TOOLTIP "uwu"
IDS_STRING_TOOLTIP "Audio Thingy"
END
#endif // English (United Kingdom) resources

View File

@@ -93,6 +93,7 @@
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<DisableSpecificWarnings>4305; 4244</DisableSpecificWarnings>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@@ -111,6 +112,7 @@
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<DisableSpecificWarnings>4305; 4244</DisableSpecificWarnings>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@@ -129,6 +131,7 @@
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<DisableSpecificWarnings>4305; 4244</DisableSpecificWarnings>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@@ -147,6 +150,7 @@
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<DisableSpecificWarnings>4305; 4244</DisableSpecificWarnings>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@@ -164,6 +168,7 @@
<ClCompile Include="AudioNotificationListener.cpp" />
<ClCompile Include="Settings.cpp" />
<ClCompile Include="Util.cpp" />
<ClCompile Include="WindowsShell.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="ApplicationData.h" />
@@ -175,6 +180,7 @@
<ClInclude Include="resource.h" />
<ClInclude Include="Settings.h" />
<ClInclude Include="Util.h" />
<ClInclude Include="WindowsShell.h" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ImguiBase\ImguiBase.vcxproj">

View File

@@ -39,6 +39,9 @@
<ClCompile Include="Settings.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="WindowsShell.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="AsuroTool.h">
@@ -65,6 +68,9 @@
<ClInclude Include="PolicyConfig.h">
<Filter>Header Files\Audio</Filter>
</ClInclude>
<ClInclude Include="WindowsShell.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<CopyFileToFolders Include="Montserrat-Regular.ttf">

View File

@@ -1,14 +1,18 @@
#include "AudioApi.h"
#include "Util.h"
#include "PolicyConfig.h"
#include <windows.h>
#include <functiondiscoverykeys.h>
#include <endpointvolume.h>
#include <initguid.h>
#include <mmdeviceapi.h>
#include <array>
#include <vector>
#include <algorithm>
#include "Util.h"
#include "AudioApi.h"
#include "PolicyConfig.h"
#include <iostream>
void initAudio(ApplicationData& appData)
{
@@ -66,57 +70,35 @@ void loadAudioDevices(AudioData& audioData, std::vector<AudioDevice>& deviceList
for (UINT i = 0; i < deviceCount; i += 1)
{
AudioDevice deviceData{};
err = deviceCollection->Item(i, &deviceData.device);
IMMDevice* device;
err = deviceCollection->Item(i, &device);
if (isError(err, std::stringstream("Failed to get device ") << i << ": "))
{
continue;
}
LPWSTR deviceId;
err = deviceData.device->GetId(&deviceId);
isError(err, std::stringstream("Failed to get device id ") << i << ": ");
deviceData.id = std::wstring(deviceId);
IPropertyStore* propertyStore;
err = deviceData.device->OpenPropertyStore(STGM_READ, &propertyStore);
isError(err, std::stringstream("Failed to open device ") << i << "prop store: ");
PROPVARIANT deviceNameProp;
const wchar_t* deviceName;
err = getDevicePropertyString(propertyStore, PKEY_Device_FriendlyName, &deviceNameProp, deviceName);
isError(err, std::stringstream("Failed to read name of device ") << i << ": ");
deviceData.name = utf8Encode(deviceName);
err = deviceData.device->GetState(&deviceData.state);
isError(err, std::stringstream("Failed to reat state of device ") << i << ": ");
err = deviceData.device->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (LPVOID*)&deviceData.volumeInterface);
isError(err, "Failed to get audio endpoint volume interface: ");
err = deviceData.device->Activate(__uuidof(IAudioMeterInformation), CLSCTX_INPROC_SERVER, NULL, (LPVOID*)&deviceData.meterInterface);
isError(err, "Failed to get audio meter interface: ");
if (defaultConsoleId)
err = device->GetId(&deviceId);
if (!isError(err, std::stringstream("Failed to get device id ") << i << ": "))
{
deviceData.isDefaultConsole = wcscmp(defaultConsoleId, deviceId) == 0;
}
if (defaultMediaId)
{
deviceData.isDefaultMedia = wcscmp(defaultMediaId, deviceId) == 0;
}
if (defaultCommunicationId)
{
deviceData.isDefaultCommunication = wcscmp(defaultCommunicationId, deviceId) == 0;
AudioDevice audioDevice(device, deviceId);
if (defaultConsoleId)
{
audioDevice.isDefaultConsole = wcscmp(defaultConsoleId, deviceId) == 0;
}
if (defaultMediaId)
{
audioDevice.isDefaultMedia = wcscmp(defaultMediaId, deviceId) == 0;
}
if (defaultCommunicationId)
{
audioDevice.isDefaultCommunication = wcscmp(defaultCommunicationId, deviceId) == 0;
}
deviceList.push_back(std::move(audioDevice));
}
deviceList.push_back(std::move(deviceData));
if (propertyStore)
{
propertyStore->Release();
}
CoTaskMemFree(deviceId);
}
@@ -178,13 +160,85 @@ void setVolume(IAudioEndpointVolume* volumeInterface, float newVolume)
isError(hr, "Failed to set volume level: ");
}
float getMeterValue(IAudioMeterInformation* meterInterface)
UINT getMeterValues(IAudioMeterInformation* meterInterface, std::array<float, 2>& outLevels)
{
float volume;
if (FAILED(meterInterface->GetPeakValue(&volume)))
{
volume = 0.;
}
UINT channelCount;
if (FAILED(meterInterface->GetMeteringChannelCount(&channelCount)) || channelCount > 2) return 0;
return volume;
if (FAILED(meterInterface->GetChannelsPeakValues(channelCount, &outLevels[0]))) return 0;
return channelCount;
}
void getVolumeLimit(IAudioEndpointVolume* volumeInterface, float* outMin, float* outMax)
{
float dummy;
volumeInterface->GetVolumeRange(outMin, outMax, &dummy);
}
bool isMuted(IAudioEndpointVolume* volumeInterface)
{
BOOL result = false;
if (FAILED(volumeInterface->GetMute(&result))) return false;
return static_cast<bool>(result);
}
void setMuted(IAudioEndpointVolume* volumeInterface, bool newState)
{
volumeInterface->SetMute(static_cast<BOOL>(newState), NULL);
}
void CreateLoudnessEqualizationKey(PROPERTYKEY& key)
{
// Realtek: const wchar_t* guid = L"E0A941A0-88A2-4df5-8D6B-DD20BB06E8FB";
const wchar_t* guid = L"FC52A749-4BE9-4510-896E-966BA6525980";
UuidFromString((RPC_WSTR)guid, &key.fmtid);
key.pid = 3;
}
AudioProcessingState getAudioProcessing(AudioDevice& device)
{
return AudioProcessingState::Unknown;
LPWSTR pwstrEndpointId = NULL;
HRESULT hr = device.device->GetId(&pwstrEndpointId);
IPolicyConfig* pPolicyConfig;
hr = CoCreateInstance(__uuidof(CPolicyConfigClient), NULL, CLSCTX_ALL, IID_PPV_ARGS(&pPolicyConfig));
if (SUCCEEDED(hr))
{
PROPVARIANT var;
PropVariantInit(&var);
PROPERTYKEY key{};
CreateLoudnessEqualizationKey(key);
hr = pPolicyConfig->GetPropertyValue(pwstrEndpointId, TRUE, key, &var);
pPolicyConfig->Release();
return var.boolVal ? AudioProcessingState::Disabled : AudioProcessingState::Enabled;
}
return AudioProcessingState::Unknown;
}
void setAudioProcessing(AudioDevice& device, bool newVal)
{
return;
LPWSTR pwstrEndpointId = NULL;
HRESULT hr = device.device->GetId(&pwstrEndpointId);
IPolicyConfig* pPolicyConfig;
hr = CoCreateInstance(__uuidof(CPolicyConfigClient), NULL, CLSCTX_ALL, IID_PPV_ARGS(&pPolicyConfig));
if (SUCCEEDED(hr))
{
PROPVARIANT var;
PropVariantInit(&var);
var.vt = VT_UI4;
var.uintVal = newVal ? ENDPOINT_SYSFX_ENABLED : ENDPOINT_SYSFX_DISABLED;
PROPERTYKEY key{};
CreateLoudnessEqualizationKey(key);
hr = pPolicyConfig->SetPropertyValue(pwstrEndpointId, TRUE, key, &var);
pPolicyConfig->Release();
}
}

View File

@@ -1,9 +1,14 @@
#pragma once
#include <mmdeviceapi.h>
#include "ApplicationData.h"
enum class AudioProcessingState
{
Enabled,
Disabled,
Unknown
};
void initAudio(ApplicationData& appData);
void loadAudioDevices(AudioData& audioData, std::vector<AudioDevice>& deviceList, EDataFlow deviceType);
void reloadDeviceLists(AudioData& audioData);
@@ -14,4 +19,9 @@ void setDefaultAudioDevice(AudioData& audioData, const wchar_t* deviceId, ERole
float getVolume(IAudioEndpointVolume* volumeInterface);
void setVolume(IAudioEndpointVolume* volumeInterface, float newVolume);
float getMeterValue(IAudioMeterInformation* meterInterface);
UINT getMeterValues(IAudioMeterInformation* meterInterface, std::array<float, 2>& levels);
void getVolumeLimit(IAudioEndpointVolume* volumeInterface, float* outMin, float* outMax);
bool isMuted(IAudioEndpointVolume* volumeInterface);
void setMuted(IAudioEndpointVolume* volumeInterface, bool newState);
AudioProcessingState getAudioProcessing(AudioDevice& device);
void setAudioProcessing(AudioDevice& device, bool newVal);

View File

@@ -34,7 +34,25 @@ HRESULT __stdcall AudioNotificationListener::OnDeviceAdded(LPCWSTR pwstrDeviceId
HRESULT result = audioData->deviceEnumerator->GetDevice(pwstrDeviceId, &newDevice);
if (SUCCEEDED(result) && newDevice != nullptr)
{
// TODO: add to device list
IMMEndpoint* endpoint;
result = newDevice->QueryInterface(&endpoint);
if (SUCCEEDED(result) && endpoint != nullptr)
{
EDataFlow dataFlow;
result = endpoint->GetDataFlow(&dataFlow);
if (SUCCEEDED(result))
{
AudioDevice audioDevice(newDevice, pwstrDeviceId);
if (dataFlow == EDataFlow::eCapture)
{
audioData->recordingDevices.push_back(std::move(audioDevice));
}
else
{
audioData->playbackDevices.push_back(std::move(audioDevice));
}
}
}
}
return result;
@@ -50,7 +68,9 @@ HRESULT __stdcall AudioNotificationListener::OnDeviceRemoved(LPCWSTR pwstrDevice
if (wcscmp(deviceListIterator->id.c_str(), pwstrDeviceId) == 0)
{
deviceListIterator = deviceList.erase(deviceListIterator);
continue;
}
deviceListIterator++;
}
};

View File

@@ -1,8 +1,10 @@
#pragma once
#include <memory>
#include <initguid.h>
#include <mmdeviceapi.h>
#include <memory>
class AudioData;
class ApplicationData;
class AudioNotificationListener : public IMMNotificationClient {

View File

@@ -115,6 +115,9 @@ public:
PCWSTR,
INT
);
virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(PCWSTR pszDeviceName, BOOL bFxStore, const PROPERTYKEY& pKey, PROPVARIANT* pv) = 0;
virtual HRESULT STDMETHODCALLTYPE SetPropertyValue(PCWSTR pszDeviceName, BOOL bFxStore, const PROPERTYKEY& pKey, PROPVARIANT* pv) = 0;
};
interface DECLSPEC_UUID("568b9108-44bf-40b4-9006-86afe5b5a620") IPolicyConfigVista;

View File

@@ -3,6 +3,8 @@
#include "Util.h"
#include "Settings.h"
#include <chrono>
extern bool justDocked;
extern DrawData* gDrawData;
extern ApplicationData* gAppData;
@@ -10,8 +12,8 @@ extern ApplicationData* gAppData;
void initSettings(DrawData& drawData, ApplicationData& appData)
{
ImGuiSettingsHandler ini_handler;
ini_handler.TypeName = "ApplicationSettings";
ini_handler.TypeHash = ImHashStr("ApplicationSettings");
ini_handler.TypeName = APPLICATION_SETTINGS_GROUP;
ini_handler.TypeHash = ImHashStr(APPLICATION_SETTINGS_GROUP);
ini_handler.ReadOpenFn = settingsReadOpen;
ini_handler.ReadLineFn = settingsReadLine;
ini_handler.WriteAllFn = settingsWriteAll;
@@ -29,6 +31,8 @@ void* settingsReadOpen(ImGuiContext* ctx, ImGuiSettingsHandler* handler, const c
void settingsReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler, void* entry, const char* line)
{
if (strlen(line) == 0) return;
ApplicationSettings* settings = (ApplicationSettings*)entry;
int docked;
@@ -36,19 +40,54 @@ void settingsReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler, void* en
{
settings->docked = (bool)docked;
}
int autostart;
if (sscanf_s(line, "autostart=%i", &autostart))
{
settings->autostart = (bool)autostart;
}
std::string taskName{};
taskName.resize(MAX_TASK_NAME_LENGTH);
if (sscanf_s(line, "taskname=%s", &taskName[0], MAX_TASK_NAME_LENGTH))
{
settings->taskNames.push_back(taskName);
}
std::string task{};
task.resize(MAX_TASK_NAME_LENGTH);
time_t dayTimestamp;
if (sscanf_s(line, "task=%lld %s", &dayTimestamp, &task[0], MAX_TASK_NAME_LENGTH))
{
if (settings->tasks.contains(task))
{
settings->tasks[task].push_back(dayTimestamp);
}
else
{
settings->tasks.insert({ task, std::vector{ dayTimestamp } });
}
}
}
void settingsWriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* outBuf)
{
outBuf->appendf("[%s][%s]\n", "ApplicationSettings", "ApplicationSettings");
outBuf->appendf("[%s][%s]\n", APPLICATION_SETTINGS_GROUP, APPLICATION_SETTINGS_GROUP);
outBuf->appendf("docked=%i\n", (int)gAppData->settings.docked);
outBuf->appendf("autostart=%i\n", (int)gAppData->settings.autostart);
outBuf->append("\n");
for (std::string& taskName : gAppData->settings.taskNames)
{
outBuf->appendf("taskname=%s\n", taskName.c_str());
}
for (auto& task : gAppData->settings.tasks)
{
for (auto& date : task.second)
{
outBuf->appendf("task=%lld %s\n", date, task.first.c_str());
}
}
}
void applySettings(DrawData& drawData, ApplicationData& appData)

View File

@@ -1,8 +1,11 @@
#pragma once
#include "ApplicationData.h"
#include "ImguiBase.h"
#include <imgui_internal.h>
#include "ApplicationData.h"
#define APPLICATION_SETTINGS_GROUP "ApplicationSettings"
#define MAX_TASK_NAME_LENGTH 1024
void initSettings(DrawData& drawData, ApplicationData& appData);

View File

@@ -1,6 +1,7 @@
#pragma once
#include <windows.h>
#include <sstream>
bool isError(const HRESULT result, const std::stringstream message);

102
AsuroTool/WindowsShell.cpp Normal file
View File

@@ -0,0 +1,102 @@
#include "WindowsShell.h"
#include "resource.h"
#include "ApplicationData.h"
#include "Util.h"
#include <Windows.h>
#include <shellapi.h>
#include <commctrl.h>
#include <strsafe.h>
#include <iostream>
const UINT TRAY_ID = 420;
const UINT WMAPP_NOTIFYCALLBACK = WM_APP + 1;
extern DrawData* gDrawData;
extern ApplicationData* gAppData;
extern bool justDocked;
extern bool isHidden;
void initShell(DrawData& drawData)
{
// Set window icon
HINSTANCE instance = GetModuleHandle(NULL);
LPWSTR iconId = MAKEINTRESOURCE(IDI_ICON1);
HANDLE iconLarge = LoadImageW(instance, iconId, IMAGE_ICON, 64, 64, 0);
HANDLE iconSmall = LoadImageW(instance, iconId, IMAGE_ICON, 32, 32, 0);
SendMessage(drawData.window_handle, WM_SETICON, ICON_BIG, (LPARAM)iconLarge);
SendMessage(drawData.window_handle, WM_SETICON, ICON_SMALL, (LPARAM)iconSmall);
SendMessage(drawData.window_handle, WM_SETICON, ICON_SMALL2, (LPARAM)iconSmall);
// Set tray icon
NOTIFYICONDATA nid = { sizeof(nid) };
nid.hWnd = drawData.window_handle;
nid.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE | NIF_SHOWTIP;
nid.uID = TRAY_ID;
nid.uCallbackMessage = WMAPP_NOTIFYCALLBACK;
HRESULT iconResult;
iconResult = LoadIconMetric(instance, iconId, LIM_SMALL, &nid.hIcon);
isError(iconResult, "Failed to load tray icon image: ");
iconResult = LoadString(instance, IDS_STRING_TOOLTIP, nid.szTip, ARRAYSIZE(nid.szTip));
isError(iconResult, "Failed to load tray icon text: ");
Shell_NotifyIcon(NIM_ADD, &nid);
nid.uVersion = NOTIFYICON_VERSION_4;
Shell_NotifyIcon(NIM_SETVERSION, &nid);
if (!SetWindowsHookEx(WH_CALLWNDPROC, trayIconEventHandler, instance, GetCurrentThreadId()))
{
std::cout << "Failed to hook tray icon events: " << std::hex << GetLastError() << std::endl;
}
// Set window minimize behavior
glfwSetWindowIconifyCallback(drawData.window, [](GLFWwindow* window, int isIconified) {
if (isIconified && gAppData->settings.docked)
{
glfwHideWindow(window);
}
isHidden = isIconified;
});
glfwSetWindowFocusCallback(drawData.window, [](GLFWwindow* window, int isFocused) {
if (!isFocused && gAppData->settings.docked && !justDocked)
{
glfwIconifyWindow(window);
}
});
}
void cleanupShell(DrawData& drawData)
{
// Remove tray icon
NOTIFYICONDATA nid = { sizeof(nid) };
nid.hWnd = drawData.window_handle;
nid.uID = TRAY_ID;
Shell_NotifyIcon(NIM_DELETE, &nid);
}
LRESULT CALLBACK trayIconEventHandler(int code, WPARAM wParam, LPARAM lParam)
{
if (code >= 0)
{
CWPSTRUCT* data = (CWPSTRUCT*)lParam;
if (data->message == WMAPP_NOTIFYCALLBACK)
{
auto id = HIWORD(data->lParam);
auto trayEvent = LOWORD(data->lParam);
if (id == TRAY_ID && (trayEvent == WM_LBUTTONUP || trayEvent == WM_RBUTTONUP))
{
glfwShowWindow(gDrawData->window);
glfwRestoreWindow(gDrawData->window);
}
}
}
return CallNextHookEx(NULL, code, wParam, lParam);
}

8
AsuroTool/WindowsShell.h Normal file
View File

@@ -0,0 +1,8 @@
#pragma once
#include <ImguiBase.h>
void initShell(DrawData& drawData);
void cleanupShell(DrawData& drawData);
LRESULT trayIconEventHandler(int code, WPARAM wParam, LPARAM lParam);

View File

@@ -9,6 +9,7 @@
#include <GLFW/glfw3.h>
#include "imgui.h"
#include "remixicon.h"
class DrawData {
public:

View File

@@ -202,6 +202,7 @@
<ClInclude Include="imgui\headers\imstb_rectpack.h" />
<ClInclude Include="imgui\headers\imstb_textedit.h" />
<ClInclude Include="imgui\headers\imstb_truetype.h" />
<ClInclude Include="remixicon.h" />
</ItemGroup>
<ItemGroup>
<Font Include="Montserrat-Regular.ttf" />

View File

@@ -74,6 +74,9 @@
<ClInclude Include="ImguiBase.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="remixicon.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Font Include="Montserrat-Regular.ttf">

2273
ImguiBase/remixicon.h Normal file

File diff suppressed because it is too large Load Diff