11 Commits

Author SHA1 Message Date
ebdd35b1e1 1.1 2022-07-22 01:49:30 +02:00
b986bfde39 fix missing tray icon bug 2022-07-22 01:48:26 +02:00
ecfb2f206d 1.0 2022-07-21 17:48:52 +02:00
1ab7fd1129 more cleanup 2022-07-21 17:39:25 +02:00
d5d4521f58 autostart + more cleanup 2022-07-21 17:29:43 +02:00
27be3fb0d4 cleanup 2022-07-21 16:18:54 +02:00
df939555b9 load settings on start 2022-07-21 15:56:44 +02:00
4c73dba8ad docked mode 2022-07-15 19:14:59 +02:00
5c785ac4e7 volume slider 2022-07-15 18:46:44 +02:00
ab2bb6055c tray icon 2022-07-15 17:29:22 +02:00
4f804dc5c3 templates 2022-07-15 05:00:44 +02:00
16 changed files with 505 additions and 117 deletions

View File

@@ -39,12 +39,16 @@ public:
class ApplicationSettings {
public:
bool autostart = false;
bool docked = false;
bool showDisabledDevices = false;
bool fitWindowHeight = true;
};
class ApplicationData {
public:
ApplicationSettings settings = {};
std::shared_ptr<AudioData> audioData = std::make_shared<AudioData>();
ApplicationData(const ApplicationData&) = delete;
ApplicationData& operator=(const ApplicationData&) = delete;
};

View File

