20 Commits

Author SHA1 Message Date
5f5e24fa9c 1.0.4 2022-07-26 18:08:13 +02:00
cdffa1b50b per channel peak meters 2022-07-26 18:07:57 +02:00
eec9e25e12 1.0.3 2022-07-26 15:22:51 +02:00
3b4853acda Fix device add/remove handlers 2022-07-26 15:22:23 +02:00
cea13986f0 1.0.2 2022-07-24 02:09:58 +02:00
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 1030 additions and 224 deletions

View File

@@ -1,9 +1,123 @@
#include "ApplicationData.h" #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: ");
getVolumeLimit(volumeInterface, &minVolumeDb, &maxVolumeDb);
if (propertyStore)
{
propertyStore->Release();
}
}
AudioDevice::AudioDevice(AudioDevice&& other) noexcept
: device(other.device), volumeInterface(other.volumeInterface), meterInterface(other.meterInterface),
id(other.id), name(other.name), state(other.state),
isDefaultConsole(other.isDefaultConsole), isDefaultMedia(other.isDefaultMedia), isDefaultCommunication(other.isDefaultCommunication),
minVolumeDb(other.minVolumeDb), maxVolumeDb(other.maxVolumeDb)
{
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;
this->minVolumeDb = other.minVolumeDb;
this->maxVolumeDb = other.maxVolumeDb;
other.device = nullptr;
other.volumeInterface = nullptr;
other.meterInterface = nullptr;
return *this;
}
AudioDevice::~AudioDevice() AudioDevice::~AudioDevice()
{ {
if (volumeInterface)
{
volumeInterface->Release();
volumeInterface = nullptr;
}
if (meterInterface)
{
meterInterface->Release();
meterInterface = nullptr;
}
if (device) 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 <endpointvolume.h>
#include <string> #include <string>
#include <vector> #include <vector>
#include "AudioNotificationListener.h"
class AudioDevice { class AudioDevice {
public: public:
@@ -13,22 +14,43 @@ public:
std::wstring id = {}; std::wstring id = {};
std::string name = {}; std::string name = {};
unsigned long state = {}; unsigned long state = {};
bool isDefaultConsole = {}; bool isDefaultConsole = false;
bool isDefaultMedia = {}; bool isDefaultMedia = false;
bool isDefaultCommunication = {}; bool isDefaultCommunication = false;
float minVolumeDb = 0.f;
float maxVolumeDb = 0.f;
AudioDevice(IMMDevice* device, LPCWSTR deviceId);
AudioDevice(AudioDevice&& other) noexcept;
AudioDevice& operator=(AudioDevice&& other) noexcept;
~AudioDevice(); ~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 { class ApplicationSettings {
public: public:
bool autostart = false;
bool docked = false;
bool showDisabledDevices = false; bool showDisabledDevices = false;
bool fitWindowHeight = true;
}; };
class ApplicationData { class ApplicationData {
public: public:
ApplicationSettings settings = {}; ApplicationSettings settings = {};
std::vector<AudioDevice> playbackDevices = {}; std::shared_ptr<AudioData> audioData = std::make_shared<AudioData>();
std::vector<AudioDevice> recordingDevices = {};
ApplicationData(const ApplicationData&) = delete;
ApplicationData& operator=(const ApplicationData&) = delete;
}; };

View File

@@ -3,87 +3,210 @@
#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup") #pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")
#endif #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 <windows.h>
#include <array>
#include <functional>
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <shellapi.h>
#include <commctrl.h>
#include <strsafe.h>
#include "Util.h" #include "Util.h"
#include "AudioApi.h" #include "AudioApi.h"
#include "Settings.h"
#include "resource.h" #include "resource.h"
#include "AsuroTool.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() 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 // Load text font
ImGuiIO& io = ImGui::GetIO(); 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 // Load icon font
static const ImWchar icons_ranges[] = { 0xEA01, 0xF2DF, 0 }; static const ImWchar icons_ranges[] = { 0xEA01, 0xF2DF, 0 };
ImFontConfig icons_config; ImFontConfig icons_config;
icons_config.MergeMode = true; icons_config.MergeMode = true;
icons_config.PixelSnapH = true; icons_config.PixelSnapH = true;
io.Fonts->AddFontFromFileTTF("remixicon.ttf", 14.0f, &icons_config, icons_ranges); std::string iconFontPath = std::string(appPathStr);
iconFontPath.append("\\remixicon.ttf");
// Set up audio device api io.Fonts->AddFontFromFileTTF(iconFontPath.c_str(), 14.0f, &icons_config, icons_ranges);
HRESULT initResult = CoInitializeEx(NULL, COINIT_MULTITHREADED);
isError(initResult, "Failed to initialize COM: ");
reloadDeviceLists(appData);
// Set window icon // Set window icon
HINSTANCE instance = GetModuleHandle(NULL);
LPWSTR iconId = MAKEINTRESOURCE(IDI_ICON1); LPWSTR iconId = MAKEINTRESOURCE(IDI_ICON1);
HANDLE iconLarge = LoadImageW(GetModuleHandle(NULL), iconId, IMAGE_ICON, 64, 64, 0); HANDLE iconLarge = LoadImageW(instance, iconId, IMAGE_ICON, 64, 64, 0);
HANDLE iconSmall = LoadImageW(GetModuleHandle(NULL), iconId, IMAGE_ICON, 32, 32, 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_BIG, (LPARAM)iconLarge);
SendMessage(drawData.window_handle, WM_SETICON, ICON_SMALL, (LPARAM)iconSmall); SendMessage(drawData.window_handle, WM_SETICON, ICON_SMALL, (LPARAM)iconSmall);
SendMessage(drawData.window_handle, WM_SETICON, ICON_SMALL2, (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; 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::SetNextWindowPos(ImVec2(0, customYCursor));
ImGui::SetNextWindowSize(ImVec2(containingSize.x, 0)); ImGui::SetNextWindowSize(ImVec2(viewportSize.x, 0));
customYCursor += audioDeviceWindow(appData, appData->playbackDevices, " \xEE\xB8\x84 Playback").y; customYCursor += audioDeviceWindow(appData, appData.audioData->playbackDevices, " \xEE\xB8\x84 Playback").y;
customYCursor += 5.; customYCursor += 5.;
// Recording devices
ImGui::SetNextWindowPos(ImVec2(0, customYCursor)); ImGui::SetNextWindowPos(ImVec2(0, customYCursor));
ImGui::SetNextWindowSize(ImVec2(containingSize.x, 0)); ImGui::SetNextWindowSize(ImVec2(viewportSize.x, 0));
customYCursor += audioDeviceWindow(appData, appData->recordingDevices, " \xEE\xBD\x8F Recording").y; customYCursor += audioDeviceWindow(appData, appData.audioData->recordingDevices, " \xEE\xBD\x8F Recording").y;
if (appData->settings.fitWindowHeight) // Resize viewport
{
drawData.window_size.y = customYCursor; 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{}; ImVec2 size{};
bool closeMenu = false;
if (ImGui::BeginMainMenuBar()) if (ImGui::BeginMainMenuBar())
{ {
if (ImGui::BeginMenu("Settings")) if (ImGui::BeginMenu("Settings"))
{ {
ImGui::Checkbox("Show Disabled Devices", &appData->settings.showDisabledDevices); if (ImGui::Checkbox("Docked", &appData.settings.docked))
ImGui::Checkbox("Fit Window Height", &appData->settings.fitWindowHeight); {
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(); ImGui::EndMenu();
} }
@@ -91,14 +214,36 @@ ImVec2 menuBar(ApplicationData* appData)
{ {
if (ImGui::Button("Manual Refresh")) if (ImGui::Button("Manual Refresh"))
{ {
reloadDeviceLists(appData); reloadDeviceLists(*appData.audioData);
} }
ImGui::EndMenu(); 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(); size = ImGui::GetWindowSize();
ImGui::EndMainMenuBar(); ImGui::EndMainMenuBar();
} }
if (closeMenu)
{
ImGui::SetWindowFocus();
}
return size; return size;
} }
@@ -122,7 +267,7 @@ bool customButton(const char* id_start, const char* id_end, const char* title, b
return result; 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)) if (ImGui::Begin(title, 0, ImGuiWindowFlags_NoResize))
{ {
@@ -141,7 +286,7 @@ ImVec2 audioDeviceWindow(ApplicationData* appData, std::vector<AudioDevice>& dev
{ {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1., 1., 1., 1.)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1., 1., 1., 1.));
} }
else if (!appData->settings.showDisabledDevices) else if (!appData.settings.showDisabledDevices)
{ {
continue; continue;
} }
@@ -150,26 +295,57 @@ ImVec2 audioDeviceWindow(ApplicationData* appData, std::vector<AudioDevice>& dev
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(.7, .7, .7, 1.)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(.7, .7, .7, 1.));
} }
// Device Name
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text(dev.name.c_str()); ImGui::Text(dev.name.c_str());
// Volume
ImGui::TableNextColumn(); ImGui::TableNextColumn();
if (dev.state == DEVICE_STATE_ACTIVE) if (dev.state == DEVICE_STATE_ACTIVE)
{ {
float volume = log10f(getMeterValue(dev.meterInterface) * 9. + 1.); static std::array<float, 2> meterValues{};
UINT channelCount = getMeterValues(dev.meterInterface, meterValues);
auto drawList = ImGui::GetWindowDrawList(); auto drawList = ImGui::GetWindowDrawList();
ImVec2 windowPos = ImGui::GetWindowPos(); ImVec2 windowPos = ImGui::GetWindowPos();
ImVec2 cursorPos = ImGui::GetCursorScreenPos(); ImVec2 cursorPos = ImGui::GetCursorScreenPos();
ImVec2 space = ImGui::GetContentRegionAvail(); 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.); const float linePaddingX = 3.;
drawList->AddLine(ImVec2(cursorPos.x, lineY), ImVec2(cursorPos.x + space.x * volume, lineY), IM_COL32(200, 200, 255, 255), 3.); cursorPos.x += linePaddingX;
// BG line
drawList->AddLine(ImVec2(cursorPos.x, lineY), ImVec2(cursorPos.x + (space.x - 2. * linePaddingX), lineY), IM_COL32(120, 120, 120, 255), 2.);
// Channel lines
if (channelCount == 1)
{
drawList->AddLine(ImVec2(cursorPos.x, lineY), ImVec2(cursorPos.x + (space.x - 2. * linePaddingX) * meterValues[0], lineY), IM_COL32(200, 200, 255, 255), 3.);
}
if (channelCount == 2)
{
drawList->AddLine(ImVec2(cursorPos.x, lineY - 2), ImVec2(cursorPos.x + (space.x - 2. * linePaddingX) * meterValues[0], lineY - 2), IM_COL32(200, 200, 255, 255), 3.);
drawList->AddLine(ImVec2(cursorPos.x, lineY + 2), ImVec2(cursorPos.x + (space.x - 2. * linePaddingX) * meterValues[1], lineY + 2), IM_COL32(200, 200, 255, 255), 3.);
} }
ImGui::SetNextItemWidth(space.x);
ImGui::PushID(&dev.id);
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0, 0, 0, 0));
float volume = getVolume(dev.volumeInterface);
if (ImGui::SliderFloat("", &volume, 0., 1., "", ImGuiSliderFlags_NoRoundToFormat | ImGuiSliderFlags_AlwaysClamp))
{
setVolume(dev.volumeInterface, volume);
}
ImGui::PopStyleColor();
ImGui::PopID();
}
// Defaults
ImGui::TableNextColumn(); ImGui::TableNextColumn();
if (dev.state == DEVICE_STATE_ACTIVE) if (dev.state == DEVICE_STATE_ACTIVE)
{ {
@@ -179,7 +355,7 @@ ImVec2 audioDeviceWindow(ApplicationData* appData, std::vector<AudioDevice>& dev
} }
if (customButton("bn_d_", deviceIdUtf8.c_str(), "\xEE\xBE\x82", !dev.isDefaultConsole)) 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(); ImGui::SameLine();
@@ -189,7 +365,7 @@ ImVec2 audioDeviceWindow(ApplicationData* appData, std::vector<AudioDevice>& dev
} }
if (customButton("bn_c_", deviceIdUtf8.c_str(), "\xEE\xBF\xA9", !dev.isDefaultCommunication)) 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 +391,25 @@ void drawCircle(float radius, ImU32 color)
ImVec2 windowPos = ImGui::GetWindowPos(); ImVec2 windowPos = ImGui::GetWindowPos();
drawList->AddCircleFilled(ImVec2(cursorPos.x + windowPos.x, cursorPos.y + windowPos.y + ImGui::GetTextLineHeight() / 2.), radius, color); 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 "ImguiBase.h"
#include "ApplicationData.h" #include "ApplicationData.h"
void init(DrawData& drawData, void* customData); void init(DrawData& drawData, ApplicationData& customData);
void draw(DrawData& drawData, void* customData); void draw(DrawData& drawData, ApplicationData& customData);
ImVec2 menuBar(ApplicationData* appData); void cleanup(DrawData& drawData, ApplicationData& appData);
ImVec2 audioDeviceWindow(ApplicationData* appData, std::vector<AudioDevice>& deviceList, const char* title);
ImVec2 menuBar(DrawData& drawData, ApplicationData& appData);
ImVec2 audioDeviceWindow(ApplicationData& appData, std::vector<AudioDevice>& deviceList, const char* title);
void drawCircle(float radius, ImU32 color); 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. // remains consistent on all systems.
IDI_ICON1 ICON "kaiju.ico" IDI_ICON1 ICON "kaiju.ico"
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,4,1
PRODUCTVERSION 1,0,4,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.4.1"
VALUE "InternalName", "AsuroTool.exe"
VALUE "LegalCopyright", "Copyright (C) 2022"
VALUE "OriginalFilename", "AsuroTool.exe"
VALUE "ProductName", "Audio Thingy"
VALUE "ProductVersion", "1.0.4.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 #endif // English (United Kingdom) resources
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////

View File

@@ -92,10 +92,14 @@
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<DisableSpecificWarnings>4305; 4244</DisableSpecificWarnings>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation> <GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>pathcch.lib;%(AdditionalDependencies)</AdditionalDependencies>
<IgnoreSpecificDefaultLibraries>
</IgnoreSpecificDefaultLibraries>
</Link> </Link>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@@ -106,12 +110,16 @@
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<DisableSpecificWarnings>4305; 4244</DisableSpecificWarnings>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding> <EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences> <OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation> <GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>pathcch.lib;%(AdditionalDependencies)</AdditionalDependencies>
<IgnoreSpecificDefaultLibraries>
</IgnoreSpecificDefaultLibraries>
</Link> </Link>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@@ -120,10 +128,14 @@
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<DisableSpecificWarnings>4305; 4244</DisableSpecificWarnings>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation> <GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>pathcch.lib;%(AdditionalDependencies)</AdditionalDependencies>
<IgnoreSpecificDefaultLibraries>
</IgnoreSpecificDefaultLibraries>
</Link> </Link>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@@ -134,17 +146,23 @@
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<DisableSpecificWarnings>4305; 4244</DisableSpecificWarnings>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding> <EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences> <OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation> <GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>pathcch.lib;%(AdditionalDependencies)</AdditionalDependencies>
<IgnoreSpecificDefaultLibraries>
</IgnoreSpecificDefaultLibraries>
</Link> </Link>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="ApplicationData.cpp" /> <ClCompile Include="ApplicationData.cpp" />
<ClCompile Include="AsuroTool.cpp" /> <ClCompile Include="AsuroTool.cpp" />
<ClCompile Include="AudioNotificationListener.cpp" />
<ClCompile Include="Settings.cpp" />
<ClCompile Include="Util.cpp" /> <ClCompile Include="Util.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -152,8 +170,10 @@
<ClInclude Include="AsuroTool.h" /> <ClInclude Include="AsuroTool.h" />
<ClCompile Include="AudioApi.cpp" /> <ClCompile Include="AudioApi.cpp" />
<ClInclude Include="AudioApi.h" /> <ClInclude Include="AudioApi.h" />
<ClInclude Include="AudioNotificationListener.h" />
<ClInclude Include="PolicyConfig.h" /> <ClInclude Include="PolicyConfig.h" />
<ClInclude Include="resource.h" /> <ClInclude Include="resource.h" />
<ClInclude Include="Settings.h" />
<ClInclude Include="Util.h" /> <ClInclude Include="Util.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -13,20 +13,32 @@
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier> <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> <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter> </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>
<ItemGroup> <ItemGroup>
<ClCompile Include="AsuroTool.cpp"> <ClCompile Include="AsuroTool.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="AudioApi.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Util.cpp"> <ClCompile Include="Util.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="ApplicationData.cpp"> <ClCompile Include="ApplicationData.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </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>
<ItemGroup> <ItemGroup>
<ClInclude Include="AsuroTool.h"> <ClInclude Include="AsuroTool.h">
@@ -35,18 +47,24 @@
<ClInclude Include="Util.h"> <ClInclude Include="Util.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="PolicyConfig.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="AudioApi.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ApplicationData.h"> <ClInclude Include="ApplicationData.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="resource.h"> <ClInclude Include="resource.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </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>
<ItemGroup> <ItemGroup>
<CopyFileToFolders Include="Montserrat-Regular.ttf"> <CopyFileToFolders Include="Montserrat-Regular.ttf">

View File

@@ -3,14 +3,116 @@
#include <functiondiscoverykeys.h> #include <functiondiscoverykeys.h>
#include <endpointvolume.h> #include <endpointvolume.h>
#include <array>
#include <vector> #include <vector>
#include <algorithm> #include <algorithm>
#include "ImguiBase.h"
#include "Util.h" #include "Util.h"
#include "AudioApi.h" #include "AudioApi.h"
#include "PolicyConfig.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) HRESULT getDeviceProperty(IPropertyStore* propertyStore, const PROPERTYKEY propertyKey, PROPVARIANT* outData)
{ {
PropVariantInit(outData); PropVariantInit(outData);
@@ -25,134 +127,23 @@ HRESULT getDevicePropertyString(IPropertyStore* propertyStore, const PROPERTYKEY
return result; 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); HRESULT hr = CoCreateInstance(__uuidof(CPolicyConfigVistaClient), NULL, CLSCTX_ALL, __uuidof(IPolicyConfigVista), (LPVOID*)&pPolicyConfig);
if (!isError(hr, "Failed to set default audio device: ")) if (!isError(hr, "Failed to set default audio device: "))
{ {
hr = pPolicyConfig->SetDefaultEndpoint(deviceId, role); hr = pPolicyConfig->SetDefaultEndpoint(deviceId, role);
pPolicyConfig->Release(); 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 getVolume(IAudioEndpointVolume* volumeInterface)
{ {
float volume; float volume;
if (FAILED(volumeInterface->GetChannelVolumeLevel(0, &volume))) if (FAILED(volumeInterface->GetMasterVolumeLevelScalar(&volume)))
{ {
volume = 0.; volume = 0.;
} }
@@ -160,13 +151,24 @@ float getVolume(IAudioEndpointVolume* volumeInterface)
return volume; return volume;
} }
float getMeterValue(IAudioMeterInformation* meterInterface) void setVolume(IAudioEndpointVolume* volumeInterface, float newVolume)
{ {
float volume; HRESULT hr = volumeInterface->SetMasterVolumeLevelScalar(newVolume, NULL);
if (FAILED(meterInterface->GetPeakValue(&volume))) isError(hr, "Failed to set volume level: ");
{ }
volume = 0.;
} UINT getMeterValues(IAudioMeterInformation* meterInterface, std::array<float, 2>& outLevels)
{
return volume; UINT channelCount;
if (FAILED(meterInterface->GetMeteringChannelCount(&channelCount)) || channelCount > 2) return 0;
if (FAILED(meterInterface->GetChannelsPeakValues(channelCount, &outLevels[0]))) return 0;
return channelCount;
}
void getVolumeLimit(IAudioEndpointVolume* volumeInterface, float* outMin, float* outMax)
{
float dummy;
volumeInterface->GetVolumeRange(outMin, outMax, &dummy);
} }

View File

@@ -4,10 +4,15 @@
#include "ApplicationData.h" #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 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"); 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 setDefaultAudioDevice(AudioData& audioData, const wchar_t* deviceId, ERole role);
void loadAudioDevices(std::vector<AudioDevice>& deviceList, EDataFlow deviceType);
void reloadDeviceLists(ApplicationData* appData);
float getVolume(IAudioEndpointVolume* volumeInterface); float getVolume(IAudioEndpointVolume* volumeInterface);
float getMeterValue(IAudioMeterInformation* meterInterface); void setVolume(IAudioEndpointVolume* volumeInterface, float newVolume);
UINT getMeterValues(IAudioMeterInformation* meterInterface, std::array<float, 2>& levels);
void getVolumeLimit(IAudioEndpointVolume* volumeInterface, float* outMin, float* outMax);

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 <iostream>
#include "Util.h" #include "Util.h"
#include "pathcch.h"
bool isError(const HRESULT result, const std::stringstream message) 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); WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &resultString[0], sizeNeeded, NULL, NULL);
return resultString; 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 std::stringstream message);
bool isError(const HRESULT result, const char* message); bool isError(const HRESULT result, const char* message);
std::string utf8Encode(const std::wstring& wstr); 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 // Used by AsuroTool.rc
// //
#define IDI_ICON1 109 #define IDI_ICON1 109
#define IDS_STRING112 112
#define IDS_STRING_TOOLTIP 112
// Next default values for new objects // Next default values for new objects
// //
#ifdef APSTUDIO_INVOKED #ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS #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_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101 #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_glfw.h"
#include "imgui_impl_vulkan.h" #include "imgui_impl_vulkan.h"
#include "ImguiBase.h"
#include <iostream> #include <iostream>
#define GLFW_INCLUDE_NONE
#define GLFW_INCLUDE_VULKAN
#define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3.h>
#include <GLFW/glfw3native.h> #include <GLFW/glfw3native.h>
#include <vulkan/vulkan.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); 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 // Setup GLFW window
glfwSetErrorCallback(glfw_error_callback); glfwSetErrorCallback(glfw_error_callback);
@@ -339,6 +337,7 @@ int startImgui(void* customState, void (*const initFunc)(DrawData&, void*), void
return 1; return 1;
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
GLFWwindow* window = glfwCreateWindow(windowWidth, windowHeight, title, NULL, NULL); GLFWwindow* window = glfwCreateWindow(windowWidth, windowHeight, title, NULL, NULL);
// Setup Vulkan // Setup Vulkan
@@ -365,6 +364,7 @@ int startImgui(void* customState, void (*const initFunc)(DrawData&, void*), void
// Setup Dear ImGui context // Setup Dear ImGui context
IMGUI_CHECKVERSION(); IMGUI_CHECKVERSION();
ImGui::CreateContext(); ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io; ImGuiIO& io = ImGui::GetIO(); (void)io;
//io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
//io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
@@ -408,10 +408,15 @@ int startImgui(void* customState, void (*const initFunc)(DrawData&, void*), void
// Our state // Our state
DrawData drawData{}; DrawData drawData{};
drawData.window = window;
drawData.window_handle = glfwGetWin32Window(window); drawData.window_handle = glfwGetWin32Window(window);
drawData.window_size = getWindowSize(window); drawData.window_size = getWindowSize(window);
initFunc(drawData, customState); if (callbacks.initFunc)
{
callbacks.initFunc(drawData);
}
glfwShowWindow(window);
// Upload Fonts // Upload Fonts
{ {
@@ -475,7 +480,10 @@ int startImgui(void* customState, void (*const initFunc)(DrawData&, void*), void
ImVec2 prevWindowSize = getWindowSize(window); ImVec2 prevWindowSize = getWindowSize(window);
drawData.window_size = prevWindowSize; 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) 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 // Cleanup
if (callbacks.cleanupFunc)
{
callbacks.cleanupFunc(drawData);
}
err = vkDeviceWaitIdle(g_Device); err = vkDeviceWaitIdle(g_Device);
check_vk_result(err); check_vk_result(err);
ImGui_ImplVulkan_Shutdown(); ImGui_ImplVulkan_Shutdown();

View File

@@ -1,16 +1,30 @@
#pragma once #pragma once
#include <Windows.h> #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 { class DrawData {
public: public:
GLFWwindow* window = nullptr;
HWND window_handle = nullptr; HWND window_handle = nullptr;
ImVec4 clear_color = {}; ImVec4 clear_color = {};
ImVec2 window_size = {}; 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); ImVec2 getWindowSize(GLFWwindow* window);

View File

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

View File

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

View File

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

View File

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