18 Commits

Author SHA1 Message Date
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
3641ffdad9 Set working dir on startup 2022-07-24 02:07:35 +02:00
7fa68792fb Use exe path to search for fonts 2022-07-22 21:48:04 +02:00
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
be949ea8a3 cleanup 2022-07-15 04:32:36 +02:00
b621d1266c live update 2022-07-15 02:34:34 +02:00
22 changed files with 994 additions and 214 deletions

View File

@@ -1,9 +1,118 @@
#include "ApplicationData.h"
#include "Util.h"
#include "AudioApi.h"
#include <functiondiscoverykeys.h>
AudioDevice::AudioDevice(IMMDevice* device, LPCWSTR deviceId)
{
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: ");
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)
{
other.device = nullptr;
other.volumeInterface = nullptr;
other.meterInterface = nullptr;
}
AudioDevice& AudioDevice::operator=(AudioDevice&& other) noexcept
{
this->device = other.device;
this->volumeInterface = other.volumeInterface;
this->meterInterface = other.meterInterface;
this->id = other.id;
this->name = other.name;
this->state = other.state;
this->isDefaultConsole = other.isDefaultConsole;
this->isDefaultMedia = other.isDefaultMedia;
this->isDefaultCommunication = other.isDefaultCommunication;
other.device = nullptr;
other.volumeInterface = nullptr;
other.meterInterface = nullptr;
return *this;
}
AudioDevice::~AudioDevice()
{
if (volumeInterface)
{
volumeInterface->Release();
volumeInterface = nullptr;
}
if (meterInterface)
{
meterInterface->Release();
meterInterface = nullptr;
}
if (device)
{
//device->Release();
device->Release();
device = nullptr;
}
}
AudioData::AudioData()
{
}
AudioData::AudioData(AudioData&& other) noexcept
: playbackDevices(std::move(other.playbackDevices)), recordingDevices(std::move(other.recordingDevices)),
deviceEnumerator(other.deviceEnumerator), audioNotificationListener(other.audioNotificationListener)
{
other.deviceEnumerator = nullptr;
other.audioNotificationListener = nullptr;
}
AudioData& AudioData::operator=(AudioData&& other) noexcept
{
playbackDevices = std::move(other.playbackDevices);
recordingDevices = std::move(other.recordingDevices);
deviceEnumerator = other.deviceEnumerator;
audioNotificationListener = other.audioNotificationListener;
other.deviceEnumerator = nullptr;
other.audioNotificationListener = nullptr;
return *this;
}
AudioData::~AudioData()
{
if (deviceEnumerator)
{
deviceEnumerator->Release();
deviceEnumerator = nullptr;
}
if (audioNotificationListener)
{
audioNotificationListener->Release();
audioNotificationListener = nullptr;
}
}

View File

@@ -4,6 +4,7 @@
#include <endpointvolume.h>
#include <string>
#include <vector>
#include "AudioNotificationListener.h"
class AudioDevice {
public:
@@ -17,18 +18,37 @@ public:
bool isDefaultMedia = {};
bool isDefaultCommunication = {};
AudioDevice(IMMDevice* device, LPCWSTR deviceId);
AudioDevice(AudioDevice&& other) noexcept;
AudioDevice& operator=(AudioDevice&& other) noexcept;
~AudioDevice();
};
class AudioData {
public:
std::vector<AudioDevice> playbackDevices = {};
std::vector<AudioDevice> recordingDevices = {};
IMMDeviceEnumerator* deviceEnumerator = nullptr;
AudioNotificationListener* audioNotificationListener = nullptr;
AudioData();
AudioData(AudioData&& other) noexcept;
AudioData& operator=(AudioData&& other) noexcept;
~AudioData();
};
class ApplicationSettings {
public:
bool autostart = false;
bool docked = false;
bool showDisabledDevices = false;
bool fitWindowHeight = true;
};
class ApplicationData {
public:
ApplicationSettings settings = {};
std::vector<AudioDevice> playbackDevices = {};
std::vector<AudioDevice> recordingDevices = {};
std::shared_ptr<AudioData> audioData = std::make_shared<AudioData>();
ApplicationData(const ApplicationData&) = delete;
ApplicationData& operator=(const ApplicationData&) = delete;
};