@@ -3,26 +3,52 @@
#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")
#endif
// we need commctrl v6 for LoadIconMetric()
// 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>
#include "Util.h"
#include "AudioApi.h"
#include "Settings.h"
#include "resource.h"
#include "AsuroTool.h"
const UINT TRAY_ID = 420;
const UINT WMAPP_NOTIFYCALLBACK = WM_APP + 1;
// Globals for use in callbacks
DrawData* gDrawData;
ApplicationData* gAppData;
bool justDocked = false;
bool isHidden = false;
int main()
{
ApplicationData applicationData{};
ImGuiCallbacks callbacks{};
startImgui(&applicationData, init, draw, "Asuro's Tool", 600, 400);
callbacks.initFunc = std::bind(init, std::placeholders::_1, std::ref(applicationData));
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);
}
void init(DrawData& drawData, void* customData)
void init(DrawData& drawData, ApplicationData& appData)
{
ApplicationData* appData = static_cast<ApplicationData*>(customData);
// sad :(
gDrawData = &drawData;
gAppData = &appData;
// Load text font
ImGuiIO& io = ImGui::GetIO();
@@ -35,67 +61,133 @@ void init(DrawData& drawData, void* customData)
icons_config.PixelSnapH = true;
io.Fonts->AddFontFromFileTTF("remixicon.ttf", 14.0f, &icons_config, icons_ranges);
// Set up audio device api
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));
isError(audioResult, "Failed to set up audio device enumerator: ");
appData->audioData->audioNotificationListener = new AudioNotificationListener(appData->audioData);
audioResult = appData->audioData->deviceEnumerator->RegisterEndpointNotificationCallback(appData->audioData->audioNotificationListener);
isError(audioResult, "Failed to register audio notification listener: ");
reloadDeviceLists(*appData->audioData);
// Set window icon
HINSTANCE instance = GetModuleHandle(NULL);
LPWSTR iconId = MAKEINTRESOURCE(IDI_ICON1);
HANDLE iconLarge = LoadImageW(GetModuleHandle(NULL), iconId, IMAGE_ICON, 64, 64, 0);
HANDLE iconSmall = LoadImageW(GetModuleHandle(NULL), iconId, IMAGE_ICON, 32, 32, 0);
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
initSettings(drawData, appData);
// Set up audio device api
initAudio(appData);
}
void draw(DrawData& drawData, void* customData)
void draw(DrawData& drawData, ApplicationData& appData)
{
ApplicationData* appData = static_cast<ApplicationData*>(customData);
justDocked = false;
// Actual Drawing
if (isHidden)
{
glfwWaitEvents();
return;
}
float customYCursor = 0;
ImVec2 viewportSize = ImGui::GetMainViewport()->Size;
// Menu Bar
customYCursor += menuBar(appData).y;
customYCursor += menuBar(drawData, appData).y;
// 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, " \xEE\xB8\x84 Playback").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, " \xEE\xBD\x8F Recording").y;
// Resize viewport
if (appData->settings.fitWindowHeight)
drawData.window_size.y = customYCursor;
if (appData.settings.docked)
{
drawData.window_size.y = customYCursor;
int monitorX, monitorY, monitorW, monitorH;
glfwGetMonitorWorkarea(glfwGetPrimaryMonitor(), &monitorX, &monitorY, &monitorW, &monitorH);
glfwSetWindowPos(drawData.window, monitorX + monitorW - drawData.window_size.x, monitorY + monitorH - drawData.window_size.y);
}
}
ImVec2 menuBar(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);
}
ImVec2 menuBar(DrawData& drawData, ApplicationData& appData)
{
ImVec2 size{};
bool closeMenu = false;
if (ImGui::BeginMainMenuBar())
{
if (ImGui::BeginMenu("Settings"))
{
ImGui::Checkbox("Show Disabled Devices", &appData->settings.showDisabledDevices);
ImGui::Checkbox("Fit Window Height", &appData->settings.fitWindowHeight);
if (ImGui::Checkbox("Docked", &appData.settings.docked))
{
closeMenu = true;
updateDocked(drawData, appData);
}
ImGui::Checkbox("Show Disabled Devices", &appData.settings.showDisabledDevices);
if (ImGui::Checkbox("Autostart", &appData.settings.autostart))
{
setAutostart(appData.settings.autostart);
}
ImGui::EndMenu();
}
@@ -103,14 +195,36 @@ ImVec2 menuBar(ApplicationData* appData)
{
if (ImGui::Button("Manual Refresh"))
{
reloadDeviceLists(*appData->audioData);
reloadDeviceLists(*appData.audioData);
}
ImGui::EndMenu();
}
if (appData.settings.docked)
{
ImVec2 availableSpace = ImGui::GetContentRegionAvail();
ImVec2 cursorPos = ImGui::GetCursorPos();
ImGui::SetCursorPosX(cursorPos.x + availableSpace.x - 33.);
if (ImGui::SmallButton("-"))
{
glfwIconifyWindow(drawData.window);
}
if (ImGui::SmallButton("x"))
{
glfwSetWindowShouldClose(drawData.window, GLFW_TRUE);
}
}
size = ImGui::GetWindowSize();
ImGui::EndMainMenuBar();
}
if (closeMenu)
{
ImGui::SetWindowFocus();
}
return size;
}
@@ -134,7 +248,7 @@ bool customButton(const char* id_start, const char* id_end, const char* title, b
return result;
}
ImVec2 audioDeviceWindow(ApplicationData* appData, std::vector<AudioDevice>& deviceList, const char* title)
ImVec2 audioDeviceWindow(ApplicationData& appData, std::vector<AudioDevice>& deviceList, const char* title)
{
if (ImGui::Begin(title, 0, ImGuiWindowFlags_NoResize))
{
@@ -153,7 +267,7 @@ ImVec2 audioDeviceWindow(ApplicationData* appData, std::vector<AudioDevice>& dev
{
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1., 1., 1., 1.));
}
else if (!appData->settings.showDisabledDevices)
else if (!appData.settings.showDisabledDevices)
{
continue;
}
@@ -172,18 +286,33 @@ ImVec2 audioDeviceWindow(ApplicationData* appData, std::vector<AudioDevice>& dev
ImGui::TableNextColumn();
if (dev.state == DEVICE_STATE_ACTIVE)
{
float volume = log10f(getMeterValue(dev.meterInterface) * 9. + 1.);
// Log scale because it looks better (no actual reason for these exact numbers)
float meterValue = log10f(getMeterValue(dev.meterInterface) * 9. + 1.);
auto drawList = ImGui::GetWindowDrawList();
ImVec2 windowPos = ImGui::GetWindowPos();
ImVec2 cursorPos = ImGui::GetCursorScreenPos();
ImVec2 space = ImGui::GetContentRegionAvail();
float lineY = cursorPos.y + ImGui::GetTextLineHeight() / 2.;
float lineY = cursorPos.y + ImGui::GetTextLineHeight() / 2. + 2.;
drawList->AddLine(ImVec2(cursorPos.x, lineY), ImVec2(cursorPos.x + space.x, lineY), IM_COL32(120, 120, 120, 255), 2.);
drawList->AddLine(ImVec2(cursorPos.x, lineY), ImVec2(cursorPos.x + space.x * volume, lineY), IM_COL32(200, 200, 255, 255), 3.);
const float linePaddingX = 3.;
cursorPos.x += linePaddingX;
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.);
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)
{
setVolume(dev.volumeInterface, volume);
}
}
// Defaults
@@ -196,7 +325,7 @@ ImVec2 audioDeviceWindow(ApplicationData* appData, std::vector<AudioDevice>& dev
}
if (customButton("bn_d_", deviceIdUtf8.c_str(), "\xEE\xBE\x82", !dev.isDefaultConsole))
{
setDefaultAudioDevice(*appData->audioData, dev.id.c_str(), ERole::eConsole);
setDefaultAudioDevice(*appData.audioData, dev.id.c_str(), ERole::eConsole);
}
ImGui::SameLine();
@@ -206,7 +335,7 @@ ImVec2 audioDeviceWindow(ApplicationData* appData, std::vector<AudioDevice>& dev
}
if (customButton("bn_c_", deviceIdUtf8.c_str(), "\xEE\xBF\xA9", !dev.isDefaultCommunication))
{
setDefaultAudioDevice(*appData->audioData, dev.id.c_str(), ERole::eCommunications);
setDefaultAudioDevice(*appData.audioData, dev.id.c_str(), ERole::eCommunications);
}
}
@@ -232,3 +361,25 @@ 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

