From ae2214369cde82531247060fe598edc80c94e037 Mon Sep 17 00:00:00 2001 From: Erimel Date: Sun, 20 Oct 2024 19:50:39 -0400 Subject: [PATCH] Show the window when left-clicking tray icon https://github.com/Erimelowo/OpenVR-Dynamic-Resolution/issues/37 --- CMakeLists.txt | 13 +-- README.md | 2 +- resources/ThirdPartyLicenses.txt | 6 +- src/main.cpp | 102 +++++++++-------- src/tray.h | 38 +++++++ src/tray_windows.c | 189 +++++++++++++++++++++++++++++++ 6 files changed, 289 insertions(+), 61 deletions(-) create mode 100644 src/tray.h create mode 100644 src/tray_windows.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 1fb6797..3fa82d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,13 +80,6 @@ CPMAddPackage( add_library(lodepng STATIC "${lodepng_SOURCE_DIR}/lodepng.cpp") target_include_directories(lodepng PUBLIC "${lodepng_SOURCE_DIR}") -IF(WIN32) -CPMAddPackage( - NAME tray - URL https://github.com/Soundux/traypp/archive/698db7d58dd450cc9e30dc12d3bd0c5ca4d6a5b1.zip -) -ENDIF() - set(CMAKE_SKIP_BUILD_RPATH FALSE) set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) set(CMAKE_INSTALL_RPATH $ORIGIN) @@ -107,16 +100,12 @@ if(WIN32) link_directories("${OPENVR_CMAKE_LIBRARY_OUTPUT_DIRECTORY}") endif() if(WIN32) -add_executable("${PROJECT_NAME}" ${GUI_TYPE} "src/main.cpp" "src/pathtools_excerpt.cpp" "src/setup.cpp") +add_executable("${PROJECT_NAME}" ${GUI_TYPE} "src/main.cpp" "src/pathtools_excerpt.cpp" "src/setup.cpp" "src/tray_windows.c") else() add_executable("${PROJECT_NAME}" ${GUI_TYPE} "src/main.cpp" "src/setup.cpp") endif() target_link_libraries("${PROJECT_NAME}" openvr_api fmt::fmt-header-only simpleini imgui lodepng Threads::Threads) -if(TARGET tray) - target_link_libraries("${PROJECT_NAME}" tray) -endif() - target_include_directories("${PROJECT_NAME}" PRIVATE ${CMAKE_CURRENT_BINARY_DIR} PUBLIC "${openvr_SOURCE_DIR}/headers") target_compile_features("${PROJECT_NAME}" PRIVATE cxx_std_17) diff --git a/README.md b/README.md index edea6b8..f969e1b 100644 --- a/README.md +++ b/README.md @@ -104,4 +104,4 @@ We also use: - The [fmt](https://github.com/fmtlib/fmt) library which is available under a Boost-like license. - [DearImGui](https://github.com/ocornut/imgui) which is available under the MIT license. - [lodepng](https://github.com/lvandeve/lodepng/blob/master/LICENSE) which is available under the zlib license. -- [Traypp](https://github.com/Soundux/traypp) which is available under the MIT license. +- A [tray fork](https://github.com/dmikushin/tray) which is available under the MIT license. diff --git a/resources/ThirdPartyLicenses.txt b/resources/ThirdPartyLicenses.txt index 4664b67..078a5d5 100644 --- a/resources/ThirdPartyLicenses.txt +++ b/resources/ThirdPartyLicenses.txt @@ -154,10 +154,10 @@ freely, subject to the following restrictions: 3. This notice may not be removed or altered from any source distribution. ----------- lodepng License ---------- +---------- tray License ---------- MIT License -Copyright (c) 2021 Soundux +Copyright (c) 2017 Serge Zaitsev Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -175,4 +175,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/src/main.cpp b/src/main.cpp index 572e133..8b6f6cf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -34,10 +34,7 @@ #include "lodepng.h" // Tray icon -#if __has_include() -#include -#define TRAY_ICON -#endif +#include "tray.h" #ifndef WIN32 #define HMODULE void * @@ -69,6 +66,10 @@ static constexpr const int mainWindowHeight = 304; static constexpr const float bitsToGB = 1073741824; +GLFWwindow *glfwWindow; + +bool trayQuit = false; + #pragma region Config #pragma region Default settings // Initialization @@ -86,7 +87,7 @@ int maxRes = 250; int resIncreaseThreshold = 80; int resDecreaseThreshold = 88; int resIncreaseMin = 3; -int resDecreaseMin = 6; +int resDecreaseMin = 5; int resIncreaseScale = 140; int resDecreaseScale = 140; float minCpuTimeThreshold = 0.6f; @@ -152,7 +153,7 @@ bool loadSettings() if (dataAverageSamples > 128) dataAverageSamples = 128; // Max stored by OpenVR disabledApps = ini.GetValue("General", "disabledApps", disabledApps.c_str()); - std::replace(disabledApps.begin(), disabledApps.end(), ' ', '\n'); // todo test + std::replace(disabledApps.begin(), disabledApps.end(), ' ', '\n'); disabledAppsSet = multilineStringToSet(disabledApps); // Resolution initialRes = std::stoi(ini.GetValue("Resolution", "initialRes", std::to_string(initialRes).c_str())); @@ -316,11 +317,11 @@ bool shouldAdjustResolution(std::string appKey, bool manualRes, float cpuTime) return !inDashboard && !isCurrentAppDisabled && !manualRes && !(resetOnThreshold && cpuTime < minCpuTimeThreshold); } -void printLine(GLFWwindow *window, std::string text, long duration) +void printLine(std::string text, long duration) { long startTime = getCurrentTimeMillis(); - while (getCurrentTimeMillis() < startTime + duration && !glfwWindowShouldClose(window)) + while (getCurrentTimeMillis() < startTime + duration && !glfwWindowShouldClose(glfwWindow)) { glfwPollEvents(); @@ -346,14 +347,14 @@ void printLine(GLFWwindow *window, std::string text, long duration) ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - glfwSwapBuffers(window); + glfwSwapBuffers(glfwWindow); // Sleep to display the text for a set duration std::this_thread::sleep_for(refreshIntervalBackground); } } -void cleanup(GLFWwindow *window, nvmlLib nvmlLibrary) +void cleanup(nvmlLib nvmlLibrary) { // OpenVR cleanup vr::VR_Shutdown(); @@ -379,7 +380,7 @@ void cleanup(GLFWwindow *window, nvmlLib nvmlLibrary) ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); - glfwDestroyWindow(window); + glfwDestroyWindow(glfwWindow); glfwTerminate(); } @@ -420,10 +421,10 @@ int main(int argc, char *argv[]) glfwWindowHint(GLFW_RESIZABLE, false); // Create window with graphics context - GLFWwindow *window = glfwCreateWindow(mainWindowWidth, mainWindowHeight, fmt::format("OVR Dynamic Resolution {}", version).c_str(), nullptr, nullptr); - if (window == nullptr) + glfwWindow = glfwCreateWindow(mainWindowWidth, mainWindowHeight, fmt::format("OVR Dynamic Resolution {}", version).c_str(), nullptr, nullptr); + if (glfwWindow == nullptr) return 1; - glfwMakeContextCurrent(window); + glfwMakeContextCurrent(glfwWindow); // Setup Dear ImGui context IMGUI_CHECKVERSION(); @@ -438,7 +439,7 @@ int main(int argc, char *argv[]) ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.03, 0.03, 0.03, 1)); // Setup Platform/Renderer backends - ImGui_ImplGlfw_InitForOpenGL(window, true); + ImGui_ImplGlfw_InitForOpenGL(glfwWindow, true); #ifdef __EMSCRIPTEN__ ImGui_ImplGlfw_InstallEmscriptenCallbacks(window, "#canvas"); #endif @@ -450,7 +451,7 @@ int main(int argc, char *argv[]) unsigned test = lodepng_decode32_file(&(icon.pixels), &(iconWidth), &(iconHeight), iconPath); icon.width = (int)iconWidth; icon.height = (int)iconHeight; - glfwSetWindowIcon(window, 1, &icon); + glfwSetWindowIcon(glfwWindow, 1, &icon); #pragma endregion #pragma region VR init @@ -460,12 +461,12 @@ int main(int argc, char *argv[]) if (init_error) { system = nullptr; - printLine(window, VR_GetVRInitErrorAsEnglishDescription(init_error), 6000l); + printLine(VR_GetVRInitErrorAsEnglishDescription(init_error), 6000l); return EXIT_FAILURE; } if (!VRCompositor()) { - printLine(window, "Failed to initialize VR compositor.", 6000l); + printLine("Failed to initialize VR compositor.", 6000l); return EXIT_FAILURE; } #pragma endregion @@ -477,13 +478,13 @@ int main(int argc, char *argv[]) // Set auto-start int autoStartResult = handle_setup(autoStart); if (autoStartResult != 0) - printLine(window, fmt::format("Error toggling auto-start ({}) ", autoStartResult), 6000l); + printLine(fmt::format("Error toggling auto-start ({}) ", autoStartResult), 6000l); // Minimize or hide the window according to config if (minimizeOnStart == 1) // Minimize - glfwIconifyWindow(window); + glfwIconifyWindow(glfwWindow); else if (minimizeOnStart == 2) // Hide - glfwHideWindow(window); + glfwHideWindow(glfwWindow); // Make sure we can set resolution ourselves (Custom instead of Auto) vr::VRSettings()->SetInt32(vr::k_pch_SteamVR_Section, @@ -555,21 +556,32 @@ int main(int argc, char *argv[]) nvmlEnabled = false; } #pragma endregion -#if defined(TRAY_ICON) - // Add tray icon - Tray::Tray tray("OVRDR", "icon.ico"); - // Construct menu - tray.addEntries(Tray::Button("Show", [&] - { glfwShowWindow(window); }), - Tray::Button("Hide", [&] - { glfwHideWindow(window); }), - Tray::Separator(), - Tray::Button("Exit", [&] - { cleanup(window, nvmlLibrary); tray.exit(); return 0; })); - - // Run in a thread - std::thread trayThread(&Tray::Tray::run, &tray); -#endif // TRAY_ICON +#if defined(_WIN32) + const char *hideToggleText = "Hide"; + if (minimizeOnStart == 2) + { + hideToggleText = "Show"; + } + + tray trayInstance = { + "icon.ico", + "OVR Dynamic Resolution", + [](tray *trayInstance) + { trayInstance->menu->text = "Hide"; tray_update(trayInstance); glfwShowWindow(glfwWindow); }, + new tray_menu_item[5]{ + {hideToggleText, 0, 0, [](tray_menu_item *item) + { if (item->text == "Hide") { item->text = "Show"; tray_update(tray_get_instance()); glfwHideWindow(glfwWindow); } + else { item->text = "Hide"; tray_update(tray_get_instance()); glfwShowWindow(glfwWindow); } }}, + {"-", 0, 0, nullptr}, + {"Quit", 0, 0, [](tray_menu_item *item) + { trayQuit = true; }}, + {nullptr, 0, 0, nullptr}}}; + + tray_init(&trayInstance); + + std::thread trayThread([&] + { while(tray_loop(1) == 0); trayQuit = true; }); +#endif // _WIN32 // Initialize loop variables Compositor_FrameTiming *frameTiming = new vr::Compositor_FrameTiming[dataAverageSamples]; @@ -595,7 +607,7 @@ int main(int argc, char *argv[]) bool prevAutoStart = autoStart; // event loop - while (!glfwWindowShouldClose(window) && !openvrQuit) + while (!glfwWindowShouldClose(glfwWindow) && !openvrQuit && !trayQuit) { // Get current time long currentTime = getCurrentTimeMillis(); @@ -828,7 +840,7 @@ int main(int argc, char *argv[]) ImGui::SameLine(0, 10); if (manualRes) { - ImGui::PushItemWidth(172); + ImGui::PushItemWidth(192); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); if (ImGui::SliderInt("", &newRes, 20, 500, "%d", ImGuiSliderFlags_AlwaysClamp)) { @@ -905,7 +917,7 @@ int main(int argc, char *argv[]) if (ImGui::CollapsingHeader("General")) { if (ImGui::InputInt("Resolution change delay ms", &resChangeDelayMs, 100)) - resChangeDelayMs = std::max(resChangeDelayMs, 10); + resChangeDelayMs = std::max(resChangeDelayMs, 100); addTooltip("Delay in milliseconds between resolution changes."); if (ImGui::InputInt("Data average samples.", &dataAverageSamples, 2)) @@ -1043,7 +1055,7 @@ int main(int argc, char *argv[]) ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - glfwSwapBuffers(window); + glfwSwapBuffers(glfwWindow); #pragma endregion // Check if OpenVR is quitting so we can quit alongside it @@ -1060,7 +1072,7 @@ int main(int argc, char *argv[]) // Calculate how long to sleep for depending on if the window is focused or not. std::chrono::milliseconds sleepTime; - if (glfwGetWindowAttrib(window, GLFW_FOCUSED)) + if (glfwGetWindowAttrib(glfwWindow, GLFW_FOCUSED)) sleepTime = refreshIntervalFocused; else sleepTime = refreshIntervalBackground; @@ -1069,11 +1081,11 @@ int main(int argc, char *argv[]) std::this_thread::sleep_for(sleepTime); } - cleanup(window, nvmlLibrary); + cleanup(nvmlLibrary); -#if defined(TRAY_ICON) - tray.exit(); -#endif // TRAY_ICON +#if defined(_WIN32) + tray_exit(); +#endif // _WIN32 return 0; } diff --git a/src/tray.h b/src/tray.h new file mode 100644 index 0000000..ac01f77 --- /dev/null +++ b/src/tray.h @@ -0,0 +1,38 @@ +#ifndef TRAY_H +#define TRAY_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct tray { + const char *icon_filepath; + const char *tooltip; + void (*cb)(struct tray *); // called on left click, leave null to just open menu + struct tray_menu_item *menu; +}; + +struct tray_menu_item { + const char *text; + int disabled; + int checked; + void (*cb)(struct tray_menu_item *); + struct tray_menu_item *submenu; +}; + +struct tray * tray_get_instance(); + +int tray_init(struct tray *tray); + +int tray_loop(int blocking); + +void tray_update(struct tray *tray); + +void tray_exit(void); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* TRAY_H */ diff --git a/src/tray_windows.c b/src/tray_windows.c new file mode 100644 index 0000000..aebdc75 --- /dev/null +++ b/src/tray_windows.c @@ -0,0 +1,189 @@ +#include +#include +#include "tray.h" + +#define WM_TRAY_CALLBACK_MESSAGE (WM_USER + 1) +#define WC_TRAY_CLASS_NAME "TRAY" +#define ID_TRAY_FIRST 1000 + +static struct tray *tray_instance; +static WNDCLASSEX wc; +static NOTIFYICONDATA nid; +static HWND hwnd; +static HMENU hmenu = NULL; +static UINT wm_taskbarcreated; +static BOOL exit_was_called = FALSE; + +static LRESULT CALLBACK _tray_wnd_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam) { + switch (msg) { + case WM_CLOSE: + DestroyWindow(hwnd); + return 0; + case WM_DESTROY: + PostQuitMessage(0); + return 0; + case WM_TRAY_CALLBACK_MESSAGE: + if (lparam == WM_LBUTTONUP && tray_instance->cb != NULL) { + tray_instance->cb(tray_get_instance()); + return 0; + } + if (lparam == WM_LBUTTONUP || lparam == WM_RBUTTONUP) { + POINT p; + GetCursorPos(&p); + SetForegroundWindow(hwnd); + WORD cmd = TrackPopupMenu(hmenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON | + TPM_RETURNCMD | TPM_NONOTIFY, + p.x, p.y, 0, hwnd, NULL); + SendMessage(hwnd, WM_COMMAND, cmd, 0); + return 0; + } + break; + case WM_COMMAND: + if (wparam >= ID_TRAY_FIRST) { + MENUITEMINFO item = { + .cbSize = sizeof(MENUITEMINFO), .fMask = MIIM_ID | MIIM_DATA, + }; + if (GetMenuItemInfo(hmenu, (UINT)wparam, FALSE, &item)) { + struct tray_menu_item *menu = (struct tray_menu_item *)item.dwItemData; + if (menu != NULL && menu->cb != NULL) { + menu->cb(menu); + } + } + return 0; + } + break; + } + + if (msg == wm_taskbarcreated) { + Shell_NotifyIcon(NIM_ADD, &nid); + return 0; + } + + return DefWindowProc(hwnd, msg, wparam, lparam); +} + +static HMENU _tray_menu_item(struct tray_menu_item *m, UINT *id) { + HMENU hmenu = CreatePopupMenu(); + for (; m != NULL && m->text != NULL; m++, (*id)++) { + if (strcmp(m->text, "-") == 0) { + InsertMenu(hmenu, *id, MF_SEPARATOR, TRUE, ""); + } else { + MENUITEMINFO item; + memset(&item, 0, sizeof(item)); + item.cbSize = sizeof(MENUITEMINFO); + item.fMask = MIIM_ID | MIIM_TYPE | MIIM_STATE | MIIM_DATA; + item.fType = 0; + item.fState = 0; + if (m->submenu != NULL) { + item.fMask = item.fMask | MIIM_SUBMENU; + item.hSubMenu = _tray_menu_item(m->submenu, id); + } + if (m->disabled) { + item.fState |= MFS_DISABLED; + } + if (m->checked) { + item.fState |= MFS_CHECKED; + } + item.wID = *id; + item.dwTypeData = (LPSTR)m->text; + item.dwItemData = (ULONG_PTR)m; + + InsertMenuItem(hmenu, *id, TRUE, &item); + } + } + return hmenu; +} + +struct tray * tray_get_instance() { + return tray_instance; +} + +int tray_init(struct tray *tray) { + OutputDebugStringA("Init started"); + wm_taskbarcreated = RegisterWindowMessage("TaskbarCreated"); + OutputDebugStringA("Init 2"); + + memset(&wc, 0, sizeof(wc)); + OutputDebugStringA("Memset done"); + wc.cbSize = sizeof(WNDCLASSEX); + wc.lpfnWndProc = _tray_wnd_proc; + wc.hInstance = GetModuleHandle(NULL); + wc.lpszClassName = WC_TRAY_CLASS_NAME; + if (!RegisterClassEx(&wc)) { + return -1; + } + + hwnd = CreateWindowEx(0, WC_TRAY_CLASS_NAME, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0); + if (hwnd == NULL) { + return -1; + } + UpdateWindow(hwnd); + + memset(&nid, 0, sizeof(nid)); + nid.cbSize = sizeof(NOTIFYICONDATA); + nid.hWnd = hwnd; + nid.uID = 0; + nid.uFlags = NIF_ICON | NIF_MESSAGE; + nid.uCallbackMessage = WM_TRAY_CALLBACK_MESSAGE; + Shell_NotifyIcon(NIM_ADD, &nid); + + tray_update(tray); + return 0; +} + +int tray_loop(int blocking) { + MSG msg; + if (blocking) { + GetMessage(&msg, hwnd, 0, 0); + } else { + PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE); + } + if (msg.message == WM_QUIT) { + return -1; + } + TranslateMessage(&msg); + DispatchMessage(&msg); + return 0; +} + +void tray_update(struct tray *tray) { + HMENU prevmenu = hmenu; + UINT id = ID_TRAY_FIRST; + hmenu = _tray_menu_item(tray->menu, &id); + SendMessage(hwnd, WM_INITMENUPOPUP, (WPARAM)hmenu, 0); + HICON icon; + ExtractIconEx(tray->icon_filepath, 0, NULL, &icon, 1); + if (nid.hIcon) { + DestroyIcon(nid.hIcon); + } + nid.hIcon = icon; + if (tray->tooltip != 0 && strlen(tray->tooltip) > 0) { + strncpy(nid.szTip, tray->tooltip, sizeof(nid.szTip)); + nid.uFlags |= NIF_TIP; + } + Shell_NotifyIcon(NIM_MODIFY, &nid); + + if (prevmenu != NULL) { + DestroyMenu(prevmenu); + } + + tray_instance = tray; +} + +void tray_exit(void) { + if (exit_was_called != FALSE) { + return; + } + exit_was_called = TRUE; + Shell_NotifyIcon(NIM_DELETE, &nid); + if (nid.hIcon != 0) { + DestroyIcon(nid.hIcon); + } + if (hmenu != 0) { + DestroyMenu(hmenu); + } + DestroyWindow(hwnd); + UnregisterClass(WC_TRAY_CLASS_NAME, GetModuleHandle(NULL)); +} +