View File

@@ -3,87 +3,209 @@
#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 size_t MAX_FONT_PATH_LENGTH = 2048;
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{};
std::wstring appDir;
getAppDir(appDir);
if (_wchdir(appDir.c_str()) != 0)
{
std::cout << "Failed to set working dir." << std::endl;
}
startImgui(&applicationData, init, draw, "Asuro's Tool", 600, 400);
ApplicationData applicationData{};
ImGuiCallbacks callbacks{};
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);
std::wstring appPath;
getAppDir(appPath);
char appPathStr[MAX_FONT_PATH_LENGTH];
HRESULT convResult = WideCharToMultiByte(CP_UTF8, NULL, &appPath[0], -1, appPathStr, MAX_FONT_PATH_LENGTH, NULL, nullptr);
isError(convResult, "Failed to convert path: ");
// sad :(
gDrawData = &drawData;
gAppData = &appData;
// Load text font
ImGuiIO& io = ImGui::GetIO();
io.Fonts->AddFontFromFileTTF("Montserrat-Regular.ttf", 18.0f);
std::string fontPath = std::string(appPathStr);
fontPath.append("\\Montserrat-Regular.ttf");
io.Fonts->AddFontFromFileTTF(fontPath.c_str(), 18.0f);
// Load icon font
static const ImWchar icons_ranges[] = { 0xEA01, 0xF2DF, 0 };
ImFontConfig icons_config;
icons_config.MergeMode = true;
icons_config.PixelSnapH = true;
io.Fonts->AddFontFromFileTTF("remixicon.ttf", 14.0f, &icons_config, icons_ranges);
// Set up audio device api
HRESULT initResult = CoInitializeEx(NULL, COINIT_MULTITHREADED);
isError(initResult, "Failed to initialize COM: ");
reloadDeviceLists(appData);
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(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;
customYCursor += menuBar(appData).y;
ImVec2 viewportSize = ImGui::GetMainViewport()->Size;
ImVec2 containingSize = ImGui::GetMainViewport()->Size;
// Menu Bar
customYCursor += menuBar(drawData, appData).y;
// Playback Devices
ImGui::SetNextWindowPos(ImVec2(0, customYCursor));
ImGui::SetNextWindowSize(ImVec2(containingSize.x, 0));
customYCursor += audioDeviceWindow(appData, appData->playbackDevices, " \xEE\xB8\x84 Playback").y;
ImGui::SetNextWindowSize(ImVec2(viewportSize.x, 0));
customYCursor += audioDeviceWindow(appData, appData.audioData->playbackDevices, " \xEE\xB8\x84 Playback").y;
customYCursor += 5.;
// Recording devices
ImGui::SetNextWindowPos(ImVec2(0, customYCursor));
ImGui::SetNextWindowSize(ImVec2(containingSize.x, 0));
customYCursor += audioDeviceWindow(appData, appData->recordingDevices, " \xEE\xBD\x8F Recording").y;
ImGui::SetNextWindowSize(ImVec2(viewportSize.x, 0));
customYCursor += audioDeviceWindow(appData, appData.audioData->recordingDevices, " \xEE\xBD\x8F Recording").y;
if (appData->settings.fitWindowHeight)
{
// Resize viewport
drawData.window_size.y = customYCursor;
if (appData.settings.docked)
{
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();
}
@@ -91,14 +213,36 @@ ImVec2 menuBar(ApplicationData* appData)
{
if (ImGui::Button("Manual Refresh"))
{
reloadDeviceLists(appData);
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;
}
@@ -122,7 +266,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))
{
@@ -141,7 +285,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;
}
@@ -150,26 +294,46 @@ ImVec2 audioDeviceWindow(ApplicationData* appData, std::vector<AudioDevice>& dev
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(.7, .7, .7, 1.));
}
// Device Name
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text(dev.name.c_str());
// Volume
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
ImGui::TableNextColumn();
if (dev.state == DEVICE_STATE_ACTIVE)
{
@@ -179,7 +343,7 @@ ImVec2 audioDeviceWindow(ApplicationData* appData, std::vector<AudioDevice>& dev
}
if (customButton("bn_d_", deviceIdUtf8.c_str(), "\xEE\xBE\x82", !dev.isDefaultConsole))
{
setDefaultAudioDevice(appData, dev.id.c_str(), ERole::eConsole);
setDefaultAudioDevice(*appData.audioData, dev.id.c_str(), ERole::eConsole);
}
ImGui::SameLine();
@@ -189,7 +353,7 @@ ImVec2 audioDeviceWindow(ApplicationData* appData, std::vector<AudioDevice>& dev
}
if (customButton("bn_c_", deviceIdUtf8.c_str(), "\xEE\xBF\xA9", !dev.isDefaultCommunication))
{
setDefaultAudioDevice(appData, dev.id.c_str(), ERole::eCommunications);
setDefaultAudioDevice(*appData.audioData, dev.id.c_str(), ERole::eCommunications);
}
}
@@ -215,3 +379,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,12 @@
#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);
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,3,1
PRODUCTVERSION 1,0,3,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.3.1"
VALUE "InternalName", "AsuroTool.exe"
VALUE "LegalCopyright", "Copyright (C) 2022"
VALUE "OriginalFilename", "AsuroTool.exe"
VALUE "ProductName", "Audio Thingy"
VALUE "ProductVersion", "1.0.3.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,10 +92,14 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<DisableSpecificWarnings>4305; 4244</DisableSpecificWarnings>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>pathcch.lib;%(AdditionalDependencies)</AdditionalDependencies>
<IgnoreSpecificDefaultLibraries>
</IgnoreSpecificDefaultLibraries>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@@ -106,12 +110,16 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<DisableSpecificWarnings>4305; 4244</DisableSpecificWarnings>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>pathcch.lib;%(AdditionalDependencies)</AdditionalDependencies>
<IgnoreSpecificDefaultLibraries>
</IgnoreSpecificDefaultLibraries>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@@ -120,10 +128,14 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<DisableSpecificWarnings>4305; 4244</DisableSpecificWarnings>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>pathcch.lib;%(AdditionalDependencies)</AdditionalDependencies>
<IgnoreSpecificDefaultLibraries>
</IgnoreSpecificDefaultLibraries>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@@ -134,17 +146,23 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<DisableSpecificWarnings>4305; 4244</DisableSpecificWarnings>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>pathcch.lib;%(AdditionalDependencies)</AdditionalDependencies>
<IgnoreSpecificDefaultLibraries>
</IgnoreSpecificDefaultLibraries>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="ApplicationData.cpp" />
<ClCompile Include="AsuroTool.cpp" />
<ClCompile Include="AudioNotificationListener.cpp" />
<ClCompile Include="Settings.cpp" />
<ClCompile Include="Util.cpp" />
</ItemGroup>
<ItemGroup>
@@ -152,8 +170,10 @@
<ClInclude Include="AsuroTool.h" />
<ClCompile Include="AudioApi.cpp" />
<ClInclude Include="AudioApi.h" />
<ClInclude Include="AudioNotificationListener.h" />
<ClInclude Include="PolicyConfig.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="Settings.h" />
<ClInclude Include="Util.h" />
</ItemGroup>
<ItemGroup>