@@ -5,8 +5,13 @@
#include "ImguiBase.h"
#include "ApplicationData.h"
void init(DrawData& drawData, void* customData);
void draw(DrawData& drawData, void* customData);
ImVec2 menuBar(ApplicationData* appData);
ImVec2 audioDeviceWindow(ApplicationData* appData, std::vector<AudioDevice>& deviceList, const char* title);
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 audioDeviceWindow(ApplicationData& appData, std::vector<AudioDevice>& deviceList, const char* title);
void drawCircle(float radius, ImU32 color);
void GenerateTrayUUID(UUID* uuid);
LRESULT trayIconEventHandler(int code, WPARAM wParam, LPARAM lParam);

View File

@@ -64,6 +64,56 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK
// remains consistent on all systems.
IDI_ICON1 ICON "kaiju.ico"
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,1,1
PRODUCTVERSION 1,0,1,1
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "200004b0"
BEGIN
VALUE "CompanyName", "Asuro"
VALUE "FileDescription", "Audio Thingy"
VALUE "FileVersion", "1.0.1.1"
VALUE "InternalName", "AsuroTool.exe"
VALUE "LegalCopyright", "Copyright (C) 2022"
VALUE "OriginalFilename", "AsuroTool.exe"
VALUE "ProductName", "Audio Thingy"
VALUE "ProductVersion", "1.0.1.1"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x2000, 1200
END
END
/////////////////////////////////////////////////////////////////////////////
//
// String Table
//
STRINGTABLE
BEGIN
IDS_STRING_TOOLTIP "uwu"
END
#endif // English (United Kingdom) resources
/////////////////////////////////////////////////////////////////////////////

View File

@@ -92,6 +92,7 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<DisableSpecificWarnings>4305; 4244</DisableSpecificWarnings>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@@ -106,6 +107,7 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<DisableSpecificWarnings>4305; 4244</DisableSpecificWarnings>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@@ -120,6 +122,7 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<DisableSpecificWarnings>4305; 4244</DisableSpecificWarnings>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@@ -134,6 +137,7 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<DisableSpecificWarnings>4305; 4244</DisableSpecificWarnings>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@@ -146,6 +150,7 @@
<ClCompile Include="ApplicationData.cpp" />
<ClCompile Include="AsuroTool.cpp" />
<ClCompile Include="AudioNotificationListener.cpp" />
<ClCompile Include="Settings.cpp" />
<ClCompile Include="Util.cpp" />
</ItemGroup>
<ItemGroup>
@@ -156,6 +161,7 @@
<ClInclude Include="AudioNotificationListener.h" />
<ClInclude Include="PolicyConfig.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="Settings.h" />
<ClInclude Include="Util.h" />
</ItemGroup>
<ItemGroup>

View File

@@ -36,6 +36,9 @@
<ClCompile Include="AudioNotificationListener.cpp">
<Filter>Source Files\Audio</Filter>
</ClCompile>
<ClCompile Include="Settings.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="AsuroTool.h">
@@ -44,9 +47,6 @@
<ClInclude Include="Util.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="PolicyConfig.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ApplicationData.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -59,6 +59,12 @@
<ClInclude Include="AudioNotificationListener.h">
<Filter>Header Files\Audio</Filter>
</ClInclude>
<ClInclude Include="Settings.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="PolicyConfig.h">
<Filter>Header Files\Audio</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<CopyFileToFolders Include="Montserrat-Regular.ttf">

View File

