Compare commits
20 Commits
ff996e8e55
...
1.0.4
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f5e24fa9c | |||
| cdffa1b50b | |||
| eec9e25e12 | |||
| 3b4853acda | |||
| cea13986f0 | |||
| 3641ffdad9 | |||
| 7fa68792fb | |||
| ebdd35b1e1 | |||
| b986bfde39 | |||
| ecfb2f206d | |||
| 1ab7fd1129 | |||
| d5d4521f58 | |||
| 27be3fb0d4 | |||
| df939555b9 | |||
| 4c73dba8ad | |||
| 5c785ac4e7 | |||
| ab2bb6055c | |||
| 4f804dc5c3 | |||
| be949ea8a3 | |||
| b621d1266c |
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
float customYCursor = 0;
|
|
||||||
customYCursor += menuBar(appData).y;
|
|
||||||
|
|
||||||
ImVec2 containingSize = ImGui::GetMainViewport()->Size;
|
// Actual Drawing
|
||||||
|
if (isHidden)
|
||||||
|
{
|
||||||
|
glfwWaitEvents();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float customYCursor = 0;
|
||||||
|
ImVec2 viewportSize = 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;
|
||||||
|
|
||||||
|
if (appData.settings.docked)
|
||||||
{
|
{
|
||||||
drawData.window_size.y = customYCursor;
|
int monitorX, monitorY, monitorW, monitorH;
|
||||||
|
glfwGetMonitorWorkarea(glfwGetPrimaryMonitor(), &monitorX, &monitorY, &monitorW, &monitorH);
|
||||||
|
glfwSetWindowPos(drawData.window, monitorX + monitorW - drawData.window_size.x, monitorY + monitorH - drawData.window_size.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImVec2 menuBar(ApplicationData* appData)
|
void cleanup(DrawData& drawData, ApplicationData& appData)
|
||||||
|
{
|
||||||
|
// Remove tray icon
|
||||||
|
NOTIFYICONDATA nid = { sizeof(nid) };
|
||||||
|
nid.hWnd = drawData.window_handle;
|
||||||
|
nid.uID = TRAY_ID;
|
||||||
|
Shell_NotifyIcon(NIM_DELETE, &nid);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec2 menuBar(DrawData& drawData, ApplicationData& appData)
|
||||||
{
|
{
|
||||||
ImVec2 size{};
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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.;
|
|
||||||
}
|
|
||||||
|
|
||||||
return volume;
|
UINT getMeterValues(IAudioMeterInformation* meterInterface, std::array<float, 2>& outLevels)
|
||||||
|
{
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
170
AsuroTool/AudioNotificationListener.cpp
Normal file
170
AsuroTool/AudioNotificationListener.cpp
Normal 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;
|
||||||
|
}
|
||||||
25
AsuroTool/AudioNotificationListener.h
Normal file
25
AsuroTool/AudioNotificationListener.h
Normal 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
94
AsuroTool/Settings.cpp
Normal 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
15
AsuroTool/Settings.h
Normal 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);
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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:
|
||||||
HWND window_handle = nullptr;
|
GLFWwindow* window = nullptr;
|
||||||
ImVec4 clear_color = {};
|
HWND window_handle = nullptr;
|
||||||
ImVec2 window_size = {};
|
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);
|
ImVec2 getWindowSize(GLFWwindow* window);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user