View File

@@ -13,20 +13,32 @@
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Source Files\Audio">
<UniqueIdentifier>{37b6f2f7-f9d0-428e-9a8f-4b034610a39a}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Audio">
<UniqueIdentifier>{b65d213d-ddf6-4816-90d1-bf0811a51abf}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="AsuroTool.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="AudioApi.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Util.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ApplicationData.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="AudioApi.cpp">
<Filter>Source Files\Audio</Filter>
</ClCompile>
<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">
@@ -35,18 +47,24 @@
<ClInclude Include="Util.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="PolicyConfig.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="AudioApi.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ApplicationData.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="AudioApi.h">
<Filter>Header Files\Audio</Filter>
</ClInclude>
<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

@@ -6,11 +6,112 @@
#include <vector>
#include <algorithm>
#include "ImguiBase.h"
#include "Util.h"
#include "AudioApi.h"
#include "PolicyConfig.h"
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));
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);
}
void loadAudioDevices(AudioData& audioData, std::vector<AudioDevice>& deviceList, EDataFlow deviceType)
{
deviceList.clear();
HRESULT err;
IMMDeviceCollection* deviceCollection = nullptr;
err = audioData.deviceEnumerator->EnumAudioEndpoints(deviceType, DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED, &deviceCollection);
if (isError(err, "Failed to enumerate audio devices: ")) return;
UINT deviceCount;
err = deviceCollection->GetCount(&deviceCount);
if (isError(err, "Failed to count audio devices: ")) return;
IMMDevice* defaultConsoleDevice = nullptr;
LPWSTR defaultConsoleId = nullptr;
err = audioData.deviceEnumerator->GetDefaultAudioEndpoint(deviceType, ERole::eConsole, &defaultConsoleDevice);
if (!FAILED(err))
{
defaultConsoleDevice->GetId(&defaultConsoleId);
}
IMMDevice* defaultMediaOutput = nullptr;
LPWSTR defaultMediaId = nullptr;
err = audioData.deviceEnumerator->GetDefaultAudioEndpoint(deviceType, ERole::eMultimedia, &defaultMediaOutput);
if (!FAILED(err))
{
defaultMediaOutput->GetId(&defaultMediaId);
}
IMMDevice* defaultCommunicationOutput = nullptr;
LPWSTR defaultCommunicationId = nullptr;
err = audioData.deviceEnumerator->GetDefaultAudioEndpoint(deviceType, ERole::eCommunications, &defaultCommunicationOutput);
if (!FAILED(err))
{
defaultCommunicationOutput->GetId(&defaultCommunicationId);
}
for (UINT i = 0; i < deviceCount; i += 1)
{
IMMDevice* device;
err = deviceCollection->Item(i, &device);
if (isError(err, std::stringstream("Failed to get device ") << i << ": "))
{
continue;
}
LPWSTR deviceId;
err = device->GetId(&deviceId);
if (!isError(err, std::stringstream("Failed to get device id ") << i << ": "))
{
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));
}
CoTaskMemFree(deviceId);
}
std::sort(deviceList.begin(), deviceList.end(), [](AudioDevice& a, AudioDevice& b) { return a.state < b.state; });
if (deviceCollection)
{
deviceCollection->Release();
}
}
void reloadDeviceLists(AudioData& audioData)
{
loadAudioDevices(audioData, audioData.playbackDevices, EDataFlow::eRender);
loadAudioDevices(audioData, audioData.recordingDevices, EDataFlow::eCapture);
}
HRESULT getDeviceProperty(IPropertyStore* propertyStore, const PROPERTYKEY propertyKey, PROPVARIANT* outData)
{
PropVariantInit(outData);
@@ -25,134 +126,23 @@ HRESULT getDevicePropertyString(IPropertyStore* propertyStore, const PROPERTYKEY
return result;
}
void setDefaultAudioDevice(ApplicationData* appData, const wchar_t* deviceId, ERole role)
void setDefaultAudioDevice(AudioData& audioData, const wchar_t* deviceId, ERole role)
{
IPolicyConfigVista* pPolicyConfig;
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(appData);
reloadDeviceLists(audioData);
}
}
void loadAudioDevices(std::vector<AudioDevice>& deviceList, EDataFlow deviceType)
{
deviceList.clear();
HRESULT err;
IMMDeviceEnumerator* deviceEnumerator = NULL;
IMMDeviceCollection* deviceCollection = NULL;
err = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator));
if (isError(err, "Failed to set up audio device enumerator: ")) return;
err = deviceEnumerator->EnumAudioEndpoints(deviceType, DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED, &deviceCollection);
if (isError(err, "Failed to enumerate audio devices: ")) return;
UINT deviceCount;
err = deviceCollection->GetCount(&deviceCount);
if (isError(err, "Failed to count audio devices: ")) return;
IMMDevice* defaultConsoleDevice = NULL;
LPWSTR defaultConsoleId = nullptr;
err = deviceEnumerator->GetDefaultAudioEndpoint(deviceType, ERole::eConsole, &defaultConsoleDevice);
if (!FAILED(err))
{
defaultConsoleDevice->GetId(&defaultConsoleId);
}
IMMDevice* defaultMediaOutput = NULL;
LPWSTR defaultMediaId = nullptr;
err = deviceEnumerator->GetDefaultAudioEndpoint(deviceType, ERole::eMultimedia, &defaultMediaOutput);
if (!FAILED(err))
{
defaultMediaOutput->GetId(&defaultMediaId);
}
IMMDevice* defaultCommunicationOutput = NULL;
LPWSTR defaultCommunicationId = nullptr;
err = deviceEnumerator->GetDefaultAudioEndpoint(deviceType, ERole::eCommunications, &defaultCommunicationOutput);
if (!FAILED(err))
{
defaultCommunicationOutput->GetId(&defaultCommunicationId);
}
for (UINT i = 0; i < deviceCount; i += 1)
{
IMMDevice* device;
err = deviceCollection->Item(i, &device);
isError(err, std::stringstream("Failed to get device ") << i << ": ");
LPWSTR deviceId;
err = device->GetId(&deviceId);
isError(err, std::stringstream("Failed to get device id ") << i << ": ");
IPropertyStore* propertyStore;
err = 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 << ": ");
DWORD deviceState;
err = device->GetState(&deviceState);
isError(err, std::stringstream("Failed to reat state of device ") << i << ": ");
IAudioEndpointVolume* volumeInterface;
err = device->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (LPVOID*)&volumeInterface);
isError(err, "Failed to get audio endpoint volume interface: ");
IAudioMeterInformation* meterInterface;
err = device->Activate(__uuidof(IAudioMeterInformation), CLSCTX_INPROC_SERVER, NULL, (LPVOID*)&meterInterface);
isError(err, "Failed to get audio meter interface: ");
deviceList.push_back({
device,
volumeInterface,
meterInterface,
std::wstring(deviceId),
utf8Encode(deviceName),
deviceState,
utf8Encode(defaultConsoleId) == utf8Encode(deviceId),
utf8Encode(defaultMediaId) == utf8Encode(deviceId),
utf8Encode(defaultCommunicationId) == utf8Encode(deviceId),
});
// Free stuff
if (propertyStore)
{
propertyStore->Release();
}
CoTaskMemFree(deviceId);
}
if (deviceEnumerator)
{
deviceEnumerator->Release();
}
if (deviceCollection)
{
deviceCollection->Release();
}
std::sort(deviceList.begin(), deviceList.end(), [](AudioDevice& a, AudioDevice& b) { return a.state < b.state; });
}
void reloadDeviceLists(ApplicationData* appData)
{
loadAudioDevices(appData->playbackDevices, EDataFlow::eRender);
loadAudioDevices(appData->recordingDevices, EDataFlow::eCapture);
}
float getVolume(IAudioEndpointVolume* volumeInterface)
{
float volume;
if (FAILED(volumeInterface->GetChannelVolumeLevel(0, &volume)))
if (FAILED(volumeInterface->GetMasterVolumeLevelScalar(&volume)))
{
volume = 0.;
}
@@ -160,6 +150,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(ApplicationData* appData, const wchar_t* deviceId, ERole role);
void loadAudioDevices(std::vector<AudioDevice>& deviceList, EDataFlow deviceType);
void reloadDeviceLists(ApplicationData* appData);
void setDefaultAudioDevice(AudioData& audioData, const wchar_t* deviceId, ERole role);
float getVolume(IAudioEndpointVolume* volumeInterface);
void setVolume(IAudioEndpointVolume* volumeInterface, float newVolume);
float getMeterValue(IAudioMeterInformation* meterInterface);

View File

@@ -0,0 +1,170 @@
#include <windows.h>
#include "AudioApi.h"
#include "AudioNotificationListener.h"
AudioNotificationListener::AudioNotificationListener(std::shared_ptr<AudioData> audioData) : audioData(audioData)
{
}
HRESULT __stdcall AudioNotificationListener::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState)
{
auto updateDevice = [pwstrDeviceId, dwNewState](std::vector<AudioDevice>& deviceList)
{
for (auto& audioDevice : deviceList)
{
if (wcscmp(audioDevice.id.c_str(), pwstrDeviceId) == 0)
{
audioDevice.state = dwNewState;
}
}
};
updateDevice(audioData->playbackDevices);
updateDevice(audioData->recordingDevices);
return S_OK;
}
HRESULT __stdcall AudioNotificationListener::OnDeviceAdded(LPCWSTR pwstrDeviceId)
{
IMMDevice* newDevice;
HRESULT result = audioData->deviceEnumerator->GetDevice(pwstrDeviceId, &newDevice);
if (SUCCEEDED(result) && newDevice != nullptr)
{
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;
}
HRESULT __stdcall AudioNotificationListener::OnDeviceRemoved(LPCWSTR pwstrDeviceId)
{
auto deleteDevice = [pwstrDeviceId](std::vector<AudioDevice>& deviceList)
{
auto deviceListIterator = deviceList.begin();
while (deviceListIterator != deviceList.end())
{
if (wcscmp(deviceListIterator->id.c_str(), pwstrDeviceId) == 0)
{
deviceListIterator = deviceList.erase(deviceListIterator);
continue;
}
deviceListIterator++;
}
};
deleteDevice(audioData->playbackDevices);
deleteDevice(audioData->recordingDevices);
return S_OK;
}
HRESULT __stdcall AudioNotificationListener::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId)
{
std::vector<AudioDevice>* deviceList;
if (flow == EDataFlow::eRender)
{
deviceList = &audioData->playbackDevices;
}
else
{
deviceList = &audioData->recordingDevices;
}
for (AudioDevice& audioDevice : *deviceList)
{
if (wcscmp(audioDevice.id.c_str(), pwstrDefaultDeviceId) == 0)
{
switch (role)
{
case ERole::eCommunications:
audioDevice.isDefaultCommunication = true;
break;
case ERole::eConsole:
audioDevice.isDefaultConsole = true;
break;
case ERole::eMultimedia:
audioDevice.isDefaultMedia = true;
break;
}
}
else
{
switch (role)
{
case ERole::eCommunications:
audioDevice.isDefaultCommunication = false;
break;
case ERole::eConsole:
audioDevice.isDefaultConsole = false;
break;
case ERole::eMultimedia:
audioDevice.isDefaultMedia = false;
break;
}
}
}
return S_OK;
}
HRESULT __stdcall AudioNotificationListener::OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key)
{
return S_OK;
}
// Copy pasted stuff from Windows SDK Example
// No clue what it does lol
// https://github.com/pauldotknopf/WindowsSDK7-Samples/blob/master/multimedia/audio/osd/endpointMonitor.cpp
HRESULT __stdcall AudioNotificationListener::QueryInterface(REFIID riid, void** ppvObject)
{
if ((riid == __uuidof(IUnknown)) ||
(riid == __uuidof(IMMNotificationClient)))
{
*ppvObject = static_cast<IMMNotificationClient*>(this);
}
else
{
*ppvObject = nullptr;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
ULONG __stdcall AudioNotificationListener::AddRef(void)
{
return InterlockedIncrement(&refCount);
}
ULONG __stdcall AudioNotificationListener::Release(void)
{
long refCountResult = InterlockedDecrement(&refCount);
if (refCountResult == 0)
{
delete this;
}
return refCountResult;
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include <memory>
#include <mmdeviceapi.h>
class AudioData;
class ApplicationData;
class AudioNotificationListener : public IMMNotificationClient {
public:
std::shared_ptr<AudioData> audioData;
long refCount = 1;
AudioNotificationListener(std::shared_ptr<AudioData> audioData);
virtual HRESULT __stdcall OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) override;
virtual HRESULT __stdcall OnDeviceAdded(LPCWSTR pwstrDeviceId) override;
virtual HRESULT __stdcall OnDeviceRemoved(LPCWSTR pwstrDeviceId) override;
virtual HRESULT __stdcall OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId) override;
virtual HRESULT __stdcall OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) override;
virtual HRESULT __stdcall QueryInterface(REFIID riid, void** ppvObject) override;
virtual ULONG __stdcall AddRef(void) override;
virtual ULONG __stdcall Release(void) override;
};