@@ -10,31 +10,20 @@
#include "AudioApi.h"
#include "PolicyConfig.h"
HRESULT getDeviceProperty(IPropertyStore* propertyStore, const PROPERTYKEY propertyKey, PROPVARIANT* outData)
void initAudio(ApplicationData& appData)
{
PropVariantInit(outData);
HRESULT nameResult = propertyStore->GetValue(propertyKey, outData);
return nameResult;
}
HRESULT audioResult;
audioResult = CoInitializeEx(NULL, COINIT_MULTITHREADED);
isError(audioResult, "Failed to initialize COM: ");
HRESULT getDevicePropertyString(IPropertyStore* propertyStore, const PROPERTYKEY propertyKey, PROPVARIANT* outData, const wchar_t*& outString, const wchar_t* defaultStr)
{
HRESULT result = getDeviceProperty(propertyStore, propertyKey, outData);
outString = outData->vt != VT_LPWSTR ? defaultStr : outData->pwszVal;
return result;
}
audioResult = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&appData.audioData->deviceEnumerator));
isError(audioResult, "Failed to set up audio device enumerator: ");
void setDefaultAudioDevice(AudioData& audioData, const wchar_t* deviceId, ERole role)
{
IPolicyConfigVista* pPolicyConfig;
appData.audioData->audioNotificationListener = new AudioNotificationListener(appData.audioData);
audioResult = appData.audioData->deviceEnumerator->RegisterEndpointNotificationCallback(appData.audioData->audioNotificationListener);
isError(audioResult, "Failed to register audio notification listener: ");
HRESULT hr = CoCreateInstance(__uuidof(CPolicyConfigVistaClient), NULL, CLSCTX_ALL, __uuidof(IPolicyConfigVista), (LPVOID*)&pPolicyConfig);
if (!isError(hr, "Failed to set default audio device: "))
{
hr = pPolicyConfig->SetDefaultEndpoint(deviceId, role);
pPolicyConfig->Release();
reloadDeviceLists(audioData);
}
reloadDeviceLists(*appData.audioData);
}
void loadAudioDevices(AudioData& audioData, std::vector<AudioDevice>& deviceList, EDataFlow deviceType)
@@ -42,7 +31,7 @@ void loadAudioDevices(AudioData& audioData, std::vector<AudioDevice>& deviceList
deviceList.clear();
HRESULT err;
IMMDeviceCollection* deviceCollection = NULL;
IMMDeviceCollection* deviceCollection = nullptr;
err = audioData.deviceEnumerator->EnumAudioEndpoints(deviceType, DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED, &deviceCollection);
if (isError(err, "Failed to enumerate audio devices: ")) return;
@@ -51,7 +40,7 @@ void loadAudioDevices(AudioData& audioData, std::vector<AudioDevice>& deviceList
err = deviceCollection->GetCount(&deviceCount);
if (isError(err, "Failed to count audio devices: ")) return;
IMMDevice* defaultConsoleDevice = NULL;
IMMDevice* defaultConsoleDevice = nullptr;
LPWSTR defaultConsoleId = nullptr;
err = audioData.deviceEnumerator->GetDefaultAudioEndpoint(deviceType, ERole::eConsole, &defaultConsoleDevice);
if (!FAILED(err))
@@ -59,7 +48,7 @@ void loadAudioDevices(AudioData& audioData, std::vector<AudioDevice>& deviceList
defaultConsoleDevice->GetId(&defaultConsoleId);
}
IMMDevice* defaultMediaOutput = NULL;
IMMDevice* defaultMediaOutput = nullptr;
LPWSTR defaultMediaId = nullptr;
err = audioData.deviceEnumerator->GetDefaultAudioEndpoint(deviceType, ERole::eMultimedia, &defaultMediaOutput);
if (!FAILED(err))
@@ -67,7 +56,7 @@ void loadAudioDevices(AudioData& audioData, std::vector<AudioDevice>& deviceList
defaultMediaOutput->GetId(&defaultMediaId);
}
IMMDevice* defaultCommunicationOutput = NULL;
IMMDevice* defaultCommunicationOutput = nullptr;
LPWSTR defaultCommunicationId = nullptr;
err = audioData.deviceEnumerator->GetDefaultAudioEndpoint(deviceType, ERole::eCommunications, &defaultCommunicationOutput);
if (!FAILED(err))
@@ -106,7 +95,6 @@ void loadAudioDevices(AudioData& audioData, std::vector<AudioDevice>& deviceList
err = deviceData.device->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (LPVOID*)&deviceData.volumeInterface);
isError(err, "Failed to get audio endpoint volume interface: ");
IAudioMeterInformation* meterInterface;
err = deviceData.device->Activate(__uuidof(IAudioMeterInformation), CLSCTX_INPROC_SERVER, NULL, (LPVOID*)&deviceData.meterInterface);
isError(err, "Failed to get audio meter interface: ");
@@ -146,10 +134,37 @@ void reloadDeviceLists(AudioData& audioData)
loadAudioDevices(audioData, audioData.recordingDevices, EDataFlow::eCapture);
}
HRESULT getDeviceProperty(IPropertyStore* propertyStore, const PROPERTYKEY propertyKey, PROPVARIANT* outData)
{
PropVariantInit(outData);
HRESULT nameResult = propertyStore->GetValue(propertyKey, outData);
return nameResult;
}
HRESULT getDevicePropertyString(IPropertyStore* propertyStore, const PROPERTYKEY propertyKey, PROPVARIANT* outData, const wchar_t*& outString, const wchar_t* defaultStr)
{
HRESULT result = getDeviceProperty(propertyStore, propertyKey, outData);
outString = outData->vt != VT_LPWSTR ? defaultStr : outData->pwszVal;
return result;
}
void setDefaultAudioDevice(AudioData& audioData, const wchar_t* deviceId, ERole role)
{
IPolicyConfigVista* pPolicyConfig = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(CPolicyConfigVistaClient), NULL, CLSCTX_ALL, __uuidof(IPolicyConfigVista), (LPVOID*)&pPolicyConfig);
if (!isError(hr, "Failed to set default audio device: "))
{
hr = pPolicyConfig->SetDefaultEndpoint(deviceId, role);
pPolicyConfig->Release();
reloadDeviceLists(audioData);
}
}
float getVolume(IAudioEndpointVolume* volumeInterface)
{
float volume;
if (FAILED(volumeInterface->GetChannelVolumeLevel(0, &volume)))
if (FAILED(volumeInterface->GetMasterVolumeLevelScalar(&volume)))
{
volume = 0.;
}
@@ -157,6 +172,12 @@ float getVolume(IAudioEndpointVolume* volumeInterface)
return volume;
}
void setVolume(IAudioEndpointVolume* volumeInterface, float newVolume)
{
HRESULT hr = volumeInterface->SetMasterVolumeLevelScalar(newVolume, NULL);
isError(hr, "Failed to set volume level: ");
}
float getMeterValue(IAudioMeterInformation* meterInterface)
{
float volume;

View File

@@ -4,10 +4,14 @@
#include "ApplicationData.h"
void initAudio(ApplicationData& appData);
void loadAudioDevices(AudioData& audioData, std::vector<AudioDevice>& deviceList, EDataFlow deviceType);
void reloadDeviceLists(AudioData& audioData);
HRESULT getDeviceProperty(IPropertyStore* propertyStore, const PROPERTYKEY propertyKey, PROPVARIANT* outData);
HRESULT getDevicePropertyString(IPropertyStore* propertyStore, const PROPERTYKEY propertyKey, PROPVARIANT* outData, const wchar_t*& outString, const wchar_t* defaultStr = L"Unknown");
void setDefaultAudioDevice(AudioData& audioData, const wchar_t* deviceId, ERole role);
void loadAudioDevices(AudioData& audioData, std::vector<AudioDevice>& deviceList, EDataFlow deviceType);
void reloadDeviceLists(AudioData& audioData);
float getVolume(IAudioEndpointVolume* volumeInterface);
void setVolume(IAudioEndpointVolume* volumeInterface, float newVolume);
float getMeterValue(IAudioMeterInformation* meterInterface);

View File

@@ -126,7 +126,7 @@ HRESULT __stdcall AudioNotificationListener::QueryInterface(REFIID riid, void**
}
else
{
*ppvObject = NULL;
*ppvObject = nullptr;
return E_NOINTERFACE;
}

97
AsuroTool/Settings.cpp Normal file
View File

@@ -0,0 +1,97 @@
#include "ApplicationData.h"
#include "ImguiBase.h"
#include "Util.h"
#include "Settings.h"
extern bool justDocked;
extern DrawData* gDrawData;
extern ApplicationData* gAppData;
void initSettings(DrawData& drawData, ApplicationData& appData)
{
ImGuiSettingsHandler ini_handler;
ini_handler.TypeName = "ApplicationSettings";
ini_handler.TypeHash = ImHashStr("ApplicationSettings");
ini_handler.ReadOpenFn = settingsReadOpen;
ini_handler.ReadLineFn = settingsReadLine;
ini_handler.WriteAllFn = settingsWriteAll;
GImGui->SettingsHandlers.push_back(ini_handler);
ImGui::LoadIniSettingsFromDisk("imgui.ini");
applySettings(drawData, appData);
}
void* settingsReadOpen(ImGuiContext* ctx, ImGuiSettingsHandler* handler, const char* name)
{
ApplicationSettings* settings = &gAppData->settings;
*settings = ApplicationSettings();
return (void*)settings;
}
void settingsReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler, void* entry, const char* line)
{
ApplicationSettings* settings = (ApplicationSettings*)entry;
int docked;
if (sscanf_s(line, "docked=%i", &docked))
{
settings->docked = (bool)docked;
}
int autostart;
if (sscanf_s(line, "autostart=%i", &autostart))
{
settings->autostart = (bool)autostart;
}
}
void settingsWriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* outBuf)
{
outBuf->appendf("[%s][%s]\n", "ApplicationSettings", "ApplicationSettings");
outBuf->appendf("docked=%i\n", (int)gAppData->settings.docked);
outBuf->appendf("autostart=%i\n", (int)gAppData->settings.autostart);
outBuf->append("\n");
}
void applySettings(DrawData& drawData, ApplicationData& appData)
{
updateDocked(drawData, appData);
setAutostart(appData.settings.autostart);
}
void updateDocked(DrawData& drawData, ApplicationData& appData)
{
justDocked = true;
glfwSetWindowAttrib(drawData.window, GLFW_DECORATED, !appData.settings.docked);
ShowWindow(drawData.window_handle, SW_HIDE);
SetWindowLongPtr(drawData.window_handle, GWL_EXSTYLE, appData.settings.docked ? WS_EX_TOOLWINDOW : 0);
ShowWindow(drawData.window_handle, SW_SHOW);
}
void setAutostart(bool newValue)
{
const size_t MAX_PATH_LENGTH = 2048;
const wchar_t* KEY_APP_NAME = L"AsuroTool";
HRESULT hr;
HKEY runKey = nullptr;
hr = RegCreateKey(HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", &runKey);
if (isError(hr, "Failed to find/create autostart run key: ")) return;
if (newValue)
{
std::wstring appPath;
appPath.resize(MAX_PATH_LENGTH);
hr = GetModuleFileName(NULL, &appPath[0], static_cast<DWORD>(appPath.size()));
if (isError(hr, "Failed to get executable name: ")) return;
appPath.resize(wcslen(appPath.data()));
hr = RegSetValueEx(runKey, KEY_APP_NAME, 0, REG_SZ, (BYTE*)appPath.c_str(), (appPath.size() + 1) * sizeof(wchar_t));
if (isError(hr, "Failed to write autostart key: ")) return;
}
else
{
hr = RegDeleteValue(runKey, KEY_APP_NAME);
if (isError(hr, "Failed to delete autostart key: ")) return;
}
}

15
AsuroTool/Settings.h Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
#include "ImguiBase.h"
#include <imgui_internal.h>
#include "ApplicationData.h"
void initSettings(DrawData& drawData, ApplicationData& appData);
void* settingsReadOpen(ImGuiContext* ctx, ImGuiSettingsHandler* handler, const char* name);
void settingsReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler, void* entry, const char* line);
void settingsWriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* outBuf);
void applySettings(DrawData& drawData, ApplicationData& appData);
void updateDocked(DrawData& drawData, ApplicationData& appData);
void setAutostart(bool newValue);

View File

@@ -3,12 +3,14 @@
// Used by AsuroTool.rc
//
#define IDI_ICON1 109
#define IDS_STRING112 112
#define IDS_STRING_TOOLTIP 112
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 112
#define _APS_NEXT_RESOURCE_VALUE 113
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101

View File

@@ -1,12 +1,10 @@
#include "imgui.h"
#include "ImguiBase.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_vulkan.h"
#include "ImguiBase.h"
#include <iostream>
#define GLFW_INCLUDE_NONE
#define GLFW_INCLUDE_VULKAN
#define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3.h>
#include <GLFW/glfw3native.h>
#include <vulkan/vulkan.h>
@@ -331,7 +329,7 @@ static void glfw_error_callback(int error, const char* description)
fprintf(stderr, "Glfw Error %d: %s\n", error, description);
}
int startImgui(void* customState, void (*const initFunc)(DrawData&, void*), void (*const drawFunc)(DrawData&, void*), const char* title, int windowWidth, int windowHeight)
int startImgui(ImGuiCallbacks& callbacks, const char* title, int windowWidth, int windowHeight)
{
// Setup GLFW window
glfwSetErrorCallback(glfw_error_callback);
@@ -339,6 +337,7 @@ int startImgui(void* customState, void (*const initFunc)(DrawData&, void*), void
return 1;
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
GLFWwindow* window = glfwCreateWindow(windowWidth, windowHeight, title, NULL, NULL);
// Setup Vulkan
@@ -365,6 +364,7 @@ int startImgui(void* customState, void (*const initFunc)(DrawData&, void*), void
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
//io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
//io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
@@ -408,10 +408,15 @@ int startImgui(void* customState, void (*const initFunc)(DrawData&, void*), void
// Our state
DrawData drawData{};
drawData.window = window;
drawData.window_handle = glfwGetWin32Window(window);
drawData.window_size = getWindowSize(window);
initFunc(drawData, customState);
if (callbacks.initFunc)
{
callbacks.initFunc(drawData);
}
glfwShowWindow(window);
// Upload Fonts
{
@@ -475,7 +480,10 @@ int startImgui(void* customState, void (*const initFunc)(DrawData&, void*), void
ImVec2 prevWindowSize = getWindowSize(window);
drawData.window_size = prevWindowSize;
drawFunc(drawData, customState);
if (callbacks.drawFunc)
{
callbacks.drawFunc(drawData);
}
if (drawData.window_size.x != prevWindowSize.x || drawData.window_size.y != prevWindowSize.y)
{
@@ -498,6 +506,10 @@ int startImgui(void* customState, void (*const initFunc)(DrawData&, void*), void
}
// Cleanup
if (callbacks.cleanupFunc)
{
callbacks.cleanupFunc(drawData);
}
err = vkDeviceWaitIdle(g_Device);
check_vk_result(err);
ImGui_ImplVulkan_Shutdown();

View File

@@ -1,16 +1,30 @@
#pragma once
#include <Windows.h>
#include "imgui_impl_glfw.h"
#include "imgui/headers/imgui.h"
#include <functional>
#define GLFW_INCLUDE_NONE
#define GLFW_INCLUDE_VULKAN
#define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3.h>
#include "imgui.h"
class DrawData {
public:
HWND window_handle = nullptr;
ImVec4 clear_color = {};
ImVec2 window_size = {};
GLFWwindow* window = nullptr;
HWND window_handle = nullptr;
ImVec4 clear_color = {};
ImVec2 window_size = {};
};
int startImgui(void* customState, void (*const initFunc)(DrawData&, void*), void (* const drawFunc)(DrawData&, void*), const char* title, int windowWidth, int windowHeight);
class ImGuiCallbacks {
public:
std::function<void(DrawData&)> initFunc = nullptr;
std::function<void(DrawData&)> drawFunc = nullptr;
std::function<void(DrawData&)> cleanupFunc = nullptr;
};
int startImgui(ImGuiCallbacks& callbacks, const char* title, int windowWidth, int windowHeight);
ImVec2 getWindowSize(GLFWwindow* window);

View File

@@ -11,14 +11,17 @@
int main()
{
ApplicationData applicationData{};
ImGuiCallbacks callbacks{};
startImgui(&applicationData, init, draw, "Node Test", 1280, 720);
callbacks.initFunc = std::bind(init, std::placeholders::_1, applicationData);
callbacks.drawFunc = std::bind(draw, std::placeholders::_1, applicationData);
startImgui(callbacks, "Node Test", 1280, 720);
}
/// <summary>
/// Setup before first draw.
/// </summary>
void init(DrawData& drawData, void* customData)
void init(DrawData& drawData, ApplicationData& customData)
{
ImGuiIO& io = ImGui::GetIO();
io.Fonts->AddFontFromFileTTF("Montserrat-Regular.ttf", 16.0f);
@@ -27,17 +30,15 @@ void init(DrawData& drawData, void* customData)
/// <summary>
/// Draw main application content.
/// </summary>
void draw(DrawData& drawData, void* customData)
void draw(DrawData& drawData, ApplicationData& applicationData)
{
ApplicationData* applicationData = static_cast<ApplicationData*>(customData);
// Top menu bar
ImGui::BeginMainMenuBar();
if (ImGui::Button("Add Node"))
{
std::string name{ "Node " };
name.append(std::to_string(applicationData->nodes.size() + 1));
applicationData->nodes.push_back(NodeWindow(name));
name.append(std::to_string(applicationData.nodes.size() + 1));
applicationData.nodes.push_back(NodeWindow(name));
}
ImGui::EndMainMenuBar();
@@ -45,8 +46,8 @@ void draw(DrawData& drawData, void* customData)
bool isDraggingLine = false;
ImVec2 lineStartPos = ImVec2{};
auto nodeIterator = applicationData->nodes.begin();
while (nodeIterator != applicationData->nodes.end())
auto nodeIterator = applicationData.nodes.begin();
while (nodeIterator != applicationData.nodes.end())
{
NodeWindow& node = *nodeIterator;
@@ -56,7 +57,7 @@ void draw(DrawData& drawData, void* customData)
if (!nodeOpen)
{
cleanEreaseNodeElements(*nodeIterator, applicationData);
nodeIterator = applicationData->nodes.erase(nodeIterator);
nodeIterator = applicationData.nodes.erase(nodeIterator);
ImGui::End();
break; // TODO: contine creates bug with closing too many windows???
}
@@ -112,7 +113,7 @@ void draw(DrawData& drawData, void* customData)
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("NODE_CONNECTION"))
{
ConnectionPayload* payloadData = static_cast<ConnectionPayload*>(payload->Data);
applicationData->addConnection(payloadData->sourceID, triggerIterator->id);
applicationData.addConnection(payloadData->sourceID, triggerIterator->id);
}
ImGui::EndDragDropTarget();
}
@@ -153,11 +154,11 @@ void draw(DrawData& drawData, void* customData)
// Draw connected lines in background
ImDrawList* bgDrawList = ImGui::GetBackgroundDrawList();
for (NodeConnection& nodeConnection : applicationData->connections)
for (NodeConnection& nodeConnection : applicationData.connections)
{
ImVec2 sourcePos = ImVec2{ 0, 0 };
ImVec2 targetPos = ImVec2{ 0, 0 };
for (auto node : applicationData->nodes)
for (auto node : applicationData.nodes)
{
for (auto alert : node.alerts)
{
@@ -237,14 +238,14 @@ bool invisibleInlineButton(const char* title, const char* id)
/// Removes any attached connections before erasing this NodeElement from a vector.
/// </summary>
/// <returns>Continued iterator</returns>
std::vector<NodeElement>::iterator cleanEraseElement(std::vector<NodeElement>& elements, std::vector<NodeElement>::iterator toErase, ApplicationData* data)
std::vector<NodeElement>::iterator cleanEraseElement(std::vector<NodeElement>& elements, std::vector<NodeElement>::iterator toErase, ApplicationData& data)
{
auto connectionIterator = data->connections.begin();
while (connectionIterator != data->connections.end())
auto connectionIterator = data.connections.begin();
while (connectionIterator != data.connections.end())
{
if (connectionIterator->sourceID == toErase->id || connectionIterator->targetID == toErase->id)
{
connectionIterator = data->connections.erase(connectionIterator);
connectionIterator = data.connections.erase(connectionIterator);
continue;
}
connectionIterator++;
@@ -252,7 +253,7 @@ std::vector<NodeElement>::iterator cleanEraseElement(std::vector<NodeElement>& e
return elements.erase(toErase);
}
void cleanEreaseNodeElements(NodeWindow& node, ApplicationData* data)
void cleanEreaseNodeElements(NodeWindow& node, ApplicationData& data)
{
auto triggerIterator = node.triggers.begin();
while (triggerIterator != node.triggers.end())

View File

@@ -46,12 +46,12 @@ enum class CircleDragType
Target
};
void init(DrawData& drawData, void* customData);
void draw(DrawData& drawData, void* customDataVoid);
void init(DrawData& drawData, ApplicationData& customData);
void draw(DrawData& drawData, ApplicationData& customDataVoid);
void invisibleDragArea(const void* id, ImVec2& size);
void drawCircle(ImVec2* outCircleCenter);
bool inlineButton(const char* title, const void* id);
bool invisibleInlineButton(const char* title, const char* id);
std::vector<NodeElement>::iterator cleanEraseElement(std::vector<NodeElement>& vector, std::vector<NodeElement>::iterator toErase, ApplicationData* data);
void cleanEreaseNodeElements(NodeWindow& node, ApplicationData* data);
std::vector<NodeElement>::iterator cleanEraseElement(std::vector<NodeElement>& vector, std::vector<NodeElement>::iterator toErase, ApplicationData& data);
void cleanEreaseNodeElements(NodeWindow& node, ApplicationData& data);