94
AsuroTool/Settings.cpp Normal file
View File

@@ -0,0 +1,94 @@
#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;
if (FAILED(getAppPath(appPath))) return;
hr = RegSetValueEx(runKey, KEY_APP_NAME, 0, REG_SZ, (BYTE*)appPath.c_str(), static_cast<DWORD>((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

@@ -1,6 +1,7 @@
#include <iostream>
#include "Util.h"
#include "pathcch.h"
bool isError(const HRESULT result, const std::stringstream message)
{
@@ -29,3 +30,24 @@ std::string utf8Encode(const std::wstring& wstr)
WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &resultString[0], sizeNeeded, NULL, NULL);
return resultString;
}
HRESULT getAppPath(std::wstring& outPath)
{
const size_t MAX_PATH_LENGTH = 32767;
outPath.resize(MAX_PATH_LENGTH);
HRESULT hr = GetModuleFileName(NULL, &outPath[0], static_cast<DWORD>(outPath.size()));
if (isError(hr, "Failed to get executable name: ")) return hr;
outPath.resize(wcslen(outPath.data()));
return hr;
}
HRESULT getAppDir(std::wstring& outPath)
{
HRESULT hr = getAppPath(outPath);
if (FAILED(hr)) return hr;
hr = PathCchRemoveFileSpec(&outPath[0], static_cast<DWORD>(outPath.size()));
if (isError(hr, "Failed to get executable dir: ")) return hr;
outPath.resize(wcslen(outPath.data()));
return hr;
}

View File

@@ -6,3 +6,5 @@
bool isError(const HRESULT result, const std::stringstream message);
bool isError(const HRESULT result, const char* message);
std::string utf8Encode(const std::wstring& wstr);
HRESULT getAppPath(std::wstring& outPath);
HRESULT getAppDir(std::wstring& outPath);

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:
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

@@ -107,6 +107,8 @@
<Lib>
<AdditionalLibraryDirectories>E:\Code\glfw-3.3.7.bin.WIN64\lib-vc2022;D:\Applications\VulkanSDK\1.2.182.0\Lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>vulkan-1.lib;glfw3.lib;%(AdditionalDependencies)</AdditionalDependencies>
<IgnoreSpecificDefaultLibraries>
</IgnoreSpecificDefaultLibraries>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@@ -130,6 +132,8 @@
<Lib>
<AdditionalLibraryDirectories>E:\Code\glfw-3.3.7.bin.WIN64\lib-vc2022;D:\Applications\VulkanSDK\1.2.182.0\Lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>vulkan-1.lib;glfw3.lib;%(AdditionalDependencies)</AdditionalDependencies>
<IgnoreSpecificDefaultLibraries>
</IgnoreSpecificDefaultLibraries>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@@ -149,6 +153,8 @@
<Lib>
<AdditionalLibraryDirectories>E:\Code\glfw-3.3.7.bin.WIN64\lib-vc2022;D:\Applications\VulkanSDK\1.2.182.0\Lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>vulkan-1.lib;glfw3.lib;%(AdditionalDependencies)</AdditionalDependencies>
<IgnoreSpecificDefaultLibraries>
</IgnoreSpecificDefaultLibraries>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@@ -172,6 +178,8 @@
<Lib>
<AdditionalLibraryDirectories>E:\Code\glfw-3.3.7.bin.WIN64\lib-vc2022;D:\Applications\VulkanSDK\1.2.182.0\Lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>vulkan-1.lib;glfw3.lib;%(AdditionalDependencies)</AdditionalDependencies>
<IgnoreSpecificDefaultLibraries>
</IgnoreSpecificDefaultLibraries>
</Lib>
</ItemDefinitionGroup>
<ItemGroup>

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

View File

@@ -96,6 +96,8 @@
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<IgnoreSpecificDefaultLibraries>
</IgnoreSpecificDefaultLibraries>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@@ -112,6 +114,8 @@
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<IgnoreSpecificDefaultLibraries>
</IgnoreSpecificDefaultLibraries>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@@ -124,6 +128,8 @@
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<IgnoreSpecificDefaultLibraries>
</IgnoreSpecificDefaultLibraries>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@@ -140,6 +146,8 @@
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<IgnoreSpecificDefaultLibraries>
</IgnoreSpecificDefaultLibraries>
</Link>
</ItemDefinitionGroup>
<ItemGroup>